# PR-00 spike — `shared` versionability + app-packs self-gated Go CI (feasibility)

**Status: non-blocking spike / feasibility note. NOT a decision, NOT on the L0-B critical path.**

L0-B continues on the merged fallback contract (#3278 + #3283): `PR-0b` (dialect goldens, branched
from the current app-packs pin) → `PR-0c` (NexusCRM pin-bump executes those goldens in `go-ci`) →
`B1a/B1b/B1c/B2/B6/PR-6`. This note only evaluates **whether** a later change could simplify that
choreography, and at **what cost**. The PR-0c pin-bump is the current fallback; `shared`-versioning
**may** simplify it later — but only if this spike's conditions are met, and only if it does not
trade the current gate for a worse version-skew problem.

## Question
Can `github.com/nexuscrm/shared` be made an independently *versioned* module so that
`nexuscrm-app-packs` runs its **own** Go CI on the dialect goldens (executing them in the app-packs
PR itself, instead of only at a NexusCRM pin-bump)? At what blast radius and risk?

## Findings (evidence-grounded)

**Q1 / Q3 — closure & tagability: CLEAN.** `shared/go.mod` has a single external require —
`github.com/pingcap-inc/nexuscrm-contract v0.1.0` — and **no** dependency on `backend`, `kernel`, or
any runtime/engine module. Its full external closure is 4 `nexuscrm-contract` packages. `GOWORK=off
go build ./...` exits 0; the contract dep resolves from a real published tag (not a path-replace). So
`shared` **can be git-tagged and consumed by version today**, dragging nothing heavy.
*Guard needed:* a CI check (`go list -deps … | grep -E 'nexuscrm/(backend|kernel)'` stays empty) so a
future import can't silently re-pollute the published closure.

**Q2 — consumers: 20, all on the `replace` escape.** Every consumer requires `shared v0.0.0` and
resolves it via `replace => …/shared` (0 versioned). **But the migration for *this* goal is bounded:**
the 17 NexusCRM-internal consumers are co-developed via `go.work` and can **keep** their `replace`;
only the **3 then-existing app-packs dialect modules** (`dialect-sf-crm`, `-soql`, `-issue-tracker`) had to drop
`replace` and pin a published `shared` version for app-packs to self-gate. So the real edit surface is
~3 go.mods + the tag + a new CI workflow + a consistency gate — **not** all 20.

> P1x later deleted the non-product third dialect entirely. This paragraph is
> retained as versioning-spike evidence, not a current module inventory.

**Q4 — private-module auth: a new credential is required.** Today `nexuscrm-contract` is fetched in
NexusCRM `go-ci` via a per-module SSH **deploy key** + `insteadOf` rewrite, scoped to the
`pingcap-inc/*` namespace (`go-ci.yml:124-184`). `shared`'s module path is
`github.com/nexuscrm/*` — a **different namespace** outside that deploy-key infra. A new app-packs Go
CI would need its **own** deploy key + host alias + `GOPRIVATE` entry for `shared` (or `shared` must
first be relocated/published under `pingcap-inc/*` to reuse the existing pattern). Easy to miss → first
CI run red.

**Q5 — version consistency + a LIVE skew.** Enforcing "app-packs' `shared` version == shipping
NexusCRM `shared`" has **no single SSoT** (NexusCRM tags it, app-packs pins it), so the gate must run
in **both** CIs, derived via `go list -m` (never a hardcoded literal — per our CI-gate-from-SSoT rule).
⚠️ **There is already a live skew this would expose:** shipping `backend` pins
`nexuscrm-contract v0.1.1`, but `shared/go.mod` and **all 3 app-packs dialects** pin `v0.1.0`. It is
invisible today (`go.work` excludes app-packs; the `replace` shim masks it) — but the moment `replace`
is dropped, app-packs (`contract v0.1.0`) and shipping backend (`v0.1.1`) genuinely diverge. **A
consistency gate must exist *before* the escape is removed, or the divergence ships silently.**

**Q6 — migration count & rollback: ~4–6 coordinated PRs, producer-first, reversible.**
1. NexusCRM: fix the internal `contract` skew (bump `shared` → `v0.1.1`) + make `shared` taggable; tag `shared vX.Y.Z`.
2. app-packs: new Go CI workflow + deploy-key/`insteadOf`/`GOPRIVATE` for `shared` (+ confirm `contract`).
3. app-packs: drop `replace …/shared` in the 3 dialect go.mods, pin published `shared vX.Y.Z` (lands after the tag exists).
4. NexusCRM: add the Q5 consistency gate to the app-packs integration job + bump `OfficialAppPacksSourceRevision`.
Rollback is clean/incremental: reverting the app-packs go.mod PR restores `replace …/shared` (instant local-path fallback). The only irreversible op is the git tag (harmless if unreferenced) — so snapshot SHAs and land producer-before-consumer.

## Verdict
**Feasible, and cleaner than feared** (closure is clean, edit surface is ~3 dialect go.mods, rollback
is easy). **But it does not dominate the fallback**, for two reasons the spike confirmed:
- It **does not remove** the cross-repo integration guarantee — NexusCRM still must prove the
  *shipping* `shared`+pin combination builds. Versioning `shared` lowers app-packs feedback *latency*
  (goldens run earlier, in the app-packs PR) but the NexusCRM integration pin-bump must remain, now
  as an automated **version-consistency** check rather than the bespoke commit-pin choreography.
- It surfaces a **real version-skew landmine** (the live `contract v0.1.0` vs `v0.1.1` divergence)
  that the `replace` shim currently hides; the consistency gate is a hard prerequisite.

## Recommendation
**Keep this as a parallel, non-blocking spike. Do not promote it onto the L0-B critical path.**
Promote only if a follow-up confirms all three: (a) `shared` can publish under a credential path
app-packs CI can use (own deploy key or relocation under `pingcap-inc/*`); (b) the version-consistency
gate is in place and the live `contract` skew is resolved *first*; (c) the migration stays ~3 dialect
go.mods + tag + gate. Until then, `PR-0c` choreography is the accepted fallback.

**Side-finding worth fixing independently of this spike:** the live `nexuscrm-contract v0.1.0`
(app-packs/`shared`) vs `v0.1.1` (shipping backend) skew is a real, currently-masked divergence — it
should be reconciled regardless of whether `shared` is ever versioned.
