Skip to main content

Test Analysis: Convoy Stage & Launch

Testing plan for gt convoy stage and gt convoy launch (PRD: stage-launch/prd.md).


Critical Invariants

#InvariantCategoryBlast RadiusCurrently Tested?
I-1Cycles in blocking deps MUST prevent convoy creationdatahighno
I-2Beads with no valid rig MUST prevent convoy creationdatahighno
I-3Errors MUST produce non-zero exit code and no side effectssafetyhighno
I-4Wave 1 contains ONLY tasks with zero unsatisfied blocking depsdatahighno
I-5parent-child deps NEVER create execution edgesdatahighpartial (feeder tests)
I-6Epics and non-slingable types are NEVER placed in wavesdatahighpartial (IsSlingableType)
I-7Daemon MUST NOT feed issues from staged:* convoyssafetyhighno
I-8--launch on staged_warnings MUST require --forcesafetymediumno
I-9Re-staging a convoy MUST NOT create duplicatesdatamediumno
I-10--json output MUST be parseable JSON on stdoutdatamediumno
I-11Wave computation is deterministic for the same input DAGdatamediumno
I-12All beads validated to exist before any convoy creationsafetymediumno
I-13Launch dispatches ONLY Wave 1, not subsequent wavessafetyhighno
I-14Dispatch failure for one task does NOT abort remaining Wave 1 taskslivenessmediumno
I-15gt convoy launch <id> on already-open convoy MUST errorsafetylowno

Failure Modes

Input failures

#FailureLikelihoodDetectionRecoveryCovered?
F-01Nonexistent bead ID passedhighimmediate (bd show fails)user fixes typono
F-02Empty args (no bead IDs)mediumimmediate (arg validation)usage helpno
F-03Mixed input types (epic + task IDs)mediumambiguousdetect and errorno
F-04Bead ID is actually a rig namelowprefix checkerror with hintno
F-05Convoy ID passed to stage that is already openmediumstatus checkre-analyze and warnno
F-06Flag-like bead ID (--verbose)lowimmediaterejectno

State failures

#FailureLikelihoodDetectionRecoveryCovered?
F-07Cycle in blocks deps (A blocks B blocks A)mediumcycle detectionreport path, refuse stagingno
F-08Orphan tasks (no parent, no deps) in epic treemediumreachability checkwarn, still stageno
F-09Stale convoy: re-stage finds beads deleted since first stagelowbd show failserror per-beadno
F-10Concurrent staging of same beads by two userslowno detectionlast write wins (acceptable)no
F-11Partial dep-add failure during convoy creationmediumper-dep error checkpartial tracking (existing pattern)partial
F-12Epic has no children (empty DAG)lowimmediateerror: nothing to stageno

Dependency failures

#FailureLikelihoodDetectionRecoveryCovered?
F-13bd binary not on PATHlowexec errorclear error messageno
F-14bd dep list returns malformed JSONlowjson.Unmarshal failserror with raw outputno
F-15routes.jsonl missing or corruptmediumparse errorerror: cannot resolve rigspartial
F-16gt sling fails during launch dispatchmediumexec errorcontinue to next task, reportno
F-17Dolt store unavailable during re-stagelowstore open failserrorno

Timing failures

#FailureLikelihoodDetectionRecoveryCovered?
F-18Bead closed between stage and launchmediumstatus check at launchskip closed, logno
F-19Rig parked between stage and launchmediumpark check at launchskip parked rig, warnno
F-20New deps added between stage and launchlowre-analyze at launchwaves may differ (acceptable)no

Configuration failures

#FailureLikelihoodDetectionRecoveryCovered?
F-21Not inside a workspace (no mayor/rig/)mediumworkspace.FindFromCwdclear errorno
F-22Bead prefix maps to town root (path=".")lowrig resolve returns ""error: no valid rigpartial
F-23Unknown bead prefix (not in routes.jsonl)mediumGetRigNameForPrefix ""error with suggestionpartial

Test Strategy Matrix

Unit tests (package cmd, convoy)

These test pure logic in isolation using in-memory data. No Dolt, no bd stubs.

IDTestFunction Under TestFailure ModesInvariantsPriority
U-01Cycle detection: simple A->B->AdetectCycles(dag)F-07I-1P0
U-02Cycle detection: no cycle (linear chain)detectCycles(dag)F-07I-1P0
U-03Cycle detection: self-loop (A blocks A)detectCycles(dag)F-07I-1P0
U-04Cycle detection: diamond (no cycle)detectCycles(dag)F-07I-1P0
U-05Cycle detection: long chain with back-edgedetectCycles(dag)F-07I-1P0
U-06Wave computation: 3 independent tasks -> all Wave 1computeWaves(dag)--I-4P0
U-07Wave computation: linear chain A->B->C -> 3 wavescomputeWaves(dag)--I-4P0
U-08Wave computation: diamond deps -> 3 wavescomputeWaves(dag)--I-4P0
U-09Wave computation: mixed parallel + serialcomputeWaves(dag)--I-4P0
U-10Wave computation: deterministic output (run 100x)computeWaves(dag)--I-11P1
U-11Wave computation: excludes epics from wavescomputeWaves(dag)--I-6P0
U-12Wave computation: excludes non-slingable typescomputeWaves(dag)--I-6P0
U-13Wave computation: parent-child deps don't create edgescomputeWaves(dag)--I-5P0
U-14Wave computation: empty DAG -> errorcomputeWaves(dag)F-12--P1
U-15DAG construction: blocks deps create edgesbuildDAG(beads)--I-4P0
U-16DAG construction: conditional-blocks create edgesbuildDAG(beads)--I-4P1
U-17DAG construction: waits-for creates edgesbuildDAG(beads)--I-4P1
U-18DAG construction: parent-child recorded but no edgebuildDAG(beads)--I-5P0
U-19DAG construction: related/tracks deps ignoredbuildDAG(beads)----P2
U-20Error categorization: cycle is error (not warning)categorize(findings)--I-1, I-3P0
U-21Error categorization: no-rig is errorcategorize(findings)--I-2, I-3P0
U-22Error categorization: parked rig is warningcategorize(findings)----P1
U-23Error categorization: orphan is warningcategorize(findings)----P1
U-24Error categorization: missing integration branch is warningcategorize(findings)----P2
U-25Status selection: no errors + no warnings -> staged_readychooseStatus(errs, warns)--I-3P0
U-26Status selection: warnings only -> staged_warningschooseStatus(errs, warns)----P0
U-27Status selection: any error -> no creationchooseStatus(errs, warns)--I-3P0
U-28Tree display: flat task list -> no tree, just listrenderTree(dag)----P2
U-29Tree display: epic with nested sub-epicsrenderTree(dag)----P2
U-30Wave table display: includes blockers columnrenderWaveTable(waves)----P2
U-31JSON output: valid JSON, all required fields presentrenderJSON(result)--I-10P1
U-32JSON output: errors array populated on failurerenderJSON(result)--I-10P1
U-33JSON output: convoy_id empty when errors foundrenderJSON(result)--I-3, I-10P1
U-34Error categorization: cross-rig routing mismatch is warningcategorize(findings)----P1
U-35Error categorization: capacity estimation is warningcategorize(findings)----P2
U-36Tree node rendering: includes bead ID, title, type, status, rigrenderTreeNode(node)----P1
U-37Tree node rendering: blocked tasks show blockers inlinerenderTreeNode(node)----P1
U-38Wave table summary line: total waves, tasks, parallelism per waverenderWaveTable(waves)----P1
U-39Error output: each error includes suggested fix textrenderErrors(errs)--I-3P1

Integration tests (bd stub + workspace, package cmd)

These test the full command flow with stubbed bd and gt binaries.

IDTestUser StoryFailure ModesInvariantsPriority
IT-01Stage epic: walks parent-child tree, collects all descendantsUS-001F-01I-12P0
IT-02Stage epic: nonexistent child fails entire stageUS-001, US-002F-01, F-09I-3, I-12P0
IT-03Stage task list: analyzes only given tasksUS-001----P0
IT-04Stage convoy: reads tracked beads from existing convoyUS-001F-05--P1
IT-05Stage with cycle: refuses to create convoyUS-002F-07I-1, I-3P0
IT-06Stage with no valid rig: refuses to create convoyUS-002F-23I-2, I-3P0
IT-07Stage with errors: exit code non-zero, no bd create calledUS-002--I-3P0
IT-08Stage with parked rig: creates convoy as staged_warningsUS-003F-19--P1
IT-09Stage epic with unreachable task (not in descendant tree): warns, creates staged_warningsUS-003F-08--P1
IT-10Stage clean: creates convoy as staged_readyUS-007----P0
IT-11Stage convoy: tracks all slingable beads via depsUS-007F-11--P0
IT-12Stage convoy: description includes wave count + timestampUS-007----P2
IT-13Re-stage: updates status, no duplicate convoyUS-007F-09I-9P1
IT-14Launch staged_ready: transitions to open, dispatches Wave 1US-008F-16I-13P0
IT-15Launch staged_warnings without --force: errorsUS-008--I-8P0
IT-16Launch staged_warnings with --force: dispatchesUS-008--I-8P1
IT-17Launch dispatch failure: continues to next taskUS-008F-16I-14P1
IT-18Launch already-open convoy: errorsUS-010F-05I-15P1
IT-19gt convoy launch <epic> = gt convoy stage <epic> --launchUS-010----P1
IT-20gt convoy launch <task1> <task2> works as aliasUS-010----P1
IT-21--json output: valid JSON with all fieldsUS-011--I-10P1
IT-22--json output: no human-readable text on stdoutUS-011--I-10P1
IT-23Stage with --launch: full end-to-end (stage + dispatch)US-008--I-4, I-13P0
IT-24Empty args: usage error--F-02--P2
IT-25Flag-like bead ID: rejected--F-06--P2
IT-26Stage with missing integration branch: warns, creates staged_warningsUS-003----P1
IT-27Stage with cross-rig routing mismatch: warns, includes in outputUS-003----P1
IT-28Stage with capacity warning: informational, creates staged_warningsUS-003----P2
IT-29Launch output: convoy ID + gt convoy status command printedUS-009----P1
IT-30Launch output: each dispatched task shows polecat nameUS-009----P1
IT-31Launch output: TUI hint (gt convoy -i) printedUS-009----P2
IT-32Launch output: daemon feed explanation printedUS-009----P2
IT-33Launch staged_ready convoy: skips re-analysis, dispatches directlyUS-010----P0
IT-34--json with errors: non-zero exit codeUS-011--I-3, I-10P1
IT-35Mixed input (epic + task IDs): errors with hint--F-03--P1
IT-36Bead ID that looks like rig name: errors with hint--F-04--P2
IT-37bd binary not on PATH: clear error message--F-13--P2
IT-38Malformed JSON from bd: error with raw output--F-14--P2
IT-39Not inside workspace: clear error--F-21--P2
IT-40Display ordering: tree printed before wave tableUS-005, US-006----P2
IT-41Stage clean: convoy ID printed to stdoutUS-007----P1
IT-42Bead closed between stage and launch: skip closed, logUS-008F-18--P1
IT-43Stage epic with isolated task (no blocking deps from other staged tasks): warns (epic input only — task-list input never warns for isolation)US-003F-08--P1
IT-44Stage with missing/corrupt routes.jsonl: errors with clear messageUS-002F-15I-2P2

Integration tests (real Dolt store, package convoy)

These test DAG walking and wave computation against a real beads store.

IDTestUser StoryFailure ModesInvariantsPriority
DS-01Epic tree walk: collects all descendants (3 levels deep)US-001----P0
DS-02Epic tree walk: handles cross-rig external referencesUS-001----P1
DS-03Wave computation with real deps: 3-wave linear chainUS-004--I-4, I-5P0
DS-04Wave computation with real deps: parallel + serial mixedUS-004--I-4P0
DS-05Cycle detection with real store: 2-node cycleUS-002F-07I-1P0
DS-06isIssueBlocked integration: blocked task not in Wave 1US-004--I-4P1
DS-07Event-driven path skips staged_ready convoy (CheckConvoysForIssuefeedNextReadyIssue)US-007--I-7P0
DS-08Event-driven path skips staged_warnings convoyUS-007--I-7P0
DS-09Stranded scan path excludes staged convoys (findStrandedConvoys queries --status=open)US-007--I-7P0
DS-10Daemon feeds convoy after status transitions from staged_ready to openUS-008--I-7P1

Snapshot tests (package cmd)

Capture and verify console output format stability.

IDTestUser StoryPriority
SN-01Tree display: epic with 2 sub-epics, 5 tasksUS-005P2
SN-02Wave table: 3 waves with blockers columnUS-006P2
SN-03Launch output: convoy ID, wave summary, polecat list, hintsUS-009P2
SN-04Error output: cycle path formattingUS-002P2
SN-05Warning output: parked rig + orphan listUS-003P2
SN-06JSON output: full structureUS-011P2

Property tests (package cmd or convoy)

Prove invariants hold over randomized DAGs.

IDTestInvariantPriority
PT-01Random acyclic DAG: wave computation terminates, Wave 1 non-emptyI-4P1
PT-02Random acyclic DAG: every task appears in exactly one waveI-4P1
PT-03Random acyclic DAG: no task appears before its blocker's waveI-4P1
PT-04Random DAG with cycle: cycle detection always finds itI-1P1
PT-05Random acyclic DAG: wave assignment is deterministic (same input -> same output)I-11P2
PT-06Random acyclic DAG: parent-child edges never affect wave assignmentI-5P1

Harness Scorecard

Assessment of existing test infrastructure for stage/launch needs.

DimensionScore (1-5)Key Gap
Fixtures & Setup3setupTownWithBdStub and setupTestStore exist but no DAG fixture builder; each test hand-rolls bead graphs
Isolation4Tests use temp dirs, PATH injection, chdir with cleanup; parallel-safe except for os.Chdir (serializes cmd tests)
Observability3bd stub logs capture commands; but no structured assertion on DAG shape or wave output
Speed4Unit tests <1s, Dolt store tests ~0.1s each, full cmd suite ~10s; acceptable
Determinism4No known flaky tests; Dolt store tests skip when unavailable rather than flake

Key gaps for stage/launch

  1. No DAG fixture builder. Every test that needs a bead graph with deps will hand-roll bd show/dep list JSON responses. A shared helper that declares a graph structure and generates the bd stub script would eliminate duplication and make tests readable.

  2. No wave assertion helpers. Tests will need to verify "task X is in Wave N" and "Wave N has exactly K tasks." Without a helper, every test parses raw output or maintains parallel data structures.

  3. os.Chdir serialization. The setupTownWithBdStub pattern uses os.Chdir which is process-global. This prevents t.Parallel() on any test that needs workspace resolution. Stage/launch tests will inherit this limitation.

  4. No snapshot infrastructure. Display tests (tree, wave table, launch output) need golden-file comparison. Currently no snapshot tooling exists in the repo.


Tooling Recommendations

DAG Fixture Builder

Problem: Every integration test that involves beads with deps must manually construct bd stub scripts with hardcoded JSON. This is error-prone, verbose (~30 lines per test), and makes the graph structure invisible.

Proposal: A dagBuilder test helper that accepts a declarative graph definition and produces:

  • A bd stub script that returns correct JSON for show, dep list, and list commands
  • A routes.jsonl file for rig resolution
  • Optionally, real Dolt store issues + deps (for DS-* tests)
dag := newTestDAG(t).
Epic("epic-1", "Root Epic").
Task("task-1", "First task", rig("gastown")).ParentOf("epic-1").
Task("task-2", "Second task", rig("gastown")).ParentOf("epic-1").BlockedBy("task-1").
Task("task-3", "Third task", rig("gastown")).ParentOf("epic-1").BlockedBy("task-2")
// dag.BdStubScript() -> shell script
// dag.RoutesJSONL() -> routes file content
// dag.Populate(store) -> creates issues + deps in real Dolt store

Compound Value: Every new convoy test (stage, launch, future milestones) reuses this. The graph definition IS the test documentation.

Exists Today? No. molecule_dag.go has buildDAG but it operates on live beads, not test fixtures.

Priority: P0 -- build this before writing integration tests.

Wave Assertion Helper

Problem: Verifying wave assignments requires either parsing console output or maintaining a parallel map[string]int and comparing element-by-element. Both are fragile.

Proposal: A waveAssert helper:

waveAssert(t, waves).
Wave(1, "task-1", "task-3"). // unordered within wave
Wave(2, "task-2").
Wave(3, "task-4", "task-5").
NoTask("epic-1"). // epics excluded
Total(3) // 3 waves

Compound Value: Used by every wave computation test (U-06 through U-14, IT-14, IT-23, DS-03, DS-04, PT-01 through PT-06).

Exists Today? No.

Priority: P1 -- build alongside wave computation implementation.

Property Test Harness

Problem: Wave computation correctness is hard to exhaustively verify with hand-crafted cases. Random DAGs expose edge cases (wide graphs, deep chains, disconnected components) that humans miss.

Proposal: Use testing/quick or a custom random DAG generator:

func randomAcyclicDAG(seed int64, nodes, edges int) *testDAG { ... }
func randomDAGWithCycle(seed int64, nodes int) *testDAG { ... }

Combined with property assertions: "every task in exactly one wave", "no task before its blocker", "cycle always detected."

Compound Value: Catches regressions from any future DAG algorithm changes. Seed is logged on failure for reproduction.

Exists Today? No random graph generators exist. testing/quick is available in stdlib.

Priority: P1 -- write after core wave computation is stable.


Test Priority Summary

P0 — Must ship with the feature (38 tests)

TierCountTests
Unit19U-01..U-09, U-11, U-12, U-13, U-15, U-18, U-20, U-21, U-25, U-26, U-27
Integration (bd stub)12IT-01, IT-02, IT-03, IT-05, IT-06, IT-07, IT-10, IT-11, IT-14, IT-15, IT-23, IT-33
Integration (Dolt)7DS-01, DS-03, DS-04, DS-05, DS-07, DS-08, DS-09

P1 — Ship within 1 week of feature (42 tests)

TierCountTests
Unit14U-10, U-14, U-16, U-17, U-22, U-23, U-31, U-32, U-33, U-34, U-36, U-37, U-38, U-39
Integration (bd stub)20IT-04, IT-08, IT-09, IT-13, IT-16, IT-17, IT-18, IT-19, IT-20, IT-21, IT-22, IT-26, IT-27, IT-29, IT-30, IT-34, IT-35, IT-41, IT-42, IT-43
Integration (Dolt)3DS-02, DS-06, DS-10
Property5PT-01, PT-02, PT-03, PT-04, PT-06

P2 — Nice to have (25 tests)

TierCountTests
Unit6U-19, U-24, U-28, U-29, U-30, U-35
Integration (bd stub)12IT-12, IT-24, IT-25, IT-28, IT-31, IT-32, IT-36, IT-37, IT-38, IT-39, IT-40, IT-44
Snapshot6SN-01 through SN-06
Property1PT-05

Total: 105 tests (38 P0 + 42 P1 + 25 P2)


Anti-Patterns to Watch For

PatternRisk in Stage/LaunchMitigation
Sleep-based syncDaemon integration tests (DS-09, DS-10) wait for convoy status transitions; temptation to add delaysUse completion channels or check bd state, never time.Sleep in tests
God fixturesOne massive DAG used by all testsEach test declares its own DAG via the builder; share nothing
Assertion-free tests"It didn't panic" is not a testEvery test asserts on: wave assignment, error/warning categorization, or bd commands logged
Snapshot overloadTempting to snapshot all console outputUse snapshots only for display format (SN-*); use specific assertions for logic (waves, errors)
Test-after-the-factWriting tests to hit coverage after implWrite U-01 through U-13 (cycle + wave) BEFORE implementing; they define the contract
Environment couplingTests relying on real bd or gt on PATHAlways use stub binaries in bin/ with PATH override; never depend on system binaries

Next Actions

  1. Build dagBuilder test helper (P0 tooling) -- shared fixture builder for declaring bead graphs. Output: bd stub script, routes.jsonl, optional Dolt population.

  2. Write cycle detection + wave computation unit tests (U-01 through U-13) -- these define the algorithm contract before implementation begins. TDD.

  3. Implement cycle detection + wave computation -- detectCycles(), computeWaves() as pure functions on in-memory DAG. No I/O.

  4. Write integration tests for staging flow (IT-01 through IT-07, IT-10, IT-11) -- full command flow with bd stubs.

  5. Implement gt convoy stage -- bead validation, DAG construction, analysis, convoy creation.

  6. Write daemon integration tests (DS-07, DS-08, DS-09) -- staged convoys must not be fed via either feeding path (event-driven + stranded scan).

  7. Implement daemon staged-convoy guard -- add staged-status check in CheckConvoysForIssue (event-driven path, operations.go). Stranded scan path (convoy.go:1231) is already safe via --status=open query. Update ensureKnownConvoyStatus and validateConvoyStatusTransition for staged statuses.

  8. Write launch tests (IT-14, IT-15, IT-23) -- dispatch Wave 1 only.

  9. Implement gt convoy launch -- status transition, Wave 1 dispatch.

  10. Build waveAssert helper + property tests (PT-01 through PT-04) -- prove invariants over random graphs.


PRD Cross-Reference

Every acceptance criterion in the PRD mapped to its covering test(s).

US-001: Bead validation and DAG construction

ACCriterionTests
1bd show each bead, error if missingIT-01, IT-02
2Epic: walks full parent-child tree recursivelyIT-01, DS-01, DS-02
3Task list: analyzes only given tasksIT-03
4Convoy: reads tracked beads via dep listIT-04
5DAG from blocks/conditional-blocks/waits-forU-15, U-16, U-17
6parent-child: recorded, no execution edgesU-13, U-18, PT-06

US-002: Error detection

ACCriterionTests
1Cycles detected with cycle pathU-01..U-05, IT-05, DS-05, SN-04, PT-04
2No valid rig detectedIT-06, U-21
3Errors: no convoy, no status changesIT-07, U-27
4Error output: bead IDs + suggested fixU-39, SN-04
5Non-zero exit code on errorsIT-07

US-003: Warning detection

ACCriterionTests
1Orphan detection (epic input only: unreachable from descendant tree + isolated in wave graph)IT-09, IT-43, U-23
2Missing integration branchesU-24, IT-26
3Parked rigsIT-08, U-22
4Cross-rig routing warningsU-34, IT-27
5Capacity estimationU-35, IT-28
6Warnings distinguished from errorsU-20..U-24
7Warnings only -> staged_warningsIT-08, U-26

US-004: Wave computation

ACCriterionTests
1Wave 1 = no unsatisfied blocking depsU-06..U-09, DS-03, PT-01..PT-03
2Wave N+1 = blockers all in earlier wavesU-07, U-08, PT-03
3No blocking deps = Wave 1U-06, PT-01
4Epics/non-slingable excludedU-11, U-12
5Full descendant set or just given tasksIT-01 + IT-03 (input), U-06..U-09 (waves)

US-005: DAG tree display

ACCriterionTests
1Epic: full tree with indentationU-29, SN-01
2Each node: bead ID, title, type, status, rigU-36, SN-01
3Sub-epics visually distinctSN-01
4Blocked tasks show blockers inlineU-37, SN-01
5Task list: flat listU-28
6Tree displayed before wave tableIT-40

US-006: Wave dispatch plan display

ACCriterionTests
1Table: wave #, IDs, titles, rig, blockersU-30, SN-02
2Displayed after DAG treeIT-40
3Summary line: waves, tasks, parallelismU-38, SN-02
4Warnings after wave table for staged_warningsSN-05

US-007: Convoy creation with staged status

ACCriterionTests
1No errors, no warnings -> staged_readyIT-10, U-25
2Warnings only -> staged_warningsIT-08, U-26
3Errors -> no convoyIT-07, U-27
4Tracks all slingable beadsIT-11
5Description: wave count, task count, timestampIT-12
6Convoy ID printed to consoleIT-41
7Re-staging updates status, no duplicateIT-13

US-008: Launch — dispatch Wave 1

ACCriterionTests
1Transitions staged_ready -> openIT-14
2staged_warnings requires --forceIT-15, IT-16
3Dispatches Wave 1 via internal Go dispatchIT-14, IT-23
4Subsequent waves NOT dispatchedIT-14, IT-23
5Dispatch failure continuesIT-17

US-009: Launch console output

ACCriterionTests
1Convoy ID + status commandIT-29, SN-03
2Wave summarySN-03
3Each Wave 1 task with polecatIT-30, SN-03
4TUI hint (gt convoy -i)IT-31, SN-03
5Daemon feeds explanationIT-32, SN-03

US-010: gt convoy launch as alias

ACCriterionTests
1launch epic = stage epic --launchIT-19
2launch task1 task2 worksIT-20
3launch staged_ready: no re-analysisIT-33
4launch staged_warnings requires --forceIT-15, IT-16
5launch already-open errorsIT-18

US-011: JSON output

ACCriterionTests
1--json outputs JSON to stdoutIT-21, U-31
2All required fields presentU-31, U-32, U-33, SN-06
3Human-readable suppressedIT-22
4Non-zero exit code on errors (JSON mode)IT-34

Failure modes coverage

FMFailureTests
F-01Nonexistent bead IDIT-01, IT-02
F-02Empty argsIT-24
F-03Mixed input typesIT-35
F-04Bead ID looks like rig nameIT-36
F-05Convoy already openIT-18
F-06Flag-like bead IDIT-25
F-07Cycle in blocks depsU-01..U-05, IT-05, DS-05, PT-04
F-08Orphan tasksIT-09, IT-43
F-09Stale convoy on re-stageIT-02, IT-13
F-10Concurrent staging(accepted risk, no test)
F-11Partial dep-add failureIT-11 (existing pattern)
F-12Epic has no childrenU-14
F-13bd not on PATHIT-37
F-14Malformed JSON from bdIT-38
F-15routes.jsonl missingIT-06, IT-44
F-16gt sling fails during launchIT-17
F-17Dolt unavailable(skips via setupTestStore)
F-18Bead closed between stage and launchIT-42
F-19Rig parked between stage and launchIT-08
F-20New deps between stage and launch(accepted risk, re-analysis handles)
F-21Not inside workspaceIT-39
F-22Bead prefix maps to town rootIT-06 (existing coverage)
F-23Unknown bead prefixIT-06