diff --git a/docs/section-4-source-of-evidence.md b/docs/section-4-source-of-evidence.md new file mode 100644 index 0000000..40ae70c --- /dev/null +++ b/docs/section-4-source-of-evidence.md @@ -0,0 +1,133 @@ +# Who is the §4 source of evidence + +`AUDIT-IN-0005`. Statute: `net-kingdom` security layer model §4, §9.6, §11, §17, +companion profile `emission-cadence-security-profile_v0.1.md`. + +This document is **not derived**. It states audit-core's own boundary in +audit-core's own voice, which is the only form §11 accepts for a declaration. + +## The question + +§11 requires every repository catalogued in §4 **as a source of evidence** to +declare an emission guarantee — class, cadence, and the detection surface the +governing cadence profile requires — in its machine-readable layer declaration, +and says a source declaring none is not conforming. + +`access-engine` (the repository still answering to `flex-auth`) produces the +decision record, declares no emission guarantee, and raised the ambiguity itself +as B3 / G2: either it is a §4 source and is non-conformant today, or audit-core +is the source and `access-engine` is merely the producer of an artifact +audit-core is the source *of*. It declined to pick the second — the reading that +favours it — and asked audit-core which side of the line audit-core holds. + +## The answer + +**audit-core is not the source of evidence for the decision record. The emitter +is. For the decision record that emitter is `access-engine`.** + +audit-core holds the custody half and the detection surface. It does not hold, +and will not accept, the emission guarantee for an event another repository +produces. + +## Why, from audit-core's own record rather than from preference + +**1. All three declared things are properties of emitting, not of holding.** +Class, cadence, and detection surface each describe the act of producing an +event. audit-core can declare none of them for an event it does not produce: it +cannot classify a stream it does not generate, cannot promise a rate or an +interval for it, and — stated to gate-house in `AUDIT-IN-0003` against +`GH-DEC-2026-014` limit 3, before this question existed — it **cannot detect +non-production**. audit-core performs no retrieval; its egress permits Postgres +and DNS only. A declaration audit-core made here would be a promise about +behaviour audit-core cannot observe, which is the same defect as an event +claiming an occurrence nobody watched. + +**2. It is already audit-core's stated doctrine, twice, in writing.** +`AUDIT-IN-0001`: *"completeness at the boundary is the emitter's property, not +the archive's"*, and emission atomicity is *"a requirement on approval-engine, +not a task audit-core can discharge for it."* `AUDIT-WP-0009` lists *"emission +atomicity at the source, which is the emitter's obligation (§9.6)"* among its +non-goals. Taking the source role now would reverse a boundary audit-core has +held against gate-house and against approval-engine, for the one repository that +asked. + +**3. The estate's operative shape already assigns it that way.** Every +obligation §11 names is carried on the **sender registration**, per source: +`evidence_kind` in `deploy/senders-scope.{json,yaml}`, and `heartbeat_classes` +per class rather than per source (`docs/stream-completeness.md`). Four sources +are registered today — `user-engine`, `tenant-engine` (attributive, with its +declared `completeness_trade`), `approval-engine` and `informed-decision` (both +load-bearing) — and each carries its own classification because §11 and the +cadence profile both say the classification is the **source's** published one and +MUST NOT be inferred by the observer. Reading `access-engine` out of that set +would make it the only producer in the estate whose emission obligation was held +by the archive. + +**4. The archive-as-source reading makes §11 vacuous.** If custody conferred +source status, every emission obligation in the estate would land on the single +repository that can observe no emission, and no source could ever be +non-conforming under §11. A rule that assigns an obligation the holder cannot +discharge is the §9.1 defect §11 says it has now corrected four times; applying +it here would make the fifth. + +**5. As a matter of fact, audit-core does not hold the record.** There is no +registered sender named `access-engine` or `flex-auth`, no token, no ingress and +no scope row. The decision record does not reach audit-core custody at all. A +repository cannot be the source of evidence it has never received, and could not +have been the source of it for the period in which the ambiguity stood. + +## What audit-core does owe, and it is not nothing + +The surface exists and is not a promise. §11 requires a **rare** load-bearing +class to carry a heartbeat **and** reconciliation and forbids rate monitoring +alone. audit-core built both, and built them in the shape a decision record +needs: + +| Owed by the source | Owed by audit-core | +| --- | --- | +| The published class — load-bearing, and rare or volume | Accepting that classification as supplied and never inferring it | +| The cadence, per class | `heartbeat_classes` on the registration; `POST /v1/events` class `audit-core.heartbeat` | +| Reconciliation of its own counts | `GET /v1/reconciliation`, scoped to the caller's own sources, counts and never payloads | +| — | `GET /v1/stream-findings`, and the `means` field on every finding | + +`access-engine`'s expectation — heartbeat plus reconciliation rather than rate, +because a decision record is bursty and a quiet period is normal traffic — is +correct and is the form already implemented. Registration goes through an intake +(`AUDIT-IN-0002`, `AUDIT-IN-0003` are the worked examples) and needs a token +lane, not a doctrine change. + +**The bound, stated so no conformance argument rests on more.** Heartbeat and +reconciliation cover loss, outage, drain failure and accident. Neither covers a +compromised source suppressing an event and its own count together. §16 placed +the independent observer outside audit-core's scope. Nothing in this document +may be read as audit-core detecting adversarial omission by a source. + +## What this ruling does not do + +It binds audit-core. It does not rule §11. + +§11 is `net-kingdom` canon authored by `gate-house`, and whether `access-engine` +is a §4 source is on gate-house's list along with five other §11 questions +(custodian survey, `the-custodian/docs/assessments/2026-09-21-layer-declaration-boundaries.md`, +2026-09-21). audit-core has removed the only alternative reading by declining it +on its own authority; the ruling itself is still gate-house's to make. + +So `access-engine`'s **G2 does not close on this document alone.** flex-auth +holds it open until gate-house rules *and* audit-core confirms; this is the +second of those two and not the first. audit-core's position is that with the +alternative reading withdrawn by its holder, the remaining ruling has one +defensible outcome — but saying so is a prediction about gate-house, not a +substitute for it. + +## audit-core's own emission + +Asked which side of the line it holds, audit-core owes the same answer about +itself. audit-core is catalogued in §4 as the Evidence engine, not as a source, +and it emits no event into anyone else's custody. It does produce one artifact +others rely on: the **chain-head attestation**. Its class, cadence and detection +surface are now stated in `layer.yaml` under `emission_guarantee` rather than +only in `docs/integrity.md`, because a guarantee legible only by following a +document trail is the exact defect audit-core raised against flex-auth's +declaration in §11 — and leaving it in prose while charging another repository +with declaring theirs would be the flattering reading of an unruled boundary, +which is the thing both repositories keep objecting to. diff --git a/intakes/intakes.md b/intakes/intakes.md index ca481cb..a007ad1 100644 --- a/intakes/intakes.md +++ b/intakes/intakes.md @@ -1,5 +1,78 @@ # Intake records +## AUDIT-IN-0005 — Which repository is the §4 source of evidence for the decision record + +```yaml +id: AUDIT-IN-0005 +kind: intake +title: 'Which repository is the §4 source of evidence for the decision record + (flex-auth B3 / declared gap G2)' +status: closed +origin: cross-repo +origin_ref: FLEX-WP-0030 B3 / flex-auth conformance gap G2 +priority: high +owner: audit-core +requested_by: flex-auth +description: > + §11 requires every repository catalogued in §4 as a source of evidence to + declare its emission guarantee — class, cadence and the detection surface the + governing cadence profile requires — in its machine-readable layer + declaration, and says a source declaring none is not conforming. + + flex-auth (access-engine) produces the decision record and declares none. Two + readings are defensible from the text: flex-auth is a §4 source and is + non-conformant today, or audit-core is the source and flex-auth is merely the + producer of an artifact audit-core is the source of. flex-auth declined to + pick the second — the reading that favours it — and asked audit-core to say + which side of the line audit-core holds. The custodian confirmed audit-core + can answer without waiting on gate-house, and that answering does not close + G2. Estate-wide view: the-custodian/docs/assessments/2026-09-21-layer-declaration-boundaries.md. +created: '2026-09-21' +updated: '2026-09-21' +outcome: declined-with-the-surface-offered +closed: '2026-09-21' +resolution: 'audit-core is NOT the §4 source of evidence for the decision + record. The emitter is, and for the decision record the emitter is + access-engine. Five reasons, none of them preference. (1) Class, cadence and + detection surface are all properties of the act of emitting; audit-core + produces no decision record, and told gate-house before this question existed + that it cannot detect non-production at all — AUDIT-IN-0003 against + GH-DEC-2026-014 limit 3. A declaration here would promise behaviour audit-core + cannot observe. (2) It is audit-core''s standing doctrine in writing: + completeness at the boundary is the emitter''s property, not the archive''s + (AUDIT-IN-0001), and emission atomicity at the source is an AUDIT-WP-0009 + non-goal. Taking the role now would reverse for the asking repository a + boundary held against gate-house and approval-engine. (3) The operative shape + already assigns it: evidence_kind and heartbeat_classes sit on each SENDER + registration, per source and per class, for all four registered sources, + because §11 and the cadence profile both make the classification the source''s + published one. (4) Archive-as-source makes §11 vacuous — every emission + obligation would land on the one repository that can observe no emission, and + no source could ever be non-conforming. That is the §9.1 defect §11 says it has + corrected four times. (5) As fact: no sender named access-engine or flex-auth + is registered, no token, no ingress, no scope row — the decision record never + reaches audit-core custody, and a repository cannot be the source of evidence + it has never received. WHAT AUDIT-CORE OWES INSTEAD, and it is not nothing: the + rare load-bearing form access-engine expects — heartbeat per class plus + reconciliation, never rate — is built and registerable through an intake, with + the standing bound that neither control covers a source suppressing an event + and its own count together (§16). WHAT THIS DOES NOT DO: it binds audit-core, + it does not rule §11. gate-house authors §11 and still owns the ruling, so G2 + does not close on this record alone — this is audit-core''s confirmation, not + gate-house''s ruling. REFLEXIVE HALF: asked which side of the line it holds, + audit-core answered about itself too. It emits no event into another + repository''s custody, but it does produce the chain-head attestation, whose + class, cadence and detection surface were stated only in docs/integrity.md + prose. They are now declared in layer.yaml under emission_guarantee + (load-bearing, rare, daily 17 3 * * *, 168h freshness window degrading + tamper_evidence to False with the reason recorded) and asserted by + tests/test_layer_conformance.py against the CronJob and the contract. Leaving + it in prose while charging another repository with declaring theirs would have + been the flattering reading of an unruled boundary.' +recorded_in: docs/section-4-source-of-evidence.md +work: AUDIT-WP-0009-T13 +``` + ## AUDIT-IN-0003 — Register informed-decision as a load-bearing sender; commitment-only payload doctrine ```yaml diff --git a/layer.yaml b/layer.yaml index 338284a..4655c89 100644 --- a/layer.yaml +++ b/layer.yaml @@ -81,3 +81,53 @@ evidence_bound: - external chain-head attestation stored outside platform-pg contract: docs/integrity.md not_claimed: [WORM, object-lock, archival-custody] + +# §11 emission guarantee — AUDIT-IN-0005, docs/section-4-source-of-evidence.md +# +# §11 requires the declaration from a repository catalogued in §4 as a SOURCE of +# evidence. audit-core is catalogued as the Evidence engine — the custody and +# detection half — and emits no event into another repository's custody. The +# emission guarantee for an event belongs to the repository that emits it; that +# is audit-core's standing boundary (AUDIT-IN-0001, AUDIT-WP-0009 non-goals) and +# the reason audit-core declines the source role for access-engine's decision +# record. +# +# The one artifact audit-core does produce on its own behalf is declared anyway, +# in the same spirit as uncatalogued_infrastructure above: stated so the check is +# total rather than vacuous, not because §11 is read to compel it. Prose in +# docs/integrity.md is not a machine-readable declaration — the defect audit-core +# raised against another repository, so not one it leaves standing in its own. +source_of_evidence: false +source_of_evidence_note: >- + audit-core holds custody and the detection surface. Emission class, cadence and + detection obligations sit on each sender's registration + (deploy/senders-scope.{json,yaml}, heartbeat_classes per class), never on the + archive. audit-core cannot detect non-production by a source and claims no + ability to — AUDIT-IN-0003, GH-DEC-2026-014 limit 3. + +emission_guarantee: + - id: chain-head-attestation + emits: external chain-head attestation for the audit event chain + class: load-bearing + rarity: rare # one scheduled artifact per day, never volume + rate_monitoring: forbidden # §11 / emission-cadence profile, rare class + cadence: + form: scheduled + interval: daily + schedule: "17 3 * * *" # UTC, deploy/attest-cronjob.yaml + producer: CronJob audit-core-attest-chain, its own ServiceAccount + published_to: ConfigMap audit-core-chain-head + detection_surface: + form: freshness-window + window_hours: 168 # 7x the cadence; widens, never removes + on_absence: >- + tamper_evidence degrades to False with the reason recorded, and a missing, + unreadable, undated or stale attestation is treated the same as absent. + Non-production is detected deterministically at read time by the consumer + of the claim, not inferred from a rate. + surfaces: ["GET /v1/integrity", "GET /readyz (last-known, no chain walk)"] + contract: docs/integrity.md + bound: >- + The attestation proves the head it cites; it is not WORM and does not prove + any record was ever sent. An operator-run offsite copy is a separate lane + and is not claimed as part of this guarantee. diff --git a/tests/test_layer_conformance.py b/tests/test_layer_conformance.py index b68f99d..54b8540 100644 --- a/tests/test_layer_conformance.py +++ b/tests/test_layer_conformance.py @@ -100,3 +100,49 @@ def test_the_receiver_has_no_api_server_egress(): assert "443" in attest assert "6443" not in receiver assert "443" not in receiver + + +# AUDIT-IN-0005 — §11's emission-guarantee check, made mechanical for audit-core's +# own declaration. The ruling itself lives in docs/section-4-source-of-evidence.md; +# these assert that the declaration keeps saying what the ruling says. + + +def test_audit_core_declines_the_source_of_evidence_role(): + """The archive is not the source. AUDIT-IN-0001, AUDIT-IN-0005.""" + assert LAYER["source_of_evidence"] is False + note = LAYER["source_of_evidence_note"].lower() + assert "non-production" in note + assert "sender" in note + + +def test_the_attestation_emission_is_declared_and_not_rate_monitored(): + """A rare load-bearing class may not rest on rate monitoring (§11).""" + rows = {row["id"]: row for row in LAYER["emission_guarantee"]} + attestation = rows["chain-head-attestation"] + assert attestation["class"] == "load-bearing" + assert attestation["rarity"] == "rare" + assert attestation["rate_monitoring"] == "forbidden" + # All three things §11 asks a source to state. + assert attestation["cadence"]["interval"] == "daily" + assert attestation["detection_surface"]["form"] == "freshness-window" + + +def test_the_declared_cadence_matches_the_deployed_schedule(): + """Declared against the manifest, so drift fails here rather than at review.""" + cronjob = (ROOT / "deploy" / "attest-cronjob.yaml").read_text() + declared = next( + row for row in LAYER["emission_guarantee"] + if row["id"] == "chain-head-attestation" + )["cadence"] + assert declared["schedule"] in cronjob + assert declared["published_to"].split()[-1] in cronjob + + +def test_the_declared_freshness_window_matches_the_contract(): + surface = next( + row for row in LAYER["emission_guarantee"] + if row["id"] == "chain-head-attestation" + )["detection_surface"] + assert surface["window_hours"] == 168 + contract = (ROOT / surface["contract"]).read_text() + assert "168 hours" in contract diff --git a/workplans/AUDIT-WP-0009-evidence-role-conformance.md b/workplans/AUDIT-WP-0009-evidence-role-conformance.md index 607e3d8..63b8e08 100644 --- a/workplans/AUDIT-WP-0009-evidence-role-conformance.md +++ b/workplans/AUDIT-WP-0009-evidence-role-conformance.md @@ -9,7 +9,7 @@ flavor: implementation owner: claude topic_slug: railiance created: "2026-08-29" -updated: "2026-09-15" +updated: "2026-09-21" depends_on: - AUDIT-WP-0007 state_hub_workstream_id: "46a96b03-bc08-53b5-9c93-4071adabf734" @@ -672,3 +672,38 @@ Service/domain-transaction/human and factory runtime/spend admission remain separate; factory attempts and paid model calls remain zero. Receipt: [native readback](../docs/evidence/2026-09-11-factory-native-readback.json). + + +## Answer flex-auth's B3: which repository is the §4 source of evidence + +```task +id: AUDIT-WP-0009-T13 +status: done +priority: high +``` + +`AUDIT-IN-0005`. flex-auth (access-engine) produces the decision record, +declares no §11 emission guarantee, and raised B3 / G2 rather than take the +reading that puts the obligation on audit-core. Answered 2026-09-21: +**audit-core is not the source; the emitter is.** Class, cadence and detection +surface are properties of emitting, audit-core cannot detect non-production +(`AUDIT-IN-0003`, `GH-DEC-2026-014` limit 3), the obligations already sit on +each sender registration, archive-as-source would make §11's check vacuous, and +no `access-engine` sender is registered — the record never reaches this custody. + +Ruling: `docs/section-4-source-of-evidence.md`. It binds audit-core and does not +rule §11; gate-house still owns that, so **G2 stays open** on flex-auth's list. + +Reflexive half, taken rather than avoided: audit-core's own chain-head +attestation had its class, cadence and detection surface stated only in +`docs/integrity.md` prose. Now declared in `layer.yaml` under +`emission_guarantee` — load-bearing, rare, rate monitoring forbidden, daily +`17 3 * * *`, 168h freshness window degrading `tamper_evidence` to `False` with +the reason recorded — and asserted against the CronJob and the contract by +`tests/test_layer_conformance.py`, so the declaration cannot drift from what is +deployed. + +Not done here: the INTENT.md `Engine` versus layer.yaml `engine` split +(flex-auth's corrected B1). Precedence and case-sensitivity are gate-house's to +rule, and aligning on a guess would be authoring a declaration §11 says only +this repository may author.