Set flavor on open workplans from origin/prose/status. Copy existing depends_on aliases only. Do not promote residuals. Assistant: grok Assistant-Session: 01a09dc1-b21e-77e1-919e-fcad2f82b267
186 lines
8.6 KiB
Markdown
186 lines
8.6 KiB
Markdown
---
|
|
id: AUDIT-WP-0010
|
|
type: workplan
|
|
title: "Admit tenant-engine as an attributive sender"
|
|
domain: infotech
|
|
repo: audit-core
|
|
status: active
|
|
flavor: implementation
|
|
owner: claude
|
|
topic_slug: railiance
|
|
created: "2026-08-29"
|
|
updated: "2026-08-29"
|
|
depends_on:
|
|
- AUDIT-WP-0005
|
|
state_hub_workstream_id: "54655357-74da-5f7f-8fa6-647d4c969f21"
|
|
---
|
|
|
|
# AUDIT-WP-0010 — Admit tenant-engine as an attributive sender
|
|
|
|
## Goal
|
|
|
|
Close `AUDIT-IN-0002`. `tenant-engine` emits mutation evidence through a local
|
|
outbox and **cannot land events in production until audit-core admits the
|
|
sender**. This is another repository's production blocked on ours, which is why
|
|
it is a workplan of its own rather than a task inside `AUDIT-WP-0009`.
|
|
|
|
The request is well-formed and arrives with its own bound already declared:
|
|
class **attributive**, drain non-blocking, documented at
|
|
`tenant-engine/docs/evidence-emission.md`. Under §9.6 that is a legitimate
|
|
trade — attributive evidence may deliberately trade away atomicity provided the
|
|
trade is declared and completeness is never claimed. `tenant-engine` declared
|
|
it. Nothing here needs renegotiating; it needs admitting.
|
|
|
|
Intake: `AUDIT-IN-0002` (`01a04d8f-b1c1-746a-8007-15221ebf0a48`), origin
|
|
`TEN-WP-0011-T04`. Onboarding follows INTENT principle 4 — a source is onboarded
|
|
when ownership, retention, access, export, and evidence policy are declared and
|
|
validated, not when events start arriving.
|
|
|
|
## Non-goals
|
|
|
|
- Reclassifying `tenant-engine` as load-bearing. It declared attributive and the
|
|
declaration is the emitter's to make; §9.6 obligations follow the class.
|
|
- Carrying any credential value in this repository, in a work record, in State
|
|
Hub, or in chat. The intake says so and it is right.
|
|
- Blocking on `AUDIT-WP-0009-T03`. The `evidence_kind` field lands there;
|
|
admission does not wait for it, and T04 below records the class in the
|
|
meantime.
|
|
|
|
## Tasks
|
|
|
|
```task
|
|
id: AUDIT-WP-0010-T01
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "e26170e3-78f1-5d98-bb8f-46cef32244bb"
|
|
```
|
|
Done 2026-09-10. `deploy/senders-scope.{json,yaml}`, recorded in
|
|
`docs/tenant-engine-source-registration.md`. `evidence_kind: attributive` with
|
|
the declared `completeness_trade` carried on the identity, so §9.6's
|
|
"declared where the trail is documented" is satisfied on the receiver side and
|
|
not only in the emitter. `tenants: ["*"]` is justified rather than inherited:
|
|
tenant-engine's events carry the *affected* tenant, the set is every tenant
|
|
including ones created later, and an explicit list would fail closed at exactly
|
|
the moment a tenant is provisioned — dead-lettering the creation evidence of
|
|
the tenant whose creation it is. `source` stays pinned exactly, asserted by
|
|
test. Inert until the token exists.
|
|
Register the sender identity: name `tenant-engine`, `sources: [tenant-engine]`,
|
|
`may_write: true`, `may_read: false`, `secret_policy: redact`, tenants scoped as
|
|
the envelope requires. Follow the existing `user-engine` shape in
|
|
`deploy/externalsecret-senders.yaml` and `deploy/senders-scope.yaml`. No token
|
|
value in Git — projected only, per the established lane.
|
|
|
|
```task
|
|
id: AUDIT-WP-0010-T02
|
|
status: todo
|
|
priority: high
|
|
state_hub_task_id: "13855bd9-4ff4-5e8c-ab98-d5ff45103d22"
|
|
```
|
|
Provide the token lane. Coordinate the projected credential with the owning
|
|
credential operator through the `ops-warden` route, matching the custody pattern
|
|
already used for `user-engine`. Verify field presence without disclosure. Do not
|
|
transport a value through State Hub messages.
|
|
|
|
```task
|
|
id: AUDIT-WP-0010-T03
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "1a75fba2-a625-5e87-bba9-e38f010856fc"
|
|
```
|
|
Done 2026-09-10. `audit-core-tenant-engine-ingress` in
|
|
`deploy/networkpolicies.yaml`, namespace AND pod label in one peer. Attributive
|
|
rather than load-bearing changes what may be claimed of the stream, not how
|
|
narrow its reachability should be — a weaker evidence class is not a reason for
|
|
a wider rule, so this follows approval-engine rather than user-engine's older
|
|
namespace-only breadth.
|
|
|
|
The workplan asked that a future third sender fail the test rather than fail
|
|
silently in production. `test_every_sender_has_exactly_one_ingress_rule` does
|
|
that by deriving the expected set from `senders-scope.json`: the policy set and
|
|
the registered sender set can no longer grow independently.
|
|
Extend the NetworkPolicy. `deploy/networkpolicies.yaml` currently admits
|
|
`user-engine`; add `tenant-engine`. `tests/test_networkpolicies.py` exists —
|
|
extend it so the second sender is asserted rather than assumed, and so a future
|
|
third sender fails the test rather than silently failing in production.
|
|
|
|
```task
|
|
id: AUDIT-WP-0010-T04
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "56bde19e-6b54-5d9e-a3d7-8b13e810cb51"
|
|
```
|
|
Done 2026-09-10 — and it found the thing the task existed to find.
|
|
|
|
**tenant-engine cannot deliver a single event today.** `envelope_for` emits a
|
|
shape `normalize()` rejects: five required fields are sent under other names
|
|
(`event_id`/`action`/`resource`/`observed_at`/`details` for
|
|
`id`/`type`/`subject`/`occurred_at`/`data`) and `correlation_id` is absent
|
|
entirely. Verified by running the real envelope through the real `normalize()`,
|
|
which raises `invalid_event`. Every event would 400 and dead-letter.
|
|
|
|
Worse than an ordinary integration bug, because tenant-engine's drain treats
|
|
400 as terminal. The outbox row is marked handled while audit-core holds only a
|
|
dead letter — not chained, not custody. The event is lost on both sides, and
|
|
since the drain is non-blocking and attributive, nothing fails loudly: a silent
|
|
total loss of the stream presenting as a working integration.
|
|
|
|
Taking the correction the intake invited rather than accepting a lossy record:
|
|
`normalize()` is **not** being relaxed to accept the alternate spellings. A
|
|
receiver that guesses which sender key means which stored field would be
|
|
inventing the mapping, and a record whose field meanings the archive chose is
|
|
no longer the sender's assertion. `correlation_id` especially cannot be
|
|
synthesized — an invented one ties an event to an operation audit-core never
|
|
observed.
|
|
|
|
**Root cause is ours.** The accepted envelope was published nowhere a sender
|
|
could read it; `docs/audit-backend-contract.md` describes the stored record, and
|
|
a sender reading it would reasonably infer exactly the names tenant-engine used.
|
|
`schema_version: audit-core.event.v1alpha1` selects nothing here and gave a
|
|
false impression of a negotiated contract. The wire contract is now published:
|
|
`docs/event-envelope.md`.
|
|
|
|
`tests/test_tenant_engine_envelope.py` pins the mismatch, the corrected
|
|
envelope, and the decision not to relax the receiver.
|
|
Validate the envelope against what `tenant-engine` actually sends:
|
|
`schema_version: audit-core.event.v1alpha1`, `tenant` = affected `tenant_id`,
|
|
`action` = domain event type, `resource` = `tenant:<id>`, duplicate event ids
|
|
returning 200. Confirm the duplicate path reconciles rather than minting a
|
|
second link (`test_replay_reconciles_rather_than_duplicating`). If the envelope
|
|
needs a field this engine does not send, reply with the correction the intake
|
|
invited rather than accepting a lossy record.
|
|
|
|
Record the declared class (**attributive**, non-blocking drain, trade declared
|
|
at `tenant-engine/docs/evidence-emission.md`) alongside the sender registration,
|
|
so the §9.6 trade is documented here and not only in the emitter.
|
|
|
|
```task
|
|
id: AUDIT-WP-0010-T05
|
|
status: wait
|
|
priority: medium
|
|
state_hub_task_id: "777bb728-4a4d-5c5b-8b62-24fdb6ab31e3"
|
|
```
|
|
**Waiting**, re-statused 2026-09-10. Blocked on the T04 envelope correction in
|
|
tenant-engine, then on the T02 token. There is nothing to prove end to end
|
|
until an event can be accepted at all; running this now would only reproduce
|
|
the dead-letter path already covered by test.
|
|
Prove it end to end against the production receiver: a real event from
|
|
`tenant-engine` accepted, attributed to the right tenant, redacted per policy,
|
|
and linked into the chain. Record the evidence under `docs/evidence/`. Then
|
|
close `AUDIT-IN-0002` with outcome `promoted` citing this workplan, and reply to
|
|
`tenant-engine`.
|
|
|
|
## Acceptance
|
|
|
|
- `tenant-engine` events land in production, correctly attributed and redacted.
|
|
- The token never appears in Git, a work record, State Hub, or chat.
|
|
- The NetworkPolicy admits exactly the intended senders, asserted by test.
|
|
- The attributive trade is recorded on audit-core's side, and completeness is
|
|
nowhere claimed for this stream.
|
|
- `AUDIT-IN-0002` closed and the requester answered.
|
|
|
|
## Notes
|
|
|
|
`tenant-engine` did this the right way round: it built the outbox, declared the
|
|
class honestly, documented the trade, and asked before assuming it could emit.
|
|
The correct response is to move quickly, not to make it wait behind
|
|
`AUDIT-WP-0009`.
|