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
155 lines
6.7 KiB
Markdown
155 lines
6.7 KiB
Markdown
---
|
|
id: TEN-WP-0012
|
|
type: workplan
|
|
title: "Track the two external dispositions tenant-engine is waiting on"
|
|
domain: infotech
|
|
repo: tenant-engine
|
|
status: blocked
|
|
flavor: residual
|
|
owner: claude
|
|
topic_slug: netkingdom
|
|
created: "2026-09-07"
|
|
updated: "2026-09-10"
|
|
depends_on:
|
|
- TEN-WP-0011
|
|
unblocks: []
|
|
origin: residual
|
|
origin_ref: TEN-WP-0011
|
|
state_hub_workstream_id: "cb7387d0-431d-56a2-bb7b-86a024aeeefb"
|
|
---
|
|
|
|
# TEN-WP-0012 — Track the two external dispositions tenant-engine is waiting on
|
|
|
|
`TEN-WP-0011` closed correctly. Its T04 and T06 are `done` in the sense that
|
|
matters here: this repository built everything it owns. What neither task
|
|
could close is the other side — a credential audit-core issues, and a canon
|
|
edit only `net-kingdom` may make.
|
|
|
|
Those two waits have no durable local record. The intakes were filed
|
|
outbound, on other repositories' intake files, so nothing in this checkout
|
|
holds them. `SCOPE.md` mentions both in prose, but prose is not scanned.
|
|
|
|
The cost is small and repeating: session protocol Step 3 scans `workplans/`
|
|
for open work, finds none, and each session re-derives the same two
|
|
externally-owned gaps from `SCOPE.md` and the audit-core source before
|
|
concluding — correctly — that there is nothing to do. This workplan holds
|
|
that conclusion so it is read rather than rediscovered.
|
|
|
|
## Why this is a workplan and not an intake
|
|
|
|
This repository's intake files record work *it* may act on. Both records
|
|
here are the opposite: work filed elsewhere, awaiting someone else's
|
|
disposition. Only `workplans/` is read at session start, and `wait` is the
|
|
convention's status for work that is blocked on another party.
|
|
|
|
## Non-goals
|
|
|
|
- Not editing `net-kingdom` canon from this repository. `TEN-WP-0011-T06`
|
|
settled that: file an intake, never fork the contract.
|
|
- Not requesting a credential over the message bus. Credential routing is
|
|
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 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)
|
|
|
|
```task
|
|
id: TEN-WP-0012-T01
|
|
status: wait
|
|
flavor: residual
|
|
priority: medium
|
|
state_hub_task_id: "7898c1aa-b5de-54cf-87b7-67b727b5d0b2"
|
|
```
|
|
|
|
The emission path shipped in `TEN-WP-0011-T04`: the local outbox is inside
|
|
the mutation transaction, the drain POSTs `/v1/events` and never fails a
|
|
mutation, and the trade is declared in `docs/evidence-emission.md`. What is
|
|
missing is sender registration — a credential audit-core issues and holds
|
|
custody of. `src/tenant_engine/audit_core.py` documents that this module
|
|
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.
|
|
|
|
Do not: request the token by message, or synthesize a local stand-in that
|
|
would make the outbox look drained when it is not.
|
|
|
|
## T02 — boundary-contract amendment (NET-IN-0002)
|
|
|
|
```task
|
|
id: TEN-WP-0012-T02
|
|
status: wait
|
|
flavor: residual
|
|
priority: low
|
|
state_hub_task_id: "f7d27d7c-f599-5d0e-b60b-9c3ad2b27b87"
|
|
```
|
|
|
|
`net-kingdom/canon/standards/tenant-engine-boundary-contract_v0.1.md` still
|
|
calls guardrail/quota policy "reserved, not implemented" and this repository
|
|
"not a policy enforcement point". Guardrails shipped in `TEN-WP-0006`/`0007`,
|
|
and the write path is PEP-shaped without this repository being a PDP, so
|
|
both sentences now mislead a reader who trusts the contract over the code.
|
|
|
|
Waiting on: `NET-IN-0002`, filed on `net-kingdom` by `TEN-WP-0011-T06`.
|
|
|
|
Done when: the contract is amended at its next cut, **or** `net-kingdom`
|
|
declines and the standing divergence is recorded here rather than left as
|
|
a live contradiction between contract and code.
|
|
|
|
## Closing this workplan
|
|
|
|
Close when both tasks reach a disposition, whichever way each falls. If
|
|
either stays open long enough to stop being worth re-reading, `cancel` the
|
|
task and say in `SCOPE.md` that the gap is permanent — an accepted,
|
|
documented divergence beats an indefinite `wait`.
|