report: record the consumer-asserted reporter source as a closed vocabulary
Implements #26 as agreed: your counter-proposal shape, seeded with the three-way set the consumer confirmed.
The shape:
ReporterSourceclosed enum inaudit.rsbeside the other vocabularies:direct,relay_status_field,relay_message_parse, reservedunrecognised. Out-of-contract input maps toUnrecognised; the submitted string is dropped without being persisted or logged anywhere.- New nullable
auth_events.reporter_sourcecolumn (migration 8).detailkeeps its vault-observed meaning; the new column is consumer-asserted-unverified, doc-commented as such at the struct and in the runbook. - Optional serde-default wire param on
report_auth_failure; absent → NULL, old consumers untouched. - Rider isolation: the fence, stale-vs-latch arm, and rate limiting are unchanged; a superseded-version report carrying a
reporter_sourceis still a silent no-op (asserted). - Runbook subsection with the same exhaustive-match doc pin the other three vocabularies carry — an undocumented variant fails the build.
Verification (all on this tree, gate.sh exit 0):
| mutation | result |
|---|---|
mapping site → raw passthrough (params.reporter_source.as_deref()) | RED: "must never appear in any string column" |
store INSERT → NULL for the column | RED: left: None, right: Some("unrecognised") |
runbook line for relay_message_parse removed | RED: "missing from its documented runbook section" |
| unmutated | GREEN, full gate |
The load-bearing test reports a 40-char lowercase token-shaped string — the exact case that passes ^[a-z0-9_]{1,64}$ — and asserts it appears in no string column of any row, not merely that reporter_source came back mapped. First draft of that assertion checked one column while its message claimed all of them; strengthened before this went up.
Consumer side: anthropic-auth confirmed they'll thread {status, source} out of the relay parse (the arm is known at match time) and assert direct on the direct path, so the seeded set reflects what will actually be reported.
Base is 6094a4e (one behind — 0aa6f79 touches only scripts/check-doc-status.py, no overlap).
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.
Summary by cubic
Records the consumer-asserted source of an auth failure so a relay parse false positive is distinguishable from the direct path and the structured relay field. report_auth_failure now accepts an optional reporter_source with a closed vocabulary (direct, relay_status_field, relay_message_parse, unrecognised), stored in a new nullable auth_events.reporter_source column via migration 8.
- Implements #26: the new column holds consumer-asserted values;
detailkeeps recording only vault-observed values. - Out-of-contract input maps to
unrecognised; the submitted string is dropped, never persisted or logged. - The fence, stale-vs-latch arm, and rate limiting are unchanged; old consumers are unaffected since the absent wire field deserializes to
Noneand writesNULL. - The load-bearing test reports a token-shaped string and asserts it appears in no string column of
auth_events, not merely inreporter_source. - The runbook documents all four values with a build-time pin, so an undocumented variant fails the build.