# L0-C — Tooling DTO + SOQL/SOSL + neutral Query/Search boundary (execution plan)

Status: **APPROVED execution contract (architect-approved with required edits applied)** · Phase 2 step 3 · follows L0-B (closed).
Method: 6 parallel surface investigators (file:line evidence) → minimal-PR synthesis → adversarial completeness/minimality critique → orchestrator re-verified every load-bearing claim against `NexusCRM@a8c7a15e9` / `app-packs@2a34d56`. Every claim below is **evidence-bound**; where the adversary over-claimed, the re-check corrected it (noted inline).

> **Approval (architect) + required edits applied:** (1) PR-C3 gate honesty — build-dependent dialect goldens are enforced at the **PR-C4 pin-bump go-ci**, not app-packs PR time (which runs only the B6 hermetic seal); PR-C3 merges on local evidence + review. (2) The child-envelope dialect producer (carve **C10**, renamed from `C7'`) must be **additive + backward-compatible** with legacy already-wrapped engine output and must not double-wrap. (3) No machine-specific clone paths in this contract. (4) C3 — the app-blind engine must **stop knowing** the `__nx_semijoin_` token (remove it), not keep a token-literal assert; defer/ledger if removal can't be proven safe. (5) `WithToolingWriteTx` rename — verified internal-only (0 external callers) → direct rename, no alias. Plus: PR-C1's no-orphaned-helper proof promoted to a mandatory gate; PR-C2's swagger regen isolated to its own commit.

> **Post-approval correction (PR-C1 execution, architect-decided):** (a) **C2 dropped from L0-C** — `WithToolingWriteTx` is the public tip of a pervasive Flow "Tooling API" spelling (private `inToolingWriteTx` + ~18 Flow/RecordType CRUD callers + comments); a 2-site rename can't meet its death-condition, so the whole Flow-Tooling de-skin is deferred and PR-C1 ([#3318](https://github.com/pingcap-inc/NexusCRM/pull/3318)) ships as **pure deletion**. (b) **Seal markers narrowed to wire-only** — `displayNameField`/`object_api_name` are legitimate engine validation/storage concepts, not wire skin, so the PR-C1 death-condition + the C6 seal use only `"sobjects"`/`// Tooling API uses :id`/`calculatedFormula`.

---

## TL;DR — L0-C is small, L0-B-shaped

The SOQL/SOSL **language spine**, the top-level query/search **wire envelope**, and the SF **Tooling/Describe DTOs** are **already in L1** (`dialect-sf-crm` + `dialect-sf-crm-soql` + `backend/distributions/soqlexpr`); the neutral `contract.Metadata`/`Query`/`Search` seams already exist and are largely complete (verified, not trusted). L0-C is **cut the last few app-blind-L0 threads + de-skin + seal**, plus **one real (small) cross-repo query-skin carve** the first-pass missed. No engine rewrite (LIKE/TiFlash and the query engine are already noun-neutral).

**Lands in 4 PRs:** 2 NexusCRM-internal + 1 cross-repo pair (app-packs producer → NexusCRM pin-bump consumer).

---

## Corrections the deep-read + re-verification made

| Claim (first pass / adversary) | Verified reality (how) |
|---|---|
| "Query read path is already clean; no `totalSize`/`done` on the contract." | **WRONG → real leak.** `nexuscrm-analytics/query_service.go:921-925` (app-blind, `package analytics`, no build tag) mints the SF child-subquery envelope `parent[RelationshipName] = {"totalSize", "done": true, "records": childRecs}`. `neutralRecordsFromSObjects` (:721-738) strips only the **top-level** `"attributes"` key and copies every other key verbatim, so this **nested** SF envelope rides through the neutral contract intact. This is the one genuinely in-scope query-skin migration. |
| Adversary: "delete the 6-file dead MetadataHandler cluster (incl. `metadata_runtime_orchestration.go` + `fieldtypes_helper.go`)." | **Current-tree correction:** `metadata_runtime_orchestration.go` remains live through the neutral MetadataAuthoring contract. The former `GetAllFieldTypes` consumers have since moved to the shared field-type registry, so `fieldtypes_helper.go` is now caller-free and can be deleted with the four retired gin handlers. |
| C7 CreateTree is "L0-C work." | **OUT OF SCOPE (user decision).** CreateTree is on the *Records* contract (`contract.go:32`), not Query/Search → **deferred to L0-E** (records lifecycle). It is the only "real migration" the synthesis found, but it is not this surface. |
| "L1 re-wraps the nested child envelope from neutral records." | **No.** `dialect-sf-crm/query_projection.go:111 nestRelationshipFields` only builds *parent*-reference nesting; it does NOT rebuild the child-subquery `{totalSize,done,records}`. So neutralizing the engine requires L1 to add the re-wrap → the query-envelope carve is **cross-repo**. |
| `MaxSOSLLimit`/`DefaultSOSLLimit` are "L0-internal constants, rename/delete freely." | **WRONG → published cross-repo API.** Both are **exported** symbols of the standalone `github.com/nexuscrm/shared` module and are consumed by **`dialect-sf-crm/search.go:26-27`** (app-packs L1); `DefaultSOSLLimit` also has 4 `nexuscrm-persistence` sites. Renaming/deleting them in `shared` is a source-incompatible break that red-locks the dialect build — caught by Codex on PR-C2 ([#3321](https://github.com/pingcap-inc/NexusCRM/pull/3321)) after an in-NexusCRM-only grep missed the app-packs consumer. So the de-skin must ride the **C8 cross-repo wave** (update `shared` + the dialect consumer together under the pin-bump), NOT a standalone NexusCRM PR. |

---

## Evidence-bound surface findings (the durable diagnosis)

1. **Tooling/Describe DTOs — already L1; L0 residue is dead handlers (Tooling-name de-skin deferred).** Neutral seam complete: `shared/pkg/contract/contract.go:666 Metadata.Describe → *models.ObjectMetadata` (doc :663-665 "without dialect URLs or transport envelopes"); `MetadataAuthoring` (:736-758) neutral. All SF Tooling/Describe wire DTO lives in `dialect-sf-crm` (`describe_dtos.go`, `metadata_authoring.go`, `tooling_query.go`, `partition.go:716-754` routes). The app-blind L0 residue: the **4 dead gin handlers** (`metadata_{schema,field,handler,validation}_handler.go`) carrying `"sobjects"` envelope (`metadata_schema_handler.go:30`), camelCase DTOs (:122-133/:186-197), `displayNameField` (:200-213), `object_api_name` (`metadata_field_handler.go:33-37`), `calculatedFormula` (:110-113), `// Tooling API uses :id` (:232,292) — `NewMetadataHandler` has **0 callers**, no live router registration. Plus `WithToolingWriteTx` (`nexuscrm-metadata/metadata_flows.go:272`): live path, neutral mechanism, SF name — but the **public tip of a pervasive Flow "Tooling API" spelling** (private `inToolingWriteTx` + ~18 callers + comments), so its de-skin is **DEFERRED** out of L0-C (see Decisions / C2).
2. **SOQL — language fully in L1; one engine literal remains.** Zero `soql` in app-blind backend non-test (only `backend/distributions/soqlexpr` (L1-tagged) + `dialect-sf-crm-soql`). Residue: `nexuscrm-analytics/query_clause_fls.go:1587 isClauseKeyword()` hard-codes `"__nx_semijoin_"` (an L1 placeholder token) + SOQL-flavored comments in `shared/pkg/models/data.go`.
3. **SOSL / search language — fully L1.** SOSL parse + 5 route handlers (~1300 LOC) live in `dialect-sf-crm/search.go`; old backend handlers already deleted. Residue: stale generated swagger (`backend/docs/docs.go` still documents `/search`).
4. **Neutral Query engine — mostly clean; the nested-envelope leak (see Corrections).** Plus read-tolerance `attributes` shims (lower priority, several sites; `query_helpers.go:303`, `record_write_canonicalization.go:161`, `StampObjectType` at `models/data.go:98`) — these *tolerate* inbound SF `attributes`; not minters → out of L0-C except the one minter at `query_service.go:921`.
5. **Neutral Search engine — clean engine; SOSL field resolver mis-homed.** `shared/pkg/searchmetadata/search_fields.go ResolveSOSLSearchFields`/`scopedSOSLSearchFields`/`SearchAllDefaultFieldsDeclaration` (~250-300 LOC) encode SF SOSL semantics (`scope=="ALL"` == SF "IN ALL FIELDS") inside a **neutral** shared package → belongs in L1. The neutral classifier (`IsUserSearchableObject`/`IsSearchableField`) STAYS. `SearchSpec.SearchFields` (`queryspec/spec.go:568`) + json-tag-free `contract.SearchResult` (`contract.go:326`) already suffice for the move — no new search seam.

---

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

- **C7 (CreateTree neutral seam) → DEFERRED to L0-E** (records lifecycle). Not Query/Search.
- **C8 (SOSL search-field resolver relocation) → DEFERRED to its own carve.** It was originally slated to ride the same app-packs PR + pin-bump as C10, but the cross-repo wave shipped **C10-only** (PR-C3 [#284] @`79c8a66` + PR-C4). C8 is entangled: `shared/pkg/searchmetadata/search_fields.go` mixes the SOSL scope→fields resolver (L1 SF semantics) with a **neutral pack-fields registry** consumed by 8 `nexuscrm-controlplane/packs/*` files, so it needs a **function-level split**, not a file move — too risky to fold into the C10 pin-bump. Carved as its own follow-up (function-level demix → relocate only the SOSL half to the dialect, keep the neutral registry in `shared`).
- **C9 (`MaxSOSLLimit`/`DefaultSOSLLimit` const de-skin) → DROPPED.** Cosmetic. `DefaultSOSLLimit` is an **exported** symbol of the published `shared` module consumed by `dialect-sf-crm/search.go:26-27` (app-packs), so cross-repo-renaming it is a 3-step producer-first/pin-bump dance not worth the churn for a rename. Revisit at `shared` versioning (#3284), where an exported-symbol rename can ride a semver bump instead of a pin-choreography.
- **`toolingOnlyTables` write-gate (`nexuscrm-records/persistence_metadata_guards.go:96-144`, 12 call sites) → DEFERRED.** Neutral mechanism, SF spelling; risk-bearing (a missed call site un-gates metadata-table writes). Rename in a focused later pass behind a write-gate golden — NOT amid L0-C deletions.
- **Flow "Tooling API" spelling (`nexuscrm-metadata/metadata_flows.go`: `WithToolingWriteTx` + private `inToolingWriteTx` + ~18 Flow/RecordType CRUD callers + comments) → DEFERRED** (surfaced during PR-C1; the draft's C2 2-site rename was reverted). Neutral mechanism, pervasive SF spelling; it is the Flow authoring subsystem, not L0-C's Tooling-DTO surface → separate focused de-skin (L0-E-adjacent or standalone).
- **SemiJoin (`__nx_semijoin_`) de-skin + SOQL comment de-skin (C3 + C4) → DEFERRED follow-up** (PR-C2 trace; see C3). The app-blind L0 defensive branch (`query_clause_fls.go:1587`) + the SOQL-flavored `data.go` comments; a focused pass that either proves the cross-repo substitution invariant or replaces the token special-case with a neutral schema-field rule.
- **Stale SF `/search` in generated swagger (`backend/docs/docs.go`, C5) → DEFERRED follow-up** — needs a reproducible `swag` regen path (none in repo today); do it as a docs-regen pass, not a hand-edit of generated output.
- **Out of scope, do NOT absorb (Codex-overbroaden guard):** metadata-**deploy** (`metadata_deploy_contract.go:223` `models.SObject` + `:232 json:"done"`) = a sibling metadata-deploy carve; the read-tolerance `attributes` shims (`StampObjectType` et al.); CreateTree (L0-E); formula (L0-F).

---

## Canonical L0-C execution order — single source of truth

| # | PR | Repo | Prod change | Contains | Gate / death-condition |
|---|---|---|:--:|---|---|
| 1 | **PR-C1** — delete dead Tooling handlers (pure deletion) ✅ [#3318] | NexusCRM | no runtime (dead-code) | C1 delete the **4** dead gin handlers (`metadata_{schema,field,handler,validation}_handler.go` + their `_test.go`). *(Draft C2 `WithToolingWriteTx` rename **dropped/deferred** — see C2 below.)* | **GATE (mandatory pre-delete proof):** `NewMetadataHandler` has 0 live callers AND every helper the deleted handlers call retains a live non-handler caller (grep-proven, recorded in the PR) — only then delete. go-ci green; grep-assert 0 **wire-only markers** `"sobjects"`/`// Tooling API uses :id`/`calculatedFormula` in app-blind non-test (**NOT** `displayNameField`/`object_api_name` — those are legit engine validation/storage concepts) |
| 2 | **PR-C2** — SF Describe-envelope seal (C6 only) ✅ [#3321] | NexusCRM | no (test-only) | **C6** count-seal pinning the SF Describe wire envelope (`"sobjects"`, `// Tooling API uses :id`) to 0 over `{backend/internal, nexuscrm-analytics}`. *(C9 **moved to the C8 wave** — published-`shared`-API; C3/C4/C5 deferred — see per-carve.)* | go-ci green; seal at 0, scoped + excludes `*_test.go`/generated, adversarially planted |
| 3 | **PR-C3** — app-packs producer (dialect re-wraps child envelope) ✅ [#284] | app-packs | yes | **C10a only** — dialect builds the child-subquery `{totalSize,done,records}` re-wrap from neutral child records, **backward-compatible** (see C10). *(C8a SOSL resolver move **deferred** — see C8; the cross-repo wave is C10-only.)* | **app-packs PR-time = local evidence + review only.** Merged at `79c8a66` on full-dialect-test evidence + review; **the authoritative build-dependent dialect-golden gate is PR-C4's NexusCRM pin-bump `go-ci`, NOT app-packs PR CI.** Merged before PR-C4 ✅ |
| 4 | **PR-C4** — NexusCRM consumer + pin-bump (authoritative integration gate) ✅ | NexusCRM | yes | **C10b only** — engine `query_service.go:917-925` emits neutral child records (attaches `[]models.SObject` under the relationship name; no SF envelope); bump `OfficialAppPacksSourceRevision`→`79c8a66` (PR-C3); C6-(b) query seal forbidding `"totalSize"`/`"nextRecordsUrl"` minting in app-blind L0. *(C8b SOSL-resolver delete from `shared` **deferred**; C9 const de-skin **dropped** — see C8/C9.)* | **This is the source-of-truth gate**: go-ci green at bumped pin executes the build-dependent dialect goldens (C10 byte-parity — dialect re-wraps the engine's neutral slice → subquery goldens hold); query seal at 0 |

> **PR-minimality rationale.** C1+C2 are pure NexusCRM Tooling removal (no seam, no cross-repo) → cheapest PR, kept separate so the ~900-LOC deletion is reviewable and so PR-C2's seal runs *after* the tokens are gone. The cross-repo wave shipped **C10-only**: the child query-envelope re-wrap is the one cross-repo carve that landed as one app-packs producer PR (PR-C3 [#284]) + one NexusCRM pin-bump consumer PR (PR-C4). **C8** (SOSL resolver) was de-scoped from this wave — it is entangled (function-level split inside a `shared` file the controlplane also consumes), so it gets its own carve rather than being forced into the C10 pin-bump. **C9** (const de-skin) is dropped (cosmetic exported-symbol rename; revisit at `shared` versioning #3284). C3/C4/C5 remain deferred (see per-carve). The query seal that pins `"totalSize"`/`"nextRecordsUrl"` to 0 **must** live in PR-C4 because it cannot be green until the C10 engine flip lands.

---

## Per-carve detail (the *how*)

- **C1** — *Scope:* delete `backend/internal/interfaces/rest/metadata_{schema,field,handler,validation}_handler.go` + `_test.go`, and delete the now caller-free `fieldtypes_helper.go`. *Death-condition:* `NewMetadataHandler` and `GetAllFieldTypes` have no live callers or router registration; `go build`/`go test` green. `metadata_runtime_orchestration.go` stays because the neutral `metadata_authoring_contract.go` still consumes it.
- **C2 (`WithToolingWriteTx` rename) — DEFERRED out of L0-C.** PR-C1 execution found `WithToolingWriteTx` is the **public tip of a pervasive Flow "Tooling API" spelling** in `nexuscrm-metadata/metadata_flows.go`: a private `inToolingWriteTx` used by **~18 Flow/RecordType CRUD methods** plus comments (`// Flow Definition CRUD (Tooling API)`, `// Flow Version CRUD (Tooling API)`, `sparse Tooling PATCH`, …). Renaming the 2 public sites leaves the live path Tooling-named, so the original death-condition ("0 `Tooling` on the authoring path") **cannot be met by a small rename**. This is the **Flow authoring subsystem**, not L0-C's named Tooling-DTO/Query/Search surface → carved as a **separate focused Flow-"Tooling API" de-skin** (revisit during L0-E records/flow lifecycle, or as a standalone pass with a write-gate-preserving golden). Consequently **PR-C1 ships as pure deletion** ([#3318](https://github.com/pingcap-inc/NexusCRM/pull/3318)); the 2-site rename was reverted.
- **C3 — DEFERRED out of PR-C2 (safe removal not provable).** *Goal (unchanged):* the app-blind engine STOPS KNOWING the L1 placeholder token `__nx_semijoin_`. PR-C2 trace: the placeholder is produced ONLY in L1 (`dialect-sf-crm-soql/ast.go:216`) and substituted by `ResolveSemiJoins` (L1; called at `dialect-sf-crm/query.go:180,284`) **before dispatch**; the L0 branch at `query_clause_fls.go:1587` is a **defensive net** against an unsubstituted placeholder reaching the FLS collector (`validateQueryFormulaReadClause`, `query_clause_fls.go:21`). Proving it can NEVER fire needs a cross-repo dispatch-flow invariant that can't be established by reading — so per the contract's own rule (defer rather than claim "dead" on a guess) **C3 is deferred** to a focused SemiJoin de-skin pass (which can land the cross-repo invariant, or replace the special-case with a neutral "token is not a real field on the schema" rule). The trap stands: an assert that still branches on the `__nx_semijoin_` string does NOT satisfy the death-condition.
- **C4/C5 — DEFERRED.** C4 (de-skin `shared/pkg/models/data.go` SOQL comments) folds into the C3 SemiJoin de-skin (same subsystem). C5 (`swag` regen of `backend/docs/docs.go` dropping the stale SF `/search`) is deferred: the `/search` block is **stale generated content with no live `@Router` source annotation** and there is **no `swag` regen target in the repo**, so regen isn't reproducible and hand-editing a generated file is fragile — do it as a proper docs-regen pass. *Out of scope (unchanged):* the fuller OpenAPI L0/L1 split.
- **C6** — *Scope:* L0-C count-seal. Two markers/passes: (a) Tooling/SOQL **wire-only** markers (`"sobjects"`, `// Tooling API uses :id`, `__nx_semijoin_`) over `{rest, host, analytics}` — **NOT** `displayNameField`/`object_api_name`/`calculatedFormula` as bare tokens, which the engine legitimately uses for validation/storage (`displayNameField` validation in `metadata_schema.go`, `object_api_name` in `metadata_validations.go`); markers must be wire-specific so the seal does not flag neutral engine code; (b) query/search envelope (`totalSize`/`done`/`nextRecordsUrl` minting + SOSL markers) — the (b) half lands in PR-C4 after C10. *Scope pins:* exactly `{backend/internal/interfaces/rest, backend/internal/host, nexuscrm-analytics}`, exclude `*_test.go` + `backend/docs/docs.go`; use identifier-precise markers so `setupcmd/setup.go:175` "Deployment tooling" comment is not a false positive. Adversarially plant+revert.
- **C9 — MOVED to the C8 SOSL cross-repo wave (PR-C3/PR-C4).** `MaxSOSLLimit` + `DefaultSOSLLimit` are **exported** in the published `shared` module and consumed by **`dialect-sf-crm/search.go:26-27`** (app-packs), so renaming/deleting them standalone is a source-incompatible break (Codex BLOCK on PR-C2 [#3321](https://github.com/pingcap-inc/NexusCRM/pull/3321)). The de-skin (delete dead `MaxSOSLLimit`, rename `DefaultSOSLLimit`→`DefaultSearchResultLimit`) rides the C8 wave: app-packs PR-C3 updates `search.go` to the neutral name, then NexusCRM PR-C4 renames in `shared` + the 4 `nexuscrm-persistence` sites + bumps the pin — producer-first/pin-bump choreography. **Lesson:** changing an exported symbol in published `shared` requires a cross-repo consumer scan (app-packs + controlplane), not an in-NexusCRM grep.
- **C10 (child query envelope, cross-repo) — ✅ DONE (PR-C3 [#284] @`79c8a66` + PR-C4).** Renamed from the draft's `C7'` to avoid collision with the deferred **C7 (CreateTree)**. *Scope:* engine `query_service.go:917-925` stops minting `{totalSize,done,records}` and attaches the neutral `[]models.SObject` child-records slice under the relationship name; `dialect-sf-crm` (`query_projection.go wrapChildSubqueryEnvelope` + `query.go:468`) re-wraps the SF child-subquery envelope from the neutral slice. **Backward-compatibility (merge-order safety) — held:** PR-C3 (dialect) landed BEFORE PR-C4 (engine flip), and the dialect discriminates by shape and does **not** double-wrap: (a) legacy engine — child value already `map{records}` → pass through unchanged; (b) new engine — child value is a `[]models.SObject`/`[]contract.Record` slice → wrap. Against the pre-flip engine the re-wrap was a behavior-preserving no-op (byte-identical wire). *Repo-ness verified:* L1 `nestRelationshipFields` (`query_projection.go:111`) builds only parent-reference nesting, NOT the child envelope → genuinely cross-repo. *Death-condition (met):* app-blind L0 emits no `"totalSize"`/`"nextRecordsUrl"` (PR-C4 query seal pinned at 0); the dialect re-wrap re-establishes the SF subquery goldens at the bumped pin.
- **C8 (SOSL resolver, cross-repo) — DEFERRED to its own carve.** *Scope (unchanged goal):* move the SOSL scope→fields resolver (`ResolveSOSLSearchFields`/`scopedSOSLSearchFields`/`SearchAllDefaultFieldsDeclaration`) from `shared/pkg/searchmetadata` into `dialect-sf-crm`; L0 keeps the neutral classifier and consumes neutral `SearchSpec.SearchFields`. *Why de-scoped from the C10 wave:* `shared/pkg/searchmetadata/search_fields.go` **mixes** the SOSL resolver (L1 SF semantics) with a **neutral pack-fields registry** consumed by 8 `nexuscrm-controlplane/packs/*` files, so the carve is a **function-level split** of one file, not a clean file move — folding that into the C10 pin-bump would have coupled an unrelated controlplane-facing change to the engine flip. *Pre-req at execution:* trace the persisted `RuntimeSearchAllDefaultFields` reader (if any) before deleting from shared. *Death-condition:* `shared/pkg/searchmetadata` has 0 SOSL/`scope=="ALL"` semantics, the neutral pack-fields registry stays; one-consumer move verified (no second dialect planned).
- **C9 (`MaxSOSLLimit`/`DefaultSOSLLimit` const de-skin) — DROPPED.** Cosmetic exported-symbol rename in published `shared`, consumed cross-repo by `dialect-sf-crm/search.go:26-27`; a 3-step producer-first/pin-bump dance not worth it. Revisit at `shared` versioning (#3284), where it can ride a semver bump instead of pin choreography.

---

## Cross-repo protocol (same as L0-B)

- Operate on an **independent app-packs clone outside the NexusCRM working tree**; do NOT edit the gitignored nested `NexusCRM/nexuscrm-app-packs` snapshot (a stale build-extraction copy). Machine-specific clone paths belong in a personal worklog, not this contract.
- **Producer-first + backward-compatible (C10-only wave):** PR-C3 (app-packs gains the child-envelope re-wrap) merged **before** PR-C4 (NexusCRM flips the engine to the neutral child slice + bumps `OfficialAppPacksSourceRevision`→`79c8a66`). The dialect's PR-C3 changes were **additive and backward-compatible** with the *pre-flip* engine output (legacy already-wrapped child envelope → passed through unchanged, no double-wrap), so there was no broken window between the two merges (see C10). The engine flip and the pin-bump land **atomically in PR-C4** — the same PR that flips the engine to emit the neutral slice also advances the pin so the dialect-with-wrap is live, so there is never a window where L0 emits the neutral slice but the pinned dialect doesn't re-wrap it. (C8 SOSL-resolver move was de-scoped from this wave → its own carve.)
- **Gate honesty (binding — the PR-0b lesson):** app-packs PR CI does **not** run the build-dependent dialect goldens; only the B6 hermetic `dialect-go-seals` workflow runs at app-packs PR time. The build-dependent goldens execute **only** in NexusCRM `go-ci` at the **PR-C4 pin-bump**, which is therefore the authoritative integration gate. **PR-C3 merges on local full-dialect-test evidence + review, never on "app-packs PR is green."** Do not add dual-checkout/full Go CI to app-packs in L0-C — that is the deferred `shared`-versioning spike (#3284).

---

## Binding rules

1. **Evidence-bound only** — every carve cites file:line + how verified; the corrected claims above must never regress to the over-claimed forms.
2. **Don't over-broaden** — metadata-deploy, CreateTree, `toolingOnlyTables`, the read-tolerance `attributes` shims, and formula are explicitly OUT. If a "while I'm here" edit isn't in the carve table, it's a different track.
3. **Seal after the cut** — a seal's pinned count must be 0 at its own merge; the query/search envelope seal therefore rides PR-C4, not PR-C2.
4. **Verify deletions don't orphan a live helper** before deleting (the dead-cluster over-claim is the cautionary tale).
