access-engine/workplans/FLEX-WP-0035-per-identity-application-readback.md
tegwick 22185e94ec Withhold uncommitted decision effects and pin accepted layer canon
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 02:21:40 +02:00

10 KiB

id type title domain repo status owner topic_slug created updated related state_hub_workstream_id
FLEX-WP-0035 workplan Per-identity application access readback and readable protected-system registry for user-engine infotech access-engine finished claude netkingdom 2026-10-09 2026-10-10
USER-WP-0026
USER-WP-0028
cb9755fa-6824-5288-81c4-3aa2fbff1a9f

user-engine (message f1637c32, 2026-10-09, routed by net-kingdom 19a0cbcd) needs to show a signed-in person which applications they can use. Its USER-WP-0026-T03 and USER-WP-0028-T02 are blocked_on access-engine. The PEP stance is already fail-closed: no decision renders unavailable, and allow is never inferred from membership, platform role, registration, or a missing record.

What exists today (2026-10-09 review)

  • POST /v1/batch_check takes several resources in one call and returns one decision per resource, each with a lifetime (expires_at) per the decision envelope. Shape-wise this is close to request (1).
  • Caller auth authorizes the caller token against every resource.system in the batch (cmd/flex-auth/main.go, /v1/batch_check). The flex-auth-user-engine caller is bound to its own protected system, so a readback across other systems is a 403 today. That is correct behaviour for enforcement, and the reason this needs design rather than a config change.
  • There is no generic "may use this application" action. Each package has its own action vocabulary, so "access to system X" has no defined meaning yet.
  • There is no registry read endpoint. The protected-system registry is loaded from files and never served.

Define the readback contract

id: FLEX-WP-0035-T01
status: done
priority: high
state_hub_task_id: "0585a684-d462-5caa-937f-b1730efddbd8"

Decide the action that a readback evaluates per system: a declared application.access action each consumer package must opt into, versus a per-system mapping held in the registry. A system that declares nothing is returned as not_applicable, never allow. State per input class the revocation-visibility deadline the readback carries (§9.7). Decide whether the readback is a decision for audit purposes (decision record, emission) or an explicitly non-enforcing advisory read. A displayed allow must not be usable as a PEP decision by another system.

Gate: written contract in docs/ plus an ADR or decision record; reviewed with user-engine before code.

2026-10-09: user-engine accepted the contract (message f54df082) with three amendments, now folded in: the /v1/check subject shape with tenant, the rendering table, and a separate USER_ENGINE_READBACK_TOKEN_FILE. Also added: a realm-level-claims-only rule. FLEX-DEC-2026-019 is accepted.

2026-10-09: activated by the founder. Draft contract docs/application-access-readback.md and FLEX-DEC-2026-019 (proposed). Key finding: each pin loads one consumer's package, so no existing pin can answer across applications. The draft proposes a dedicated readback pin with multi-package mode, a purpose: "readback" binding that keeps decisions non-transferable, and a new optional client_binding manifest field. Sent to user-engine with four review questions. The gate closes on their reply.

Caller authorization for cross-system readback

id: FLEX-WP-0035-T02
status: done
priority: high
depends_on: [FLEX-WP-0035-T01]
state_hub_task_id: "eb0b6a91-7750-553b-b87f-c4262eb9df07"

Add a distinct caller capability (readback scope) instead of widening the flex-auth-user-engine binding to other systems' check rights. The readback caller must not be able to obtain a decision that another system's PEP would accept, and its decisions must say so in the binding.

Gate: tests prove a readback-scoped token gets 403 on /v1/check for a foreign system and is admitted only on the readback route.

2026-10-09 done. flex-auth serve-readback is a separate subcommand that mounts no /v1/check or /v1/batch_check (404 there). --readback-caller is bound under the non-system key application-readback. CheckRequest.purpose is hashed into request_digest, submitted_request_digest and approval_binding_digest, but only when set, so enforcement digests are unchanged. /v1/check refuses a request that carries it. Tests: cmd/flex-auth/readback_test.go (TestReadbackPrincipalCannotCheckOnEnforcementPin, TestReadbackHTTPCallerAndRequestShape, TestReadbackWithholdsUncommittedAllow) and pkg/api/purpose_test.go.

Readable protected-system registry

id: FLEX-WP-0035-T03
status: done
priority: medium
depends_on: [FLEX-WP-0035-T01]
state_hub_task_id: "5dbb3280-8490-58a5-a0bb-f3883e568f58"

Serve a read-only projection: system id, display name, key-cape client binding. Include the registry-snapshot digest so the catalogue is tied to the same snapshot that decisions record (G1, FLEX-WP-0019). Grants and revocations stay inside access-engine; user-domain admins get no grant surface from this work.

Gate: endpoint plus schema, admitted only for the readback caller, tests.

2026-10-09 done. GET /v1/readback/systems; optional client_binding on protected_system_manifest. Schemas readback_request, readback_response and readback_systems are checked against live service output (TestReadbackShapesMatchSchemas). schemaguard gained maxItems for this.

informed-decision declares application.access

id: FLEX-WP-0035-T05
status: done
priority: high
state_hub_task_id: "701cbd99-1a84-5290-8f66-15fa7f407d3b"

informed-decision is the first participant. Its informed-decision.lifecycle package reads authentication_audience, which the readback must not use (contract §3). Add an application.access rule over realm-level claims only, as a new package version with fixtures. Coordinate the version bump with informed-decision, because its own pin must run the same digest. Add client_binding to its registry entry.

Gate: package valid with allow/deny fixtures, informed-decision agrees, and the rule reads no audience, authorized-party or client-scoped claim.

2026-10-09 source done. informed-decision.lifecycle is now v2 (sha256:da01e045…, pin.json updated). It adds application.access, which requires purpose == "readback" and the existing human bar: net-kingdom-admins, KeyCape AAL2 pwd+otp within 12h, platform tenant. It has no workload path and reads no audience or client-scoped claim. v1's twelve operations are unchanged. Eight Go cases cover it, plus a fixture showing that it denies without the readback purpose. The catalogue examples/application-readback/ binds informed-decision to https://kc.coulomb.social / informed-decision-approver. TestReadbackInformedDecisionCatalogue proves the readback over the shipped files. The live lifecycle pin still runs v1 (image bdac62bc) until the T04 rollout.

2026-10-10 done. Founder agreed to lifecycle v2 for informed-decision (informed-decision itself had not replied to 7fcb1eb7).

Deploy the readback pin

id: FLEX-WP-0035-T04
status: done
depends_on: [FLEX-WP-0035-T05]
priority: medium
state_hub_task_id: "82a8a7e6-54e9-5262-a271-24ce2f161362"

Add values/flex-auth-application-readback.yaml (or a deploy manifest) for a dedicated flex-auth-application-readback pin, plus a test asserting that its package paths match each participant's own pin values (digest-equality, §4). Add the user-engine ingress rule, then get the founder's go for the rollout.

Promised to user-engine (reply to d7e02758, 2026-10-10), so the values file must match: URL http://flex-auth-application-readback.flex-auth.svc.cluster.local.:8080 (trailing dot, in-cluster only), caller token audience flex-auth (serve-readback --caller-audience default), readback caller system:serviceaccount:user-engine:user-engine.

2026-10-10 repo side done (bb14ec6). values/application-readback.yaml and the chart's readback.enabled mode, which renders --readback-caller. The lifecycle pin moves to e8c57c18 (main-b5da745, v2). Rollback target is bdac62bc. TestReadbackPinMatchesParticipantPins checks that the readback pin uses the same image and package path as each participant's own pin. Rollout order: the lifecycle pin, then the readback pin.

2026-10-10 done. The founder ran both helm upgrades: the lifecycle pin is at revision 2 and flex-auth-application-readback at revision 1, both on e8c57c18. Verified: both Ready, /healthz 200 on both, the readback routes return 401 without a token, /v1/check returns 404 on the readback pin, anonymous lifecycle /v1/check returns 401, and ingress admits only user-engine/user-engine. user-engine's live check with its own token is still to come. The pin ships with the image that contains serve-readback. Hand user-engine the evidence that unblocks USER-WP-0026-T03.

Completion review — 2026-10-10

All five tasks are done. Fresh read-only verification on railiance01 (node 92.205.62.239) confirms the lifecycle and application-readback deployments are each Ready 1/1 on identical immutable image sha256:e8c57c18a4a9d5583be099146984f7e9cd881446218b65aeefd7619b18aa9229. Readback ingress is restricted to the user-engine namespace and app.kubernetes.io/name=user-engine pods on port 8080. The source regression run go test ./cmd/access-engine ./pkg/api -run 'TestReadback|TestPurpose' -count=1 passes, including participant image/package parity, non-transferable readback purpose, unauthorized-caller refusal, enforcement-route isolation, MFA denial, response schema and outbox failure handling.

The earlier task records retain founder rollout authority and native anonymous route/health receipts; this review performed no deployment or credential action.

The consumer integration and actual user-engine token probe remain live in the existing owner task USER-WP-0026-T03 (and the related consumer UI task USER-WP-0028-T02). Those checks do not require unfinished Access Engine implementation. The service contract and URL are in this workplan and docs/application-access-readback.md. No new residual workplan is needed: the receiving owner records already carry the work. This closure does not assert that the consumer has integrated or that other applications opted in.