Canonical-pipeline → task-list migration (Linear-style umbrella/tag model) — TECH
Canonical-pipeline → task-list migration (Linear-style umbrella/tag model) — TECH
Section titled “Canonical-pipeline → task-list migration (Linear-style umbrella/tag model) — TECH”Spec status header Slug:
canonical-pipeline-task-list-migrationVersion: v1.0 (draft, S64 W2b) Last verified: 22/05/2026 Linked PRODUCT:docs/specs/id-31-canonical-pipeline-task-list-migration/PRODUCT.mdAuthored by: task-planner underwrite-tech-spec, dispatched per ID-31.3 brief. Fresh Planner instance per Q-PLANNER-2 / B4 — independent review pass over PRODUCT.md by a Planner who did not author PRODUCT.md. Status: DRAFT — awaits Checker promotion of Subtask 31.3in_progress → done, then dispatch of Subtask 31.4 PLAN.md.
§1 Current state
Section titled “§1 Current state”The Knowledge Hub Task ledger (docs/reference/task-list.json) is a single-document
ledger conforming to a strict Zod schema (lib/validation/task-list-schema.ts, 254L).
It encodes a flat tasks[] array with two-level nesting (Task → Subtask). There is
no umbrella entity anywhere in the schema, the file system, or runtime tooling.
1.1 Existing schema surface
Section titled “1.1 Existing schema surface”| Module | Lines | Purpose |
|---|---|---|
lib/validation/task-list-schema.ts | 254 | SubtaskSchema + TaskSchema + TaskListSchema (root) + parseTaskListWithWarnings() helper for 25-Subtask soft-ceiling. |
lib/validation/backlog-schema.ts | 180 | Pattern mirror — same idioms, strict mode, single root document. |
lib/validation/roadmap-schema.ts | 259 | Pattern mirror — also exports DocLinkSchema (re-used by Task cross_doc_links[]). |
lib/validation/work-status.ts | n/a | TaskListStatus enum (done, pending, in_progress, blocked, deferred, cancelled, spec_needed, imp_deferred). Subtask subset drops cancelled / spec_needed / imp_deferred. |
__tests__/validation/task-list-schema.test.ts | n/a | Existing Vitest unit coverage of TaskListSchema. |
__tests__/validation/backlog-schema-roundtrip.test.ts | n/a | Round-trip pattern reference — reads live JSON, parses via schema, asserts no warnings. |
__tests__/validation/roadmap-roundtrip.test.ts | n/a | Same pattern, roadmap surface. |
1.2 Existing runtime tooling that touches the ledgers
Section titled “1.2 Existing runtime tooling that touches the ledgers”| Skill / agent | Reads | Writes |
|---|---|---|
.claude/skills/update-roadmap-backlog/SKILL.md (448L) | product-backlog.json, product-roadmap.json, task-list.json | product-backlog.json, product-roadmap.json, task-list.json (via Promote mode at line 364+). Four operation modes: Create, Update, Delete, Promote. |
.claude/skills/workflow-orchestration/SKILL.md | All three ledgers | task-list.json status transitions (Orchestrator-owned per §6.3 of docs/plans/phase-0-investigation/kh-sdlc-workflow.md). |
.claude/agents/workflow-curator.md | All three ledgers | All three ledgers (via the skill above). |
1.3 T1-T14 mapping snapshot (per RESEARCH §3, ratified S62 W3)
Section titled “1.3 T1-T14 mapping snapshot (per RESEARCH §3, ratified S62 W3)”The canonical-pipeline plan in docs/specs/id-31-canonical-pipeline-implementation-plan/PLAN.md
has 14 enumerated Tasks T1-T14. Post-OQ-1-ALT ratification, T0 is added as a
retrospective umbrella Task. Current status as of S64 W0c (read from
docs/reference/task-list.json HEAD + cross-branch read of
origin/content-items-investigation):
| Tn | Proposed Task ID | Status today | Shipping evidence |
|---|---|---|---|
| T0 | next-available (≥36) | not yet opened | Will land via this migration. Subtask granularity 4-8 per shipping cadence (Inv 11). |
| T1 | next-available + 1 | not yet opened (retro done at open) | Commits 2f142936, ddebada1, f324fe93, a89440f1, c8fd217b (S242, content-items-investigation branch). |
| T2 | next-available + 2 | not yet opened (retro done at open) | WP2a commits f6a13f4e, c8cf4e8f (S245); T2 SQL 38242fef (S246); prod-apply 2f98c8cf (S247). |
| T3 | next-available + 3 | not yet opened (retro done at open) | Migration 20260514150238_enable_rls_auto_event_trigger_and_grants_pattern.sql (S239, verified S243). |
| T4 | next-available + 4 | not yet opened (retro done at open) | 10 commits dc1d7850..776d3a73 (S248); ID-23 followup 95b660ef (S249). |
| T5 | next-available + 5 | not yet opened (retro done at open) | S248 commit 44ff65c7 + S251 W1B 8-commit Phase A-G chain (c84631f8..45317dc6). |
| T6 | next-available + 6 | not yet opened (retro done at open) | Migrations 20260520225456_t6_q_a_pairs_full_schema.sql (2f3428c7), 20260520231524_t6_q_a_search_rpcs.sql (6614d8c3), 20260521095209_t6_followup_revoke_public_execute_anon_inherit_fix.sql (b3a4f792); integration test 275bc9e5. |
| T7 | future-JIT (per Inv 6) | not opened | Gated on T8. |
| T8 | ID-28 anchored (per Inv 5) | in_progress on content-items-investigation; 28.1 done S252 (58162181), 28.2-28.4 pending | Spec chain authored on content-items-investigation per cross-branch read. |
| T9-T14 | future-JIT (per Inv 6) | not opened | Awaiting JIT triggers per OQ-3 ratification. |
ID-space audit (cross-branch read, performed at TECH-authoring time):
origin/main: max ID = 23.origin/content-items-investigation: max ID = 29 (adds 28, 29).origin/production-readiness(this branch): max ID = 35 (adds 24-27 + spec migration Tasks 30-35 opened S62 W2-W4 + S64 W0c).- Highest occupied across branches: 35. Next-available cascade for T0-T6 starts at ID-36.
1.4 Why a TECH spec is needed
Section titled “1.4 Why a TECH spec is needed”PRODUCT.md prescribes 17 invariants spanning ledger schema (Inv 7-9), retrospective journal-block discipline (Inv 11-12), cross-branch ID coordination (Inv 10), cascade resolution rules (Inv 3-4), JIT forward-Task discipline (Inv 6), and PLAN.md ↔ Task description sync (Inv 13-14). Five P-OQs surfaced for ratification (P-OQ-1..5). Each invariant needs a concrete mechanism — a file, a schema, a function, a test path, or a documented discipline-with-checkpoint — before the migration can be decomposed into implementation Subtasks (the {31.4} PLAN.md scope).
§2 Proposed changes (one-to-one mapping against PRODUCT invariants)
Section titled “§2 Proposed changes (one-to-one mapping against PRODUCT invariants)”Seventeen rows, one per PRODUCT invariant. Each row carries Proposed change /
Mechanism / Test verification.
| # | Inv (PRODUCT.md) | Proposed change | Mechanism | Test verification |
|---|---|---|---|---|
| 1 | Inv 1 — Retro umbrella Task T0 (4-8 Subtasks, done at open, citing pre-phase substrate). | Open one Task (id = next-available, ≥36) with title T0 — Phase 0.9 investigation substrate (architecture sub-docs + drift audit + ratification arc), status done, 4-8 Subtasks each done with journal blocks per Inv 11-12. | Append a new entry under task-list.json#/tasks[] via Promote-shape (see §3.3) or manual edit committed at retro-opening. Subtask details carries the journal block per Inv 12. | New test __tests__/docs/canonical-pipeline-t0-presence.test.ts: asserts a Task exists with title prefix T0 — Phase 0.9 investigation and status === 'done' and subtasks.length >= 4 and subtasks.length <= 8 and every Subtask status === 'done'. |
| 2 | Inv 2 — Six retro Tasks T1-T6 opened done with shipping-cadence Subtasks. | Open six Tasks (ids T0+1 through T0+6) with titles T{n} — <topic>, slim ≤200-word description, cross_doc_links referencing PLAN.md §4.n, populated commit_refs[] + session_refs[], Subtasks per shipping cadence (Inv 11) with journal blocks (Inv 12). | Six append operations on task-list.json#/tasks[] — one commit per Task per P-OQ-5 default. | New test __tests__/docs/canonical-pipeline-t1-t6-presence.test.ts: for each title prefix T{n} — (n ∈ {1..6}), asserts status === 'done', description.length <= 1500 chars (≤200 words ≈ ≤1500 chars), commit_refs[].length > 0, cross_doc_links[] includes one entry with path === 'docs/specs/id-31-canonical-pipeline-implementation-plan/PLAN.md' and section === '§4.n'. |
| 3 | Inv 3 — Final Task ID assignment locks at PLAN dispatch, not PRODUCT. | TECH.md specifies the resolution algorithm (§4); the PLAN dispatch ({31.4}) reads cross-branch task-list.json snapshots at PLAN-dispatch time, computes the cascade, writes the final ID mapping table into 31.4 PLAN.md output. PRODUCT.md stays ID-agnostic — only references T0..T14 symbols. | No code/schema change required. The cascade algorithm (§4) is procedural — runs at {31.4} PLAN dispatch and again at retro-Task-open time as a sanity check. | No automated test. Manual gate at {31.4} PLAN dispatch: the PLAN.md output table must list T0-T14 → ID-N mapping; the Checker verifies the table against a fresh git show origin/<branch>:docs/reference/task-list.json MAX-ID query. |
| 4 | Inv 4 — Cascade documentation invariant (PRODUCT.md documents the shape, not specific IDs). | TECH §4 carries the resolution algorithm. PLAN.md output ({31.4}) carries the resolved table. | Algorithm specified once in §4 below. Re-runnable each time a new T-Task is opened. | Tested as part of Inv 3 — manual gate. |
| 5 | Inv 5 — T8 = ID-28 anchor; cross-branch collision resolved S62 W4 commit 9e498e2f. | The cascade algorithm in §4 hard-codes T8 → ID-28 as a fixed point. T1-T6 cascade around it: T0 = MAX-ID + 1, T1-T6 = T0+1..T0+6, T7 = T0+7 (skipping ID-28 in the cascade), T9-T14 = T0+8 onwards (also skipping ID-28). | §4 algorithm. | New test __tests__/docs/canonical-pipeline-t8-anchor.test.ts: if a Task with title prefix T8 — exists on any active branch, asserts its id === "28" exactly. Skip gracefully if T8 not yet opened on the branch under test (gated test). |
| 6 | Inv 6 — Forward Tasks T7 + T9-T14 open just-in-time, not upfront. | This Task does NOT open T7 or T9-T14. The migration land asserts (via the gated test in row 5) that the absence is intentional. The orchestrator opens each forward Task only when becoming the next-up dispatch. | No code change. Procedural discipline encoded in §6 below + invoked at each future Task open. | No automated test. Inverse-gated test in __tests__/docs/canonical-pipeline-forward-jit.test.ts: if a Task with title prefix T7 — or T9 — …T14 — exists, asserts subtasks.length >= 1 AND subtasks[0].title starts with RESEARCH or PRODUCT (i.e. spec-chain seeded at open, not empty pending placeholder). |
| 7 | Inv 7 — umbrellas.json exists with prescribed top-level shape (document_name, document_purpose, last_updated, related_documents, umbrellas[]). | New file docs/reference/umbrellas.json authored to schema. New Zod schema lib/validation/umbrellas-schema.ts mirroring roadmap-schema.ts / backlog-schema.ts strict-mode idioms (see §3 below). | Author the file once at first commit. The schema enforces the top-level shape via .strict() mode. last_updated re-uses the same kh-{prod-readiness|main}-S\d+ regex + ≤200-char cap + single-line discipline as the other ledgers. | New test __tests__/validation/umbrellas-schema.test.ts (unit) + __tests__/validation/umbrellas-roundtrip.test.ts (round-trip — reads live docs/reference/umbrellas.json, parses, asserts no schema errors). |
| 8 | Inv 8 — Each umbrellas[] entry has fields id, title, substrate_doc, task_ids[], status, phase. Many-to-many (Task may appear in multiple umbrellas). | UmbrellaEntrySchema Zod object with strict mode. id is kebab-case string regex; title is min(1) string; substrate_doc is min(1) string (relative path); task_ids is z.array(z.string().regex(BARE_ID_REGEX)) (same regex as Task.id); status is enum; phase is min(1) string. | See §3.2 below for the schema definition. Many-to-many is the natural consequence of task_ids being a curated list — no schema rejection if a Task id appears in two umbrellas. | Unit test in umbrellas-schema.test.ts: valid entry per each enum value (5 status states × 1 phase example), invalid status rejected, invalid id-shape rejected, multi-membership accepted (one Task id in two umbrella entries → both parse). |
| 9 | Inv 9 — umbrellas.json round-trips with task-list.json: every id in any task_ids[] references a real Task. | Integration test reads both docs/reference/umbrellas.json and docs/reference/task-list.json, builds the set of real Task ids, asserts every umbrellas[].task_ids[] member is in that set. | Test fails build (Vitest red) on broken reference. Orphan Tasks (in task-list, not in any umbrella) emit a console.warn log but do not fail the test — P-OQ-2 default (zero memberships allowed but flagged as soft warning). | New test __tests__/docs/umbrellas-task-list-roundtrip.test.ts: parses both JSON files via their schemas; for each umbrellas[].task_ids[] entry, asserts presence in tasks[]; collects orphan Tasks; emits console.warn listing orphan ids; passes if zero broken references regardless of orphan count. |
| 10 | Inv 10 — Orchestrator assigns NEW Task IDs by querying all active branches; rename on collision uses chore(s{NN}-w{N}): rename ID-{old} → ID-{new} pattern. | Document the discipline inside .claude/skills/workflow-orchestration/SKILL.md (new sub-section “Task ID assignment: cross-branch MAX-ID discipline”). No CI guard per OQ-5 ratification. The S62 W4 ID-28 rename (9e498e2f) is the worked example. | Skill body edit only — no code or schema change. | No automated test. Audited by Liam-eye on each PR that opens a new Task. |
| 11 | Inv 11 — Retrospective Subtasks match actual shipping cadence (1-3 per WP if single-session, per-session-WP if multi-session). | Procedural discipline at retro-Task-open time. Documented inside this TECH.md §6 (migration sequencing) + cross-referenced from {31.4} PLAN.md. | Documentation-only; the orchestrator hand-decomposes each retro Task per the discipline. | No automated test. Gated by the {31.5+} implementation Subtask Checker — Checker verifies Subtask count against PLAN.md §4.n shipping-cadence summary. |
| 12 | Inv 12 — Each retro Subtask carries <info added on YYYY-MM-DDTHH:MM:SS.sssZ> journal block in details (opening sentence + Commits block + migration refs + PLAN.md §4.n acceptance close + follow-up flags). | New helper lib/validation/umbrellas-helpers.ts exports formatRetrospectiveJournalBlock(input: RetrospectiveOpeningInput): string returning the formatted journal text. Used by the orchestrator at retro-Task open. Pattern: see §5 below. | Helper signature and template specified in §5. The orchestrator pastes the helper output into the Subtask details at open time. | New test __tests__/validation/umbrellas-helpers.test.ts: asserts helper output starts with <info added on , contains RETROSPECTIVE OPENING, contains Commits (S{NN}, contains PLAN.md §4.{n} acceptance criteria all met., ends with </info added on . Round-trips a fixture input. |
| 13 | Inv 13 — PLAN.md stays as canonical substrate; Task descriptions reference via cross_doc_links[] as { "type": "spec_substrate", "path": "...", "section": "§4.n" }. | Re-use existing DocLinkSchema from roadmap-schema.ts (already imported by task-list-schema.ts). The type value "spec_substrate" is a new convention — verify it’s already permitted by DocLinkSchema (it should be, since DocLinkSchema.type is z.string() in current shape; verify at impl time). | No schema change if DocLinkSchema.type accepts free strings; otherwise add "spec_substrate" to its enum. Audit happens in {31.4} PLAN dispatch. | New test __tests__/docs/canonical-pipeline-cross-doc-links.test.ts: for each retro Task (T0-T6), asserts at least one cross_doc_links[] entry with type === 'spec_substrate' and path === 'docs/specs/id-31-canonical-pipeline-implementation-plan/PLAN.md'. |
| 14 | Inv 14 — PLAN.md backlinks added manually at Task-open time; absence does not break round-trip. | Procedural discipline; no automation. The orchestrator MAY append See Task ID-NN to PLAN.md §4.n header text at retro-Task-open time (single batch commit acceptable for T0-T6). | Documentation-only. | No automated test. Round-trip test in row 9 (umbrellas-task-list-roundtrip.test.ts) verifies the umbrellas → task-list direction; the inverse (Task → PLAN.md backlink) is NOT tested — explicit per Inv 14 spec text. |
| 15 | Inv 15 — UK English throughout umbrellas.json, retro journal blocks, Task descriptions. | Standard KH discipline — same lint convention as other ledgers. No new test infrastructure. | Standard editorial discipline. UK words: “organisation”, “colour”, “behaviour”. DD/MM/YYYY date format. | Existing project-wide guardrails (no new test). Checker verifies during {31.5+} per-Subtask review. |
| 16 | Inv 16 — Status-machine compliance for retro Tasks (done at open is the explicit exception). | The schema accepts done as a Task-level status (it’s in TaskListStatus enum). The discipline gate is that retro Tasks are the ONLY case where a Task opens directly in done (skipping pending → in_progress). | Procedural — no schema change. Documented as the explicit exception in §6 below + cross-referenced from kh-sdlc-workflow.md §3.5 state machine. | No automated test. Manual orchestrator gate at each retro-Task open. |
| 17 | Inv 17 — umbrellas.json parses against the TECH-spec’d Zod schema; orchestrator updates task-list.json + umbrellas.json in the same commit when affecting umbrella membership. | Schema in §3 below. Commit-coupling discipline documented in §6 migration sequencing + cross-referenced from update-roadmap-backlog SKILL.md when its Promote mode is extended to know about umbrellas (Inv 17 + §6.4). | The schema enforces the file is well-formed. Commit-coupling is procedural; no automated guard. (Considered: a CI guard that fails if a Task is added/removed to/from task-list.json without an accompanying umbrellas.json diff. Deferred per P-OQ-2 default — orphans are warnings, not failures.) | Round-trip test in row 9. Manual orchestrator gate for commit-coupling. |
One-to-one mapping check: 17 invariants, 17 rows. Verified by Planner before write.
§3 umbrellas.json schema (Option C ratified S62 W3, OQ-4)
Section titled “§3 umbrellas.json schema (Option C ratified S62 W3, OQ-4)”3.1 File location
Section titled “3.1 File location”docs/reference/umbrellas.json — sibling to the other three ledgers
(task-list.json, product-backlog.json, product-roadmap.json). The Astro docs
site renderer (ID-9, ID-20 follow-up) loads it from this canonical path.
3.2 Zod schema (new module lib/validation/umbrellas-schema.ts)
Section titled “3.2 Zod schema (new module lib/validation/umbrellas-schema.ts)”/** * umbrellas-schema.ts — Zod schema for the KH umbrella (Linear-Initiative analogue) * surface (TECH §3, PRODUCT inv 7–9). * * Mirrors the structure of `roadmap-schema.ts` (Zod, strict, typed exports). * Three exported schemas: * - UmbrellaStatus — 4-value enum (proposed | in_progress | done | archived) * - UmbrellaEntrySchema — single umbrella object (PRODUCT inv 8) * - UmbrellasSchema — root document (PRODUCT inv 7) * * Many-to-many semantics: a Task id may appear in multiple `task_ids[]` arrays * across umbrellas — schema imposes no uniqueness across umbrellas (PRODUCT inv 8). * * Round-trip with `task-list.json` is enforced by integration test * `__tests__/docs/umbrellas-task-list-roundtrip.test.ts`, NOT by the schema * (PRODUCT inv 9 — broken reference fails the test; orphan Task fires a console.warn * but passes — P-OQ-2 default). * * No barrel re-export. */
import { z } from 'zod';import { BARE_ID_REGEX } from '@/lib/validation/schemas';
// ──────────────────────────────────────────────────────────────────────────────// UmbrellaStatus enum (PRODUCT inv 8)// ──────────────────────────────────────────────────────────────────────────────
export const UmbrellaStatus = z.enum([ 'proposed', 'in_progress', 'done', 'archived',]);export type UmbrellaStatus = z.infer<typeof UmbrellaStatus>;
// ──────────────────────────────────────────────────────────────────────────────// UmbrellaEntrySchema (PRODUCT inv 8)// ──────────────────────────────────────────────────────────────────────────────
export const UmbrellaEntrySchema = z .object({ /** Kebab-case stable identifier (URL slug). Lowercase + hyphen + digits only. */ id: z .string() .regex( /^[a-z][a-z0-9-]*[a-z0-9]$/, 'Umbrella id must be kebab-case (lowercase, hyphens, digits; no leading/trailing hyphen).', ), /** Human-readable display name (Title Case, UK English). */ title: z.string().min(1), /** Relative path from repo root to canonical substrate doc. */ substrate_doc: z.string().min(1), /** * Array of Task id strings (matches `task-list.json#/tasks[].id` regex — * bare-digit). Order matters for rendering (insertion order = display order). * Many-to-many: a Task id may also appear in another umbrella entry's * task_ids[]. The cross-doc validation test (see Inv 9) verifies the * referenced ids exist in `task-list.json`. */ task_ids: z.array(z.string().regex(BARE_ID_REGEX, 'task_ids[] entries must be bare-digit Task ids')), /** 4-value status enum (mapped to aggregate umbrella phase, not auto-derived). */ status: UmbrellaStatus, /** Short string identifier for project phase (e.g. "Phase 1", "Phase 2"). */ phase: z.string().min(1), }) .strict();
export type UmbrellaEntry = z.infer<typeof UmbrellaEntrySchema>;
// ──────────────────────────────────────────────────────────────────────────────// UmbrellasSchema — root document (PRODUCT inv 7)// ──────────────────────────────────────────────────────────────────────────────
export const UmbrellasSchema = z .object({ /** Literal document identifier (PRODUCT inv 7). */ document_name: z.literal('umbrellas'), /** One-paragraph human-readable purpose. */ document_purpose: z.string().min(1), /** * Freshness marker — same shape as task-list/backlog/roadmap (200-char cap, * single-line, single session-id, kh-{prod-readiness|main}-S{N} prefix). */ last_updated: z .string() .min(1) .max(200, 'last_updated must be ≤200 chars.') .regex( /^kh-(prod-readiness|main)-S\d+/, 'last_updated must start with "kh-{prod-readiness|main}-S{N}" session-id prefix.', ) .refine((s) => !s.includes('\n'), { message: 'last_updated must be a single line (no newlines).', }) .refine( (s) => (s.match(/\bkh-(prod-readiness|main)-S\d+/g) ?? []).length === 1, { message: 'last_updated must contain exactly one session-id.' }, ), /** Array of repo-relative paths to related documents. */ related_documents: z.array(z.string()), /** Array of umbrella entries — empty allowed (PRODUCT inv 7). */ umbrellas: z.array(UmbrellaEntrySchema), }) .strict();
export type Umbrellas = z.infer<typeof UmbrellasSchema>;3.3 Initial populated set (P-OQ-1 default — canonical-pipeline only at first commit + 4 stubs)
Section titled “3.3 Initial populated set (P-OQ-1 default — canonical-pipeline only at first commit + 4 stubs)”Per P-OQ-1 default ratification (recommended pending Liam override):
{ "document_name": "umbrellas", "document_purpose": "Cross-Task strategic umbrellas (Linear Initiative analogue) — curated task_ids[] arrays + substrate_doc per umbrella for navigation, querying, and rendering on the Astro docs site (ID-9 follow-up).", "last_updated": "kh-prod-readiness-S64 W2c close-out — curator added canonical-pipeline (umbrella) + 4 stubs (production-readiness, sales-proposals, intelligence-workspaces, docs-foundation)", "related_documents": [ "docs/reference/task-list.json", "docs/reference/product-roadmap.json", "docs/reference/product-backlog.json" ], "umbrellas": [ { "id": "canonical-pipeline", "title": "Canonical Pipeline Implementation", "substrate_doc": "docs/specs/id-31-canonical-pipeline-implementation-plan/PLAN.md", "task_ids": ["28", "36", "37", "38", "39", "40", "41"], "status": "in_progress", "phase": "Phase 1" }, { "id": "production-readiness", "title": "Production Readiness", "substrate_doc": "docs/tracks/production-readiness.md", "task_ids": [], "status": "in_progress", "phase": "Phase 1" }, { "id": "sales-proposals", "title": "Sales Proposals (Phase 3)", "substrate_doc": "docs/specs/sales-proposal-workspaces/PRODUCT.md", "task_ids": [], "status": "proposed", "phase": "Phase 3" }, { "id": "intelligence-workspaces", "title": "Intelligence Workspaces (typed Shape B)", "substrate_doc": "docs/specs/intelligence-workspaces/TECH.md", "task_ids": [], "status": "in_progress", "phase": "Phase 1" }, { "id": "docs-foundation", "title": "Astro + Starlight Docs Foundation", "substrate_doc": "docs/specs/id-9-astro-starlight-docs-foundation/PRODUCT.md", "task_ids": [], "status": "in_progress", "phase": "Phase 1" } ]}The exact task_ids[] array for canonical-pipeline is illustrative; the resolved
list lands at {31.4} PLAN dispatch when the cascade algorithm (§4) executes.
Note that the T0-T6 retro Task ids are inserted at retro-Task-open time (one per
retro-open commit per P-OQ-5 default), so the initial commit’s canonical-pipeline
entry may carry an empty or partial task_ids[] and grow per retro-open.
3.4 Schema-roundtrip test (Inv 17 + Inv 9)
Section titled “3.4 Schema-roundtrip test (Inv 17 + Inv 9)”import { describe, it, expect } from 'vitest';import { readFileSync } from 'fs';import { UmbrellasSchema } from '@/lib/validation/umbrellas-schema';
describe('umbrellas.json round-trip', () => { it('parses cleanly via UmbrellasSchema', () => { const raw = readFileSync('docs/reference/umbrellas.json', 'utf-8'); const parsed = UmbrellasSchema.parse(JSON.parse(raw)); expect(parsed.umbrellas.length).toBeGreaterThan(0); });});
// __tests__/docs/umbrellas-task-list-roundtrip.test.tsimport { describe, it, expect } from 'vitest';import { readFileSync } from 'fs';import { UmbrellasSchema } from '@/lib/validation/umbrellas-schema';import { TaskListSchema } from '@/lib/validation/task-list-schema';
describe('umbrellas ↔ task-list round-trip (PRODUCT inv 9)', () => { const umbrellas = UmbrellasSchema.parse( JSON.parse(readFileSync('docs/reference/umbrellas.json', 'utf-8')), ); const taskList = TaskListSchema.parse( JSON.parse(readFileSync('docs/reference/task-list.json', 'utf-8')), ); const realTaskIds = new Set(taskList.tasks.map((t) => t.id));
it('every umbrellas[].task_ids[] entry references a real Task', () => { const broken: { umbrella: string; missing_task_id: string }[] = []; for (const u of umbrellas.umbrellas) { for (const id of u.task_ids) { if (!realTaskIds.has(id)) { broken.push({ umbrella: u.id, missing_task_id: id }); } } } expect(broken).toEqual([]); });
it('warns (but does not fail) on orphan Tasks (P-OQ-2 default)', () => { const assigned = new Set<string>(); for (const u of umbrellas.umbrellas) for (const id of u.task_ids) assigned.add(id); const orphans = taskList.tasks .map((t) => t.id) .filter((id) => !assigned.has(id)); if (orphans.length > 0) { // Soft warning per PRODUCT inv 9 + P-OQ-2. console.warn(`Umbrella round-trip: ${orphans.length} orphan Task(s) (no umbrella membership):`, orphans); } expect(true).toBe(true); // never fail });});3.5 Migration (where the file lives, how it loads, how skills know about it)
Section titled “3.5 Migration (where the file lives, how it loads, how skills know about it)”- File location:
docs/reference/umbrellas.json(new). Created at first commit by the orchestrator runningupdate-roadmap-backlog’s extended Promote/Create mode (see §6.4 below for the skill extension). - Load path at runtime: No runtime app loader yet (the Astro docs site ID-9 + ID-20 will be the consumer). Until then, the file is read at test-time only — by the round-trip tests above and the per-Subtask journal lookup at retro-open time. The orchestrator reads it via Bash + jq at Task-open time to determine umbrella membership.
- Skill awareness:
.claude/skills/update-roadmap-backlog/SKILL.mdis extended per §6.4 below to know about the umbrellas surface — specifically, the Promote mode’s destination-shape options gain an optionalumbrella_idparameter that triggers a same-commit edit toumbrellas.jsonadding the new Task id to the named umbrella’stask_ids[]array. - Per-Subtask journal-block lookup: The retrospective journal-block helper
(§5 below,
formatRetrospectiveJournalBlock()) takes an optionalumbrella_idinput parameter that maps to the relevant umbrella’sidfield, used in the journal text (e.g.Umbrella: canonical-pipeline). The lookup is constant-time via aMap<string, UmbrellaEntry>built once per helper invocation.
§4 Cascade resolution algorithm
Section titled “§4 Cascade resolution algorithm”PRODUCT Inv 3 + Inv 4 prescribe that PRODUCT.md is ID-agnostic and the final T0-T14 → ID-N mapping locks at {31.4} PLAN dispatch (and is re-checked at each retro-Task open). This section specifies the deterministic algorithm.
4.1 Algorithm steps
Section titled “4.1 Algorithm steps”INPUT: - Active branches: ["origin/main", "origin/production-readiness", "origin/content-items-investigation"] - Fixed-point anchor: T8 → ID-28 (per PRODUCT inv 5) - The set of T-Tasks to cascade: {T0, T1, T2, T3, T4, T5, T6, T7, T9, T10, T11, T12, T13, T14}
STEP 1 — Cross-branch MAX-ID query. For each branch B in active branches: raw_json = git show ${B}:docs/reference/task-list.json parsed = JSON.parse(raw_json) max_id_B = max(int(t.id) for t in parsed.tasks) MAX_ID_ACROSS_BRANCHES = max(max_id_B for B in active branches) // Discount any Task ids that this migration has reserved but not yet opened // (e.g. the {31.x} subtask chain is on this branch but the T0-T6 retro Task IDs // are not yet present on any branch — they're being assigned NOW).
STEP 2 — Compute T0 base. T0_id = MAX_ID_ACROSS_BRANCHES + 1 // As of TECH-author time: MAX = 35 (production-readiness). T0 → 36.
STEP 3 — Cascade T1-T6 (sequential, retro tasks). For n in 1..6: T{n}_id = T0_id + n // T1 → 37, T2 → 38, T3 → 39, T4 → 40, T5 → 41, T6 → 42 (illustrative; // recompute at PLAN dispatch time per Step 1).
STEP 4 — T7 cascade (forward JIT, skipping ID-28 since it's anchored to T8). T7_id = T6_id + 1 IF T7_id == 28 OR T7_id == ANCHOR (T8): T7_id += 1 // skip the anchor // T7 → 43 (using T6 = 42 example).
STEP 5 — T8 anchored. T8_id = 28 (FIXED — never recomputed)
STEP 6 — T9-T14 cascade (forward JIT). candidate = T7_id + 1 For n in 9..14: IF candidate == 28: candidate += 1 // skip anchor T{n}_id = candidate candidate += 1 // T9 → 44, T10 → 45, T11 → 46, T12 → 47, T13 → 48, T14 → 49 (illustrative).
OUTPUT: Final mapping table {T0: 36, T1: 37, ..., T7: 43, T8: 28, T9: 44, ..., T14: 49}4.2 Where assignment lives
Section titled “4.2 Where assignment lives”umbrellas.jsoncanonical-pipeline.task_ids[]carries the resolved Task ids (as bare-digit strings). This is the source of truth for umbrella → Task membership. Order in the array follows T-number order (T0 first, then T1-T6, then T7, T8, T9-T14) per renderer convention.task-list.json#/tasks[].idcarries the Task id. There is noumbrella_idfield on Task — the reverse lookup is via the round-trip test in §3.4.
This is a deliberate normalisation choice: umbrellas know their members; Tasks do not know their umbrellas. The Astro renderer (ID-9, ID-20 follow-up) inverts the mapping at render-time if needed.
4.3 Edge cases
Section titled “4.3 Edge cases”| Edge case | Resolution |
|---|---|
| Task in 2 umbrellas (e.g. T2 = canonical-pipeline + intelligence-workspaces) | Allowed (PRODUCT inv 8). The Task id appears in both umbrellas’ task_ids[] arrays. The round-trip test (§3.4) does not flag this. The Astro renderer (future) will surface multi-membership in the Task’s render-time metadata. |
Umbrella retired (status archived) | Tasks listed in task_ids[] are NOT removed from the array. The umbrella entry stays in umbrellas.json with status: "archived". The Astro renderer hides archived umbrellas from default views but the data persists for audit. Migration step: orchestrator sets status field; no task_ids[] edit. |
Task cancelled or rejected (status: cancelled in task-list.json) | The Task id stays in any umbrella’s task_ids[] for traceability. The Astro renderer surfaces cancelled-Task badges. Schema does not enforce removal. |
| Cross-branch ID collision (re-opened) | Per Inv 10: orchestrator MAX-ID-checks pre-open. If post-open collision detected at merge time, rename via chore(s{NN}-w{N}): rename ID-{old} → ID-{new} commit; updates task-list.json + umbrellas.json + any cross_doc_links in one commit. S62 W4 ID-28 rename (9e498e2f) is the worked example. |
| Anchor reassignment (would T8 ever move off ID-28?) | Not contemplated. T8 = ID-28 is treated as a load-bearing fact for the canonical-pipeline migration. Should the user later request reassignment, that’s a separate migration with its own spec chain — out of scope here. |
§5 Journal-block templating helper
Section titled “§5 Journal-block templating helper”PRODUCT Inv 12 prescribes the journal-block contents for retrospective Subtasks. The shape is deterministic enough to encode in a helper.
5.1 Helper signature
Section titled “5.1 Helper signature”// lib/validation/umbrellas-helpers.ts (NEW module — sibling to umbrellas-schema.ts)
import type { UmbrellaEntry } from '@/lib/validation/umbrellas-schema';
export interface RetrospectiveOpeningInput { /** Session counter when the retro is being opened (NOT the original work session). */ retro_open_session: string; // e.g. "kh-prod-readiness-S64" /** Original work session for the shipped piece. */ original_session: string; // e.g. "S242" /** Original work branch. */ original_branch: string; // e.g. "content-items-investigation" /** Path to the continuation prompt that documented the original work. */ continuation_prompt_path: string; // e.g. "docs/continuation-prompts/continuation-prompt-kh-s242-main-t1-specs-and-phase-0-close.md" /** Load-bearing commits in chronological order. */ commits: Array<{ sha8: string; message_line: string }>; /** Migration file references (paths from repo root), optional. */ migration_files?: string[]; /** PLAN.md section number this retro Task delivered against (e.g. "4.1"). */ plan_md_section: string; /** Follow-up flags / known gaps (one-liners), optional. */ followup_flags?: string[]; /** Optional umbrella id for the cross-reference line (Inv 8 — Map<string, UmbrellaEntry>). */ umbrella_id?: string;}
/** * Format a retrospective journal block per PRODUCT inv 12. * * Output shape: * * <info added on YYYY-MM-DDTHH:MM:SS.sssZ> * RETROSPECTIVE OPENING — Task opened in `done` status post-implementation. * Original work happened S{NN} per `docs/continuation-prompts/<file>.md`. * * Commits (S{NN}, <branch>): * - <sha8> — <message-line> * ... * * Migration files: (optional) * - supabase/migrations/<timestamp>_<name>.sql * * Follow-up flags: (optional) * - <flag> * * Umbrella: <id> (optional, when umbrella_id provided) * * PLAN.md §4.{n} acceptance criteria all met. * </info added on YYYY-MM-DDTHH:MM:SS.sssZ> * * NOTE: The opening + closing `<info added on …>` timestamps must match exactly. * The helper generates a single ISO 8601 timestamp using `new Date().toISOString()` * at invocation time. Callers wanting a fixed timestamp (test reproducibility) * inject via `Date.now()` mock — standard Vitest pattern. * * UK English throughout output (Inv 15). */export function formatRetrospectiveJournalBlock( input: RetrospectiveOpeningInput,): string { // Implementation lands at impl Subtask {31.5+}. Output template + signature // specified above; reference test in `__tests__/validation/umbrellas-helpers.test.ts` // verifies behaviour against a fixture input. Not implemented here — TECH.md // specifies, PLAN.md decomposes, Executor implements. throw new Error('Not implemented (see TECH.md §5 + impl Subtask {31.5+}).');}5.2 Worked example output (T1 retrospective Subtask 30.1, for reference)
Section titled “5.2 Worked example output (T1 retrospective Subtask 30.1, for reference)”<info added on 2026-05-22T10:00:00.000Z>RETROSPECTIVE OPENING — Task opened in `done` status post-implementation.Original work happened S242 per `docs/continuation-prompts/continuation-prompt-kh-s242-main-t1-specs-and-phase-0-close.md`.
Commits (S242, content-items-investigation):- 2f142936 — docs(spec): procurement-workspaces PRODUCT + TECH — S242 W3 fix-pass per verifier findings- ddebada1 — docs(spec): procurement-workspaces PRODUCT + TECH (absorbs EP8 v5) — S242 T4.7- f324fe93 — docs(spec): content-model-invariants PRODUCT — S242 W3 fix-pass per verifier findings- a89440f1 — docs(verify): content-model-invariants verifier report — S242 WP1.3- c8fd217b — docs(verify): procurement-workspaces verifier report — S242 WP1.4
Umbrella: canonical-pipeline
PLAN.md §4.1 acceptance criteria all met.</info added on 2026-05-22T10:00:00.000Z>5.3 Existing journal blocks unchanged (T0 retro discipline)
Section titled “5.3 Existing journal blocks unchanged (T0 retro discipline)”Existing per-Subtask journal blocks in task-list.json (e.g. ID-31.1 31.2 details on
this branch) are NOT retroactively edited to add Umbrella: lines. New retro Tasks
opened by this migration (T0-T6) get the line; old Tasks do not. This is a forward-
only adoption — no historical rewriting per standard KH discipline.
§6 Per-Subtask migration sequencing
Section titled “§6 Per-Subtask migration sequencing”The {31.4} PLAN.md output prescribes the exact Subtask shape; this section specifies the file boundaries and dependency order so the Planner has a clear starting point.
6.1 Recommended decomposition (target: 5-7 implementation Subtasks)
Section titled “6.1 Recommended decomposition (target: 5-7 implementation Subtasks)”The decomposition below is the recommended starting point — the {31.4} Planner adapts based on the latest task-list.json state at PLAN-dispatch time.
| Sub | Title | File boundary | Sibling-only deps |
|---|---|---|---|
| 31.5 | Author lib/validation/umbrellas-schema.ts + unit test | lib/validation/umbrellas-schema.ts (new) + __tests__/validation/umbrellas-schema.test.ts (new) | none |
| 31.6 | Author lib/validation/umbrellas-helpers.ts (formatRetrospectiveJournalBlock) + unit test | lib/validation/umbrellas-helpers.ts (new) + __tests__/validation/umbrellas-helpers.test.ts (new) | [5] |
| 31.7 | Author initial docs/reference/umbrellas.json (canonical-pipeline + 4 stubs per P-OQ-1 default) | docs/reference/umbrellas.json (new) | [5] |
| 31.8 | Add round-trip tests umbrellas-roundtrip.test.ts + umbrellas-task-list-roundtrip.test.ts | __tests__/validation/umbrellas-roundtrip.test.ts (new) + __tests__/docs/umbrellas-task-list-roundtrip.test.ts (new) | [5, 7] |
| 31.9 | Extend .claude/skills/update-roadmap-backlog/SKILL.md Promote mode to know about umbrellas | .claude/skills/update-roadmap-backlog/SKILL.md (edit) | [5, 7] |
| 31.10 | Open retrospective Tasks T0-T6 (7 commits per P-OQ-5 default; resolved IDs per §4 algorithm) | docs/reference/task-list.json + docs/reference/umbrellas.json (edit per retro-open) | [5, 6, 7, 8, 9] |
| 31.11 | Add presence + cross-doc-links tests for retro Tasks (Inv 1, 2, 5, 6, 13) | __tests__/docs/canonical-pipeline-{t0-presence, t1-t6-presence, t8-anchor, forward-jit, cross-doc-links}.test.ts (new — 5 files) | [10] |
Subtask count: 7. Well within the 25-soft-ceiling. Sibling-only deps verified across all rows (every dep references a same-Task sibling). No cross-Task references needed.
6.2 Smoke-test gate before {31.10}
Section titled “6.2 Smoke-test gate before {31.10}”Before the orchestrator opens any retro Task at {31.10}, run:
bun run test -- umbrellas-schema umbrellas-helpers umbrellas-roundtripAll four test files must be green (zero failures) — this confirms the schema + helper + round-trip infrastructure is ready to receive the retro-Task land.
6.3 Retro-Task open commit pattern (per P-OQ-5 default — one commit per Task)
Section titled “6.3 Retro-Task open commit pattern (per P-OQ-5 default — one commit per Task)”# For each retro Task n in {0, 1, 2, 3, 4, 5, 6}:# 1. Compute resolved id via §4 algorithm (fresh cross-branch MAX-ID query)# 2. Compose Task entry per PRODUCT inv 2 (or inv 1 for T0)# 3. Generate journal block per §5 helper for each Subtask# 4. Edit task-list.json: append Task entry# 5. Edit umbrellas.json: append id to canonical-pipeline.task_ids[]# 6. Bump last_updated on BOTH files (single-line, ≤200 chars, single session-id)# 7. Validate: TaskListSchema.parse() + UmbrellasSchema.parse() + round-trip test# 8. Commit:# chore(s64-w2-wp{X}): open Task ID-{N} T{n} retrospective (`done`) — <one-line summary>Total: 7 commits for the retro-Task land (1 × T0 + 6 × T1-T6).
6.4 update-roadmap-backlog skill extension (Subtask 31.9)
Section titled “6.4 update-roadmap-backlog skill extension (Subtask 31.9)”The skill’s Promote mode currently has two destination_shape options:
new_top_level_task and new_subtask_under_task_id. Extend by adding:
| New input field | Type | Required? | Purpose |
|---|---|---|---|
umbrella_id | string (kebab-case) or null | Optional (default null) | When supplied AND destination_shape === 'new_top_level_task': triggers a same-commit edit to umbrellas.json appending the new Task id to the named umbrella’s task_ids[] array. When null: no umbrella edit (Task is orphan per P-OQ-2 default — warning only). When destination_shape is subtask: ignored (subtasks inherit parent Task’s umbrella membership). |
The skill body gains a new step 9. Umbrella membership edit (optional):
9. **Umbrella membership edit (optional).** If input.umbrella_id is non-null: - Load docs/reference/umbrellas.json. - Parse via UmbrellasSchema. - Find umbrella entry with id === input.umbrella_id; if not found, error. - Append destination Task id to that umbrella's task_ids[] (skip if already present — idempotent). - Bump umbrellas.json last_updated per the field-discipline rule. - Validate via UmbrellasSchema.parse(); abort on failure.This extension is the Subtask 31.9 scope. The skill’s existing 8 steps and the last_updated discipline apply unchanged.
6.5 Forward-Task open (T7, T9-T14) discipline
Section titled “6.5 Forward-Task open (T7, T9-T14) discipline”When the orchestrator opens each forward Task JIT (per Inv 6):
- Compute fresh resolved id via §4 algorithm (re-run cross-branch MAX-ID).
- Open the Task with spec-chain Subtasks ({N.1 RESEARCH, N.2 PRODUCT, N.3 TECH,
N.4 PLAN}) per
spec-driven-implementationskill. - Call
update-roadmap-backlogCreate (NOT Promote — these are not backlog items) withumbrella_id: "canonical-pipeline". - Optionally append
(see Task ID-NN)backlink to PLAN.md §4.n header per Inv 14.
§7 Testing and validation table (per invariant)
Section titled “§7 Testing and validation table (per invariant)”| Invariant | Test file | Test type | Asserts |
|---|---|---|---|
| Inv 1 (T0 retro) | __tests__/docs/canonical-pipeline-t0-presence.test.ts | Doc | T0 Task exists, status done, 4-8 Subtasks all done |
| Inv 2 (T1-T6 retro) | __tests__/docs/canonical-pipeline-t1-t6-presence.test.ts | Doc | Each Task title prefix T{n} — present, done, ≤200-word desc, commit_refs non-empty |
| Inv 3, 4 (cascade docs) | (manual gate at PLAN dispatch) | n/a | Checker manual verify |
| Inv 5 (T8 anchor) | __tests__/docs/canonical-pipeline-t8-anchor.test.ts | Doc (gated) | If Task title T8 — present, id === "28" |
| Inv 6 (forward JIT) | __tests__/docs/canonical-pipeline-forward-jit.test.ts | Doc (gated) | If T7/T9-T14 present, spec-chain seeded |
| Inv 7 (umbrellas.json shape) | __tests__/validation/umbrellas-schema.test.ts + umbrellas-roundtrip.test.ts | Unit + round-trip | Root parses via UmbrellasSchema |
| Inv 8 (entry shape) | __tests__/validation/umbrellas-schema.test.ts | Unit | Each enum value + invalid rejected + multi-membership accepted |
| Inv 9 (round-trip with task-list) | __tests__/docs/umbrellas-task-list-roundtrip.test.ts | Doc | Every task_ids[] entry resolves; orphans warn (don’t fail) |
| Inv 10 (cross-branch coord) | (manual + skill body) | n/a | Liam-eye on each new-Task PR |
| Inv 11 (shipping-cadence) | (manual at retro-open) | n/a | Orchestrator hand-decomposes per PLAN.md §4.n |
| Inv 12 (journal block format) | __tests__/validation/umbrellas-helpers.test.ts | Unit | Helper output matches fixture template |
| Inv 13 (cross_doc_links shape) | __tests__/docs/canonical-pipeline-cross-doc-links.test.ts | Doc | Each retro Task has cross_doc_links[] entry with PLAN.md path |
| Inv 14 (PLAN backlinks manual) | n/a | n/a | Discipline only |
| Inv 15 (UK English) | (manual + existing project guards) | n/a | Editorial discipline |
| Inv 16 (status-machine retro exception) | (manual at retro-open) | n/a | Documented exception |
| Inv 17 (schema-roundtrip + commit-coupling) | __tests__/validation/umbrellas-roundtrip.test.ts + (manual for commit-coupling) | Unit + manual | Schema validates; commit-coupling is procedural |
Test file count: 6 new test files (3 in __tests__/validation/, 3 in
__tests__/docs/). Smoke-test command: bun run test -- umbrellas canonical-pipeline.
§8 Risks and mitigations
Section titled “§8 Risks and mitigations”| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Umbrella drift — Task moves between umbrellas mid-flight without round-trip test catching the move | Medium | Low | The round-trip test (Inv 9) catches broken references (missing Task id). It does NOT catch re-assignments (Task id removed from umbrella A, added to umbrella B). Mitigation: orchestrator commit-couples umbrella edits per Inv 17 — both files in one commit. Audit trail surfaces via git log -- umbrellas.json. If drift surfaces post-merge as a problem, add a __tests__/docs/umbrella-membership-stability.test.ts that diffs vs git show HEAD~10:umbrellas.json — deferred until evidence warrants. |
| Retrospective umbrella shape — T0 carries dozens of legacy Tasks (S180-S241 era), making the entry unwieldy | Medium | Medium | T0 is a Task (not the umbrella). The umbrella canonical-pipeline carries Task ids only — T0 plus T1-T14 (15 ids). The T0 Task itself has 4-8 Subtasks (Inv 1) collapsing the ~15 doc landings into shipping-cadence groupings. Mitigation: rely on Inv 1 cap (4-8 Subtasks) to prevent T0 explosion. If pre-phase work needs deeper granularity, open separate Tasks T0a, T0b, etc. (with cascade renumbering). Default per P-OQ-4: T0 is one Task; canonical-pipeline umbrella is a distinct ledger entity. |
Interaction with existing cross_doc_links field — type "spec_substrate" may not be in DocLinkSchema.type enum | Low | Low | Verify at impl-time. DocLinkSchema source: lib/validation/roadmap-schema.ts. If type is an enum, the new value "spec_substrate" must be added (single-line change). If type is z.string(), no change needed. Verify Subtask 31.5 includes this check; pre-emptive read in §2 row 13. |
| Cascade race condition — multiple branches simultaneously open new Tasks consuming the same MAX+1 id | Low | Medium | The MAX-ID discipline (Inv 10) is human-orchestrator-only, no CI guard (per OQ-5 ratification). Mitigation: the S62 W4 ID-28 rename pattern (9e498e2f) is the worked recovery procedure. If recurrence becomes frequent, escalate to T-OQ-3 (below) and consider CI guard. |
| {31.10} retro-open commit batching — 7 commits is verbose | Medium | Low | P-OQ-5 default = one commit per Task. Alternative = single batched commit. If Liam ratifies single batched commit, collapse Subtask 31.10 to a single execution; if P-OQ-5 default holds, 7 commits is the expected output. No mitigation needed — explicit P-OQ choice. |
Astro renderer doesn’t exist yet — umbrellas.json has no live consumer | Low | Low | Per Inv 7 / Non-goal: Astro renderer is a follow-up Task (downstream of ID-9 / ID-20). Until then, umbrellas.json is consumed only by tests + retro-Task-open helper. Mitigation: defer renderer concern. The schema is the load-bearing contract; the renderer follows. |
Schema-roundtrip test failure on merge to main — stale task_ids[] references | Medium | Medium | The round-trip test (§3.4) is the canary. On a merge where Task ids appear/disappear, the test catches it pre-merge if run locally. CI enforcement via bun run test blocks the PR. Mitigation: orchestrator runs bun run test -- umbrellas after every merge that touches task-list.json or umbrellas.json. |
§9 Estimated effort
Section titled “§9 Estimated effort”| Subtask | Effort estimate |
|---|---|
31.5 — umbrellas-schema.ts + unit test | 30 min |
31.6 — umbrellas-helpers.ts (formatRetrospectiveJournalBlock) + unit test | 45 min |
31.7 — Initial umbrellas.json (P-OQ-1 default — 1 + 4 stubs) | 15 min |
| 31.8 — Round-trip tests (2 new test files) | 30 min |
31.9 — Extend update-roadmap-backlog skill body (Promote mode + new step) | 30 min |
| 31.10 — Open retro Tasks T0-T6 (7 commits, hand-composed journal blocks) | 2h |
| 31.11 — Presence + cross-doc-links tests (5 new test files) | 1h |
| Total | ≈ 5h |
The 31.10 retro-Task open is the dominant cost — composing 7 Tasks with shipping- cadence Subtasks + journal blocks is hand-composed work, not automated. The helper (§5) reduces per-Subtask formatting cost but doesn’t generate Task descriptions or commit_refs[] population.
Effort exceeds the 2h-per-Subtask soft ceiling for 31.10. Recommend splitting 31.10 into 31.10a (T0 open), 31.10b (T1-T3 opens), 31.10c (T4-T6 opens) at {31.4} PLAN dispatch to keep each sub-execution under 2h. Decision deferred to the {31.4} Planner.
§10 T-OQs (TECH-level open questions)
Section titled “§10 T-OQs (TECH-level open questions)”Five TECH-level open questions surface from this authoring pass. Each has a recommended default suitable for {31.4} PLAN dispatch to assume unless Liam overrides.
T-OQ-1 — lib/validation/umbrellas-helpers.ts location
Section titled “T-OQ-1 — lib/validation/umbrellas-helpers.ts location”Should the retrospective-journal-block helper live in lib/validation/ (next to
the schema) or in a new lib/ledger/ module (proposed by the brief)? The brief
suggested lib/ledger/ but no such module exists today; all schema-adjacent
helpers (e.g. lib/validation/jsonb.ts) live under lib/validation/.
Default: lib/validation/umbrellas-helpers.ts. Consistent with the
existing pattern (schema.ts + helpers.ts in the same dir). Creating
lib/ledger/ for one file is premature — defer until 2+ ledger helpers exist.
Alternative: Create lib/ledger/ with umbrellas-helpers.ts. Pro: anticipates
future ledger-domain organisation (could also house task-list-helpers.ts,
backlog-helpers.ts). Con: premature, single-file dir.
T-OQ-2 — update-roadmap-backlog skill — Create mode vs Promote mode for new Tasks
Section titled “T-OQ-2 — update-roadmap-backlog skill — Create mode vs Promote mode for new Tasks”The skill’s Promote mode is documented as backlog → task-list MOVE only. New
Tasks NOT originating from backlog (e.g. forward-JIT canonical-pipeline Tasks
T7/T9-T14) currently use no clearly-named skill path. Should the skill grow a
new mode (e.g. OpenTask) or do new Tasks use Create against task-list.json?
Default: Create against task-list.json. Add task-list as a target
option to the Create mode’s “Resolve target → file” mapping (currently roadmap +
backlog only). The Subtask 31.9 scope includes this Create-target extension
alongside the Promote-umbrella extension.
Alternative: New mode OpenTask. Pro: cleaner semantics. Con: more
maintenance, more skill API surface.
T-OQ-3 — CI guard for cross-branch MAX-ID collision
Section titled “T-OQ-3 — CI guard for cross-branch MAX-ID collision”Per Inv 10 (and OQ-5 ratification), no CI guard exists. If multiple sessions on different branches open new Tasks concurrently and the MAX-ID discipline fails (human error), a collision merges in. The S62 W4 rename pattern is the recovery, but the cost grows with frequency.
Default: Defer CI guard until recurrence. The S62 W4 ID-28 incident is
the only known collision to date. If a second collision occurs within 6 months,
re-engage T-OQ-3 with a concrete CI guard proposal (likely a workflow_run job
that queries gh api repos/.../contents/docs/reference/task-list.json?ref=<branch>
for each active branch and diffs).
Alternative: Build CI guard now. Pro: prevents recurrence. Con: requires GitHub API tokens, cross-branch read pattern, and is heavyweight for an incident rate of 1/year.
T-OQ-4 — Round-trip test runtime cost — read both JSONs per test
Section titled “T-OQ-4 — Round-trip test runtime cost — read both JSONs per test”The round-trip test (§3.4) reads both umbrellas.json and task-list.json once.
At scale (e.g. when task-list.json grows to 100+ Tasks), this is fine (<10ms).
But re-reading both files in every CI run may be a redundant cost if other tests
also do this.
Default: Accept per-test re-read. Vitest’s beforeAll() could hoist the
read into a shared fixture, but the test runs in O(ms) regardless. No
optimisation needed.
Alternative: Centralise via a __tests__/helpers/ledger-fixtures.ts. Pro:
DRY. Con: indirection for a 2-line read.
T-OQ-5 — Future Astro renderer schema needs
Section titled “T-OQ-5 — Future Astro renderer schema needs”The Astro renderer for umbrellas (per Inv 7 + Non-goal — downstream follow-up of
ID-9 / ID-20) will need additional fields not currently in
UmbrellaEntrySchema (e.g. display_order: number, description: string,
tags: string[]). Should we anticipate these now or add when the renderer
lands?
Default: Add when renderer lands. PRODUCT.md prescribed 6 fields per
entry; honour that and grow when concrete consumer needs surface. Schema is
.strict() so additions are explicit and audited.
Alternative: Add description: string + display_order: number now (low-risk
additions that the renderer is likely to want). Pro: avoids a schema-migration
beat later. Con: speculative.
End of TECH.md. Subtask 31.3 status in_progress → done pending Checker
promotion. Subtask 31.4 PLAN.md dispatch may begin once TECH.md ratified.