# Repo-split — status & sequencing

This repo is being split by **release boundary** (5 L0 repos + 1 L1 + 1 L2). Full design lives outside the repo with the architect; this file tracks in-repo status and the deliberate interim states so reviewers aren't surprised.

## PR sequence

| PR | Scope | Status |
|---|---|---|
| PR0 | `tools/boundarygate` module-boundary gate + `docs/architecture/boundary-ledger.md` | ✅ done |
| PR1 | Contract quarantine — evict `database/sql`/`net/http` out of `nexuscrm-contract` into `nexuscrm-db`; contract runtime-pure, hard-enforced | ✅ done; source now lives in `pingcap-inc/nexuscrm-contract@v0.1.0` |
| **PR2** | **Sever the test-only `controlplane → backend` cycle** | ✅ done |
| PR2b | Deduplicate the bootstrap implementation (see below) | ✅ done |
| **PR3** | **Publish `nexuscrm-controlplane`; pin NexusCRM to the published module** | ✅ done; CI-confirmed in run `28291436501`; publish hygiene advanced to `v0.1.1` |
| PR4 | Collapse `nexuscrm-apihost` wrapper | ✅ done; external transport published as `nexuscrm-api-host@v0.1.0`, backend glue moved under `backend/internal/host/adapters/apihost` |
| P1-E | Final published-module pins + standalone repo hygiene | ✅ done locally; all published L0 deps are real tags |
| L0-A…L0-F | finite Salesforce-derived L0/L1 boundary roadmap | closed as scoped 2026-07-02 — each candidate has an explicit landed, non-carve, retained-compatibility, or deferred disposition in `docs/refactor/l0-l1-boundary/salesforce-derived-l0-boundary-ledger.md`; this row is deliberately not named `PR5`, because ledger `PR5` means only the L0-E component-registry candidate |
| PR6 | internal-package convergence (`foundation`/`engine`/`governance`/`host`) | ⏸ needs a design decision — the package→target mapping lives out-of-repo with the architect; not yet a scoped task |
| ~~PR7~~ | ~~single root module / delete `go.work`~~ | ❌ **rejected (2026-07-02 recon)** — merging the intra-repo modules into one `go.mod` **obviates boundarygate**: its SCC-ratchet, forbidden-edge, and contract-runtime-leak checks reason over cross-module `require` edges (`tools/boundarygate/modgraph.go`), so a single module leaves 0 first-party edges and the accepted engine-SCC invariant silently dissolves. The multi-module split is the **permanent L0/L1 enforcement substrate**, not leftover scaffolding. (`delete go.work` in isolation is a trivial, low-value, separable option — CI is already `GOWORK=off`.) |

## PR1 remote — contract source of truth

`nexuscrm-contract` has been published as private module
`github.com/pingcap-inc/nexuscrm-contract v0.1.0`. NexusCRM now consumes it as a
versioned dependency; the old in-repo `nexuscrm-contract/` source directory was
removed so the published module is the single editable source of truth.

## PR2 — backend-free severance

PR2 banked backend-free severance first; PR2b then removed the temporary
bootstrap duplication.

**Current status:**
- `controlplane` is **backend-free** (zero `backend` imports anywhere, incl. test files).
- boundarygate **Forbidden-edge = 0** (the `controlplane → backend` edge is hard-enforced gone).
- focused local gates are green (backend build, backend boundary tests, backend bootstrap tests, controlplane/bootstrapimpl tests, boundarygate).
- The 6 LOW seams were truly relocated into `nexuscrm-db` (`pkg/bootstate`, `pkg/connectorreaders`); `backend/internal/bootstrap` keeps thin aliases.
- The HEAVY bootstrap implementation (schema provisioning, field-provenance init, autonumber seeding, assertions, schema readiness, managed relationship metadata, etc.) now has a single production source in `controlplane/bootstrapimpl`; `backend/internal/bootstrap` keeps exported aliases for backend callers.

## PR2b — bootstrap relocation & dedup

- The duplicated non-test heavy files were removed from `backend/internal/bootstrap`.
- `backend/internal/bootstrap/bootstrapimpl_aliases.go` aliases exported API to `github.com/pingcap-inc/nexuscrm-controlplane/bootstrapimpl`.
- Eligible white-box tests moved into the published controlplane module; backend-only tests stay in `backend/internal/bootstrap`.
- Structure tests now read the current owner files via `go list -m github.com/pingcap-inc/nexuscrm-controlplane` instead of old duplicated backend paths.

## PR3 — controlplane publish & pin

`nexuscrm-controlplane` has been published as private module
`github.com/pingcap-inc/nexuscrm-controlplane v0.1.1`. NexusCRM now consumes it
as a versioned dependency; the old in-repo `controlplane/` source directory was
removed so the published module is the single editable source of truth.

**Current status:**
- `go.work` no longer includes `./controlplane`.
- `backend/go.mod` and `backend/distributions/go.mod` require
  `github.com/pingcap-inc/nexuscrm-controlplane v0.1.1` with no local replace.
- The controlplane repo itself no longer relies on local `../nexuscrm-*`
  replacement directories: its `github.com/nexuscrm/*` core-module replaces now
  point at the NexusCRM Phase 1 commit as remote submodule versions. Cross-repo
  frontend/backend guardrails skip in standalone controlplane runs unless
  `NEXUSCRM_REPO_ROOT` points at a NexusCRM checkout.
- CI and `infra/backend.Dockerfile` are wired with
  `NEXUSCRM_CONTROLPLANE_DEPLOY_KEY`, `GOPRIVATE/GONOSUMDB`, and SSH `insteadOf`
  rules matching the existing private-module pattern.
- The l0-completeness source-backed recovery proof runs the external
  `github.com/pingcap-inc/nexuscrm-controlplane` package from the backend module
  context, instead of reading a local module.
- boundarygate shows no `backend ↔ controlplane` SCC and `Forbidden-edge
  violations: 0`.
- CI run `28291436501` passed `build-test-seal`, `backend-docker-image-smoke`,
  and `l0-neutrality-gate`.

**Verified locally:**
- `GOWORK=off go -C backend test ./...`
- `GOWORK=off go -C backend build ./...`
- `GOWORK=off go -C backend build -tags crm ./...`
- `GOWORK=off go -C backend/distributions build ./...`
- `GOWORK=off go -C backend/distributions test ./...`
- `GOWORK=off go -C backend test -tags integration github.com/pingcap-inc/nexuscrm-controlplane -run 'Test(VerifyPathFailsClosedForMissingPhysicalColumnWithoutHashDrift|ExplicitApplyAllRepairsMissingPhysicalColumn|RegisterInstalledRuntimeMetadataRestoresInstalledSourceBackedRuntimeState|RebuildDerivedRestoresInstalledSourceBackedRuntimeState|ApplyAllRepairsMissingPhysicalColumnForInstalledSourceBackedPack)$' -count=1`
- `GOWORK=off go -C backend test github.com/pingcap-inc/nexuscrm-controlplane/bootstrapimpl -count=1`
- `go test ./...` and `go vet ./...` in `pingcap-inc/nexuscrm-controlplane`
- `GOWORK=off go -C shared test ./...`
- `go run ./tools/boundarygate`
- `git diff --check`

**Local caveat:**
- `backend/distributions` CRM tag build/test and `frontend npm run build` still
  require the CI-pinned app-packs checkout. The local `nexuscrm-app-packs` path
  is not that checkout and cannot resolve pinned frontend revision
  `f862964dce66464a32d38dde94a07f34220c0cf8`.

## PR4 — apihost wrapper collapse

`nexuscrm-apihost` is no longer a top-level module. The wrapper was split by
responsibility:

- kernel-free transport primitives (`system` version discovery, per-IP rate
  limiting, trusted internal bearer-token auth, security headers) moved into
  the published private module `github.com/pingcap-inc/nexuscrm-api-host
  v0.1.0`;
- backend composition glue moved under
  `backend/internal/host/adapters/apihost`, where it can depend on
  kernel/shared models, backend request context keys, DB-backed Connected App
  origin/scope registries, and Salesforce-compatible error projection without
  polluting the external transport module.

`backend/go.mod` and `backend/distributions/go.mod` now consume
`github.com/pingcap-inc/nexuscrm-api-host v0.1.0` with no local replace. The
old `github.com/nexuscrm/apihost` module, `./nexuscrm-apihost` `go.work` entry,
and Docker source-copy entries were removed.

**Verified locally:**
- `go test ./...` in `pingcap-inc/nexuscrm-api-host`
- `GOWORK=off go -C backend test ./internal/host/adapters/apihost/... -count=1`
- `GOWORK=off go -C backend test ./pkg/serverruntime ./internal/interfaces/rest ./internal/interfaces/rest/oauth ./internal/boundary -count=1`
- `GOWORK=off go -C backend test ./...`
- `GOWORK=off go -C backend build ./...`
- `GOWORK=off go -C backend build -tags crm ./...`
- `GOWORK=off go -C backend/distributions test ./...`
- `GOWORK=off go -C shared test ./...`
- `go run ./tools/boundarygate`
- `git diff --check`

## P1-E — final published-module pins

`nexuscrm-connectors` has been published as private module
`github.com/pingcap-inc/nexuscrm-connectors v0.1.0` after pinning its own
`nexuscrm-contract` dependency to `v0.1.0`. NexusCRM now consumes the four
graduated L0 repos as real version tags:

- `github.com/pingcap-inc/nexuscrm-contract v0.1.0`
- `github.com/pingcap-inc/nexuscrm-api-host v0.1.0`
- `github.com/pingcap-inc/nexuscrm-connectors v0.1.0`
- `github.com/pingcap-inc/nexuscrm-controlplane v0.1.1`

There are no local replaces for those published modules in NexusCRM. The
remaining local replaces are the intentional in-repo modular-monolith modules
and the L1 app-packs dialect checkout used by `backend/distributions`. These
workspace-local replacements are versionless because each module path has one
live source tree in the workspace. A version-qualified local replacement is not
a supported composition shape; published-version selection uses a real module
pin without a local replacement.

**Verified locally:**
- `go test ./...` and `go vet ./...` in `pingcap-inc/nexuscrm-connectors`
- `go test ./...` and `go vet ./...` in `pingcap-inc/nexuscrm-controlplane`
- `GOWORK=off go -C backend test ./...`
- `GOWORK=off go -C backend build ./...`
- `GOWORK=off go -C backend build -tags crm ./...`
- `GOWORK=off go -C backend/distributions test ./...`
- `GOWORK=off go -C backend/distributions build ./...`
- `GOWORK=off go -C shared test ./...`
- `go run ./tools/boundarygate`
- `git diff --check`

---

# Phase boundary — scope contract and historical execution order

The work was split into **two strictly separate kinds**: Phase 1 is **physical /
release boundaries**; Phase 2 is **product semantics / architectural purity**.
This distinction remains binding, but the original branch-order instruction
below is historical and must not be read as the current phase status.

> Phase 1 — re-lay the building's load-bearing structure and plumbing, and make
> all tests pass. Phase 2 — start the interior design: polish the L0/L1/L2
> semantic boundaries until they are elegant.

**Historical branch rule:** no Phase 2 behavior migration could land in the
Phase 1 branch. Current work uses the evidence-backed status below.

## Phase 1 — Repo / module split physical reorganization  ✅ COMPLETE
Goal: a stable, testable, releasable repo topology — NOT cleaning Salesforce shape.

Definition of Done (all met on this branch):
- Repo topology fixed: `NexusCRM`, `nexuscrm-contract`, `nexuscrm-api-host`,
  `nexuscrm-connectors`, `nexuscrm-controlplane`, `nexuscrm-app-packs`,
  `nexuscrm-pingcap-pack`.
- Published repos consumed via **real version pins**, not workspace-only replace
  (`contract v0.1.0`, `api-host v0.1.0`, `connectors v0.1.0`, `controlplane v0.1.1`).
- `controlplane` is **backend-free** and independently testable.
- Controlplane heavy duplication removed (PR2b single-source in `controlplane/bootstrapimpl`).
- `nexuscrm-apihost` wrapper no longer straddles api-host and backend (PR4).
- NexusCRM internal module/dir layout converged; it is a **modular monolith**, not leftovers.
- All boundary gates / build / vet / `go test` pass; boundarygate `Forbidden-edge = 0`.
- Each repo's README / module ownership / dependency rule is clear.

At Phase 1 closeout it deliberately had **not** touched the following Phase 2
candidates: Salesforce REST skin migration, SOQL/SOSL query-IR cleanup, policy
schema-binding rewrite, records lifecycle profile rewrite, formula evaluator /
SQL-emitter rewrite, and L1/L2 app-pack semantic changes. Their later landed,
non-carve, retained, and deferred dispositions are recorded below and in the
boundary ledger.

## Phase 1 closeout — CI guards (make the guards real)

A small closeout PR (`phase1-closeout-ci-guards`) hardened the Phase-1 enforcement
without touching topology or behavior:

- **Pin gate is now SSoT-derived.** `backend/go.mod` owns each external-module
  version; the `verify external module pins` CI step reads it via `go list -m`
  and only checks consistency (real **published tag** — empty / `<nil>` /
  `v0.0.0` / Go pseudo-versions are rejected — no local replace, sibling
  consumers agree). It no longer hardcodes a version literal, so a routine
  controlplane bump can no longer break `main` by drifting against the gate.
- **boundarygate is now a blocking CI step** inside `build-test-seal`
  (`go run ./tools/boundarygate`). It enforces: Forbidden-edge = 0, contract
  runtime leaks = 0, accepted engine SCC only, **and no growth of the L0
  dialect-token baseline**. Previously it was dev-local only, so L0 shape could
  grow onto green `main`. (It inspects this repo's first-party modules; the
  published L0 repos — contract / controlplane / api-host / connectors — enforce
  their own source-level boundaries in their own CI.)
- **Frozen L0 dialect-token debt — token-scoped, may only shrink.** The
  baseline (`tools/boundarygate/l0_dialect_baseline.txt`) is **token-aware**: a
  `"<file>\t<token,token>"` entry waives only those tokens, so a NEW dialect
  token added to an already-frozen file still fails the gate (bare-path lines
  are the legacy whole-file freeze that the semantic-cleanup roadmap was
  intended to retire). The gate blocks both a
  new leak **file** and a new **token** in a token-scoped file.
  `nexuscrm-records/persistence_final_create_image.go` was **consciously
  baselined token-scoped to `sobject` only** (the core `models.SObject` type) as
  frozen semantic-boundary debt, **not** approved new L0 shape — a `salesforce` / `soql` /
  etc. token appearing in it later will still break CI. Any future carve needs a
  new current-tree-scoped owner; the baseline must only shrink.

## Phase 2 — L0 / L1 / L2 semantic cleanup

**Status: the finite L0-A…L0-F roadmap is closed as scoped; broader semantic
purity is not globally complete.** This is neither `NOT STARTED` nor a claim
that all future L0/L1 cleanup is done. The current-tree audit supports the
following finite reading:

| Roadmap step | Current disposition source |
|---|---|
| L0-A | landed in #3273; live boundary and golden seals remain in `backend/internal/boundary` |
| L0-B | complete in `docs/architecture/l0-b-plan.md` |
| L0-C | its scoped PR set landed; its plan explicitly retains separately owned deferred residues rather than treating them as complete |
| L0-D | closeout recorded in `docs/architecture/l0-d-plan.md`; security remained a standalone golden-first unit |
| L0-E | complete/non-carve dispositions recorded in the boundary ledger |
| L0-F | closed with a neutral SQL-lowering capability and an explicitly retained warm-upgrade compatibility fallback |

The boundary ledger is the status authority for the exact carve, non-carve,
compatibility, and deferred dispositions. A deferred row is not silently
complete. New work found by a current-tree audit receives a new scoped owner;
it must not reuse `PR0`…`PR6` or `L0-A`…`L0-F` as though those names were still
an open queue. The broader rule remains: **mechanism stays in L0;
product/vendor/tenant shape moves to L1/L2.**

- **L0 owns** (mechanism): metadata engine, records engine, policy evaluator,
  query/search executor, formula VM, flow/orchestration runtime, lifecycle phase
  engine, pack install/drift engine, connectors runtime, audit/events/outbox,
  storage/persistence, transport substrate.
- **L1 owns** (`nexuscrm-app-packs`): Salesforce-compatible REST skin, SObject
  envelope, SOQL/SOSL parser+projection, Tooling API projection, Salesforce
  formula/lifecycle profiles, product component kinds, official packs, frontend manifests.
- **L2 owns** (`nexuscrm-pingcap-pack`): PingCAP tenant overlays, final merged
  pack, deploy values/scripts, tenant migrations, environment rollout.

Historical Phase 2 roadmap (the ledger crosswalk maps its older `PR0`…`PR6`
names to these steps). The ordering remains normative for any compatibility
follow-up that touches the same invariants: security is standalone and formula
work is last.

- **L0-A — Boundary seal + golden baseline.** Product-shape *backflow* guard
  (no app-blind L0 owning `/services/data`, no v63/Tooling DTO in neutral
  contract, no Salesforce-only formula branch in app-blind evaluator) + golden
  baselines (v63 routes, SObject envelope, SOQL/SOSL, access-decision matrix,
  formula compat). Keep thin; do not rewrite frameworks.
- **L0-B — Cut visible Salesforce wire skin.** Move `/services/data` route
  ownership + SObject envelope projection + `attributes.type/url`, Id casing, SF
  error envelope to dialect/L1; backend stays a neutral service provider.
- **L0-C — Neutral contract + query/search boundary.** SOQL/SOSL parser+projection
  and Tooling DTO move to the Salesforce dialect; L0 query/search receives neutral
  `QuerySpec`/`SearchSpec`, never SOQL/SOSL text.
- **L0-D — Security schema binding (STANDALONE PR, do not mix).** One L0 policy
  evaluator decides on **registered** `PolicySchemaBinding`s; Salesforce
  schema/default grants move to L1 bindings. Highest-risk path → own golden tests,
  rollback unit, review focus; missing binding fails closed.
- **L0-E — Runtime profiles + component registry.** Records lifecycle = generic
  phase engine + registered Salesforce order-of-execution profile (L0 safety
  floors non-disableable); component kinds registered by product modules; unknown
  kinds fail closed.
- **L0-F — Formula hard split (LAST).** Formula VM stays L0; Salesforce
  spelling/profile/evaluator/SQL-emitter become registered native capabilities;
  separate declaration/catalog vs runtime-evaluator vs SQL-emitter ownership.

Phase 2 acceptance criteria (12, hard). These remain boundary guards and review
tests; the scoped closeout above does not relabel retained compatibility or
deferred work as completed:

1. app-blind L0 owns no Salesforce public REST routes. 2. `/services/data` only via
product/dialect composition. 3. neutral contract exposes no SF Tooling DTO. 4. L0
query/search takes neutral IR, not SOQL/SOSL text. 5. Policy/FLS/RLS/sharing decided
by one L0 evaluator over registered schema bindings. 6. records lifecycle via
registered profiles, L0 safety floors independent. 7. product component kinds add
without editing app-blind L0. 8. formula declaration/evaluator/SQL-emitter/profile
ownership separated. 9. app-blind formula runtime hardcodes no Salesforce-only
semantics. 10. CRM/Salesforce compatibility preserved via dialect/app-pack/profile
registration. 11. the CI-only non-CRM issue-tracker conformance fixture uses the
same L0 mechanisms without pretending to be Salesforce; this proves contract
neutrality, not a second production App. 12. missing registration everywhere
fails closed.
