State tenant-engine's tenant relation in the write-api package, v3.
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 1m24s

tenant-engine named the relation in TEN-DEC-2026-002: `tenant` is the target
tenant record and always equals `resource.id`; the write API is cross-tenant
by design and `tenant.guardrail.read` does not differ. v2 carried no tenant
rule and a constant fixture tenant, so the deliberate scope and an omitted
rule were indistinguishable.

tenant-engine.write-api.mutate v3 (FLEX-DEC-2026-016):
- allowed requires tenant_is_target; a mismatch or absent tenant is denied
  tenant_not_target (object.get, so an absent key names the right cause).
- the cross-tenant scope is stated in the package and quantified by
  test_tenant_never_changes_effect over every action, three subjects and
  four tenants, with guards against passing by denying everything.
- fixtures rotate tenant across four tenants; five cross-tenant allows and
  two tenant_not_target denies added (42 fixtures, 33 tests, all pass).
- user-engine's tenant:platform exclusion is named as a fixed-record rule,
  not a subject/tenant relation, and tested separately.

Closes FLEX-WP-0022 (T01, T02 done). Also records TEN-IN-0004 and
SECRETS-IN-0002 on FLEX-WP-0020 and acknowledges the GH-DEC-2026-017
replies on FLEX-WP-0030-T04.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 63291@bnt-lap001
Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703
This commit is contained in:
tegwick 2026-09-21 07:39:57 +02:00
parent bd3d270531
commit 3081067325
17 changed files with 783 additions and 150 deletions

View file

@ -10,10 +10,10 @@ protected-system consumer, gating its own write API
| --- | --- |
| `protected_system_manifest.yaml` | Resource types (`tenant`, `role-grant`, `plan-assignment`, `guardrail`) and the nine actions: the original four, the `FLEX-WP-0010` lifecycle trio, and the `FLEX-WP-0014` guardrail pair `tenant.guardrail.read` / `tenant.guardrail.set` |
| `subject_manifest.yaml` | Two registered callers: `tenant-engine` (all nine actions) and `flex-auth` (read-only on `tenant.guardrail.read`) |
| `policy_package.md` | Rego rules + embedded tests gating the write API |
| `policy_fixtures.yaml` | Allow/deny request/decision pairs, referenced by `policy_package.md`'s frontmatter |
| `policy_package.md` | Rego rules + embedded tests gating the write API. v3 states the tenant relation (`TEN-DEC-2026-002`, `FLEX-DEC-2026-016`): `tenant` is the target record and must equal `resource.id` (`tenant_not_target` otherwise); the scope is cross-tenant by design |
| `policy_fixtures.yaml` | Allow/deny request/decision pairs, referenced by `policy_package.md`'s frontmatter. `tenant` varies across four tenants so the suite reports on the field |
| `registry_snapshot.json` | Merged `systems`/`subjects`/`groups` snapshot assembled from the two manifests above, loadable by `flex-auth serve`/`check`/`load-registry` |
| `check_request_allow_create.json`, `check_request_deny_unknown_subject.json`, `check_request_allow_retire.json`, `check_request_deny_misspelled_lifecycle.json`, `check_request_allow_guardrail_read.json`, `check_request_allow_guardrail_set.json`, `check_request_deny_guardrail_set_as_reader.json`, `check_request_deny_misspelled_guardrail.json` | Standalone example requests for `flex-auth check` |
| `check_request_allow_create.json`, `check_request_deny_unknown_subject.json`, `check_request_allow_retire.json`, `check_request_deny_misspelled_lifecycle.json`, `check_request_allow_guardrail_read.json`, `check_request_allow_guardrail_set.json`, `check_request_deny_guardrail_set_as_reader.json`, `check_request_deny_misspelled_guardrail.json`, `check_request_deny_tenant_not_target.json` | Standalone example requests for `flex-auth check` |
**No `resource_manifest.yaml`** — unlike ops-warden's fixed SSH-certificate
inventory, `tenant-engine`'s resources (tenants) are created dynamically.

View file

@ -7,7 +7,7 @@
},
"action": "tenant.create",
"resource": {
"id": "t-1",
"id": "tenant:friendly:binky",
"type": "tenant",
"system": "tenant-engine"
},

View file

@ -7,7 +7,7 @@
},
"action": "tenant.guardrail.read",
"resource": {
"id": "t-1",
"id": "tenant:friendly:binky",
"type": "guardrail",
"system": "tenant-engine"
},

View file

@ -7,7 +7,7 @@
},
"action": "tenant.guardrail.set",
"resource": {
"id": "t-1",
"id": "tenant:friendly:binky",
"type": "guardrail",
"system": "tenant-engine"
},

View file

@ -7,7 +7,7 @@
},
"action": "tenant.retire",
"resource": {
"id": "t-1",
"id": "tenant:friendly:binky",
"type": "tenant",
"system": "tenant-engine"
},

View file

@ -7,7 +7,7 @@
},
"action": "tenant.guardrail.set",
"resource": {
"id": "t-1",
"id": "tenant:friendly:binky",
"type": "guardrail",
"system": "tenant-engine"
},

View file

@ -7,7 +7,7 @@
},
"action": "tenant.guardrail.get",
"resource": {
"id": "t-1",
"id": "tenant:friendly:binky",
"type": "guardrail",
"system": "tenant-engine"
},

View file

@ -7,7 +7,7 @@
},
"action": "tenant.retired",
"resource": {
"id": "t-1",
"id": "tenant:friendly:binky",
"type": "tenant",
"system": "tenant-engine"
},

View file

@ -0,0 +1,15 @@
{
"id": "check:tenant-engine-tenant-not-target",
"tenant": "tenant:friendly:binky",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.create",
"resource": {
"id": "tenant:acme:prod",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
}

View file

@ -7,7 +7,7 @@
},
"action": "tenant.create",
"resource": {
"id": "t-1",
"id": "tenant:friendly:binky",
"type": "tenant",
"system": "tenant-engine"
},

View file

@ -4,276 +4,506 @@
"request": {
"id": "check:tenant-engine-create-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.create",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"},
"resource": {
"id": "tenant:friendly:binky",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "allow", "reason": "write_api_policy_matched"}
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-role-grant-allow",
"request": {
"id": "check:tenant-engine-role-grant-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"tenant": "tenant:acme:prod",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.role.grant",
"resource": {"id": "t-1", "type": "role-grant", "system": "tenant-engine"},
"resource": {
"id": "tenant:acme:prod",
"type": "role-grant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "allow", "reason": "write_api_policy_matched"}
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-role-revoke-allow",
"request": {
"id": "check:tenant-engine-role-revoke-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"tenant": "tenant:platform",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.role.revoke",
"resource": {"id": "t-1", "type": "role-grant", "system": "tenant-engine"},
"resource": {
"id": "tenant:platform",
"type": "role-grant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "allow", "reason": "write_api_policy_matched"}
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-plan-assign-allow",
"request": {
"id": "check:tenant-engine-plan-assign-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"tenant": "tenant:trial:demo-company",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.plan.assign",
"resource": {"id": "t-1", "type": "plan-assignment", "system": "tenant-engine"},
"resource": {
"id": "tenant:trial:demo-company",
"type": "plan-assignment",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "allow", "reason": "write_api_policy_matched"}
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-update-allow",
"request": {
"id": "check:tenant-engine-update-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.update",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"},
"resource": {
"id": "tenant:friendly:binky",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "allow", "reason": "write_api_policy_matched"}
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-retire-allow",
"request": {
"id": "check:tenant-engine-retire-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"tenant": "tenant:acme:prod",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.retire",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"},
"resource": {
"id": "tenant:acme:prod",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "allow", "reason": "write_api_policy_matched"}
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-reactivate-allow",
"request": {
"id": "check:tenant-engine-reactivate-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"tenant": "tenant:platform",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.reactivate",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"},
"resource": {
"id": "tenant:platform",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "allow", "reason": "write_api_policy_matched"}
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-update-unknown-subject-deny",
"request": {
"id": "check:tenant-engine-update-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "some-other-service", "type": "service"},
"tenant": "tenant:trial:demo-company",
"subject": {
"id": "some-other-service",
"type": "service"
},
"action": "tenant.update",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"},
"resource": {
"id": "tenant:trial:demo-company",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "unknown_subject"}
"expect": {
"effect": "deny",
"reason": "unknown_subject"
}
},
{
"id": "fixture:tenant-engine-retire-unknown-subject-deny",
"request": {
"id": "check:tenant-engine-retire-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "some-other-service", "type": "service"},
"subject": {
"id": "some-other-service",
"type": "service"
},
"action": "tenant.retire",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"},
"resource": {
"id": "tenant:friendly:binky",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "unknown_subject"}
"expect": {
"effect": "deny",
"reason": "unknown_subject"
}
},
{
"id": "fixture:tenant-engine-reactivate-unknown-subject-deny",
"request": {
"id": "check:tenant-engine-reactivate-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "some-other-service", "type": "service"},
"tenant": "tenant:acme:prod",
"subject": {
"id": "some-other-service",
"type": "service"
},
"action": "tenant.reactivate",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"},
"resource": {
"id": "tenant:acme:prod",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "unknown_subject"}
"expect": {
"effect": "deny",
"reason": "unknown_subject"
}
},
{
"id": "fixture:tenant-engine-misspelled-lifecycle-action-deny",
"request": {
"id": "check:tenant-engine-retired-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"tenant": "tenant:platform",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.retired",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"},
"resource": {
"id": "tenant:platform",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "unknown_action"}
"expect": {
"effect": "deny",
"reason": "unknown_action"
}
},
{
"id": "fixture:tenant-engine-lifecycle-underscore-action-deny",
"request": {
"id": "check:tenant-engine-underscore-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"tenant": "tenant:trial:demo-company",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant_update",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"},
"resource": {
"id": "tenant:trial:demo-company",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "unknown_action"}
"expect": {
"effect": "deny",
"reason": "unknown_action"
}
},
{
"id": "fixture:tenant-engine-unknown-subject-deny",
"request": {
"id": "check:tenant-engine-create-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "some-other-service", "type": "service"},
"subject": {
"id": "some-other-service",
"type": "service"
},
"action": "tenant.create",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"},
"resource": {
"id": "tenant:friendly:binky",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "unknown_subject"}
"expect": {
"effect": "deny",
"reason": "unknown_subject"
}
},
{
"id": "fixture:tenant-engine-wrong-system-deny",
"request": {
"id": "check:tenant-engine-create-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"tenant": "tenant:acme:prod",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.create",
"resource": {"id": "t-1", "type": "tenant", "system": "some-other-system"},
"resource": {
"id": "tenant:acme:prod",
"type": "tenant",
"system": "some-other-system"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "wrong_system"}
"expect": {
"effect": "deny",
"reason": "wrong_system"
}
},
{
"id": "fixture:tenant-engine-unknown-action-deny",
"request": {
"id": "check:tenant-engine-delete-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"tenant": "tenant:platform",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.delete",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"},
"resource": {
"id": "tenant:platform",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "unknown_action"}
"expect": {
"effect": "deny",
"reason": "unknown_action"
}
},
{
"id": "fixture:tenant-engine-wrong-subject-type-deny",
"request": {
"id": "check:tenant-engine-create-t1",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "human"},
"tenant": "tenant:trial:demo-company",
"subject": {
"id": "tenant-engine",
"type": "human"
},
"action": "tenant.create",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"},
"resource": {
"id": "tenant:trial:demo-company",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "wrong_subject_type"}
"expect": {
"effect": "deny",
"reason": "wrong_subject_type"
}
},
{
"id": "fixture:tenant-engine-guardrail-read-pdp-allow",
"request": {
"id": "check:tenant-engine-guardrail-read-pdp",
"tenant": "tenant:friendly:binky",
"subject": {"id": "flex-auth", "type": "service"},
"subject": {
"id": "flex-auth",
"type": "service"
},
"action": "tenant.guardrail.read",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"},
"resource": {
"id": "tenant:friendly:binky",
"type": "guardrail",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "allow", "reason": "write_api_policy_matched"}
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-guardrail-read-writer-allow",
"request": {
"id": "check:tenant-engine-guardrail-read-writer",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"tenant": "tenant:acme:prod",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.guardrail.read",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"},
"resource": {
"id": "tenant:acme:prod",
"type": "guardrail",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "allow", "reason": "write_api_policy_matched"}
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-guardrail-set-writer-allow",
"request": {
"id": "check:tenant-engine-guardrail-set-writer",
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"tenant": "tenant:platform",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.guardrail.set",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"},
"resource": {
"id": "tenant:platform",
"type": "guardrail",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "allow", "reason": "write_api_policy_matched"}
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-guardrail-set-pdp-deny",
"request": {
"id": "check:tenant-engine-guardrail-set-pdp",
"tenant": "tenant:friendly:binky",
"subject": {"id": "flex-auth", "type": "service"},
"tenant": "tenant:trial:demo-company",
"subject": {
"id": "flex-auth",
"type": "service"
},
"action": "tenant.guardrail.set",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"},
"resource": {
"id": "tenant:trial:demo-company",
"type": "guardrail",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "action_not_granted"}
"expect": {
"effect": "deny",
"reason": "action_not_granted"
}
},
{
"id": "fixture:tenant-engine-guardrail-read-unknown-subject-deny",
"request": {
"id": "check:tenant-engine-guardrail-read-ops",
"tenant": "tenant:friendly:binky",
"subject": {"id": "ops", "type": "service"},
"subject": {
"id": "ops",
"type": "service"
},
"action": "tenant.guardrail.read",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"},
"resource": {
"id": "tenant:friendly:binky",
"type": "guardrail",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "unknown_subject"}
"expect": {
"effect": "deny",
"reason": "unknown_subject"
}
},
{
"id": "fixture:tenant-engine-guardrail-set-unknown-subject-deny",
"request": {
"id": "check:tenant-engine-guardrail-set-ops",
"tenant": "tenant:friendly:binky",
"subject": {"id": "ops", "type": "service"},
"tenant": "tenant:acme:prod",
"subject": {
"id": "ops",
"type": "service"
},
"action": "tenant.guardrail.set",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"},
"resource": {
"id": "tenant:acme:prod",
"type": "guardrail",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "unknown_subject"}
"expect": {
"effect": "deny",
"reason": "unknown_subject"
}
},
{
"id": "fixture:tenant-engine-misspelled-guardrail-action-deny",
"request": {
"id": "check:tenant-engine-guardrail-get",
"tenant": "tenant:friendly:binky",
"subject": {"id": "flex-auth", "type": "service"},
"tenant": "tenant:platform",
"subject": {
"id": "flex-auth",
"type": "service"
},
"action": "tenant.guardrail.get",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"},
"resource": {
"id": "tenant:platform",
"type": "guardrail",
"system": "tenant-engine"
},
"context": {}
},
"expect": {"effect": "deny", "reason": "unknown_action"}
"expect": {
"effect": "deny",
"reason": "unknown_action"
}
},
{
"id": "fixture:tenant-engine-portal-create",
@ -538,5 +768,158 @@
"effect": "deny",
"reason": "action_not_granted"
}
},
{
"id": "fixture:tenant-engine-guardrail-read-pdp-cross-tenant-platform-allow",
"request": {
"id": "check:guardrail-read-pdp-tenant:platform",
"tenant": "tenant:platform",
"subject": {
"id": "flex-auth",
"type": "service"
},
"action": "tenant.guardrail.read",
"resource": {
"id": "tenant:platform",
"type": "guardrail",
"system": "tenant-engine"
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-guardrail-read-pdp-cross-tenant-acme-prod-allow",
"request": {
"id": "check:guardrail-read-pdp-tenant:acme:prod",
"tenant": "tenant:acme:prod",
"subject": {
"id": "flex-auth",
"type": "service"
},
"action": "tenant.guardrail.read",
"resource": {
"id": "tenant:acme:prod",
"type": "guardrail",
"system": "tenant-engine"
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-guardrail-read-pdp-cross-tenant-trial-demo-company-allow",
"request": {
"id": "check:guardrail-read-pdp-tenant:trial:demo-company",
"tenant": "tenant:trial:demo-company",
"subject": {
"id": "flex-auth",
"type": "service"
},
"action": "tenant.guardrail.read",
"resource": {
"id": "tenant:trial:demo-company",
"type": "guardrail",
"system": "tenant-engine"
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-create-cross-tenant-platform-allow",
"request": {
"id": "check:create-tenant:platform",
"tenant": "tenant:platform",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.create",
"resource": {
"id": "tenant:platform",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-create-cross-tenant-acme-prod-allow",
"request": {
"id": "check:create-tenant:acme:prod",
"tenant": "tenant:acme:prod",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.create",
"resource": {
"id": "tenant:acme:prod",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {
"effect": "allow",
"reason": "write_api_policy_matched"
}
},
{
"id": "fixture:tenant-engine-tenant-not-target-deny",
"request": {
"id": "check:tenant-not-target",
"tenant": "tenant:friendly:binky",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.create",
"resource": {
"id": "tenant:acme:prod",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "tenant_not_target"
}
},
{
"id": "fixture:tenant-engine-tenant-absent-deny",
"request": {
"id": "check:tenant-absent",
"subject": {
"id": "tenant-engine",
"type": "service"
},
"action": "tenant.create",
"resource": {
"id": "tenant:acme:prod",
"type": "tenant",
"system": "tenant-engine"
},
"context": {}
},
"expect": {
"effect": "deny",
"reason": "tenant_not_target"
}
}
]

View file

@ -2,7 +2,7 @@
id: tenant-engine.write-api.mutate
name: tenant-engine Write API authorization
namespace: tenant-engine:tenant
version: v2
version: v3
status: ready
package: flexauth.tenant_engine.write_api
actions:
@ -178,6 +178,45 @@ The earlier single-write-subject decisions above describe their dated baseline;
this explicit minimal onboarding grant supersedes that baseline only for these
operations and this existing service integration.
## Tenant relation (FLEX-WP-0022-T02, TEN-DEC-2026-002)
**Version v3.** v2 had no tenant rule and every fixture carried the same
tenant, so a deliberate cross-tenant scope and an omitted rule were
indistinguishable — the `FLEX-DEC-2026-008` shape. `tenant-engine` has now
named the relation in its own record (`TEN-DEC-2026-002`,
`tenant-engine/docs/flex-auth-integration.md`); this section states it so a
reviewer can check it, and `FLEX-DEC-2026-016` records the version change.
1. **`tenant` denotes the target tenant record**, not the caller's tenant.
tenant-engine verifies no inbound token and holds no caller tenant to send.
On the guardrail actions the "tenant the guardrail applies to" is the same
record.
2. **Invariant — encoded as a rule.** On every check tenant-engine sends,
`tenant` equals `resource.id` (`authz.FlexAuthWriteAuthorizer` copies one
`tenant_id` onto both). A check where they differ, or where `tenant` is
absent, did not come from this engine and is denied `tenant_not_target`.
This is fail-closed and denies nothing tenant-engine sends.
3. **Scope — deliberately cross-tenant, stated here.** No action in
`valid_actions` is refused on the relationship between `subject` and
`tenant`. The caller administers tenants: its subjects are platform service
identities and the targets are arbitrary tenant records, and `tenant.create`
has no existing target at check time. Authorization is a service-identity
question over `(subject.id, action)`. **`tenant.guardrail.read` does not
differ and must not**: flex-auth calls it while deciding about arbitrary
tenants. No rule below compares `tenant` to the subject; the embedded test
`test_tenant_never_changes_effect` quantifies that over every action and
subject, and the fixtures vary `tenant` so the suite reports on the field.
4. **The one target-dependent rule is not a tenant relation.** The
`user-engine` onboarding grant (NK-WP-0036) excludes the fixed record
`tenant:platform`. That depends on *which* record is targeted, not on any
relation between the subject and the target, so it is outside the scope
statement above and is tested separately
(`test_portal_tenant_changes_effect_only_on_platform_record`).
**Revisit when** tenant-engine gains a verified inbound identity
(`tenancy.yaml` gap I): the caller's tenant becomes knowable, and it arrives as
a new field — this one keeps its meaning.
## Rules
```rego
@ -208,27 +247,41 @@ read_subjects := {"tenant-engine", "flex-auth"}
mutate_subjects := {"tenant-engine"}
# TEN-DEC-2026-002: tenant is the target record and always equals resource.id.
# No rule compares tenant to the subject: the scope is cross-tenant by design.
request_tenant := object.get(input, "tenant", "")
tenant_is_target if {
request_tenant != ""
request_tenant == input.resource.id
}
allowed if {
tenant_is_target
granted
}
decision := {"effect": "allow", "reason": "write_api_policy_matched"} if {
allowed
} else := {"effect": "deny", "reason": first_denial} if {
true
}
allowed if {
granted if {
input.resource.system == "tenant-engine"
input.action in read_actions
input.subject.type == "service"
input.subject.id in read_subjects
}
allowed if {
granted if {
input.resource.system == "tenant-engine"
input.action in mutate_actions
input.subject.type == "service"
input.subject.id in mutate_subjects
}
allowed if {
granted if {
input.resource.system == "tenant-engine"
input.resource.type == "tenant"
input.resource.id != "tenant:platform"
@ -237,7 +290,7 @@ allowed if {
input.subject.id == "user-engine"
}
allowed if {
granted if {
input.resource.system == "tenant-engine"
input.resource.type == "tenant"
input.action == "tenant.read"
@ -249,6 +302,8 @@ default first_denial := "no_matching_rule"
first_denial := "wrong_system" if {
input.resource.system != "tenant-engine"
} else := "tenant_not_target" if {
not tenant_is_target
} else := "unknown_action" if {
not input.action in valid_actions
} else := "wrong_subject_type" if {
@ -265,7 +320,9 @@ first_denial := "wrong_system" if {
```rego test
package flexauth.tenant_engine.write_api_test
import future.keywords.every
import future.keywords.if
import future.keywords.in
import data.flexauth.tenant_engine.write_api
base_request := {
@ -273,7 +330,7 @@ base_request := {
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"action": "tenant.create",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
}
test_known_operator_create_allowed if {
@ -281,170 +338,245 @@ test_known_operator_create_allowed if {
}
test_role_grant_allowed if {
write_api.decision.effect == "allow" with input as {
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"action": "tenant.role.grant",
"resource": {"id": "t-1", "type": "role-grant", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "role-grant", "system": "tenant-engine"}
}
}
test_tenant_update_allowed if {
write_api.decision.effect == "allow" with input as {
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"action": "tenant.update",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
}
}
test_tenant_retire_allowed if {
write_api.decision.effect == "allow" with input as {
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"action": "tenant.retire",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
}
}
test_tenant_reactivate_allowed if {
write_api.decision.effect == "allow" with input as {
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"action": "tenant.reactivate",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
}
}
test_misspelled_lifecycle_action_denied if {
write_api.decision.reason == "unknown_action" with input as {
write_api.decision.reason == "unknown_action" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"action": "tenant.retired",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
}
}
test_unknown_subject_retire_denied if {
write_api.decision.reason == "unknown_subject" with input as {
write_api.decision.reason == "unknown_subject" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "some-other-service", "type": "service"},
"action": "tenant.retire",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
}
}
test_unknown_subject_denied if {
write_api.decision.reason == "unknown_subject" with input as {
write_api.decision.reason == "unknown_subject" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "some-other-service", "type": "service"},
"action": "tenant.create",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
}
}
test_wrong_system_denied if {
write_api.decision.reason == "wrong_system" with input as {
write_api.decision.reason == "wrong_system" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"action": "tenant.create",
"resource": {"id": "t-1", "type": "tenant", "system": "some-other-system"}
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "some-other-system"}
}
}
test_unknown_action_denied if {
write_api.decision.reason == "unknown_action" with input as {
write_api.decision.reason == "unknown_action" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"action": "tenant.delete",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
}
}
test_wrong_subject_type_denied if {
write_api.decision.reason == "wrong_subject_type" with input as {
write_api.decision.reason == "wrong_subject_type" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "human"},
"action": "tenant.create",
"resource": {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "tenant", "system": "tenant-engine"}
}
}
test_guardrail_read_by_pdp_allowed if {
write_api.decision.effect == "allow" with input as {
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "flex-auth", "type": "service"},
"action": "tenant.guardrail.read",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "guardrail", "system": "tenant-engine"}
}
}
test_guardrail_read_by_writer_allowed if {
write_api.decision.effect == "allow" with input as {
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"action": "tenant.guardrail.read",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "guardrail", "system": "tenant-engine"}
}
}
test_guardrail_set_by_writer_allowed if {
write_api.decision.effect == "allow" with input as {
write_api.decision.effect == "allow" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"action": "tenant.guardrail.set",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "guardrail", "system": "tenant-engine"}
}
}
test_guardrail_set_by_pdp_denied if {
write_api.decision.reason == "action_not_granted" with input as {
write_api.decision.reason == "action_not_granted" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "flex-auth", "type": "service"},
"action": "tenant.guardrail.set",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "guardrail", "system": "tenant-engine"}
}
}
test_guardrail_read_unknown_subject_denied if {
write_api.decision.reason == "unknown_subject" with input as {
write_api.decision.reason == "unknown_subject" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "ops", "type": "service"},
"action": "tenant.guardrail.read",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "guardrail", "system": "tenant-engine"}
}
}
test_misspelled_guardrail_action_denied if {
write_api.decision.reason == "unknown_action" with input as {
write_api.decision.reason == "unknown_action" with input as { "tenant": "tenant:friendly:binky",
"subject": {"id": "flex-auth", "type": "service"},
"action": "tenant.guardrail.get",
"resource": {"id": "t-1", "type": "guardrail", "system": "tenant-engine"}
"resource": {"id": "tenant:friendly:binky", "type": "guardrail", "system": "tenant-engine"}
}
}
test_portal_create if {
write_api.decision.effect == "allow" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.create", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
write_api.decision.effect == "allow" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.create", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
}
test_portal_read if {
write_api.decision.effect == "allow" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
write_api.decision.effect == "allow" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
}
test_owner_read if {
write_api.decision.effect == "allow" with input as {"subject": {"id": "tenant-engine", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
write_api.decision.effect == "allow" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "tenant-engine", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
}
test_portal_platform if {
write_api.decision.effect == "deny" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.create", "resource": {"id": "tenant:platform", "type": "tenant", "system": "tenant-engine"}}
write_api.decision.effect == "deny" with input as { "tenant": "tenant:platform","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.create", "resource": {"id": "tenant:platform", "type": "tenant", "system": "tenant-engine"}}
}
test_portal_wrong_type if {
write_api.decision.effect == "deny" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.create", "resource": {"id": "tenant:trial:demo-company", "type": "guardrail", "system": "tenant-engine"}}
write_api.decision.effect == "deny" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.create", "resource": {"id": "tenant:trial:demo-company", "type": "guardrail", "system": "tenant-engine"}}
}
test_portal_no_grants if {
write_api.decision.effect == "deny" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.role.grant", "resource": {"id": "tenant:trial:demo-company", "type": "role-grant", "system": "tenant-engine"}}
write_api.decision.effect == "deny" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.role.grant", "resource": {"id": "tenant:trial:demo-company", "type": "role-grant", "system": "tenant-engine"}}
}
test_portal_no_retirement if {
write_api.decision.effect == "deny" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.retire", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
write_api.decision.effect == "deny" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.retire", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
}
test_portal_no_guardrail if {
write_api.decision.effect == "deny" with input as {"subject": {"id": "user-engine", "type": "service"}, "action": "tenant.guardrail.set", "resource": {"id": "tenant:trial:demo-company", "type": "guardrail", "system": "tenant-engine"}}
write_api.decision.effect == "deny" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "user-engine", "type": "service"}, "action": "tenant.guardrail.set", "resource": {"id": "tenant:trial:demo-company", "type": "guardrail", "system": "tenant-engine"}}
}
test_pdp_no_tenant_read if {
write_api.decision.effect == "deny" with input as {"subject": {"id": "flex-auth", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
write_api.decision.effect == "deny" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "flex-auth", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
}
test_unknown_no_read if {
write_api.decision.effect == "deny" with input as {"subject": {"id": "unknown", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
write_api.decision.effect == "deny" with input as { "tenant": "tenant:trial:demo-company","subject": {"id": "unknown", "type": "service"}, "action": "tenant.read", "resource": {"id": "tenant:trial:demo-company", "type": "tenant", "system": "tenant-engine"}}
}
# --- Tenant relation (TEN-DEC-2026-002, FLEX-WP-0022-T02) ---
scope_tenants := {"tenant:platform", "tenant:friendly:binky", "tenant:acme:prod", "tenant:trial:demo-company"}
resource_type_for := {
"tenant.read": "tenant",
"tenant.create": "tenant",
"tenant.role.grant": "role-grant",
"tenant.role.revoke": "role-grant",
"tenant.plan.assign": "plan-assignment",
"tenant.update": "tenant",
"tenant.retire": "tenant",
"tenant.reactivate": "tenant",
"tenant.guardrail.read": "guardrail",
"tenant.guardrail.set": "guardrail",
}
scoped_request(subject, action, t) := {
"tenant": t,
"subject": {"id": subject, "type": "service"},
"action": action,
"resource": {"id": t, "type": resource_type_for[action], "system": "tenant-engine"},
}
effects_for(subject, action, tenants) := {e |
some t in tenants
e := write_api.decision.effect with input as scoped_request(subject, action, t)
}
# Scope: varying the target tenant never changes the effect.
test_tenant_never_changes_effect if {
every subject in {"tenant-engine", "flex-auth", "some-other-service"} {
every action in write_api.valid_actions {
count(effects_for(subject, action, scope_tenants)) == 1
}
}
}
# The quantified test above must not pass by denying everything.
test_cross_tenant_writes_allowed if {
every action in write_api.mutate_actions {
effects_for("tenant-engine", action, scope_tenants) == {"allow"}
}
}
test_pdp_guardrail_read_is_cross_tenant if {
effects_for("flex-auth", "tenant.guardrail.read", scope_tenants) == {"allow"}
}
# user-engine's grant excludes one fixed record; that is not a tenant relation.
test_portal_tenant_changes_effect_only_on_platform_record if {
every action in write_api.valid_actions {
count(effects_for("user-engine", action, scope_tenants - {"tenant:platform"})) == 1
}
effects_for("user-engine", "tenant.create", {"tenant:platform"}) == {"deny"}
}
# Invariant: tenant must equal resource.id.
test_tenant_not_target_denied if {
write_api.decision.reason == "tenant_not_target" with input as {
"tenant": "tenant:friendly:binky",
"subject": {"id": "tenant-engine", "type": "service"},
"action": "tenant.create",
"resource": {"id": "tenant:acme:prod", "type": "tenant", "system": "tenant-engine"}
}
}
test_tenant_absent_denied if {
write_api.decision.reason == "tenant_not_target" with input as {
"subject": {"id": "tenant-engine", "type": "service"},
"action": "tenant.create",
"resource": {"id": "tenant:acme:prod", "type": "tenant", "system": "tenant-engine"}
}
}
```