# VRX-PACK-ISO-001 — URS-36 BLK-36-01 RLS / ADR-004 Re-Audit (code-verified)

| Field | Value |
|---|---|
| Document ID | VRX-PACK-ISO-001 |
| Purpose | Operationalizes PACK-COR-003 step 1: re-audit URS-36 BLK-36-01 (RLS vs ADR-004) against actual code **before** drafting the central tenant-isolation decision. |
| Status | **DRAFT — decision input for Architect + Security/Privacy + Data Integrity + Head of QA. Not a decision; not approved.** |
| Date | 2026-06-07 |
| Verified against | `verixaai/verixa` · `dev-vimal-deploy @ d8ccdb97` (read-only). NOTE: deploy HEAD has since advanced to `9829330b` (2026-06-08) — re-confirm at the chosen baseline (VRX-PACK-BASELINE-001). |
| Method | Read-only migration + service grep; line-pinned. |

> **Headline:** the re-audit **changes the BLK-36-01 picture**. BLK-36-02 ("no code evidence") is **stale** — the licensing implementation exists. BLK-36-01 is **not** "RLS missing"; it is now a **spec↔code drift** (the code adopted platform-scoped RLS while the URS still assumes app-layer-only) **plus a partial RLS coverage gap**. Do not carry the old framing into the decision.

---

## 1. What the URS / ADR-004 documented (line-pinned)

`URS-36-TEST-INVENTORY.md` §"Test Suite 13: Tenant Isolation Hardening — RLS vs App-Layer-Only Conflict (BLK-36-01)" (line 1357+):

> "URS-36 §0.6 notes unresolved conflict: Architecture doc claims 'PostgreSQL RLS on every table'; **ADR-004 states 'RLS NOT enabled; application-layer-only isolation.'** Until resolved, URS-36 assumes RLS is NOT enabled and implements defense-in-depth: explicit `tenant_environment_id` filter on every license query as middleware backstop."

So the spec (dated 2026-05-13) **assumes RLS is OFF** and relies on an app-layer filter, pending the ADR decision.

## 2. Verified code reality at `d8ccdb97` (line-pinned)

### 2.1 Licensing implementation EXISTS (BLK-36-02 is stale)
| Artifact | Evidence |
|---|---|
| `customer_accounts` | migration `232_customer_accounts.sql` — RLS enabled + policy |
| `tenant_environments` (public) | migration `233_tenant_environments.sql` — RLS enabled + policy; **this is the table the licensing service uses** (`modules/licensing/service.ts`, `routes.ts`, `provisioning.service.ts` all `selectFrom('tenant_environments')`) |
| `platform.plans`, `platform.tenant_environments` (stub) | migration `240_platform_licensing_engineer_a_stubs.sql` |
| `platform.subscriptions`, `platform.module_entitlements`, `platform.seat_allocations`, `platform.license_assignments` | migration `241_platform_licensing_subscriptions.sql` |
| Licensing module (service, routes, provisioning, gate) | `packages/backend/src/modules/licensing/*` |
| `fix/urs-36-license-tenant-isolation @ 4aea7504` | **merged** into `dev-vimal-deploy` |

### 2.2 RLS has been ADDED to platform licensing tables — contradicting the URS assumption
Migration **`254_urs36_p0_rls_and_migration_restamp.sql`** (header: "URS-36 P0 sweep", fixes findings from `URS-36-FULL-TEST-REPORT.md`) — finding **#U-07** enables RLS on platform licensing tables:

| Platform table | ENABLE + FORCE RLS | Deny-default policy | Evidence |
|---|:--:|:--:|---|
| `platform.subscriptions` | ✅ | ✅ `subscriptions_platform_scoped` | 254:82–86 |
| `platform.license_assignments` | ✅ | ✅ `license_assignments_platform_scoped` | 254:91–95 |
| `platform.license_audit_events` | ✅ | ✅ | 254:104–108 |
| `platform.license_cache_versions` | ✅ | ✅ | 254:113–117 |
| `platform.module_entitlements` | ❌ **none** | ❌ | not found in any migration |
| `platform.seat_allocations` | ❌ **none** | ❌ | not found in any migration |
| `platform.tenant_environments` (stub) | ❌ none | ❌ | duplicate of public 233; unused by service |
| `platform.plans` | ❌ none | ❌ | catalog/global (likely non-tenant) |

Policy mechanism: `current_setting('app.is_platform_context', true) = 'true'` → callers using `tdal.withPlatformContext` get access; any tenant-scoped session is **blocked**. The 254 header states these rows are platform-scoped and the policy "matches the QS-6 contract and 21 CFR 11.10(d)."

Access path: migration **`277_grant_platform_schema_to_runtime_role.sql`** grants the runtime role `verixa_app` `SELECT/INSERT/UPDATE/DELETE` on **all** tables in schema `platform`. So schema-isolation is **not** the control — **RLS is**. Tables without an RLS deny policy are therefore reachable from a tenant-context session.

## 3. Reclassification of BLK-36-01 / BLK-36-02

| Blocker | Old framing (URS, 2026-05-13) | Verified status (d8ccdb97) | New classification |
|---|---|---|---|
| BLK-36-02 | "No code evidence of URS-36 implementation" | Tables 232/233/240/241, migrations 254/277, full licensing module exist; isolation-fix branch merged | **STALE — substantially closed** (re-verify at chosen baseline) |
| BLK-36-01 | "RLS conflict; architecture says RLS-on-every-table, ADR-004 says RLS-off app-layer-only; assume RLS off" | Code adopted **platform-scoped RLS** (254) — opposite of the URS's "assume RLS off"; coverage is **partial** | **OPEN, reframed: spec↔code drift + partial RLS coverage** |

## 4. Residual real gaps (verified)

1. **Partial RLS coverage (P0).** `platform.module_entitlements` and `platform.seat_allocations` carry licensing/entitlement data but have **no RLS and no deny policy**, while the runtime role has full DML (277). Whether each carries tenant-identifying data is `Unknown — evidence required`; for any that does, a tenant-context session is **not DB-blocked** from reading platform-wide rows → cross-context exposure. The 254 sweep covered 4 tables; these were missed.
2. **Spec↔code contradiction (P0).** URS-36 §0.6 + the architecture doc + ADR-004 disagree, and the **code (254) now implements RLS**, contradicting the URS's "assume RLS off / app-layer-only" posture. The authoritative model must be settled and all three documents reconciled to the implemented one.
3. **Duplicate `tenant_environments` (Major).** Public `tenant_environments` (233, RLS, used by the service) vs `platform.tenant_environments` (240 stub, no RLS, unused). Dual definition; remove/собreconcile the stub to prevent future mis-wiring.
4. **App-layer backstop still required (defense-in-depth).** The URS's explicit `tenant_environment_id` filter remains valuable even with RLS on — but it must not be the *sole* control where RLS is the stated control.

## 5. Decision needed (input to the central tenant-isolation decision)

Before URS-36 freeze, the Architect + Security/Privacy + DI + Head of QA **shall** decide and record (URS-13):

1. **Authoritative isolation model:** confirm **platform-scoped RLS** (as implemented in 254) as canonical; update the architecture doc, **ADR-004**, and **URS-36 §0.6** to match (withdraw the "RLS NOT enabled / app-layer-only" statement). The decision is no longer "RLS vs app-layer" — the code chose RLS; the docs must catch up.
2. **Complete RLS coverage:** add ENABLE+FORCE+deny policy to `platform.module_entitlements`, `platform.seat_allocations`, and any other platform licensing table carrying tenant-identifying data; classify `platform.plans` / `platform.tenant_environments` stub explicitly (global vs tenant; remove the stub).
3. **OQ negative tests (mandatory):** Tenant A cannot read/act on Tenant B licensing rows; a tenant-context session is denied on **every** platform licensing table; the License Gate denies unlicensed regulated writes; app-layer filter holds even if a policy gap exists.
4. **Cross-module consistency:** carry this decision into URS-41 corpus tenancy (DEC-41-07) and URS-38 cross-tenant analytics (AN-010) so the platform-scoped-RLS pattern is applied uniformly.

## 6. Required actions

1. Settle + document the authoritative isolation model; reconcile architecture doc + ADR-004 + URS-36 §0.6 to the code. [Architect + Security/Privacy + DI + QA]
2. Close the partial-RLS gap (module_entitlements, seat_allocations, +classify plans/stub). [Engineering]
3. Author + execute the Tenant A/B OQ negative suite across all platform licensing tables. [Validation Test Architect → QA]
4. Re-stamp BLK-36-02 as closed (with evidence) and BLK-36-01 as reframed per §3. [URS owner under change control]
5. Re-verify at the chosen pack baseline (deploy HEAD moved to `9829330b`). [Engineering + CSV/CSA]

| Boundary Check | Result |
|---|---|
| Primary lens | Data Integrity (RLS/QS-6/Part 11) + Product/Engineering Architect |
| Owned deliverable | Code-verified re-audit + decision input (not the decision) |
| Out-of-scope (deferred) | **Security/Privacy adequacy → `Handoff required — Security/Privacy Expert`**; final isolation decision → human Architect+Security+DI+QA; OQ test protocol → Validation Test Architect; final approval → Head of QA |
| Repo | Read-only; no files modified |
| Final status | Draft — Handoff required (Security/Privacy); Head of QA + Architect decision required |

*Verified read-only against `verixaai/verixa dev-vimal-deploy @ d8ccdb97`. No repo files modified. No claim of validation, compliance, or approval. `platform.module_entitlements` / `platform.seat_allocations` tenant-data sensitivity is `Unknown — evidence required`.*
