gate-house proposed (security layer model v0.3 §9.4) that audit-core own the tamper-evident record of approvals while approval-engine owns the operative state. Assented, with two corrections and one condition. Correction 1: INTENT principle 6 overstated the delivered guarantee. docs/integrity.md is authoritative — an in-database chain does not withstand a database owner without the external chain-head attestation, and even then it is not WORM. Principle 6 now points at that bound. Correction 2: the chain proves alteration and truncation, not omission at source. A suppressed revocation leaves the chain intact. Condition: approval-engine must guarantee emission atomicity, so an approval cannot change state without the event being durably queued in the same transaction. Not a task audit-core can discharge for it. Boundary: no approval-validity query will be exposed — that would be deciding early under §6.1. Also declares audit-core's layer (Engine, not a decision point), which §11 requires and INTENT.md was missing. 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
135 lines
6.2 KiB
Markdown
135 lines
6.2 KiB
Markdown
# Approval evidence — audit-core's assent (AUDIT-IN-0001)
|
|
|
|
**Date:** 2026-08-28
|
|
**Intake:** `AUDIT-IN-0001`
|
|
**Requested by:** gate-house
|
|
**Standard:** `net-kingdom/canon/standards/security-layer-model_v0.3.md` §9.4 (proposed)
|
|
**Outcome:** assent, with two corrections to the rationale and one condition on
|
|
the emitting engine.
|
|
|
|
---
|
|
|
|
## What was asked
|
|
|
|
gate-house proposed that audit-core own the **evidence half** of approvals —
|
|
issuance, use, supersession, and revocation, emitted as audit events — while a
|
|
new `approval-engine` owns the operative state: the durable object, atomic
|
|
supersession, single consumption, and revocation without holder cooperation.
|
|
|
|
The two reasons given for keeping the operative state out of audit-core are
|
|
correct and audit-core does not contest them:
|
|
|
|
- operational custody is append-only Postgres by design
|
|
(`docs/audit-backend-contract.md`), and an approval needs mutable
|
|
current-state semantics with compare-and-swap supersession; and
|
|
- `access-engine` must read approval state in-path, so putting it behind the
|
|
audit fabric would make an audit outage an authorization outage.
|
|
|
|
## The answer
|
|
|
|
**Assent.** The split is the right one. Approval evidence is a record of what
|
|
happened, not the authoritative answer to whether an approval is still valid,
|
|
and that distinction is what keeps this inside `INTENT.md`'s "policy decision
|
|
making" exclusion rather than a violation of it.
|
|
|
|
Two corrections and one condition follow. None of them reject the split; they
|
|
state what audit-core actually delivers so the standard does not rest on a
|
|
stronger claim than the implementation supports.
|
|
|
|
---
|
|
|
|
## Correction 1 — principle 6 is an aspiration, and the delivered bound is weaker
|
|
|
|
gate-house's rationale cites `INTENT.md` principle 6 — *signed or hash-chained
|
|
manifests so operators can later prove whether a record set was changed,
|
|
omitted, or truncated* — as already being what authenticated approval entries
|
|
need forensically.
|
|
|
|
`docs/integrity.md` is more careful than principle 6, and it is the contract
|
|
that ships. In its own words: a chain *inside* the same database detects a
|
|
rewritten `payload_hash` **only if the attacker does not also recompute the
|
|
suffix** — and a database owner can. Tamper evidence against that class of
|
|
attacker requires the **external chain-head attestation**, and even with it:
|
|
|
|
> It still does not mean WORM, object lock, or ITC-CAP `data.archive`.
|
|
|
|
`tamper_evidence=True` is conditional on two preconditions being live
|
|
(`verify` failing on a rewritten row, and verify-against-attestation reporting
|
|
a missing cited head as a break). It is not a property of the store at rest.
|
|
|
|
**So: approval events get exactly the guarantee every other audit source gets —
|
|
no more.** That guarantee is real and it is useful, but it is bounded by the
|
|
attestation being taken and stored outside `platform-pg`. If §9.4 needs a
|
|
stronger property for approvals specifically — WORM, object lock, or a
|
|
transparency log — that is an engine gap to declare under §5.3, not a setting
|
|
audit-core can turn on.
|
|
|
|
## Correction 2 — the chain proves alteration, not omission at source
|
|
|
|
This is the more consequential one, and it is the reason for the condition
|
|
below.
|
|
|
|
The hash chain proves that accepted records were not **altered or truncated**.
|
|
It cannot prove anything about an approval event that was **never emitted**.
|
|
For an approval object the omission case is not a corner: a suppressed
|
|
*revocation* is precisely the event an attacker most wants missing, and it
|
|
leaves the chain perfectly intact. Verify would report `intact`.
|
|
|
|
Principle 6 claims audit-core can prove a record set was "omitted". Against a
|
|
compromised or buggy *sender*, it cannot, and neither can any append-only
|
|
archive. Completeness at the boundary is the emitter's property, not the
|
|
archive's.
|
|
|
|
## The condition — approval-engine owns emission atomicity
|
|
|
|
audit-core accepts the evidence half **on the condition that `approval-engine`
|
|
guarantees emission is atomic with the state change** — a transactional outbox
|
|
or equivalent, such that an approval cannot be issued, consumed, superseded, or
|
|
revoked without the corresponding event being durably queued in the same
|
|
transaction.
|
|
|
|
Without that, the evidence half is silently incomplete and audit-core has no
|
|
mechanism to detect the gap. audit-core will report what it received; it will
|
|
not imply that what it received is everything that happened.
|
|
|
|
This is a requirement on `approval-engine`, not a task audit-core can discharge
|
|
on its behalf. It should appear in `approval-engine`'s contract before the
|
|
evidence half is treated as load-bearing.
|
|
|
|
## Boundary audit-core states explicitly
|
|
|
|
`approval-engine` will be registered as a **distinct source**, with its own
|
|
sender registration, tenancy mapping, retention profile, and `secret_policy`
|
|
under the existing onboarding rules (principle 4: a source is onboarded when
|
|
ownership, retention, access, export, and evidence policy are declared and
|
|
validated — not when events start arriving).
|
|
|
|
**audit-core will not expose an approval-validity query.** No read surface will
|
|
answer "is this approval still valid". Records, yes; a verdict, never. Such a
|
|
surface would be deciding early under §6.1, and a consumer branching on an
|
|
audit-core answer would be routing an authorization decision through the audit
|
|
fabric — the exact coupling §9.4 exists to prevent. Callers who need current
|
|
state ask `approval-engine`.
|
|
|
|
The four event classes accepted as a distinct source: **issuance, use,
|
|
supersession, revocation**, per §9.4.
|
|
|
|
## Conformance note raised in passing
|
|
|
|
§11 requires that every repository in §4 declare its layer in `INTENT.md`.
|
|
audit-core declares none today. It is corrected in the same change: audit-core
|
|
is **Engine layer** — it exposes a deterministic API contract and holds
|
|
operational custody — and it is explicitly *not* a decision point, per the
|
|
boundary stated above.
|
|
|
|
---
|
|
|
|
## What changed in this repository
|
|
|
|
| File | Change |
|
|
| --- | --- |
|
|
| `INTENT.md` | layer declared; approval evidence named as a source; principle 6 cross-referenced to the `docs/integrity.md` bound |
|
|
| `intakes/intakes.md` | `AUDIT-IN-0001` closed, outcome `assent-with-conditions` |
|
|
| `history/` | this record |
|
|
|
|
Reply sent to gate-house on the State Hub inbox thread.
|