# L0-D — Policy / FLS / RLS schema binding (execution plan)

Status: **CLOSEOUT** · Phase 2 step 4 · follows L0-C (core landed). **Security-critical: golden-first mandatory.** — D0a goldens **MERGED (#3346)**; D1 atomic cutover **as-built (PR #3352)**; D4 **MERGED + gated**; **D2/D3/D5 re-vetted as NON-CARVES** (D5 reverted — the SharingRule shape validator is a generic L0 invariant on a universal object). Real carves were **D1 + D4**; D6 = the seals for the already-landed state + the H3 capability-coverage guardrail that would have caught the D5 mis-carve class.
Method: 6 surface investigators (file:line evidence) → minimal-PR synthesis → adversarial completeness/security critique → orchestrator integrated the fixes + the architect's decisions. Every claim is **evidence-bound**.

---

> ### D1 reframe (recon-grounded, 2026-07-01)
> **L0-D is a vocabulary de-skin, not a recipe migration.** The `SharingModel → RowVisibilityPolicy` mapping is **generic app-blind engine semantics**, not an L1/SF recipe: every deployment (non-CRM included) needs it — persistence defaults an absent/NULL sharing model to Private *generically* — and **no dialect overrides it**. Per the "real second consumer" principle, an unshared-mechanism-with-no-override-consumer does **not** earn a registry: there is **NO PolicySchemaBinding, NO L1 binding, NO persisted `RowVisibility` field**. The carve confines the SF-named enum branching from the **~4 enforcement sites** to **ONE translator** (`models.RowVisibilityFor`), leaving each enforcement site dispatching on the neutral `RowVisibilityKind`. Private/empty/unknown → **Private → fall through to the owner engine** (NOT denyAll — an SF-only binding with a denyAll default would have regressed non-CRM deployments, which have no SharingModel at all).
>
> This is the general lesson applied: **a policy seam that replaces an enforced auth fact must land atomically with the reader + golden — computed-not-stored, with no isolated persisted/vocabulary intermediate state.** An access-control seam has no sound isolated intermediate; hence the old D0a-goldens → **D0b-seam** → D1-unify split was retired (a standalone "seam" PR would have been a half-built persisted state with no live reader). Final shape: **D0a (goldens, merged #3346) → D1 (atomic plain-function cutover, PR #3352) → D4 (contract-DTO neutralization, merged + gated) → D6 (seals + H3 guardrail).**
>
> ### Post-D1 re-vet (multi-agent, D2–D5)
> **The general lesson generalizes across the policy/permission area: the SF-skin is the wire SHAPES, NOT the policy LOGIC.** The re-vet found **D2, D3, and (after an attempted carve + revert) D5 are NON-CARVES** — like `SharingModel`, the default-grant matrix (`profilePermRules`, `permission_seeder.go:24`), the profile catalog (`constants/profiles.go:4-7`), and the SharingRule **shape** validator are **generic platform-bootstrap / universal-object invariants** with no dialect override and no SF wire shape, so they legitimately stay in L0. The only real wire-shape carve was **D4** (neutralize the SF-shaped `PermissionAdmin` contract DTOs). L0-D reduces to: **D1** (RLS row-visibility de-skin, done) + **D4** (contract-DTO neutralization, done) → **D6** (seal the already-landed state + add the H3 capability-coverage guardrail). There are **no "D2–D4 seams"** — D2/D3/D5 were never seams.
>
> ### D5 post-mortem (why the SharingRule shape validator STAYS in L0)
> D5 attempted to move `validateSharingRuleShapeContract` out of the app-blind records engine to a CRM-only L1-registered validator, while leaving the `platform.security.write.sharing-rule-shape` capability **declared** for SharingRule in `system_tables.json` (which ships in **all** distributions). That is a mis-carve: the capability declaration is universal, so in any non-CRM distribution the declared capability would have **no registered validator** and `runRecordWriteValidators` fails closed — a runtime **hard-DENY of every SharingRule write**. The validator is a sibling of the owner-references and canonicalization validators, which are already L0-universal; moving only the shape rule to CRM-only L1 is inconsistent and unsafe. **D5 was reverted** (`8313fbd07`). The generic lesson (encoded as the H3 guardrail below): a write-validation capability declared for a universal object must have a **universal** registrar — a carve that leaves declaration in L0-universal but registration in an L1 dialect is a fail-closed hard-DENY in every other distribution.

## TL;DR — L0-D is the largest, highest-risk carve in the program (opposite of L0-C)

Unlike L0-C ("mostly already carved"), L0-D is a **real migration of executable SF control-flow**, and it is the **access gate** — a wrong carve un-gates row/field visibility (cross-tenant/user data leak or privilege escalation). But the scope is narrower than "FLS + RLS":

- **FLS *enforcement* is already neutral and L0-contained — NO carve needed** (verified). The `contract.FieldAccess`/`EffectiveObjectAccess` seam is complete; `PermissionService.CheckFieldVisibilityWithUser`/`CheckFieldEditabilityWithUser`/`FieldAccessResolver.Effective{Createable,Updateable}` return neutral bools; enforcement is fail-closed. **But the `models.FieldPermissions` *row shape* is NOT yet fully internal:** it is defined in `shared/pkg/models` (`z_generated.go:90`), and while **no L1/SF dialect imports it (dialect imports 0)**, it still crosses the neutral `PermissionAdmin` contract surface (`shared/pkg/contract/permission_admin.go:46`, `EffectivePermissionsResult.FieldPermissions`) and is consumed by `nexuscrm-kernel/pkg/ports`, `nexuscrm-persistence`, and backend host adapters (`serverruntime`, `apihost` middleware). **Neutralizing that DTO shape is D4, not a standalone guard** — so the FLS "no-leak" seal lands only *after* D4 (see the FLS guard note under D6), and a blanket "0 outside `nexuscrm-permissions`" seal is NOT landable at D0/D6.
- **The neutral OUTPUT/read contract is complete and STAYS** — `contract.Policy.Access → EffectiveObjectAccess`, `RecordAccess{Read,Update,Delete,Locked}`, and the SQL emitter `kernel/pkg/rls/rls.go` (`ToSQLFilter` over a neutral owner/group/id `Result` struct) = **0 SF hits**. This is the correct L0 mechanism.
- **The RLS ENFORCEMENT-INPUT (the OWD row-visibility branch) — D1 (done).** App-blind L0 branched directly on the SF `constants.SharingModel*` OWD enum in **~4 lockstep row-visibility paths**; D1 confined that to `models.RowVisibilityFor`. **The re-vet found the default-grant recipe (`profilePermRules`), the profile catalog, and (after revert) the SharingRule shape validator are GENERIC platform-bootstrap / universal-object invariants, like `SharingModel` (Decision #1) — D2/D3/D5 are NON-CARVES** (see below). The only real SF-skin carve was the wire **SHAPE** on the admin contract surface: the `PermissionAdmin` DTOs (D4).

**Verdict:** a de-skin (not a delete) of the RLS/policy *input* onto a neutral translator, all enforcement dispatching on one neutral kind, behind goldens. **Golden-first**, then an **atomic cutover** — D0a (goldens/tests only, no runtime change) lands and is proven byte-identical **first (merged #3346)**; then **D1 flips all enforcement in ONE PR** onto the neutral translator. There is **no standalone "seam" PR** (the retired D0b): an access-control seam has no sound isolated intermediate state — a persisted binding with no live reader is a half-built inversion, not a safe checkpoint. **D2/D3/D5 re-vetted as NON-CARVES; the only real wire-shape carve was D4.** Order: **D0a → D1 (done) → (D2/D3 non-carves) → D4 (done) → (D5 non-carve, reverted) → D6 (seals + H3).**

---

## The RLS enforcement-input — evidence

The SF sharing model was executable Go control-flow that branched on `constants.SharingModel*` directly across **four row-visibility enforcement sites that MUST agree** (`record_access.go:117` — "must match BuildRLSResult logic"). D1 cuts these four over to dispatch on the neutral `models.RowVisibilityKind` produced by the ONE translator `models.RowVisibilityFor`; the SharingModel switch now lives **only** in that translator:

- `nexuscrm-permissions/rls_filter.go` — `buildRLSResult` ("the SINGLE source of truth for query-level RLS"): dispatches on `RowVisibilityKind` for OWD PublicRead/PublicReadWrite (:151-152), FolderScoped (:109, via `FolderScopeField`), ParentControlled (:161). The SF role-hierarchy grant-up (`HierarchyGranted → GetSubordinateUserIDs`) and full sharing-rule resolution are downstream of the kind, unchanged.
- `nexuscrm-permissions/record_access.go` — `checkRecordAccessInternal`: the single-record mirror, same `RowVisibilityKind` dispatch (:121-158), pinned byte-identical to `buildRLSResult`.
- `nexuscrm-analytics/query_clause_fls.go` — the `queryParentFilterBypassTargetRecordSchemaUnrestricted` bypass (:640-641): the third enforcement branch (the surface maps under-counted; the adversary verified it), now `RowVisibilityFor(schema).Kind == PublicRead||PublicReadWrite`.
- `nexuscrm-metadata/metadata_sharing.go` — `GetParentControlledObject` classification gate (:31): `RowVisibilityFor(schema).Kind != RowVisibilityParentControlled`.

> **Narrower than the old "7 hard-gate paths."** The following read `SharingModel` for **CAPABILITY / ADMIN / RELATIONSHIP routing, NOT row visibility** — so they are **out of scope for D1** and deliberately **stay reading `constants.SharingModelControlledByParent` directly** (they are generic capability/admin routing — there is no D2 carve to migrate them into, D2 is a non-carve): `nexuscrm-permissions/service.go:455,558,899`, `service_effective_permissions.go:109`, `parent_controlled.go:20`. These are not de-synced enforcement paths — they route parent-relationship/admin behavior, not the row-visibility decision, so they are outside D1's manifest and the D6 seal explicitly allows them.

- Default-grant defaults (`profilePermRules`, `nexuscrm-permissions/permission_seeder.go:24`) — **re-vetted NON-CARVE (D2).** This is the **generic OBJECT/FIELD grant matrix** (the `{read,create,edit,delete,viewAll,modifyAll}` PermRule bools) for the platform's OWN built-in profiles `system_admin`/`standard_user`; it is **disjoint** from `packs/permissions.go:415-441`, which is the SYSTEM-permission (`defaultGrantedTo`) recipe on a **different grant axis** and is already pack-data-driven. The object/field matrix has **no pack/L1 form** (0 app-packs JSON declares `ObjectPermissions`) and the engine is import-isolated from pack data — so there is nothing to move to L1. It is generic platform-bootstrap and legitimately L0. The source-limited (unknown→empty `PermRule{}`, `resolvePermRule` `permission_seeder.go:44-59`) + additive-only `InsertMissing*` invariants are already golden-covered by D0a and STAY in L0. (See D2 = NON-CARVE below.)
- Profile CATALOG: `shared/pkg/constants/profiles.go:4-7` (`ProfileSystemAdmin`/`ProfileStandardUser` + display names) + `PlatformDefaultProfileIDs` — **re-vetted NON-CARVE (D3).** Two generic built-in profile SLUGS + display names (NOT the SF Profile sObject shape), consumed by the generic engine; no dialect override, no SF wire shape. Stays in `shared` for the same reason `SharingModel` stays (Decision #1). (See D3 = NON-CARVE below.)
- The SF `SharingModel` vocabulary: `shared/pkg/constants/enums.go:106-116` (`type SharingModel` + 6 values), carried on neutral `models.ObjectMetadata.SharingModel`/`.EnableHierarchySharing` (`metadata.go:552,556`), referenced across ~19 L0 files. **`RowVisibilityFor` (`shared/pkg/models/row_visibility.go:48`) is the sole translation of this vocabulary into the neutral `RowVisibilityKind`** — a pure, in-memory switch with a `default → Private` fall-through; no json tags on `RowVisibilityPolicy`, so it is **computed, never persisted**.
- **After D1 the four enforcement sites are cut over but not yet SEALED.** The existing `TestSharingModelTaxonomyStaysLedgered` is an **owner-freeze**, not a branching seal: it pins the marker `SharingModel` at count 14 in the *explicit file* `shared/pkg/constants/enums.go` (the canonical enum owner — ledger "SharingModel Taxonomy Baseline", `salesforce-derived-l0-boundary-ledger.md:106-114`), so no *new* SF sharing model enters the enum. It says nothing about branching — there is **no absence seal on the app-blind-L0 branching** yet. That absence seal is exactly what **D6** ADDS (confine `constants.SharingModel*` to `RowVisibilityFor`, forbid re-branching in the four enforcement files); D6 must NOT touch the owner-freeze (the enum literals stay in `shared` — Decision #1).

---

## Security discipline (non-negotiable — this gate protects tenant data)

These are verified invariants any carve MUST preserve; the adversary flagged each as a concrete leak/over-grant vector:

1. **Unify, don't fork.** The four row-visibility enforcement sites must agree. They dispatch on **ONE neutral kind** from **ONE translator** (`RowVisibilityFor`), never N token-branch copies — a de-skin that changes one path but not all de-syncs enforcement (a row hidden from queries but point-readable, or vice versa = leak). D1 changes all four in one atomic PR precisely so no path lags.
2. **The over-grant vector is `RLSResult{Skip:true}` (NO row restriction), not a missing deny** (`rls_filter.go:95,125,133,139,153,…`). The `SharingModel → RowVisibilityKind` translation maps unknown/empty/Private → **`RowVisibilityPrivate`**, which **falls through to the ownership/sharing engine** (the safe default for a private object), and **MUST NEVER produce `Skip:true`** or `PublicRead`/`PublicReadWrite` for an unrecognized model. (This fall-through — not `denyAll` — is deliberate: the mapping is generic app-blind semantics, and a non-CRM deployment carries *no* SharingModel at all, so an unknown → denyAll rule would have blackholed every non-CRM row.)
3. **Fail-closed everywhere stays fail-closed** — nil schema/user → denyAll (`rls_filter.go:61-70`); unwired `folderAccess` (nil) and a malformed parent edge → denyAll; `record_access.go` default → deny. (D1 preserves each of these arms verbatim; only the `SharingModel` classification moved to `RowVisibilityFor`.)
4. **The grant seed stays source-limited + additive-only** (a generic L0 invariant — the seed is a non-carve, D2). `permission_seeder.resolvePermRule` returns empty `PermRule{}` for an unknown profile (the documented fix for the `1/0/0/0/0/0 → 1/1/1/0/0/0` over-grant); `seedPermissionRows` is `InsertMissing*` (additive-only — never upserts/widens a narrowed or admin-edited row). This invariant STAYS in L0 and is already golden-covered by D0a: "unknown principal → no default grant, ok=false".
5. **The #2585 over-grant-safe arm-drops** (`rls_filter.go:225-243,340-348` — "dropping a PROVEN-EMPTY arm is over-grant-safe") must survive **verbatim**.

**Golden-first mandate:** the regression net landed BEFORE the cutover (D0a, merged #3346), and is D1's sufficiency oracle.
- `nexuscrm-permissions/access_decision_golden_test.go` (object/field/row/admin matrices, present) — must be **byte-identical green pre/post every carve**.
- `nexuscrm-permissions/rls_policy_matrix_golden_test.go` + `testdata/rls_policy_matrix_golden.json` (**D0a, merged #3346**): the allow/deny matrix over `SharingModel × operation × visibility-source`, asserting the enforcement paths agree. **This golden staying byte-identical IS the D1 gate** — it is the sufficiency oracle; no hand-written "vocabulary-sufficiency" test substitutes for it. A cutover that changed any decision would flip a golden row.
- `permission_seeder_source_limit_test.go` (present) — the source-limit guard (D2 is a non-carve; the seed stays L0) proves "unknown profile → empty".
- Additive-only assertion (`permission_seeder.go InsertMissing*`), Private-fall-through (never `Skip:true`) coverage, #2585 arm-drop preservation.
- The D6 seal itself adversarially validated (plant `constants.SharingModelPublicReadWrite` into `rls_filter.go` → seal goes RED).

---

## The neutral seams

**Existing, complete, STAY:** `contract.Policy.Access`/`EffectiveObjectAccess`, `contract.ObjectAccess`/`FieldAccess` (`contract.go:1298-1327`), `RecordAccess` (`contract.go:46`), `kernel/pkg/rls` `Result`+`ToSQLFilter`. FLS uses these already.

**D1 adds ONE app-blind translator (a plain function, not a registry).** Row visibility is generic engine semantics needed by every deployment with no dialect override, so — per the "real second consumer" principle — it does **not** earn a binding/registry seam. There is **NO `PolicySchemaBinding`, NO L1-registered provider, NO `nexuscrm-contract` token, NO persisted `RowVisibility` field**:

- `models.RowVisibilityFor(schema *ObjectMetadata) RowVisibilityPolicy` (`shared/pkg/models/row_visibility.go:48`) — the ONE app-blind `constants.SharingModel* → RowVisibilityPolicy` translation. **Pure**: no metadata lookups, no DB, no behavior; a switch with a `default → RowVisibilityPrivate` fall-through. It returns:
  - `type RowVisibilityKind int` — a neutral in-memory enum `{RowVisibilityPrivate, RowVisibilityPublicRead, RowVisibilityPublicReadWrite, RowVisibilityParentControlled, RowVisibilityFolderScoped}`.
  - `type RowVisibilityPolicy struct { Kind RowVisibilityKind; FolderScopeField string; HierarchyGranted bool }` — **NO json tags ⇒ not persisted** (computed on every enforcement call). `FolderScopeField` is the neutral form of the engine's `AllowedIDsColumn` (`""` = FolderSharing → the record's own Id; `"FolderId"` = FolderContentSharing → the row's FolderId column). `HierarchyGranted` mirrors `ObjectMetadata.EnableHierarchySharing`.
  - The four enforcement sites dispatch on `policy.Kind` (byte-identical decision to the old direct SharingModel branch). Private/empty/unknown → `RowVisibilityPrivate` → **fall through to the owner/sharing engine**, NOT denyAll (a denyAll default would regress non-CRM deployments, which carry no SharingModel).

  There is no L1 mapping and no override consumer: the SF↔neutral translation *is* the generic engine default. D6 confines `constants.SharingModel*` to this one function.

**D2 & D3 = NON-CARVES (re-vetted).** These looked like SF recipes but the re-vet proved them **generic platform mechanism** — the SF-skin in the policy area is the wire SHAPE (D4), not this policy LOGIC (D5's SharingRule shape validator is likewise a generic universal-object invariant — see the D5 post-mortem):

- **D2 = NON-CARVE** — `profilePermRules` (`nexuscrm-permissions/permission_seeder.go:24`) is the **generic object/field default-grant matrix** for the platform's own built-in profiles (`system_admin`/`standard_user`), on a grant axis **disjoint** from the pack-data `defaultGrantedTo` SYSTEM-permission recipe (`packs/permissions.go:415-441`). There is **no `DefaultGrantSource` to build and nothing to move to L1**: the object/field matrix has no pack/L1 form (0 app-packs JSON declares `ObjectPermissions`) and the engine is import-isolated from pack data. It is generic platform-bootstrap default-grant defaults and legitimately L0. The source-limited (unknown principal → empty `PermRule{}`, ok=false) + additive-only `InsertMissing*` invariants are already golden-covered by D0a and STAY in L0.
- **D3 = NON-CARVE** — `shared/pkg/constants/profiles.go:4-7` is **generic platform identity**: two built-in profile slugs (`system_admin`/`standard_user`) + display names — NOT the SF Profile sObject shape — consumed by the generic engine (`permission_seeder`, auth fallback), with no dialect override and no SF wire shape. There is **no `PrincipalRoleBinding` to build** (it does not exist in the codebase; the role model is DB-driven). It stays in `shared` for exactly the reason `SharingModel` stays (Decision #1).

> **This is boundary-compliant, NOT a red-line "bless" (the actual test).** The platform policy adjudicates L0 membership by the **Decision Tests** (`docs/design/nexuscrm-platform-architecture.md:656-667`), and `profilePermRules`/`profiles.go` **pass** them: **#6 runtime-blindness** — the matrix keys on the generic system/business object tiers + the platform's own two profiles, with **no CRM/SF object or permission name** and no app-specific branch (works identically for Jira/ITSM/ERP); and **#1 Greenfield** — the platform boots with these built-in, no pack. The `ProfileId/Profile-name behavior` entry in `architecture.md:651-654` is a **guardrail-*scan* target (a thing to review), not a prohibition** — a scan surfaces these for adjudication, and on adjudication they pass. The diet-plan's real anti-hardcode red-lines are narrower and **already carved**: *"L0 must not infer semantics from Salesforce names"* (`diet-plan.md:298` = **D1**, done) and *"neutral contracts must not expose Salesforce-shaped `ObjectPermissions`/`FieldPermissions` DTOs"* (`:299` = **D4**). **Anti-regrowth is preserved, not deleted:** the standing guardrail-scan keeps flagging any NEW hardcoded profile/permission behavior for review, and the **D1 (SF-name-branching) + D4 (SF-DTO-at-contract) seals** stop the actual SF-permission skin from regrowing. What is deliberately NOT added is a count-pinned-0 seal on `profilePermRules`/`profiles.go` themselves — that would be **self-contradictory** (you cannot pin-0 a symbol the same plan keeps as legitimate L0), which is why the old "no `profilePermRules`-style recipe" D6 arm is dropped rather than the generic literals being red-flagged. (Optional future cleanup, tracked separately, not an L0-D carve: externalize the two literals to built-in-profile bootstrap **data** so L0 holds only the resolver — a de-hardcode, not an SF-skin move.)

**D2/D3 anti-regrowth GUARD (platform-bootstrap allowlist — NOT a count-0 ban, NOT an L1 carve).** D2/D3 stay in L0/`shared`, but as **guarded generic bootstrap**, not an open hardcode bin. D6 adds a *positive allowlist* guard (allow the platform-owned baseline; forbid app-specific/SF-specific additions), so the reviewer's valid anti-regrowth concern is satisfied without the self-contradictory "count-0 on a legit symbol" seal and without a fictional cutover:

- **D2 guard (`profilePermRules`)** — **Allowed:** only the platform-owned built-in profiles (`system_admin`, `standard_user`); the object/field grant matrix stays generic bootstrap defaults keyed on the generic system/business object tiers; unknown profile → empty `PermRule{}` (ok=false); seeding stays additive-only (`InsertMissing*`, never widens an existing/admin-edited row). **Forbidden:** adding app-specific/product profile names; Salesforce product-specific profile semantics; object-*specific* default grants that are not platform-bootstrap-generic; duplicating the recipe in L1/CRM/app-pack code. Guard test names: `TestProfilePermRulesRemainPlatformBootstrapOnly` / `TestPlatformBootstrapGrantRecipeStaysSourceLimited` — **NOT** a "no `profilePermRules`" ban.
- **D3 guard (`profiles.go`)** — **Allowed:** the shared platform identity slugs (`ProfileSystemAdmin`, `ProfileStandardUser`, `PlatformDefaultProfileIDs`) + temporary compat aliases. **Forbidden:** using profile constants as product-specific behavior switches; adding Salesforce `Profile` sObject semantics to app-blind L0; adding app-pack-specific profile catalogs in `shared`/platform code. Keeps `profiles.go` from becoming a product-hardcode dumping ground.

**D4 = REAL_CARVE — the genuine remaining wire-skin carve.** Neutralize the SF-shaped DTOs on the `PermissionAdmin` contract (`shared/pkg/contract/permission_admin.go:13-47`), which returns `[]models.ObjectPermissions`/`[]models.FieldPermissions`/`[]models.SystemPermission` + `PermissionSetRecordTypeVisibility`/`SystemCustomPermission`/`PermissionSetCustomPermission` — carrying SF PascalCase json tags (`SobjectType`, `PermissionsViewAllRecords`, `PermissionsRead`; `z_generated.go:93,202-207,459`) and SF table names — into **FULLY-NEUTRAL projection DTOs** (architect-decided naming):
  - `ObjectAccessProjection{ ResourceType; CanRead; CanCreate; CanEdit; CanDelete; CanViewAll; CanModifyAll }`
  - `FieldAccessProjection{ ResourceType; FieldName (raw); CanRead; CanEdit }`
  - neutral capability-catalog entries (system/custom-permission catalog + record-type visibility) with no SF json keys.

  **No SF json keys on the neutral structs** — they live only in the dialect wire-builder (the `ResourceAccessGrant → FolderShare` precedent, `contract.go:409` / `nexuscrm-app-packs/dialects/dialect-sf-crm/folders.go`). The producer `nexuscrm-permissions/contract_admin.go` does a thin rename (the engine keeps `[]models.*` internally; they stop crossing the seam). **Keep `FieldName` RAW** — `QualifiedField` (Object.Field) re-derives in L1. Consumer set = **4 sites**: producer (`contract_admin.go`), dialect (`nexuscrm-app-packs/dialects/dialect-sf-crm/permissions.go`), dialecttest stub (`backend/internal/host/adapters/apihost/dialect/dialecttest/deps.go`), dialect fake test — **NO controlplane/other consumers.** (See D4 in the execution table for the byte-identical-net + cross-repo choreography.)

---

## Decisions (architect) + explicit out-of-scope

- **The `SharingModel` enum VALUES stay in `shared/pkg/constants`** (NOT moved to L1). Controlplane compares them **by value** (`verify_profile_permissions.go:83`, `bootstrapimpl/schema.go:1809-1811`, `tenantoverlay/pack.go:805`) — moving the enum would break CP (the C9 exported-shared-symbol lesson). L0-D forbids only the **app-blind-L0 POLICY BRANCHING** on the enum, via the seal; the enum stays shared vocabulary, and the `SharingModel → RowVisibilityPolicy` translation is confined to the single app-blind function `models.RowVisibilityFor` (NOT an L1 dialect mapping — row visibility is generic engine semantics with no dialect override).
- **FLS *enforcement* → no carve** (already neutral). The `models.FieldPermissions` *row shape* still leaks via the `PermissionAdmin` contract (`permission_admin.go`) — that is **D4**, not a standalone guard. The "no-leak" seal lands only **after D4**; a blanket "0 outside `nexuscrm-permissions`" seal is NOT landable (the type is defined in `shared/pkg/models` and legitimately used by kernel ports / persistence / host adapters).
- **Permission-TABLE schema → DEFERRED to L0-E.** The SF permission-table *definitions* (`PermissionSet`/`Profile`/`SharingRule` in `nexuscrm-kernel/pkg/schemacatalog/system_tables.json`) move with L0-E (records lifecycle / component schema), to avoid a double-regeneration. L0-D carves the policy **logic** only.
- **Out of scope, do NOT absorb:** the RLS SQL emitter (`kernel/pkg/rls`, already neutral), the FLS enforcement (already neutral), CreateTree (L0-E), formula (L0-F), the permission-table schema (L0-E).

---

## Canonical L0-D execution order

Golden-first, then atomic cutover; producer-first for D2+ (each cross-repo carve: app-packs/controlplane consumer updated first where an exported `shared` symbol changes, then pin-bump — the C9 lesson). **There is no D0b seam PR** — D1 is the atomic reader+translator cutover (retired-D0b rationale in the D1 reframe above).

| # | PR | Repo | Prod change | Contains | Gate / death-condition |
|---|---|---|:--:|---|---|
| 0a | **PR-D0a-goldens** — land the regression net (**MERGED #3346**) | NexusCRM | no (tests) | the allow/deny matrix golden (`rls_policy_matrix_golden_test.go` + `testdata/rls_policy_matrix_golden.json`) over `SharingModel × operation × visibility-source`, on the CURRENT code | all green on current code; the safety net every later PR must keep **byte-identical** — this is D1's sufficiency oracle |
| 1 | **PR-D1** — ATOMIC de-SF-name cutover onto `RowVisibilityFor` (**SECURITY-CRITICAL**, PR #3352) | NexusCRM only | yes | add `models.RowVisibilityFor` + `RowVisibilityKind`/`RowVisibilityPolicy` (pure, in-memory, no json tags), and **in the SAME PR** flip the **four** enforcement sites (`rls_filter.go` `buildRLSResult`, `record_access.go` `checkRecordAccessInternal`, `nexuscrm-analytics/query_clause_fls.go` bypass, `nexuscrm-metadata/metadata_sharing.go` gate) to dispatch on `policy.Kind`. NO registry, NO L1 registration, NO persisted field. Private/empty/unknown → Private → fall through (never `Skip:true`) | **HARD GATE:** (a) the D0a golden matrix stays **byte-identical green** (the oracle — no separate vocabulary-sufficiency test substitutes); (b) `constants.SharingModel*` appears in **row-visibility** enforcement only inside `RowVisibilityFor` (grep-proven — the four sites branch on `RowVisibilityKind`; the out-of-scope capability/relationship reads listed above intentionally remain). Single self-contained, independently-revertible PR. **No pin-bump / no cross-repo** — all touched files are in the NexusCRM workspace |
| 2 | **PR-D2 — NON-CARVE** (re-vetted) | — | no | `profilePermRules` (`permission_seeder.go:24`) is the **generic object/field default-grant matrix** for the platform's built-in profiles, on an axis **disjoint** from the pack-data `defaultGrantedTo` recipe (`packs/permissions.go:415-441`); no pack/L1 form (0 app-packs JSON declares `ObjectPermissions`), engine import-isolated from pack data. Nothing to move — it is generic and stays L0 | n/a — the source-limited (unknown→empty) + additive-only `InsertMissing*` invariants are already **golden-covered by D0a** and STAY in L0 |
| 3 | **PR-D3 — NON-CARVE** (re-vetted) | — | no | `constants/profiles.go:4-7` = two generic built-in profile slugs + display names (NOT the SF Profile sObject shape), no dialect override, no SF wire shape; `PrincipalRoleBinding` does not exist (role model is DB-driven). Stays in `shared` for the same reason `SharingModel` stays (Decision #1) | n/a |
| 4 | **PR-D4** — neutralize the PermissionAdmin contract DTOs (**REAL CARVE**) | app-packs then NexusCRM (pin-bump; signature change) | yes | replace the SF-shaped returns on `PermissionAdmin` (`permission_admin.go:13-47`: `[]models.ObjectPermissions`/`FieldPermissions`/`SystemPermission` + `PermissionSetRecordTypeVisibility`/`SystemCustomPermission`/`PermissionSetCustomPermission`, SF json tags `SobjectType`/`PermissionsViewAllRecords`) with **fully-neutral projection DTOs** — `ObjectAccessProjection{ResourceType;CanRead/Create/Edit/Delete/ViewAll/ModifyAll}`, `FieldAccessProjection{ResourceType;FieldName(raw);CanRead/CanEdit}`, neutral capability-catalog entries. SF json keys live **only** in the dialect wire-builder (`ResourceAccessGrant→FolderShare` precedent, `contract.go:409`/`folders.go`). Thin rename in producer `nexuscrm-permissions/contract_admin.go` (engine keeps `[]models.*` internally). **FieldName RAW** (`QualifiedField` re-derives in L1). Consumer set = **4 sites** (producer, `dialect-sf-crm/permissions.go`, `dialecttest/deps.go` stub, dialect fake test); **NO controlplane/other consumers** | **Byte-identical net FIRST:** the object-permission wire has **no test today** — add a full-envelope golden pinning the 7 object keys + qualified Field + catalog passthroughs BEFORE the carve. **Cross-repo C9, producer-first:** app-packs PR first (dialect reads the neutral struct) → merge → SHA; then NexusCRM PR (neutralize contract + producer + `dialecttest` fake + bump `OfficialAppPacksSourceRevision`). Gate: dialect builds against the neutral DTO; no SF perm-row shape crosses the contract |
| 5 | **PR-D5 — NON-CARVE** (attempted + reverted `8313fbd07`) | — | no | `validateSharingRuleShapeContract` (the SF SharingRule row shape: `SharedFromRoleId`/`SharedFromGroupId` XOR + criteria-vs-owner dichotomy; keyPrefix `01q`) **STAYS in app-blind L0.** It is a generic write-validation invariant on a **universal** object, a sibling of the owner-references and canonicalization validators which are already L0-universal. Its capability `platform.security.write.sharing-rule-shape` is declared for SharingRule in `system_tables.json` (**all** distributions), so moving only the validator to a CRM-only L1 registration would leave the declared capability with **no universal registrar** → `runRecordWriteValidators` fails closed → **hard-DENY of every SharingRule write in every non-CRM distribution.** See the D5 post-mortem above | n/a — validator stays L0-universal; the coverage invariant is now enforced by the **H3 guardrail** (arm below) |
| 6 | **PR-D6** — seal the already-landed state (D1 + D4 + D2/D3) + the H3 coverage guardrail | NexusCRM go-ci + nexuscrm-records (see note) | yes | **(a)** the D1 `SharingModel`-confinement seal: confining `constants.SharingModel*` **row-visibility branching** to `models.RowVisibilityFor` in the four enforcement files (see allowlist below — must NOT ban `SharingModel` globally); **KEEP `TestSharingModelTaxonomyStaysLedgered` intact** (owner-freeze on `enums.go`; enum literals STAY in `shared` per Decision #1). Seal file: `backend/internal/boundary/l0_d_row_visibility_seal_test.go`. **(b)** D4 no-SF-DTO seal: 0 SF permission-row DTOs (`ObjectPermissions`/`FieldPermissions`/`SystemPermission`/`PermissionSetRecordTypeVisibility`/`SystemCustomPermission`/`PermissionSetCustomPermission`) cross the `shared/pkg/contract` seam. Seal file: `backend/internal/boundary/l0_d_contract_perm_dto_seal_test.go`. **(d)** D2/D3 platform-bootstrap **positive allowlist** (NOT a count-0 ban): `profilePermRules` and `PlatformDefaultProfileIDs` key on ONLY the two platform built-in profiles (`ProfileSystemAdmin`/`ProfileStandardUser`); a NEW profile key fails the guard. Seal file: `backend/internal/boundary/l0_d_profile_bootstrap_guard_test.go`. **(H3)** capability-coverage guardrail: **every** write-validation (and canonicalization) capability DECLARED by `managedTableDefinitions()` has a REGISTERED validator in the universal built-in records registry — the invariant that would have caught the D5 mis-carve (and a rename bug). Home: `nexuscrm-records/write_validation_capability_coverage_test.go`. **The old D5 seal arm (c) is DROPPED** — D5 is a non-carve, so "0 `validateSharingRuleShapeContract` in records" is FALSE (the validator correctly STAYS in records). | adversarially planted per arm (`constants.SharingModelPrivate` into `rls_filter.go` → (a) RED; `models.FieldPermissions` into a `contract` file → (b) RED; a fake `acme_sales` key into `profilePermRules` → (d) RED; comment out one `mustRegisterSecurityWriteValidator` → (H3) RED), each reverted to green |

### D6 seal — precise allowlist (do NOT ban `SharingModel` globally)

`SharingModel` is a legitimate shared vocabulary; arm (a) of the seal forbids only app-blind-L0 **row-visibility branching**, not the token's existence. Getting this scope wrong either (a) leaves the leak open or (b) breaks CP/L1 legitimately using the enum. (Arm (b) seals the D4 contract-DTO shape; arm (d) is the D2/D3 positive allowlist; arm (H3) is the capability-coverage guardrail. **There is no "(c) D5 seal"** — D5 is a non-carve, the SharingRule shape validator stays in L0-universal, so a "0 `validateSharingRuleShapeContract` in records" ban would be FALSE; its coverage is instead protected by the H3 guardrail.)

> **Two distinct seals — do not conflate.** The **existing** `TestSharingModelTaxonomyStaysLedgered` is the OWNER-freeze on `shared/pkg/constants/enums.go` (pins the enum literals in place; STAYS untouched — the enum stays owned in `shared`). D6 adds a **separate NEW** absence seal on the *branching sites*. D6 must not flip, delete, or repurpose the owner-freeze.

**ALLOWED (seal must NOT flag):**
- the `type SharingModel` + value definitions in `shared/pkg/constants/enums.go` (the vocabulary itself).
- the **single translator** `models.RowVisibilityFor` in `shared/pkg/models/row_visibility.go` — this is where the `SharingModel → RowVisibilityPolicy` switch is *supposed* to live (the D6 confinement target).
- controlplane **by-value** comparisons (`verify_profile_permissions.go`, `bootstrapimpl/schema.go`, `tenantoverlay/pack.go`) — CP is not app-blind L0.
- **the out-of-scope capability / relationship reads** (NOT row visibility): `nexuscrm-permissions/service.go:455,558,899`, `service_effective_permissions.go:109`, `parent_controlled.go:20` — these read `SharingModelControlledByParent` for parent-relationship / admin routing, NOT the row-visibility decision. They are **generic and stay as-is** (there is no D2 carve to migrate them into — D2 is a non-carve). The seal must NOT flag them.
- ledgered/allowlisted **tests** (the golden matrix names SF cases deliberately).

**FORBIDDEN (seal pins to 0):**
- **(a) D1:** `constants.SharingModel*` **row-visibility** branching in the four enforcement files — `nexuscrm-permissions/rls_filter.go`, `nexuscrm-permissions/record_access.go`, `nexuscrm-analytics/query_clause_fls.go`, `nexuscrm-metadata/metadata_sharing.go` — outside a call to `models.RowVisibilityFor` (they must dispatch on `RowVisibilityKind`).
- **(b) D4:** a **NEW** SF-shaped `shared/pkg/models` DTO on the `shared/pkg/contract` seam. Because `permission_admin.go` currently imports `models`, a hard "no models in contract" ban fails immediately — so **either** seed an allowlist baseline and ratchet against NEW references, **or** land this arm only after D4 fully neutralizes `permission_admin.go`. Prefer an **AST/type-shape scan of exported contract DTO signatures** (does an exported `contract` type/method surface a `models.*` perm-row shape?) over a coarse `ImportsOnly` ban.
- **(H3) capability-coverage guardrail** (replaces the dropped D5 arm): **every** write-validation (and canonicalization) capability DECLARED by `managedTableDefinitions()` (i.e. authored in `system_tables.json`, shipped in **all** distributions) must have a REGISTERED validator/canonicalizer in the universal, app-blind built-in records registry. Home: `nexuscrm-records/write_validation_capability_coverage_test.go` (app-blind / universal build, NO build tag — it is the only place that can instantiate the write-validator registrars *and* read `managedTableDefinitions()`). It builds the declared `(object, phase, capability)` set from the managed definitions, instantiates a records `PersistenceService` and runs the built-in security + folder write-validator/canonicalizer registrars, then asserts every declared capability is universally registered. This is the invariant that would have caught the D5 mis-carve: a capability declared for a universal object but registered only under a CRM-only L1 registrar is a runtime hard-DENY in every non-CRM distribution. **Rationale (why this gap existed):** neither local unit-green nor the controlplane reconciliation check (`AssertManagedWriteValidationCapabilitiesReconciled`, metadata-only) checks runtime **registration** — the reconciliation check compares declared capabilities against embedded metadata and never instantiates the runtime registry. H3 closes that gap. (See the D5 post-mortem above.)

*(D2/D3 are NON-CARVES but **GUARDED** — no count-0 seal on `profilePermRules`/`profiles.go` themselves (self-contradictory: they legitimately stay L0 per Decision Test #6), but D6 arm (d) adds a **platform-bootstrap allowlist guard** (allow the platform-owned baseline: `system_admin`/`standard_user` + generic bootstrap grants, source-limited, additive-only; **forbid** NEW app-specific/SF profile names, product-specific profile semantics, object-specific non-generic default grants, L1/app-pack recipe duplication, and SF `Profile` sObject semantics — see the "D2/D3 anti-regrowth GUARD" spec above). Plus the standing guardrail-scan (`architecture.md:651-654`) and the D1/D4 seals catch actual SF-permission skin. So the boundary is STRENGTHENED, not weakened — the removed "profilePermRules-recipe count-0 ban" was based on the disproven premise that the generic literal was SF-skin, and is replaced by a correct allowlist guard.)*

Scope by **exact root-relative path** (the B6 lesson — basename exclusion is an escape hatch); exclude generated files; bidirectional ratchet; adversarially plant `constants.SharingModelPublicReadWrite` into `rls_filter.go` and confirm RED. **Do NOT ban `SharingModel` globally** — the enum, the translator, CP's by-value reads, and the out-of-scope capability reads all legitimately reference it.

> **Where the seal is enforced.** Arms (a), (b), (d) are standard NexusCRM `go-ci` tests in `backend/internal/boundary` — every scoped surface builds in the NexusCRM workspace, so an in-repo Go test suffices; no hermetic scaffolding needed. Arm (a) scopes the four enforcement files (`nexuscrm-permissions/{rls_filter,record_access}.go`, `nexuscrm-analytics/query_clause_fls.go`, `nexuscrm-metadata/metadata_sharing.go`); arm (b) scans `shared/pkg/contract` production files for the forbidden perm-row DTO selectors; arm (d) scans `nexuscrm-permissions/permission_seeder.go` (`profilePermRules`) and `shared/pkg/constants/profiles.go` (`PlatformDefaultProfileIDs`). Arm (H3) lives in the **`nexuscrm-records` module** (not `backend/internal/boundary`) because it must instantiate the records write-validator registrars and read `managedTableDefinitions()` — it runs in the records package's `go test ./...`. The boundary seals run in **app-packs CI only if a hermetic stdlib-only seal is explicitly added there** (the B6 pattern — warranted only when a forbidden symbol could regrow in a dialect that can't build standalone; not the case for these app-blind-L0 surfaces). Do NOT assume "seal in both CIs" — state which CI enforces it.

> **FLS guard** (rides D6, **AFTER D4**): a seal asserting (a) `models.FieldPermissions` does not appear in any L1/SF **dialect** (0 today — assertable now), AND (b) — once **D4** has neutralized `permission_admin.go` — the SF perm-row shape does not cross the `PermissionAdmin` contract. A blanket "0 outside `nexuscrm-permissions`" seal is **NOT landable** at D0/D6: the type is defined in `shared/pkg/models` and legitimately consumed by `shared/pkg/contract`, `nexuscrm-kernel/pkg/ports`, `nexuscrm-persistence`, and the backend host adapters. Sealing before D4 would go RED immediately (this is why the guard is D4-gated, not a standalone light guard).

---

## D1 enforcement-path manifest (the HARD GATE for PR-D1) — FOUR sites, not seven

D1's scope is **row-visibility** enforcement: the sites that branch on `SharingModel` to decide *whether a row is visible*. There are **four**. Each routes through the single translator `models.RowVisibilityFor` and dispatches on `RowVisibilityKind`; no local `constants.SharingModel*` row-visibility switch survives. Anchors re-verified against this branch:

| # | Path | Site | As-built |
|---|---|---|---|
| 1 | `shared/pkg/models/row_visibility.go` | `RowVisibilityFor` (:48) | the ONE `SharingModel → RowVisibilityPolicy` switch (all four sites call it) |
| 2 | `nexuscrm-permissions/rls_filter.go` | `buildRLSResult` (:101,109,135,151-152,161) | the "single source of truth" query-level branch → dispatch on `rowVis.Kind` (+`FolderScopeField` → `AllowedIDsColumn`) |
| 3 | `nexuscrm-permissions/record_access.go` | `checkRecordAccessInternal` (:84,121-158) | the single-record mirror (":117 must match BuildRLSResult") → same `rowVis.Kind` dispatch |
| 4 | `nexuscrm-analytics/query_clause_fls.go` | `queryParentFilterBypassTargetRecordSchemaUnrestricted` (:640-641) | the third (under-counted) enforcement branch → `RowVisibilityFor(schema).Kind == PublicRead\|\|PublicReadWrite` |
| 5 | `nexuscrm-metadata/metadata_sharing.go` | `GetParentControlledObject` (:31) | classification gate → `RowVisibilityFor(schema).Kind != RowVisibilityParentControlled` |

> **Deliberately OUT of D1 (they stay reading `SharingModel` directly, → D2):** `nexuscrm-permissions/service.go:455,558,899`, `service_effective_permissions.go:109`, `parent_controlled.go:20`. They read `SharingModelControlledByParent` for **capability / admin / parent-relationship routing**, not the row-visibility decision — including them in D1 would conflate two concerns. This is why the manifest is **narrower than the old "7 hard-gate paths"** (which mis-scoped them in). The D6 seal explicitly allowlists these.

**Death-condition (grep-provable):** (a) the four enforcement sites contain no `constants.SharingModel*` row-visibility `switch`/`if` outside a `RowVisibilityFor` call; (b) all dispatch on the ONE `RowVisibilityKind` from the ONE translator; (c) the D0a golden matrix stays byte-identical green. D6 then seals this so the branching cannot regrow.

---

## Cross-repo protocol (D4 only — D1/D6 are in-repo; D2/D3/D5 are non-carves)

**D1 is NexusCRM-only:** `models.RowVisibilityFor` + the four enforcement sites all live in the NexusCRM workspace; the translator adds no exported `shared`/`contract` symbol that app-packs or controlplane consume, and there is **no `nexuscrm-contract` change** (row visibility is not a published contract token). No publish/pin choreography for D1. **D2/D3/D5 are non-carves — no cross-repo work** (D5's SharingRule shape validator stays L0-universal in `nexuscrm-records`; only its coverage is guarded by H3, an in-repo test). The cross-repo protocol below applies to **D4 only**, which changes the `contract` surface:

- The `SharingModel` enum **stays in `shared/pkg/constants`** because CP compares it by value; only the row-visibility *branching* is confined (to `RowVisibilityFor`) / later sealed.
- D4 is a **contract signature change** with a producer-first choreography: the object-permission wire has **no test today**, so add the byte-identical full-envelope golden FIRST; land the **app-packs PR first** (dialect reads the neutral `ObjectAccessProjection`/`FieldAccessProjection`), merge, take the SHA, then the NexusCRM PR neutralizes the contract + producer + `dialecttest` fake and bumps `OfficialAppPacksSourceRevision`.
- For D4: before changing ANY exported `shared`/`contract` symbol, grep **app-packs + controlplane** (the C9 lesson — a NexusCRM-only grep is insufficient); publish + pin the changed module **before** the de-skin. (For D4 the verified consumer set is 4 sites, all in NexusCRM + app-packs — no controlplane consumer.) Operate on an independent app-packs clone (not the gitignored nested snapshot); build-dependent goldens execute in NexusCRM `go-ci` at the pin-bump.

---

## Binding rules

1. **Golden-first, always.** No enforcement carve merges without its allow/deny golden byte-identical green. For D1 the D0a golden (#3346) staying byte-identical **is** the sufficiency oracle.
2. **Fail-closed is sacred.** For row visibility: unknown/empty SharingModel → `RowVisibilityPrivate` → **fall through to the ownership/sharing engine** (the safe default for a private object), never `Skip:true`/`PublicRead`/`PublicReadWrite`. (Not `denyAll` — the mapping is generic app-blind, and non-CRM has no SharingModel.) For the L0 grant seed (a non-carve, stays in L0): additive-only seed, source-limited default (unknown principal → empty `PermRule{}`, ok=false).
3. **Unify, don't fork** — one translator (`RowVisibilityFor`), one neutral `RowVisibilityKind`, not N token-branch copies; all four enforcement sites (incl. `query_clause_fls.go`) cut over **atomically** in D1.
4. **Enum stays in shared** — confine the branching to one translator, not the vocabulary (CP consumes it by value).
5. **Don't over-broaden** — FLS (done), the RLS SQL emitter (neutral), CreateTree (L0-E), the permission-table schema (L0-E), formula (L0-F) are OUT.
6. **Evidence-bound** — every claim carries file:line + how-verified.
