Skip to content

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 via bun scripts/ledger-cli.ts add-subtasks 113); this document is the human-facing decomposition + sequencing summary.


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-59GET() returns NextResponse.json({ status, supabase, env, timestamp }, { status: statusCode }) (:51-57); no version field today. export const dynamic = 'force-dynamic' (:4). The env-drift check reads process.env by literal name (:25-46). This is where {113.5} adds the additive version field.
  • next.config.ts:34-79 — an existing build-time env-injection precedent: the Sentry release block reads process.env.VERCEL_GIT_COMMIT_SHA (:71) directly (NOT serverEnv, rationale documented :38-41: next.config runs at build orchestration time). {113.5}’s NEXT_PUBLIC_RELEASE_VERSION injection follows the same pattern.
  • __tests__/api/health.test.ts:1-204 — the existing 8-test mirror; imports { GET as healthGET } (:34), mocks @supabase/supabase-js via vi.hoisted (:18-28), setEnvVars/resetEnv helpers (:42-66). {113.5} extends this mirror (behaviour-first per test-philosophy.md).
  • .github/workflows/onprem-deploy.ymlenv.IMAGE (:107), env.TAG=sha-${{ github.sha }} (:108), pack build "${IMAGE}:${TAG}" ... --publish (:239-247), Coolify COCOINDEX_IMAGE_TAG PATCH (:343-367 prod / :448-472 staging), push:[main,staging] trigger (:92-93). {113.6} adds the vX.Y.Z image re-tag.
  • runbooks/ (private docs-site): client-app-deploy.md + onprem-b1-deploy.md exist (the cross-link neighbours); per-client-release-pins.md does NOT yet exist (the new crib {113.7} creates, per PI-7 / OQ-113-5).
  • AGPL surface (verified): NO THIRD-PARTY-NOTICES / NOTICES / LICENSE / COPYING file anywhere in the KH repo; zero agpl/copyleft/corresponding source mentions 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 only v0.2.0-task-view (vendored); no platform vX.Y.Z; git branch -r shows no release/*. 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).


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:

SubtaskSurfacePIsRatified revs
{113.5}Source: build-time vX.Y.Z stamp on /api/healthPI-1, PI-8rev 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 runbookPI-1/2/9/10rev 3, rev 4, rev 5
{113.9}Docs: upgrade-a-client runbookPI-3/4/9/12rev 1, rev 3
{113.10}Ops: standup verification sweepPI-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).
{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/health version 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.


The TECH was ratified WITH five staff-review revisions. Each lands as a concrete Subtask element:

  1. 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.
  2. Ship the minimal version stamp (OQ-113-8: ~10 LOC on /api/health). -> {113.5} (the only source Subtask): additive version field on the health response, build-time NEXT_PUBLIC_RELEASE_VERSION injection via next.config.ts, + two behaviour tests.
  3. Elevate OQ-113-7 (old-code-tolerance gate) to a BLOCKING release-cut field. -> {113.8} step 3: classify each release’s migration set old-code-tolerant: yes/no at cut, no forcing 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.
  4. 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.
  5. 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.

{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.

{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.


RiskImpactMitigation
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 orderingHigh{113.9} enforces migrate-DB-first (OQ-113-2); OLD deploy serves the compatibility window.
Forward-only migration with no clean rollbackMed{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 behaviourLowAdditive 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 ergonomicsLow{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.N suffix 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.