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

@ -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"}
}
}
```