PLAN -- Platform per-client deploy: tag/release version pinning (ID-113.4)
PLAN — Platform per-client deploy: tag/release version pinning (ID-113.4)
Section titled “PLAN — Platform per-client deploy: tag/release version pinning (ID-113.4)”Task: ID-113 — Platform per-client deploy: tag/release version pinning design + standup.
Subtask: {113.4} PLAN.
Author date: 16/06/2026.
Status: SPEC — decomposition. Fresh-Planner artefact (Q-PLANNER-2): authored by a
different instance from the {113.1}/{113.2}/{113.3} authors; PRODUCT.md + TECH.md +
RESEARCH.md all read in full. TECH.md is RATIFIED (Liam took the staff senior-engineer
default recommendations). Baseline HEAD 7fe481f74 on canonical-pipeline-setup. UK
English throughout (DD/MM/YYYY, “colour”, “organisation”, “behaviour”). ASCII only.
This PLAN decomposes the ratified PRODUCT (twelve
PI-N) + TECH (1:1 Proposed changes per PI, OQ-113-2 migrate-DB-first + OQ-113-3 roll-forward-only resolved) into six implementation Subtasks{113.5}-{113.10}. It folds the FIVE ratified staff-review revisions the TECH was ratified WITH (see “Ratified revisions -> Subtask mapping”). The load-bearing artefact is the Subtask JSON array (returned separately for the Orchestrator to bulk-insert viabun scripts/ledger-cli.ts add-subtasks 113); this document is the human-facing decomposition + sequencing summary.
Code-intelligence orientation (Context)
Section titled “Code-intelligence orientation (Context)”Per the code-intelligence discipline, orientation ran before decomposition. Outputs cited verbatim (not paraphrased):
gitnexus_query({query: "health endpoint version release deploy", repo: "knowledge-hub"})
returned the workspace-health route and the Python cocoindex server health handler in its
processes — NOT the Next.js app/api/health/route.ts GET — and grouped definitions
(_health_handler, worker_is_healthy in scripts/cocoindex_pipeline/server.py;
SourceHealthSummary/WorkspaceHealthResponse in
hooks/intelligence/use-workspace-health.ts). The release/versioning concept is a
config-and-CI concern, not an indexed runtime call graph (consistent with the
{113.1}/{113.2}/{113.3} orientation that returned "processes": [] for it).
gitnexus_context({name: "GET", file_path: "app/api/health/route.ts", repo: "knowledge-hub"})
returned the exact symbol (uid Function:app/api/health/route.ts:GET, lines 21-59) with
"incoming": {}, "outgoing": {}, "processes": [] — a greenfield leaf in the call
graph with no callers/callees indexed. This grounds the ONLY application-source touch point
({113.5} version stamp): the route handler is safe to extend additively (no upstream blast
radius).
Grounding (file:line verified live for this PLAN):
app/api/health/route.ts:22-59—GET()returnsNextResponse.json({ status, supabase, env, timestamp }, { status: statusCode })(:51-57); noversionfield today.export const dynamic = 'force-dynamic'(:4). The env-drift check readsprocess.envby literal name (:25-46). This is where{113.5}adds the additiveversionfield.next.config.ts:34-79— an existing build-time env-injection precedent: the Sentry release block readsprocess.env.VERCEL_GIT_COMMIT_SHA(:71) directly (NOTserverEnv, rationale documented:38-41: next.config runs at build orchestration time).{113.5}’sNEXT_PUBLIC_RELEASE_VERSIONinjection follows the same pattern.__tests__/api/health.test.ts:1-204— the existing 8-test mirror; imports{ GET as healthGET }(:34), mocks@supabase/supabase-jsviavi.hoisted(:18-28),setEnvVars/resetEnvhelpers (:42-66).{113.5}extends this mirror (behaviour-first pertest-philosophy.md)..github/workflows/onprem-deploy.yml—env.IMAGE(:107),env.TAG=sha-${{ github.sha }}(:108),pack build "${IMAGE}:${TAG}" ... --publish(:239-247), CoolifyCOCOINDEX_IMAGE_TAGPATCH (:343-367prod /:448-472staging),push:[main,staging]trigger (:92-93).{113.6}adds thevX.Y.Zimage re-tag.runbooks/(private docs-site):client-app-deploy.md+onprem-b1-deploy.mdexist (the cross-link neighbours);per-client-release-pins.mddoes NOT yet exist (the new crib{113.7}creates, per PI-7 / OQ-113-5).- AGPL surface (verified): NO
THIRD-PARTY-NOTICES/NOTICES/LICENSE/COPYINGfile anywhere in the KH repo; zeroagpl/copyleft/corresponding sourcementions in tracked source. Ratified revision 5 therefore CREATES the discharge fresh, folded into the cut runbook ({113.8}). - Ref state (verified):
git tag --list 'v*'shows onlyv0.2.0-task-view(vendored); no platformvX.Y.Z;git branch -rshows norelease/*. Greenfield convention.
Empirical-verification note (Q-EX2 scope). This PLAN cites no external-library API
symbols requiring an import-and-call check. The implementation surface is one additive
Next.js route field + a CI YAML delta + three docs-site runbook sections. The {113.5}
stamp uses Next.js’s already-in-use env config and process.env (framework built-ins, out
of Q-EX2 scope).
Decomposition overview
Section titled “Decomposition overview”ID-113 is overwhelmingly a git-convention + Vercel/Coolify-config + CI-delta + docs-site
runbook Task. There is exactly ONE application-source touch point (the {113.5} version
stamp, elevated to ship by ratified revision 2). The decomposition slices vertically by
deliverable surface:
| Subtask | Surface | PIs | Ratified revs |
|---|---|---|---|
{113.5} | Source: build-time vX.Y.Z stamp on /api/health | PI-1, PI-8 | rev 2 |
{113.6} | CI: on-prem image vX.Y.Z re-tag (onprem-deploy.yml) | PI-10 | — |
{113.7} | Docs: private client->version crib (new artefact) | PI-5/6/7/8/12 | — |
{113.8} | Docs: cut-a-release runbook | PI-1/2/9/10 | rev 3, rev 4, rev 5 |
{113.9} | Docs: upgrade-a-client runbook | PI-3/4/9/12 | rev 1, rev 3 |
{113.10} | Ops: standup verification sweep | PI-3/5/6/8/9/11 | (OQ-113-1 residual) |
Six Subtasks, well under the 25-Subtask soft ceiling. No Subtask touches more than ~3 files.
All twelve PRODUCT PIs are covered (PI-1..PI-12 each map to at least one Subtask’s details
testStrategy).
Dependency graph (sibling-only)
Section titled “Dependency graph (sibling-only)”{113.5} version stamp ----------------------+ |{113.6} on-prem re-tag --+ | | |{113.7} crib ------------+--> {113.8} cut --+--> {113.10} standup verification | | | +--------+--> {113.9} upgrade --+ (7,8) | (5,8,9){113.5},{113.6},{113.7}are independent foundations (no deps) — parallelisable.{113.8}cut runbook deps[6, 7]: it references the on-prem re-tag step ({113.6}) and writes the crib row at cut ({113.7}).{113.9}upgrade runbook deps[7, 8]: it appends to the crib ({113.7}) and follows the cut-runbook ordering conventions ({113.8}).{113.10}standup verification deps[5, 8, 9]: it exercises the/api/healthversion axis ({113.5}), and validates the cut/upgrade runbooks ({113.8}/{113.9}) end-to-end.
All dependencies are sibling-only (Sx -> Sy within Task 113). No cross-Task Subtask dependency surfaced; no escalation required.
Ratified revisions -> Subtask mapping
Section titled “Ratified revisions -> Subtask mapping”The TECH was ratified WITH five staff-review revisions. Each lands as a concrete Subtask element:
- Mandatory pre-upgrade Supabase snapshot (BLOCKING pre-flight; revises OQ-113-3 from
roll-forward-only — the snapshot is the rollback path). ->
{113.9}step 2: a BLOCKING pre-flight that takes the snapshot before any migration, records the snapshot id/time in the crib upgrade-history row, and forbids the upgrade proceeding without it. The snapshot is also named in{113.9}’s rollback step as the last-resort emergency restore. - Ship the minimal version stamp (OQ-113-8: ~10 LOC on
/api/health). ->{113.5}(the only source Subtask): additiveversionfield on the health response, build-timeNEXT_PUBLIC_RELEASE_VERSIONinjection vianext.config.ts, + two behaviour tests. - Elevate OQ-113-7 (old-code-tolerance gate) to a BLOCKING release-cut field. ->
{113.8}step 3: classify each release’s migration setold-code-tolerant: yes/noat cut,noforcing a coordinated cutover + MAJOR. Re-checked as a gate in{113.9}step 1. This is what makes the migrate-DB-first OQ-113-2 strategy safe. - OQ-113-9 (CI tag-ancestry guard) stays DEFERRED (runbook suffices at single-tenant).
-> NO CI-guard Subtask authored;
{113.8}records the PI-2 tag-from-main-only check as a runbook step and notes OQ-113-9 deferred. - Fold the AGPL THIRD-PARTY-NOTICES + Corresponding-Source-pointer into the cut/promote
runbook regardless of D1. ->
{113.8}step 7: generate/refresh a THIRD-PARTY-NOTICES manifest + Corresponding-Source pointer at cut (creating it fresh — none exists today), discharging per-client redistribution of any copyleft image.
Phase / checkpoint sequencing
Section titled “Phase / checkpoint sequencing”Phase 1 — Foundations (parallelisable)
Section titled “Phase 1 — Foundations (parallelisable)”{113.5} (source stamp), {113.6} (on-prem re-tag), {113.7} (crib artefact). Independent
file ownership (next.config.ts+route+test / onprem-deploy.yml / new docs-site file) — no
shared-file conflict, safe to dispatch as a parallel wave.
Checkpoint A: bun run test __tests__/api/health.test.ts green (existing 8 + 2 new);
onprem-deploy.yml re-tag path lints; crib template exists + cross-linked.
Phase 2 — Runbooks (sequential on the crib)
Section titled “Phase 2 — Runbooks (sequential on the crib)”{113.8} cut runbook, then {113.9} upgrade runbook. Both write into the same crib
ops-surface (per-client-release-pins.md); sequence them to avoid same-file churn and so the
upgrade runbook can reference the cut runbook’s established orderings.
Checkpoint B: cut + upgrade checklists complete with all five ratified revisions present; crib has changelog + append-only upgrade-history scaffolding.
Phase 3 — Verification (end-to-end)
Section titled “Phase 3 — Verification (end-to-end)”{113.10} standup verification sweep. Binds the layer: PI-5/6 ref cleanliness, PI-3 no-op
merge, the PI-9 deploy-gate NEGATIVE test (the headline hazard), PI-11 branding
non-regression, PI-8 version-axis check, and the OQ-113-1 residual non-blocking multi-project
opt-in sanity check.
Checkpoint C (Task close): all twelve PIs verifiable from the runbooks + the standup results recorded in the crib; ready for Liam’s verification gate.
Risks and mitigations
Section titled “Risks and mitigations”| Risk | Impact | Mitigation |
|---|---|---|
| Deploy-gate silent un-gate (PI-9, headline hazard) | High | {113.9} ordered deploy-gate verify BEFORE any repoint; {113.10} negative test (mismatched check name MUST block prod). |
| Schema/code skew on wrong upgrade ordering | High | {113.9} enforces migrate-DB-first (OQ-113-2); OLD deploy serves the compatibility window. |
| Forward-only migration with no clean rollback | Med | {113.8} old-code-tolerance gate (rev 3) + {113.9} mandatory snapshot pre-flight (rev 1) + roll-forward posture (OQ-113-3). |
{113.5} stamp regresses existing health behaviour | Low | Additive field only; {113.5} testStrategy asserts the existing 8 tests still pass; greenfield leaf (no blast radius). |
{113.6} re-tag breaks prior-tag rollback ergonomics | Low | {113.6} adds an alias path only; leaves the existing sha- publish + Coolify PATCH paths intact; grep-sweep before edit. |
Open decisions (carried; none block the decomposition)
Section titled “Open decisions (carried; none block the decomposition)”OQ-113-1 RESOLVED upstream (Option A, Liam-ratified). OQ-113-2/3 RESOLVED in TECH and
ratified. OQ-113-7 elevated to BLOCKING (rev 3, in {113.8}). OQ-113-9 DEFERRED (rev 4).
Residual operator-preference items, none gating implementation:
- OQ-113-4 — upgrade cadence / staleness signal. Default: platform-owner-initiated only; no staleness column at v1. (Not a Subtask; revisit if a client-facing signal is wanted.)
- OQ-113-6 — pre-release / staging channel (
-rc.N). Default: GA-only at v1; the semver-compatible-rc.Nsuffix reserved, no machinery built now. (Not a Subtask.)
These can be folded into the {113.7} crib / {113.8} cut runbook later without re-opening
the decomposition.