Summary
docs/building/by-layer/L1/request-signing.mdx § ## Testing → ### Error codes tells implementers to return 401 with WWW-Authenticate: Signature error="<code>" and then lists seven codes:
missing_signature, invalid_signature, expired_signature,
replayed_nonce, revoked_key, unknown_key, unsupported_algorithm
None of those seven names exists anywhere in the 3.1.1 compliance tree. Every real code is request_signature_*.
This is not a stale count. It is the normative contract being wrong on the primary L1 page that teaches it.
Impact
universal/signed-requests.yaml grades the code byte-for-byte:
Negative vectors MUST produce 401 with WWW-Authenticate: Signature error="<code>", where the <code> matches the vector's expected_outcome.error_code byte-for-byte.
So an implementer who follows this page fails all 28 negative vectors while having correctly implemented what the documentation told them to do — correct 401, correct header, correct rejection reason, wrong string. The docs and the conformance suite describe two different protocols.
The failure mode is expensive precisely because the behaviour is right: nothing in the implementation looks wrong, and the grader gives 28 failures with no hint that the source of the names was the docs.
Verification
Vector codes:
for f in $(git ls-tree -r --name-only v3.1.1 \
dist/compliance/3.1.1/test-vectors/request-signing/negative/); do
git show v3.1.1:$f | jq -r .expected_outcome.error_code
done | sort -u
→ 15 distinct codes across 28 negative vectors, all request_signature_*.
None of the seven documented names appears:
git grep -E "\b(missing_signature|invalid_signature|expired_signature|replayed_nonce|unknown_key|unsupported_algorithm)\b" \
v3.1.1 -- 'dist/compliance/3.1.1/*'
# no output
(revoked_key matches only as a substring of pre_revoked_keyid / revoked_kids, never as an error code.)
What the page should have said:
| Documented |
Actually graded |
missing_signature |
request_signature_required |
invalid_signature |
request_signature_invalid |
expired_signature |
request_signature_window_invalid |
replayed_nonce |
request_signature_replayed |
revoked_key |
request_signature_key_revoked |
unknown_key |
request_signature_key_unknown |
unsupported_algorithm |
request_signature_alg_not_allowed |
The canonical set is larger than seven
docs/building/by-layer/L1/security.mdx carries the authoritative taxonomy in two places — #transport-error-taxonomy (20 rows) and #discovering-an-agents-signing-keys-via-brand_json_url (the discovery-phase codes). Together they define 27 concrete request_signature_* codes, of which the negative vectors currently exercise 15.
Note the transport table also carries two codes that are not request_signature_* — request_target_uri_malformed and request_body_malformed — so "all signing errors are request_signature_*" is not quite a rule.
The protocol error-code enum is not the source here. dist/schemas/3.1.1/enums/error-code.json has 92 values and zero request_signature_*: these are HTTP transport-layer codes carried in WWW-Authenticate, not AdCP protocol error codes. The only schema that names any of them is get-adcp-capabilities-response.json (3 of them, in prose). Schema, vectors, and security.mdx do not disagree — they have different scopes, and security.mdx is the only complete list.
Why no guard caught it
scripts/lint-error-codes.cjs and scripts/lint-error-code-drift.cjs scan static/compliance/source/ and static/schemas/source/enums/error-code.json. Neither scans docs/. No check validates an error code named in prose against anything. That structural gap is #6075; this issue is the instance.
Fix
PR incoming: replace the table with the 15 codes the negative vectors actually grade, worded to match the canonical security.mdx rows, ordered by the verifier checklist, and pointing at both canonical sections for the remaining 12.
Related
Summary
docs/building/by-layer/L1/request-signing.mdx§## Testing→### Error codestells implementers to return401withWWW-Authenticate: Signature error="<code>"and then lists seven codes:None of those seven names exists anywhere in the 3.1.1 compliance tree. Every real code is
request_signature_*.This is not a stale count. It is the normative contract being wrong on the primary L1 page that teaches it.
Impact
universal/signed-requests.yamlgrades the code byte-for-byte:So an implementer who follows this page fails all 28 negative vectors while having correctly implemented what the documentation told them to do — correct 401, correct header, correct rejection reason, wrong string. The docs and the conformance suite describe two different protocols.
The failure mode is expensive precisely because the behaviour is right: nothing in the implementation looks wrong, and the grader gives 28 failures with no hint that the source of the names was the docs.
Verification
Vector codes:
→ 15 distinct codes across 28 negative vectors, all
request_signature_*.None of the seven documented names appears:
(
revoked_keymatches only as a substring ofpre_revoked_keyid/revoked_kids, never as an error code.)What the page should have said:
missing_signaturerequest_signature_requiredinvalid_signaturerequest_signature_invalidexpired_signaturerequest_signature_window_invalidreplayed_noncerequest_signature_replayedrevoked_keyrequest_signature_key_revokedunknown_keyrequest_signature_key_unknownunsupported_algorithmrequest_signature_alg_not_allowedThe canonical set is larger than seven
docs/building/by-layer/L1/security.mdxcarries the authoritative taxonomy in two places —#transport-error-taxonomy(20 rows) and#discovering-an-agents-signing-keys-via-brand_json_url(the discovery-phase codes). Together they define 27 concreterequest_signature_*codes, of which the negative vectors currently exercise 15.Note the transport table also carries two codes that are not
request_signature_*—request_target_uri_malformedandrequest_body_malformed— so "all signing errors arerequest_signature_*" is not quite a rule.The protocol
error-codeenum is not the source here.dist/schemas/3.1.1/enums/error-code.jsonhas 92 values and zerorequest_signature_*: these are HTTP transport-layer codes carried inWWW-Authenticate, not AdCP protocol error codes. The only schema that names any of them isget-adcp-capabilities-response.json(3 of them, in prose). Schema, vectors, andsecurity.mdxdo not disagree — they have different scopes, andsecurity.mdxis the only complete list.Why no guard caught it
scripts/lint-error-codes.cjsandscripts/lint-error-code-drift.cjsscanstatic/compliance/source/andstatic/schemas/source/enums/error-code.json. Neither scansdocs/. No check validates an error code named in prose against anything. That structural gap is #6075; this issue is the instance.Fix
PR incoming: replace the table with the 15 codes the negative vectors actually grade, worded to match the canonical
security.mdxrows, ordered by the verifier checklist, and pointing at both canonical sections for the remaining 12.Related
39/12/27counts in this same file