From 6eaa33699d0354461a65129d54139812f25c4592 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 29 Aug 2026 14:42:51 +0200 Subject: [PATCH] Align to Security Layer Model v0.7; revise SCOPE; raise AUDIT-WP-0009 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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 --- INTENT.md | 92 +++++++- SCOPE.md | 115 +++++++--- ...-29-v0.7-alignment-and-scope-assessment.md | 177 +++++++++++++++ layer.yaml | 20 +- ...AUDIT-WP-0009-evidence-role-conformance.md | 201 ++++++++++++++++++ 5 files changed, 567 insertions(+), 38 deletions(-) create mode 100644 history/2026-08-29-v0.7-alignment-and-scope-assessment.md create mode 100644 workplans/AUDIT-WP-0009-evidence-role-conformance.md diff --git a/INTENT.md b/INTENT.md index 2259442..dabdc46 100644 --- a/INTENT.md +++ b/INTENT.md @@ -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 diff --git a/SCOPE.md b/SCOPE.md index f35015e..9090942 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -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/` diff --git a/history/2026-08-29-v0.7-alignment-and-scope-assessment.md b/history/2026-08-29-v0.7-alignment-and-scope-assessment.md new file mode 100644 index 0000000..7c8c80d --- /dev/null +++ b/history/2026-08-29-v0.7-alignment-and-scope-assessment.md @@ -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 G3–G5 | +| 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`. diff --git a/layer.yaml b/layer.yaml index e35db69..efe70f7 100644 --- a/layer.yaml +++ b/layer.yaml @@ -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: diff --git a/workplans/AUDIT-WP-0009-evidence-role-conformance.md b/workplans/AUDIT-WP-0009-evidence-role-conformance.md new file mode 100644 index 0000000..48f5736 --- /dev/null +++ b/workplans/AUDIT-WP-0009-evidence-role-conformance.md @@ -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 T04–T06. + +```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.