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
8.8 KiB
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:
- Declare the layer in
INTENT.mdfrontmatter plus prose in its own voice (§11, companion §2).layer.yamlalone does not discharge this. - Render and cache no decision; expose no approval-validity query (§6, §9.4).
- Never claim more than the archive delivers, in either direction (§9.6).
- 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.
- Register
approval-engineas a distinct source (§9.4,AUDIT-IN-0001). - 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:
verifyexists and fails on a rewritten row; and- 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.mdstates the proof bound, names the attacker class it does not cover, and explicitly disclaims WORM anddata.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=operationalrather thanarchive, recovery cited to the platform window rather thanretention_days. - Tenant isolation and redaction are built and tested.
immutable=Trueis 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.