Found at the very end of orchestrate run prd352-20260803-015333 — the render tools were the first thing to reject the checkpoint, after twelve slices had already been written with it.
Symptom
render_dashboard failed with RUN_STATE_INVALID on all twelve slices at once:
slices.353.resolvedRouting.fallback: Expected object, received null
slices.354.resolvedRouting.fallback: Expected object, received null
... (all 12)
render_graph and render_report fail identically, since they validate against the same schema.
Why the orchestrator wrote null
The spine describes the field as "the returned {model, variant, optional fallback}" and instructs the orchestrator to freeze that into resolvedRouting. For a slice with no configured fallback, null is the natural encoding of "there is no fallback" — and the sibling key fallbackTaken: false reads as its natural companion.
But the schema models "no fallback" as the key being absent, not as null. Nothing in the spine or in resolve_routing's output says so. resolve_routing itself returns fallbacks: [] — a plural array — while the checkpoint field is fallback, a singular optional object, so the orchestrator is already translating between two shapes with no stated mapping for the empty case.
Why it surfaced so late
validate_run_state is documented as the fast-fail guard — "call it right after writing the first run-state checkpoint and on every resume read, so a mis-shaped checkpoint fails in seconds rather than after expensive subagent work." That advice is sound and it would have caught this immediately.
But the spine only calls for it on resume, not after writing a slice's resolvedRouting on a fresh run. This run was a single fresh session, so it was never invoked until the end. Twelve slices' worth of work sat on an invalid checkpoint; only the fact that nothing else parses run-state.json through the schema — finalize_slice mutates raw JSON surgically — kept the run itself working.
Suggested fixes, in order of value
- Accept
null as equivalent to absent (.nullish() rather than .optional()). An optional field that rejects an explicit null is a foot-gun in a file a model writes by hand, and nothing is gained by the strictness.
- State the empty-case encoding where the orchestrator reads its instructions — the spine says to freeze "the optional fallback" without saying what to write when there is none. Either form is fine once it is written down.
- Have the spine call
validate_run_state after the first slice's resolvedRouting is written on a fresh run, not only on resume. The tool's own description already recommends exactly this; the spine only wires it into the resume path.
Workaround used
Deleted the fallback key wherever it was null, re-ran validate_run_state (valid), and re-rendered all three artifacts successfully. No slice work was affected.
Severity
Low for the run, higher for trust: the failure is late, loud and total — all three artifacts, all slices at once — and it is reachable by following the spine's own wording.
Found at the very end of orchestrate run
prd352-20260803-015333— the render tools were the first thing to reject the checkpoint, after twelve slices had already been written with it.Symptom
render_dashboardfailed withRUN_STATE_INVALIDon all twelve slices at once:render_graphandrender_reportfail identically, since they validate against the same schema.Why the orchestrator wrote
nullThe spine describes the field as "the returned
{model, variant, optional fallback}" and instructs the orchestrator to freeze that intoresolvedRouting. For a slice with no configured fallback,nullis the natural encoding of "there is no fallback" — and the sibling keyfallbackTaken: falsereads as its natural companion.But the schema models "no fallback" as the key being absent, not as
null. Nothing in the spine or inresolve_routing's output says so.resolve_routingitself returnsfallbacks: []— a plural array — while the checkpoint field isfallback, a singular optional object, so the orchestrator is already translating between two shapes with no stated mapping for the empty case.Why it surfaced so late
validate_run_stateis documented as the fast-fail guard — "call it right after writing the first run-state checkpoint and on every resume read, so a mis-shaped checkpoint fails in seconds rather than after expensive subagent work." That advice is sound and it would have caught this immediately.But the spine only calls for it on resume, not after writing a slice's
resolvedRoutingon a fresh run. This run was a single fresh session, so it was never invoked until the end. Twelve slices' worth of work sat on an invalid checkpoint; only the fact that nothing else parsesrun-state.jsonthrough the schema —finalize_slicemutates raw JSON surgically — kept the run itself working.Suggested fixes, in order of value
nullas equivalent to absent (.nullish()rather than.optional()). An optional field that rejects an explicitnullis a foot-gun in a file a model writes by hand, and nothing is gained by the strictness.validate_run_stateafter the first slice'sresolvedRoutingis written on a fresh run, not only on resume. The tool's own description already recommends exactly this; the spine only wires it into the resume path.Workaround used
Deleted the
fallbackkey wherever it wasnull, re-ranvalidate_run_state(valid), and re-rendered all three artifacts successfully. No slice work was affected.Severity
Low for the run, higher for trust: the failure is late, loud and total — all three artifacts, all slices at once — and it is reachable by following the spine's own wording.