Authorize tenant-engine guardrail read and set actions
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 39s
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s

FLEX-WP-0014. Package tenant-engine.write-api.mutate v1 now carries
nine actions: tenant.guardrail.read and tenant.guardrail.set on
resource type guardrail, verbatim from tenant-engine's mapping.

The read/write split is used now: flex-auth may read ceilings and is
denied action_not_granted on set; tenant-engine may do both. 17/17
Rego tests and 23/23 fixtures pass. Live e2e against a real
tenant-engine FlexAuthWriteAuthorizer matches.

In source only; production still serves the seven-action 9320df39 pin.
This commit is contained in:
tegwick 2026-08-16 02:46:25 +02:00
parent 8563c17a9f
commit f304688d72
13 changed files with 627 additions and 34 deletions

View file

@ -15,20 +15,23 @@ repos, not independently invented.
| `tenant.update` | `PATCH /tenants/{id}` | `tenant` | `allow`, `deny` |
| `tenant.retire` | `POST /tenants/{id}/retire` | `tenant` | `allow`, `deny` |
| `tenant.reactivate` | `POST /tenants/{id}/reactivate` | `tenant` | `allow`, `deny` |
| `tenant.guardrail.read` | `GET /tenants/{id}/guardrails` | `guardrail` | `allow`, `deny` |
| `tenant.guardrail.set` | `PUT` / `DELETE /tenants/{id}/guardrails/{limit_key}` | `guardrail` | `allow`, `deny` |
The last three are the tenant lifecycle actions added by `FLEX-WP-0010` for
`TEN-WP-0005`. They are three actions rather than one `tenant.write` because
tenant-engine separated them so policy *can* permit renaming a tenant without
thereby permitting retiring it. Under the current single-operator model all
three resolve identically — the separation is a seam for later, and the
reasoning for not yet treating `tenant.retire` more strictly is recorded in
The lifecycle trio is from `FLEX-WP-0010` / `TEN-WP-0005`. The last two are
the TEN-WP-0006 guardrail actions (`FLEX-WP-0014`). They are two actions
rather than one `tenant.guardrail` so policy can grant a PDP the read
without granting the write. That split is used today: `flex-auth` may
`tenant.guardrail.read` and may not `tenant.guardrail.set`; `tenant-engine`
may do both. Reasoning is recorded in
`examples/tenant-engine/policy_package.md`.
These are the only seven actions `tenant-engine` sends to `POST /v1/check`
its `FlexAuthCheckClient` (`TEN-WP-0003`) never sends anything else, and
`tenant-engine`'s reads (`/roles`, `/roles/live`) are never gated through
flex-auth at all; they're `tenant-engine`'s own cache-read and live-lookup
endpoints, consumed *by* `key-cape` and `flex-auth` respectively.
These are the only nine actions `tenant-engine` sends to `POST /v1/check`
its `FlexAuthCheckClient` (`TEN-WP-0003`) never sends anything else.
`tenant-engine`'s role reads (`/roles`, `/roles/live`) are still not gated
through flex-auth; they remain `tenant-engine`'s own cache-read and
live-lookup endpoints. The guardrail GET is different: it is authorized,
because a ceiling is an input to a decision, not an unauthenticated lookup.
## Not in scope for this vocabulary

View file

@ -5,13 +5,13 @@ consumed by flex-auth (`FLEX-WP-0008-T01`).
Unlike Markitect's document hierarchy or ops-warden's fixed SSH-certificate
inventory, `tenant-engine`'s resources (tenants, their role grants, their
plan assignments) are created **dynamically** — there is no fixed,
pre-registrable resource list to publish a `resource_manifest.yaml` for.
Every `CheckRequest` carries the resource identity inline
(`resource.id == tenant_id`), and the policy evaluates against `input`
directly rather than looking up a registered resource by id. This is a
deliberate, documented deviation from the ops-warden pattern, not an
omission.
plan assignments, their guardrails) are created **dynamically** — there is
no fixed, pre-registrable resource list to publish a
`resource_manifest.yaml` for. Every `CheckRequest` carries the resource
identity inline (`resource.id == tenant_id`), and the policy evaluates
against `input` directly rather than looking up a registered resource by
id. This is a deliberate, documented deviation from the ops-warden
pattern, not an omission.
## Resource Types
@ -19,10 +19,13 @@ omission.
tenant -- a tenant record (existence, grouping)
role-grant -- a capability-role grant/revocation
plan-assignment -- a tenant's plan/subscription assignment
guardrail -- a tenant's resolved ceiling and its provenance
```
All three are `scope_level: Resource`, always accessed within
`system: tenant-engine`.
All four are `scope_level: Resource`, always accessed within
`system: tenant-engine`. A guardrail check still sends `resource.id` equal
to the tenant id — there is no separately addressable guardrail object on
the wire.
## Ownership Boundary