# Profile Refactor Design

Date: `2026-04-26`
Scope: `Profile` / `PermissionSet` / implicit-child permission parity redesign
Primary issue: `#1970`
Related issues: `#1919`, `#1920`
Primary reference PR: `#1993`

## 1. Goal

This document is the staged closure blueprint for the remaining
implicit-child permission problem tracked under `#1970`.

It is execution-ready for the buckets whose owner models are already decided in
this document.

It is **not** a claim that every object under the broader `#1970` umbrella is
fully design-complete today.

The target is **not** "make every `ControlledByParent` child inherit all parent
permissions."

The target is:

1. keep Salesforce-faithful row-level inheritance where `ControlledByParent`
   already proves it
2. preserve real admin-authored child CRUD / FLS surfaces where Salesforce
   still exposes them
3. add a generic metadata-authored model for standard objects whose object
   access surface is composite or delegated rather than self-owned
4. align runtime, seeder, repair, verify, and cleanup to the same ownership
   model so the issue can close without hidden tails

The broader `#1970` line is only closed when all affected objects have an
explicit steady-state owner for:

- row-level visibility
- object CRUD
- field FLS
- seed / repair / verify expectations

## 2. Current State

### 2.1 Facts already implemented

The following pieces are already real in the repo:

1. query-level row visibility can follow a `ControlledByParent` relationship
   through the RLS builder:
   `backend/internal/features/permissions/rls_filter.go:111`
2. single-record access can follow a `ControlledByParent` relationship through
   the record-access walker:
   `backend/internal/features/permissions/record_access.go:101`
3. `#1993` added metadata-backed standard-object declarations for:
   - `QuoteLineItem`
   - `OrderItem`
   - `PricebookEntry`
   evidence:
   `backend/internal/controlplane/packspec/standard_objects_spec.json:1737`
   `backend/internal/controlplane/packspec/standard_objects_spec.json:1822`
   `backend/internal/controlplane/packspec/standard_objects_spec.json:1920`
4. the current standard-object boundary is guarded by tests that require:
   - `QuoteLineItem`, `OrderItem`, and `PricebookEntry` stay in the standard
     spec as row-level-proven children
   - `OpportunityContactRole` is present only through a derived composite
     access policy, not as a fake metadata-only child
   - `CaseComment` stays out
   evidence:
   `backend/internal/controlplane/packs/platform_core_test.go:515`
5. `OpportunityLineItem` was already declared in the standard spec with
   `sharingModel: ControlledByParent`, so the generic runtime dispatch already
   applies to it today:
   `backend/internal/controlplane/packspec/standard_objects_spec.json:1663`
   `backend/internal/features/permissions/record_access.go:101`
   `backend/internal/features/permissions/rls_filter.go:111`
6. object CRUD still resolves from the target object's own effective
   `ObjectPermissions` row:
   `backend/internal/features/permissions/service.go:295`
7. field access still resolves from the target object's own effective
   `FieldPermissions` row with object-permission fallback:
   `backend/internal/features/permissions/service.go:386`
   `backend/internal/features/permissions/service.go:417`
8. permission seeding still creates self `ObjectPermissions` and self
   `FieldPermissions` rows for each schema passed into the seeder:
   `backend/internal/features/permissions/permission_seeder.go:44`
9. profile repair / convergence still counts business-object permission coverage
   by "one business object => one expected object-permission row + N field rows"
   evidence:
   `backend/internal/controlplane/packs/permissions.go:19`
   `backend/internal/controlplane/verify_profile_permissions.go:13`

### 2.2 What `#1993` actually closed

`#1993` closed one safe slice:

> objects missing from standard metadata could not participate in the existing
> row-level `ControlledByParent` access path, so their record visibility stayed
> artificially private.

`#1993` did **not** close the whole `#1970` problem, because it intentionally
left these surfaces untouched:

- child object CRUD ownership
- child field FLS ownership
- seeder ownership for composite / delegated objects
- repair ownership for composite / delegated objects
- verify expectations for composite / delegated objects
- destructive cleanup rules for historical self permission rows

Repo evidence for the intentionally pinned current boundary:

- row-level additions:
  `backend/internal/features/permissions/controlled_by_parent_standard_objects_test.go:57`
- child CRUD still self-owned:
  `backend/internal/features/permissions/controlled_by_parent_standard_objects_test.go:93`
- aggregate access still combines child CRUD with parent-derived row visibility:
  `backend/internal/features/permissions/controlled_by_parent_standard_objects_test.go:210`

### 2.3 Root cause behind the remaining gap

The unresolved problem is **not** "we forgot another whitelist entry."

The unresolved problem is that implicit-child objects fall into three different
product shapes:

| Shape | Row-level owner | CRUD owner | FLS owner | Example |
| --- | --- | --- | --- | --- |
| row-level-proven child | parent-derived via `ControlledByParent` | not yet frozen; current repo behavior still reads child self rows | not yet frozen; current repo behavior still reads child self rows | `QuoteLineItem`, `OpportunityLineItem`, `OrderItem`, `PricebookEntry` |
| composite-access child | parent-derived or related-parent-derived | predicate over other canonical object permissions | usually derived from object access | `OpportunityContactRole` |
| delegated-access child | delegated / object-specific | not generic self CRUD | not generic self FLS | `CaseComment` |

The broader `#1970` line only fully closes once the platform can represent all
three shapes
without:

- `if objectName == ...` branches in L0
- fake generic seeding for non-generic objects
- verify expecting rows that the runtime no longer treats as authoritative

## 3. Evidence-Backed Baseline

### 3.0 Product-Form-Parity lens

This doc uses the `CLAUDE.md` product-form-parity split:

- strict-parity surfaces:
  - object metadata shape
  - profile / permission-set authoring surface
  - install / upgrade topology
- soft-parity surfaces:
  - internal runtime behavior once the strict authoring surfaces are preserved

For this issue line, the decisive question is not "must NexusCRM copy every
Salesforce runtime convenience coupling?" It is:

> does NexusCRM preserve the same product form for admins while declaring any
> intentional runtime delta explicitly?

That framing lets this design distinguish:

- `CONFIRMED`: Salesforce-faithful product boundary
- `DECLARED-DELTA`: intentional NexusCRM runtime choice with preserved strict
  parity surfaces
- `DEFERRED`: object bucket still blocked on a missing generic primitive

### 3.1 Three gates, three owners

Every affected object must answer these three questions independently:

| Gate | Question | Allowed owner types |
| --- | --- | --- |
| Sharing gate | can this record be seen / queried? | `SharingModel` + relationship metadata |
| CRUD gate | can this object be read/create/edit/delete? | self `ObjectPermissions` or metadata-authored derived/composite policy |
| FLS gate | can this field be read/edit? | self `FieldPermissions` or metadata-authored derived field policy |

The key rule:

> `ControlledByParent` directly proves row-level behavior. It does not
> automatically prove CRUD ownership or FLS ownership.

### 3.2 Salesforce baseline and candidate paths by object bucket

This section is the actual review baseline for the current staged contract. It
starts from Salesforce/product questions and repo evidence first, then declares
what this doc is and is not freezing.

#### A. Row-level-proven children

- `QuoteLineItem`
- `OpportunityLineItem`
- `OrderItem`
- `PricebookEntry`

1. Salesforce baseline / candidate paths
   - baseline already proven enough for this issue line:
     `ControlledByParent` is valid evidence for row-level visibility
   - candidate runtime paths once the strict authoring surfaces are preserved:
     - child self-owned CRUD/FLS
     - parent-gated CRUD/FLS
     - another generic metadata-authored policy
2. Repo evidence checked
   - standard metadata entries now exist only for row-level activation:
     `backend/internal/controlplane/packspec/standard_objects_spec.json:1737`
     `backend/internal/controlplane/packspec/standard_objects_spec.json:1822`
     `backend/internal/controlplane/packspec/standard_objects_spec.json:1920`
   - `OpportunityLineItem` was already present in the standard spec before
     `#1993` and already rides the same generic dispatch:
     `backend/internal/controlplane/packspec/standard_objects_spec.json:1663`
   - runtime object CRUD still reads the target object's effective
     `ObjectPermissions`:
     `backend/internal/features/permissions/service.go:295`
   - runtime field access still reads the target object's effective
     `FieldPermissions` with object fallback:
     `backend/internal/features/permissions/service.go:386`
     `backend/internal/features/permissions/service.go:417`
   - tests pin the current runtime choice:
     `backend/internal/features/permissions/controlled_by_parent_standard_objects_test.go:93`
     `backend/internal/features/permissions/controlled_by_parent_standard_objects_test.go:210`
3. Declared delta from Salesforce
   - `QuoteLineItem`: `DECLARED-DELTA`
     - strict-parity surfaces preserved:
       object metadata + child `ObjectPermissions` / `FieldPermissions`
       authoring surface remain present
     - NexusCRM runtime choice:
       child `ObjectPermissions` remain authoritative without additional
       parent-CRUD-gating
   - `OpportunityLineItem`: `DECLARED-DELTA`
     - same rationale as `QuoteLineItem`
   - `OrderItem`: `DECLARED-DELTA`
     - strict authoring surface is preserved
     - current runtime continues to read child `ObjectPermissions`
     - this doc does not freeze Salesforce-faithful CRUD/FLS ownership more
       strongly than the current evidence package proves
   - `PricebookEntry`: `DECLARED-DELTA`
     - strict authoring surface is preserved, including admin-authored FLS
     - current runtime continues to read child `ObjectPermissions` and child
       `FieldPermissions`
     - this doc does not freeze that runtime choice as Salesforce-faithful
       product truth without a stronger baseline package
4. Next-tenant impact
   - next tenants may safely receive row-level parity for this bucket
   - the next tenant gets preserved profile/permission authoring surfaces
     instead of a destructive cutover that removes real admin control
   - declared runtime deltas remain explicit and reviewable
5. Verification
   - row-level dispatch remains pinned by generic runtime tests
   - child-row CRUD runtime choice remains pinned by permission tests
   - any future change away from child-row authority requires a new
     design-doc PR plus convergence-layer verification

Current frozen contract for this bucket:

- row-level visibility is parent-derived
- `QuoteLineItem` and `OpportunityLineItem` preserve their strict product form
  while NexusCRM explicitly keeps the softer runtime delta:
  child-row CRUD authority without mandatory parent-CRUD-gating
- `OrderItem` and `PricebookEntry` preserve their current child-row authority
  surfaces as explicit runtime deltas for this stage, not as stronger
  Salesforce-frozen product truth
- no destructive cleanup of child permission rows is allowed under a
  product-form-parity-preserving baseline

Authoritative Salesforce references for this bucket:

- QuoteLineItem object reference:
  <https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_quotelineitem.htm>
- OpportunityLineItem object reference:
  <https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_opportunitylineitem.htm>
- OrderItem object reference:
  <https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_orderitem.htm>
- PricebookEntry object reference:
  <https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_pricebookentry.htm>
- Object permissions authoring surface:
  <https://developer.salesforce.com/docs/atlas.en-us.securityImplGuide.meta/securityImplGuide/users_profiles_object_perms.htm>

#### B. Composite-access objects

- `OpportunityContactRole`

1. Salesforce baseline / candidate paths
   - baseline:
     object access is composite rather than generic self-owned CRUD
   - candidate paths:
     - generic metadata-authored composite access primitive
     - any other generic metadata-authored equivalent
   - rejected path:
     ship OCR as if row-level `ControlledByParent` also settled its object
     access surface
2. Repo evidence checked
   - composite-access design already records the product shape and why a
     metadata-only cutover is insufficient:
     `docs/design/permission-system/composite-object-access-policy.md`
   - platform-core guardrail test keeps OCR out of the current metadata-only
     cutover:
     `backend/internal/controlplane/packs/platform_core_test.go:564`
3. Declared delta from Salesforce
   - storing the composite rule as metadata on `_System_Object` is an allowed
     engine-storage delta if the external permission contract remains aligned
4. Next-tenant impact
   - the next tenant should gain a reusable generic primitive, not an
     `if objectName == "OpportunityContactRole"` branch
5. Verification
   - the composite-access design's verification section is the minimum bar
   - OCR should not re-enter the standard spec until that bar is satisfied

Current frozen contract for this bucket:

- do not ship as a generic self-owned `ControlledByParent` object
- re-introduce only after a metadata-authored composite access primitive exists
- do not describe OCR self permission rows as dead until the replacement owner
  model is implemented across runtime, seeder, repair, and verify

Primary design reference:

- `docs/design/permission-system/composite-object-access-policy.md`

Current Salesforce-shaped baseline already captured there:

- read requires composite canonical permissions
- create / edit require a different composite canonical permission predicate

#### C. Delegated / special-surface objects

- `CaseComment`

1. Salesforce baseline / candidate paths
   - dedicated baseline and owner-model decision now live in:
     `docs/design/permission-system/casecomment-delegated-access-policy.md`
   - chosen path:
     keep `CaseComment` on the same `_System_Object.AccessPolicy` family as the
     OCR line, but extend the expression grammar for delegated,
     actor-sensitive rules
   - rejected path:
     assume `CaseComment` is solved by the same model as the row-level-proven
     child bucket or by a fake self-owned permission surface
2. Repo evidence checked
   - platform-core guardrail test keeps `CaseComment` out of the metadata-only
     cutover:
     `backend/internal/controlplane/packs/platform_core_test.go:561`
   - dedicated follow-up design freezes the owner model before implementation:
     `docs/design/permission-system/casecomment-delegated-access-policy.md`
3. Declared delta from Salesforce
   - engine-storage delta only:
     delegated access remains metadata-authored through
     `_System_Object.AccessPolicy` instead of a hidden platform-internal rule
4. Next-tenant impact
   - no tenant should inherit a fake generic self-owned `CaseComment` model by
     default
5. Verification
   - closure requires the runtime/seeder/verify evidence listed in the
     dedicated follow-up design

Current frozen contract for this bucket:

- keep out of the current metadata-only cutover
- do not pretend it is solved by the same self-owned model as
  `QuoteLineItem` / `OrderItem` / `PricebookEntry`
- freeze the owner model through the same `_System_Object.AccessPolicy` family
  as composite objects, with delegated actor-sensitive terms rather than a
  fake self-owned permission surface

Dedicated follow-up design reference:

- `docs/design/permission-system/casecomment-delegated-access-policy.md`

## 4. Design Contract

### 4.1 What this doc freezes now

This design doc freezes only the following facts today:

1. `ControlledByParent` directly proves row-level visibility
2. `#1993` is the accepted row-level baseline for:
   - `QuoteLineItem`
   - `OpportunityLineItem`
   - `OrderItem`
   - `PricebookEntry`
3. `OpportunityContactRole` does not participate in the metadata-only cutover;
   it may re-enter only through the generic composite-access owner model
4. `CaseComment` is explicitly outside the execution-ready scope of this staged
   blueprint and remains a deferred follow-up under the broader `#1970` line
5. no PR may delete child permission rows or rewrite profile convergence
   expectations based only on row-level evidence

### 4.2 What this doc does not freeze yet

This design doc does **not** yet freeze:

1. any future move away from the currently accepted product-form-parity model
   for `QuoteLineItem`
2. any future move away from the currently accepted product-form-parity model
   for `OpportunityLineItem`
3. any future move away from the currently accepted product-form-parity model
   for `OrderItem`
4. any future move away from the currently accepted product-form-parity model
   for `PricebookEntry`
5. whether a later generic policy should replace child-row authority for any of
   those objects

Those changes would require a separate evidence package plus a declared contract
update under §10.

## 5. Target End State

The broader `#1970` line is considered closed only when the steady-state model
looks like this:

### 5.1 Row-level-proven children preserve product form and explicit runtime verdicts

For `QuoteLineItem`, `OpportunityLineItem`, `OrderItem`, and `PricebookEntry`:

- standard metadata continues to declare the object and sharing parent
- runtime row-level access uses the parent-derived path
- strict profile / permission-set authoring surfaces remain present
- runtime verdict is explicitly declared per object as either
  `DECLARED-DELTA` or `DEFERRED`, unless a stronger product-form evidence
  package justifies `CONFIRMED`
- seeder / repair / verify continue to consume the same declared owner model

This line now freezes the runtime choice through the product-form-parity lens
rather than describing it as an unresolved fact.

### 5.2 Composite / delegated objects get a generic metadata capability

The platform gains one reusable metadata-authored access model, described in
the composite-access design, that can express:

- `objectRead`
- `objectCreate`
- `objectEdit`
- optional `fieldRead`
- optional `fieldEdit`

using canonical terms such as:

- object permission on another object
- system permission
- boolean composition (`allOf`, `anyOf`, `not`)
- inheritance (`fieldRead <- objectRead`, `fieldEdit <- objectEdit`)

The runtime then has one evaluator, not object-name branches.

### 5.3 Seeder / repair / verify align to the same ownership model

If an object has a derived/composite access policy:

- runtime reads the policy
- seeder does not fabricate self permission rows for that object
- repair does not try to backfill self permission rows for that object
- verify does not count self permission rows for that object
- historical cleanup lives in `data-specs/`, not in long-lived runtime logic

This is mandatory for closure. Otherwise the repo keeps multiple live meanings
for the same object surface.

### 5.4 Deferred follow-up outside this execution-ready stage

`CaseComment` is not yet part of the execution-ready portion of this blueprint.

Current status:

- dedicated baseline / owner-model design now exists
- dedicated verification plan now exists
- blocked on delegated-policy implementation plus convergence evidence

Contract for this stage:

- keep `CaseComment` out of the metadata-only cutover
- do not let any implementation PR treat it as implicitly covered by the rest of
  this doc
- require the dedicated follow-up design to be implemented and validated before
  it can enter execution mode

## 6. Execution Plan

This section defines the exact staged closure sequence under the broader
`#1970` line.

### Phase 0 — Freeze the current object inventory and boundaries

Status:

- partially complete

Required outcomes:

1. keep `QuoteLineItem`, `OpportunityLineItem`, `OrderItem`, and
   `PricebookEntry` in
   `standard_objects_spec.json`
2. keep `OpportunityContactRole` and `CaseComment` out of the metadata-only
   cutover
3. keep guardrail tests that enforce the above

Evidence today:

- `backend/internal/controlplane/packs/platform_core_test.go:515`

Verification:

- targeted tests for platform-core spec parsing / cutover boundary

Exit criteria:

- the object inventory is no longer ambiguous for implementers

### Phase 1 — Treat `#1993` as the row-level baseline, not the full closure

Status:

- complete for runtime/spec baseline

Required outcomes:

1. keep row-level `ControlledByParent` access active for:
   - `QuoteLineItem`
   - `OpportunityLineItem`
   - `OrderItem`
   - `PricebookEntry`
2. keep guardrail tests that pin the accepted current runtime choice for the
   row-level-proven bucket

Evidence today:

- `backend/internal/features/permissions/controlled_by_parent_standard_objects_test.go`

Verification:

- permission package tests covering:
  - row-level parent inheritance
  - child-row CRUD remains authoritative for the accepted bucket
  - aggregate access remains "parent visibility + child-row CRUD"

Exit criteria:

- the repo has a stable starting point for the remaining `#1970` work

### Phase 2 — Add the generic metadata-authored composite access primitive

Status:

- implemented in the current `#1970` mainline PR

Required code/design outputs:

1. add an `AccessPolicy`-style metadata field on `_System_Object`
2. define typed parsing for the metadata model
3. add one shared object-access evaluator in the permissions subsystem
4. reuse the evaluator from:
   - `CheckObjectPermissionWithUser`
   - global describe / `/sobjects`
   - query / analytics read gate
   - record CRUD entrypoints
5. define field-access behavior for derived objects

Primary design source:

- `docs/design/permission-system/composite-object-access-policy.md`

Verification:

1. evaluator unit tests for term parsing and boolean composition
2. entrypoint tests proving the same policy result is reused across:
   - describe
   - query gate
   - retrieve
   - insert / update / delete
3. grep check confirming no object-name branch was added in L0

Exit criteria:

- the platform can express a composite object surface without special-casing
  `OpportunityContactRole`

### Phase 3 — Re-introduce `OpportunityContactRole` on the composite primitive

Status:

- implemented in the current `#1970` mainline PR

Required outcomes:

1. add OCR standard metadata only once the composite primitive exists
2. author OCR object-access rules in metadata
3. make OCR discoverability, queryability, and writeability all consume the
   composite policy
4. keep row-level sharing aligned with the correct parent-derived model after
   object access passes

Verification:

1. OCR absent from `/sobjects` without the required canonical permissions
2. OCR query denied when only a subset of canonical permissions is present
3. OCR create / edit denied until the full composite write predicate is met
4. OCR row-level visibility still follows the declared parent path after
   object-level access succeeds

Exit criteria:

- OCR is no longer blocked on a missing platform primitive

### Phase 4 — Deferred follow-up for `CaseComment`

Status:

- deferred

Required outcomes:

1. keep the dedicated owner-model design for `CaseComment` review-clean
2. implement the delegated `AccessPolicy` follow-up against that design
3. prove runtime / seeder / verify converge on the same owner predicate
4. only after that follow-up implementation passes review, execute the generic
   metadata path

Current dedicated design reference:

- `docs/design/permission-system/casecomment-delegated-access-policy.md`

Verification:

1. explicit follow-up design note citing the Salesforce baseline used
2. runtime tests proving the delegated/composite rule is enforced through the
   generic evaluator path
3. guardrail tests proving `CaseComment` does not re-enter as a fake
   metadata-only child

Exit criteria:

- `CaseComment` is either promoted through its dedicated follow-up design or
  remains explicitly deferred without blocking the execution-ready part of this
  stage

### Phase 5 — Align seeder, repair, verify, and cleanup

Status:

- partially complete for derived/composite objects
- runtime / seeder / repair / verify ownership convergence is implemented for
  derived-policy objects in the current `#1970` mainline PR
- OCR historical cleanup spec is authored in the current `#1970` mainline PR
- environment apply / final operator evidence remain deferred

Required outcomes:

1. seeder skips self permission rows for derived/composite objects
2. `InitializePermissions` and repair reconciliation compute expected coverage
   from the same metadata ownership model
3. profile verify excludes self-row expectations for derived/composite objects
4. historical self permission rows are cleaned by `data-specs/`, not runtime
   branches

Repo surfaces that must be changed together:

- `backend/internal/features/permissions/permission_seeder.go`
- `backend/internal/controlplane/packs/permissions.go`
- `backend/internal/controlplane/operations.go`
- `backend/internal/controlplane/verify_profile_permissions.go`
- any required `data-specs/` cleanup docs

Verification:

1. seeder tests proving self rows are skipped only for derived/composite objects
2. repair tests proving the reconciliation path does not recreate skipped rows
3. verify tests proving expected coverage matches the new ownership model
4. idempotence checks on apply / repair / verify

Exit criteria:

- runtime, seed, repair, verify, and cleanup all agree on which rows are
  authoritative

### Phase 6 — Environment validation and issue closure

Status:

- not started

Required outcomes:

1. run the final closure sequence in a real environment
2. capture pre/post evidence for:
   - metadata-only child objects
   - OCR
   - any delegated object included in scope
3. show apply / repair / verify remain stable on rerun

Recommended operator evidence:

1. targeted API or SQL evidence for object discoverability
2. targeted API or SQL evidence for object CRUD gates
3. targeted API or SQL evidence for row-level visibility
4. repair log proving the expected convergence steps ran
5. verify output proving the final ownership model passes

Exit criteria:

- the staged contract implemented by this doc can be closed without any
  remaining "but runtime/seeder/verify still
  disagree" tail

## 7. What Can Ship Together

### Safe to ship in one PR

1. documentation clarifications and guardrail tests
2. metadata-only row-level additions and test pins that preserve the accepted
   product-form-parity verdict for the row-level-proven bucket
3. the generic composite-access primitive itself, if it lands with no object
   reclassification yet

### Must stay staged

1. adding the generic composite primitive
2. reclassifying OCR onto that primitive
3. reclassifying `CaseComment`
4. seeder / repair / verify expectation changes
5. destructive cleanup of historical self permission rows

Why staging is mandatory:

- these are shared-state permission surfaces
- runtime correctness alone is insufficient
- `R1` through `R10` style evidence is required for every cutover boundary

## 8. Definition of Done

The staged contract in this doc is done when all of the following are true:

1. `QuoteLineItem`, `OpportunityLineItem`, `OrderItem`, and
   `PricebookEntry` have row-level behavior frozen with explicit
   product-form-parity verdicts
2. `OpportunityContactRole` is implemented through a generic metadata-authored
   composite policy, not a fake self-owned CBP model
3. `CaseComment` remains explicitly outside this execution-ready stage unless a
   dedicated follow-up design has already landed
4. runtime, seeder, repair, verify, and cleanup all read the same ownership
   model
5. no implementer can "finish the issue" by changing only one layer, such as:
   - one runtime helper
   - one `_test` file
   - one spec entry
   - one repair step
6. final environment evidence proves the steady-state model, not only unit-test
   agreement

Meeting the above means this staged blueprint is internally closed.

The broader `#1970` line still remains open until the deferred `CaseComment`
follow-up also reaches its own approved owner-model contract.

## 9. Review Checklist For Follow-Up PRs

Before approving any implementation PR under this design, the reviewer should be
able to answer all of these with evidence:

1. Which exact object class is this PR changing: metadata-only, composite, or
   delegated?
2. Which gate is changing: row-level, CRUD, FLS, or seed/repair/verify?
3. What is the Salesforce baseline for that exact gate?
4. Which repo files currently own that gate?
5. Are runtime, seeder, repair, and verify all being updated together where the
   ownership model changes?
6. Is destructive cleanup isolated to `data-specs/` rather than welded into the
   runtime?
7. Does the PR close the full phase it claims, or does it still leave a hidden
   tail?

## 10. Contract Update Protocol

Any change to a product-form-parity verdict in §3.2 requires a design-doc PR,
not just an implementation PR.

Required update payload:

1. the exact object + gate whose verdict is changing
2. Salesforce baseline for that exact surface
3. repo evidence for the current owner model
4. declared delta, if any
5. next-tenant impact
6. verification plan across runtime, seeder, repair, and verify

Triggers that force a contract re-audit:

- any change to `standard_objects_spec.json` adding or removing a
  `sharingModel: ControlledByParent` object in this issue line
- Salesforce API version baseline bump for this repository
- runtime / contract divergence discovered in tests
- any PR that changes the owner model read path in:
  - `record_access.go`
  - `rls_filter.go`
  - `service.go`
  - `permission_seeder.go`
  - `permissions.go`
  - `verify_profile_permissions.go`

Ownership:

- architect review for `docs/design/permission-system/**` and this contract path
- implementation PRs touching the affected runtime or convergence files must
  cite this doc and state whether they do or do not change the contract
