Align to Security Layer Model v0.7; revise SCOPE; raise AUDIT-WP-0009
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

The standard is accepted at v0.7 and all three of audit-core's v0.6
findings landed in it (§9.6 threat decomposition, cadence MUST for
load-bearing sources with reconciliation/heartbeat for low-volume
classes, §3.3's Evidence row restated as an estate trade).

INTENT.md: layer/role declared in frontmatter as §11 and companion §2
require — layer.yaml alone did not discharge it. Layer section rewritten
for the Evidence role and its obligations. New Evidence Bound section
carrying the §9.6 sound/unsound forms and the three-row threat table,
including the residual nothing in the model prevents.

SCOPE.md: replaced the statehub register stub, which carried no boundary
at all. Statute-fixed prohibitions now live here, separated from the
merely-not-yet — §16 ruled the stronger-custody gap closed, so WORM and
data.archive are not ours rather than not yet.

Assessment found nine gaps. Headline: postgres_backend returns
tamper_evidence=True unconditionally while docs/integrity.md permits it
only against a live external attestation, and the one on record is
2026-08-16 with no job renewing it — audit-core overclaiming its own
bound, the §9.6 defect turned inward. Also: no cadence, heartbeat,
reconciliation, or load-bearing classification exists, so the obligation
audit-core argued up from SHOULD to MUST is not yet dischargeable
against audit-core.

AUDIT-WP-0009 raised, ten tasks.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4040362@bnt-lap001
Assistant-Session: 4fd0fd24-2ee8-4413-bd67-43bd79ca73f1
This commit is contained in:
tegwick 2026-08-29 14:42:51 +02:00
parent 4debbd834f
commit 6eaa33699d
5 changed files with 567 additions and 38 deletions

View file

@ -1,3 +1,15 @@
---
# NetKingdom Security Layer Model v0.7 (accepted) — §11 / companion §2.
# Declared by audit-core, in audit-core's own voice. Machine-readable form
# and the full conformance map: layer.yaml
layer: Engine
role: Evidence
standard: canon/standards/security-layer-model_v0.7.md
standard_version: "0.7"
declared_by: intakes/intakes.md AUDIT-IN-0001
declared_at: "2026-08-29"
---
# Audit Core Intent
## Purpose
@ -19,15 +31,31 @@ on NetKingdom to exist or operate.
## Layer
Audit Core is **Engine layer** under the NetKingdom security layer model
(`net-kingdom/canon/standards/security-layer-model_v0.3.md`): it exposes a
deterministic API contract and holds operational custody of the records it
accepts.
Audit Core is **Engine layer, role Evidence** under the NetKingdom Security
Layer Model v0.7 (`net-kingdom/canon/standards/security-layer-model_v0.7.md`,
accepted; working form `net-kingdom/SECURITY-COMPANION.md`). It exposes a
deterministic API contract over a modeled concept — the audit event — and holds
operational custody of what it accepts. Same authoritative input, same result.
It is explicitly **not a decision point**. Audit Core answers what was recorded;
it never answers whether an action is permitted, and it exposes no surface that
returns such a verdict. See `## Out Of Scope Initially` and
`history/2026-08-28-approval-evidence-assent.md`.
**Evidence is a role, and it carries obligations the other engine roles do not:**
- Audit Core is explicitly **not a decision point** (§6). It answers what was
recorded; it never answers whether an action is permitted, and exposes no
surface returning such a verdict.
- An Audit Core outage **must not block the operation being recorded** (§3.3).
v0.7 states this as a deliberate estate trade — availability of the recorded
operation over independence of its recorder — not as a property of evidence
engines. The consequence is accepted knowingly: a compromised source can
suppress, and detection rather than prevention is the answer (§9.6).
An operation whose control requirement is *independent recording before
effect* remains raisable as a declared exception; the estate has not needed
one, and Audit Core would want it raised rather than assumed.
- What Audit Core may **never claim** is fixed by §9.6 and restated in
`## Evidence Bound` below.
Machine-readable declaration and the conformance map: `layer.yaml`.
Reviews: `history/2026-08-28-approval-evidence-assent.md`,
`history/2026-08-29-security-layer-model-v0.6-review.md`.
## Problem
@ -242,6 +270,54 @@ NetKingdom must not be required for Audit Core's internal authorization model.
Audit Core may accept NetKingdom OIDC claims when configured, but should also
support a generic OIDC provider and local development auth mode.
## Evidence Bound
Security Layer Model v0.7 §9.6 fixes what Audit Core may claim, and the bound is
narrower than principle 6 aspires to. `docs/integrity.md` is authoritative on the
delivered guarantee; this section states the doctrine it must never exceed.
**Sound:** *the archive proves the records it holds were not altered or truncated
after arrival.*
**Unsound, and Audit Core must never say or imply either:**
- *"the audit record proves it happened"* — the archive proves nothing about an
event that was never sent;
- *"there is no record, so it did not happen"* — absence of a record is not
evidence of non-occurrence, and no control may read it as such.
Which control covers which threat:
| Threat | Covered by | When |
| --- | --- | --- |
| Accidental omission — process dies between mutation and emit | atomic emission via a **local** outbox at the source (§9.4) | prevented |
| Adversarial omission — a compromised source declines to insert, deletes before drain, or drains to nowhere | cadence and reconciliation | detected, after the fact |
| Adversarial omission at a compromised source | — | **nothing in the model prevents it** |
That third row is a known, accepted residual. Audit Core raised it against a
remedy it had itself proposed, and states it here so no consumer plans around a
guarantee that does not exist.
**Load-bearing versus attributive.** Where a control's soundness depends on an
event being present or absent — a revocation, a denial, a containment action —
the evidence is **load-bearing**: emission MUST be atomic with the state change
at the source, and the source **MUST** declare an expected emission cadence.
Otherwise it is **attributive**: atomicity SHOULD be sought, and where it is
deliberately traded away the trade MUST be declared and completeness MUST NOT be
claimed.
**Rate monitoring is the wrong form for rare events**, which is exactly where the
stakes are highest — the most valuable event to suppress is the negative one, and
revocations and denials are infrequent by nature. For **low-volume load-bearing
classes** the required form is **positive reconciliation or a heartbeat**:
compare the source's own state transitions against Audit Core's event count per
class, or assert *nothing to report* as a signed positive claim that can itself
go missing. Rate monitoring never produces a claim that can be missing.
Supporting these obligations is Audit Core's work, not only its senders' — a
source cannot declare a cadence to a system that has nowhere to put it. See
`workplans/AUDIT-WP-0009-evidence-role-conformance.md`.
## Approval Evidence
Audit Core carries the **evidence half** of approvals as a distinct source, per

115
SCOPE.md
View file

@ -1,50 +1,115 @@
# SCOPE
> This file was generated by `statehub register`. Refine it as the repository
> boundaries become clearer.
Boundary contract for `audit-core`, aligned to NetKingdom Security Layer Model
v0.7 (accepted) and its working companion. `INTENT.md` says what Audit Core is
for; this file says what it does and does not do, and is the file to check
before adding a surface.
## One-liner
Reliable multi-tenant auto setup audit capability.
The estate's Evidence engine: durable, tenant-aware custody of audit events,
with an honest and bounded integrity claim.
## Core Idea
## Layer and role
audit-core exists to provide the capability described in INTENT.md.
**Engine / Evidence** (statute §3.3, §4). Deterministic API over one modeled
concept — the audit event. Declared in `INTENT.md` frontmatter and `layer.yaml`.
Audit Core is **not** a PDP, not a PIP, and not PEP-shaped. It renders no
decision, supplies no claim a decision consumes, and causes no protected side
effect of its own.
## In Scope
- Maintain the repository's primary implementation.
- Keep docs, tests, and operational metadata current.
- Operational audit custody (`operations.audit`) and its declared recovery bound.
**Custody and integrity**
- Event ingestion, validation, normalization to the Audit Core envelope,
redaction, and append-only operational custody.
- Sender registration and binding; tenant and scope attribution.
- Hash-chain integrity: link, verify, chain-head attestation, and the
`GET /v1/integrity` operator surface.
- The honest custody claim — `custody_class`, `immutable`, `tamper_evidence`,
`recoverable_days` — stated at no more than `docs/integrity.md` delivers.
- Retention profiles, search and export entitlements, dead-letter and
secret-finding surfaces.
**Evidence-role obligations under v0.7 §9.6** *(largely not yet built — see
`workplans/AUDIT-WP-0009-evidence-role-conformance.md`)*
- Recording, per source, whether its evidence is **load-bearing** or
**attributive**, because the obligations differ.
- Accepting and storing a source's declared **emission cadence**, and raising a
finding when the stream falls below it — the stream observed, not only its
contents.
- **Heartbeat** ingestion and missing-heartbeat findings for low-volume
load-bearing classes, where rate monitoring cannot work.
- A **reconciliation** surface: per-source, per-class event counts a source can
compare against its own state transitions.
- Registering `approval-engine` as a distinct source carrying issuance, use,
supersession, and revocation (§9.4, `AUDIT-IN-0001`).
**Conformance**
- Machine-readable layer declaration (`layer.yaml`) and a conformance test.
- A total list of infrastructure contacts, catalogued or not, so the §5 check
is total rather than vacuous.
## Out of Scope
- Own unrelated adjacent systems.
- Make irreversible operational decisions without human approval.
- Procuring or operating S3 / Barman / WAL.
- Booked cost or a second usage stream for `platform:audit-storage`.
**Boundaries fixed by the statute — these are permanent, not "not yet"**
- **Any authorization decision.** `access-engine` is the only decision point
(§6). Audit Core renders and caches none.
- **An approval-validity query.** Records, yes; a verdict on whether an approval
is still valid, never (§9.4). A consumer branching on such an answer would
route an authorization decision through the audit fabric.
- **Operative approval state** — the durable object, atomic supersession, single
consumption, revocation. That is `approval-engine`'s (§9.4).
- **Emission atomicity at the source.** Completeness at the boundary is the
emitter's obligation; an archive cannot retrofit it (§9.6).
- **Any claim that the archive proves an event occurred, or that absence proves
it did not** (§9.6).
- **Policy decision making**, per `INTENT.md`.
**Not owned here**
- Procuring or operating S3 / Barman / WAL; the `platform:audit-storage` booked
cost or a second usage stream.
- WORM, object lock, or ITC-CAP `data.archive`. Ruled a different store with a
different owner (statute §16); the stronger-custody gap is withdrawn, not
pending.
- A `rapp.yaml` in this repo (schema requires `rapp-*`).
- Public ingest.
- Unrelated adjacent systems; irreversible operational decisions without human
approval.
## Current State
- Status: production
- Production receiver on railiance01 (`namespace audit-core`), Postgres
- Status: production. Receiver on railiance01 (`namespace audit-core`), Postgres
operational custody on `platform-pg`, sender `user-engine`.
- Recovery is the platform `data.backup` window (30 days). Live `/readyz`
reports `custody_class=operational`, `tamper_evidence=true`,
`recoverable_days=30` (image `sha256:7febc28e…`).
- Recovery is the platform `data.backup` window (30 days). `/readyz` reports
`custody_class=operational`, `recoverable_days=30`.
- Hash chain verified on 30 live events
(`docs/evidence/chain-head-20260816.json`).
- ITC-CAP case: `data/capability/audit-core-operational.json` at D4.
`data.archive` is an unmet requirement.
- Tenancy posture: `tenancy.yaml` (NetKingdom Tenancy Posture v0.1 draft-8,
Decision 5.4). Declared `I1 A2 E1 P1 R1`, target `E3` and `R2`. E is quoted
at 1 although the E2 mechanism is in place on both paths — the §13 E2
artifact is adversarial and does not exist yet (AUDIT-WP-0008-T05).
- Tenancy posture: `tenancy.yaml` — declared `I1 A2 E1 P1 R1`, target `E3`/`R2`.
**Known conformance gaps** (assessed 2026-08-29,
`history/2026-08-29-v0.7-alignment-and-scope-assessment.md`):
- `tamper_evidence=True` is returned unconditionally by the Postgres backend
while `docs/integrity.md` permits it only when a live external attestation
exists. The one attestation on record is 2026-08-16 and no job renews it.
This is Audit Core overclaiming its own bound — the §9.6 defect turned inward.
- No cadence, heartbeat, reconciliation, or load-bearing classification exists.
The §9.6 obligations Audit Core argued for are not yet supportable by Audit
Core.
- `approval-engine` is not yet registered as a source.
- No negative test asserts the absence of an approval-validity surface.
## Getting Oriented
- Start with: INTENT.md
- Agent instructions: AGENTS.md
- Workplans: workplans/
- Start with: `INTENT.md`, then `net-kingdom/SECURITY-COMPANION.md`
- Bound on what may be claimed: `docs/integrity.md`
- Agent instructions: `AGENTS.md`
- Workplans: `workplans/`

View file

@ -0,0 +1,177 @@
# Security layer model v0.7 — alignment, scope revision, and gap assessment
**Date:** 2026-08-29
**Standard:** `net-kingdom/canon/standards/security-layer-model_v0.7.md` — **accepted**
**Companion:** `net-kingdom/SECURITY-COMPANION.md` v0.2
**Scope of this review:** align `INTENT.md`, revise `SCOPE.md`, assess scope
against intent and against the shipped implementation, and raise the work.
**Result:** `INTENT.md` and `SCOPE.md` updated; nine gaps found, one of them a
live overclaim; `AUDIT-WP-0009` raised.
---
## 1. Disposition of audit-core's v0.6 findings
All three landed in v0.7:
| v0.6 finding | v0.7 |
| --- | --- |
| atomicity closes accidental, not adversarial, omission | §9.6 gained the three-row **threat decomposition**, including the explicit *nothing in this model prevents it* residual |
| cadence is a SHOULD and is the only control in its class | §9.6 cadence is **MUST for load-bearing sources**, with positive reconciliation or a heartbeat as the required form for low-volume classes |
| §3.3's Evidence row states a trade as a property | restated as an estate trade, leaving independent-recording-before-effect raisable as a declared exception |
The standard is now accepted. What follows is not review of gate-house's text
but assessment of audit-core against it.
## 2. What v0.7 binds audit-core to
Audit Core is **Engine / Evidence** (§3.3, §4). Its obligations:
1. Declare the layer in `INTENT.md` **frontmatter** plus prose in its own voice
(§11, companion §2). `layer.yaml` alone does not discharge this.
2. Render and cache no decision; expose no approval-validity query (§6, §9.4).
3. Never claim more than the archive delivers, in either direction (§9.6).
4. **Support** the §9.6 obligations it argued for: the load-bearing/attributive
distinction, declared emission cadence, and reconciliation or heartbeat for
low-volume load-bearing classes.
5. Register `approval-engine` as a distinct source (§9.4, `AUDIT-IN-0001`).
6. Keep the §5 conformance check total by listing infrastructure contacts even
where uncatalogued (companion §4), noting that carve-out sunsets within two
review intervals.
Point 4 is the one with teeth. audit-core argued cadence up from SHOULD to MUST
and specified the reconciliation form. **A source cannot declare a cadence to a
system with nowhere to put it.** Having won the obligation, audit-core owes the
surface that makes it dischargeable.
## 3. Scope versus intent
`SCOPE.md` was a `statehub register` stub — *"audit-core exists to provide the
capability described in INTENT.md"* — carrying no boundary at all. Against an
`INTENT.md` that had grown a Layer section, an Approval Evidence section, and a
corrected principle 6, the gap was total: **every statute-fixed prohibition
lived only in INTENT and none of it in SCOPE**, which is the file a contributor
checks before adding a surface.
`SCOPE.md` is rewritten to carry the permanent boundaries explicitly — no
decision, no validity query, no operative approval state, no completeness claim
— and to separate them from the merely-not-yet. The distinction matters: §16
ruled the stronger-custody gap **closed**, so WORM and `data.archive` are now
*not ours* rather than *not yet*, and the old scope line implying a pending
archive requirement was stale.
Intent and scope now agree. The disagreement that remains is between both of
them and the code.
## 4. Gaps
### G1 — `tamper_evidence` is claimed unconditionally *(headline)*
`audit_core/postgres_backend.py:306` returns `tamper_evidence=True` as a
constant. `docs/integrity.md` permits that claim **only when**:
1. `verify` exists and fails on a rewritten row; **and**
2. an external head attestation exists, and verify-against-attestation reports a
missing cited head as a break.
Condition 2 is operational, not structural. The only attestation on record is
`docs/evidence/chain-head-20260816.json` — thirteen days old — and `deploy/`
contains no job that renews it. Every sender reading `/readyz` is therefore told
audit-core has a property whose precondition is unverified and probably stale.
This is §9.6's defect turned inward: **audit-core claiming more than its bound
delivers**, in the exact idiom it corrected in gate-house's own doctrine twice.
It is the highest-priority gap for that reason, independent of severity.
Fix: derive the flag from live attestation state, and make a stale or absent
attestation degrade the claim rather than leave it standing.
### G2 — no load-bearing / attributive classification
`SenderIdentity` (`audit_core/senders.py:30`) carries `sources`, `tenants`,
`may_write`, `may_read`, `secret_policy`, `expires_at`. Nothing records which
kind of evidence a source produces, so audit-core cannot tell which sources owe
a cadence. §9.6's central distinction is unrepresentable.
### G3 — no cadence declaration
Nothing accepts, stores, or evaluates a declared emission cadence. `grep` for
`cadence` across `audit_core/`, `docs/`, and `tests/` returns nothing; the
`reconcile` hits are duplicate-event reconciliation, an unrelated concept.
Depends on the §17 emission-cadence schema, which `kings-guard` is drafting.
### G4 — no heartbeat, and no missing-heartbeat finding
The required form for low-volume load-bearing classes does not exist. This is
the gap that matters most substantively: it is the only control covering
adversarial omission for exactly the rare negative events — revocations,
denials, containment — where suppression is most valuable and least visible.
### G5 — no reconciliation surface
A source cannot compare its own state transitions against audit-core's event
count per class, because no endpoint exposes those counts. `GH-WP-0002-T04` is
the reference instance and has no counterpart here.
### G6 — stream-completeness findings have no home
`/v1/dead-letters` and `/v1/secret-findings` exist; a stream-observation finding
has no surface. INTENT principle 10 already says degraded audit streams are
themselves audit and operations events — the principle is in place and the
mechanism is not.
### G7 — `approval-engine` not registered
Committed to in `AUDIT-IN-0001`. Needs sender registration, the four event
classes, tenancy mapping, retention profile, and `secret_policy`, onboarded
under principle 4 — declared policy, not merely arriving events. Not blocking:
`approval-engine` is not yet emitting.
### G8 — no negative test on the approval-validity prohibition
§9.4's MUST NOT is currently honoured by absence. The estate's own idiom for a
published-equals-shipped property is a test (§6.4 obligation 3 requires exactly
that of a PEP stance map). A prohibition worth stating is worth asserting.
### G9 — Tooling contact list is empty rather than total
`layer.yaml` declares `tooling_contacts: []`, true under §5 as written since
audit-core is an Engine holding no `key-cape` or OpenBao client. But companion
§4 asks that uncatalogued infrastructure be listed anyway so the check is total,
and the carve-out sunsets within two review intervals: `platform-pg` is a store
another layer reads. Listing it now costs nothing and pre-empts the sunset.
## 5. What is already conforming
Not everything is a gap, and the assessment would be dishonest without this:
- **No decision surface exists.** The §6 prohibition is met in fact.
- **The integrity contract is honest.** `docs/integrity.md` states the proof
bound, names the attacker class it does not cover, and explicitly disclaims
WORM and `data.archive`. G1 is a defect in the *code's* claim, not the
document's — the doctrine was right and the implementation drifted from it.
- **Custody claims are otherwise sober**: `custody_class=operational` rather than
`archive`, recovery cited to the platform window rather than `retention_days`.
- **Tenant isolation and redaction** are built and tested.
- **`immutable=True`** is correctly qualified in-code as not a claim against the
database owner.
The pattern is worth naming: where audit-core wrote doctrine it was accurate;
where a value was hard-coded it drifted optimistic. G1 is the one place the two
diverge, and it diverges in the direction the estate is least able to detect.
## 6. Priority
| Gap | Priority | Why |
| --- | --- | --- |
| G1 `tamper_evidence` | **high** | live overclaim to every sender; audit-core's own doctrine violated inward |
| G2 classification | high | prerequisite for G3G5 |
| G4 heartbeat | high | only control covering the adversarial residual for rare events |
| G3 cadence | medium | blocked on the §17 schema |
| G5 reconciliation | medium | `GH-WP-0002-T04` counterpart |
| G8 negative test | medium | cheap; closes a MUST NOT by assertion |
| G6 findings surface | medium | needed for G3/G4 to be actionable |
| G7 approval-engine source | low | not blocking; no emitter yet |
| G9 contact list | low | pre-empts a sunset |
Raised as `AUDIT-WP-0009-evidence-role-conformance.md`.

View file

@ -1,9 +1,10 @@
# audit-core — NetKingdom security layer declaration
#
# Framework: net-kingdom/canon/standards/security-layer-model_v0.6.md
# Framework: net-kingdom/canon/standards/security-layer-model_v0.7.md
# Assent: AUDIT-IN-0001 (audit-core's own voice, per §11 "who must declare")
# history/2026-08-28-approval-evidence-assent.md
# history/2026-08-29-security-layer-model-v0.6-review.md
# history/2026-08-29-v0.7-alignment-and-scope-assessment.md
#
# Reference form offered by ops-warden and adopted here, so §11's declaration
# check is mechanical rather than a reader's judgment about prose. audit-core
@ -11,7 +12,7 @@
schema_version: "0.1"
framework: netkingdom-security-layer-model
standard_version: "0.6"
standard_version: "0.7"
repository: audit-core
layer: engine
role: evidence # §3.3 engine typing
@ -27,10 +28,19 @@ decision_surfaces_exposed: none
# authorization decision through the audit fabric.
approval_validity_query: forbidden
# §5 applies to Staff. audit-core is an Engine and holds no Tooling contact
# requiring a §5.1/§5.2/§5.3 declaration. Its PostgreSQL custody is its own
# operational store, not a §4 Tooling row.
# §5 applies to Staff. audit-core is an Engine and holds no §4 Tooling contact
# (key-cape, OpenBao). Companion §4 asks that UNCATALOGUED infrastructure be
# listed anyway so the check is total, and that carve-out sunsets within two
# review intervals for a store another layer reads. Completing this list and
# adding a conformance test is AUDIT-WP-0009-T10.
tooling_contacts: []
uncatalogued_infrastructure:
- id: platform-pg
system: CNPG PostgreSQL on railiance01
role: audit-core's own operational custody store
read_by_other_layers: true # subject to the companion §4 sunset
note: >-
Not a §4 Tooling row. Listed for totality, not as a declared gap.
# §9.6 — the bound audit-core delivers, stated so no doctrine rests on more.
evidence_bound:

View file

@ -0,0 +1,201 @@
---
id: AUDIT-WP-0009
type: workplan
title: "Evidence-role conformance under Security Layer Model v0.7"
domain: infotech
repo: audit-core
status: ready
owner: claude
topic_slug: railiance
created: "2026-08-29"
updated: "2026-08-29"
depends_on:
- AUDIT-WP-0007
---
# AUDIT-WP-0009 — Evidence-role conformance under Security Layer Model v0.7
## Goal
Make audit-core dischargeable as the estate's **Evidence engine** under
NetKingdom Security Layer Model v0.7 (accepted), and stop it claiming a property
its own contract does not permit.
Two things drive this workplan.
**First, a live overclaim.** `audit_core/postgres_backend.py` returns
`tamper_evidence=True` as a constant, while `docs/integrity.md` permits that
claim only when a live external chain-head attestation exists. The one
attestation on record is 2026-08-16 and nothing renews it. Audit Core is
currently telling every sender it has a property whose precondition is
unverified — the §9.6 defect it twice corrected in gate-house's doctrine, turned
inward.
**Second, an obligation audit-core argued for and cannot yet support.** v0.7
§9.6 makes a declared emission cadence a **MUST** for load-bearing sources, with
reconciliation or a heartbeat as the required form for low-volume classes.
audit-core raised both, moving cadence from SHOULD to MUST. A source cannot
declare a cadence to a system with nowhere to put it. Having won the obligation,
audit-core owes the surface.
Full assessment: `history/2026-08-29-v0.7-alignment-and-scope-assessment.md`.
Standard: `net-kingdom/canon/standards/security-layer-model_v0.7.md`, companion
`net-kingdom/SECURITY-COMPANION.md`.
## Non-goals
Fixed by the statute; not deferred, not ours:
- No authorization decision surface, and no approval-validity query (§6, §9.4).
- No operative approval state (§9.4).
- No WORM, object lock, or ITC-CAP `data.archive` — §16 ruled the
stronger-custody gap closed, a different store with a different owner.
- Emission atomicity at the source, which is the emitter's obligation (§9.6).
## Tasks
```task
id: AUDIT-WP-0009-T01
status: todo
priority: high
```
Make `tamper_evidence` conditional on live attestation state. Derive the flag
rather than hard-coding it: the backend reports `True` only when a chain-head
attestation exists, verifies against the live chain, and is within a declared
freshness window. Stale or absent attestation degrades the claim — it does not
leave it standing. Assert the degradation with a test, and state the freshness
window in `docs/integrity.md` alongside the two existing preconditions, which
today are documented but unenforced.
```task
id: AUDIT-WP-0009-T02
status: todo
priority: high
```
Schedule chain-head attestation so the precondition T01 enforces is normally
met. `attest-chain` exists and is operator-run; `deploy/` has no job. Add one,
write the attestation to the logical-offsite path already used by
`RESOURCE-WP-0002-T06` (`rapp-postgres` / Nextcloud + age) and **not** the
Barman prefix, per `docs/integrity.md` — that copy is restored with the table
and proves nothing. Record the cadence in `docs/integrity.md`.
```task
id: AUDIT-WP-0009-T03
status: todo
priority: high
```
Represent the §9.6 evidence kind per source. Add `evidence_kind`
(`load-bearing` | `attributive`) to `SenderIdentity` and the sender registration
schema, defaulting to `attributive` — a source that has not declared is not
silently treated as load-bearing. Where a source declares `attributive` and has
deliberately traded away atomicity, carry the declared trade with it, because
§9.6 requires the trade be declared where the trail is documented. Prerequisite
for T04T06.
```task
id: AUDIT-WP-0009-T04
status: todo
priority: high
```
Heartbeat ingestion and missing-heartbeat findings. The required form for
low-volume load-bearing classes, and the only control covering adversarial
omission for exactly the rare negative events — revocation, denial, containment
— where suppression is most valuable and least visible. Accept a signed
*nothing to report* assertion per source and class; raise a finding when an
expected heartbeat does not arrive. The point is a positive claim that can
itself go missing, which rate monitoring can never produce.
```task
id: AUDIT-WP-0009-T05
status: wait
priority: medium
```
Accept and evaluate a declared emission cadence per source, and raise a finding
when the stream falls below it. **Waiting on** the §17 emission-cadence
declaration schema, which `kings-guard` is drafting and Taxonomy will own; do
not invent a competing shape. Cadence covers high-volume streams; T04 covers the
low-volume load-bearing case, and the two are complementary rather than
alternatives.
```task
id: AUDIT-WP-0009-T06
status: todo
priority: medium
```
Reconciliation surface: per-source, per-class event counts over a bounded
window, readable by the source itself, so an emitter can compare audit-core's
count against its own state transitions and treat divergence as a finding.
`GH-WP-0002-T04` is the reference instance on the `approval-engine` side; this
is its counterpart here. Respect the existing read model — `may_read`, tenant
scoping, and no payloads in a counts response.
```task
id: AUDIT-WP-0009-T07
status: todo
priority: medium
```
Give stream-completeness findings a home. `/v1/dead-letters` and
`/v1/secret-findings` exist; a cadence miss (T05) and a missing heartbeat (T04)
have nowhere to surface. INTENT principle 10 already says a degraded audit
stream is itself an audit and operations event — the principle is in place and
the mechanism is not. Follow the existing findings-surface conventions rather
than adding a new shape.
```task
id: AUDIT-WP-0009-T08
status: todo
priority: medium
```
Assert the §9.4 approval-validity prohibition with a negative test. It is
currently honoured by absence, which is not the estate's idiom: §6.4 obligation
3 requires a PEP's published stance map equal shipped behaviour *asserted by
test*. A prohibition worth stating in `INTENT.md`, `SCOPE.md`, and `layer.yaml`
is worth asserting in `tests/`.
```task
id: AUDIT-WP-0009-T09
status: todo
priority: low
```
Register `approval-engine` as a distinct source under §9.4 and `AUDIT-IN-0001`:
sender registration, the four event classes (issuance, use, supersession,
revocation), tenancy mapping, retention profile, `secret_policy`, and
`evidence_kind: load-bearing` from T03. Onboarded under INTENT principle 4 —
declared ownership, retention, access, export and evidence policy, not merely
events arriving. Not blocking: `approval-engine` is not yet emitting.
```task
id: AUDIT-WP-0009-T10
status: todo
priority: low
```
Make the §5 conformance check total. `layer.yaml` declares
`tooling_contacts: []`, true under §5 as written — audit-core is an Engine and
holds no `key-cape` or OpenBao client. Companion §4 asks that uncatalogued
infrastructure be listed anyway, and that carve-out sunsets within two review
intervals for a store another layer reads, which `platform-pg` is. List it and
the State Hub now. Add a conformance script and test in the shape ops-warden
offered estate-wide.
## Acceptance
- No custody claim is returned unconditionally where `docs/integrity.md`
conditions it, and a stale attestation visibly degrades the claim.
- A source can declare its evidence kind, and a load-bearing source can
discharge the §9.6 cadence MUST against a real audit-core surface.
- A suppressed low-volume load-bearing class produces a finding rather than
silence.
- The approval-validity prohibition is asserted by test, not by absence.
- `layer.yaml` lists every infrastructure contact, catalogued or not, and a test
checks it.
## Notes
T01 and T02 are separable and T01 is the one that matters: scheduling
attestation without making the claim conditional would leave the overclaim in
place whenever the job fails, which is precisely the case the claim is supposed
to cover.
T05's dependency is real and should not be worked around. audit-core argued the
cadence obligation up to a MUST; inventing a local cadence shape ahead of the
Taxonomy schema would fragment the very interface it asked for.