Files
stack/docs/native-kanban-sot/KBN-101-ENVELOPE-A.md
jason.woltje dc45eb7c30
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
feat(kbn): land KBN-101 Envelope A v6 (rc.20) — declarative sink-RBAC + RLS write-source (Form A) (#902)
Co-authored-by: jason.woltje <jason@diversecanvas.com>
Co-committed-by: jason.woltje <jason@diversecanvas.com>
2026-07-25 23:58:18 +00:00

185 KiB
Raw Blame History

KBN-101 — B1/B2 Envelope A (v6, FINAL) — Declarative Sink-RBAC + Per-Role Credential/Connection-Selection + RLS Write-Source (INSERT tenant-bound, single-compound-or-RESTRICTIVE composition) + Sink-Resident User-Override + Read/USING Enforcement

Ratification status: RATIFIED — part of the frozen SSOT as of this PR (KBN-101 Envelope A, landed FORM A — apply-in-place). This is the converged v6 envelope, ratified as the authoritative record of the rc.20 contract amendment now inlined into KBN-101-DB-ROLE-SPLIT.md §4/§10, SHARED-CONTRACT.md (### 1.0.0-rc.20), and MISSION-MANIFEST.md §10.

Ratification lineage (authoritative): Jason B1 ruling (2026-07-20T23:08Z) + Mos coordinator OPTION A (home the layer) + Mos Q1 ruling (TIER-LEVEL) + Mos Q2 ruling (RLS WITH CHECK AUTHORIZED, two mandatory conditions — ENABLE/FORCE ROW LEVEL SECURITY as the terminal migration step, and non-owner rung roles) + Gate A (a11a1e2c) + Gate B (a6aac2cd).

Ratification note. The sections below are the converged v6 envelope text, retained VERBATIM for traceability. Their design-time framing — e.g. "Scope: DESIGN / CONTRACT-AMENDMENT only. No implementation code, no PRs, no SSOT edits." and "Base: … this doc modifies nothing there." — describes the envelope as originally authored against base b0d78d86. As of this PR that rc.20 amendment is ratified and applied in-place (FORM A) to the frozen SSOT; the design-time "no SSOT edits" wording is historical authoring context, not a live constraint on this ratified record. The Ratification lineage above is authoritative.


Status: v6 envelope (design / contract-amendment), FINAL. v6 = v5 + N1/N2/F-NB4 non-blocking test/lint/cert hardening ONLY; NO design predicate/policy/grant/role/table change. v5 CONVERGED — BOTH terminal re-gate-4 gates returned GO (Gate A-delta-4 a4ce075a; Gate B-delta-4 a379e0de), NO HALT; Gate B-delta-4 flagged 3 EXPLICITLY NON-BLOCKING hardening notes "for the trunk-commit record," and v6 folds exactly those 3 as additive test/lint/cert wording on an already-BOTH-GO design: N1 (strengthen the pg_policy.polpermissive lint — group by effective role incl. PUBLIC/inheritance; reject an internally-disjunctive single WITH CHECK; behavioral positive-controls remain the PRIMARY proof), N2 (state the option-(b) vacuous-deny guard invariant — ≥1 permissive policy per writable (rung, command)), and F-NB4 (extend owner non-reachability to SET-ROLE assumability — conjoin the MEMBER variant of pg_has_role). No design predicate, policy, grant, role, or table is changed. v6 supersedes v5 (a6e790fd), which supersedes v4 (a48ad69c). Preserves EVERY verified v4 closure (all checks passed on v4 EXCEPT one bounded item) and closes the SINGLE remaining BLOCKING finding raised identically by BOTH re-gate-3 gates (Gate A-delta-3 aa5cbf73 = CHANGES-NEEDED; Gate B-delta-3 aa881074 = 1 blocking): the RLS WITH CHECK composition semantics — v4's construction is right but it justifies the composition with a FALSE PostgreSQL claim ("PostgreSQL requires ALL applicable WITH CHECK clauses to pass / they AND") and leaves the realization unspecified, so an implementation could realize the status-pin, the workspace-bind, and the override as SEPARATE PERMISSIVE policies which combine with OR (not AND) → a foreign-workspace INSERT satisfying only the status-pin would be admitted (B2-INSERT / B-1 re-open). v5 (a) corrects the semantics, (b) mandates the composition be realized as a SINGLE compound WITH CHECK OR as AS RESTRICTIVE policies and explicitly forbids the multi-permissive-intended-to-AND realization, and (c) hardens the red-first negatives to an otherwise-valid row + adds a positive-control. It also folds Gate B-delta-3's NB-4-transitive nit (state owner non-reachability as TRANSITIVE membership). This is a PRECISE, LIGHT revision: every v4 closure (3-rung ladder / zero per-user; F1F7; GA-1/3/4; B-1/B-2/B-3; B2-INSERT workspace bind itself; N-1..N-5; AD-1/AD-2; NB-1..NB-5; continuous -06 scan + L174 GUC fold; rc.20) carries forward intact. Prior rc.18/rc.19 evidence does NOT carry. Authoring authority: Jason B1 ruling (2026-07-20T23:08Z) + Mos coordinator OPTION A (home the layer) + Mos Q1 ruling (TIER-LEVEL) + Mos Q2 ruling (RLS WITH CHECK AUTHORIZED, two mandatory conditions). No new coordinator authority is required for v5: the composition fix is a DDL-realization detail + corrected PostgreSQL-semantics statement + test-wording hardening entirely inside the already-authorized WITH CHECK/FORCE RLS mechanism (Q2); it introduces no new principal, key, or authority axis. Both delta-3 gates independently confirmed the per-user/same-tier boundary HOLDS (the crit-1 defect is a TENANT-ISOLATION correctness risk, NOT a per-user authority axis). Base: frozen SSOT at b0d78d86, /src/mosaic-stack/docs/native-kanban-sot/ (READ-ONLY; this doc modifies nothing there). Scope: DESIGN / CONTRACT-AMENDMENT only. No implementation code, no PRs, no SSOT edits.

HALT self-check result (re-run for v5): NO HALT. No v5 change forces two SAME-TIER writers to hold DIFFERENT WRITE AUTHORITY. The v5 composition fix pins how the already-uniform predicates are physically composed (one compound WITH CHECK, or AS RESTRICTIVE) — it changes no predicate's content and adds no writer-varying axis; both delta-3 gates confirmed this is a tenant-isolation correctness pin, not a per-user distinction. The B2-INSERT fix ANDs a shared, per-request workspace predicate (workspace_id = current_setting('mosaic.workspace_id', true)::uuid) into every rung's INSERT WITH CHECK — applied uniformly to every writer in a tier, exactly like the B-2 read/USING predicate; it is tenant isolation, never a per-writer distinction. The B-1 sink-resident User-override remains keyed on (workspace_id, task_id, tier) — task-identity + tier ONLY, NO per-federated-writer key — so every writer inside a given tier is subject to the identical veto for a given task. The five NB fixes (override-relation no-status enumeration; two claim-scopings; owner-toggle-FORCE containment naming; baseline runtime grants for the two capability roles) introduce zero per-writer authority. Per-writer attribution remains an AUDIT concern routed to metadata (task_events.actor_id), never a DB credential/role/RLS key. The design remains satisfiable with the fixed 3-rung role ladder (User / Orchestrator / others), federated identity → rung at authorization time. The Q1 hard re-open trigger (same-tier / different write-authority) is not hit. See §5.


0. What the "raw task-status sink" is (grounding — unchanged from v1/v2)

  • The canonical task-status store is tasks.status (SHARED-CONTRACT §5.4 L214 "canonical authority"); legacy mission_tasks.status is frozen read-only / prohibited as a write source (SHARED-CONTRACT §5.4 L219; §5.1 phase 1 L165). tasks is a mutable relation created by KBN-100. Canonical statuses are backlog | ready | in_progress | blocked | in_review | done | cancelled (SHARED-CONTRACT §3 L134); initial creation state is backlog (§5.4 L214 not-started→backlog).
  • The append-only event relations task_events, artifacts, task_checkpoints, task_checkpoint_artifacts, approval_decision_artifacts receive runtime-only SELECT, INSERT with UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER denied (frozen §4 L178). That producer/consumer precedent — grants applied after KBN-100 creates the relations — is the mechanism this envelope extends to the mutable tasks.status sink and to the new sink-resident override table (§2.9).
  • Today there is exactly one runtime login, mosaic_runtime (LOGIN INHERIT), sole member of NOLOGIN mosaic_runtime_capability (§4 L149-150). The sink therefore has no writer-authority discrimination — the root of RC19-B1-01.

The three B1 findings are writes to this sink; the two B2 findings are the privileged-fd → UID-drop handoff used by the runner/importer (§5 L184/L191). This envelope homes one coherent layer over both, now with the User-override and the SELECT/tenant predicate resident at the sink.

[NB-2 / NB-3 — precise compromise-resistance scope (Mos Q1 accepted co-resident-pool residual; NO HALT).] The prior "a compromised Gateway cannot bypass a User deny or read cross-workspace" framing is overstated and is corrected here. All three rung credentials are co-resident in one Gateway process (DB authenticates the credential, never the federated end-user — the accepted Mos Q1 residual), and the workspace GUC is app-set with no per-tenant DB check. Precisely:

  • B-1 sink-enforces the User veto against the Orchestrator-rung path — an Orchestrator-pool write to a task carrying an active User deny ERRORS in PostgreSQL regardless of app-layer behavior (a buggy or Orchestrator-path-compromised Gateway cannot skip it). It does NOT stop a Gateway compromised badly enough to hold the User pool: User = god and may itself clear any veto (User-write-only on the override table). That is the accepted co-resident-pool residual, not a new hole.
  • B-2 + B2-INSERT sink-enforce tenant isolation against a buggy Gateway — an omitted/wrong workspace predicate fails closed (reads return zero rows; INSERTs ERROR on the NULL/mismatched GUC). They do NOT defend a compromised Gateway that forges mosaic.workspace_id to a victim tenant, because the GUC carries no per-tenant DB authentication (accepted Mos Q1 residual). The genuine, real closure B2-INSERT delivers is the buggy / unbound-INSERT hole: without it, any rung could write a foreign workspace_id on INSERT even with a correct GUC set — that is now impossible.

Defending against a fully compromised Gateway forging identity/tenant requires per-federated-user DB credentials = the HALT boundary; Mos Q1 explicitly accepts this residual and rules NO HALT. The claims in §2.9 (B-1) and §2.2/§2.4 (B-2/INSERT) are scoped to match.


1. Contract amendment (exact ownership + text changes)

1.1 Homing decision — one NEW owner card + minimal responsibility-widenings

The layer is genuinely new scope. The minimal closed/disjoint expansion adds one new owner card, KBN-101-10, owning two brand-new, currently-unowned globs (disjoint from every existing manifest → KBN-101-06 overlap/ownerless/path-existence still pass):

  • packages/db/src/sink-rbac/** — B1 layer (policy artifact + schema + evaluator + ladder + connection-selection + status-UPSERT + invariant-set + view/rule-forbid enumeration).
  • packages/db/src/credential-handoff/** — B2 library (SCM_RIGHTS + non-dumpable assert/verify).

Plus responsibility-widenings of already-owned files (no manifest-glob change → no overlap):

Existing card Already-owned path(s) touched Widened responsibility (v4; v3 items preserved)
KBN-101-00 infra/pg-bootstrap/roles.sql, infra/pg-bootstrap/tests/** [GA-1 FIX] Create the fixed rung-ROLES (3 LOGIN roles + their capability roles) with deny-by-default base attributes and CONNECT grants; bootstrap catalog tests that each rung role carries the safe attributes (F2) and is non-owner of the sink. [AD-1 FIX] Amend the frozen §4 L176 CONNECT-allowlist certification so the two new LOGIN rungs (mosaic_runtime_user, mosaic_runtime_orchestrator) are admitted to the CONNECT allowlist and asserted TEMPORARY-denied; otherwise the frozen L176 cert test REDS (it fails if an app login retains TEMPORARY, and it enumerates the CONNECT allowlist). [NB-5 FIX] Grant the two new capability roles (mosaic_runtime_user_capability, mosaic_runtime_orchestrator_capability) the baseline frozen §4 L176 runtime grants they need to function: USAGE ON SCHEMA mosaic, USAGE ON SCHEMA drizzle + SELECT ON drizzle.__drizzle_migrations, drizzle.__mosaic_migration_manifest (ledger read), and the relevant sequence USAGE/SELECT grants — mirroring what mosaic_runtime_capability already holds; without these the two new rungs cannot connect/operate and would FAIL the frozen §6 L222 runtime verify (missing inherited capability/sequence/ledger grants → fail-closed). It owns NO DDL on tasks or the override table — those relations do not exist at bootstrap time, so RLS/column-grant/policy/FORCE/unique-key/trigger DDL cannot live here.
KBN-100 (producer; SPEC target of -10) its own packages/db/src/schema.ts + generated packages/db/drizzle/** migration + migration tests [GA-1 + GA-2/Q2 + B-1 + B-2 + B-3 + B2-INSERT FIX] Homes ALL tasks-specific sink DDL and the new task_status_write_override table DDL after it creates tasks, per the frozen §4 L178 producer/consumer precedent: the rung column-grant matrix (F1/F7/N-3), the RLS WITH CHECK write-source policies (Q2/F1/N-3), [B2-INSERT] the workspace-predicate workspace_id = current_setting('mosaic.workspace_id', true)::uuid as a conjunct of EVERY rung's INSERT enforced check on tasks — realized as a single compound WITH CHECK OR AS RESTRICTIVE, NEVER as separate permissive policies (v5 composition mandate, §1.4 item 3-bis) — (and thus into the UPSERT INSERT arm), and the symmetric INSERT workspace binding on task_status_write_override so tenant isolation is homed at the sink on the write path too, the per-rung SELECT/UPDATE USING tenant policies (B-2), the override-table subquery in every tasks write policy (B-1), the tasks (workspace_id,id) UNIQUE candidate key (F4), the actor_id attribution metadata column (Q1 audit channel), the no-status-normalizing-trigger / no-status-view / no-status-rule invariants (F3/N-1/N-2) — [NB-1] extended to cover the task_status_write_override relation as well (no status-writing trigger/RULE/SECURITY DEFINER function on the override relation may write tasks.status), and — as the TERMINAL migration stepENABLE/FORCE ROW LEVEL SECURITY on tasks and on the override table, run strictly AFTER all expand/backfill/repair DML (B-3). KBN-100's DDL scope statement is explicitly amended to include RLS + the override table on tasks under Mos Q2 coordinator co-authorization (§1.4). Certified deployed by KBN-101-09.
KBN-101-01 packages/db/src/connection-identity.ts(+spec), apps/gateway/src/database/database.module.ts(+spec) Provision only the three generic rung pools at boot; set the per-request workspace session predicate (B-2) on each checked-out connection. [F2 FIX] Extend the frozen L222 unsafe-attribute checkout assertion to each rung connection: effective role == authorized rung AND fail closed on SUPERUSER / CREATEROLE / CREATEDB / REPLICATION / BYPASSRLS / ownership of the sink tables (DATABASE_ROLE_UNSAFE). [AD-2/N-5 FIX] -01 provisions pools only; it does not import -10. The rung→pool SELECTION lives in -10's connection-selection.ts, which consumes -01's provisioned pools (import direction 10→01; §2.2).
KBN-101-03 packages/db/src/migrator/** (glob), docker/db-migrator.Dockerfile Consume -10's credential-handoff: SCM_RIGHTS-only fd acquisition + non-dumpable re-verify after the drop to 10003:10003. [GA-4(ii) FIX] The root-only migrator launch wrapper homes here (the migrator image).
KBN-101-05 apps/gateway/Dockerfile(+spec), renderer secret matrix Renderer mounts three rung DSN secrets (mosaic-db-runtime-url + mosaic-db-runtime-user-url + mosaic-db-runtime-orchestrator-url), each 0600 10001:10001, Gateway-only. Gateway rung-connection selection is a DB-role choice, not a UID drop, so -05 owns no SCM_RIGHTS/dumpable behavior (moved to -03 per GA-4(ii)).
KBN-101-02 packages/storage/src/{cli,migrate-tier}.ts, packages/storage/src/adapters/postgres.ts (already-owned) Importer privileged-fd case uses SCM_RIGHTS (extends existing safe-open, drop to 10002:10002); status writes go through the -10 status-preserving-UPSERT helper.
KBN-101-06 tools/ci/fixtures/kbn101-ddl-inventory.json, .woodpecker/ci.yml Add KBN-101-10 to the inventory fixture/command matrix; add the rung-selection + SCM_RIGHTS + per-tier RLS negatives + override-veto negative + cross-workspace read negative + [B2-INSERT] cross-workspace INSERT/UPSERT negative + NULL-GUC INSERT negative + owner/backfill-ordering cases to the matrix. [Gate A-delta-2 obs #2 / N-2 continuous — ADOPTED] Home the continuous no-status catalog scan as a -06 CI gate that runs on EVERY migration (promoted from the v3 one-shot -09 recommendation): the scan asserts no status-writing trigger/RULE/SECURITY DEFINER function/view on tasks or on task_status_write_override (NB-1), so a future migration that adds a status-write path fails CI, not only the one-time cert.

1.2 §7 manifest expansion (KBN-101-06 must still pass)

Add exactly one row to the §7 card table (complete, disjoint ownership universe):

| KBN-101-10 declarative sink-RBAC + per-role connection-selection + credential-handoff | 00,01 | Only: packages/db/src/sink-rbac/**, packages/db/src/credential-handoff/**. It homes: the source-controlled declarative RBAC policy artifact + schema + evaluator; the fixed rung ladder + federation-aware identity→rung resolver; the rung→DB-credential connection-selection module (consumes -01 pools; import direction 10→01); the status-preserving-UPSERT contract + invariant field-set module + no-status view/rule/trigger enumeration; and the SCM_RIGHTS credential-handoff + non-dumpable assert/verify library. It creates no roles, no DDL, no Compose/CI/deploy path; it is consumed by -00 (role names), -01 (connection wiring + identity/attribute verify), -02/-03/-05 (write path + fd handoff), specified-against by KBN-100 (sink DDL: grants/RLS/USING+WITH CHECK policies/override-table/FORCE/unique-key/trigger-view-rule-invariant on tasks), and certified deployed by KBN-101-09. |

Amend the §7 L245 closing sentence (added clause bold): "The manifests above are the complete ownership universe for KBN-101 implementation paths including KBN-101-10; the KBN-101-06 inventory test fails on overlap, an ownerless in-scope path, or a nonexistent declared path."

Dependency edges (v3, unchanged from v2 — acyclic; -10 depends only on 00,01):

  • KBN-101-10 depends on 00,01
  • KBN-101-02 depends on 01,03,10
  • KBN-101-03 depends on 00,01,10
  • KBN-101-05 depends on 00,03,10
  • KBN-101-06 depends on 02,03,05,07,10
  • KBN-101-08 depends on 00…07,10
  • KBN-101-09 depends on KBN-100,08 (unchanged; evidence-only)

Why still disjoint (KBN-101-06 green — UNCHANGED from v2): the only NEW ownership is the two -10 globs. The B-1 override table, B-2 USING policies, and B-3 terminal-FORCE ordering are all producer DDL homed in KBN-100's already-owned schema.ts/drizzle/** (a responsibility widening on files KBN-100 already owns, exactly like the taskssink DDL). The N-1/N-5 additions live inside -10's already-declaredsink-rbac/**glob. AD-1 amends -00's already-ownedroles.sql/tests. **No glob is split, narrowed, or shared → KBN-101-06 overlap/ownerless/path-existence stay green.**

1.3 §4 identity-model amendment (per-role, closed additive set — F2 hardened; AD-1 CONNECT cert)

Add these rows to the §4 role table (§4 L141-151). Per-ROLE, not per-user. Implements Jason's B1 (User = god; main Orchestrator near-god, User-overridable; others deny-by-default):

Role (added) Attributes (F2-hardened) Membership / session use
mosaic_runtime_user_capability NOLOGIN, no ownership/admin attrs. Holds the User (god) rung's named grants incl. INSERT(status), UPDATE(status) + the status-transition RLS WITH CHECK policy on tasks; plus the SOLE INSERT/UPDATE/DELETE grant on task_status_write_override (B-1); never granted to Orchestrator/base.
mosaic_runtime_orchestrator_capability NOLOGIN, no ownership/admin attrs. Holds the Orchestrator (near-god) rung's grants; identical to User except (i) every tasks write policy subqueries the override table and is REJECTED at the sink when an active User deny row exists for that (workspace_id, task_id, orchestrator) (B-1); (ii) INSERT(status) is pinned by WITH CHECK to a non-terminal initial status (N-3) — only User may INSERT an arbitrary/terminal status; (iii) it has only SELECT (never write) on the override table. Scope is tier/task-identity only (F6).
mosaic_runtime_user LOGIN INHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS, no ownership; non-owner of every sink table + the override table. Member of mosaic_runtime_user_capability only (WITH INHERIT TRUE, SET FALSE, ADMIN FALSE); the connection-selection User-rung credential. [AD-1] Requires CONNECT, denied TEMPORARY.
mosaic_runtime_orchestrator LOGIN INHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS, no ownership; non-owner of every sink table + the override table. Member of mosaic_runtime_orchestrator_capability only (same INHERIT/SET/ADMIN flags); the Orchestrator-rung credential. [AD-1] Requires CONNECT, denied TEMPORARY.

The existing mosaic_runtime is confirmed already LOGIN INHERIT with no admin attrs (frozen L150) and non-owner; it becomes the others / deny-by-default base rung. On the sink it retains SELECT (workspace-scoped USING, B-2), INSERT (explicit non-status column list) (F1), and column-level UPDATE on non-status, non-invariant, non-key, non-tenancy columns only (F7), each USING workspace-scoped; it has no INSERT(status), no UPDATE(status), no DELETE, no TRUNCATE on tasks, no write on the override table, and RLS denies its status/override writes by default. The rung set is fixed at three.

F2 note: the three rung LOGIN roles' NOBYPASSRLS + NOSUPERUSER + non-ownership are load-bearing — FORCE ROW LEVEL SECURITY collapses for any SUPERUSER/BYPASSRLS role and RLS is silently bypassed by a table owner. These attributes are declared in -00 bootstrap, catalog-proven in -00 tests, and re-verified at every checkout by -01 (extended L222 assertion).

[AD-1] CONNECT-allowlist certification amendment (frozen §4 L176): L176 today grants CONNECT only to mosaic_runtime, mosaic_migrator, and the bootstrap actor, and its cert fails if an unrelated login retains CONNECT or either application login retains TEMPORARY. The two new LOGIN rungs are additional application logins that require CONNECT and must be TEMPORARY-denied. The -00 row + rc.20 text explicitly amend the L176 allowlist to {mosaic_runtime, mosaic_runtime_user, mosaic_runtime_orchestrator, mosaic_migrator, bootstrap-actor}, and the -00 cert asserts TEMPORARY denial for all three runtime rungs. Without this amendment the frozen -00 cert test REDS on the two new logins.

rc.20 amendment text (v3 — extends v2) — amend §4's closing note and SHARED-CONTRACT rc.5 L82/L88-90 ("Non-effect: role graph … unchanged / neither creates roles/secrets") with:

rc.20 (Envelope A) — authorized by Jason's declarative-RBAC B1 ruling + Mos OPTION A + Mos Q1 (TIER-LEVEL) + Mos Q2 (RLS): adds (i) the fixed User/Orchestrator/others runtime rung-roles (per-ROLE, deny-by-default); each rung LOGIN role is NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS, NON-OWNER of every sink/override table, added to the §4 L176 CONNECT allowlist, and asserted TEMPORARY-denied. No per-federated-user role or credential is created — federated identity maps to a rung at authorization time only, and per-writer attribution lives in metadata columns (app-supplied actor_id on task_events), never in DB credentials/roles. (ii) Two new fixed rung DSN secrets mosaic-db-runtime-user-url and mosaic-db-runtime-orchestrator-url (Gateway-only, 0600 10001:10001), extending the frozen runtime-URL secret row. (iii) RLS WITH CHECK write-source policies (including the B2-INSERT per-rung INSERT tenant binding workspace_id = current_setting('mosaic.workspace_id', true)::uuid, composed as a SINGLE compound WITH CHECK or AS RESTRICTIVE — never multi-permissive-intended-to-AND, per the v5 composition mandate §1.4 item 3-bis) + per-rung SELECT/UPDATE USING tenant policies + FORCE ROW LEVEL SECURITY on the sink table tasks and on the new sink-resident task_status_write_override table (tighten-only, deny-by-default) — a §10 schema-v1 mechanism addition beyond the prior grant/revoke-only model, authorized by Mos Q2 as coordinator co-authorization with Jason's B1. The two new rung capability roles additionally receive the baseline frozen §4 L176 runtime grants (USAGE ON mosaic, drizzle ledger SELECT, sequence grants) [NB-5] so they satisfy the frozen §6 L222 runtime verify. (iv) The task_status_write_override table (keyed (workspace_id, task_id, tier), no per-writer key), written only by the User rung, read by the Orchestrator/base rung tasks write-policy subquery, homes the User-override at the sink. The sink DDL (grants + RLS USING/WITH CHECK + override table + FORCE + unique key + trigger/view/rule invariants) is homed in the KBN-100 producer after it creates tasks, with FORCE RLS as the terminal migration step after all backfill/repair DML, per the §4 L178 producer/consumer precedent.

Frozen-invariant note (authorization basis): touching the frozen role graph, the §4 L176 CONNECT allowlist, and adding RLS + the override table are §10 contract-change classes (MISSION-MANIFEST §10; SHARED-CONTRACT L82/L88-90; frozen §4 L176). They are authorized here by Jason B1 + Mos OPTION A + Mos Q1 + Mos Q2. The addition is strictly a fixed per-ROLE set + a tighten-only RLS mechanism + a task/tier-scoped override table. It stops exactly at the HALT boundary: no per-federated-user topology is introduced (§5 proof).

1.4 §10 mechanism amendment — RLS WITH CHECK (write-source + INSERT tenant-bound) + USING + sink-resident override at the sink (Q2), with the two mandatory conditions

The frozen write-authority model is grant/revoke-only; RLS appears nowhere in the frozen contract. Mos Q2 AUTHORIZES adding declarative, deny-by-default, enforce-AT-THE-SINK, fail-closed RLS policies, because grant/revoke alone cannot express (a) row-level write-source enforcement (pin new-row status, F1/N-3), (b) a sink-enforced User-override that the Orchestrator-rung path cannot skip (B-1), (c) tenant-scoped reads/updates (B-2), or (d) [B2-INSERT] tenant-scoped INSERTs — binding workspace_id on the write/create path, which neither column grants nor the USING read policy can do (PostgreSQL does not apply USING to INSERT). RLS is tighten-only: it can only further restrict beyond the column grants, never widen them.

KBN-100 producer DDL scope is explicitly amended to include, on tasks (and, where noted, the override table):

  1. ALTER TABLE tasks ENABLE ROW LEVEL SECURITY; — and the same on task_status_write_override.
  2. CONDITION (b) — EXPLICIT DECISION: ALTER TABLE tasks FORCE ROW LEVEL SECURITY; (and on the override table) — DECIDED YES/ON, stated explicitly and not left defaulted, so that even the table owner (mosaic_schema_owner) and any maintenance connection cannot silently bypass RLS. (Directly closes the live U-Connect failure mode where an owner connection with rls_forced=false silently VOIDED RLS.)
  3. A deny-all default posture (no permissive policy) plus per-rung policies:
    • Write (WITH CHECK): base/others rung → new-row status = <initial state 'backlog'> only, no status UPDATE; Orchestrator rung → status INSERT pinned to a non-terminal initial status (N-3), and every write (INSERT/UPDATE/transition) additionally WITH CHECK'd against NOT EXISTS(<active User deny row for (workspace_id, id, 'orchestrator')>) (B-1); User rung → status INSERT (any legal status) / transition. Every write policy is WITH CHECK (write-source), tighten-only.
    • [B2-INSERT — tenant isolation on the write path] Every rung's INSERT WITH CHECK on tasks MUST include the workspace predicate workspace_id = current_setting('mosaic.workspace_id', true)::uuid as a conjunct of the same enforced check that carries the status-pin (and, for the Orchestrator rung, the B-1 override subquery) — see the COMPOSITION MANDATE (item 3-bis) for the exact realization. This is REQUIRED because PostgreSQL does NOT apply USING to INSERT, and the explicit status-pinning WITH CHECK suppresses the USINGWITH CHECK substitution — so in v3 workspace_id was unbound on the INSERT path and any rung could INSERT INTO tasks (workspace_id, …) VALUES (<foreign W2>, …) (directly or via the sanctioned status-upsert INSERT arm) into a victim workspace, defeating B-2's "tenant isolation homed at the sink" goal and evading SHARED-CONTRACT §7.1 (body workspace forbidden). CORRECTED SEMANTICS (v5 — the delta-3 fix): multiple PERMISSIVE policies for the same command combine with OR — a row is admitted if it satisfies ANY one permissive policy's WITH CHECK, ANDed with all AS RESTRICTIVE policies' checks. "All applicable WITH CHECK clauses must pass / they AND" is FALSE as a general rule and is struck from this envelope; the AND-conjunction the B2-INSERT closure depends on holds ONLY (a) within a single policy whose WITH CHECK is one AND-conjoined expression, or (b) across AS RESTRICTIVE policies. Realizing the status-pin, the workspace-bind, and the override as separate permissive policies would let a foreign-workspace INSERT that satisfies only the status-pin be admitted by OR — re-opening B2-INSERT/B-1 — which item 3-bis forbids. Fail-closed on NULL: an absent GUC makes current_setting(...,true) return NULL, the equality yields NULL (not true), and — provided the predicate is a conjunct of the single enforced check (item 3-bis) — the INSERT ERRORS at the sink. The same INSERT workspace binding is applied symmetrically to task_status_write_override, under the identical composition mandate (its own single compound WITH CHECK or AS RESTRICTIVE).
    • Read/lock (USING) — B-2: per-rung SELECT and UPDATE USING policies workspace-scoped by the session predicate workspace_id = current_setting('mosaic.workspace_id', true)::uuid (set per request by -01, §2.2). USING(true) is explicitly FORBIDDEN — tenant isolation rides on this predicate; a lazy USING(true) would expose every workspace's rows to every rung and is a fail-closed contract violation caught by a negative test.
    • 3-bis. [v5 — LOAD-BEARING] COMPOSITION MANDATE (how the sink check MUST be realized; both delta-3 gates prescribed it). Per rung, per command (INSERT / UPDATE / transition), the sink enforcement of the status-pin AND the workspace-bind AND (Orchestrator rung only) the User-override subquery MUST be realized as EITHER:
      • (a) a SINGLE policy whose WITH CHECK is the full conjunction<status-pin> AND workspace_id = current_setting('mosaic.workspace_id', true)::uuid and, for the Orchestrator rung, AND NOT EXISTS(<active User-override deny row for (workspace_id, id, 'orchestrator')>); OR
      • (b) the status-pin as the SOLE PERMISSIVE policy for that command, plus the workspace-bind and (Orchestrator) the override subquery authored AS RESTRICTIVE (restrictive policies AND with everything, so a row must pass every restrictive check and at least one permissive check).
      • EXPLICITLY FORBIDDEN: realizing the status-pin, the workspace-bind, and the override as multiple permissive policies whose predicates are intended to AND. Permissive policies combine with OR, so that realization admits a foreign-workspace INSERT that satisfies only the status-pin (cross-tenant injection) and equally bypasses the B-1 override — exactly the delta-3 defect. This mandate applies to the tasks INSERT/UPDATE/transition policies AND to the task_status_write_override table's own policies (same single-compound-WITH CHECK-or-AS RESTRICTIVE requirement; never multi-permissive-intended-to-AND). The producer DDL and the -09/-06 certification assert the realized policies are either single-compound or AS RESTRICTIVE (never separate permissive policies expected to AND) — a lint/catalog check over pg_policy.polpermissive backs it.
      • [v6 — N1: strengthened pg_policy.polpermissive lint (supplementary, NOT sufficient).] The pg_policy.polpermissive lint (continuous -06 scan, §2.7) MUST (a) count permissive policies grouped by EFFECTIVE role — including PUBLIC and role inheritance, not merely by the policy's named TO role: a TO PUBLIC (or inherited-through-a-capability-role) permissive policy applicable to a rung's command is counted alongside that rung's status-pin, because at evaluation time it OR-widens exactly like a second same-role permissive policy — so a TO PUBLIC permissive next to a rung's status-pin is a lint failure; and (b) ideally reject an internally-disjunctive single WITH CHECK — a single policy whose expression is itself status_pin OR workspace_bind satisfies the naive one-permissive-policy count yet violates the composition mandate exactly as two permissive policies would, so the lint should detect a top-level OR between the status-pin and the workspace-bind/override conjuncts within one policy expression. The lint is SUPPLEMENTARY, not sufficient: the behavioral positive-controls remain the PRIMARY proof of correct composition (dropping the workspace conjunct — resp. the override subquery — alone flips the hardened negative to a PASS; §2.4 clause 5 / §2.9). The lint backstops the catalog shape; it does not replace the behavioral proof.
      • [v6 — N2: option-(b) vacuous-deny guard invariant — ≥1 permissive per writable (rung, command).] Under option (b) (status-pin as the sole PERMISSIVE policy + workspace-bind/override AS RESTRICTIVE), PostgreSQL admits a row only if ≥1 PERMISSIVE WITH CHECK is TRUE AND all RESTRICTIVE checks pass — so if a writable (rung, command) has zero permissive policies, every restrictive-only evaluation denies and the command over-denies / fails closed (nothing can ever be written). Therefore there MUST be ≥1 PERMISSIVE policy per writable (rung, command) — this "≥1 permissive per writable (rung, command)" is the load-bearing option-(b) guard invariant (the workspace-bind/override being restrictive is safe only because the status-pin supplies the required permissive). It is verified by the mandated "same-workspace valid-status write succeeds" positive tests for each writable command — INSERT, transition/UPDATE, and SELECT — which would FAIL (spurious over-deny) if the permissive were dropped, and by the N1 lint confirming exactly one permissive status-pin (grouped by effective role) is present per writable (rung, command).
  4. B-3 migration ordering (TERMINAL FORCE): all expand/backfill/repair DML on tasks (SHARED-CONTRACT §5.1 phase-3 L165-171, §5.3 L188) runs FIRST, while tasks has RLS disabled; the ENABLE/FORCE ROW LEVEL SECURITY statements are the TERMINAL DDL of the activation migration. No standing mosaic_schema_owner ALLOW write policy exists after activation (that would recreate the unconstrained owner write path = U-Connect mode). Any schema_owner maintenance policy, if ever needed, is migration-only and must be proven unreachable by the 3 runtime LOGIN roles + a negative test.

CONDITION (a): every runtime per-role LOGIN role (mosaic_runtime, mosaic_runtime_user, mosaic_runtime_orchestrator) MUST remain NON-OWNER of every sink table and the override table (owner stays mosaic_schema_owner). Enforced by -00 bootstrap catalog assertion + re-verified at each checkout by -01 (F2). A rung role that owns a sink/override table is a fail-closed contract violation.

Break-glass boundary (B-3, named explicitly in KBN-101-09 cert): the ONLY identities that legitimately bypass FORCE RLS are the sanctioned break-glass actors named in the frozen contract — mosaic_extension_owner (NOLOGIN SUPERUSER, §4.1 L146) and the external platform bootstrap actor (superuser, §4/§7; §9 L266 residual authority). Both are NOLOGIN/external, carry no runtime credential, and are outside application containment. The -09 cert names these two as the accepted, audited boundary — not a new hole — and asserts the three runtime LOGIN rungs are NOT superuser/NOT BYPASSRLS and cannot assume either.

[NB-4 — owner-toggle-FORCE containment is NON-REACHABILITY, not incapability.] The -09 break-glass enumeration must additionally name mosaic_schema_owner (frozen §4 L145, NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS) as a contained-but-CAPABLE path. A table owner — regardless of NOSUPERUSER/NOBYPASSRLS — can ALTER TABLE tasks DISABLE ROW LEVEL SECURITY or ALTER TABLE tasks NO FORCE ROW LEVEL SECURITY and then write freely; FORCE/NOBYPASSRLS do not make an owner incapable. So the containment claim is NOT "the owner cannot bypass RLS" — it is "the owner role is NOT REACHABLE from the three runtime LOGIN rungs." The -09 cert asserts this non-reachability explicitly: (i) mosaic_schema_owner is NOLOGIN (no direct credential); (ii) it is reachable only via mosaic_migrator's SET ROLE (frozen §4 L147, migrator runner-before-Gateway, max:1 locked migration session), which is not one of the three runtime rungs and holds no runtime pool; (iii) none of mosaic_runtime / mosaic_runtime_user / mosaic_runtime_orchestrator is a member of, or can SET ROLE to, mosaic_schema_owner or mosaic_migrator (catalog-proven at -00, re-verified at every checkout by -01's L222 non-owner/assumability assertion). [NB-4-transitive — Gate B-delta-3 nit, folded] the non-reachability assertion in the -09 cert MUST be stated as TRANSITIVE membership, not merely the absence of a direct pg_auth_members row: PostgreSQL's ownership/privilege check returns true for an inherited membership reached through a capability role, which would confer the owner's ALTER TABLE … NO FORCE / DISABLE ROW LEVEL SECURITY right. The assertion therefore uses the recursive predicates — NOT pg_has_role('mosaic_runtime_user', 'mosaic_schema_owner', 'USAGE') (and the same for each runtime rung against mosaic_schema_owner and mosaic_migrator), equivalently NOT has_privs_of_role(<rung>, 'mosaic_schema_owner') — which fold in transitive/inherited membership through any intermediate (capability) role. The frozen §6 L222 fail-closed-on "owner/migrator identity or assumability" check plausibly already covers this transitive case; v5 states "transitive membership" explicitly in the -09 cert assertion so no implementation reads "non-member" as only the direct-edge check. [v6 — F-NB4: cover SET-ROLE assumability, not only inherited privilege.] The v5 predicates NOT pg_has_role(<rung>, 'mosaic_schema_owner', 'USAGE') / NOT has_privs_of_role(<rung>, 'mosaic_schema_owner') capture the inherited-privilege axis, but a membership granted WITH INHERIT FALSE, SET TRUE yields has_privs_of_role = FALSE yet still allows SET ROLE mosaic_schema_owner followed by ALTER TABLE … NO FORCE ROW LEVEL SECURITY — an assumability path the USAGE/has_privs_of_role predicate alone does not close. The -09 cert assertion therefore conjoins the MEMBER variantNOT pg_has_role(<rung>, 'mosaic_schema_owner', 'MEMBER') AND NOT pg_has_role(<rung>, 'mosaic_migrator', 'MEMBER') — for each of the three runtime rungs (mosaic_runtime, mosaic_runtime_user, mosaic_runtime_orchestrator); the MEMBER privilege captures SET-ROLE reachability regardless of INHERIT, so pg_has_role(rung, owner, 'USAGE') AND pg_has_role(rung, owner, 'MEMBER') are asserted false together. This is stated explicitly rather than leaning on "frozen §6 L222 assumability plausibly covers it." (The intended role graph is already safe — per envelope §1.3 + frozen §4 L150, each rung is a member of only its own capability role, WITH INHERIT TRUE, SET FALSE, ADMIN FALSE, and of no owner/migrator role — so no live SET TRUE edge to the owner/migrator exists; this fold is assertion-completeness hardening, not a live hole.) The owner's toggle-then-write capability is therefore real but confined to the migrator/owner phase (frozen §5, runner-before-Gateway), never to a live runtime rung — that is the containment, stated as transitive non-reachability rather than incapability.

Red-team requirement (baked into §3/§4; v5-HARDENED so the negatives cannot false-green under the OR-trap): each per-rung policy is proven deny-by-default via NEGATIVE tests PER TIER in real PostgreSQL:

  • an unauthorized write-source (base terminal-status INSERT/transition) that ERRORS;
  • [B2-INSERT — hardened] a cross-workspace INSERT/UPSERT using a row OTHERWISE VALID for the rung — a status the rung's pin ACCEPTS (e.g. base rung + status='backlog', and no active override), differing from a passing insert ONLY in workspace_id = <foreign W2> — so the rejection isolates the workspace bind (not a status/override rejection). Tested both directly and via the status-upsert INSERT arm; it ERRORS at the sink. Positive-control: removing the workspace-bind conjunct alone (leaving the status-pin) flips this negative to a PASS, proving the workspace bind, not the status-pin, is doing the rejecting (and proving the composition is a single enforced conjunction / RESTRICTIVE, not an OR of separate permissive policies).
  • [B-1 — hardened] an Orchestrator write vetoed by an active User-override deny row, using a transition the Orchestrator pin OTHERWISE ACCEPTS (a legal non-terminal transition on (W, T) that would succeed absent the deny row) — so the rejection isolates the override subquery. It ERRORS at the sink. Positive-control: removing the override subquery alone flips this negative to a PASS, proving the override, not the status-pin/workspace-bind, is doing the rejecting.
  • a cross-workspace SELECT under the wrong/absent session predicate → zero rows;
  • [B2-INSERT] a NULL-GUC INSERT (absent mosaic.workspace_id) that ERRORS (fail-closed on NULL);
  • a same-workspace INSERT with a valid status that succeeds (proving the predicate composes with F1/N-3 and does not over-deny);
  • an owner/maintenance connection attempting a post-activation tasks write that must fail under FORCE. All rejections ERROR at the sink. The two positive-controls are load-bearing: they are the direct proof that the composition is single-compound-WITH CHECK-or-AS RESTRICTIVE (delta-3), because under the forbidden multi-permissive-OR realization the hardened cross-workspace / override negatives would already PASS (false-green) without removing anything.

2. Design

2.1 Declarative RBAC policy (source-controlled artifact + schema)

  • Artifact: packages/db/src/sink-rbac/policy.task-status.v1.ts — a source-controlled, versioned, deny-by-default policy object; the single source of truth for "who may write the sink." No policy lives in the database except the derived GRANT/RLS state the KBN-100 producer emits from it.
  • Schema: packages/db/src/sink-rbac/policy.schema.ts — a typed schema (zod/TS) validating: rungs (exactly the fixed ladder), per-rung allow/deny verbs (insert, insert:status, update:<column-set>, transition:status, delete — always deny), federationMap (federated-identity-claim → rung, never a role/credential per user), and userOverride.
    • [F6 FIX] userOverride scope guardrail: the schema constrains override predicates to tier- + task-identity (row) scope (workspace_id, task_id, tier) and explicitly FORBIDS a per-federated-writer identity as an override or authority key (the Q1 re-open trigger). A policy that names a per-federated-writer identity as an override/authority key fails schema validation (fail-closed at load). The override is realized as the sink-resident task_status_write_override table (§2.9), NOT as app-resolved state. Per-writer attribution is not an authority input; it flows only to the audit metadata column (§2.6).
  • Evaluator: packages/db/src/sink-rbac/evaluate.ts — pure (identityClaims, requestedWrite) → Decision ∈ {allow(rung), deny(reason)}, deny-by-default, tier resolution only. It only chooses a rung; it never mints credentials and — critically (B-1) — it is NOT the enforcement point for the User-override: the override is enforced in PostgreSQL by the write-policy subquery, so a compromised Gateway that skips the evaluator still cannot bypass a User deny.
  • Ladder / federation-awareness: packages/db/src/sink-rbac/ladder.ts — fixed order User > Orchestrator > others and resolveRung(federatedIdentity) → rung at authorization time (Q1: federation-awareness lives in the resolver; the sink authorizes by the writer's declared ROLE/tier).
  • Enforcement is AT THE SINK, fail-closed: the evaluator's rung choice only selects which pre-provisioned connection is used; PostgreSQL then enforces via (a) revoked base privileges, (b) column-level INSERT(status)/UPDATE(status) granted only to the User/Orchestrator capabilities, (c) FORCE ROW LEVEL SECURITY + deny-all default + per-rung WITH CHECK policies including the override subquery (B-1), (d) per-rung SELECT/UPDATE USING workspace predicate (B-2, tenant isolation at the sink), (e) DELETE/TRUNCATE revoked from all runtime rungs, and (f) UPDATE on key/tenancy/invariant columns revoked from all rungs (F7). A mis-authorized OR override-vetoed OR cross-workspace write fails closed in PostgreSQL because the connection's effective DB role lacks the privilege or the policy predicate rejects the row — caller discipline is not trusted.

2.2 Per-role credential + connection-selection model (NO per-user roles; import direction pinned)

federated writer ──(verified identity claim)──► evaluate.ts ──► rung (one of 3, fixed)
                                                                 │
                                      connection-selection.ts ───┤ selects the PRE-PROVISIONED
                                        (in -10; consumes -01)    │ pool for that rung; sets
                                                                 │ SET LOCAL mosaic.workspace_id
                                                                 ▼
                    mosaic_runtime_user | mosaic_runtime_orchestrator | mosaic_runtime
                     └── PostgreSQL enforces grants + FORCE RLS WITH CHECK(+override) + USING(workspace) ──┘
  • packages/db/src/sink-rbac/connection-selection.ts maps rung → pooled connection. Exactly three connection pools, one per rung, each opened with that rung's fixed mounted-secret DSN. Credentials = rungs = 3, constant.
  • [AD-2/N-5 — import direction pinned] -01 (database.module.ts) provisions ONLY the three generic rung pools at boot and knows nothing of rung selection. The rung→pool SELECTION lives entirely in -10's connection-selection.ts, which is consumed by the write-path cards (-02/-03/-05, which already depends on 10) and is NEVER imported by -01. Direction is therefore 10 → 01 (10's selection consumes 01's pools) — acyclic; there is no 01 → 10 cycle. An impl lane must not read "01 wires 10" as 01 → 10.
  • [B-2 — workspace session predicate] on checkout, before the first query, -01 sets SET LOCAL mosaic.workspace_id = <verified request workspace> inside the request transaction; the per-rung USING policies and the B2-INSERT WITH CHECK read current_setting('mosaic.workspace_id', true)::uuid. The predicate is set from the server-verified tenant context, never from a body field (SHARED-CONTRACT §7.1 "Body workspace fields are forbidden"). A missing/invalid setting → USING yields no rows (fail-closed read) and INSERT WITH CHECK yields NULL→ERROR (fail-closed write), never cross-workspace exposure.
    • [NB-3 — compromise scope] the GUC is app-set with no per-tenant DB authentication, so B-2 (reads/UPDATE USING) and B2-INSERT protect a buggy Gateway (omitted/wrong predicate → fail-closed), NOT a compromised Gateway that forges mosaic.workspace_id to a victim tenant (accepted Mos Q1 co-resident-pool residual; defending it requires per-user DB creds = the HALT boundary). The genuine closure B2-INSERT delivers is the buggy / unbound-INSERT hole: even with a correct GUC, v3 left workspace_id unbound on INSERT so any rung could stamp a foreign workspace — now impossible.
    • [Gate B 2(a) — poisoned-pool / SET LOCAL negative] the mosaic.workspace_id GUC is folded into the frozen L174 poisoned-pooled-session-reset + transaction SET LOCAL restoration negative test (which already covers search_path): a negative proves mosaic.workspace_id is transaction-scoped via SET LOCAL (mirroring the frozen SET LOCAL search_path discipline, §4 L152) so a pooled connection cannot leak a stale workspace across requests — an un-reset/poisoned session fails closed, and a committed/rolled-back transaction does not carry the prior request's workspace.
  • [F2 FIX] Identity + attribute verification at checkout (connection-identity.ts): verify effective role == the rung's expected DB role AND assert the safe attributes — fail closed (DATABASE_ROLE_UNSAFE) on SUPERUSER, CREATEROLE, CREATEDB, REPLICATION, BYPASSRLS, or ownership of any sink/override table. Extends the frozen L222 check to each rung connection.
  • No per-federated-user role/credential: a new federated user needs zero new DB roles/credentials/secrets/pools — it resolves to an existing rung. Federation growth is O(1) in DB-role count.

2.3 Status-preserving UPSERT on a stable natural key (F4 bound)

  • packages/db/src/sink-rbac/status-upsert.ts — the single sanctioned refresh helper: INSERT INTO tasks (natural_key…, <mutable non-status cols…>) VALUES (…) ON CONFLICT (<natural key>) DO UPDATE SET <mutable non-status cols…>status is never in the DO UPDATE SET list, so a refresh preserves existing status byte-for-byte.
  • [B-2 dependency] the ON CONFLICT conflict-probe/refresh reads the existing row; under FORCE RLS this requires the per-rung SELECT/UPDATE USING policy to admit the row (workspace-scoped). Without B-2's USING policies the probe would see zero rows and the UPSERT would spuriously INSERT-conflict or mis-refresh — B-2 is what makes the sanctioned UPSERT actually work post-activation.
  • [B2-INSERT dependency] the UPSERT's INSERT arm is a tasks INSERT and is therefore subject to the B2-INSERT workspace WITH CHECK (§1.4/§2.4): a status-upsert that supplies a foreign workspace_id (or runs under an absent GUC) ERRORS at the sink, closing the sanctioned-UPSERT INSERT path that v3 left tenant-unbound. The helper sets workspace_id from the server-verified tenant context (never a body field, §7.1); it matches the session GUC by construction.
  • [F4 FIX] Natural key: the tenant-scoped stable identity (workspace_id, id). No frozen tasks(workspace_id,id) unique key exists (only missions_workspace_id_uidx, rc.4 L94) — ON CONFLICT against a nonexistent unique index is a HARD planning error that fails loud. Therefore KBN-100 MUST create tasks (workspace_id,id) UNIQUE (aligned to the SI-001 pattern, SHARED-CONTRACT §5.2). Fallback: if KBN-100 keys tasks on global id only, the UPSERT natural key re-binds to (id) (spec-level, no HALT).
  • Status changes only via an authorized transition: transitionStatus() guarded by (a) the rung's transition:status policy verb, (b) UPDATE(status) present only on User/Orchestrator capabilities, (c) the RLS WITH CHECK transition policy (admits the update only as an explicit transition, never as a refresh/grant side effect) plus the override subquery for the Orchestrator rung (B-1), and (d) the USING workspace predicate to see/lock the row (B-2). DELETE is denied to all runtime rungs → DELETE + default-INSERT status reset is impossible at the sink (fails closed on the DELETE).

2.4 INSERT-time status constraint (F1 BLOCKING + N-3 hardening)

Base mosaic_runtime must not stamp an arbitrary terminal status at creation, and the Orchestrator rung must not create a task directly at a terminal status (N-3). Layered fail-closed controls (homed in the KBN-100 producer DDL):

  1. Column-privilege: GRANT INSERT (<explicit non-status column list>) ON tasks TO mosaic_runtime_capability; — the base rung cannot name status on INSERT, so status takes its column DEFAULT (backlog). INSERT(status) is granted only to the User/Orchestrator capabilities.
  2. RLS INSERT … WITH CHECK (base): pins new-row status = 'backlog' for the base rung.
  3. [N-3 FIX] RLS INSERT … WITH CHECK (Orchestrator): pins the Orchestrator rung's new-row status to a non-terminal initial status (status IN {backlog, ready}; never done/cancelled/in_review). Only the User (god) rung may INSERT an arbitrary/terminal status. This prevents an Orchestrator creating a task directly at done/cancelled, skipping the transition/lease/review trail (SHARED-CONTRACT §7.2 POST /tasks L272 vs POST /tasks/:taskId/transition L275). The exact terminal/non-terminal set is bound by KBN-100 to the §3 L134 vocabulary.
  4. [B2-INSERT FIX] RLS INSERT … WITH CHECK (EVERY rung, tenant binding): every rung's INSERT sink-check MUST include workspace_id = current_setting('mosaic.workspace_id', true)::uuid as a conjunct of the same enforced check that carries the status-pin (base=backlog, Orch=non-terminal, User=any) and the B-1 override subquery on the Orchestrator rung. status is bound by clause 2/3; workspace_id is bound by this conjunct; they are orthogonal columns so there is no conflict. [v5 — corrected PostgreSQL semantics; the delta-3 fix] the earlier justification "because PostgreSQL requires all applicable WITH CHECK clauses to pass, they AND" is FALSE and is struck: multiple PERMISSIVE policies for a command combine with OR (a row is admitted if it satisfies ANY one permissive WITH CHECK, ANDed with all AS RESTRICTIVE checks). The AND this closure needs holds ONLY (a) within a single policy whose WITH CHECK is one AND-conjoined expression, or (b) across AS RESTRICTIVE policies. Fail-closed on NULL: an absent GUC → NULL equality → INSERT ERRORS — provided the workspace conjunct sits in the single enforced check (clause 5). This closes the v3 gap where the explicit status WITH CHECK suppressed the USINGWITH CHECK substitution, leaving workspace_id unbound on INSERT and allowing any rung to INSERT into a foreign workspace.
  5. [v5 COMPOSITION MANDATE — LOAD-BEARING] Single-compound-WITH CHECK-or-AS RESTRICTIVE, never multi-permissive-intended-to-AND. The status-pin (clause 2/3), the workspace-bind (clause 4), and (Orchestrator) the B-1 override subquery MUST be realized per rung, per command as EITHER (a) a SINGLE policy whose WITH CHECK is the full conjunction <status-pin> AND workspace_id = current_setting('mosaic.workspace_id', true)::uuid [AND NOT EXISTS(<active override deny row>)]; OR (b) the status-pin as the sole PERMISSIVE policy plus the workspace-bind and (Orchestrator) the override authored AS RESTRICTIVE. Realizing them as multiple permissive policies intended to AND is EXPLICITLY FORBIDDEN — permissive policies OR, so a foreign-workspace INSERT satisfying only the status-pin (e.g. base + backlog + foreign workspace_id) would be OR-admitted, re-opening B2-INSERT, and an override-vetoed Orchestrator write satisfying the status-pin would likewise slip the B-1 veto. The identical mandate applies to task_status_write_override's own policies (single compound WITH CHECK or AS RESTRICTIVE; never multi-permissive-AND). A pg_policy.polpermissive catalog check + the hardened positive-control negatives (below) prove the realization. [v6 — N1] the pg_policy.polpermissive check is strengthened to count permissive policies grouped by EFFECTIVE role (including PUBLIC and role inheritance) — a TO PUBLIC/inherited permissive applicable to a rung's command counts alongside its status-pin — and to reject an internally-disjunctive single WITH CHECK (a lone policy whose expression is status_pin OR workspace_bind OR-widens exactly like two permissive policies); the lint is supplementary, and the behavioral positive-controls below remain the PRIMARY proof (§1.4 item 3-bis). [v6 — N2] because option (b) admits a row only when ≥1 permissive WITH CHECK is TRUE AND all restrictive checks pass, there MUST be ≥1 PERMISSIVE policy per writable (rung, command) — the load-bearing option-(b) vacuous-deny guard — else the command over-denies (fails closed); this invariant is proven by the mandated "same-workspace valid-status write succeeds" positive tests (INSERT, transition/UPDATE, SELECT).

Red-first tests (v5-hardened): base-rung INSERT … status='done' ERRORS (column-privilege and/or RLS); base-rung INSERT omitting status succeeds at DEFAULT; Orchestrator INSERT … status='done' ERRORS (N-3), Orchestrator INSERT … status='backlog' succeeds; User-rung INSERT … status='done' succeeds. [B2-INSERT — hardened valid-status form] the cross-workspace negative uses a row otherwise valid for the rungbase rung + status='backlog' + workspace_id=<foreign W2>, no active override — differing from a passing insert ONLY in workspace_id, so the ERROR isolates the workspace bind (not a status/override rejection); tested direct and via the status-upsert INSERT arm. Positive-control: removing the workspace-bind conjunct alone flips this to a PASS (proving the bind — not the status-pin — rejects, hence a single enforced conjunction / RESTRICTIVE, not a permissive OR). An INSERT under an absent mosaic.workspace_id GUC ERRORS (fail-closed on NULL); a same-workspace INSERT with a valid status succeeds (predicate composes with F1/N-3, does not over-deny).

2.5 Invariant / key / tenancy field-set (F7 — closure)

  • packages/db/src/sink-rbac/invariant-set.ts defines the invariant field-set = every sink column NOT in the explicitly-mutable set: { natural-key columns (workspace_id, id), status, creation/tenancy columns, immutable metadata }. The complement (title, tags-normalized, due_at, rank, …) is the mutable set the UPSERT DO UPDATE touches.
  • [F7 FIX] UPDATE on key/tenancy/invariant columns (workspace_id, id, tenancy columns) is REVOKED from ALL rungs — including User and Orchestrator. A re-key would otherwise launder status (relocate a row into a new identity to escape the status invariant). Column-level UPDATE grants for every rung exclude these columns.
  • Grant/data-plane disjointness (RC19-B1-03): grants are declarative + physically separate from the data plane, so a grant/policy mutation writes zero tasks rows. A test applies an arbitrary policy/grant delta → asserts zero byte delta on any tasks row (closes RC19-B1-03 by construction).

2.6 Per-writer attribution → metadata column (Q1 audit channel)

  • Per-writer attribution (which federated writer inside a tier acted) is an AUDIT concern, not an authority concern. It is captured as an app-supplied actor_id metadata column on task_events (append-only, already runtime INSERT/SELECT-only per frozen §4 L178). KBN-100 owns the column; the application supplies the verified federated-writer identity as data.
  • It is never a DB credential, role, or RLS predicate key, and (per F6) never a userOverride/authority key (the override is keyed on task+tier, not writer). This keeps "which writer acted" auditable without a per-writer DB topology — i.e. without hitting the Q1 HALT trigger.

2.7 No-status-write via trigger / view / rule (F3 BLOCKING + N-1 + N-2)

Column-level UPDATE(status) alone does not close every indirect status-write path. Three complementary invariants (homed in KBN-100 producer; enumerated by -10's invariant-set.ts/no-status enumeration; certified deployed by KBN-101-09):

  • F3 — no status-normalizing trigger on tasks: no trigger on tasks writes/normalizes status outside the sanctioned transitionStatus() path. The transition path is the only status writer.
  • [N-2 FIX + NB-1 + continuous-scan ADOPTED] Complete no-status-write enumeration (F3 completeness): the cert asserts, in addition to F3: no AFTER trigger on tasks executing UPDATE tasks SET status; no SECURITY DEFINER function that writes tasks.status (frozen §4 L176 already forbids SECURITY DEFINER unless a separately reviewed exception — cited as the backstop); no trigger on a RELATED runtime-writable table (task_events and the other §4 L178 relations) that writes tasks.status; and no CREATE RULE on tasks. [NB-1] The enumeration is extended to the task_status_write_override relation: no status-writing trigger / RULE / SECURITY DEFINER function on the override relation may write tasks.status (the override table is runtime-writable by the User rung, so it is exactly the class §2.7's related-table clause must cover). The enumeration scans function BODIES (pg_proc.prosrc / dependency graph) for tasks.status writes, not merely pg_trigger rows. [Gate A-delta-2 obs #2 — ADOPTED, not just recommended] this enumeration is now a continuous -06 CI catalog scan run on EVERY migration (promoted from the v3 one-shot -09 recommendation), so a later migration that adds a status-writing trigger/function/rule/view on tasks or on task_status_write_override fails CI, not only the one-time -09 cert. The -09 deployed cert still records the final deployed proof; the -06 scan is the standing gate. [v6 — N1] the same continuous -06 scan family carries the strengthened pg_policy.polpermissive composition lint (§1.4 item 3-bis / §2.4 clause 5): on every migration it counts permissive policies grouped by effective role — including PUBLIC and role inheritance (a TO PUBLIC/inherited permissive applicable to a writable command counts alongside that rung's status-pin) and flags an internally-disjunctive single WITH CHECK (status_pin OR workspace_bind within one policy). This lint is supplementary: it backstops the catalog shape but is not sufficient — the behavioral positive-controls (§2.4 clause 5 / §2.9) remain the PRIMARY proof that the composition is single-compound/AS RESTRICTIVE rather than an OR of permissive policies.
  • [N-1 FIX] no status-write via VIEW or RULE: forbid any VIEW or RULE on tasks that yields a status-write path. An owner-owned view runs with the owner's privileges (security_invoker = false by default), side-stepping rung grants; an ON INSERT/UPDATE DO INSTEAD rule rewrites a write onto tasks similarly. The invariant-set.ts enumeration forbids such views/rules OR mandates security_invoker = true + ZERO runtime grants on any view over tasks. A negative test proves a rung cannot mutate tasks.status through any view/rule.

2.8 B2 — SCM_RIGHTS credential-acquisition + non-dumpable re-verify (F5 hardened — unchanged from v2)

  • packages/db/src/credential-handoff/scm-rights.ts — the privileged process (the -03 migrator launch wrapper) opens the privileged fd (attestation signing key, §5 L191) before the UID drop and passes the descriptor itself over a Unix-domain socket via an SCM_RIGHTS ancillary message. The receiver never re-opens /proc/self/fd/N (the kernel re-checks permission and returns EACCES after the drop — the FD5 failure). O_CLOEXEC managed explicitly on both ends.
    • [F5 FIX] Socket authentication: the transfer socket MUST be a socketpair() created pre-fork (no filesystem socket) OR a filesystem socket in a 0700 directory verified with SO_PEERCRED (assert peer UID/GID/PID). The receiver sets MSG_CMSG_CLOEXEC on recvmsg. An unauthenticated/anonymous peer is rejected fail-closed.
  • packages/db/src/credential-handoff/process-hardening.ts — after every credential/UID transition, re-assert prctl(PR_SET_DUMPABLE, 0) and verify prctl(PR_GET_DUMPABLE) == 0; treat dumpable != 0 as a fail-closed abort.
  • Consumers: KBN-101-03 migrator wrapper (drop to 10003:10003), KBN-101-02 importer (privileged-fd case, drop to 10002:10002). The runtime rung-connection selection is a DB-role choice, NOT an OS-UID transition — no new setuid surface, no dumpability handling, reinforcing the no-per-user / no-new-privilege-drop property.

2.9 [B-1 FIX] Sink-resident, task/tier-scoped User-override table (the User deny is now enforced IN PostgreSQL)

Problem (Gate B-delta B-1): in v2 the evaluator resolved the User-override in-app before selecting a connection, so a compromised/buggy Gateway on the Orchestrator pool could bypass a User deny — contradicting v2 §1.4/Q2, which mandates that "an Orchestrator write vetoed by a User-override deny must ERROR at the sink." App-resolution is not sink-enforcement.

Fix (option a — DB-resident override, RLS-forced):

  • New relation mosaic.task_status_write_override (owned/created by the KBN-100 producer, exactly like tasks), columns: workspace_id, task_id, tier (enum {orchestrator} for v1 — the only vetoable sub-god tier; extensible to a future 4th rung), active boolean, created_by_actor_id (audit metadata, NOT an authority key), timestamps. Primary/unique key (workspace_id, task_id, tier)task-identity + tier ONLY; NO per-federated-writer column is part of the key or an authority input (preserves F6 + the Q1 HALT boundary: every Orchestrator-tier writer is vetoed identically for a given task).

  • Who may WRITE it (sink-enforced): INSERT/UPDATE/DELETE on the override table is granted ONLY to mosaic_runtime_user_capability (the User god rung), with the table under FORCE RLS and a WITH CHECK policy binding the row's workspace_id to the session workspace predicate. [B2-INSERT parity] that WITH CHECK workspace binding applies to the override table's INSERT path too (workspace_id = current_setting('mosaic.workspace_id', true)::uuid, fail-closed on NULL), so the override table's write path carries the identical symmetric tenant isolation as tasks — a User-rung INSERT of a veto row for a foreign workspace, or under an absent GUC, ERRORS at the sink. [v5 COMPOSITION MANDATE parity] the override table's write policies are subject to the SAME composition rule as tasks (§1.4 item 3-bis / §2.4 clause 5): the workspace-bind (and any status/authority conjunct) MUST be realized as a single compound WITH CHECK OR AS RESTRICTIVEnever as multiple permissive policies intended to AND (which would OR-admit a foreign-workspace veto row). The Orchestrator and base rungs have no write privilege — a non-User attempt to author/clear a veto ERRORS at the sink. Thus only the User rung can raise or lift a veto.

  • Who READS it: all three rung capabilities get SELECT on the override table (workspace-scoped USING), because the tasks write policy subquery evaluates as the current (querying) rung role. The read is confined to the querying connection's workspace by the override table's own USING predicate.

  • How it vetoes (the subquery): every tasks write policy for the Orchestrator rung (INSERT … WITH CHECK, UPDATE … WITH CHECK, and the transition WITH CHECK) is extended with:

    AND NOT EXISTS (
      SELECT 1 FROM mosaic.task_status_write_override o
      WHERE o.workspace_id = tasks.workspace_id
        AND o.task_id      = tasks.id
        AND o.tier         = 'orchestrator'
        AND o.active
    )
    

    So an Orchestrator-tier write to a task carrying an active User deny is REJECTED in PostgreSQL, regardless of app-layer behavior. [v5 COMPOSITION MANDATE] this NOT EXISTS(<override>) subquery MUST be a conjunct of the Orchestrator rung's single compound WITH CHECK (alongside the status-pin and the workspace-bind), OR authored AS RESTRICTIVE — it may NOT be a separate permissive policy intended to AND, because permissive policies OR and an Orchestrator write satisfying the status-pin permissive policy would then be OR-admitted despite an active deny (bypassing B-1). The User rung's own tasks policies do not subquery the override (User = god; the User authored the deny and overrides it). The base rung already cannot write status.

  • Per-tier NEGATIVE test (real PostgreSQL, red-first; v5-HARDENED): insert an active override row for (W, T, orchestrator) via the User rung; then an Orchestrator-rung write on (W, T) using a transition the Orchestrator pin OTHERWISE ACCEPTS (a legal non-terminal UPDATE/transition that would succeed absent the deny row, and under the correct workspace GUC) must ERROR at the sink — so the rejection isolates the override subquery (not a status/workspace rejection). Positive-control: removing the override subquery alone flips this negative to a PASS, proving the override — not the status-pin or workspace-bind — is doing the rejecting (and proving the subquery is a single-enforced conjunct / RESTRICTIVE, not an OR of separate permissive policies). Clearing the row (active=false, User rung only) re-permits the Orchestrator write; a non-User attempt to write the override table ERRORS. This proves the User-override is sink-enforced (satisfies v2 Q2) and stays within Mos Q1 tier-level (task/tier-scoped, not per-writer).

[NB-2 — compromise-resistance scope of B-1.] B-1 sink-enforces the veto against the Orchestrator-rung path — an Orchestrator-pool write to a vetoed task ERRORS in PostgreSQL regardless of app behavior. It does NOT claim to stop a Gateway compromised badly enough to hold the User pool: User = god and holds the sole write on the override table, so a compromised-User-pool Gateway can itself clear the veto. That is the accepted co-resident-pool residual (Mos Q1, NO HALT), not a defended boundary. The v3 §0 framing "a compromised Gateway cannot bypass a User deny" is corrected to "an Orchestrator-path write cannot bypass a User deny at the sink." Defending against a compromised User-pool Gateway would require per-federated-user DB credentials = the HALT boundary, which Mos Q1 explicitly declines.

HALT note: because the key is (workspace_id, task_id, tier) and never writer, this fix does not give two same-tier writers different authority — it gives the whole Orchestrator tier the same per-task veto. The B2-INSERT workspace predicate is likewise applied uniformly to every writer in a tier. NO HALT.


3. Findings-closure map (each → fail-closed, red-first testable)

Finding Closure Fail-closed enforcement point Test (red-first, real PostgreSQL / real UID drop)
RC19-B1-01 raw sink has no enforceable authority boundary Declarative deny-by-default RBAC (§2.1) → fixed rung → per-rung DB credential; PG enforces via revoked base privileges + FORCE RLS deny-all + per-rung WITH CHECK. At the sink: effective DB role lacks the privilege; mis-authorized write errors in PG. -00: rung roles created, safe attrs, non-owner. KBN-100: base UPDATE(status)/DELETE denied, User/Orch allowed. -01: checkout identity==rung + safe-attr.
RC19-B1-02 DELETE+default-INSERT resets status Status-preserving UPSERT on (workspace_id,id); status excluded from DO UPDATE; DELETE revoked all rungs; status only via authorized transition. At the sink: DELETE fails closed; refresh cannot touch status. KBN-100 + -09: refresh preserves status byte-stable; DELETE denied; transition-only status change.
RC19-B1-03 grant change breaks non-status compatibility Invariant field-set (§2.5); grants declarative + physically separate → grant mutation writes zero tasks rows. Structural: data plane and grant plane disjoint. Apply arbitrary policy/grant delta → zero byte delta on all tasks rows + invariant-set membership test.
RC19-B2-01 FD5 /proc/self/fd EACCES after UID drop SCM_RIGHTS descriptor-passing (§2.8); never re-open /proc/self/fd/N post-drop; explicit O_CLOEXEC; authenticated socket (F5). Post-drop process receives a live fd; no privileged re-open. Drop UID → /proc/self/fd re-open EACCES/absent vs SCM_RIGHTS receive succeeds; unauthenticated peer rejected.
RC19-B2-02 dumpability resets after credential transition Re-assert + verify PR_SET_DUMPABLE=0 after every transition; fail closed if PR_GET_DUMPABLE != 0. Process aborts if dumpable ≠ 0 post-transition. After simulated setuid: assert PR_GET_DUMPABLE == 0; inject reset → fail-closed abort.
F1 (BLOCKING) INSERT-time status unconstrained for base rung GRANT INSERT (non-status list) → status DEFAULT for base; RLS INSERT … WITH CHECK pins new-row status to initial; INSERT(status) only User/Orch (§2.4). At the sink: base status-on-insert denied by column-privilege and/or RLS WITH CHECK. Red-first: base INSERT … status='done' ERRORS; base INSERT omitting status → DEFAULT; User/Orch initial-status INSERT succeeds.
F2 (BLOCKING) rung LOGIN roles lack declared/verified safe attributes Declare both rung roles NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS + non-owner (§1.3); extend L222 checkout assertion (§2.2). Bootstrap catalog + every checkout: fail closed on SUPERUSER/BYPASSRLS/ownership. -00 catalog asserts attrs + non-ownership; -01 injects unsafe attr/ownership → DATABASE_ROLE_UNSAFE.
F3 (BLOCKING) BEFORE-trigger status bypass Invariant: no trigger on tasks writes/normalizes status outside transitionStatus() (§2.7); homed in KBN-100 producer. Producer DDL admits no status-normalizing trigger; transition path is only status writer. KBN-100 static: enumerate triggers on tasks, none assign status. -09 deployed: base UPDATE of a permitted col cannot change status via any trigger.
F4 (non-blocking) no frozen tasks(workspace_id,id) unique key Bind tasks (workspace_id,id) UNIQUE as explicit KBN-100 req; else re-bind UPSERT to (id) (§2.3). ON CONFLICT resolves to a real unique index; else hard planning error (fails loud). KBN-100 migration: unique key exists; UPSERT plans/executes; missing-key variant fails loud.
F5 (non-blocking) SCM_RIGHTS socket unauthenticated socketpair() pre-fork or SO_PEERCRED + 0700 dir; MSG_CMSG_CLOEXEC on recvmsg (§2.8). Fd transfer only over an authenticated peer; received fd close-on-exec. Unauthenticated/wrong-peer rejected; SO_PEERCRED mismatch fails closed; MSG_CMSG_CLOEXEC asserted.
F6 (non-blocking) userOverride scope Schema constrains override to (workspace_id,task_id,tier); forbids per-writer key (§2.1); realized as the sink-resident override table (§2.9). Attribution → metadata (§2.6). Fail-closed at policy load: a per-writer authority key fails schema validation. Schema test: per-writer override/authority key rejected; tier/task-scoped accepted.
F7 (non-blocking) re-key launders status Revoke UPDATE(workspace_id,id,tenancy) from all rungs incl. User/Orch (§2.5). At the sink: no rung may UPDATE key/tenancy columns. Each rung (incl. User) UPDATE workspace_id/id ERRORS.
B-1 (BLOCKING; composition PINNED in v5) User-override was app-enforced, not sink-enforced Sink-resident task_status_write_override table (workspace_id,task_id,tier), User-rung-write-only; the NOT EXISTS(<active deny>) subquery is a conjunct of the Orchestrator rung's single compound WITH CHECK (or authored AS RESTRICTIVE), never a separate permissive policy — else an Orchestrator write satisfying the status-pin permissive policy is OR-admitted despite the deny (§2.9 / §1.4 item 3-bis). At the sink: an active User deny → Orchestrator write REJECTED in PG (single enforced check, not OR-bypassable); non-User cannot author the veto. Per-tier NEGATIVE (real PG; v5-hardened): User inserts deny row → Orchestrator write using a transition the pin OTHERWISE ACCEPTS ERRORS (isolates the override). Positive-control: dropping the override subquery alone flips it to PASS (proves single-conjunction/RESTRICTIVE, not permissive-OR). Clear row → re-permitted; Orch/base write to override table ERRORS.
B-2 (BLOCKING) FORCE + WITH CHECK-only bricks all reads (SELECT returns 0 rows for every rung and owner) Add per-rung SELECT/UPDATE USING policies, workspace-scoped via current_setting('mosaic.workspace_id'); forbid USING(true) (§1.4/§2.2). Homes tenant isolation at the sink. At the sink: reads succeed only for the session's workspace; wrong/absent predicate → zero rows (no cross-tenant leak). Post-activation: each rung SELECT/UPSERT-probe within its workspace succeeds; a SELECT under a foreign/absent mosaic.workspace_id returns zero rows; a USING(true) variant fails the isolation negative.
B-3 (BLOCKING) owner/backfill write path under FORCE RLS Backfill/repair DML runs FIRST (RLS disabled); ENABLE/FORCE RLS is the TERMINAL migration step; no standing schema_owner ALLOW write policy after activation; break-glass (mosaic_extension_owner §4.1 L146 + external bootstrap actor §9 L266) named in -09 cert (§1.4). Migration ordering: backfill completes before FORCE binds; post-activation owner write has no policy → fails closed. KBN-100 migration test: backfill INSERTs succeed pre-FORCE; post-activation mosaic_schema_owner tasks write ERRORS under FORCE; no runtime rung is superuser/BYPASSRLS; -09 names the two break-glass actors as the accepted boundary.
N-1 (non-blocking) VIEW/RULE status-write path Forbid views/rules on tasks yielding a status write, OR mandate security_invoker=true + zero runtime grants; enumerated by invariant-set.ts (§2.7). Enumeration admits no status-writable view/rule; any view over tasks is invoker-rights + ungranted. Negative: a rung cannot mutate tasks.status through any view/rule; enumeration fails an owner-rights view with runtime grants.
N-2 (non-blocking) F3 enumeration incomplete Extend enumeration: no AFTER trigger UPDATE tasks SET status; no SECURITY DEFINER fn writing tasks.status (L176 backstop); no trigger on related runtime-writable table (task_events, L178) writing tasks.status; no CREATE RULE; scan function BODIES (§2.7). One-shot -09 cert; recommend continuous -06 CI scan. Producer/-09 cert admits no indirect status-write path. Catalog scan of pg_proc.prosrc/triggers/rules asserts none write tasks.status; deployed behavioral proof; (recommended) -06 CI re-scan per migration.
N-3 (non-blocking) Orchestrator can INSERT terminal status Orchestrator INSERT(status) WITH CHECK pinned to non-terminal initial status; only User may INSERT arbitrary/terminal (§2.4). At the sink: Orchestrator terminal-status INSERT rejected by WITH CHECK. Orch INSERT … status='done' ERRORS; Orch status='backlog' succeeds; User status='done' succeeds.
N-4 (non-blocking) transition-graph legality not DB-enforced (done→backlog reset by another name) DECISION (documented, §2.10): edge-legality is app-enforced within near-god/god authority; the sink closes RC19-B1-02 for base only (DELETE-denial + no base status write). No status-writing trigger is added (would collide with F3/N-2); a validation-only guard is deferred. Documented boundary: User/Orch hold near-god/god authority; illegal edges are an app-layer concern, not a sink guarantee. -09 records the decision + asserts base cannot reset status at the sink; app-layer transition-graph tests are owned by the Gateway command lane (out of KBN-101 scope).
N-5 / AD-2 (non-blocking) import direction ambiguity -01 provisions generic pools only; rung→pool SELECTION lives in -10's connection-selection.ts, consumed by -02/-03/-05; never imported by -0110→01, acyclic (§2.2). Structural: no 01→10 edge exists. Static import test: -01 has no import of sink-rbac/connection-selection; -02/-03/-05 do; graph acyclic.
AD-1 (completeness) frozen §4 L176 CONNECT cert reds on the 2 new logins Amend the L176 allowlist to admit mosaic_runtime_user/mosaic_runtime_orchestrator + assert their TEMPORARY denial; named in -00 row/rc.20 (§1.3). -00 cert: CONNECT allowlist includes the 2 new logins; all 3 runtime rungs TEMPORARY-denied. -00 cert asserts CONNECT granted to the 2 new logins and no runtime rung retains TEMPORARY; an un-amended allowlist REDS.
B2-INSERT (BLOCKING; composition PINNED in v5) tenant isolation not enforced on the INSERT write path (v3 bound workspace only in SELECT/UPDATE USING; PostgreSQL does not apply USING to INSERT, and the explicit status WITH CHECK suppresses USING substitution → workspace_id unbound on INSERT → any rung can INSERT/UPSERT into a foreign workspace). [v5 delta-3] the v4 justification ("all WITH CHECK AND") was FALSE — multiple permissive policies OR — so the workspace conjunct must be pinned into a single compound check or AS RESTRICTIVE, else the bind is bypassable. Include workspace_id = current_setting('mosaic.workspace_id', true)::uuid as a conjunct of the single compound INSERT WITH CHECK (with the F1/N-3 status-pin and, Orchestrator, the B-1 override subquery) OR author it AS RESTRICTIVE, on tasks (and the UPSERT INSERT arm) + symmetric on task_status_write_override. Multiple permissive policies intended to AND are EXPLICITLY FORBIDDEN (they OR). Corrected semantics + realization mandate in §1.4 item 3-bis / §2.4 clauses 45 / §2.9. At the sink: an INSERT/UPSERT with a foreign workspace_id fails the single enforced check; a NULL GUC → NULL predicate → INSERT ERRORS (fail-closed). Write-side tenant isolation homed at the sink and no longer OR-bypassable. Red-first (real PG; v5-hardened): the cross-workspace negative uses an otherwise-valid row (base + status='backlog' + no override, differing only in workspace_id=<foreign W2>) so the ERROR isolates the workspace bind; direct and via status-upsert INSERT arm. Positive-control: dropping the workspace conjunct alone flips it to PASS (proves single-conjunction/RESTRICTIVE, not permissive-OR). NULL-mosaic.workspace_id INSERT ERRORS; same-workspace valid-status INSERT succeeds (no over-deny); override-table foreign-workspace INSERT ERRORS.
NB-1 (non-blocking) override relation absent from the no-status enumeration Extend the F3/N-1/N-2 no-status-write enumeration and the -09 cert / continuous -06 scan to task_status_write_override: no status-writing trigger / RULE / SECURITY DEFINER function on the override relation writes tasks.status (§2.7). Enumeration/CI admits no indirect status-write path via the override relation. Catalog + fn-body scan asserts no trigger/rule/SECURITY-DEFINER on task_status_write_override writes tasks.status; a planted one REDS the -06 scan.
NB-2 (non-blocking) overstated B-1 compromise claim Scope §0/§2.9 wording: B-1 sink-enforces the veto against the Orchestrator-rung path; a compromised User-pool Gateway (User=god) can clear a veto — accepted Mos Q1 co-resident residual, NO HALT (§0/§2.9). Documented boundary: sink-enforcement is Orchestrator-path-scoped; the User-pool residual is accepted, not defended. Doc/claim assertion in -09 cert notes: the override-veto negative proves the Orchestrator-path ERROR; the framing no longer claims User-pool compromise resistance.
NB-3 (non-blocking) overstated B-2 compromise claim Scope §0/§2.2 wording: the workspace GUC is app-set with no per-tenant DB check → B-2 + B2-INSERT protect a buggy Gateway (omitted predicate → fail-closed), NOT a compromised one forging the GUC (accepted residual). The B2-INSERT unbound-INSERT closure remains a real fail-closed win (§0/§2.2). Documented boundary: fail-closed on omission/NULL; forged-GUC cross-tenant is the accepted per-user residual. Cross-workspace read → zero rows; NULL/foreign-GUC INSERT ERRORS (buggy-Gateway closure proven); doc states forged-GUC is out of scope (HALT boundary).
NB-4 (non-blocking; v5 states transitive) owner-toggle-FORCE containment mis-stated as incapability Name mosaic_schema_owner (reachable via mosaic_migrator SET ROLE) in the -09 break-glass enum as contained-but-CAPABLE: an owner can ALTER TABLE tasks DISABLE/NO FORCE RLS then write regardless of NOSUPERUSER/NOBYPASSRLS → containment = transitive non-reachability from the 3 runtime LOGIN rungs, asserted explicitly (§1.4). -09 cert: owner toggle-then-write is real but confined to the migrator/owner phase; runtime rungs cannot reach mosaic_schema_owner/mosaic_migrator through any inherited (capability-role) path. [NB-4-transitive] -09 asserts non-reachability as TRANSITIVE membershipNOT pg_has_role(<rung>, 'mosaic_schema_owner', 'USAGE') / NOT has_privs_of_role(...) recursion, NOT merely the absence of a direct pg_auth_members row (ownership check returns true for an inherited membership through a capability role). Frozen §6 L222 "assumability" plausibly already covers this; "transitive membership" is stated explicitly. [v6 — F-NB4] the assertion additionally conjoins the MEMBER variantNOT pg_has_role(<rung>, 'mosaic_schema_owner', 'MEMBER') AND NOT pg_has_role(<rung>, 'mosaic_migrator', 'MEMBER') for each of the 3 runtime rungs — because a WITH INHERIT FALSE, SET TRUE membership yields has_privs_of_role = FALSE yet still permits SET ROLE … ; ALTER TABLE … NO FORCE; the MEMBER privilege captures SET-ROLE reachability regardless of INHERIT (the intended graph has each rung SET FALSE to only its capability role, so this is assertion-completeness, not a live hole). mosaic_schema_owner is NOLOGIN, reachable only via mosaic_migrator SET ROLE; the 3 runtime rungs are transitive non-members and non-assumers (catalog + L222 checkout).
NB-5 (non-blocking) 2 new capability roles lack baseline runtime grants → fail frozen §6 L222 verify Grant mosaic_runtime_user_capability / mosaic_runtime_orchestrator_capability the frozen §4 L176 baseline runtime grants (USAGE ON SCHEMA mosaic; USAGE ON SCHEMA drizzle + SELECT on the two ledger relations; relevant sequence USAGE/SELECT) at -00 (§1.1/§1.3). Bootstrap: the 2 new rungs hold the baseline grants → they connect/operate and pass the L222 runtime verify. -00 catalog asserts each new capability role holds USAGE ON mosaic, ledger SELECT, sequence grants; an omission → L222 runtime verify fails closed (missing inherited grant).
Q2 per-tier deny-by-default (red-team requirement; v5 composition-pinned) Per-rung single compound WITH CHECK (status-pin + B2-INSERT workspace bind + Orchestrator override) or AS RESTRICTIVEnever multi-permissive-intended-to-AND — plus USING + FORCE RLS; deny-all default (§1.4 item 3-bis). At the sink, every tier: unauthorized write-source rejected even for owner/maintenance (FORCE); tenant/override binds not OR-bypassable. NEGATIVE per tier (v5-hardened): base terminal-status INSERT/transition ERROR; Orchestrator write under User-override deny, using an otherwise-accepted transition, ERROR (+ positive-control: drop override subquery → PASS); cross-workspace INSERT/UPSERT with an otherwise-valid status ERROR (+ positive-control: drop workspace conjunct → PASS); NULL-GUC INSERT ERROR; owner/maintenance post-activation write ERROR (FORCE proven); cross-workspace read → zero rows; same-workspace valid-status write SUCCEEDS per writable command (INSERT, transition/UPDATE, SELECT) — the [v6 — N2] ≥1-permissive-per-writable(rung,command) option-(b) vacuous-deny guard; pg_policy.polpermissive check confirms single-compound-or-RESTRICTIVE, [v6 — N1] grouped by effective role incl. PUBLIC/inheritance and rejecting an internally-disjunctive single WITH CHECK — supplementary to, never a substitute for, the behavioral positive-controls (PRIMARY proof).

4. Implementation card DAG (owner + disjoint manifest, red-first at Gate A/B)

contract(amended) ─► KBN-101-00 (rung-ROLES + capability roles, safe attrs [F2],
                     │            deny-default base, CONNECT + [AD-1] L176 allowlist
                     │            amend (2 new logins CONNECT, TEMPORARY-denied);
                     │            bootstrap catalog tests. NO tasks/override DDL [GA-1])
                     │
        KBN-101-01 ──┤ (provisions 3 generic rung pools; sets mosaic.workspace_id
                     │  session predicate [B-2]; checkout identity==rung AND
                     │  safe-attribute/non-owner verify [F2, extended L222].
                     │  Does NOT import -10 [AD-2/N-5])
                     │
        KBN-101-10 ──┘ depends 00,01:
                        packages/db/src/sink-rbac/**  (policy+schema[F6]+evaluator+ladder+
                                                       connection-selection[consumes 01; 10→01, N-5]+
                                                       status-upsert[F4]+invariant-set[F7]+
                                                       no-status trigger/view/rule enum[N-1,N-2])
                        packages/db/src/credential-handoff/** (scm-rights[F5] + process-hardening +specs)
                              │
      ┌─────────────┬────────┼──────────────────────┬──────────────────────────────┐
 KBN-101-03     KBN-101-05  KBN-101-02          KBN-100 (producer lane)          KBN-101-09 (evidence-only)
 (dep 00,01,10) (dep 00,03, (dep 01,03,10)      SPEC target of -10:              (dep KBN-100,08)
 migrator        10)         importer fd +       homes tasks + override DDL       DEPLOYED cert:
 wrapper:        renderer:   sink adapter uses    AFTER create:                   real rung INSERT/
 SCM_RIGHTS      3 rung DSN   status-upsert       - rung column-grant matrix       transition success;
 SEND + dumpable secret       (drop 10002)         [F1,F7,N-3]                     override-veto [B-1];
 re-assert       mounts;                          - RLS WITH CHECK(+override)      cross-workspace read
 (drop 10003)    Gateway                           + USING workspace pred          zero-rows [B-2];
 [GA-4(ii):      Dockerfile;                       [Q2,B-1,B-2, conds a&b]         backfill-then-FORCE
 docker/db-      NO SCM_RIGHTS                     - task_status_write_override     ordering + owner-write
 migrator.       (DB-role, no                       table [B-1]                    denial [B-3] + break-
 Dockerfile]     UID drop)                         - tasks(workspace_id,id) UNIQUE  glass named;
                              │                     [F4]                            base status
                        KBN-101-06 (dep 02,03,05,07,10): inventory += KBN-101-10;   INSERT/UPDATE/DELETE
                                    overlap/ownerless/path-existence green;         denial; refresh
                                    matrix += rung selection + SCM_RIGHTS[F5] +      status-preservation;
                                    per-tier RLS negatives + override-veto[B-1] +    no-status trigger/
                                    cross-workspace read[B-2] + backfill-order[B-3]  view/rule [F3,N-1,N-2];
                                    + ADOPTED continuous no-status scan[N-2,NB-1]    grant-delta zero-byte;
                                    + cross-workspace/NULL-GUC INSERT neg[B2-INSERT]
                              │                                                      dumpable=0 evidence
                        KBN-101-08 (dep 00…07,10): foundation + atomic activation cert
  • Gate A (re-review): every element traces to a manifest owner; disjointness/overlap/ownerless proven against amended §7; the SPEC-vs-implement split for tasks+override sink DDL matches the frozen §4 L178 precedent (producer = KBN-100, applied after table creation — GA-1); dep edges acyclic with 10→01 import direction (AD-2/N-5); the L176 CONNECT amendment named (AD-1).
  • Gate B (red-team): each B1/B2 finding and F1F7 and B-1/B-2/B-3 and B2-INSERT and N-1..N-4 and NB-1..NB-5 and the Q2 per-tier deny-by-default has a red-first test that fails on the current design and passes only with the enforcement point; sink privilege/RLS/override/tenant denials — including cross-workspace and NULL-GUC INSERT/UPSERT [B2-INSERT] — proven in PostgreSQL (not PGlite — §5 L218); SCM_RIGHTS/dumpable against a real UID drop with an authenticated socket.

5. Executability self-audit (re-run for v5)

Every element has an owner in the amended manifest — nothing ownerless:

Element Owner
Declarative RBAC policy artifact + schema [F6] + evaluator + ladder + no-status trigger/view/rule enum [N-1,N-2] KBN-101-10 (packages/db/src/sink-rbac/**)
Rung→credential connection-selection module (consumes -01 pools; 10→01 [N-5]) KBN-101-10; pools provisioned in KBN-101-01 (database.module.ts)
Status-preserving-UPSERT [F4] + invariant-set [F7] module KBN-101-10 (packages/db/src/sink-rbac/**)
SCM_RIGHTS handoff [F5] + non-dumpable assert/verify KBN-101-10 (packages/db/src/credential-handoff/**)
Fixed rung DB roles + safe attributes [F2] + deny-default base + CONNECT + [AD-1] L176 allowlist amend + TEMPORARY denial + [NB-5] baseline L176 runtime grants (USAGE mosaic, ledger SELECT, sequence grants) on the 2 new capability roles KBN-101-00 (infra/pg-bootstrap/roles.sql + tests) — no tasks/override DDL [GA-1]
tasks sink DDL: rung grants [F1,F7,N-3] + RLS WITH CHECK(+override) [Q2,B-1] + INSERT WITH CHECK workspace bind on every rung [B2-INSERT] + SELECT/UPDATE USING workspace predicate [B-2] + FORCE RLS terminal-after-backfill [B-3, conds a&b] + (workspace_id,id) UNIQUE [F4] + actor_id col [Q1] + no-status trigger/view/rule invariant on tasks and override relation [F3,N-1,N-2,NB-1] KBN-100 producer (SPEC by -10; §4 L178 precedent; scope amended per Q2)
task_status_write_override table [B-1] (create + grants + FORCE RLS + policies; User-write-only incl. INSERT WITH CHECK workspace bind [B2-INSERT parity], all-rung-SELECT) KBN-100 producer (SPEC by -10; same L178 precedent)
mosaic.workspace_id session predicate set at checkout [B-2] KBN-101-01 (database.module.ts / connection-identity.ts)
Rung DSN secret mounts (3 fixed) KBN-101-05 (renderer, extends runtime-URL row)
Checkout identity==rung + safe-attribute/non-owner verify [F2] KBN-101-01 (connection-identity.ts, extended L222)
Migrator launch wrapper SCM_RIGHTS send + dumpable re-assert KBN-101-03 (docker/db-migrator.Dockerfile [GA-4(ii)])
Importer privileged-fd case KBN-101-02
Deployed enforcement certification (F3/N-1/N-2 + NB-1 override-relation scan, per-tier RLS negatives, B-1 override-veto, B-2 cross-workspace read, B2-INSERT cross-workspace/NULL-GUC INSERT negatives, B-3 ordering + break-glass naming incl. [NB-4] mosaic_schema_owner contained-but-capable / non-reachability assertion, N-4 decision) KBN-101-09 (evidence-only)
Inventory/overlap/path-existence + command matrix (rung sel + SCM_RIGHTS + per-tier RLS + override-veto + cross-workspace read + [B2-INSERT] cross-workspace/NULL-GUC INSERT negatives + backfill-order + [ADOPTED] continuous no-status scan on tasks + override relation) KBN-101-06 (fixture += -10)

Manifest closure (KBN-101-06 green): the only NEW ownership is the two disjoint -10 globs. The B-1 override table, B-2 USING policies, B-3 terminal-FORCE ordering, and the v4 B2-INSERT INSERT WITH CHECK workspace binding (+ its override-table parity) and the NB-1 override-relation no-status enumeration are ALL producer DDL homed in KBN-100's already-owned schema.ts/drizzle/** (a responsibility widening, not a glob change — identical treatment to the v3 tasks sink DDL). The NB-5 baseline capability-role grants amend -00's already-owned roles.sql/tests (like AD-1); the continuous -06 no-status scan lives in -06's already-owned tools/ci/fixtures/kbn101-ddl-inventory.json + .woodpecker/ci.yml; the NB-2/NB-3/NB-4 claim-scopings/enumeration are doc + -09 cert wording on already-owned paths. N-1/N-2/N-5 additions live in -10's already-declared sink-rbac/**. No glob is split/shared/narrowed → overlap/ownerless/path-existence stay green (KBN-101-06 unchanged-green vs v3).

NO per-federated-user topology (HALT boundary respected — re-verified for v5): DB-role/credential cardinality = 3 (User / Orchestrator / others), fixed and independent of the federated-user count. Federated identity → rung happens in the evaluator at authorization time. A new federated user creates zero roles/credentials/secrets/pools. The B-1 override table is keyed (workspace_id, task_id, tier) — task+tier, never writer — so it homes a tier-level veto, not a per-user distinction. The B-2 read USING predicate AND the B2-INSERT INSERT check predicate are the SAME shared per-request session var (mosaic.workspace_id), applied uniformly to whichever rung role holds the connection — tenant isolation, not writer discrimination (and, per v5, composed into a single compound check or AS RESTRICTIVE rather than an OR of separate permissive policies). Per-writer attribution is a metadata column (task_events.actor_id, §2.6), not a DB principal. userOverride forbids a per-writer authority key (F6, fail-closed at load). The NB-1..NB-5 fixes add no principal, key, or predicate that varies by writer. Enforce-at-sink is satisfied by the fixed rung roles' grants + FORCE RLS WITH CHECK(+override, +workspace-bound INSERT) + USING — it does not require, and this design does not use, a DB role/credential per federated user.

Q1 same-tier / different-authority check (the ONLY HALT trigger): no v5 change gives two SAME-TIER writers different WRITE authority. The v5 composition pin fixes only how the already-uniform predicates are physically composed (single compound WITH CHECK or AS RESTRICTIVE, never multi-permissive-OR) — it changes no predicate's content and introduces no writer-varying axis. F1/F2/F3/F7/N-3 tighten authority uniformly per tier; B2-INSERT binds the same workspace predicate uniformly into every rung's INSERT check (tenant isolation, not writer discrimination); B-1 vetoes the entire Orchestrator tier per task (not a subset of Orchestrator writers); B-2 isolates by workspace uniformly; F6 forbids per-writer authority keys; attribution is audit-only. The NB-4-transitive nit only strengthens the owner non-reachability assertion (transitive membership) — no authority axis. No same-tier-different-authority requirement exists → NO HALT — an executable envelope is delivered.


6. Flagged ambiguities in the frozen contract (for reviewer awareness)

  1. Sink identity of the status store. The RBAC-guarded sink is tasks.status; legacy mission_tasks.status stays frozen read-only. Confirm no reviewer reads "raw sink" as the legacy column.
  2. Frozen "role graph unchanged" + "no RLS" + L176 CONNECT allowlist vs. the rulings. SHARED-CONTRACT rc.5 L82/L88-90 froze the role graph and grant/revoke-only (no RLS); frozen §4 L176 froze the CONNECT allowlist. Jason B1 + Mos OPTION A add fixed rung-roles; Mos Q2 authorizes RLS WITH CHECK/USING + FORCE RLS + the override table; AD-1 amends the L176 allowlist for the 2 new logins. The rc.20 amendment text (§1.3/§1.4) records all three as deliberate, human-authorized departures. Reviewer should confirm rc.20 is adopted (not treated as a violation), that KBN-100's DDL scope is amended to include RLS + the override table on tasks, and that the -00 row amends the L176 allowlist.
  3. Natural-key column list for tasks (F4). tasks (workspace_id, id) UNIQUE is asserted by the SI-001 pattern but must be bound + created by KBN-100. If KBN-100's tasks identity differs, the UPSERT natural key re-binds to (id) (spec-level, no HALT).
  4. Initial/terminal status values (F1/N-3). F1 pins base new-row status to backlog (§5.4 L214); N-3 pins the Orchestrator's INSERT set to non-terminal ({backlog, ready}) and reserves terminal ({done, cancelled}; in_review treated as non-initial) to the User rung. KBN-100 binds the exact column DEFAULT and the RLS WITH CHECK literals against the §3 L134 vocabulary so they agree.
  5. Workspace session-var name + set-site (B-2 + B2-INSERT). The USING predicate and the v4 INSERT WITH CHECK predicate both use current_setting('mosaic.workspace_id', true). -01 sets it per request from the server-verified tenant (never a body field, §7.1). Reviewer should confirm the exact GUC name and that a transaction-local SET LOCAL is used so a pooled connection cannot leak a stale workspace across requests (mirrors the frozen SET LOCAL search_path discipline, §4 L152) — now explicitly folded into the frozen L174 poisoned-pooled-session-reset + SET LOCAL restoration negative test (Gate B 2(a), §2.2). Reviewer should also confirm the accepted scope (NB-3): fail-closed on an omitted/NULL GUC (buggy Gateway), but a forged GUC to a victim tenant is the accepted per-user residual (Mos Q1), not defended.
  6. N-4 transition-graph decision. Edge-legality (illegal/backward transitions such as done→backlog) is app-enforced within near-god/god authority; the sink closes RC19-B1-02 for base only (DELETE-denial + no base status write). A validation-only sink guard is deferred to avoid colliding with the F3/N-2 no-status-trigger invariant. Reviewer should confirm this decision is acceptable (it is stated, not silently dropped).
  7. N-2 continuous scan — now ADOPTED. The complete no-status-write enumeration (trigger/view/rule/SECURITY DEFINER/function-body), covering tasks and task_status_write_override (NB-1), is now a continuous -06 CI catalog scan run on every migration (promoted from the v3 one-shot -09 recommendation, per Gate A-delta-2 obs #2); the -09 deployed cert still records the final proof. Reviewer should confirm the -06 scan is owned/wired in tools/ci/fixtures/kbn101-ddl-inventory.json + .woodpecker/ci.yml (already-owned -06 paths).
  8. KBN-101-09 is evidence-only. B-1/B-2/B-3/F3/N-1/N-2 negative tests are authored/owned by the producing implementation cards (KBN-100 producer for tasks/override triggers/RLS; -10/-01 for the rung path); KBN-101-09 records the deployed certification evidence and changes no implementation path. Confirm the test-ownership split is acceptable (mirrors the frozen -09 "evidence-only" role).
  9. B2-INSERT / B-1 WITH CHECK composition — SEMANTICS CORRECTED + REALIZATION MANDATED (v5; the delta-3 fix). v4 justified the composition with a FALSE PostgreSQL claim — "multiple applicable WITH CHECK clauses must all pass (they AND)". That is struck. The correct semantics: multiple PERMISSIVE policies for a command combine with OR (a row is admitted if it satisfies ANY one permissive WITH CHECK, ANDed with all AS RESTRICTIVE checks); the AND this closure needs holds ONLY (a) within a single policy whose WITH CHECK is one AND-conjoined expression, or (b) across AS RESTRICTIVE policies. Accordingly v5 MANDATES (§1.4 item 3-bis / §2.4 clause 5 / §2.9) that, per rung per command, the status-pin AND the workspace-bind AND (Orchestrator) the override subquery be realized as EITHER a single compound WITH CHECK OR the status-pin-as-sole-permissive plus the workspace-bind/override AS RESTRICTIVE, and EXPLICITLY FORBIDS realizing them as multiple permissive policies intended to AND (which would OR-admit a foreign-workspace INSERT satisfying only the status-pin — cross-tenant injection — and equally bypass the B-1 veto). Reviewer should confirm: (i) the corrected OR-semantics statement replaces every "all WITH CHECK AND" assertion; (ii) the realized policies are single-compound or AS RESTRICTIVE (a pg_policy.polpermissive catalog check backs it); (iii) the hardened red-first negatives use an otherwise-valid row (cross-workspace: base + backlog + foreign ws; override: an otherwise-accepted Orchestrator transition) so each rejection isolates the bind/override under test, and each carries a positive-control (dropping the workspace conjunct — resp. the override subquery — alone flips the negative to a PASS, proving the bind/override, not the status-pin, is rejecting). Confirm the accepted scope (NB-2/NB-3) is unchanged: write-side sink tenant isolation is complete against a buggy Gateway; a compromised Gateway forging the GUC or holding the User pool is the accepted Mos Q1 residual.

Envelope A v6 authored from base b0d78d86. v6 = v5 + N1/N2/F-NB4 non-blocking test/lint/cert hardening ONLY — NO design predicate, policy, grant, role, or table is changed (only test/lint/cert wording). v5 CONVERGED with BOTH terminal re-gate-4 gates GO (Gate A-delta-4 a4ce075a; Gate B-delta-4 a379e0de), NO HALT; Gate B-delta-4 flagged 3 EXPLICITLY NON-BLOCKING hardening notes "for the trunk-commit record," folded here additively: N1 — the pg_policy.polpermissive lint is strengthened to count permissive policies grouped by EFFECTIVE role (incl. PUBLIC/inheritance) and to reject an internally-disjunctive single WITH CHECK, stated as SUPPLEMENTARY with the behavioral positive-controls remaining the PRIMARY proof (§1.4 item 3-bis / §2.4 clause 5 / §2.7 / §3 Q2 row); N2 — the option-(b) vacuous-deny guard is stated explicitly as the load-bearing "≥1 PERMISSIVE policy per writable (rung, command)" invariant, verified by the "same-workspace valid-status write succeeds" positive tests for INSERT / transition-UPDATE / SELECT (§1.4 item 3-bis / §2.4 clause 5 / §3 Q2 row); F-NB4 — owner non-reachability is extended to SET-ROLE assumability by conjoining the MEMBER variant NOT pg_has_role(<rung>, 'mosaic_schema_owner', 'MEMBER') AND NOT pg_has_role(<rung>, 'mosaic_migrator', 'MEMBER') for each of the 3 runtime rungs in the -09 cert (§1.4 NB-4 para / §3 NB-4 row). All three are assertion/test/lint completeness on an already-BOTH-GO design; the intended role graph is already safe. The v6 body otherwise preserves the v5 text verbatim, which in turn preserved EVERY v4 closure (all v4 checks passed except one bounded item) and closed the SINGLE remaining BLOCKING finding raised identically by BOTH re-gate-3 gates (Gate A-delta-3 aa5cbf73 CHANGES-NEEDED; Gate B-delta-3 aa881074 1 blocking) — the RLS WITH CHECK composition semantics: (1) corrected semantics — struck the FALSE "PostgreSQL requires ALL applicable WITH CHECK to pass / they AND" from §1.4/§2.4/§6.9 and stated the true rule (multiple PERMISSIVE policies OR; AND holds only within a single compound WITH CHECK or across AS RESTRICTIVE); (2) mandated the realization — per rung per command, status-pin + workspace-bind + (Orchestrator) override subquery MUST be a single compound WITH CHECK OR the status-pin-as-sole-permissive plus the bind/override AS RESTRICTIVE, with multi-permissive-intended-to-AND EXPLICITLY FORBIDDEN, applied to tasks and task_status_write_override (§1.4 item 3-bis, §2.4 clauses 45, §2.9); (3) hardened the red-first negatives — cross-workspace INSERT uses an otherwise-valid row (base + backlog + foreign ws) and the B-1 override negative uses an otherwise-accepted Orchestrator transition, so each rejection isolates the bind/override, each with a positive-control (dropping the workspace conjunct — resp. override subquery — alone flips the negative to PASS); plus NB-4-transitive (owner non-reachability stated as TRANSITIVE membership — NOT pg_has_role(...)/has_privs_of_role(...) recursion, not a direct pg_auth_members edge; frozen L222 "assumability" noted as plausibly already covering it). Every other v4 closure carries forward intact (3-rung ladder / 0 per-user; F1F7; GA-1/3/4; B-1/B-2/B-3; the B2-INSERT workspace bind itself; N-1..N-5; AD-1/AD-2; NB-1..NB-5; continuous -06 no-status scan; L174 poisoned-pool SET LOCAL GUC fold; rc.20). Manifest disjoint (KBN-101-06 green) — all v5 changes land on already-owned KBN-100-producer / -09 / -06 paths (DDL realization detail + test wording; no glob change). Design/contract only; no SSOT file modified, no code, no PR. HALT self-check: NO HALT (fixed 3-rung ladder; override keyed task+tier not writer; workspace predicate shared per-request and uniform per tier, now composed as single-compound/RESTRICTIVE; attribution → metadata; no per-federated-user DB role/credential; no same-tier different-authority requirement — both delta-3 gates independently confirmed the boundary HOLDS). v6 changes NOTHING in this HALT calculus: N1/N2/F-NB4 add only lint-scope, a vacuous-deny test invariant, and a MEMBER assumability assertion — zero new principal, key, predicate, grant, role, or table, and no writer-varying axis — so NO HALT is re-confirmed for v6. Manifest remains disjoint (KBN-101-06 green): every v6 fold lands on already-owned paths — the -06 continuous scan / pg_policy.polpermissive lint in tools/ci/fixtures/kbn101-ddl-inventory.json + .woodpecker/ci.yml; the option-(b) positive tests in the KBN-100-producer / -09 evidence paths; the MEMBER assertion in the -09 cert — no glob split/shared/narrowed. NO code, NO PR, NO SSOT edit.