AUDIT-WP-0009-T11 — register informed-decision, and answer GH-DEC-2026-014
informed-decision is the browser-facing approver surface; GH-DEC-2026-012 limit 3 makes its evidence copy the one that must reach audit-core independently of the emitter, because there the actor being audited and the evidence source are the same component. Registration accepted on every proposed field — exact source, ["tenant:platform"], write true, read false, load-bearing, secret_policy redact. Prepared and inert: the scope overlay applies only to a sender the Secret already carries, asserted by test rather than by reading. Ingress ANDs namespace and pod label in one peer, following approval-engine rather than user-engine's older breadth. Gate House asked whether the record shape can carry a source-held-content declaration with a retrieval expectation, and asked for a straight answer rather than a rule the storage cannot meet. Both halves, which must travel together: It CAN carry the declaration. data is stored verbatim into details.data and hash-chained, so content_exists and custody need no schema change and become as tamper-evident as the commitment they accompany. It CANNOT detect non-production. audit-core performs no retrieval and its egress permits Postgres and DNS only. Detection happens at retrieval, by the reviewer; the stored declaration is what turns a blank into a failure attributable to the named custodian. Residual stated rather than left to be found: a custodian that never held the content can emit a false content_exists. audit-core validates the declaration's shape, never its truth — the same class as omission at source, and not closed by the chain, by attestation, or by T04/T06. A test asserts no egress to the emitter exists, because that claim silently stops being true if one appears. Cadence: reconciliation plus heartbeat is right for a mixed-volume source, with both scoped per class rather than per source — a per-source heartbeat is satisfied by the high-volume presentation stream and says nothing about a quiet month of dispositions. Bound: a compromised emitter suppresses the event and its own count together. Also recorded: commitment-only satisfies non-alteration and never reconstructability, in this repo's documents as in theirs; and tenant provenance under GH-DEC-2026-013 lands in the registration record, not the envelope, since audit-core checks a value the credential may write rather than resolving an identity claim. No secret was created and no production manifest applied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nb7Q6ZmXppNDkTWytfYqfv Assistant: claude-code Assistant-Model: opus Assistant-Process: 2069992@bnt-lap001 Assistant-Session: 167dd7f8-2a25-4be1-aa46-3b6f1a5f94c6
This commit is contained in:
parent
565e7e38d4
commit
c4016a70d5
8 changed files with 486 additions and 4 deletions
|
|
@ -93,6 +93,36 @@ spec:
|
||||||
---
|
---
|
||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: NetworkPolicy
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: audit-core-informed-decision-ingress
|
||||||
|
namespace: audit-core
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: audit-core
|
||||||
|
policyTypes: [Ingress]
|
||||||
|
ingress:
|
||||||
|
# AUDIT-WP-0009-T11 / AUDIT-IN-0003. Load-bearing presentation evidence
|
||||||
|
# under GH-DEC-2026-012 limit 3 and GH-DEC-2026-014. Both selectors belong
|
||||||
|
# to one peer and are therefore ANDed, following the approval-engine rule
|
||||||
|
# rather than user-engine's older namespace-only breadth.
|
||||||
|
#
|
||||||
|
# Note what this rule does NOT create: no egress from audit-core to
|
||||||
|
# informed-decision. The commitment-only record's custody declaration is
|
||||||
|
# carried, never dereferenced from here — audit-core makes no retrieval
|
||||||
|
# call, and the egress policy below is the proof.
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: informed-decision
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: informed-decision
|
||||||
|
ports:
|
||||||
|
- {protocol: TCP, port: 8080}
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
metadata:
|
metadata:
|
||||||
name: audit-core-operator-ingress
|
name: audit-core-operator-ingress
|
||||||
namespace: audit-core
|
namespace: audit-core
|
||||||
|
|
|
||||||
|
|
@ -21,5 +21,17 @@
|
||||||
"may_write": true,
|
"may_write": true,
|
||||||
"may_read": false,
|
"may_read": false,
|
||||||
"evidence_kind": "load-bearing"
|
"evidence_kind": "load-bearing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "informed-decision",
|
||||||
|
"sources": [
|
||||||
|
"informed-decision"
|
||||||
|
],
|
||||||
|
"tenants": [
|
||||||
|
"tenant:platform"
|
||||||
|
],
|
||||||
|
"may_write": true,
|
||||||
|
"may_read": false,
|
||||||
|
"evidence_kind": "load-bearing"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,35 @@ data:
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "user-engine",
|
"name": "user-engine",
|
||||||
"sources": ["user-engine"],
|
"sources": [
|
||||||
"tenants": ["*"],
|
"user-engine"
|
||||||
|
],
|
||||||
|
"tenants": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
"may_write": true,
|
"may_write": true,
|
||||||
"may_read": false
|
"may_read": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "approval-engine",
|
"name": "approval-engine",
|
||||||
"sources": ["approval-engine"],
|
"sources": [
|
||||||
"tenants": ["tenant:platform"],
|
"approval-engine"
|
||||||
|
],
|
||||||
|
"tenants": [
|
||||||
|
"tenant:platform"
|
||||||
|
],
|
||||||
|
"may_write": true,
|
||||||
|
"may_read": false,
|
||||||
|
"evidence_kind": "load-bearing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "informed-decision",
|
||||||
|
"sources": [
|
||||||
|
"informed-decision"
|
||||||
|
],
|
||||||
|
"tenants": [
|
||||||
|
"tenant:platform"
|
||||||
|
],
|
||||||
"may_write": true,
|
"may_write": true,
|
||||||
"may_read": false,
|
"may_read": false,
|
||||||
"evidence_kind": "load-bearing"
|
"evidence_kind": "load-bearing"
|
||||||
|
|
|
||||||
198
docs/informed-decision-source-registration.md
Normal file
198
docs/informed-decision-source-registration.md
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
# informed-decision source registration
|
||||||
|
|
||||||
|
`AUDIT-WP-0009-T11` · intake `AUDIT-IN-0003` · statute §9.6 · rulings
|
||||||
|
`GH-DEC-2026-012` (limit 3), `GH-DEC-2026-014`, `GH-DEC-2026-013`
|
||||||
|
|
||||||
|
The registration inputs for `informed-decision` as a distinct audit source: the
|
||||||
|
browser-facing approver surface that `approval-engine` deliberately does not
|
||||||
|
contain. Onboarded under `INTENT.md` principle 4 — declared ownership,
|
||||||
|
retention, access, export and evidence policy, not merely events arriving.
|
||||||
|
|
||||||
|
This file is the owner-side record. Everything in it is non-secret. No sender
|
||||||
|
token appears here, in Git, in State Hub, or in a workplan.
|
||||||
|
|
||||||
|
## Declaration
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Sender name | `informed-decision` |
|
||||||
|
| Permitted `source` | `informed-decision` (exact; no wildcard) |
|
||||||
|
| `evidence_kind` | **`load-bearing`** (§9.6 — GH-DEC-2026-012 limit 3 makes the presentation record the independent evidence of a binding act) |
|
||||||
|
| `completeness_trade` | none, and none is permitted — §9.6 requires emission atomicity of a load-bearing source |
|
||||||
|
| `may_write` | true |
|
||||||
|
| `may_read` | false — a source does not gain a read surface by emitting. See the reconciliation note below, which is where the temptation to grant one will come from |
|
||||||
|
| `secret_policy` | `redact` — accepted as proposed |
|
||||||
|
| `tenants` | `["tenant:platform"]` — accepted as proposed, with the provenance obligation below |
|
||||||
|
| Retention | no expiry set; recoverable history is the platform `data.backup` window, 30 days, `measured` |
|
||||||
|
| Custody class | `operational` — never `archive`, never WORM |
|
||||||
|
|
||||||
|
Accepted as proposed on every field. The proposal was correct; the substance of
|
||||||
|
this record is in what follows it.
|
||||||
|
|
||||||
|
## Event classes — one source, not two
|
||||||
|
|
||||||
|
Asked directly: is commitment-only content one event class or two, and does it
|
||||||
|
belong in the same class as other evidence at all.
|
||||||
|
|
||||||
|
**One source, distinct `type` values per class.** This is the same shape
|
||||||
|
`approval-engine` carries for its four §9.4 classes: audit-core stores classes
|
||||||
|
as actions on one source and derives nothing from them.
|
||||||
|
|
||||||
|
| Class | Volume | Why it matters |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| presentation | high — one per render | Carries the commitment. The only class carrying a custody declaration |
|
||||||
|
| disposition | low | **security-relevant** — the binding verb |
|
||||||
|
| stance application | low | **security-relevant** — what was applied to the act |
|
||||||
|
|
||||||
|
A separate sender for the commitment-bearing class was considered and refused.
|
||||||
|
Two senders would mean two credentials, two tenant scopes and two ingress rules
|
||||||
|
for one component whose defining property is that the actor and the evidence
|
||||||
|
source are the same — splitting them makes that single residual look like two
|
||||||
|
smaller ones. The distinction that matters is per class, and `type` carries it.
|
||||||
|
|
||||||
|
Commitment-only content does belong in the same store as other evidence,
|
||||||
|
because the custody guarantee it receives is the same one and no stronger. What
|
||||||
|
differs is what may be claimed of it, and that is stated below rather than
|
||||||
|
encoded in a separate class.
|
||||||
|
|
||||||
|
## GH-DEC-2026-014 limit 3: can this fabric carry it?
|
||||||
|
|
||||||
|
Gate House asked whether a source-held-content declaration with a retrieval
|
||||||
|
expectation is a thing the record shape can carry today, and asked for a
|
||||||
|
straight answer rather than a rule nobody can meet.
|
||||||
|
|
||||||
|
**It can carry the declaration. It cannot itself detect non-production.** Both
|
||||||
|
halves are load-bearing; neither should be read without the other.
|
||||||
|
|
||||||
|
**What is carried.** `data` is an arbitrary structured object stored verbatim,
|
||||||
|
normalized into the envelope's `details.data`, appended under the trigger and
|
||||||
|
linked into the hash chain like every other field. `content_exists` and
|
||||||
|
`custody` therefore need no schema change, no new column and no new surface.
|
||||||
|
Once stored, the chain detects any later alteration or truncation of them
|
||||||
|
exactly as it does for the commitment digest itself. So the declaration is as
|
||||||
|
tamper-evident as the thing it accompanies, which is the property the condition
|
||||||
|
needs — a custodian cannot quietly retract the assertion that content existed.
|
||||||
|
|
||||||
|
**What is not delivered, and must not be inferred.** audit-core performs no
|
||||||
|
retrieval. It holds no client for `informed-decision`, and its egress policy
|
||||||
|
permits Postgres and DNS and nothing else (`deploy/networkpolicies.yaml`) — the
|
||||||
|
new ingress rule adds no egress. So audit-core never learns whether a custodian
|
||||||
|
would in fact produce the content, and raises no finding when it would not.
|
||||||
|
Non-production becomes detectable **at retrieval, by the reviewer**, and the
|
||||||
|
stored declaration is what makes it a finding rather than a blank: the reviewer
|
||||||
|
holds a chained, timestamped statement that content existed and where custody
|
||||||
|
sat, so failure to produce is attributable to the named custodian. That is the
|
||||||
|
condition met in the form audit-core can honestly meet it.
|
||||||
|
|
||||||
|
**The residual, stated rather than left to be found.** A custodian that never
|
||||||
|
held the content can emit a false `content_exists`. audit-core validates the
|
||||||
|
declaration's shape, never its truth. This is the same class of defect as
|
||||||
|
omission at source — the archive cannot retrofit a property the boundary did
|
||||||
|
not have — and it is not closed by the chain, by attestation, or by T04/T06. It
|
||||||
|
narrows to this: the declaration converts an unattributable absence into an
|
||||||
|
attributable false statement, which is strictly better and is not the same as
|
||||||
|
proof.
|
||||||
|
|
||||||
|
**One design consequence for the emitter.** `secret_policy: redact` scans
|
||||||
|
`data`. If the `custody` locator is ever secret-shaped — a token in a URL, a
|
||||||
|
path that trips the scanner — it will be redacted out and the declaration will
|
||||||
|
reach the store without its pointer. The redaction is recorded in
|
||||||
|
`details.redaction.paths`, so this fails visibly rather than silently, but the
|
||||||
|
locator should be a stable non-secret identifier resolved by the custodian, not
|
||||||
|
a credentialed URL. Under commitment-only the exposure is small by
|
||||||
|
construction, as informed-decision observed; the custody field is the one place
|
||||||
|
that stops being true.
|
||||||
|
|
||||||
|
## Cadence: the shape for a mixed-volume source
|
||||||
|
|
||||||
|
Asked whether reconciliation per class as primary, plus heartbeat for the
|
||||||
|
low-volume classes, is right. **Yes, with one refinement and one bound.**
|
||||||
|
|
||||||
|
**Refinement — both are per class, not per source.** A per-source heartbeat
|
||||||
|
from a component emitting one presentation per render is nearly worthless: it
|
||||||
|
is satisfied by the high-volume class and says nothing about a quiet month of
|
||||||
|
dispositions. Heartbeat must be scoped to the low-volume classes individually,
|
||||||
|
carrying a *nothing to report* assertion for that class. Likewise reconciliation
|
||||||
|
counts must be keyed `(source, class, window)`, which is the shape
|
||||||
|
`AUDIT-WP-0009-T06` is being built to.
|
||||||
|
|
||||||
|
The high-volume class needs reconciliation too, and rate alone is not a
|
||||||
|
substitute for it. Rate monitoring detects a stream stopping; it does not detect
|
||||||
|
a stream missing the particular renders that mattered.
|
||||||
|
|
||||||
|
**The bound, which matters more here than for approval-engine.** Reconciliation
|
||||||
|
compares audit-core's counts against counts the source computes from its own
|
||||||
|
state. Where the threat is a compromised emitter — and GH-DEC-2026-012 limit 3
|
||||||
|
exists precisely because here the actor and the evidence source are one
|
||||||
|
component — a compromised `informed-decision` suppresses the event and its own
|
||||||
|
count together, and reconciliation agrees. Heartbeat inherits the same weakness:
|
||||||
|
a compromised source emits a truthful-looking *nothing to report*.
|
||||||
|
|
||||||
|
So reconciliation and heartbeat cover loss, accident, outage and drain failure,
|
||||||
|
which is most of what actually goes wrong. They do not cover the residual
|
||||||
|
informed-decision already declares and should not be described as covering it.
|
||||||
|
Nothing audit-core can build closes that; it needs an observer independent of
|
||||||
|
the emitter, which is out of scope here (§16).
|
||||||
|
|
||||||
|
**Sequencing.** T04 and T06 are open. informed-decision may declare this cadence
|
||||||
|
now and must not describe it as operating until they land — which is exactly
|
||||||
|
what the intake proposed, and is the right handling.
|
||||||
|
|
||||||
|
## Tenant provenance
|
||||||
|
|
||||||
|
`tenant:platform` is accepted. informed-decision notes that under
|
||||||
|
`GH-DEC-2026-013` the value arrives by the declared-gap route
|
||||||
|
(registration-bound rather than directory-sourced) and asks whether the
|
||||||
|
registration or envelope inherits the obligation to record which route it came
|
||||||
|
by.
|
||||||
|
|
||||||
|
**Not in this envelope, and here is why.** audit-core's `tenant` is not an
|
||||||
|
asserted identity claim it resolves; it is a value the credential is *permitted
|
||||||
|
to write*, checked by exact string equality against this registration. The
|
||||||
|
provenance question — how a principal came to hold `tenant:platform` — is a
|
||||||
|
property of the token issuer's claim, and recording a route in the audit event
|
||||||
|
would be audit-core restating something it did not observe. That is the same
|
||||||
|
error as claiming an event occurred.
|
||||||
|
|
||||||
|
Where it does land is here, in the registration record, which is the document
|
||||||
|
that says why this sender may write this tenant. Recorded: the scope is exact,
|
||||||
|
single-valued, and its authority is `GH-DEC-2026-013`'s bounded gap rather than
|
||||||
|
a populated directory record. If the gap closes and the terminal
|
||||||
|
directory-sourced shape lands, this entry should be revisited rather than
|
||||||
|
assumed still correct.
|
||||||
|
|
||||||
|
## Applied in this repository
|
||||||
|
|
||||||
|
| Input | Where |
|
||||||
|
| --- | --- |
|
||||||
|
| Non-secret scope | `deploy/senders-scope.json` + `deploy/senders-scope.yaml` (lockstep) |
|
||||||
|
| Receiver ingress | `deploy/networkpolicies.yaml`, `audit-core-informed-decision-ingress` |
|
||||||
|
| Evidence kind in the model | `audit_core/senders.py`, `SenderIdentity.evidence_kind` |
|
||||||
|
|
||||||
|
The scope overlay only applies to a sender the Secret already carries, so this
|
||||||
|
entry is **inert until the token exists** — asserted by test, not by reading.
|
||||||
|
The ingress rule ANDs namespace and pod label in one `from` peer.
|
||||||
|
`user-engine`'s and `approval-engine`'s policies are unchanged.
|
||||||
|
|
||||||
|
## Still owed by others
|
||||||
|
|
||||||
|
| Input | Owner | Note |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Sender token, custody path via `warden route` | OpenBao (`railiance-platform`) | audit-core never holds or transports it |
|
||||||
|
| Protected registry entry with explicit `secret_policy: redact` | `railiance-platform` | The overlay deliberately does not override `secret_policy`; provisioning must set it |
|
||||||
|
| Applying the manifests to `railiance01` | operator | audit-core does not apply cluster changes unprompted |
|
||||||
|
| The `content_exists` / `custody` fields on the emitted payload | `informed-decision` | audit-core carries them; it does not require, validate or dereference them |
|
||||||
|
|
||||||
|
## What Audit Core will and will not claim about this stream
|
||||||
|
|
||||||
|
**Will:** every event it accepted is stored append-only, and the hash chain
|
||||||
|
detects alteration or truncation of what it received — including of the
|
||||||
|
`content_exists` and `custody` declarations.
|
||||||
|
|
||||||
|
**Will not:** that the archive proves a presentation occurred, or that the
|
||||||
|
absence of one proves it did not. And specifically, per GH-DEC-2026-014:
|
||||||
|
audit-core will not describe a commitment-only record as satisfying
|
||||||
|
**reconstructability**. It satisfies non-alteration of the commitment. About
|
||||||
|
what was committed to it establishes nothing, except conditionally — if a
|
||||||
|
document is later produced, whether it is the one. No conformance claim, ITC-CAP
|
||||||
|
case, or `docs/integrity.md` statement may say otherwise.
|
||||||
|
|
@ -1,5 +1,74 @@
|
||||||
# Intake records
|
# Intake records
|
||||||
|
|
||||||
|
## AUDIT-IN-0003 — Register informed-decision as a load-bearing sender; commitment-only payload doctrine
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
id: AUDIT-IN-0003
|
||||||
|
kind: intake
|
||||||
|
title: 'Register informed-decision as a load-bearing sender, and answer GH-DEC-2026-014
|
||||||
|
limit 3'
|
||||||
|
status: closed
|
||||||
|
origin: cross-repo
|
||||||
|
origin_ref: INFD-WP-0001-T08 / GH-DEC-2026-012 / GH-DEC-2026-014
|
||||||
|
priority: high
|
||||||
|
owner: audit-core
|
||||||
|
requested_by: informed-decision
|
||||||
|
description: >
|
||||||
|
informed-decision is the browser-facing approver surface approval-engine
|
||||||
|
deliberately does not contain, ruled PEP-shaped by GH-DEC-2026-012. Limit 3
|
||||||
|
of that ruling requires its evidence copy to reach audit-core independently
|
||||||
|
of the emitter, because the actor being audited and the evidence source are
|
||||||
|
the same component.
|
||||||
|
|
||||||
|
Requested: (1) sender registration — source informed-decision exact, tenants
|
||||||
|
[tenant:platform], write true, read false, evidence_kind load-bearing,
|
||||||
|
secret_policy redact; (2) whether reconciliation per class as primary plus
|
||||||
|
heartbeat for the low-volume classes is the right cadence form for a
|
||||||
|
mixed-volume source; (3) whether commitment-only content is one event class
|
||||||
|
or two, and whether it belongs in the same class as other evidence.
|
||||||
|
|
||||||
|
GH-DEC-2026-014 attached a condition addressed to audit-core: non-production
|
||||||
|
of committed content must be detectable as a FINDING rather than present as
|
||||||
|
an absence, so the record must carry an assertion that committed content
|
||||||
|
exists and where custody sits. Gate House asked audit-core to say plainly
|
||||||
|
whether the record shape can express that, rather than have a rule written
|
||||||
|
that the storage cannot meet.
|
||||||
|
created: '2026-09-09'
|
||||||
|
updated: '2026-09-10'
|
||||||
|
outcome: registered-with-a-stated-bound
|
||||||
|
closed: '2026-09-10'
|
||||||
|
resolution: 'Registration accepted on every proposed field; the proposal was
|
||||||
|
correct and the substance is in the bounds. Prepared and inert pending token,
|
||||||
|
protected-registry entry and operator apply —
|
||||||
|
docs/informed-decision-source-registration.md, deploy/senders-scope.{json,yaml},
|
||||||
|
audit-core-informed-decision-ingress. (1) GH-DEC-2026-014 limit 3: the fabric
|
||||||
|
CAN carry the declaration — data is stored verbatim into details.data and
|
||||||
|
hash-chained, so content_exists and custody need no schema change and become
|
||||||
|
as tamper-evident as the commitment they accompany. It CANNOT detect
|
||||||
|
non-production: audit-core performs no retrieval and its egress permits
|
||||||
|
Postgres and DNS only. Detection happens at retrieval by the reviewer, and the
|
||||||
|
stored declaration is what makes non-production attributable rather than a
|
||||||
|
blank. Residual: a custodian that never held the content can emit a false
|
||||||
|
content_exists; audit-core validates the declaration shape, never its truth —
|
||||||
|
same class as omission at source, not closed by chain, attestation or T04/T06.
|
||||||
|
(2) Cadence: reconciliation plus heartbeat is right, with both scoped PER
|
||||||
|
CLASS rather than per source — a per-source heartbeat is satisfied by the
|
||||||
|
high-volume presentation stream and says nothing about a quiet month of
|
||||||
|
dispositions. Bound: a compromised emitter suppresses the event and its own
|
||||||
|
count together, so neither control covers the limit-3 residual. Depends on
|
||||||
|
T04/T06; declare now, do not describe as operating. (3) One source, distinct
|
||||||
|
type values per class, following approval-engine four-class shape; a second
|
||||||
|
sender would split one residual into two smaller-looking ones. (4) Tenant
|
||||||
|
provenance is recorded in the registration document, not the envelope:
|
||||||
|
audit-core does not resolve the tenant claim, it checks a value the credential
|
||||||
|
is permitted to write, and restating an unobserved route in an event is the
|
||||||
|
same error as claiming an event occurred. (5) Stated for all documents:
|
||||||
|
commitment-only satisfies non-alteration and never reconstructability, and no
|
||||||
|
audit-core document or conformance claim may describe it otherwise.'
|
||||||
|
recorded_in: docs/informed-decision-source-registration.md
|
||||||
|
work: AUDIT-WP-0009-T11
|
||||||
|
```
|
||||||
|
|
||||||
## AUDIT-IN-0002 — Register tenant-engine as an attributive audit-core sender
|
## AUDIT-IN-0002 — Register tenant-engine as an attributive audit-core sender
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|
|
||||||
|
|
@ -59,3 +59,41 @@ def test_user_engine_sender_ingress_is_unchanged_by_the_new_sender():
|
||||||
)
|
)
|
||||||
assert "kubernetes.io/metadata.name: user-engine" in policy
|
assert "kubernetes.io/metadata.name: user-engine" in policy
|
||||||
assert "approval-engine" not in policy
|
assert "approval-engine" not in policy
|
||||||
|
|
||||||
|
|
||||||
|
def test_informed_decision_ingress_is_bound_to_namespace_and_pod_labels():
|
||||||
|
"""AUDIT-WP-0009-T11. A second load-bearing source gets the narrow rule too."""
|
||||||
|
documents = (ROOT / "deploy" / "networkpolicies.yaml").read_text().split("\n---\n")
|
||||||
|
policy = next(
|
||||||
|
document
|
||||||
|
for document in documents
|
||||||
|
if "name: audit-core-informed-decision-ingress" in document
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_peer = """ - namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: informed-decision
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: informed-decision"""
|
||||||
|
assert expected_peer in policy
|
||||||
|
assert policy.count(" - namespaceSelector:") == 1
|
||||||
|
assert " - {protocol: TCP, port: 8080}" in policy
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_egress_to_informed_decision_is_created():
|
||||||
|
"""The custody declaration is carried, never dereferenced.
|
||||||
|
|
||||||
|
GH-DEC-2026-014 limit 3 is met by storing an attributable declaration, not
|
||||||
|
by audit-core retrieving content. If an egress rule to the emitter ever
|
||||||
|
appears, the claim in docs/informed-decision-source-registration.md that
|
||||||
|
audit-core performs no retrieval has silently stopped being true.
|
||||||
|
"""
|
||||||
|
documents = (ROOT / "deploy" / "networkpolicies.yaml").read_text().split("\n---\n")
|
||||||
|
egress = next(
|
||||||
|
document for document in documents if "name: audit-core-egress" in document
|
||||||
|
)
|
||||||
|
assert "informed-decision" not in egress
|
||||||
|
assert "approval-engine" not in egress
|
||||||
|
# Postgres and DNS only.
|
||||||
|
assert egress.count(" - namespaceSelector:") == 2
|
||||||
|
|
|
||||||
|
|
@ -339,3 +339,62 @@ def test_user_engine_evidence_kind_is_not_asserted_on_its_behalf():
|
||||||
scope = json.loads(SCOPE_FILE.read_text())
|
scope = json.loads(SCOPE_FILE.read_text())
|
||||||
entry = next(e for e in scope if e["name"] == "user-engine")
|
entry = next(e for e in scope if e["name"] == "user-engine")
|
||||||
assert "evidence_kind" not in entry
|
assert "evidence_kind" not in entry
|
||||||
|
|
||||||
|
|
||||||
|
# --- AUDIT-WP-0009-T11: informed-decision registration inputs --------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_informed_decision_is_declared_load_bearing():
|
||||||
|
scope = json.loads(SCOPE_FILE.read_text())
|
||||||
|
entry = next(e for e in scope if e["name"] == "informed-decision")
|
||||||
|
assert entry["evidence_kind"] == "load-bearing"
|
||||||
|
assert entry["sources"] == ["informed-decision"]
|
||||||
|
assert entry["tenants"] == ["tenant:platform"]
|
||||||
|
assert entry["may_read"] is False
|
||||||
|
# §9.6 permits no completeness trade for a load-bearing source.
|
||||||
|
assert "completeness_trade" not in entry
|
||||||
|
assert "tokens" not in entry and "token" not in entry
|
||||||
|
|
||||||
|
|
||||||
|
def test_informed_decision_scope_entry_admits_nothing_without_a_token():
|
||||||
|
"""Safe to land ahead of the credential, asserted rather than read."""
|
||||||
|
registry = SenderRegistry.from_env({
|
||||||
|
"AUDIT_CORE_SENDERS": json.dumps(
|
||||||
|
[{"name": "user-engine", "tokens": ["live"], "sources": ["user-engine"]}]
|
||||||
|
),
|
||||||
|
"AUDIT_CORE_SENDERS_SCOPE_PATH": str(SCOPE_FILE),
|
||||||
|
})
|
||||||
|
assert "informed-decision" not in [i.name for i in registry.identities]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"tenant", ["platform", "tenant:coulomb", "tenant:Platform", "tenant:platform "]
|
||||||
|
)
|
||||||
|
def test_informed_decision_scope_restricts_a_stale_wildcard_registration(tenant):
|
||||||
|
registry = SenderRegistry.from_env({
|
||||||
|
"AUDIT_CORE_SENDERS": json.dumps([{
|
||||||
|
"name": "informed-decision", "tokens": ["fixture-only"],
|
||||||
|
"sources": ["*"], "tenants": ["*"],
|
||||||
|
"may_read": True, "secret_policy": "redact",
|
||||||
|
}]),
|
||||||
|
"AUDIT_CORE_SENDERS_SCOPE_PATH": str(SCOPE_FILE),
|
||||||
|
})
|
||||||
|
identity = registry.authenticate("Bearer fixture-only")
|
||||||
|
assert identity.permits_tenant("tenant:platform")
|
||||||
|
assert not identity.permits_tenant(tenant)
|
||||||
|
assert identity.permits_source("informed-decision")
|
||||||
|
assert not identity.permits_source("approval-engine")
|
||||||
|
assert identity.may_write and not identity.may_read
|
||||||
|
assert identity.is_load_bearing
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_load_bearing_source_cannot_be_given_a_completeness_trade():
|
||||||
|
"""informed-decision declares no trade and may not acquire one by overlay."""
|
||||||
|
with pytest.raises(ValueError, match="completeness_trade"):
|
||||||
|
SenderIdentity(
|
||||||
|
name="informed-decision",
|
||||||
|
tokens=("t",),
|
||||||
|
sources=frozenset({"informed-decision"}),
|
||||||
|
evidence_kind="load-bearing",
|
||||||
|
completeness_trade="emits after commit",
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -287,6 +287,62 @@ 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
|
the State Hub now. Add a conformance script and test in the shape ops-warden
|
||||||
offered estate-wide.
|
offered estate-wide.
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: AUDIT-WP-0009-T11
|
||||||
|
status: progress
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
Register `informed-decision` as a distinct load-bearing source under
|
||||||
|
`AUDIT-IN-0003`, and answer the doctrine question `GH-DEC-2026-014` put to
|
||||||
|
audit-core: whether the record shape can carry a source-held-content
|
||||||
|
declaration with a retrieval expectation.
|
||||||
|
|
||||||
|
`informed-decision` is the browser-facing approver surface. `GH-DEC-2026-012`
|
||||||
|
limit 3 makes its evidence copy the one that must reach audit-core
|
||||||
|
independently of the emitter, because there the actor being audited and the
|
||||||
|
evidence source are the same component. `GH-DEC-2026-014` then granted
|
||||||
|
commitment-only payloads for Stage 1 under a condition Gate House added: the
|
||||||
|
record must assert that committed content **exists** and where **custody**
|
||||||
|
sits, so non-production is a finding attributable to the custodian rather than
|
||||||
|
an unremarkable blank.
|
||||||
|
|
||||||
|
**Done 2026-09-10, prepared and inert:**
|
||||||
|
`docs/informed-decision-source-registration.md`, scope entries in
|
||||||
|
`deploy/senders-scope.{json,yaml}`, and
|
||||||
|
`audit-core-informed-decision-ingress` in `deploy/networkpolicies.yaml`
|
||||||
|
(namespace AND pod label in one peer, following approval-engine rather than
|
||||||
|
`user-engine`'s older breadth). Every proposed field accepted as-is: exact
|
||||||
|
source, `["tenant:platform"]`, write true, read false, `load-bearing`,
|
||||||
|
`secret_policy: redact`.
|
||||||
|
|
||||||
|
**The answer to Gate House's condition, in two halves that must travel
|
||||||
|
together.** The fabric *can* carry the declaration — `data` is stored verbatim
|
||||||
|
into `details.data` and chained, so `content_exists` and `custody` need no
|
||||||
|
schema change and become as tamper-evident as the commitment they accompany. It
|
||||||
|
*cannot* detect non-production: audit-core performs no retrieval and its egress
|
||||||
|
policy permits Postgres and DNS only, so it never learns whether a custodian
|
||||||
|
would produce. Detection happens at retrieval, by the reviewer, and the stored
|
||||||
|
declaration is what turns a blank into an attributable failure. Residual stated
|
||||||
|
rather than left to be found: a custodian that never held the content can emit a
|
||||||
|
false `content_exists`; audit-core validates the declaration's shape, never its
|
||||||
|
truth. Not a requirement nobody can meet, and not the stronger thing it might be
|
||||||
|
mistaken for.
|
||||||
|
|
||||||
|
**Cadence answered:** reconciliation-per-class plus heartbeat for the low-volume
|
||||||
|
classes is the right shape for a mixed-volume source, with both scoped **per
|
||||||
|
class** rather than per source — a per-source heartbeat is satisfied by the
|
||||||
|
high-volume presentation stream and says nothing about a quiet month of
|
||||||
|
dispositions. Bound stated: where the emitter itself is compromised, it
|
||||||
|
suppresses the event and its own count together, so neither control covers the
|
||||||
|
residual `GH-DEC-2026-012` limit 3 exists for. Depends on T04/T06 for the
|
||||||
|
detection half; declaring the cadence now is correct, describing it as operating
|
||||||
|
is not.
|
||||||
|
|
||||||
|
Remaining: token custody, the protected registry entry, operator manifest
|
||||||
|
application, and live ingestion evidence. No secret was created and no
|
||||||
|
production manifest applied.
|
||||||
|
|
||||||
|
|
||||||
## Acceptance
|
## Acceptance
|
||||||
|
|
||||||
- No custody claim is returned unconditionally where `docs/integrity.md`
|
- No custody claim is returned unconditionally where `docs/integrity.md`
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue