diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 9bda84a..2bd1962 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -13,8 +13,8 @@ | task | INFD-WP-0001-T02 | done | — | workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md | | task | INFD-WP-0001-T03 | done | — | workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md | | task | INFD-WP-0001-T04 | done | — | workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md | -| task | INFD-WP-0001-T05 | todo | — | workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md | -| task | INFD-WP-0001-T06 | progress | — | workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md | +| task | INFD-WP-0001-T05 | done | — | workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md | +| task | INFD-WP-0001-T06 | done | — | workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md | | task | INFD-WP-0001-T07 | todo | — | workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md | | task | INFD-WP-0001-T08 | todo | — | workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md | | intake | INFD-IN-0001 | open | blue | intakes/intakes.md | diff --git a/docs/evidence-path-design.md b/docs/evidence-path-design.md new file mode 100644 index 0000000..c31cca0 --- /dev/null +++ b/docs/evidence-path-design.md @@ -0,0 +1,184 @@ +# The independent evidence path — design and decision request + +**From:** informed-decision +**Raised:** 2026-09-09 +**Intake:** `INFD-IN-0003` +**Blocks:** `INFD-WP-0001-T08` +**Governing:** `GH-DEC-2026-012` limit **L3-independent-evidence-path** +**Addressed to:** `audit-core` (registration and payload), `gate-house` (the +content question, if it is doctrine) + +--- + +## 1. The obligation + +`GH-DEC-2026-012` permitted this repository to emit a presentation claim under +three limits. The third: + +> The evidence copy reaches `audit-core` **independently** of you. Claim +> endpoint and evidence path are different things and neither substitutes for +> the other. […] audit evidence is protected from the actor being audited, and +> here the actor and the source are the same component. The copy that is +> evidence must not be reachable only through the party it is evidence about. + +Gate House named this the limit that matters most in our case, and we agree: we +render the view, we compute the hash over it, and we are the party an auditor is +checking. A design where the only route to the evidence runs through us is not +evidence. + +`docs/specs/ArchitectureBlueprint.md` `O-02` records the mechanism as unchosen +and states T08 must not ship without it, because "we will add the independent +path later" is how limit 3 becomes limit-3-in-principle. + +## 2. What is settled and not being asked + +**Read independence is the property.** A reader obtains the evidence from +`audit-core` without asking us and without our cooperation. Our claim endpoint +serves presentation claims to consumers; it is a different surface with a +different purpose and does not discharge this obligation. + +**The write path is a local transactional outbox.** Adopted from +`approval-engine`'s reasoning rather than rediscovered: the queue lives in our +own store, written in the **same transaction** as the state change it evidences. +Emit-after-commit is a defect. The queue is local so an `audit-core` outage +cannot block a binding act, for the same reason it must not block a revocation +upstream. + +**Atomicity covers accident, not compromise.** It does not close the residual in +`layer.yaml` and will not be described as doing so. + +**Cadence is owed.** Our `layer.yaml` declares presentation evidence +**load-bearing**, so §9.6 requires a declared cadence. See §5. + +## 3. The question: what travels + +A presentation record is not like an approval transition. It contains, or points +at, the **brief and packet material actually shown to a human** — which is +frequently commercially or personally sensitive. So "emit the evidence" is +ambiguous in a way it was not for `approval-engine`, and the ambiguity has to be +resolved before we build, not after we have shipped a payload shape. + +Three candidates. + +### (a) Commitment only — hash and metadata + +Emit `memo_id`, `memo_version`, `presentation_id`, `principal`, `locale`, +`ui_release`, `rendered_at`, `acked_highlight_ids`, `view_hash`, +`awareness_hash`, the co-referenced approval identifier, the disposition verb, +and the stance application. **Not** the brief, not the packet, not the highlight +text. + +- **Proves independently:** that this principal was shown a view whose canonical + form hashes to H, at time T, and bound act A against approval X. +- **Cost:** the *content* lives only here. An auditor can verify that content we + later produce matches H, and can detect alteration — but if we destroy it, + nobody can ever see what was shown. Audit-core proves the commitment, not the + material. +- **Also:** this is strictly stronger than storing nothing, because it removes + our ability to alter the record after the fact. It does not remove our ability + to erase it. + +### (b) Full binding document + +Emit the canonical binding document itself, from which `view_hash` is +recomputable. + +- **Proves independently:** everything (a) does, plus what was actually shown, + surviving our compromise or our deletion. +- **Cost:** `audit-core` now holds business-sensitive and possibly personal + content, under retention profiles and export entitlements designed for audit + events rather than for the substance of commercial decisions. Its + secret-finding and redaction surfaces would be operating on material where a + redaction is not a defect-signal but routine. That is a meaningful change in + what `audit-core` is holding, and it is theirs to accept or refuse — not ours + to assume. +- **Also:** an L4 instrument under (b) puts contract text in the audit fabric. + +### (c) Split — commitment to `audit-core`, content to a separate evidence store + +(a)'s payload to `audit-core`, plus the full bundle to a store that is neither +`audit-core` nor us. + +- **Proves independently:** as (b). +- **Cost:** a store with no owner. There is no such component today, and + inventing one to satisfy a limit is how a surface acquires an operational axis + it was never given. §16 also decided against stronger archival custody for + approval evidence, and we should not route around that decision by building a + parallel archive under another name. + +## 4. What we propose + +**(a) for Stage 1, with the gap stated rather than papered over.** + +Reasoning: + +1. It discharges limit 3 as written. The copy that is evidence — the commitment + that a presentation with hash H happened to principal P at time T — is + reachable from `audit-core` without us. +2. It does not silently expand what `audit-core` holds. Expanding that is a + decision for its owner, and (b) presented as a fait accompli would be exactly + the drift we asked gate-house to prevent in a different context. +3. It is honest about what it does not close: **we can still erase the + content.** That residual is adjacent to the one `layer.yaml` already declares + (a compromised surface presents X and attests Y) and belongs in the same + place — declared, tracked, not claimed as closed. +4. (b) remains available and is a payload change, not a redesign, if the estate + later decides a commitment is insufficient. + +**We are not asking to defer (b) indefinitely.** We are asking whether the +content question is `audit-core`'s to answer as its custodian, or `gate-house`'s +as doctrine. If it is doctrine and the ruling is (b), we will implement (b). + +## 5. Cadence + +Presentation evidence is declared load-bearing, so §9.6 requires a cadence and +rate monitoring is not automatically the right form. + +Note a difference from `approval-engine`: their classes are genuinely low-volume +(a handful of revocations a month), which is why heartbeat is their answer. +Ours are mixed: + +- **Presentations** are relatively high-volume — one per render. +- **Dispositions and stance applications** are low-volume and are the + security-relevant ones. + +So we propose **reconciliation as the primary form**, per class: our own count +of presentations, dispositions and stance applications compared against +`audit-core`'s event count per class, with divergence a finding. Plus a +**heartbeat for the low-volume classes**, because a quiet month of dispositions +is indistinguishable from suppression by rate alone — the same argument +`approval-engine` makes. + +This depends on `AUDIT-WP-0009` T04/T06, which we understand are open. We are +declaring the cadence now and will not describe it as operating until those +land. + +## 6. What we need + +| From | Decision | +| --- | --- | +| `audit-core` | Sender registration for `informed-decision`, and whether payload (a) or (b) is acceptable to hold | +| `audit-core` | Whether reconciliation-plus-heartbeat is the right cadence form for a mixed-volume source | +| `gate-house` | Only if the content question is doctrine rather than custody | + +Proposed registration, following `approval-engine`'s form: + +| Field | Value | +| --- | --- | +| Sender and permitted source | `informed-decision` (exact) | +| Tenants | `["tenant:platform"]` — but see the open tenant question, `INFD-IN-0002` | +| Write / read | `true` / `false` | +| Evidence kind | `load-bearing` | +| Cadence form | reconciliation, plus heartbeat for low-volume classes | +| Secret policy | `redact` | + +`redact` chosen for the same reason `approval-engine` chose it: a receiver +should retain a legitimate record with an accidentally secret-shaped field +removed rather than reject the event and leave delivery pending. Under payload +(a) the exposure is small by construction; under (b) `redact` would be doing +substantially more work, which is itself an argument for (a). + +**Note the coupling:** the tenant on our registration is the same unresolved +question as `INFD-IN-0002`. If a human token cannot carry `tenant:platform`, our +audit registration tenant and our approval tenant should be settled together +rather than separately. diff --git a/docs/specs/ArchitectureBlueprint.md b/docs/specs/ArchitectureBlueprint.md index ce4b7f9..a8b264d 100644 --- a/docs/specs/ArchitectureBlueprint.md +++ b/docs/specs/ArchitectureBlueprint.md @@ -131,10 +131,19 @@ Consequences, binding on implementation: 4. **Atomicity covers accident, not compromise.** It does not close the residual in `layer.yaml`, and must never be described as doing so. -Open for implementation: whether the independent path is `audit-core` pull, -a separately-credentialed push, or a third party drain. **T08 must not ship -without one chosen**, because "we will add the independent path later" is how -limit 3 quietly becomes limit 3-in-principle. +**Cadence is owed**, because `layer.yaml` declares presentation evidence +load-bearing (§9.6). The form differs from `approval-engine`'s: their classes are +genuinely low-volume, so a heartbeat is the answer. Ours are mixed — +presentations are high-volume (one per render), while dispositions and stance +applications are low-volume and are the security-relevant ones. Proposed: +reconciliation per class as the primary form, plus a heartbeat for the +low-volume classes, since a quiet month of dispositions is indistinguishable +from suppression by rate alone. Depends on `AUDIT-WP-0009` T04/T06; declared, +not claimed operating. + +What travels is the open question — see `docs/evidence-path-design.md` and O-02. +**T08 must not ship without it chosen**, because "we will add the independent +path later" is how limit 3 quietly becomes limit 3-in-principle. --- @@ -236,8 +245,15 @@ condition that it holds *only* because registrations are static and deployment-owned should be written into the contract, not left as reasoning in a message. -**O-02 — The independent evidence path.** Its mechanism is unchosen. Must be -resolved before T08 ships. +**O-02 — The independent evidence path.** Design and decision request written: +`docs/evidence-path-design.md`, filed as `INFD-IN-0003`. Read independence and +the local transactional outbox are settled; the open question is **what +travels**, because a presentation record carries the brief and packet material +actually shown to a human. Proposal is commitment-only (hashes, principal, +timestamps, acks, co-referenced approval id) for Stage 1, with the erasure +residual declared rather than papered over. Awaiting `audit-core` on the payload +and the sender registration, and `gate-house` only if the content question is +doctrine. Must be resolved before T08 ships. **O-03 — Requester identity for an engine-originated memo.** `approval-engine` does not model a requester. Likely the approval's `principal`; needs confirming diff --git a/intakes/intakes.md b/intakes/intakes.md index 011c679..76d5392 100644 --- a/intakes/intakes.md +++ b/intakes/intakes.md @@ -6,7 +6,7 @@ id: INFD-IN-0001 kind: intake title: Layer placement and approver-UI ownership -status: open +status: closed origin: coordination origin_ref: INFD-WP-0001-T02 priority: high @@ -18,6 +18,23 @@ tags: - cross-repo created: '2026-09-09' updated: '2026-09-09' +resolution: >- + Resolved 2026-09-09 as GH-DEC-2026-012 (gate-house@0a1d1d9), within a day of + filing. All three ruled. R1: PEP-shaped, confirmed as proposed; the ruling + settles the shape, the layer stays this repository's to declare, so layer.yaml + is written in its own voice. R2: yes to a presentation claim and no second + catalog row — PEP and PIP are shapes a repository has — under three limits now + declared and tested in layer.yaml; limit 2 (never an input to the decision it + presents for) is load-bearing, since the self-dealing argument was accepted + because it holds. R3: option (b) as proposed, with the authority rule written + down — the binding digest is authoritative for what the request is, view_hash + only for what was shown, neither substitutable, and a disagreement between + them is a finding against the presenting surface rather than a fact about the + request. Linkage is co-reference; (c) was refused because nesting reproduces + the GH-DEC-2026-008 hash cycle. Also directed: build the stance map to v0.8 + obligation 3 rather than migrate later, and inherit GH-DEC-2026-010 as a + declared open gap. Delivered in layer.yaml, pep-stance.yaml, + informed_decision/stance.py and tests/test_layer_conformance.py. description: >- approval-engine names an approvals inbox under Non-Goals, leaving the browser-facing approver UI unowned; key-cape KEY-WP-0013-T02 is blocked on a @@ -38,3 +55,98 @@ description: >- Blocks INFD-WP-0001 T05 and T07; T03, T04 and T06 proceed regardless. state_hub_intake_id: "01a08610-f458-7fd5-b284-26a65d1d73c2" ``` + +## INFD-IN-0002 — Human access tokens cannot carry `tenant:platform` + +```yaml +id: INFD-IN-0002 +kind: intake +title: Human access tokens cannot carry tenant:platform +status: open +origin: coordination +origin_ref: INFD-WP-0001-T07 +priority: high +owner: key-cape +repo: informed-decision +lane: blue +tags: +- decision-request +- cross-repo +- blocker +created: '2026-09-09' +updated: '2026-09-09' +description: >- + Raised by key-cape (KEY-WP-0013-T05) while reviewing the approver client + shape, and it blocks INFD-WP-0001-T07. The tenant claim on a human token + resolves from the directory user record via effectiveTenant(user); no adapter + populates domain.User.Tenant, so every human token falls back to the platform + default tenant:coulomb. The per-client tenant field, which is how the two + approval service clients carry tenant:platform, is read only on the + client_credentials path. approval-engine compares tenant by exact string + equality and pins near-miss spellings as refused, so an approver token issued + today would be rejected and the failure would surface as a rejected approval + rather than as a registration defect. Two resolutions: directory-sourced + (tenant becomes a property of the person and changes everywhere, needs a + directory attribute and an owner for who is a platform-zone human), or + registration-bound and fail-closed (symmetric with the service registrations + and with decision 5ed3fb35, but writes a cross-tenant capability into the + issuer). informed-decision prefers registration-bound: under it the tenant is + a property of the surface and its registration, which is exactly what the + pre-sign binding slice commits, whereas directory-sourced makes tenant + describe the person, which is closer to awareness than to binding. key-cape + leans the same way but will not implement either unilaterally. Not this + repository's to decide alone; routed to key-cape, approval-engine and + gate-house. If registration-bound is chosen, the condition that it holds only + because registrations are static and deployment-owned should be written into + the contract rather than left as reasoning in a message. Blocks T07; the + client_id and callback URI will not be published until it is resolved, since + registering a client that fails closed at first use is the failure key-cape + flagged. +``` + +## INFD-IN-0003 — The independent evidence path: what travels to audit-core + +```yaml +id: INFD-IN-0003 +kind: intake +title: The independent evidence path — what travels to audit-core +status: open +origin: residual +origin_ref: INFD-WP-0001-T05 +priority: high +owner: audit-core +repo: informed-decision +lane: blue +tags: +- decision-request +- cross-repo +created: '2026-09-09' +updated: '2026-09-09' +description: >- + GH-DEC-2026-012 limit L3 requires the evidence copy to reach audit-core + independently of informed-decision, because here the actor being audited and + the evidence source are the same component. Read independence and a local + transactional outbox are settled and not in question. The open question is + what travels, and it is sharper here than for approval-engine because a + presentation record contains the brief and packet material actually shown to a + human, which is frequently commercially or personally sensitive. Three + candidates with costs are in docs/evidence-path-design.md: (a) commitment only + — hashes, principal, timestamps, acks, co-referenced approval id — which + discharges limit 3 and removes our ability to alter the record but not to + erase the content; (b) the full binding document, which survives our + compromise but puts commercial and personal material into the audit fabric + under retention and export entitlements designed for audit events, a + meaningful change in what audit-core holds and its owner's to accept or + refuse; (c) a split with a separate evidence store, refused here because that + store has no owner and inventing one routes around the §16 decision against + stronger archival custody. informed-decision proposes (a) for Stage 1 with the + erasure residual declared alongside the existing compromised-surface residual, + and asks whether the content question is audit-core's as custodian or + gate-house's as doctrine. Also requests a sender registration and asks whether + reconciliation plus a heartbeat for low-volume classes is the right cadence + form for a mixed-volume source — presentations are high-volume, dispositions + and stance applications are low-volume and are the security-relevant ones. + Cadence depends on AUDIT-WP-0009 T04/T06 and is declared, not claimed + operating. The registration tenant is coupled to INFD-IN-0002. Blocks + INFD-WP-0001-T08. +``` diff --git a/workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md b/workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md index 2d8a7bd..6994289 100644 --- a/workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md +++ b/workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md @@ -352,6 +352,16 @@ registration is accepted by `approval-engine`'s verifier; `KEY-WP-0013-T02` is unblocked. **This is the task that discharges the gap that created this repository.** +2026-09-09: blocked on `INFD-IN-0002`. `key-cape` found that a human access +token cannot carry `tenant:platform` today — the tenant claim resolves from a +directory record no adapter populates, so every human token falls back to +`tenant:coulomb`, which `approval-engine` refuses by exact match. Registering +the client before this is resolved would ship a login that fails closed at first +use, and the failure would present as a rejected approval rather than as a +registration defect. Position stated (registration-bound) with an +evidence-model reason, and routed — it writes a cross-tenant capability into the +issuer, so it is not this repository's to decide alone. + ## Walking skeleton — one approval, end to end ```task @@ -380,6 +390,11 @@ act is permitted. Gated externally on `approval-engine` `APPROVAL-WP-0002-T01` reaching `done` and on the service being deployed with an origin this surface can reach. +2026-09-09: additionally gated on `INFD-IN-0003` — `GH-DEC-2026-012` limit 3 +requires the evidence copy to reach `audit-core` independently of this +component, and the payload question is open. Design and decision request in +`docs/evidence-path-design.md`. This task must not ship before it is answered. + ## Known risks - **T02 is a hard gate.** Writing the blueprint before the layer ruling risks