Correct the audit-core envelope to the published wire contract
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 43s

audit-core registered our sender (AUDIT-IN-0002) and, reviewing the
emitter, found that no event we sent could ever have been accepted.
envelope_for sent five of the eight required fields under its own names
-- event_id, action, resource, observed_at, details -- and omitted
correlation_id entirely. normalize() rejects that whole, 400.

Our drain treated 400 as terminal, so every event would have been marked
handled here while audit-core held only an unchained dead letter: lost on
both sides, silently, with the integration looking healthy.

- envelope_for emits exactly the eight required fields and none of the
  six audit-core derives. The acting principal moves into `data`, where
  it reads as our claim rather than the archive's finding.
- Thread correlation_id through create / revoke / plan, which had no such
  field. Optional on those three bodies for compatibility; when a caller
  supplies none this engine mints req-<uuid> for the operation it
  performed and returns it. The store mints op-<uuid> as a floor for
  direct callers, written into the local payload so both records agree.
- Send Idempotency-Key equal to the body id.
- A 400 no longer dead-letters. The row stays pending with the reason
  recorded on it: a 400 is an integration defect to fix, not a delivery
  outcome to record.
- wire_envelope upgrades outbox rows written in the old shape at send
  time, and refuses to send one whose correlation cannot be recovered
  from its payload rather than inventing one.

Verified by running all nine event types, through the API, through
audit-core's actual normalize() with a matching SenderIdentity -- all
accepted. A live non-production 202 still needs the token, so
TEN-WP-0012-T01 stays `wait`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DFmHM6fugwfqoobUCp9GiQ

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2106375@bnt-lap001
Assistant-Session: aa26c34d-71e8-4478-a962-c79c74694dc8
This commit is contained in:
tegwick 2026-09-10 18:46:16 +02:00
parent 59c59a1560
commit f91c5323c3
8 changed files with 574 additions and 62 deletions

View file

@ -8,7 +8,7 @@ status: blocked
owner: claude
topic_slug: netkingdom
created: "2026-09-07"
updated: "2026-09-07"
updated: "2026-09-10"
depends_on:
- TEN-WP-0011
unblocks: []
@ -49,8 +49,11 @@ convention's status for work that is blocked on another party.
explicit that `POST /messages/` asking for a token is an anti-pattern;
sender registration is audit-core's to issue.
- Not re-opening `TEN-WP-0011`. Our side of both items shipped.
- No code change is expected from this workplan. If either disposition
arrives and *does* require code, that becomes its own workplan.
- No code change was expected from this workplan. That held until
2026-09-10, when audit-core's disposition on T01 arrived carrying a
blocking defect in our emitter. The fix was small, wholly inside this
repo, and belonged to the wait it unblocks, so it was made here rather
than spun into a workplan of its own. A larger one still would.
## T01 — audit-core sender registration (AUDIT-IN-0002)
@ -70,6 +73,49 @@ holds no audit-core credential beyond that sender token.
Waiting on: `AUDIT-IN-0002`, filed on audit-core.
**2026-09-10 — registered, and a blocking defect found on our side.**
audit-core registered the sender (attributive, `source` pinned to
`tenant-engine`, `may_write`, `secret_policy: redact`, `tenants: ["*"]`
justified per sender) and recorded our declared completeness trade on the
sender identity itself. The token and the operator apply are still theirs
to issue, so this task stays `wait`.
Their review then found that **no event we emitted could ever have been
accepted**. `envelope_for` sent five of the eight required fields under
its own names (`event_id`, `action`, `resource`, `observed_at`,
`details`) and omitted `correlation_id` entirely; `normalize()` rejects
that whole, `400`. Our drain treated `400` as terminal, so every event
would have been marked handled here while audit-core held only an
unchained dead letter — lost on both sides, silently, with the
integration looking healthy. The root cause was on their side: the wire
contract was published nowhere we could read, and
`docs/audit-backend-contract.md` describes the *stored* record, from
which the names we chose were a reasonable inference. It is now published
as audit-core `docs/event-envelope.md`.
Fixed here the same day, ahead of any token:
- `envelope_for` emits exactly the eight required fields and sends none
of the six audit-core derives.
- `correlation_id` is threaded through create / revoke / plan, which had
no such field. It is optional on those three request bodies for
compatibility; when a caller supplies none this engine mints
`req-<uuid>` for the operation it actually performed and returns it.
Never synthesized at the archive, which is what audit-core refuses and
is right to refuse.
- `Idempotency-Key` is sent and equals the body `id`.
- `400` no longer dead-letters. The row stays pending with the rejection
reason recorded on it, per their guidance that a 400 is an integration
defect to fix, not a delivery outcome to record.
- `wire_envelope` upgrades outbox rows written in the old shape at send
time, and refuses to send one whose correlation cannot be recovered
from its payload.
Verified by running all nine event types, through the API, through
audit-core's actual `normalize()` with a matching `SenderIdentity` — all
accepted. That is the check their message asked for, short of a live
non-production `202`, which still needs the token.
Done when: audit-core registers the sender and production mutations land
externally, **or** audit-core declines and `SCOPE.md` row
"Independent audit-core emission" is restated to say so.