Name what CheckRequest.tenant denotes; record the access-engine rename intake
TEN-DEC-2026-002 answers flex-auth's FLEX-WP-0022-T01, open since 2026-09-15: `tenant` denotes the target tenant record, equals `resource.id` by intent, and the write API is cross-tenant by design — no action is refused on the subject/tenant relationship, and tenant.guardrail.read must not differ because flex-auth itself calls it across tenants. docs/flex-auth-integration.md states the relation in this repo's voice. TEN-IN-0004 is the live record flex-auth asked for on FLEX-WP-0020. Runtime names stay flex-auth (FLEX-DEC-2026-013) and all deploy, cluster and settings coordinates verify as retained; only five documentation repository paths change when the rename lands. 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:
parent
6e59840379
commit
d132db064f
4 changed files with 221 additions and 0 deletions
|
|
@ -74,3 +74,121 @@ review note naming Engine above a line that admitted the body was unadapted.
|
|||
workplan: guardrails are no longer reserved, and "not a policy
|
||||
enforcement point" must be restated so it cannot be read as denying the
|
||||
PEP *shape* of our writes.
|
||||
|
||||
---
|
||||
|
||||
## TEN-DEC-2026-002 — `CheckRequest.tenant` denotes the target tenant record; the write API is cross-tenant by design
|
||||
|
||||
```yaml
|
||||
id: TEN-DEC-2026-002
|
||||
kind: decision
|
||||
title: "CheckRequest.tenant denotes the target tenant record; the write API is cross-tenant by design"
|
||||
status: resolved
|
||||
disposition: approved
|
||||
origin: cross-repo
|
||||
origin_ref: FLEX-WP-0022-T01
|
||||
owner: tenant-engine
|
||||
affects:
|
||||
- tenant-engine
|
||||
- flex-auth
|
||||
requested_by: flex-auth
|
||||
created: "2026-09-21"
|
||||
updated: "2026-09-21"
|
||||
decided_by: tenant-engine (the consumer naming its own relation)
|
||||
decided_at: "2026-09-21"
|
||||
state_hub_decision_id: "ecaebbb7-fe90-4235-a79f-23457e9727bc"
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
flex-auth asked, on 2026-09-15 and again on 2026-09-20, for tenant-engine to
|
||||
*name* the relation rather than infer it from code, and declined to encode a
|
||||
policy rule from reading `FlexAuthWriteAuthorizer.authorize`. That refusal is
|
||||
correct: a PDP that infers a consumer's intent and then enforces it has
|
||||
stopped being a decision point. The custodian's estate-wide review
|
||||
(`the-custodian/docs/assessments/2026-09-21-layer-declaration-boundaries.md`,
|
||||
question 8) records this as the only one of nine open estate questions
|
||||
blocking implementation.
|
||||
|
||||
Three questions were asked. This decision answers all three in
|
||||
tenant-engine's own words.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **`CheckRequest.tenant` denotes the TARGET tenant record** — the tenant
|
||||
record the action operates on. Not the caller's tenant. Not, as a separate
|
||||
meaning, "the tenant a guardrail applies to".
|
||||
|
||||
flex-auth's observation is correct and it *is* the intended relation:
|
||||
`authorize()` copies the same `tenant_id` onto both `CheckRequest.tenant`
|
||||
and `resource.id`, and that is deliberate, not an artefact. Every call site
|
||||
in `app.py` supplies the target: `payload.tenant_id` for `tenant.create`,
|
||||
the `{tenant_id}` path segment for every other action.
|
||||
|
||||
2. **`tenant` cannot denote the caller's tenant, because tenant-engine does
|
||||
not know it.** This service verifies no inbound token; the acting identity
|
||||
arrives as a caller-supplied `actor` string (`tenancy.yaml` I1, gap I).
|
||||
There is no caller tenant in scope at the point `authorize()` is called.
|
||||
Putting one in `CheckRequest.tenant` would mean shipping a value we do not
|
||||
hold.
|
||||
|
||||
3. **For guardrail actions the third reading collapses into the second.** A
|
||||
guardrail is addressed only through the tenant it constrains, so "the
|
||||
target tenant record" and "the tenant the guardrail applies to" are the
|
||||
same tenant on `tenant.guardrail.read` and `tenant.guardrail.set`. The
|
||||
third option is not a distinct semantics in this API; it is the same field
|
||||
seen from the guardrail side.
|
||||
|
||||
4. **None of the nine write actions is refused cross-tenant.** This is a
|
||||
deliberate, unrestricted scope, not an omitted rule:
|
||||
|
||||
- The write API is a platform-operator admin surface. Its subjects are
|
||||
platform service identities in `tenant:platform`; the targets are
|
||||
arbitrary tenant records. Creating and retiring tenants is what this
|
||||
engine is *for*, and it cannot be done from inside the tenant being
|
||||
created or retired.
|
||||
- `tenant.create` in particular has no existing target at check time. Any
|
||||
rule requiring the caller's tenant to equal `CheckRequest.tenant` would
|
||||
deny every create.
|
||||
- Authorization on this surface is therefore a **service-identity**
|
||||
question over `(subject.id, action)`. `tenant` and `resource.id` carry
|
||||
*which record* is being touched, for the decision record, the audit
|
||||
trail, and any future per-target scoping — they are not a same-tenant
|
||||
constraint input today.
|
||||
|
||||
5. **`tenant.guardrail.read` does not differ, and must not.** flex-auth is
|
||||
the platform PDP and calls it while rendering decisions about arbitrary
|
||||
tenants. A same-tenant rule on `tenant.guardrail.read` would break
|
||||
flex-auth itself. Same relation (target tenant record), same unrestricted
|
||||
scope.
|
||||
|
||||
6. **There is nonetheless a checkable rule, and tenant-engine asks for it to
|
||||
be stated.** flex-auth is right that an unrestricted scope and an omitted
|
||||
rule are indistinguishable in a package with no tenant rule and constant
|
||||
fixtures — that is the defect, and it is ours to remove by saying so.
|
||||
Two statements tenant-engine commits to, which flex-auth may encode as
|
||||
rules a reviewer can check:
|
||||
|
||||
- **Invariant:** on every action tenant-engine sends, `tenant` equals
|
||||
`resource.id`. A check where they differ did not come from this engine.
|
||||
- **Scope:** no action in the vocabulary is refused on the relationship
|
||||
between `subject` and `tenant`. Varying `tenant` across fixtures must
|
||||
not change the effect; that is the assertion we want the suite to make.
|
||||
|
||||
If flex-auth prefers to encode only the second and leave the first as a
|
||||
documented expectation, that is flex-auth's call as the PDP.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `docs/flex-auth-integration.md` states the relation, the invariant, and the
|
||||
cross-tenant scope in tenant-engine's voice. That document, not this code,
|
||||
is the statement of intent.
|
||||
- flex-auth's `tenant-engine.write-api.mutate` package can now carry a stated
|
||||
tenant rule with varying fixtures. Per `FLEX-DEC-2026-008` that is a version
|
||||
bump; tenant-engine expects one and needs no notice period, since the stated
|
||||
rule matches shipped behaviour and denies nothing that is allowed today.
|
||||
- Should tenant-engine ever gain a verified inbound identity (`tenancy.yaml`
|
||||
gap I), the caller's tenant becomes knowable and this decision must be
|
||||
revisited — but it would be a *new* field, not a change of meaning for this
|
||||
one.
|
||||
- Unblocks `FLEX-WP-0022-T02`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue