Skip to content

fix(tmproto): accept optional $schema/format_kind, relax wire-ID charset, fix verifier error envelope - #509

Merged
ohalushchak-exadel merged 3 commits into
mainfrom
ohalushchak-exadel/tmproto-decode-strict-echo-charset-error-envelope
Sep 8, 2026
Merged

fix(tmproto): accept optional $schema/format_kind, relax wire-ID charset, fix verifier error envelope#509
ohalushchak-exadel merged 3 commits into
mainfrom
ohalushchak-exadel/tmproto-decode-strict-echo-charset-error-envelope

Conversation

@ohalushchak-exadel

Copy link
Copy Markdown
Collaborator

Summary

Three related Trusted Match Protocol conformance fixes, shipped together as one tmproto module change so both agents pick them up via a single pin bump.

Accept schema-valid $schema and format_kind. The context-match and identity-match request schemas declare an optional top-level $schema, and Artifact declares format_kind with additionalProperties: true. The generator previously skipped $schema, and the hand-written Artifact struct had no FormatKind, so any schema-legal request carrying either was 400'd by DisallowUnknownFields() in the verifier middleware. Both are now known fields on the Go structs. DisallowUnknownFields stays on — the verifier reconstructs the signing input from the parsed struct, so silently dropping unknown fields would let a future extension produce a signature the verifier could never reproduce.

Relax the wire-ID charset to match the schema. validateSafeID had rejected : / \ on request_id, placement_id, package_id, and property_id. The TMP schemas place no charset constraint on those, and real callers use them: urn:uuid:… request ids, homepage/atf placement slugs, tenant-prefixed package ids like acme:q1. Split into a new validateEchoID (length + C0/DEL rejection only) for the four wire IDs. validateSafeID stays on property_rid — that one flows into store keys and is UUID-shaped by spec anyway. SafeRequestIDForEcho now uses the relaxed rule too, so a urn:uuid: request_id can be echoed on error responses and structured logs (control bytes still stripped).

Fix the verifier error envelope. writeVerifierError was emitting {"type":"","request_id":"","code":…}, violating error.json which requires type == "error" and echoes request_id. It now emits the correct discriminator and echoes the request_id through SafeRequestIDForEcho. Pre-parse failures still pass an empty request_id (nothing to echo yet); post-parse failures echo the parsed one.

The second commit is an unrelated go fix ./... autofix (slices.Backward loop migration) that the tooling gate surfaced. Kept in-PR so the working tree stays clean.

Release order

After merge:

  1. Cut a new tmproto submodule tag.
  2. Bump the pin in cmd/context-agent, cmd/identity-agent, cmd/router, e2e, and targeting.

Two router-side follow-ups (no-follow-redirect on fan-out clients; complete asset-credential stripping for the unknown-asset variant and signed-URL fields) will stack on this branch as a separate PR.

Test plan

  • go test ./... in tmproto, targeting, and root (router) — all green
  • CI matrix green after push
  • Manual sanity: a request carrying $schema at the top level no longer 400s
  • Manual sanity: a request with request_id: "urn:uuid:..." no longer 400s
  • Manual sanity: a verifier-rejected response body includes "type":"error" and echoes request_id

ohalushchak-exadel and others added 2 commits September 8, 2026 19:22
… charset, fix verifier error envelope

Three related Trusted Match Protocol conformance fixes shipped together
so both agents pick them up via one pin bump.

Accept schema-valid $schema and format_kind. The context-match and
identity-match request schemas declare an optional top-level $schema
and Artifact declares format_kind with additionalProperties: true. The
generator previously skipped $schema and the hand-written Artifact
had no FormatKind, so any schema-legal request carrying either was
400'd by DisallowUnknownFields() in the verifier middleware. Both are
now known fields; the strict-decode guarantee for signature
reconstruction stays.

Relax wire-ID charset to match the schema. validateSafeID had
rejected ':' '/' '\\' on request_id, placement_id, package_id, and
property_id. The TMP schemas place no charset constraint on those,
and real callers use them (urn:uuid:... request ids, homepage/atf
placement slugs, tenant-prefixed package ids like acme:q1). Split
into a new validateEchoID (length + C0/DEL rejection only) for the
four wire IDs. validateSafeID stays on property_rid — that one flows
into store keys and is UUID-shaped by spec.

Fix verifier error envelope. writeVerifierError was emitting
{"type":"","request_id":"","code":...}, violating error.json (which
requires type == "error" and echoes request_id). It now emits the
correct discriminator and echoes the request_id through
SafeRequestIDForEcho. Pre-parse failures still pass an empty
request_id, since there is nothing to echo.

Tests in router, contextagent, and identityagent that pinned the old
strict-charset behavior now trigger via property_rid (still strict)
or a DEL byte in the request_id.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaced by `go fix ./...` while running the tooling gate on the
tmproto conformance changes. Independent of that work; bundled to
keep the working tree clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread tmproto/verify_middleware.go
Comment thread tmproto/artifact.go
aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Sep 8, 2026

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — TMP conformance fixes with two non-blocking test-coverage gaps.

What I checked:

  • Charset relaxation via validateEchoID is safe: relaxed wire IDs never reach a separator-interpreting sink (context cache uses NUL, property_rid stays strict), and control-byte/log-injection protection plus signing semantics are preserved.
  • Accepting optional $schema/format_kind and the verifier error-envelope fix preserve wire-shape fidelity and TMP signing/verification semantics — no signature/replay/nonce/canonicalization weakening.
  • No hand-edits to generated types requiring regen; tmproto/types_gen.go change is consistent with the schema source.

Medium findings (non-blocking):

  • Missing test coverage for $schema/format_kind acceptance (headline behavior; manual test-plan checkbox unchecked).
  • Missing test coverage for the corrected error-envelope shape.

Two medium findings fall through to approve (row 9). high_risk is true only via (modified) test/impl files, but with no medium finding on a specific modified sensitive file tied to a risk (findings list is empty), row 5 does not fire. gated_paths is false; no author-team gate; no deletions.

Cover the two behaviors this branch's tmproto changes ship:

* Error envelope: on both post-parse (bad signature) and pre-parse
  (invalid JSON) verifier rejections, assert the response body has
  type == "error" and, when a request_id was parsed, echoes it back;
  empty otherwise. Prevents a future refactor from silently dropping
  either field and re-violating the error.json contract.

* Strict decode: feed decodeStrict a context-match request carrying a
  top-level $schema and an artifact with format_kind, and an
  identity-match request carrying $schema. Both must decode without
  error. Prevents a regen that re-skips $schema in the generator, or
  a hand-edit removing FormatKind, from silently reintroducing a 400
  on schema-valid traffic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approval: no reviewable delta since the prior approval; re-submitting after stale-review dismissal.

@ohalushchak-exadel
ohalushchak-exadel merged commit 966b12b into main Sep 8, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant