informed-decision/SCOPE.md
tegwick f0e9341650 Align lifecycle persistence with review isolation and runtime health
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
2026-10-04 18:34:40 +02:00

298 lines
17 KiB
Markdown

# 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 (first local persistence slice)
The product now targets the full decision lifecycle for agents and humans with
an API foundation and a UI at `decisions.coulomb.social`. See
`docs/specs/DecisionLifecycleService.md` and `INFD-WP-0007`. Existing memo,
presentation, disposition, storage, review and intake modules are reusable
building blocks; they are not a complete headless lifecycle API. Research,
evaluation, collaborative authoring and audience-scoped publication still need
service/UI implementation. The first internal `LifecycleRepository` now supports
registration, immutable revisions, attributed research sources/evaluations/
comments and event retrieval using the existing Memo and Store (schema 4). Its
local event queue is durable. `LifecycleService` now wraps these operations
with fresh exact-policy observations, expiry checks inside write transactions,
and a verified human-session adapter. HTTP exposure, workload identity composition
and external event delivery remain unimplemented. Unpublished lifecycle records
are excluded from the legacy approver overview, legacy import cannot bypass their
revision history, and pending lifecycle events close runtime readiness. See
`docs/specs/LifecycleFoundationContract.md` and
`docs/specs/LifecycleServiceContract.md`. Frictionless intake has local implementation
but its live delivery remains under `INFD-WP-0005`. The dated deployment snapshot
below is historical, not a fresh observation of production.
## 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](docs/browser-authentication.md),
[durable-review-evidence.md](docs/durable-review-evidence.md) and
[protected-browser-review.md](docs/protected-browser-review.md). The
[deployment packet](deploy/README.md) 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`.