flex-auth/workplans/FLEX-WP-0022-tenant-scope-coverage.md
tegwick 3081067325
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
State tenant-engine's tenant relation in the write-api package, v3.
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
2026-09-21 07:39:57 +02:00

6.3 KiB

id type title domain repo status flavor depends_on owner topic_slug planning_priority planning_order depends_on_workplans related_workplans created updated state_hub_workstream_id
FLEX-WP-0022 workplan Tenant scoping is unstated in tenant-engine and untested in two more packages infotech flex-auth finished implementation
FLEX-WP-0021
claude netkingdom P1 220
FLEX-WP-0021
FLEX-WP-0010
FLEX-WP-0014
2026-09-06 2026-09-21 804c588c-f47a-50c4-bdd7-51b24bbf9539

FLEX-WP-0022 — Tenant scoping is unstated in tenant-engine and untested in two more packages

Opened by the sweep in FLEX-DEC-2026-008, which found secrets-engine.catalog-lane.lifecycle v1 allowing a foreign tenant and then asked whether the other packages shared the defect.

What the sweep found

Package Fixture tenants Tenant rule State
secrets-engine 3 distinct added at v2 fixed, FLEX-DEC-2026-008
qonto-assistant 2 distinct wrong_tenant fine
user-engine 2 distinct cross_tenant fine
ops-warden constant wrong_tenant rule real, fixtures do not vary it
railiance-platform constant wrong_tenant same
tenant-engine constant none deployed, unscoped

Verified against tenant-engine: an allowed tenant.create re-sent under tenant: tenant:coulomb returns allow / write_api_policy_matched.

Why tenant-engine is not the same fix

secrets-engine sends its own calling identity's tenant, so a constant known_tenant is the right rule. tenant-engine is different in kind: its subjects all sit in tenant:platform while its fixtures send tenant:friendly:binky, so the request tenant names the target of the operation. A service whose purpose is creating and retiring tenants acts across them by design, and a constant would break it on its first real call.

The defect today is therefore not "the rule is missing" but "nobody can tell whether it is missing": a deliberate cross-tenant scope and an omitted rule look identical in the artifact. That is the same publishing-shape argument gate-house made for §12's derived-artifact rule.

1. Get the intended tenant relation from tenant-engine

id: FLEX-WP-0022-T01
status: done
priority: high
state_hub_task_id: "a84dcee5-9426-5b30-8dd3-f4c076d00174"

Owner: flex-auth to ask; tenant-engine owns the answer.

  • Ask what the CheckRequest tenant denotes on the write API: the caller's tenant, the target tenant record, or the tenant a guardrail applies to.
  • Ask whether any of the nine write actions must be refused cross-tenant, and whether tenant.guardrail.read (which flex-auth itself calls) differs.

Gate: the relation is named by tenant-engine, not inferred here. This is the FLEX-WP-0021-T01 rule applied to a field rather than to an action list.

2026-09-15: asked tenant-engine to name the relation (e8ba6a53-0093-4dc0-ad70-01f6b8c8e76b). Their live FlexAuthWriteAuthorizer.authorize copies tenant_id onto both CheckRequest.tenant and resource.id. That is observation, not admission.

2026-09-20: re-asked after five days of silence, restating the three questions and saying plainly that "none" is a complete answer. The gate is unchanged: a policy rule inferred from reading their code would make flex-auth the author of their tenancy model, which is the boundary FLEX-WP-0021-T01 exists to hold. T02 stays wait rather than being guessed forward.

2026-09-21: answered by tenant-engine in its own record, TEN-DEC-2026-002 (hub decision ecaebbb7-fe90-4235-a79f-23457e9727bc) and docs/flex-auth-integration.md, commit d132db0; message 3d3de8bc. Read from the committed record, not the message. tenant is the target tenant record; it always equals resource.id; none of the nine write actions is refused cross-tenant, deliberately; tenant.guardrail.read does not differ and must not. Gate met: the relation is named by tenant-engine.

2. Encode the relation, or record that there is none

id: FLEX-WP-0022-T02
status: done
priority: high
state_hub_task_id: "712ac826-845f-54bd-8446-f11258d21eb4"

Owner: flex-auth.

If a relation exists, encode it in tenant-engine.write-api.mutate as a new version — fail-open corrections are visible as version changes (FLEX-DEC-2026-008) — with a fixture per side.

If the scope is genuinely unrestricted, say so in the package: a stated "this package is deliberately cross-tenant, because the caller administers tenants" is a rule a reviewer can check. Silence is not.

Either way the fixtures must vary tenant, so the suite reports on the field.

2026-09-21: done as tenant-engine.write-api.mutate v3 (FLEX-DEC-2026-016). Both of tenant-engine's commitments are encoded: allowed requires tenant_is_target (non-empty tenant equal to resource.id; otherwise deny / tenant_not_target), and the package states the cross-tenant scope in prose and quantifies it in test_tenant_never_changes_effect (every action, three subjects, four tenants). Fixtures now rotate tenant across tenant:friendly:binky, tenant:acme:prod, tenant:platform, tenant:trial:demo-company, with five cross-tenant allows and two tenant_not_target denies added: 42 fixtures and 33 embedded tests pass, go test ./... green. One boundary reported back to tenant-engine rather than papered over: user-engine's grant excludes the fixed record tenant:platform, which is target-dependent but not a subject/tenant relation.

3. Vary tenant in the two suites that hold it constant

id: FLEX-WP-0022-T03
status: done
priority: medium
state_hub_task_id: "3058f171-99d2-526b-a1bb-bd7aed87d10a"

Owner: flex-auth.

ops-warden and railiance-platform have working wrong_tenant rules exercised only by Rego tests. Add a wrong-tenant deny fixture to each so the fixture suite covers what the rule claims. No policy change and no version bump: the behaviour is already correct, only the evidence is thin.

Gate: no package's fixture suite holds tenant constant.

2026-09-15: added fixture:ops-warden-wrong-tenant-deny and fixture:credential-grant-wrong-tenant-deny plus matching check_request_deny_wrong_tenant.json files. Both suites now vary tenant. test-policy and flex-auth check return deny / wrong_tenant. No policy or version change.