informed-decision/SCOPE.md
tegwick f3b71b1521 Connect lifecycle revisions to protected human review
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
2026-10-04 20:33:24 +02:00

18 KiB

SCOPE

Implemented-and-first-cut boundary for agents and contributors. Aspirational direction belongs in INTENT.md; the current stage belongs in GOAL.md; current work and gates belong in workplans/.

Lifecycle expansion — 2026-10-04 (source implementation)

INFD-WP-0007 adds a headless service and an optional browser workspace. The shared foundation supports registration with packet custody, immutable revisions, attributed sources/evaluations/comments, explicit revision-specific publication, authorized search, retrieval and history. Every operation consumes fresh Access Engine policy observations; audience facts do not implement a local ACL.

The workspace reuses verified KeyCape sessions and CSRF/origin checks for draft creation, research, discussion, publication, refinement, revision comparison and packet downloads. Runtime configuration requires a separate lifecycle policy package. The bearer API adapter remains unmounted pending workload identity and listener admission. Lifecycle records remain outside the legacy approver overview; a durable revision-specific request now connects supported records to intake and the existing human-only review path. Outcomes retain exact source revision and distinguish prepared/unresolved submissions from confirmed entries. See docs/specs/LifecycleReviewHandoff.md; production forwarding is still outstanding.

Opt-in delivery commits lifecycle evidence through the existing Audit Core outbox, with receipt-based settlement, retries and reconciliation. Current delivery is platform-tenant-only. Pending evidence closes runtime readiness. The deployment renderer supports separately pinned lifecycle policy and exact network proposals; no live configuration was changed. Cross-repository validation: 634 passed, 5 skipped, including two agent-created decisions and human collaboration with unchanged runtime configuration, actual Approval Engine acceptance of a linked source revision, and Audit Core delivery. Schema 5 prevents older code without source-revision guards from opening the database. Identity and PDP responses in that scenario are synthetic; this is not live acceptance evidence.

Contracts: docs/specs/DecisionLifecycleService.md, LifecycleFoundationContract.md, LifecycleServiceContract.md, LifecycleHTTPContract.md and LifecycleWorkspaceDelivery.md under docs/specs/. INFD-WP-0005 retains intake runtime/admission work. The deployment snapshot below is historical.

Status — 2026-09-27

Deployed and in live use. decisions.coulomb.social serves forgejo.coulomb.social/coulomb/informed-decision@sha256:0557edff... in namespace informed-decision on railiance01, behind key-cape and the informed-decision.compact-sitting Flex Auth package (currently v6). As observed today: the durable evidence store carries 99 delivered audit events and 15 confirmed human submissions; platform-root signed in and approved a memo through this exact surface. This status was last "not deployed" on 2026-09-11 — everything below it in this section was written against a production system, not a plan.

382 tests pass locally (66 skipped — the opt-in real-Approval-Engine suite, INFD_APPROVAL_ENGINE_SOURCE); 440 pass with that suite enabled, 8 skipped. 11/11 container checks. The 2026-09-21 milestone passed Chromium 13/13 and 433 tests against real Flex Auth/Approval Engine/Audit Core; no newer Chromium run has been recorded since. Four unrelated, pre-existing failures persist in both configurations (waitress not installed locally; one clock-adjacent browser-auth test) and are not this repository's regression.

Component tests use actual Flex Auth, Approval Engine and Audit Core with synthetic identity/custody:

  • layer and stance declarations — layer.yaml, pep-stance.yaml, informed_decision/stance.py, with published-equals-shipped asserted;
  • the governed canonicalizer and schema, with the three published vectors reproducing byte for byte and all four isolation properties pinned;
  • the domain core: memo.py (the Decision Memo, its versions and the binding document), presentation.py (the sole writer of view_hash), disposition.py (the verb vocabulary and guards G_NOAGENT, G_STEP, G_PRES, G_ACTOR, G_ACK, G_REASONS, G_SEALED), provenance.py (claim routes, A-16), evidence.py (the local outbox and commitment records);
  • approval_client.py — the seam to approval-engine plus a fake;
  • oidc.py and web.py — public-client PKCE sign-in, verified human/MFA profile, bounded server-side sessions, protected cookies and CSRF sign-out;
  • approval_http.py and http_transport.py — get-by-id and human-entry transport, declared-control checks, real entry correlation, no consume route or automatic mutation retry. Protected browser actions use this adapter;
  • store.py / records.py — private durable packet/memo/presentation/ disposition storage, append-only acknowledgments, transactional outbox and submission correlation, safe reservation and backup/restore;
  • audit.py — idempotent Audit Core delivery, bounded retry/blocked states, explicit per-class count/time-basis comparison. Heartbeats are generated without hiding undelivered evidence;
  • policy.py / review.py / ui.py — fresh entitlement before rendering and actions, exact response pins, immutable observations, required highlights, accept/return/discuss/decline and visible original/unresolved entry state;
  • the decision overview on the signed-in home page (INFD-WP-0003): the reviewer's memos grouped as open, accepted, declined, returned, closed, needing attention or not available. Each row now asks list, not read (INFD-WP-0004) — a comfortable, 12-hour-fresh identity bar for seeing your decisions, separate from the strict 900-second bar read/accept/etc. keep for actually binding one. As of today each row also names the requesting party — approval-engine's binding.principal, the party the approval was issued on behalf of, distinct from the memo's own binding.principal (the reviewer being bound). The overview records nothing and never presents or binds;
  • runtime.py — explicit owner configuration, rotating credential-file readers, 30-second audit draining, heartbeat/reconciliation and delivery readiness.
  • Containerfile, container.py, requirements.lock and deployment renderer — a locally built/scanned image, private projected-config handoff, one serving writer, backup/inspection commands and eight review-only Kubernetes objects. Network-isolated container restart/restore preserves unresolved submissions.

The durable Store supplies transactional atomicity for every write path; the legacy in-memory evidence.Outbox is a test double only, not part of the deployed path. /readyz requires an owner runtime configuration and recent audit delivery health — without either it returns 503 and rollouts are refused by the readiness probe, not merely warned.

Two production incidents this repository did not cause, both diagnosed by reading policy_observations directly on the live pod rather than guessing: (1) key-cape was backdating a freshly-verified MFA to a stale kc_login session cookie (INFD-IN-0005), and, separately, (2) key-cape's provider-metadata/JWKS discovery fetch was missing the split-horizon X-Forwarded-Proto/X-Forwarded-Host headers its token exchange already carried, which broke every login on Authelia 4.38 the moment a routine restart hit that code path. Both are fixed and deployed in key-cape (911e9de, 8be8065); this repository's own review correctly treated both as upstream policy_denied/LoginError outcomes to relay, not decide.

See browser-authentication.md, durable-review-evidence.md and protected-browser-review.md. The deployment packet documents the render/dry-run candidate path; the actual live rollout is docker build → push to the Forgejo OCI registry → pin the digest in deploy/sitting-admission.json → kubectl set image (Recreate) → kubectl exec backup/inspect before and after, since there is no CI pipeline in this repository (unlike key-cape's Forgejo Actions build-on-push).

One-liner

informed-decision is the presentation and binding surface for decisions: it renders a Decision Memo to the human who holds the mandate, records what was shown, and binds their identity to the act — and owns the browser-facing approver UI that approval-engine deliberately does not contain.

Layer

Declared layer: Staff, role pep-shaped, in INTENT.md frontmatter (governing) and layer.yaml (derived, marked as such, agreeing) — GH-DEC-2026-017, correcting the layer: surface this repository declared by elimination from 2026-09-09 to 2026-09-21 (§3's vocabulary is closed at four tokens; surface was never one of them). Not an Engine. Companion §5 owed, statute §6.4 in full — GH-DEC-2026-012's shape ruling is unchanged by the layer correction.

Open, no deadline, nothing waits on it: INFD-IN-0007 asks how §3.4's "Staff is interactive and non-deterministic, and MUST NOT hold state another layer depends on at runtime" reaches a repository that is deterministic by test and holds presentation evidence audit-core depends on. The value is applied unconditionally either way; this asks only about the fit, for the next repository in this position. Coupled to this repository's own requested (not yet drafted) §4 catalog row. Separately, this repository approved two narrow §11 declaration-form amendments on 2026-09-27 (GH-DEC-2026-021: A11 r2 — INTENT.md frontmatter governs, a derived file must agree and disagreement is itself a finding; A12 r3 — no version pin in a declaration, but a prose citation like "outside §5 by the v0.5 scope rule" is provenance, not reached). Both were already this repository's practice; approving changed no file.

It emits one PIP-like fact — what was presented — as a claim, under three limits that are the substance of the permission rather than caveats on it:

  1. the claim carries presentation and nothing else, and must never carry, restate, summarise or imply a decision or verdict;
  2. the claim must never be an input to the decision it presents for;
  3. the evidence copy reaches audit-core independently of this repository.

Limit 2 is load-bearing: the argument that a renderer attesting its own rendering is not self-dealing was accepted because that limit holds.

Core Idea

A decision surface is not a workflow engine and not a decision point. This repository owns the Decision Memo object, the presentation record, the canonicalization producing view_hash / awareness_hash, the disposition vocabulary, and the evidence bundle export. It does not evaluate whether an act is permitted, does not hold approval current-state, and does not archive the trail.

In Scope — first cut (Stage 1)

Built and tested:

  • Canonicalization of the binding and awareness documents, with the three published vectors reproducing byte for byte and all four isolation properties pinned (tests/test_canonicalize.py).
  • The Decision Memo schema and worked examples, governed under schemas/.
  • The unreachable-engine stance map, built to v0.8 obligation 3, with published-equals-shipped asserted by test (tests/test_layer_conformance.py).

Built with durable custody and protected HTTP integration:

  • The presentation record: what was rendered, to whom, when, in which locale and UI release.

  • Required-highlight acknowledgment as a precondition of binding.

  • The disposition vocabulary and its legality tables. Only accept reaches approval-engine; return, discuss, escalate and the rest are memo-level.

  • The browser-facing OIDC client: authorization-code + S256 PKCE against key-cape, scopes [openid, approval:read, approval:approve].

  • An L3 approver surface calling approval-engine's approval-entry mutation.

Specified, not built:

  • The evidence bundle as an offline-verifiable export.
  • German browser acceptance and the broader step/awareness UI. The current browser profile refuses unsupported locale/step/level rather than overstating it.

Out of Scope

  • Authorization decisions — access-engine, always and only (statute §6).
  • The approval object, its state machine, validity and consumption — approval-engine. Never cached, never inferred, never approval:consume.
  • Approval doctrine — gate-house.
  • Identity and authentication — key-cape. Imported, never invented. The assurance shape is key-cape's and is cited, not restated.
  • The evidence archive — audit-core.
  • Credentials materialized after a decision — secrets-engine.
  • Notification transport, ticketing, general workflow.
  • An approvals inbox backed by approval-engine. Foreclosed upstream as well as here: approval-engine exposes get-by-id only and will not add a list. The decision overview (Status, above) is not this — it lists this repository's own memos, sourced from the local Store plus a per-row list PDP check and get-by-id engine status, never a listing capability approval-engine itself provides.
  • L4/L5, QES, QTSP, qualified archival retention.
  • The mandate graph — so a Stage 1 escalate is an assertion, not a verified claim.

What this repository does not claim

Stated here because a scope file that only lists capabilities overstates them.

  • The decision path is not validated. GH-DEC-2026-010 requires a decision be attributable to access-engine. FLEX-WP-0024 (finished, 2026-09-14) now lets flex-auth sign an envelope when a key is configured — but this repository does not verify a signature anywhere in policy.py, and store.py's schema hard-constrains decision_attributable to 0 (CHECK(decision_attributable=0)). The gap flex-auth closed on its side is still fully open on this one; nothing here must describe validation as complete.
  • The residual is open. A compromised surface can present X and attest Y. GH-DEC-2026-012 states it is not closed and does not credit this repository with closing it.
  • view_hash is not inside the approval entry. POST /entries discards its body by design. Correlation is (approval_id, subject, approved_at), so an auditor holding only the approval object cannot reach the presentation.
  • Commitment-only evidence is not reconstructability. GH-DEC-2026-014 granted it for Stage 1 and bounded it: it satisfies non-alteration, and moves integrity out of our control while leaving availability entirely inside it. The party that can withhold the content is the party the evidence is about. Narrowed by the required existence assertion; not closed.
  • The registration-bound tenant is a declared bounded gap, not the terminal state. GH-DEC-2026-013 ruled directory-sourced terminal and admitted key-cape's shape because its distinguishing case fails closed. Build to it as transitional.
  • Deployed does not mean contained. This surface is live in production and observed there (see Status), but that observation is manual — kubectl exec plus informed-decision-admin inspect/backup around each rollout. There is no automated post-deploy verification, no CI pipeline in this repository, and no alerting wired to /readyz degrading.

Open

  • INFD-IN-0005, narrowed. The two live-blocking defects it predicted (MFA backdated on reuse; a downstream fetch missing split-horizon headers) are fixed and deployed. The original ask — Authelia prompt=login/max_age support, so a step-up can be requested rather than merely checked after the fact, is complete in INFD-WP-0006, with live fresh-login/p01 acceptance. An actionable freshness error in the browser instead of the flat "Review unavailable" remains open in the existing intake record.
  • INFD-IN-0007 — see Layer. No deadline.
  • A reusable account/session-status component, requested of user-engine as USER-IN-0002 on 2026-09-27: identity, MFA-freshness, logout, to replace the bare "Signed in as uid=..." string and make the list-vs-read freshness split (see Status) legible instead of a silent wall. Not started; user-engine owns the data/API contract and whether it lands as a new small repository or inside an existing one.
  • The decision_attributable gap — see "What this repository does not claim." Unowned by anyone as an active task; recorded so it is not mistaken for closed now that flex-auth can sign.

Closed 2026-09-10: the human token tenant (GH-DEC-2026-013, key-cape 329e48f) and the evidence payload question (GH-DEC-2026-014). Closed since, not previously recorded here: native browser registration and human proof (INFD-WP-0001-T08) and audit-core custody/live delivery (AUDIT-IN-0003, AUDIT-WP-0009) — both live and observed in production as of this Status section.

Offline workload intake — 2026-10-03

INFD-WP-0005 implements a separate workload-only WSGI intake application, injected TokenReview and create-only approval transports, per-request grant reload, per-submitter daily limits, and a schema-3 durable ledger. It atomically loads pinned memos and packet bytes and queues notification/posed jobs. Human terminal dispositions atomically queue optional hub resolution; a leased worker retries delivery with stable ids and leaves disabled/unconfigured adapters pending. An uncertain approval create never reposts automatically; retained receipts can resume local loading. Tests cover schema-1/2 upgrade, rollback, replay/restart, revocation and write-back. T07 also supplies opt-in scheduled delivery, matching hub message receipts and stable write keys, and immutable posed audit evidence with receipts, readiness, heartbeat and reconciliation. Actual Audit Core component tests exercise lost-reply deduplication and counts. This is local implementation, not a deployed intake listener. Attribute contracts, forwarding, workload custody, private listener composition and live acceptance are tracked as blocked tasks in INFD-WP-0005, with acceptance requirements in docs/intake-dependency-requirements.md.