--- id: FLEX-WP-0022 type: workplan title: "Tenant scoping is unstated in tenant-engine and untested in two more packages" domain: infotech repo: flex-auth status: proposed owner: claude topic_slug: netkingdom planning_priority: P1 planning_order: 220 depends_on_workplans: - FLEX-WP-0021 related_workplans: - FLEX-WP-0010 - FLEX-WP-0014 created: "2026-09-06" updated: "2026-09-06" state_hub_workstream_id: "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 ```task id: FLEX-WP-0022-T01 status: todo 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. ## 2. Encode the relation, or record that there is none ```task id: FLEX-WP-0022-T02 status: wait 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. ## 3. Vary tenant in the two suites that hold it constant ```task id: FLEX-WP-0022-T03 status: todo 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.