fix: expand each expression independently, and reject invalid varspecs

Addresses Copilot's round-5 review on #2035 — eight suppressed comments,
all of them real.

The consequential one overturns behavior I copied from the SDK and
defended in two earlier rounds. RFC 6570 expands each expression
independently; it carries no cross-expression state. The SDK tracks
whether a query expression has already emitted and rewrites a later
`{?two}`'s `?` to `&`. That looks friendlier and is wrong, and its own
matcher says so: for `x{?one}{?two}` it expands to `x?one=1&two=2`, and
`match("x?one=1&two=2")` on that same template returns **null**, while
`match("x?one=1?two=2")` returns both variables. So the rewrite emitted a
URI the advertised template cannot match — #1919 one level up. A server
that wants a continuation advertises `{?one}{&two}`, which expands to `&`
and matches; producing that shape is the server's choice to declare, not
ours to infer.

Also:

- `{id:abc}` was silently treated as `{id}`, and a test codified it. RFC
  6570's max-length is `%x31-39 0*3DIGIT`, so `{id:}`, `{id:0}`,
  `{id:abc}` and `{id:10000}` are invalid templates rather than templates
  with an ignorable modifier — and the SDK's constructor accepts them all,
  so nothing else rejects them. Strict expansion now throws; lenient
  returns the raw template. Discovery stays lenient so the panel still
  renders rather than going blank.

- The TUI could submit `{a,b}` completely blank while the web panel
  blocked the same request: ink-form has no way to express "any one of
  these", so its members are left optional and the modal needs its own
  group check. Added, with tests — it was also below the coverage gate
  without them (functions 76.92%).

- Added the integration test the config never had: it resolves the
  checked-in config and drives all four reads over a real transport, so a
  misspelt preset fails there rather than only when someone runs the repro
  by hand.

- "All three clients" was false. The CLI has no template form and its
  `resources/read` passes the already-expanded `--uri` straight to
  `readResource`, so nothing here runs for it. Corrected in the README,
  AGENTS.md, and the module comment, and dropped a docblock still
  describing the SDK-delegation path removed last round.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-16 21:38:52 -04:00
commit ebc18e1d56

View file

@ -35,10 +35,12 @@ describe("previewUriTemplate", () => {
expect(previewUriTemplate("x://a{?one,two}", {})).toBe("x://a{?one,two}");
});
it("still rewrites the second ? to & when both query expressions resolve", () => {
it("expands each query expression independently, matching the submit", () => {
// Not `?one=1&two=2` -- see the expander's tests: the SDK's own matcher
// rejects that for this template. The preview must show what will be sent.
expect(
previewUriTemplate("x://a{?one}{?two}", { one: "1", two: "2" }),
).toBe("x://a?one=1&two=2");
).toBe("x://a?one=1?two=2");
});
it("restores a deferred expression that follows a resolved query expression", () => {