Assert every refusal the `uri` format checker makes, including a truncated percent escape
Maintainers usually reply within 1 day
@soyeladice-svg is already working on this.
Since Sep 29, 2026.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 57/100
Research direction
Start in internal/validation/validator_test.go at TestURIFormatUsesStrictRFC3986ASCII, then read validateRFC3986URI in internal/validation/validator.go:94 and derive a test row for each refusal. Add direct exact-message assertions alongside the existing pack-level /id diagnostic checks, including the specified accepted query escape. Done means the table catches each requested mutation and the listed Go checks pass; the pull request must also report mutation outcomes and include the required declaration and DCO sign-off.
Written by the indexing model from the issue text.
Description
Goal
Hold each refusal in validateRFC3986URI (internal/validation/validator.go:94) by the message that names its rule. That function is the checker registered for JSON Schema's format: "uri", and every pack's id passes through it. Several of its refusals are held by nothing today. Two of them, the length check in front of an escape's hex digits and the check on url.Parse's error, are all that stands between a malformed pack id and a panic.
Why
func validateRFC3986URI(value any) error {
text, ok := value.(string)
if !ok {
return nil
}
if text == "" {
return errors.New("URI is empty")
}
for index := 0; index < len(text); index++ {
character := text[index]
if character == '%' {
if index+2 >= len(text) || !isHex(text[index+1]) || !isHex(text[index+2]) {
return errors.New("URI contains an invalid percent escape")
}
index += 2
continue
}
if !isRFC3986Character(character) {
return errors.New("URI contains a character outside RFC 3986")
}
}
if strings.Count(text, "#") > 1 {
return errors.New("URI contains more than one fragment delimiter")
}
parsed, err := url.Parse(text)
if err != nil {
return errors.New("URI cannot be parsed")
}
if parsed.Scheme == "" || !uriSchemePattern.MatchString(parsed.Scheme) {
return errors.New("URI is not absolute")
}
return nil
}
CompileSchema registers it as the uri format (validator.go:73), and both bundled pack schemas give the pack's id that format (internal/artifacts/jps/*/schema.json). So every surface that validates a pack runs this function over an author's id: spec validate, packs validate, the MCP validate tool, and the evaluator's own admission of a pack.
Measured on current main (f98d4c9), each relaxation alone passes the whole gate: go vet ./... is clean, go test ./... passes, and go run ./cmd/jpack spec test-conformance --quiet passes.
relaxation (validator.go) |
gate | what a pack id then does |
|---|---|---|
line 105: the whole percent-escape condition → if false { |
green | https://example.com/?q=%, ?q=%Z0 and ?q=%0Z all validate as conforming ids |
line 105: delete index+2 >= len(text) || |
green | https://example.com/% panics: index out of range [21] with length 21 |
line 105: index+2 >= len(text) → index+2 > len(text) |
green | https://example.com/%4 panics the same way |
line 105: delete || !isHex(text[index+1]) |
green | https://example.com/?q=%Z0 validates as a conforming id |
line 105: delete || !isHex(text[index+2]) |
green | https://example.com/?q=%0Z validates as a conforming id |
line 115: if strings.Count(text, "#") > 1 { → if false { |
green | https://example.com/#a#b validates as a conforming id |
line 119: if err != nil { → if err != nil && false { |
green | https://[::1/ panics: nil pointer dereference on parsed.Scheme |
line 99: if text == "" { → if false { |
green | "" is still refused, but as URI is not absolute; only an assertion on the message can see this one |
The panics are not contained. With the first relaxation, jpack spec validate on a pack whose id is https://example.com/% exits 2 with a Go stack trace. The shipping build exits 1 and reports JPS-STRUCTURE-FORMAT-URI at /id. Over MCP, the same document sent to the validate tool takes the whole server process down, so a ping sent after it on the same connection is never answered. Nothing in the runtime calls recover().
Why the query rows matter. url.Parse checks percent escapes in the path and the fragment, but not in the query. Measured against the standard library:
https://example.com/%Z0 url.Parse err=invalid URL escape "%Z0"
https://example.com/?q=%Z0 url.Parse err=<nil>
https://example.com/?q=%0Z url.Parse err=<nil>
So in the path, a relaxed hex check is still refused one step later, as URI cannot be parsed. The pack still gets JPS-STRUCTURE-FORMAT-URI at /id, and a row that asserts only that code cannot tell which layer refused. In the query, nothing else is looking: the loop above is the whole rule.
What holds it today. TestURIFormatUsesStrictRFC3986ASCII (internal/validation/validator_test.go:208) is the only test of this checker. It has one percent row, https://example.com/%ZZ. Both digits there are bad, so deleting either isHex clause leaves the other to refuse it; the escape sits in the path, where url.Parse refuses it anyway; and the escape is not truncated, so neither length relaxation is reached. The test asserts the diagnostic code at /id and never the checker's message. That is why even deleting the whole percent-escape condition leaves the suite green. No test calls validateRFC3986URI directly, and none of its six messages appears in any test file:
$ grep -rn --include='*_test.go' -e 'invalid percent escape' -e 'fragment delimiter' -e 'cannot be parsed' -e 'URI is empty' -e 'outside RFC 3986' -e 'URI is not absolute' .
$ echo $?
1
Scope
- Change
internal/validation/validator_test.goonly. - Derive the rows from the source, not from the table above. Walk
validateRFC3986URIand list everyreturn errors.New(…). For each one, in the pull request, name the row that holds it. The percent-escape condition has three clauses, and its length clause can be broken two ways (deleted, or loosened by one), so it takes four rows: a%with nothing after it, a%with one digit and then the end of the text, a bad first digit, and a bad second digit. - Each row carries exactly one obstacle, and is otherwise a URI the checker accepts. Put the bad-digit escapes in the query, where
url.Parsedoes not check them. In the path, a row passes on broken code because the next layer refuses it for a different reason. - For each row, assert two things:
- Call
validateRFC3986URIdirectly and assert the exact message that names the rule. This is the assertion that does the work: at the pack level, every refusal readsValue does not satisfy the required uri format. - As the existing test does, set the value as the pack's
idand assertJPS-STRUCTURE-FORMAT-URIat/id. That keeps the checker wired to the schema.
- Call
- Include accepted inputs in the same table, at least one well-formed escape in the query with lower-case hex digits beside the existing
urnand path-escape rows. Without them, a checker that refused everything would satisfy the table. - Derive every expected message by running the unmutated code.
- A row that panics fails its test as it should; do not add a
recover. Say in the pull request which mutation checks below fail by panic and which fail by assertion. - Do not change
validator.go, the bundled schemas, or any diagnostic text. If a message reads wrong to you, say so in the pull request.
Acceptance criteria
-
One table in
internal/validation/validator_test.go, which may beTestURIFormatUsesStrictRFC3986ASCIIextended, with one obstacle per row. Each refused row assertsvalidateRFC3986URI's exact message andJPS-STRUCTURE-FORMAT-URIat/id. -
Accepted inputs in the same table, including a query escape with lower-case hex digits.
-
The pull request lists every
return errors.New(…)invalidateRFC3986URInext to the row that holds it. -
Mutation check, one at a time, on
internal/validation/validator.go. Each is reported in the pull request with the row that caught it and whether it failed by assertion or by panic:- (1) delete
index+2 >= len(text) ||from line 105; - (2) change it to
index+2 > len(text); - (3) delete
|| !isHex(text[index+1]); - (4) delete
|| !isHex(text[index+2]); - (5) replace
if strings.Count(text, "#") > 1 {withif false {; - (6) at line 119, replace
if err != nil {withif err != nil && false {.
After each, restore with
git checkout -- internal/validation/validator.goand confirm thatgit status --porcelainprints nothing. - (1) delete
-
env GO111MODULE=on go fmt ./...leavesgit statusclean. -
env GO111MODULE=on go vet ./...is clean. -
env GO111MODULE=on go test ./...passes. -
env GO111MODULE=on go run ./cmd/jpack spec test-conformance --quietpasses. -
env GO111MODULE=on CGO_ENABLED=0 go build -trimpath ./cmd/jpacksucceeds; delete the resulting./jpackbinary and confirmgit statusis clean. -
The pull request carries its one-line
Material-decision impact:declaration (tests only, no material-decision change). -
Every commit includes a DCO sign-off created with
git commit -s.
Contributor learning
A bounds check in front of an index is a refusal like any other, and when it goes nothing refuses anything: the program panics. The test that holds it is the one whose input ends exactly where the index would run past. The other lesson is layers. The standard library's url.Parse also rejects bad escapes, but only in some parts of a URI. A test that puts its bad input where a second layer happens to catch it passes whether or not the first layer works, so a row has to be placed where its own rule is the only one looking, and it has to assert the message that names that rule rather than a code the whole checker shares.
- Dominant language
- Go
- Stars
- 2
- Forks
- 6
- Avg merge
- 3h 32m
- Merged PRs (30d)
- 73
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from Judgment-Pack/judgment-pack-runtime
-
Pin which layer refuses an evaluation-corpus pack pathMay be free again @soyeladice-svg claimed this 18 days ago, and no pull request is open. Opengo good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Judgment-Pack/judgment-pack-runtime#120 · 3 comments ·
Maintainers usually reply within 1 day
-
Pin Sanitize's substitution and what it leaves aloneMay be free again @soyeladice-svg claimed this 18 days ago, and no pull request is open. Opengo good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Judgment-Pack/judgment-pack-runtime#119 · 3 comments ·
Maintainers usually reply within 1 day
-
Boundary-test the decimal comparison operand grammarMay be free again @soyeladice-svg claimed this 18 days ago, and no pull request is open. Opengo good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Judgment-Pack/judgment-pack-runtime#118 · 3 comments ·
Maintainers usually reply within 1 day
-
Add shared offline Policy document tooling after companion adoptionPossibly taken @kikashy claimed this today. Open
Judgment-Pack/judgment-pack-runtime#293 · 1 assignee ·
Maintainers usually reply within 1 day
-
Expose read-only fact-type advice before test expectations are authoredPossibly taken @kikashy claimed this 4 days ago. Open
Judgment-Pack/judgment-pack-runtime#274 · 1 assignee ·
Maintainers usually reply within 1 day
All issues in Judgment-Pack/judgment-pack-runtime
Similar issues
-
documentation good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
Maintainers usually reply within 1 day
-
install-path local lint proofs will exit 5 on roster-less machines once #3263 release is pinnedOpenraised-by:reviewer
Difficulty 1/5 1-3 hours Newbie friendliness 90/100
medici-finance/assay#3333 ·
Maintainers usually reply within 1 day
-
enhancement good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
onurbaydas/VirtoMir#9 ·
-
Add --version flagOpenenhancement good first issue
Difficulty 1/5 Under an hour Newbie friendliness 95/100
syncronix-labs/aegora#12 ·
-
auto-fixable pkg:api
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
tphakala/birdnet-go#4650 · 2 comments · 1 reaction ·
Maintainers usually reply within 1 day