FLEX-WP-0035: activate; draft application access readback contract and FLEX-DEC-2026-019
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 609675@bnt-lap001 Assistant-Session: 4987a36e-eb38-4b10-803c-15a29cd78212
This commit is contained in:
parent
229a98c527
commit
9d9ca69b4c
4 changed files with 228 additions and 2 deletions
|
|
@ -43,6 +43,7 @@
|
|||
| workplan | FLEX-WP-0032 | finished | — | workplans/FLEX-WP-0032-informed-decision-list-action.md |
|
||||
| workplan | FLEX-WP-0033 | finished | — | workplans/FLEX-WP-0033-informed-decision-lifecycle.md |
|
||||
| workplan | FLEX-WP-0034 | finished | — | workplans/FLEX-WP-0034-informed-decision-review-discovery.md |
|
||||
| workplan | FLEX-WP-0035 | proposed | — | workplans/FLEX-WP-0035-per-identity-application-readback.md |
|
||||
| task | FLEX-WP-ADHOC-2026-10-03-T01 | done | — | workplans/ADHOC-2026-10-03.md |
|
||||
| task | FLEX-WP-ADHOC-2026-10-03-T02 | wait | — | workplans/ADHOC-2026-10-03.md |
|
||||
| task | FLEX-WP-0001-T001 | done | — | workplans/FLEX-WP-0001-repo-intent-and-architecture-baseline.md |
|
||||
|
|
@ -200,6 +201,10 @@
|
|||
| task | FLEX-WP-0033-T04 | done | — | workplans/FLEX-WP-0033-informed-decision-lifecycle.md |
|
||||
| task | FLEX-WP-0034-T01 | done | — | workplans/FLEX-WP-0034-informed-decision-review-discovery.md |
|
||||
| task | FLEX-WP-0034-T02 | done | — | workplans/FLEX-WP-0034-informed-decision-review-discovery.md |
|
||||
| task | FLEX-WP-0035-T01 | todo | — | workplans/FLEX-WP-0035-per-identity-application-readback.md |
|
||||
| task | FLEX-WP-0035-T02 | todo | — | workplans/FLEX-WP-0035-per-identity-application-readback.md |
|
||||
| task | FLEX-WP-0035-T03 | todo | — | workplans/FLEX-WP-0035-per-identity-application-readback.md |
|
||||
| task | FLEX-WP-0035-T04 | wait | — | workplans/FLEX-WP-0035-per-identity-application-readback.md |
|
||||
| intake | FLEX-IN-0001 | closed | — | intakes/intakes.md |
|
||||
| intake | FLEX-IN-0002 | closed | — | intakes/intakes.md |
|
||||
| decision | FLEX-DEC-2026-001 | resolved | — | decisions/decisions.md |
|
||||
|
|
|
|||
|
|
@ -2080,3 +2080,33 @@ endpoint.
|
|||
|
||||
**Bound.** Unchanged from `cadence.yaml`: none of this detects a compromised
|
||||
flex-auth suppressing a record together with its own count.
|
||||
|
||||
## FLEX-DEC-2026-019 — Application access readback is a per-package opt-in action, advisory by binding, served from a dedicated pin
|
||||
|
||||
**Date:** 2026-10-09
|
||||
**Status:** proposed (pending user-engine review)
|
||||
**Workplan:** `FLEX-WP-0035-T01`
|
||||
**Contract:** `docs/application-access-readback.md`
|
||||
|
||||
**Question.** How does access-engine tell user-engine which applications a
|
||||
signed-in person may use, without widening any pin's caller binding, and
|
||||
without letting registry content decide an allow?
|
||||
|
||||
**Ruling (proposed).**
|
||||
|
||||
1. **The action is `application.access`, declared per package.** A system
|
||||
takes part by giving the action rules in its own versioned package. If it
|
||||
does not, the answer is `not_applicable`. No registry-level mapping, because
|
||||
registry content is outside decision provenance until G1.
|
||||
2. **The answer is advisory by construction.** The binding carries
|
||||
`purpose: "readback"` inside the hashed request, so its digests never match
|
||||
an enforcement request. The route evaluates only `application.access`.
|
||||
3. **It runs on a dedicated readback pin** that loads participating packages
|
||||
at the digests their own pins run, enforced by a digest-equality test. Its
|
||||
caller binding admits the readback route only. Fan-out to consumer pins is
|
||||
rejected, because it widens every pin's binding and ingress.
|
||||
4. **Commitments do not grow.** Each result restates the §9.7 per-input-class
|
||||
deadlines from `docs/decision-input-freshness.md` and carries
|
||||
`lifetime.expires_at`. Readback decisions are recorded as their own
|
||||
non-load-bearing `readback` event class under `FLEX-DEC-2026-018`.
|
||||
5. **Grants stay inside access-engine.** No user-domain grant surface.
|
||||
|
|
|
|||
178
docs/application-access-readback.md
Normal file
178
docs/application-access-readback.md
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
# Application access readback
|
||||
|
||||
Status: proposed, for review with user-engine
|
||||
Date: 2026-10-09
|
||||
Workplan: `FLEX-WP-0035-T01`
|
||||
Decision: `FLEX-DEC-2026-019`
|
||||
Consumer: user-engine (`USER-WP-0026-T03`, `USER-WP-0028-T02`)
|
||||
|
||||
user-engine wants to show a signed-in person which applications they can use.
|
||||
This document defines what access-engine answers to that request, what the
|
||||
answer means, and what it must never be used for.
|
||||
|
||||
## 1. Constraints from the current runtime
|
||||
|
||||
1. **One pin evaluates one consumer.** Each `flex-auth-<consumer>` Deployment
|
||||
loads one `--registry` snapshot and one `--policy` package. The
|
||||
`flex-auth-user-engine` pin holds no rules for any other application. It
|
||||
cannot answer for them, and widening it to do so would mix packages.
|
||||
2. **Caller auth is per system.** `/v1/batch_check` authorizes the caller
|
||||
token against every `resource.system` in the batch. A caller bound to
|
||||
`user-engine` gets 403 for any other system. That is correct, and it stays.
|
||||
3. **There is no common action.** Each package has its own action
|
||||
vocabulary. "May use the application" has no meaning until a package says
|
||||
what it means.
|
||||
4. **Registry content is not in decision provenance** until G1
|
||||
(`FLEX-WP-0019`). Under the 2026-08-19 boundary, anything that decides an
|
||||
allow belongs in a versioned policy package, not in the registry.
|
||||
5. **No key-cape client binding exists** in `protected_system_manifest`.
|
||||
|
||||
## 2. The readback action: `application.access`, opted into per package
|
||||
|
||||
A system takes part in the readback by declaring the action
|
||||
`application.access` in its own policy package and giving it rules there.
|
||||
|
||||
- The rules are the system's own. They are reviewed, versioned and tested like
|
||||
any other action, and the decision records `policy_package_digest`.
|
||||
- A system whose package does not declare the action answers
|
||||
`not_applicable`. user-engine renders that as `unavailable`.
|
||||
- **No registry-level mapping.** Mapping "application access" onto an existing
|
||||
action in the registry was considered and rejected. Under constraint 4 it
|
||||
would let content outside provenance decide an allow.
|
||||
|
||||
`application.access` means: this subject, holding these claims, may begin to
|
||||
use the application. It grants nothing inside the application. Every action
|
||||
the application performs is still checked by that application's PEP against
|
||||
its own pin.
|
||||
|
||||
## 3. The answer is advisory and cannot be transferred
|
||||
|
||||
A readback decision is a real flex-auth decision. It is evaluated, recorded
|
||||
and attributable, but it cannot be accepted as an enforcement decision by any
|
||||
PEP:
|
||||
|
||||
- The binding carries `purpose: "readback"`. The field is part of the hashed
|
||||
request, so `request_digest` and `submitted_request_digest` can never equal
|
||||
those of an enforcement request for the same subject and resource. Under
|
||||
§6.4.2 a PEP's digest comparison therefore fails.
|
||||
- The readback route evaluates only `application.access`. Any other action is
|
||||
refused with 400 before evaluation. The readback surface cannot produce a
|
||||
decision about a real operation.
|
||||
- The decision is evaluated over the claims that user-engine presents for the
|
||||
signed-in person, taken from its key-cape session. It is only as current as
|
||||
those claims. The target application's PEP receives its own claims on its
|
||||
own path.
|
||||
|
||||
## 4. Where it runs: a dedicated readback pin
|
||||
|
||||
The readback runs on a dedicated pin, `flex-auth-application-readback`. It
|
||||
loads the participating systems' packages at **the same package digests**
|
||||
their own pins run, plus a merged registry snapshot.
|
||||
|
||||
- Drift control: each decision states the `policy_package_digest` of the
|
||||
package it used. A test compares every package digest in the readback pin's
|
||||
values against that consumer's own pin values, and fails on any mismatch.
|
||||
- The only admitted caller is a readback binding (T02), for example
|
||||
`application-readback=system:serviceaccount:user-engine:user-engine`. That
|
||||
binding authorizes the readback route only. It never authorizes `/v1/check`
|
||||
or `/v1/batch_check` on any pin.
|
||||
- Rejected alternative: fan out from user-engine, or from a readback service,
|
||||
to each consumer pin. That needs a caller binding and an ingress rule on
|
||||
every consumer pin, which is exactly the widening constraint 2 forbids. It
|
||||
also makes per-page latency the sum of N pins.
|
||||
|
||||
Code needed: `serve` today takes one `--policy`. The readback pin needs a
|
||||
multi-package mode keyed by `resource.system`, restricted to
|
||||
`application.access`.
|
||||
|
||||
## 5. Request and response
|
||||
|
||||
```http
|
||||
POST /v1/readback/application_access
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": {"issuer": "https://key-cape…/realms/…", "sub": "…", "type": "user"},
|
||||
"context": {"groups": ["…"], "acr": "…", "amr": ["pwd", "otp"], "auth_time": 0},
|
||||
"systems": ["informed-decision", "markitect"]
|
||||
}
|
||||
```
|
||||
|
||||
Response, one entry per requested system and in request order:
|
||||
|
||||
```json
|
||||
{
|
||||
"registry_snapshot_digest": "sha256:…",
|
||||
"results": [
|
||||
{
|
||||
"system": "informed-decision",
|
||||
"effect": "allow",
|
||||
"decision_id": "decision:…",
|
||||
"lifetime": {"expires_at": "…"},
|
||||
"revocation_visibility": {
|
||||
"claims": "immediate",
|
||||
"registry": "reload_or_lifetime",
|
||||
"policy_package": "reload_or_lifetime",
|
||||
"directory": "next_check"
|
||||
},
|
||||
"policy_package": "informed-decision.lifecycle",
|
||||
"policy_package_digest": "sha256:…"
|
||||
},
|
||||
{"system": "markitect", "effect": "not_applicable", "reason": "no application.access rule"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `effect` is one of `allow`, `deny`, `not_applicable`. A system the pin does
|
||||
not know answers `not_applicable`; it is never omitted. A whole-request
|
||||
failure is a non-2xx response, and user-engine renders every row
|
||||
`unavailable`.
|
||||
- `revocation_visibility` restates per input class the deadlines published
|
||||
in `docs/decision-input-freshness.md`. It contains nothing new. The
|
||||
readback commits to no more than §9.7 already does.
|
||||
- user-engine must not display an allow past `lifetime.expires_at`, and must
|
||||
re-read rather than extend.
|
||||
|
||||
## 6. Records
|
||||
|
||||
Readback decisions are recorded like any decision. They form their own event
|
||||
class, `readback`, declared in `cadence.yaml` as not load-bearing: a readback
|
||||
`allow` authorizes nothing. Under `FLEX-DEC-2026-018`, a failed commit still
|
||||
withholds an allow (503, rendered `unavailable`) and still releases
|
||||
`deny`/`not_applicable`.
|
||||
|
||||
## 7. Registry projection (T03)
|
||||
|
||||
```http
|
||||
GET /v1/readback/systems
|
||||
```
|
||||
|
||||
The response lists every system in the merged snapshot: `id`, `name`,
|
||||
`client_binding` (`{issuer, client_id}` or absent), `readback` (whether its
|
||||
package declares `application.access`), and the `registry_snapshot_digest`.
|
||||
|
||||
This needs one new optional field on `protected_system_manifest`:
|
||||
`client_binding`. A system without one is still listed, and user-engine's rule
|
||||
then excludes it from the workload catalogue. The projection authorizes
|
||||
nothing. It describes what the readback pin knows.
|
||||
|
||||
## 8. Grants
|
||||
|
||||
Grants and revocations stay inside access-engine, in packages and registry
|
||||
content under their normal review. This work gives user-domain admins no grant
|
||||
or revoke surface.
|
||||
|
||||
## 9. Open questions for user-engine review
|
||||
|
||||
1. **Claim set.** Is forwarding the session's verified claims (groups, `acr`,
|
||||
`amr`, `auth_time`) acceptable to you? Or do you want the readback to
|
||||
resolve groups itself? Resolving them itself would add a directory
|
||||
dependency.
|
||||
2. **First participants.** The proposal is informed-decision first: a
|
||||
human-facing web app with KeyCape AAL2 rules already in its packages. Which
|
||||
applications do you need in your first release?
|
||||
3. **Batch size.** The ceiling is 64 systems per request. Is that enough?
|
||||
4. **Record volume.** Each page view produces N decision records. If that is
|
||||
too much, the alternative is a shorter TTL plus client-side reuse up to
|
||||
`expires_at`, not unrecorded decisions.
|
||||
|
|
@ -4,12 +4,13 @@ type: workplan
|
|||
title: "Per-identity application access readback and readable protected-system registry for user-engine"
|
||||
domain: infotech
|
||||
repo: access-engine
|
||||
status: proposed
|
||||
status: active
|
||||
owner: claude
|
||||
topic_slug: netkingdom
|
||||
created: "2026-10-09"
|
||||
updated: "2026-10-09"
|
||||
related: [USER-WP-0026, USER-WP-0028]
|
||||
state_hub_workstream_id: "cb9755fa-6824-5288-81c4-3aa2fbff1a9f"
|
||||
---
|
||||
|
||||
user-engine (message `f1637c32`, 2026-10-09, routed by net-kingdom `19a0cbcd`)
|
||||
|
|
@ -38,8 +39,9 @@ record.
|
|||
|
||||
```task
|
||||
id: FLEX-WP-0035-T01
|
||||
status: todo
|
||||
status: progress
|
||||
priority: high
|
||||
state_hub_task_id: "0585a684-d462-5caa-937f-b1730efddbd8"
|
||||
```
|
||||
|
||||
Decide the action that a readback evaluates per system: a declared
|
||||
|
|
@ -54,6 +56,14 @@ 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: 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
|
||||
|
||||
```task
|
||||
|
|
@ -61,6 +71,7 @@ id: FLEX-WP-0035-T02
|
|||
status: todo
|
||||
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
|
||||
|
|
@ -78,6 +89,7 @@ id: FLEX-WP-0035-T03
|
|||
status: todo
|
||||
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.
|
||||
|
|
@ -96,6 +108,7 @@ needs_human: true
|
|||
depends_on: [FLEX-WP-0035-T02, FLEX-WP-0035-T03]
|
||||
blocking_reason: "Live pin change on railiance01 needs founder go."
|
||||
priority: medium
|
||||
state_hub_task_id: "82a8a7e6-54e9-5262-a271-24ce2f161362"
|
||||
```
|
||||
|
||||
Roll the readback route and its caller binding onto `flex-auth-user-engine`,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue