flex-auth/workplans/FLEX-WP-0014-tenant-guardrail-policy-actions.md
codex 1d90aac30f
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
fix(workplans): adopt ADR-007 derived identifiers for unregistered records
These workplans exist only in the retired local hub. Their random pre-ADR-007
identifiers are refused by C-06 as stale references, so they cannot be
registered. Deriving from the canonical record id takes no identity from
anything: central does not hold them and the old ids die with the cache.

Records central already holds were deliberately left untouched.

Refs CUST-WP-0068-T06

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
2026-08-25 20:10:35 +02:00

8.3 KiB
Raw Blame History

id type title domain repo status owner topic_slug planning_priority planning_order depends_on_workplans related_workplans created updated state_hub_workstream_id
FLEX-WP-0014 workplan Authorize tenant-engine guardrail actions infotech flex-auth finished grok netkingdom P1 140
FLEX-WP-0010
FLEX-WP-0013
TEN-WP-0006
2026-08-16 2026-08-16 7de17bd2-5e7c-5355-ba1d-40a051a67746

FLEX-WP-0014 - Authorize tenant-engine guardrail actions

Extend package tenant-engine.write-api.mutate from seven actions to nine with the TEN-WP-0006 guardrail surface. Until they exist here, every guardrail check resolves to deny unknown_action — correct fail-closed behaviour, but the surface is inert.

Requested by: tenant-engine (TEN-WP-0006, message 5572d198). Blocked on: FLEX-WP-0013 (four-action production pin). That restore is done; this workplan starts on a current seven-action package, not a rolled-back one.

Requirements from tenant-engine

Two new actions, both on resource type guardrail, both against system: "tenant-engine":

Action Resource type HTTP surface
tenant.guardrail.read guardrail GET /tenants/{id}/guardrails?actor=
tenant.guardrail.set guardrail PUT / DELETE /tenants/{id}/guardrails/{limit_key}

Strings are live in tenant-engine/src/tenant_engine/authz.py _RESOURCE_TYPES. Match them verbatim. CheckRequest shape is unchanged: subject_type: "service", resource_id = tenant_id.

They are two actions so policy can grant a PDP the read without granting the write. tenant-engine's own tests call the read as actor=flex-auth.

v1 stays the activated revision; the identifying fact is that v1 as of this commit contains the two guardrail actions.

Do not deploy a new production image in this workplan until the package is verified locally. Image + pin is a follow-on, named in T04.

T01 - Extend the policy package with the guardrail actions

id: FLEX-WP-0014-T01
status: done
priority: high
state_hub_task_id: "34fb239a-7f06-5e96-9f10-a6bbb85bf4c3"

Add tenant.guardrail.read and tenant.guardrail.set to valid_actions, the frontmatter actions: list, and protected_system_manifest.yaml. Add resource type guardrail.

CARING: View for the read (already used by qonto-assistant), EditAny for the set (already used by tenant.update). Extend caring.capabilities with View.

Register flex-auth as a second service subject that is allowed tenant.guardrail.read and nothing else. Update subject_manifest.yaml and rebuild examples/tenant-engine/registry_snapshot.json.

Update docs/tenant-engine-action-vocabulary.md and docs/tenant-engine-resource-namespace.md. Keep the existing note that this policy governs who may call tenant-engine's admin API, not a tenant's own capability roles.

Done when valid_actions carries all nine actions, the new resource type is declared, and flex-auth is a registered read-only subject.

Done 2026-08-16: nine actions in frontmatter, valid_actions, and the protected-system manifest. Resource type guardrail added. CARING View on the read, EditAny on the set. flex-auth registered as a read-only subject. Registry snapshot rebuilt (2 subjects, 2 groups).

T02 - Decide how the read/write split is used today

id: FLEX-WP-0014-T02
status: done
priority: medium
state_hub_task_id: "8ea87d5f-883a-5d63-89bb-33a3d0963472"

tenant-engine split the actions so a PDP can read ceilings without being able to change them. Decide whether that split is used now or only reserved.

Record the decision in policy_package.md. Using it now — flex-auth may read, tenant-engine may read and set, nobody else — is the expected outcome unless a concrete reason says otherwise. Inventing an ops write subject is out of scope; writes stay on the existing service identity.

Done when the decision and its reasoning are recorded and implemented.

Done 2026-08-16: yes — use the split now. Recorded under "Decision: use the read/write split now" in examples/tenant-engine/policy_package.md. flex-auth may tenant.guardrail.read and is denied action_not_granted on tenant.guardrail.set. tenant-engine may do both. No ops write subject.

T03 - Fixtures and verification

id: FLEX-WP-0014-T03
status: done
priority: high
state_hub_task_id: "1029d2ce-bb37-530e-91ef-ded86f6f5be8"

Add fixture pairs for: authorized tenant-engine read and set → allow; authorized flex-auth read → allow; flex-auth set → deny action_not_granted; unknown subject → deny unknown_subject; misspelled guardrail action → deny unknown_action.

Verify against the real binary: test-policy, load-registry, check on standalone request files. Then close the loop with a live flex-auth serve and a real tenant-engine on TENANT_ENGINE_FLEX_AUTH_URL, through unmodified FlexAuthWriteAuthorizer:

  • GET /tenants/{id}/guardrails?actor=flex-auth → 200
  • GET ...?actor=ops → 403
  • PUT ... as actor=tenant-engine → 200
  • PUT ... as actor=flex-auth → 403
  • PUT ... as actor=ops → 403

Done when those five calls match, go test ./... is green, and gofmt/go vet are clean.

Done 2026-08-16: 17/17 Rego tests, 23/23 fixtures. load-registry loads clean. Standalone check files return the expected allow/deny.

Live flex-auth serve on 127.0.0.1:9098 + real tenant-engine on TENANT_ENGINE_FLEX_AUTH_URL:

Call tenant-engine flex-auth
POST /tenants actor=tenant-engine 201 allow
GET .../guardrails?actor=flex-auth 200 spend.monthly=25000 grouping allow
GET ...?actor=ops 403 write_denied deny unknown_subject
PUT .../spend.monthly actor=tenant-engine 200 override 9000 v2 allow
PUT ... actor=flex-auth 403 write_denied deny action_not_granted
PUT ... actor=ops 403 write_denied deny unknown_subject

go test ./... green; gofmt -l empty; go vet ./... clean.

T04 - Closure and handoff

id: FLEX-WP-0014-T04
status: done
priority: low
state_hub_task_id: "b2b44215-ef08-5c50-b466-190156a88ef3"

Confirm T01T03. Notify tenant-engine naming the policy revision. State plainly whether the nine-action package is in source or deployed — FLEX-WP-0010 taught that distinction the hard way. Production rollout of a new image is not implied by this workplan; it is a separate pin after CI publishes a digest.

Done when the handoff is sent and this workplan is finished.

Done 2026-08-16: T01T03 confirmed. Policy revision for the handoff is package tenant-engine.write-api.mutate version v1, now carrying all nine actions. The version string is unchanged because the package is additive.

In source, not in the cluster. Production flex-auth-tenant-engine still serves sha256:9320df39 (seven actions). A CI-built image from this commit plus a pin of flex-auth-tenant-engine is the remaining step before a live guardrail check succeeds against the cluster Service. tenant-engine's own TEN-WP-0006 surface is also not deployed, so the cluster path is not yet the binding one.

Deployed 2026-08-16 after operator "sync and commit / deploy flex-auth": CI image main-f304688 / sha256:1bf060e61122693ce98359c167cc5fe8bdafc84e097e090eaa71af94d0f27cbc is live on flex-auth-tenant-engine. Approval 8d1efcea-93c7-436b-a749-e5d464e94a33. Rollback target remains sha256:9320df39. flex-auth-user-engine stayed on sha256:1f529037.

Live /v1/check against the Service:

Request Effect Decision
tenant.create allow write_api_policy_matched decision:ef18e8bbd2dead6a
tenant.retire allow write_api_policy_matched decision:996663f6902d0762
tenant.guardrail.read as flex-auth allow write_api_policy_matched decision:9b9e540de13cf7cf
tenant.guardrail.set as tenant-engine allow write_api_policy_matched decision:20c82bc38501a343
tenant.guardrail.set as flex-auth deny action_not_granted decision:a057a7030df65208
misspelled guardrail deny unknown_action decision:18ae70a79c6d47c6
unknown subject deny unknown_subject decision:a98c59c6a34f84ad
misspelled lifecycle deny unknown_action decision:1b3012abb4e2b27d

TEN-WP-0007 can pin the tenant-engine guardrail image. The seven pre-existing actions still allow.