These workplans exist only in the retired local hub. Their random pre-ADR-007 identifiers are refused by C-06 as stale references, so they cannot be registered. Deriving from the canonical record id takes no identity from anything: central does not hold them and the old ids die with the cache. Records central already holds were deliberately left untouched. Refs CUST-WP-0068-T06 Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
374 lines
17 KiB
Markdown
374 lines
17 KiB
Markdown
---
|
|
id: AUDIT-WP-0006
|
|
type: workplan
|
|
title: "Honest operational custody against ITC-CAP and the live platform backup"
|
|
domain: infotech
|
|
repo: audit-core
|
|
status: finished
|
|
owner: grok
|
|
topic_slug: railiance
|
|
created: "2026-08-15"
|
|
updated: "2026-08-16"
|
|
depends_on:
|
|
- AUDIT-WP-0005
|
|
- RESOURCE-WP-0002
|
|
- ITC-WP-0014
|
|
- ITC-WP-0015
|
|
state_hub_workstream_id: "21e7ccf8-703c-5631-95a2-b5a5743529b2"
|
|
---
|
|
|
|
# AUDIT-WP-0006 — Honest operational custody against ITC-CAP and the live platform backup
|
|
|
|
## Goal
|
|
|
|
Stop claiming a capability the live catalog and the live backup provision
|
|
contradict, and join the spine neighbours already use.
|
|
|
|
After AUDIT-WP-0005 the receiver is in production on railiance01, append-only
|
|
in `platform-pg`, fail-closed on `AUDIT_CORE_REQUIRE_CUSTODY_CLASS=archive`.
|
|
Since then three things landed that change what that claim means:
|
|
|
|
1. **info-tech-canon 0.6.0 / ITC-CAP 0.4.0** names distinct abilities:
|
|
`operations.audit` (attributable historical evidence), `data.archive`
|
|
(long-term retention under lifecycle/integrity/retrieval), `data.backup`
|
|
(recover a store), `governance.evidence`, `governance.lifecycle`.
|
|
Consumption must carry an EvidenceBasis. Maturity attaches to a
|
|
*provision*, not an abstract capability.
|
|
2. **resource-control RESOURCE-WP-0002 is finished.** Off-host Barman to
|
|
Scaleway (`resource:platform:audit-storage`) is live. Restore evidence
|
|
used `audit_core.events` as the checksum. The provision is scored
|
|
`data.backup` D4 against a D5 requirement. The 30-day window is real.
|
|
3. **fin-hub / railiance-master** tightened ownership: booked cost stays
|
|
in fin-hub; usage and inventory stay in resource-control; `rapp.yaml`
|
|
is only valid in a `rapp-*` repo; default exposure is private.
|
|
|
|
audit-core still reports `custody_class=archive`, documents archive as
|
|
"planned WORM + manifests", leaves `retention_days` unset (read as
|
|
indefinite), and keeps a reuse-surface card at A2 / `source module` /
|
|
`helix_forge`. The operator runbook still says production Barman is
|
|
fail-closed. Those statements are now false against the catalog and
|
|
against the platform.
|
|
|
|
This workplan makes the **operating claim** match the **live store and
|
|
the live backup**, and publishes the join artefacts neighbours can
|
|
require the same way they already require `data.backup`.
|
|
|
|
## Why this changes how audit-core should work
|
|
|
|
Not documentation-only. Three operating surfaces currently encode the
|
|
wrong claim:
|
|
|
|
| Surface | Today | After this workplan |
|
|
| --- | --- | --- |
|
|
| `RetentionPolicy.custody_class` + `/readyz` | `archive` | An honest class for durable operational custody. `archive` reserved for a future `data.archive` sink. |
|
|
| `AUDIT_CORE_REQUIRE_CUSTODY_CLASS` | `archive` | The honest production class. Still fail-closed: a development backend cannot serve. |
|
|
| Recovery promise | "no deletion; Barman fail-closed; do not promise an RPO" | Recoverable history = platform `data.backup` window (30 days, cited, EvidenceBasis `measured` from resource-control T05). Unbounded retention is **unknown**, not a fact. |
|
|
|
|
`None` retention is not infinite archive. Canon: unknown carries no
|
|
quantity and must name the gap and owner. The gap owner for off-host
|
|
recovery is rapp-postgres + resource-control. The gap owner for a
|
|
WORM / object-lock archive beyond that window is **this repo**, as an
|
|
unmet `data.archive` requirement — not a silent `retention_days=None`.
|
|
|
|
## Three names that must stay distinct
|
|
|
|
| Name | What it is | Owner |
|
|
| --- | --- | --- |
|
|
| `capability.audit.event-retain` / ITC-CAP `operations.audit` | Product: collect and retain audit events | **audit-core** |
|
|
| `platform:audit-core` | Postgres consumer join key for database `audit_core` | rapp-postgres (`consumers/audit-core.yaml`) |
|
|
| `platform:audit-storage` / `resource:platform:audit-storage` | Scaleway bucket for **all of** `platform-pg` Barman | resource-control + reef-storage |
|
|
|
|
Do not restatement `data.backup` here. Cite the existing capability
|
|
case at `resource-control/data/capability/platform-audit-storage.json`.
|
|
|
|
## Boundaries
|
|
|
|
**This workplan owns**
|
|
|
|
- Honest `RetentionPolicy` and the production fail-closed gate.
|
|
- Recovery language in the runbook, backend contract, and `/readyz`.
|
|
- An ITC-CAP requirement + provision record for `operations.audit`.
|
|
- A Canon Interface Card.
|
|
- Refresh of the reuse-surface card so it no longer says A2 / packaging
|
|
in progress.
|
|
- Persisting the non-secret user-engine sender tenant scope `["*"]`.
|
|
- SCOPE.md current state.
|
|
|
|
**This workplan does not own**
|
|
|
|
- Procuring or operating S3 / Barman / WAL (`resource-control`,
|
|
`rapp-postgres`, `reef-storage`).
|
|
- Booked Scaleway invoices or a second usage stream for
|
|
`platform:audit-storage` (`fin-hub` FIN-WP-0004-T05).
|
|
- Emitting `UsageObservation` or `booked_cost`. Keep the existing
|
|
`platform:audit-core` key on the postgres consumer.
|
|
- A `declarations/rapp.yaml` in this repo. The schema requires
|
|
`rapp_id: ^rapp-…`. Extraction to `rapp-audit-core` is a
|
|
railiance-master first-wave decision, not this file.
|
|
- Public ingest. ClusterIP + default-deny already matches ADR-0008
|
|
private-by-default. Do not add Ingress.
|
|
- Hash-chain / tamper evidence, hot search, object-store archive
|
|
sink, v1 nested envelope, control-plane APIs, OpenBao/K8s
|
|
collectors. Those remain INTENT residuals, not this optimisation.
|
|
- OpenBao AppRole-as-ESO-parent for `database/creds`. Residual from
|
|
AUDIT-WP-0005; different problem.
|
|
|
|
**Founder decision this workplan records, not executes:** if audit
|
|
events must survive beyond the 30-day platform backup window, that is
|
|
a `data.archive` requirement for resource-control to procure (different
|
|
bucket/lifecycle than Barman). T03 writes the requirement as unmet. Do
|
|
not build the sink here.
|
|
|
|
## T01 — Restate custody vocabulary in the running service
|
|
|
|
```task
|
|
id: AUDIT-WP-0006-T01
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "27aa1df9-0758-5c2f-831b-29c41c2805a7"
|
|
```
|
|
|
|
`CustodyClass` is currently `development | archive | hot_search`. The
|
|
backend contract defines `archive` as "long-term evidence (planned)…
|
|
durable object storage, batch manifests". The Postgres backend reports
|
|
`archive` anyway, with `tamper_evidence=False` and `retention_days=None`.
|
|
Production will not start unless the class string is `archive`.
|
|
|
|
That string is now a catalog collision with ITC-CAP `data.archive`.
|
|
|
|
Make `RetentionPolicy` say what the store actually is:
|
|
|
|
1. Add an honest production class (suggested `operational`) meaning:
|
|
durable, append-only against the runtime role, recoverable through
|
|
the platform `data.backup` provision. Keep `archive` in the enum
|
|
for a future object-store backend that can satisfy `data.archive`
|
|
hooks (`retention_policy`, `integrity_verification`, `retrieval_test`).
|
|
2. Postgres reports the honest class. `immutable=True` stays (trigger
|
|
0002). `tamper_evidence=False` stays. Do not inflate.
|
|
3. Add explicit recovery fields so `/readyz` can publish them:
|
|
recoverable window (days), source of that number, EvidenceBasis.
|
|
The 30-day figure is **cited** from the platform provision, basis
|
|
`measured` against resource-control T05 / the 2026-08-13 walk —
|
|
not re-measured here unless the walk is re-run.
|
|
4. Change `AUDIT_CORE_REQUIRE_CUSTODY_CLASS` in
|
|
`deploy/audit-core.yaml` to the honest class. Fail-closed remains:
|
|
a `development` backend still cannot serve. Accept the old `archive`
|
|
value as an alias during one deploy so a mixed rollout cannot
|
|
refuse to start.
|
|
5. Update `docs/audit-backend-contract.md`. Delete "Production archive
|
|
policy (planned)" as if the durable backend did not exist.
|
|
|
|
user-engine does not parse `custody_class`. Still treat `/readyz` as a
|
|
sender-visible contract: keep `custody_class` in the JSON and add the
|
|
new fields rather than removing the old one.
|
|
|
|
Done when: unit tests cover the new class and the alias; production
|
|
manifest requires the honest class; contract and `/readyz` no longer
|
|
call Postgres `data.archive`.
|
|
|
|
Done 2026-08-16: `CustodyClass` includes `operational`; Postgres reports
|
|
it with a 30-day cited recoverable window (`measured`).
|
|
`AUDIT_CORE_REQUIRE_CUSTODY_CLASS=operational` in the manifest; `archive`
|
|
is a one-deploy alias. `/readyz` publishes recovery fields. Contract
|
|
replaced "Production archive policy (planned)" with the live operational
|
|
policy. Suite 84 passed. **Do not apply the Deployment until a new image
|
|
is pinned** — the live image still reports `archive` and has no alias.
|
|
|
|
## T02 — Bind the recovery promise to the live platform backup
|
|
|
|
```task
|
|
id: AUDIT-WP-0006-T02
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "6d01629f-5d3f-5a83-9026-3b208f06dd89"
|
|
```
|
|
|
|
`docs/operator-runbook.md` still says production Barman is fail-closed
|
|
and "do not promise an RPO until that target exists". RESOURCE-WP-0002
|
|
closed that gap on 2026-08-14.
|
|
|
|
Rewrite the Restore section to:
|
|
|
|
- Recoverable history is the platform `data.backup` window (30 days,
|
|
prefix `platform-pg/`, bucket owned by `resource:platform:audit-storage`).
|
|
- Cite, do not copy, `resource-control/data/capability/platform-audit-storage.json`
|
|
and `rapp-postgres/docs/restore.md`.
|
|
- State the provision honesty: requirement D5, provision D4; RPO/RTO
|
|
numbers are theirs (`measured`, single observation). audit-core does
|
|
not claim a better grade.
|
|
- audit-core still does not expire rows. That is a **lifecycle policy
|
|
statement**, not a recovery guarantee. Rows older than the backup
|
|
window are not promised after a restore.
|
|
- Physical restore remains instance-wide; single-consumer path remains
|
|
logical export. Do not invent a second restore procedure.
|
|
|
|
Leave the 2026-08-13 walk in place as historical evidence. Add a short
|
|
dated note that the fail-closed sentence is superseded by
|
|
RESOURCE-WP-0002-T05 (30 events, 65 s full / 65 s PITR).
|
|
|
|
Done when the runbook and the T06 evidence note no longer contradict
|
|
the live backup provision.
|
|
|
|
Done 2026-08-16: Restore section cites `resource:platform:audit-storage`,
|
|
D5 requirement / D4 provision, and supersedes the 2026-08-13 fail-closed
|
|
sentence with RESOURCE-WP-0002-T05. Evidence JSON updated in place.
|
|
|
|
## T03 — Publish an ITC-CAP case for the live provision
|
|
|
|
```task
|
|
id: AUDIT-WP-0006-T03
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "dce899eb-7763-593b-a7d3-953d25f9e9c7"
|
|
```
|
|
|
|
Neighbours already restate real provisions against the live catalog
|
|
(`resource-control/data/capability/platform-audit-storage.json`).
|
|
audit-core's reuse-surface card uses a private id
|
|
(`capability.audit.event-retain`), puts maturity on the capability
|
|
(violates CAP-R2), and still says availability A2.
|
|
|
|
Add a capability record valid against
|
|
`info-tech-canon/infospace/schemas/capability-record.schema.yaml` and
|
|
the live `capabilities.yaml`. Suggested path:
|
|
`data/capability/audit-core-operational.json` (or equivalent under
|
|
`registry/` if a `data/` tree is not wanted — pick one and index it).
|
|
|
|
Minimum content:
|
|
|
|
- **requires**
|
|
- `operations.audit` (profile `administrative` or `security`),
|
|
production dependency.
|
|
- `data.archive` as an **unmet** requirement if INTENT's unbounded
|
|
WORM archive is still wanted. Do not invent a provision for it.
|
|
- Do **not** re-require `data.backup`. Record it under
|
|
`uses_provisions` pointing at the existing platform provision.
|
|
- **provision**
|
|
- provider: the live receiver (landscape id, e.g.
|
|
`audit-core.railiance01`).
|
|
- capability: `operations.audit`.
|
|
- maturity: **D4** (approved production dependency). Not D5:
|
|
integrity is trigger-enforced, not proven (`tamper_evidence=False`);
|
|
reliability is not actively controlled; one replica.
|
|
- `uses_provisions`:
|
|
- platform-pg / CNPG as the transactional store (`depends_on`
|
|
only if the catalog pair exists; otherwise `may_use` and say so).
|
|
- `data.backup` provided by `rapp-postgres/platform-pg CNPG
|
|
barmanObjectStore` (`may_use` or `depends_on` per catalog).
|
|
- OpenBao / ESO as `security.secrets` (`may_use`, never class `P`).
|
|
- `consumes` in native units with EvidenceBasis (CAP-R8, CAP-R10):
|
|
- `S` stored events — `measured` from `pg_total_relation_size` or
|
|
`/v1/stats`, or `unknown` with owner if not yet queried.
|
|
- `H` operator hours — `unknown` (owner: audit-core; start a
|
|
time record later).
|
|
- `I` tokens — `unknown`.
|
|
- Do not invent euros. `invoiced` is a fin-hub fact.
|
|
- `evidence` hooks:
|
|
- `audit_records` → existing failure-matrix / live accept evidence
|
|
(`measured`).
|
|
- `integrity_verification` → gap, owner audit-core (trigger is
|
|
not a proof). Do not borrow the restore drill for this hook.
|
|
|
|
Refresh `registry/capabilities/capability.audit.event-retain.md` and
|
|
`registry/indexes/capabilities.yaml`: map to `operations.audit`, move
|
|
maturity onto the provision, status reflecting production, domain
|
|
`infotech`, consumption mode no longer "source module" only. Do not
|
|
delete the reuse-surface id; join it.
|
|
|
|
Validate with `info-tech-canon capability-review` against the live
|
|
catalog. Gaps are allowed when they name owner and disposition.
|
|
|
|
Done when the record validates and the reuse-surface card no longer
|
|
contradicts the live receiver.
|
|
|
|
Done 2026-08-16: `data/capability/audit-core-operational.json` reviews
|
|
`ok` against ITC-CAP 0.4.0 (`operations.audit` met at D4; `data.archive`
|
|
unprovided). Reuse-surface card joins that provision, domain `infotech`,
|
|
status `production`.
|
|
|
|
## T04 — Publish a Canon Interface Card
|
|
|
|
```task
|
|
id: AUDIT-WP-0006-T04
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "a3d75c33-df62-5017-b54c-ebdc1165e469"
|
|
```
|
|
|
|
ITC-GOV / ITC-SEC / ITC-DATA say subsystems that produce Evidence
|
|
SHOULD publish a Canon Interface Card. Schema requires `id`, `title`,
|
|
`consumer`, `canon_surfaces`. Use the template at
|
|
`info-tech-canon/infospace/agent/templates/canon-interface-card.template.yaml`.
|
|
|
|
The card is how senders and reviewers join this repo to the canon
|
|
without reading INTENT. It is not a second capability catalog.
|
|
|
|
Declare at least:
|
|
|
|
- Produced: Evidence / AuditRecord (stored events). This service is
|
|
**not** an independent Audit-as-assessment.
|
|
- Consumed: ITC-CAP `operations.audit`, ITC-GOV Evidence / EvidenceBasis,
|
|
rapp-postgres as store, resource-control `data.backup` as recovery.
|
|
- Known deviations: no `data.archive` sink; `tamper_evidence=False`;
|
|
no hash-chain; single sender (`user-engine`); no `rapp.yaml` (not a
|
|
`rapp-*` repo); `/readyz` custody class historically overclaimed
|
|
(closed by T01).
|
|
- purpose_fit + gap rule: a gap is acceptable only with owner and
|
|
disposition.
|
|
|
|
Done when the card exists in-repo and validates against
|
|
`interface-card.schema.yaml`.
|
|
|
|
Done 2026-08-16: `docs/interface-card.yaml` validates. Schema wants
|
|
`consumer` as a string and `canon_surfaces` as a string array; richer
|
|
template fields live under `consumer_profile` / `surfaces`.
|
|
|
|
## T05 — Persist sender tenant scope and refresh SCOPE
|
|
|
|
```task
|
|
id: AUDIT-WP-0006-T05
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "f40162fd-37fc-52c1-b5f1-a8b565ffda7d"
|
|
```
|
|
|
|
Inbox 2026-08-13 from net-kingdom (NK-WP-0024): live user-engine sender
|
|
was limited to `tenant:friendly:binky`; six legitimate platform events
|
|
for other tenants returned 400. The live Secret was corrected to
|
|
`tenants: ["*"]` (source `user-engine`, `may_write=true`,
|
|
`may_read=false`). That scope is **not a secret**. Persist it in the
|
|
authoritative senders document / deployment source so an ExternalSecret
|
|
refresh cannot revert it. Do not write token values into Git.
|
|
|
|
`docs/senders.example.json` already shows `["*"]`. Confirm the live
|
|
Secret and the OpenBao KV / wrap-migrate source match. If the
|
|
workstation `~/.audit-core-senders.json` was later tightened for a
|
|
matrix run, do not restore that narrow list.
|
|
|
|
Refresh `SCOPE.md` Current State: production receiver on railiance01,
|
|
Postgres operational custody, recovery bounded by platform 30-day
|
|
backup, AUDIT-WP-0001…0005 finished, this workplan active. Out of
|
|
scope stays: Barman/S3 procurement, booked cost, rapp extraction.
|
|
|
|
Done when the live sender document cannot revert to a single-tenant
|
|
list on refresh, SCOPE matches the repo, and the net-kingdom message
|
|
is answered.
|
|
|
|
Done 2026-08-16: Live Secret already has user-engine `tenants: ["*"]`.
|
|
`deploy/senders-scope.json` + ConfigMap `audit-core-senders-scope`
|
|
applied on railiance01. The process overlays that file over the Secret
|
|
so a later KV refresh cannot shrink tenants. Tokens stay out of Git.
|
|
SCOPE current state updated.
|
|
|
|
## Acceptance
|
|
|
|
- Production fail-closed gate no longer keys off the word `archive`.
|
|
- `/readyz` and the runbook agree with RESOURCE-WP-0002: 30-day
|
|
recoverable window, cited, graded.
|
|
- An ITC-CAP case for `operations.audit` validates; `data.archive` is
|
|
either unmet-with-owner or explicitly not required.
|
|
- Interface card and reuse-surface card no longer describe a draft
|
|
module.
|
|
- user-engine sender tenant scope `["*"]` survives credential refresh.
|
|
- No new cost stream, no `rapp.yaml` in this repo, no public ingest,
|
|
no second backup procedure.
|