diff --git a/docs/request-enrichment.md b/docs/request-enrichment.md index d5059cd..4673008 100644 --- a/docs/request-enrichment.md +++ b/docs/request-enrichment.md @@ -74,6 +74,90 @@ caller assertion at all, which one merged `attributes` map makes impossible. allowlist MUST read a key its manifest declares**, and that is a review obligation on every package. +## Package attribute-read audit (FLEX-WP-0025-T02) + +Read every `input.{resource,subject}.attributes.` (including +`object.get`) in the published packages. A ceiling or allowlist must be +declared on every resource/subject that package can be asked about. +First-class registry fields that enrichment copies into attributes (`roles`, +`groups`, `labels`, `trust_zone`, `owner`, `path`, `parent`, plus subject +`metadata`/`claims`) count as declared. + +Classification: **ceiling/allowlist** must be facts; **caller-proposed** is +genuinely the request; **ambiguous** is the T01 input. + +| Package | Key | Kind | Role | Declared | +| --- | --- | --- | --- | --- | +| ops-warden | resource.max_ttl_hours | ceiling | ceiling | yes (manifest + snapshots) | +| ops-warden | resource.allowed_subjects | allowlist | allowlist | yes | +| ops-warden | resource.allowed_principals | allowlist | allowlist | yes | +| ops-warden | resource.actor_id | identity | allowlist | yes | +| ops-warden | resource.actor_type | identity | allowlist | yes | +| ops-warden | resource.security_zone | posture | ceiling | yes (snapshots; added to `resource_manifest.yaml` 2026-09-14) | +| ops-warden | resource.security_zone_admission | posture | ceiling | yes | +| ops-warden | subject.actor_type | identity | allowlist | yes (`metadata.actor_type`, copied into attributes) | +| railiance-platform | resource.max_ttl_seconds | ceiling | ceiling | yes | +| railiance-platform | resource.allowed_actor_types | allowlist | allowlist | yes | +| railiance-platform | resource.allowed_purposes | allowlist | allowlist | yes | +| railiance-platform | resource.allowed_delivery_modes | allowlist | allowlist | yes | +| railiance-platform | resource.grant_id | identity | allowlist | yes | +| secrets-engine | — | — | no attribute reads | — | +| tenant-engine | — | — | no attribute reads | — | +| qonto-assistant | — | — | no attribute reads | — | +| user-engine | subject.roles | allowlist | allowlist | yes (first-class `roles`; snapshot now names a registration-applicant) | +| user-engine | subject.issuer | allowlist | allowlist | yes (`claims.issuer` on that subject) | +| markitect (example) | resource.labels | classification | allowlist | yes (first-class `labels`) | +| markitect (example) | subject.groups | membership | allowlist | yes (first-class `groups`) | +| markitect (example) | subject.roles | membership | allowlist | yes (first-class `roles`) | +| informed-decision-t03 (fixture) | subject.assurance, groups, principal_type_source, tenant_source | mixed | see T01 | fixture-only; not a published consumer package | + +No published-package ceiling or allowlist remains unbacked after the two +declaration fixes above. `ttl_hours`, `purpose`, `requested_ttl_seconds`, and +`principals` are **context** fields (caller-proposed) and are not in this +table. + +## Chosen input shape (FLEX-WP-0025-T01) + +**Keep the merged `attributes` map. Add parallel namespaces. Do not break the +six packages.** + +```text +input.resource.attributes # merged view (status quo) +input.resource.registry.* # registry facts only +input.resource.asserted.* # caller-supplied only +input.subject.attributes # merged view (status quo) +input.subject.registry.* # registry facts only +input.subject.asserted.* # caller-supplied only +``` + +Why not stop merging: three of the six published packages (`secrets-engine`, +`tenant-engine`, `qonto-assistant`) read no attributes at all; the other three +already have their ceilings declared. A breaking change across six packages +buys nothing on day one and invites a silent miss on a package that still +reads `attributes` by habit. The merged view stays so existing rules keep +their meaning; a ceiling rule **opts in** to `registry.*`. + +**Vocabulary collision on `subject.type`:** do not overwrite it with the +CARING registry type (`Human`/`Agent`/`Automation`/`Service`). Packages +compare against the protected system's actor vocabulary (`service`, `adm`, +`agt`, `atm`). Put the CARING type at `input.subject.registry.type` (and the +caller value at `input.subject.asserted.type`). `input.subject.type` remains +the request's actor vocabulary, filled from the caller, defaulting only when +empty — the exception already in `enrichSubjectRef`. + +Ambiguous set that T03's validate check must surface, because intent cannot +be inferred from the key name: + +- `subject.roles` / `subject.groups` — first-class registry facts when the + subject is registered; caller input when it is not +- `subject.issuer` — allowlist for public registration; a fact only if the + directory wrote it +- `subject.type` — two vocabularies, one field (handled above, not by + precedence) + +Package changes wait until T03 makes "read a declared registry key" a +`flex-auth validate` finding rather than a review note. + ## Which digest a consumer can reproduce | Field | Over | Consumer-computable | diff --git a/examples/ops-warden/resource_manifest.yaml b/examples/ops-warden/resource_manifest.yaml index c4ecc5b..bea2591 100644 --- a/examples/ops-warden/resource_manifest.yaml +++ b/examples/ops-warden/resource_manifest.yaml @@ -18,6 +18,8 @@ resources: - platform - root max_ttl_hours: 8 + security_zone: z2-protected + security_zone_admission: satisfied - id: ssh-cert:actor/ci-deploy-agent type: ssh-certificate labels: @@ -35,6 +37,8 @@ resources: - deploy - git max_ttl_hours: 2 + security_zone: z2-protected + security_zone_admission: satisfied - id: ssh-cert:actor/backup-automation type: ssh-certificate labels: @@ -51,6 +55,8 @@ resources: allowed_principals: - backup max_ttl_hours: 1 + security_zone: z2-protected + security_zone_admission: satisfied actions: - sign caring_profile: caring-0.4.0-rc2 diff --git a/examples/user-engine/registry_snapshot.json b/examples/user-engine/registry_snapshot.json index 35cdcc4..8c51c2f 100644 --- a/examples/user-engine/registry_snapshot.json +++ b/examples/user-engine/registry_snapshot.json @@ -14,5 +14,21 @@ "actions": [], "metadata": {"flex_auth_contract":"protected-system-v0","dynamic_actions":true} }], - "resource_manifests": [], "tenants": [], "subjects": [], "groups": [], "relationships": [] + "resource_manifests": [], + "tenants": [], + "subjects": [ + { + "id": "registration-applicant", + "type": "Human", + "display_name": "Public registration applicant", + "organization_relation": "Customer", + "roles": ["registration-applicant"], + "tenant": "tenant:friendly:binky", + "claims": { + "issuer": "urn:netkingdom:public-registration" + } + } + ], + "groups": [], + "relationships": [] } diff --git a/workplans/FLEX-WP-0025-fact-versus-assertion.md b/workplans/FLEX-WP-0025-fact-versus-assertion.md index 8dafa6c..d55496d 100644 --- a/workplans/FLEX-WP-0025-fact-versus-assertion.md +++ b/workplans/FLEX-WP-0025-fact-versus-assertion.md @@ -15,7 +15,7 @@ related_workplans: - FLEX-WP-0022 - FLEX-WP-0024 created: "2026-09-07" -updated: "2026-09-07" +updated: "2026-09-14" state_hub_workstream_id: "f9a657ce-67b4-5d25-9933-e0fcb2c20b1c" --- @@ -54,7 +54,7 @@ while the value it produces is never read.** ```task id: FLEX-WP-0025-T01 -status: todo +status: done priority: high state_hub_task_id: "05c6a85d-ee3d-5875-bea4-e9a9c5382e2e" ``` @@ -79,11 +79,16 @@ and which are ambiguous. The ambiguous set is the real design input. Gate: the chosen shape is written down with the vocabulary collision addressed, before any package changes. +**Done 2026-09-14.** Shape recorded in `docs/request-enrichment.md`: keep merged +`attributes`, add `registry.*` / `asserted.*`, leave `subject.type` as caller +vocabulary and put CARING type at `input.subject.registry.type`. No package +rule changes. + ## 2. Audit every package for ceilings read from undeclared keys ```task id: FLEX-WP-0025-T02 -status: todo +status: done priority: high state_hub_task_id: "fc61c8a7-347b-59d2-b6a6-a72c5c1cdb08" ``` @@ -102,6 +107,11 @@ changing the rule. Gate: a table of package × key × declared-in-manifest, with no ceiling or allowlist unbacked. Add it to `docs/request-enrichment.md`. +**Done 2026-09-14.** Table is in `docs/request-enrichment.md`. Declaration +fixes: `security_zone` / `security_zone_admission` on the ops-warden resource +manifest; `claims.issuer` plus `roles` on a user-engine registration-applicant +subject. Secrets-engine, tenant-engine and qonto-assistant read no attributes. + ## 3. Make the review obligation enforceable rather than written ```task