validate_plan is advertised on every pass and wired by NO handler — and augment offers modify_node/remove_node it cannot serve
Found while running MOTIR-3368 — the same defect class, in three more tools. Verified on origin/main @ 5441061 by enumerating the tool-surface ⟷ sink cross-product (the guard MOTIR-3368 adds, tests/toolSurfaceSinkParity.test.ts), which reports these SIX gaps and nothing else.
MOTIR-3368 fixed the CAPTURE pair (log_planning_mistake / log_planning_bug), which was unwired on three handlers out of four. Running its own guard surfaced that the pair was not the only instance:
| tool | advertised on | sink method | implemented by |
|---|---|---|---|
validate_plan | both phases, all four operations | validatePlan | nobody |
modify_node | skeleton phase, all four | modifyNode | all except augment |
remove_node | skeleton phase, all four | removeNode | all except augment |
Root cause
Identical to MOTIR-3368's: the tool SURFACE is a list in src/llm/treeGeneration.ts (SKELETON_PHASE_TOOLS / DEEPEN_PHASE_TOOLS) and the CAPABILITY is a set of OPTIONAL methods on each handler's GenerationSink. grep -ran 'validatePlan\b' src/ finds the interface declaration (treeGeneration.ts:421), the executor's guard and call (:2582, :2588) — and no GenerationSink literal implementing it. There are exactly four sinks (generateTree.ts, replan.ts, augment.ts, expandItem.ts); none has the method.
So validatePlanExecutor takes its if (!sink.validatePlan) arm on every call, on every operation, since Story 7.28 shipped, and answers projected-plan validation is unavailable in this run. tests/treeGeneration.test.ts:968 asserts that arm degrades gracefully — the absence is tested as a shape, never as a wiring gap.
The projected-finishability verdict itself is real and IS computed — after the pass, by validateCandidatePlan / validateCandidateForest against the captured planId (src/jobs/validateCandidatePlan.ts → src/core/planValidationClient.ts). What has never once been available is the mid-pass self-correction the tool exists for: "Returns { valid, blockers } so the strategy self-corrects BEFORE materialize" (treeGeneration.ts, the validatePlan doc comment). The planner is told it can check its own plan and then told it cannot.
augment's missing modifyNode / removeNode is the same shape, narrower: its sink is additions-only while its surface offers reconciliation of committed items.
Acceptance criteria
validate_planis DECIDED for all four handlers and the decision is executed, not left implicit — either every sink implementsvalidatePlan(routed throughplanValidationClient.validatePlanwith the job's capturedplanId, which is what the executor'stargetKeyis resolved against), or the tool is removed fromSKELETON_PHASE_TOOLS/DEEPEN_PHASE_TOOLSand its executor deleted. A tool advertised and unserved is the state this card exists to end.- If it is wired: a pass that calls
validate_planmid-generation gets a real verdict, asserted against a real database, and the pre-markPlannedvalidateCandidatePlancall is stated to be either redundant or complementary — whichever, on the record. augment'smodify_node/remove_nodeare decided the same way in the same change: wired to the sameresolveExistingTarget+addProposalsshape the other three use, or removed from the surface foraugmentwith the reason stated (an augment adds to a tree and may not re-scope committed work).tests/toolSurfaceSinkParity.test.ts'sKNOWN_GAPSis EMPTY when this lands — the guard asserts the gap set EXACTLY, so closing a gap without deleting its row turns the suite red, by design. The card is not done while any row remains.- The
TOOL_SINK_REQUIREMENTSmap keeps a row for every tool that survives, so a future tool inherits the check.
Context refs
motir-aisrc/llm/treeGeneration.ts—GenerationSink.validatePlan(:421),TOOL_SINK_REQUIREMENTS,validatePlanExecutor(:2575),SKELETON_PHASE_TOOLS/DEEPEN_PHASE_TOOLS.motir-aisrc/jobs/handlers/{generateTree,replan,augment,expandItem}.ts— the four sinks.motir-aisrc/jobs/validateCandidatePlan.ts·src/core/planValidationClient.ts— the post-pass verdict that DOES run.motir-aitests/toolSurfaceSinkParity.test.ts— the guard that enumerates these gaps (added by MOTIR-3368).