# L0-B — Cut visible Salesforce wire skin (execution plan)

Status: **✅ COMPLETE — all L0-B carves landed; main `go-ci` green (2026-06-30).** Phase 2 step 2.
Method: multi-agent deep read of the full carve subsystems + dependency tracing +
adversarial cross-check. Every conclusion below is **evidence-bound** — file path,
line, and how it was verified.

## ✅ L0-B CLOSED (2026-06-30) — closeout

This plan is the L0-B SSoT and now records **completed** work. Do **not** re-plan B6 / PR-6 / any L0-B carve as future work — the track is closed; the next track is L0-C.

- **PR-6 complete** ([#3300](https://github.com/pingcap-inc/NexusCRM/pull/3300)). The L0 `apierr.SF*` Salesforce error vocabulary was deleted from app-blind L0 (kernel `apierr`) and relocated to the CRM/L1 distribution. A real forbidden-symbol seal (`TestAppBlindL0DoesNotReferenceSalesforceErrorVocabulary`, count-seal pinned at 0, also scanning `nexuscrm-records`/`nexuscrm-metadata`) blocks regrowth. The remaining Salesforce error projection (`WireInvalidReference→INVALID_CROSS_REFERENCE_KEY`, etc.) is owned by the L1 dialect (`dialect-sf-crm/wire`); **app-blind L0 no longer owns SF error vocabulary.**
- **B6 complete** (app-packs [#282](https://github.com/pingcap-inc/nexuscrm-app-packs/pull/282) + pin-bump [#3303](https://github.com/pingcap-inc/NexusCRM/pull/3303)). The `database/sql` runtime ratchet landed in `dialect-sf-crm/seals/runtime_database_sql_seal_test.go` — a file-scan that ledgers **exactly one** runtime exception, `content_distribution_resolver.go` (the irreducibly-Salesforce ContentDistribution lookup; auth stays neutral via `Files.ResolveAuthorizedRecordAttachmentSource`), and excludes the standalone `tools/` CLI by exact root-relative path. Enforced **at app-packs PR time** (hermetic stdlib-only `dialect-go-seals` workflow — green) **and** in NexusCRM `go-ci` (green at the pin-bump). The `RowQuerier` least-privilege port was deliberately deferred (not B6).
- **L0-B track complete:** B1a ([#3285](https://github.com/pingcap-inc/NexusCRM/pull/3285)) · B1b ([#3287](https://github.com/pingcap-inc/NexusCRM/pull/3287)) · B1c ([#3288](https://github.com/pingcap-inc/NexusCRM/pull/3288)) · B2a ([#3291](https://github.com/pingcap-inc/NexusCRM/pull/3291)) · B2b ([#3292](https://github.com/pingcap-inc/NexusCRM/pull/3292)/[#3293](https://github.com/pingcap-inc/NexusCRM/pull/3293)/[#3297](https://github.com/pingcap-inc/NexusCRM/pull/3297)) · B3 negative guard (no-op, verified) · PR-6 ([#3300](https://github.com/pingcap-inc/NexusCRM/pull/3300)) · B6 (#282 + [#3303](https://github.com/pingcap-inc/NexusCRM/pull/3303)). main `go-ci` is **green**.
- **Closeout note — the post-#3293 red was independent CI drift, NOT a B6 code failure.** go-ci's sequential `build-test-seal` job had been red since #3293 at the `dialect-sf-crm-soql` step, which masked every downstream step (including B6's own gate). Two pre-existing drifts were fixed: the `nexuscrm-contract v0.1.0→v0.1.1` dialect-pin skew (app-packs [#283](https://github.com/pingcap-inc/nexuscrm-app-packs/pull/283) + pin-bump [#3305](https://github.com/pingcap-inc/NexusCRM/pull/3305)) and an un-tidied `backend/go.mod` (missing `kylelemons/godebug` indirect; [#3306](https://github.com/pingcap-inc/NexusCRM/pull/3306)). With both fixed, **main `go-ci` is fully green for the first time since #3293.**

This document has **two layers, kept separate on purpose**:
1. **Deep-read evidence** — the technical diagnosis (Corrections, the five assertions,
   residual quick-reads, the neutral seams). This is the durable value.
2. **Execution contract** — a *single* canonical order table + per-carve detail +
   cross-repo protocol + gates. The table is the **only** authoritative order; no other
   section restates it.

---

## TL;DR — the deep read shrank L0-B's risk picture

The Salesforce v63 wire skin is **already ~90% in the L1 dialect**
(`nexuscrm-app-packs/dialects/dialect-sf-crm`). L0-B is **not a big migration** —
it is *cutting the last few app-blind-L0 wire-skin threads* and *adding two small
neutral seams*. The real residue is concentrated in **B1 (error envelope)**, and
even that is mostly dead-code deletion + one live test-mode twin.

---

## Corrections the deep read made to the first-pass plan

| First-pass claim | Verified reality (how) |
|---|---|
| B3: ~3 of 12 L0 files *produce* the SObject envelope | **WRONG → B3 is a NO-OP.** ZERO L0 files mint `attributes.type`/`url`. The 14 `StampObjectType` callers write only the **neutral** `_objectType`; the 5 that also touch `"attributes"` are carriers/validators/comments, not minters (evidence below). Minting lives **only** in the dialect. |
| B4: the dialect reaches into L0 for shape decisions; contract incomplete | **MOSTLY WRONG → records/query/search contract is complete.** Every SF-shape decision reads the neutral `models.ObjectMetadata` struct from `contract.Metadata.Describe`; FLS comes from neutral `contract.Policy.Access`. The **only** genuine raw reach-in is `content_distribution_resolver.go`'s `*sql.DB`. |
| B2: 5 hardcoding sites / 4 concerns in app-blind L0 | **CORRECTED → only TWO app-blind L0 literals remain.** The rest are in `backend/distributions/crm` (= L1, correct side, already dialect-importing). |
| B1: dialect wire is a complete superset; 2 L0 callers | **CORRECTED.** The *mapping/projector* is a verbatim duplicate, but the SFCode *vocabulary* is not yet a superset; and there are **more** L0 references — incl. a **dead** `bulk_ingest.go` twin and a **live** `helpers.go` test-mode projection. |

---

## The five high-risk assertions — and exactly how each was verified

1. **`bulk_ingest.go` is a dead legacy twin (not a live route).**
   - `NewBulkHandler` has **no non-test caller**: `grep -rn 'NewBulkHandler' --include='*.go'` returns only the definition at `backend/internal/interfaces/rest/bulk_handler.go:25-26`.
   - `BulkHandler.RegisterRoutes` is **never invoked in production wiring** (no caller found); its only construction is in `bulk_ingest_listview_test.go:36`.
   - The **live** bulk path is the neutral application service `backend/internal/application/services/bulk_ingest_contract.go` (exists; `grep -c 'SFError|apierr.SF|attributes.type'` = **0** SF-skin).
   - ⇒ classification: **dead-code**. Deletion is a no-risk removal — **but gated by the B1a reachability proof (Rule 2).**

2. **B3 has no L0 envelope producer.**
   - All 14 non-test `StampObjectType` callers: `nexuscrm-analytics/{query_relationship_projection,query_helpers}.go`, `nexuscrm-automation/{executor_record_lookup,executor_reference_helpers,approval_notifications,connector_push_source_records,flow_trigger_listener,flow_executor,executor_flow_formula_helpers,connector_push_associations,executor_actions}.go`, `nexuscrm-metadata/validation_formula_fetcher.go`, `nexuscrm-records/persistence_bulk_ops.go`, `shared/pkg/models/data.go`.
   - Grep for envelope minting (`attributes\.type|attributes\.url|["attributes"]\s*=|"url"\s*:`) across those files: the only `"attributes"` writes are **carriers** — `query_helpers.go:304 next["attributes"]=attrs` (preserves an *inbound* nested map) and `flow_trigger_listener.go:342 clone["attributes"]=attrs` (deep-copy of an inbound clone, #1390 leak fix); plus a **validator** `persistence_bulk_ops.go:334` (reads inbound `attributes.type`) and two **comments** (`executor_reference_helpers.go:25`, `data.go:23,50`). **None mint `type`/`url`.**
   - Minting is dialect-only: `nexuscrm-app-packs/dialects/dialect-sf-crm/{query_projection,query_history,records}.go`.
   - ⇒ **B3 ships no production change** — it is encoded as a *negative guard* (Rule 3).

3. **`content_distribution_resolver.go` is the only raw-SQL shape leak.**
   - `nexuscrm-app-packs/dialects/dialect-sf-crm/content_distribution_resolver.go:18` holds a `db *sql.DB`; `:21 NewContentDistributionFileLookup(db *sql.DB) contract.RecordAttachmentSourceLookup` does a raw SELECT over the SF `ContentDistribution`/`IdBody` source.
   - The neutral seam **already exists**: `shared/pkg/contract/contract.go:229 RecordAttachmentSourceLookup` (doc: "implemented by app/dialect code that knows how"), and backend owns the neutral auth `:272 ResolveAuthorizedRecordAttachmentSource(...)`.
   - **Seal gap:** `nexuscrm-app-packs/dialects/dialect-sf-crm/seal_test.go:27-28` forbids only `github.com/nexuscrm/backend` and `gin` — **not `database/sql`**, so this `*sql.DB` passes the seal. ⇒ B6 tightens the seal.
   - Goldens exist: `content_distribution_resolver_test.go`, `content_distribution_decorator_test.go`.

4. **Only two app-blind-L0 `/services/data` literals remain.**
   - `nexuscrm-kernel/pkg/oauthroutes/patterns.go:8 const serviceDataPrefix = "/services/data/"` (OAuth scope route-family validator).
   - `nexuscrm-kernel/pkg/apierr/domain_mapping.go:97 standardActionEndpointV63()` builds `/services/data/vXX/actions/standard/...` for v63 error-message URLs.
   - Everything else (`backend/distributions/crm/runtime_route_surfaces.go:164`, `backend/distributions/crm/product.go:98 buildSalesforceUserInfoClaims`) is under `//go:build crm` = the CRM distribution (**L1**, already dialect-importing) — correct side for the `/services/data` literal concern (B2). *Note: `:164` is `Code: apierr.SFCodeMissingOAuthToken`, which **PR-6** repoints to the dialect symbol when L0 `errors.go` is deleted — a separate apierr-symbol relocation, not a B2 carve.*

5. **The dialect already holds `attributes.type`/`url` minting.**
   - `nexuscrm-app-packs/dialects/dialect-sf-crm/query_projection.go` (`projectRecordToV63`, `recordAttributes`, `nestRelationshipFields`), `query_history.go`, `records.go` (`stampSObjectRecordResponseAttributes`). Backend egress strips it: `nexuscrm-analytics/query_service.go:726-733 neutralRecordsFromSObjects`.

---

## Residual quick-reads (resolved, with evidence)

| # | Question | Result (file:line) |
|---|---|---|
| 1 | Do any `StampObjectType` callers mint the envelope? | **No** — verified above (assertion 2). |
| 2 | `service_manager_init.go` (the `contract.Deps` root) bakes SF shape? | **No** — sole SF token is a comment `backend/internal/application/services/service_manager_init.go:225 "// …v63"`. |
| 3 | Does the content-distribution `*sql.DB` path have a golden? | **Yes** — `dialect-sf-crm/content_distribution_resolver_test.go` (+ `…_decorator_test.go`). |
| 4 | Does `apihostmiddleware.AuthFailure` carry a dialect-supplied Code? | **Partly** — `AuthFailure.Code` is injectable, but `backend/internal/host/adapters/apihost/middleware/auth.go:42` hardcodes the SF default `apierr.SFCodeInvalidSessionID` and emits `apierr.SFErrorResponse` directly. ⇒ auth-code de-hardcoding is **real but low-priority / separable** (B1-adjacent follow-up, not core B1). |
| 5 | Live `bulk_ingest_contract.go` emits only neutral? | **Yes** — 0 SF-skin hits (assertion 1). |

---

## The neutral seams L0-B adds (the only two)

- **`shared/pkg/dialect.PartitionMetadata`** (B2) — owned by the dialect, consumed by L0; drains the two kernel literals. Names must stay product-neutral (Rule 5): `RouteFamilyPrefix() string` and a formatter **callback** `FormatActionEndpoint(apiVersion, action string) string`. L0 holds only the callback — **not** the "standard-action" noun — and must **not** hardcode `/services/data`, `v63`, or any Salesforce-standard-action URL semantics.
- **`contract.RecordAttachmentSourceLookup`** (B6) — already exists (`contract.go:229`); B6 relocates the raw `*sql.DB` behind it / or moves the SQL to a neutral provider, and tightens the dialect seal to forbid `database/sql`.

For **records / query / search / envelope**: **no neutral delta needed** — the existing `contract.Records/Query/Metadata/Policy` are complete.

---

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

This table is the **only** authoritative declaration of order, repo, prod-change, and the
gate each step must clear. Every other section references it and does **not** restate it.

> **All rows below are ✅ landed** (the `Status` column records where). The order/gate columns are retained as the historical execution contract.

| # | Step | Repo | Prod change | Gate / required before it merges | Source-of-truth CI | Status |
|---|---|---|:--:|---|---|---|
| 0 | **L0-A-lite** | NexusCRM | no | ✅ merged (`ef00fc106`) | go-ci green | ✅ `ef00fc106` (#3273) |
| 1 | **PR-0a** — kernel goldens + this doc | NexusCRM | no | Gate A (L0-A-lite merged) | NexusCRM `go-ci` incl. the `nexuscrm-kernel` build/vet/test step | ✅ #3277 (+ gate fixes #3278/#3283) |
| 2 | **PR-0b** — app-packs dialect goldens | app-packs | no | **branched from the *current pinned* rev, not `main`** (Branch-base rule) | stores tests only; app-packs CI is frontend-only → **NOT** the execution gate | ✅ app-packs #279 |
| 3 | **PR-0c** — NexusCRM pin-bump to PR-0b's commit | NexusCRM | **no** (goldens-only, because PR-0b is pin-based) | PR-0b merged | **NexusCRM `go-ci` `dialect-*` executes PR-0b's goldens ← the carve-authorization gate** | ✅ #3286 |
| 4 | **B1a** — delete dead bulk twin | NexusCRM | no runtime | reachability proof (Rule 2) **+ a backend-rest `apierr.SF*` anti-regrowth seal** | `go build`/`go test` + that seal | ✅ #3285 |
| 5 | **B1b** — cut L0 domain→v63 projection | NexusCRM | yes | **PR-0c green** | go-ci | ✅ #3287 |
| 6 | **B1c** — neutralize generic-500 | NexusCRM | yes | **PR-0c green** | go-ci | ✅ #3288 |
| 7 | **B2** — drain the 2 kernel literals | both | yes | PR-0c green **+ its own app-packs PR + pin-bump** | go-ci | ✅ B2a #3291 · B2b #3292/#3293/#3297 |
| 8 | **B6** — relocate content-distribution SQL | both | yes | PR-0c green **+ its own app-packs PR + pin-bump** | go-ci (+ `database/sql` seal, enforced at that pin-bump) | ✅ app-packs #282 + pin #3303 |
| 9 | **PR-6** — delete L0 `apierr.SF*` half | both | yes | PR-0c green **+ Gate B `apierr.SF*` symbol seal exists & blocks first + its own app-packs PR + pin-bump** | go-ci + seal | ✅ #3300 |
| — | **B3** — negative guard | — | no | no PR — verify-only (Rule 3) | SObject-envelope count-seal stays/shrinks | ✅ verified (no-op) |

> **Catch-up rule (don't fold real source into the goldens path).** If app-packs `main` is
> ahead of the pinned rev with *real* source (e.g. the current pin lags `main` by dialect
> commits), that catch-up is a **separate production pin-bump** (`prod-change: yes`, full
> integration gate), done independently. It must **not** ride in on PR-0b/PR-0c — that is
> what keeps PR-0c genuinely goldens-only. In practice: do the catch-up **first** so the current
> pin equals a `main` commit; then PR-0b branches from it and lands its goldens on `main`, keeping
> PR-0c's pinned commit on `main` lineage (the pin must stay main-reachable).

> **B1a placement.** B1a sits at #4 only for a simple linear order; its real gate is its own
> reachability proof + anti-regrowth seal, not PR-0c. If you want it to run in parallel after
> PR-0a, do that in its own PR that self-justifies — **do not** add the parallel exception to a
> doc-correction PR.

### PR-0 — the 4 byte-goldens to write first (capture current bytes; freeze)
1. **(PR-0b, dialect)** table-golden for `dialect-sf-crm/wire.SFErrorFromDomainForVersion` — all 7 `WireConditionKind`s → exact v63 `errorCode`+`status`+`fields` (mirrors `nexuscrm-kernel/pkg/apierr/domain_mapping_test.go`). *Blocks B1b + PR-6.*
2. **(PR-0b, dialect)** `mustJSON` byte-golden for `DescribeGlobal/Object/SystemFile` success envelopes. *Reserved — written now to freeze current Describe bytes; no L0-B step consumes it.*
3. **(kernel = PR-0a, landed; dialect copy = PR-0b)** byte-golden for `CreateFromSourceGuardMessageV63` / `standardActionEndpointV63` URL + the no-formatter fallback. *Blocks B2.*
4. **(PR-0a, kernel)** `oauthroutes/patterns_test.go` empty-prefix (non-family pass-through) case. *Blocks B2 parameterization.*

### Per-carve execution detail (the *how* — does not restate the table's order/gates)

> **All carves below are ✅ landed** (see the Status column + the closeout block for PRs). The text is the historical plan; where the as-built diverged it is noted — notably **B6**.

- **B1a** — *Scope:* remove `bulk_ingest.go` + `bulk_handler.go` + their tests. *Death-condition:* reachability proof (Rule 2) + `go build`/`go test` green **+ a forbidden-symbol grep/seal over `backend/internal/interfaces/rest` for `apierr.SF*`** (it deletes 24 live `apierr.SF*` emissions there; boundarygate does **not** scan `backend/`, so without this seal the skin could regrow).
- **B1b** — *Scope:* remove `helpers.go:76-85` TestMode branch (+ dead `SalesforceAPIError` arm); make `RequireParam` (`helpers.go:117`) return a neutral `DomainWireError`. *Pre-req golden:* PR-0 #1 + `domain_wire_error_golden_test.go` byte-identical. *Death-condition:* no L0 non-test caller of `SFErrorFromDomainForVersion`.
- **B1c** — *Scope:* replace `error_handler.go` `apierr.SFGenericInternalErrorResponse()` with the injected dialect `V63InternalErrorProjector` (exists); re-point the 10 `domain_wire_error_golden_test.go` expectations. *Death-condition:* global middleware emits no `apierr.SF*` literal.
- **B2** — *Scope:* add `PartitionMetadata`; parameterize `oauthroutes` on prefix; move `standardActionEndpointV63` to a dialect formatter. *Pre-req golden:* PR-0 #3 (both copies) + PR-0 #4. *Death-condition:* no `/services/data` literal in app-blind L0 (`oauthroutes`, `apierr`).
- **B6** — ✅ DONE (app-packs [#282](https://github.com/pingcap-inc/nexuscrm-app-packs/pull/282) + pin [#3303](https://github.com/pingcap-inc/NexusCRM/pull/3303)). *As-built (diverged from the original scope):* the raw `*sql.DB` was **kept**, not moved behind a `RowQuerier` — the ContentDistribution lookup is irreducibly Salesforce-schema-specific and stays in L1 (auth remains neutral downstream via `Files.ResolveAuthorizedRecordAttachmentSource`), and a neutral row-exec port was deferred as it would only relocate the same SQL. Instead the `database/sql` access was **ledgered as exactly one runtime exception** (`content_distribution_resolver.go`) and ratcheted by a new **file-scan** seal in its own **stdlib-only package** `dialect-sf-crm/seals/runtime_database_sql_seal_test.go` (NOT `seal_test.go`; stdlib-only so it runs hermetically at app-packs PR time, where the module can't build standalone via `replace ../../../shared`). The `tools/` CLI is excluded by exact root-relative path. *Death-condition met:* the dialect runtime has exactly one ledgered `database/sql` importer; a new runtime importer fails CI (proven adversarially).
- **PR-6** — *Scope:* app-packs side (merges first) adds `SFCodeMissingOAuthToken` + relocated `SFCode*` vocab to the dialect `wire`; NexusCRM side deletes `errors.go` SF surface + `domain_mapping.go` (+ tests) and repoints `runtime_route_surfaces.go:164`. *Death-condition:* L0 `apierr` has no `SF*` symbol (enforced by the Gate B seal).
- **B3** — no PR; the SObject-envelope count-seal stays/shrinks; *death-condition:* no L0 envelope minter (Rule 3).

---

## Cross-repo execution protocol (L0-B is a two-repo carve)

L0-B is **not** a single-repo PR series. The dialect (`dialect-sf-crm`) lives in the separate
**`nexuscrm-app-packs`** repo (L1), checked out under `NexusCRM/nexuscrm-app-packs/` and
**gitignored** by NexusCRM — that nested checkout is only the distribution-build extraction,
never the place to edit. Treat `nexuscrm-app-packs` as its own repo with its own PRs.

- **Source of truth for the dialect rev** is NexusCRM `main`'s `OfficialAppPacksSourceRevision`
  (`backend/distributions/appsource/source.go`), not any local snapshot.
- **Operate on an independent app-packs clone outside the NexusCRM working tree.** Its
  `dialects/*/go.mod replace ../../../shared` resolves to NexusCRM `shared`; verify with
  `go build ./...` in `dialects/*`. (Goldens-first surfaced both the two-repo structure *and* a
  stale nested checkout before any line moved — freeze the battlefield first.)
- **Branch-base rule (scope, not just compilability).** A **goldens-only / zero-prod-change**
  PR (PR-0b) **must branch from the pinned revision** so its pin-bump carries *only* the new test
  files. Branching from `main` would make the pin-bump advance every commit `main` is ahead of
  the pin (real dialect source), violating Rule 4 — compilability of `main` is necessary but
  **not** sufficient. A deliberate **production-carve** PR may branch from `main`, and its
  pin-bump is `prod-change: yes` under the full integration gate. Any `main`-ahead commits are a
  **separate catch-up pin-bump** (Catch-up rule), never folded into PR-0b/PR-0c.
- **Pin-bump choreography for a dialect *production* carve:** the app-packs PR merges **first**;
  then a NexusCRM PR bumps `OfficialAppPacksSourceRevision` and runs the full integration gate
  (`build-test-seal` + distribution build + `source-backed-runtime-gate`).
- **Where the dialect goldens execute (claim==enforcement).** `nexuscrm-app-packs` has **no Go
  CI** — only a frontend npm workflow. The dialect Go suites run **in NexusCRM** `go-ci`
  (`dialect-*` build/vet/test) at the pinned rev. So a goldens-only app-packs PR going "green" is
  frontend-only and does **not** prove the goldens ran; they are first *executed* at the pin-bump
  (PR-0c). The kernel goldens (PR-0a) are CI-executed by the `nexuscrm-kernel` go-ci step (added
  with this plan); they were a latent gap until that step existed.

### Carve authorization
> **L0-B production carve is NOT authorized until the dialect goldens have been EXECUTED green by
> NexusCRM `go-ci`** — which happens only after a NexusCRM pin-bump (PR-0c) advances
> `OfficialAppPacksSourceRevision` to a rev containing them (table row 3). PR-0b being green in
> `nexuscrm-app-packs` is necessary but **not** sufficient (app-packs CI is frontend-only). The
> wire surface a cut moves must be byte-pinned **and the pin advanced so `go-ci` has run that
> pin's goldens** first.

---

## Binding rules (do not violate)

1. **Evidence-bound only.** Every claim carries file:line + how-verified. The five high-risk assertions must never be cited as just "verified."
2. **B1a needs a production-reachability proof before deletion.** Re-confirm at execution time: no production route registration to `BulkHandler`; no DI/service-manager wiring into a live path; no non-test direct caller; the live BulkService path is `bulk_ingest_contract.go` (neutral); `go build`/`go test ./...` green **after** deletion **+ the backend-rest `apierr.SF*` anti-regrowth seal is in place** (boundarygate is N/A — it does not scan `backend/`). If any check fails, **downgrade B1a to "detach legacy twin behind the seal"**, do not delete.
3. **B3 is a negative guard, not a verbal conclusion.** L0 may *carry* an inbound `attributes` map and *tolerate* legacy `attributes.type`; L0 must **not mint** the v63 envelope (`attributes.type`/`url`) — minting stays dialect-only. Aligns with L0-A-lite's `TestSObjectEnvelopeShapeStaysLedgered`.
4. **PR-0a / PR-0b are zero production behavior change.** Only tests / fixtures / docs / harmless test helpers. **No** route rewire, error-projector rewire, interface delta, dead-code deletion, or SQL-seam extraction. (PR-0c is a goldens-only pin advance — `prod-change: no` *by construction* because PR-0b is pin-based; table row 3.)
5. **B2 interface naming stays product-neutral.** L0 must not hardcode `/services/data`, `v63`, or Salesforce-standard-action URL semantics. The dialect supplies the route family / endpoint formatter; L0 consumes neutral route metadata or a formatter callback only.

## Merge gates

- **Gate A — L0-A-lite before PR-0a/PR-0b.** ✅ satisfied: L0-A-lite merged to `main` as `ef00fc106` (#3273); its post-merge `go-ci` is `completed/success`.
- **Gate B — a real `apierr.SF*` forbidden-symbol seal before PR-6.** boundarygate **does not** prevent `apierr.SF*` from returning today — it enforces module-edge bans, a `database/sql`|`net/http` contract-leak check, and a no-growth ratchet of dialect *tokens* (`salesforce|soql|sosl|sobject|hubspot|opportunity|tooling`); none of those is an `apierr.SF*` symbol check. So **PR-6 must first add a real forbidden-symbol seal for L0 `apierr.SF*`** (implemented inside boundarygate, or as a separate CI test). Only after that seal is *blocking* may `errors.go` / `domain_mapping.go` be deleted. (The same seal should cover `backend/internal/interfaces/rest` so it also closes B1a's regrowth gap.)
- **Gate C — PR-0c before any dialect-golden-dependent carve.** Per table rows 5-9, B1b/B1c/B2/B6/PR-6 must not land until PR-0c has merged and its `go-ci` `dialect-*` run is green. `go-ci` runs only on push-to-`main`, so this is a **reviewer-enforced process gate**, not a required status check. B1a is exempt (row 4).

---

## The point

L0-B is the keystone carve, but the deep read de-risked it: **no big move, just cut the last
app-blind-L0 wire-skin threads (mostly dead/test-mode) + add two small neutral seams**, each
behind a byte-golden and a death-condition. Keep the PRs small; never move a wire surface that
isn't byte-pinned first; and never let a goldens step smuggle in production source.
