Move AUDIT-WP-0008 to ready with dated escalations.
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s

Three additions after review:

Retention position is now conditional on a date. Routing multi-year custody to
data.archive is mechanically right and practically unfunded, so the position
carries a 2026-12-31 review and names P2 as the fallback if no provision is
procured. Also folds in R1 -> R2, which needs only an explicit
backupRetentionDays: 30 and a published erasure horizon.

The erasure exemption names its legal dependency. Declining to build erasure
rests on audit evidence being exempt under legal obligation or legitimate
interest; that is a legal conclusion nobody in the estate is recorded as having
reached, and T03 routes it rather than absorbing it.

T04 now runs before T01 so the published vector reads E2 on both paths from its
first commit. T02 still runs first and corrects the E2 guess by message, since
that guess is live in canon and wrong today.

Framework finding 1 amended to declare per path, quote the minimum. E3 target
carries 2027-03-31.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-17 21:56:37 +02:00
parent 94051bd1b6
commit 05991a4cc8

View file

@ -4,13 +4,14 @@ type: workplan
title: "Tenancy posture declaration and read-path enforcement"
domain: infotech
repo: audit-core
status: proposed
status: ready
owner: claude
topic_slug: railiance
created: "2026-08-17"
updated: "2026-08-17"
depends_on:
- AUDIT-WP-0007
state_hub_workstream_id: "84c01552-4c65-45fd-bf6c-7daab0ecc9b6"
---
# AUDIT-WP-0008 — Tenancy posture declaration and read-path enforcement
@ -87,6 +88,26 @@ Consequences we accept and want stated back:
Ask of `rapp-postgres`: amend the ADR-0002 opening context to point at archive
rather than at backup retention, so the two statements agree.
**This position is conditional, and the condition is dated.** Routing multi-year
custody to `data.archive` is correct mechanically and unfunded practically:
the capability case records it as unmet, with no booked cost and no procurement
owner in motion. Taking this position without a date would read as "audit-core
is fine", and audit-core is not fine — it is correctly routed and unresourced.
Escalation: **reviewed 2026-12-31.** If no `data.archive` provision is procured
by then, audit-core reopens placement. §4.5 makes retention a placement trigger,
and P2 — a dedicated cluster with an independent window — becomes the fallback
worth arguing for. P2 still does not deliver WORM, so it is a worse answer than
archive; it is named here so the fallback exists rather than being improvised
under pressure. Recorded now so the review has something to fire against.
**Also taken now: R1 → R2.** R2 needs only that retention be declared per
dataset rather than inherited silently, and that the erasure horizon be
published. That is an explicit `backupRetentionDays: 30` in the consumer
declaration plus the horizon rendered in our operator surface — a change of
hours, not a future target. Deferring it as a "target" would have been the
framework's own §8.1 failure: a value in a file nobody re-reads.
### On erasure versus audit (their question 11)
Yes to the fact/payload split — retain the fact of an event, encrypt its
@ -119,6 +140,23 @@ Our position: audit-core targets **R2**, not R4. Verified erasure of audit
evidence is a design tension we record rather than resolve, and a "R4
everywhere" fleet target must exempt this service explicitly.
**The exemption rests on a legal basis nobody in this estate has confirmed, and
that must be said out loud rather than assumed.** Declining to build erasure
means that if an Article 17 request arrives naming a data subject in the audit
trail, audit-core has no mechanism and must rely on audit evidence being
exempt — legal obligation, or legitimate interest in fraud and security
investigation. Those grounds are ordinary and widely relied on. They are also a
legal conclusion, and this repo is not competent to reach one. Nothing in
INTENT, SCOPE or the capability case records anyone having reached it either.
This workplan therefore **routes the question rather than absorbing it**: the
position is "we do not build erasure, on a legal basis we have named and not
validated", and the validation is somebody else's to own. An unnamed dependency
here is the difference between a recorded tension and an unnoticed liability —
and an audit service discovering in the middle of a regulator's question that
its exemption was assumed is the specific failure worth spending a paragraph to
avoid.
## Framework review findings
Sent under §20.2 — the framework is validated by whether it can describe us.
@ -126,10 +164,19 @@ Sent under §20.2 — the framework is validated by whether it can describe us.
1. **The E ladder cannot express asymmetric enforcement.** Our write path is E2
and our read path is E1. §4.3 assumes one level per service. This will recur
estate-wide, since most services enforce harder on write than on read, and
with no rule everyone will declare their best path. Recommend the vector take
the **minimum across paths**, stated explicitly — the reading consistent with
§6. Per-path declaration (`E: {write: 2, read: 1}`) is the richer option but
invites the same selective quoting downstream.
with no rule everyone will declare their best path.
Neither obvious fix is right alone. A bare **minimum across paths** is safe
to publish but destroys information: a service at E3-write/E1-read declares
identically to one at E1/E1, though the first is materially further along.
Bare **per-path declaration** (`E: {write: 3, read: 1}`) keeps the signal and
invites exactly the selective quoting §6 exists to stop — "our write path is
E3" is a sentence someone will say.
Recommend both, with the relationship stated: **declare per path, quote the
minimum.** The posture vector's `E` is the minimum across paths; the per-path
breakdown sits beside it and is where improvement is tracked. One sentence in
§4.3, and it is the only version that keeps honesty and signal at once.
2. **§13.1 collides with the ladder floor.** "A level is claimed only with its
evidence artifact present", but §13's table has no artifact for I0, I1, A0,
@ -165,48 +212,100 @@ defect in our code. That is the framework working.
contract and it is the right target, but it is blocked behind reaching E2
(§4.3) and needs the `EXPLAIN` comparison first. Declared as target with a
recorded gap in T01, not built here.
**The target carries a date: 2027-03-31.** The framework says services whose
cross-tenant exposure would be a reportable breach SHOULD target E3, and no
service on `platform-pg` fits that description better than this one. Every
quarter spent at E2-with-a-target is a quarter of stated, unclosed risk, and
an undated target is what §8.1 calls documentation rather than control. If the
date slips it slips as a decision, which is the whole point of §12.
- **No per-subject encryption is built.** T03 records a position; it does not
commit to an implementation.
- **No adversarial evidence facility is built here.** §19.3 gave that a home in
`whitehat-security`. T05 coordinates, it does not volunteer.
## Sequencing
**T04 runs before T01.** Task ids are hub-registered and not renumbered; the
execution order is T02 → T04 → T01 → T03 → T05 → T06.
The reason is narrow. A posture vector published in the repo is a canon-shaped
artifact that outlives the fortnight it takes to fix the read path, and `E1` on
an audit service is a sentence that will be quoted back long after it stopped
being true. Fixing first means the declaration is `E2` on both paths from its
first commit, and the E1 interval never enters the permanent record.
This is emphatically **not** withholding the finding. T02 goes first precisely
so the correction reaches `rapp-postgres` immediately: their guess of `E2` is
live in canon *now*, and it is wrong now. A message correcting the record is the
fast instrument; a published vector is the durable one. Deferring the durable
artifact by two weeks while the fast one goes out today is sequencing. Delaying
both would be concealment, and §6 would be right to call it that.
## Tasks
```task
id: AUDIT-WP-0008-T01
status: todo
priority: high
state_hub_task_id: "65f3109a-fa98-459a-9e59-d7ac211bd2bc"
```
Publish the posture vector in the repo per Decision 5.1 — `docs/tenancy-posture.yaml`,
declaring `current: I1 A2 E1 P1 R1`, `target: I1 A2 E3 P1 R2`, reviewed date, and a
gap note per axis. The E gap names the read path and cites T04; the R gap names
`data.archive` as the real requirement. Cross-link from `SCOPE.md` and
`docs/interface-card.yaml`.
Publish the posture vector in the repo per Decision 5.1 — `docs/tenancy-posture.yaml`.
**Runs after T04.** Declares `current: I1 A2 E2 P1 R2`, `target: I1 A2 E3 P1 R2`,
reviewed date, and a gap note per axis. Include the per-path E breakdown
(`write: 2, read: 2`) alongside the vector, per finding 1. The E gap names RLS
with its 2027-03-31 date; the R gap names `data.archive` with its 2026-12-31
review. If T04 has not landed when this is published, declare `E1` and say why —
the vector describes what is true on the day it is written, never what is
expected. Cross-link from `SCOPE.md` and `docs/interface-card.yaml`.
Fold in the R1 → R2 move: explicit `backupRetentionDays: 30` requested of
`rapp-postgres` for `consumers/audit-core.yaml` (that file is theirs, so this is
a request, not an edit), and the erasure horizon rendered in the operator
surface alongside `recoverable_days`.
```task
id: AUDIT-WP-0008-T02
status: todo
priority: high
state_hub_task_id: "224a12c0-d646-4c7f-9215-120e1ede4691"
```
Reply to `rapp-postgres` message `56b77e20-0152-4e09-8f76-99640269063b` with the
retention position: ceiling stays at 365, audit-core declares nothing above the
30-day default, multi-year routes to `data.archive`, service class `batch`
confirmed. Ask for the ADR-0002 context amendment. Mark the message read on send.
Reply to `rapp-postgres` message `56b77e20-0152-4e09-8f76-99640269063b`.
**Runs first.** Carries four things: the retention position (ceiling stays 365,
audit-core declares nothing above the 30-day default, multi-year routes to
`data.archive`) with the **2026-12-31 escalation date and the P2 fallback named**;
the request for an explicit `backupRetentionDays: 30` in `consumers/audit-core.yaml`
to take us to R2; the ADR-0002 context amendment; and `batch` confirmed.
It also **corrects their posture guess immediately**`I1 A2 E1 P1 R1`, not
`I2 A3 E2 P1 R1`, with the read-path defect stated plainly and T04 named as the
fix in flight. Their `E2` guess is live in canon and wrong today; the correction
does not wait on T01. Mark the message read on send.
```task
id: AUDIT-WP-0008-T03
status: todo
priority: high
state_hub_task_id: "0003954e-5033-457f-b9aa-97efdefe9beb"
```
Answer question 11 in `docs/erasure-and-audit.md`: the fact/payload split, the
encrypt-then-hash constraint, the confirmation-oracle finding, and the R2-not-R4
target. Record as an audit-core position; send the §4.5 condition to NetKingdom
as a framework amendment.
**Name the legal dependency explicitly and route it.** The document must state
that declining to build erasure rests on audit evidence being exempt under legal
obligation or legitimate interest, that this is a legal conclusion, and that no
one in the estate is recorded as having reached it. Route that question to
`the-custodian` for an owner rather than closing it here — audit-core is not
competent to answer it and should not appear to have. The position is publishable
with the dependency open; it is not publishable with the dependency hidden.
```task
id: AUDIT-WP-0008-T04
status: todo
priority: high
state_hub_task_id: "e4440db3-6eb0-49f6-9aa9-930db8be9d18"
```
Bind the read path to permitted tenants. `permits_tenant` governs read as it
governs write, at the same choke point: tenant-scope `/v1/events`,
@ -219,6 +318,7 @@ and the operator runbook. Raises E to 2 on both paths; update T01's declaration.
id: AUDIT-WP-0008-T05
status: todo
priority: medium
state_hub_task_id: "30b56b30-e7eb-4873-aa7c-f4a3bf2fcb24"
```
Produce the §13 E2 evidence artifact — adversarial, with a review date: an
identity bound to tenant A demonstrably cannot read tenant B. Coordinate with
@ -230,6 +330,7 @@ per §19.3.
id: AUDIT-WP-0008-T06
status: todo
priority: medium
state_hub_task_id: "ed58ca37-755b-4284-b1ac-2a02baaceb21"
```
Send the five framework review findings to `net-kingdom` (owner of the canon
path) and `policy-nexus` (owner of the rendered copy), as a §20.2 review
@ -237,8 +338,13 @@ response rather than as objections.
## Done when
- `docs/tenancy-posture.yaml` declares an accurate vector with targets and gaps.
- `docs/tenancy-posture.yaml` declares an accurate vector with targets, per-path
E breakdown, and dated gaps.
- Read path enforces tenant scope, with a test that fails if it regresses.
- `rapp-postgres` has the retention answer and the question 11 answer.
- `rapp-postgres` has the retention answer, the corrected posture guess, and the
question 11 answer.
- NetKingdom has the five framework findings.
- The §5 worked example for `audit-core` is corrected upstream.
- Two dates are recorded and monitored per §8.1 rather than merely written:
`data.archive` review 2026-12-31, E3 target 2027-03-31.
- The erasure legal question has an owner outside audit-core.