Canonicalize Dispositions Before You Redesign Routing

disposition contracts fail-closed parsing legacy compatibility producer-consumer boundaries workflow routing Jul 28, 2026

When an actionable review disposition never reaches the router, the defect looks like a broken route. Often the lifecycle already works. The real failure is a producer–consumer contract mismatch: the review emits a disposition the controller does not recognise, so an actionable result collapses to unknown. Canonicalize the disposition format — with bounded legacy compatibility and fail-closed handling of malformed input — before you redesign routing that already existed.

The problem

A review stage can emit something that is semantically clear to a human — rework needed, accepted, blocked — while the controller still treats the outcome as unknown.

The first instinct is to blame the route. If actionable dispositions are not triggering rework, the routing logic must be incomplete. In a recent case that assumption was wrong. The rework path already existed. What failed was recognition: the producer wrote a disposition under one marker and vocabulary; the consumer only recognised another. An actionable result never entered the existing routing logic at all.

That distinction matters. Redesigning a lifecycle that already routes correctly wastes effort and risks widening the blast radius. The sharper diagnostic question is:

Is the lifecycle wrong, or is a producer/parser boundary preventing the lifecycle from receiving the correct disposition?

What actually happened

The work narrowed to three connected concerns:

  1. Define one canonical disposition format for the review that drives routing.
  2. Support a legacy alternate form only through a bounded compatibility path.
  3. Prove that recognised dispositions enter the existing routing logic.

The surface area looked small. The contract design was not. Hidden complexity included narrative lines that happened to contain the disposition keyword, empty or malformed canonical markers, duplicate and conflicting markers, punctuation and decorative formatting around values, legacy lines that looked like dispositions but were ordinary summaries, and precedence between a broken canonical marker and a tempting legacy fallback.

The first specification was close but not implementation-ready. An adversarial review found two genuine contradictions: whether an empty canonical marker should suppress legacy fallback, and whether the stated normalisation order could actually parse the decorated value used in its own acceptance criterion. Those were not stylistic nits. They were executable ambiguities that would have produced incorrect edge-case behaviour or ambiguous tests.

The revised specification separated four ideas that had been tangled together:

  • Syntactic presence of a canonical marker
  • Admissibility of its value
  • Bounded legacy compatibility for older alternate forms
  • Fail-closed behaviour for malformed or ambiguous input

The implementation stayed structural and local. It aligned the producer prompt, the parser, and the router contract. It did not add a one-off special case for a single historical artifact, and it did not redesign accepted, rework, blocked, or unknown routing. Those routes remained unchanged. The change was confined to disposition interpretation, proof, and documentation of the contract.

Proof covered the real seam: direct parser tests, a fixture shaped like the historical failure, an unmocked path from artifact through parser into routing, routing and stage-preservation tests, producer/parser vocabulary alignment, and broader validation of the affected package. Residual risks were recorded deliberately rather than widened into the same item — adjacent readers with related substring behaviour, unsupported heading-then-value forms, ambiguous legacy forms that still fail closed, and missing lint tooling where the intended static checks were already type checking plus tests.

A second operational lesson appeared beside the contract work. Durable execution-context state can lag behind repository operations. A branch or worktree transition can reach a review stage while durable item state still declares an older context. The audit stop that catches that mismatch is justified. The avoidable cost is the review cycle created when durable truth was not updated atomically with the transition.

The lesson

Route what arrived. If nothing actionable arrived, fix the disposition contract first.

For similar items:

  1. Draw the producer → artifact → parser → router path before proposing lifecycle changes. Confirm whether the downstream route already works.
  2. Define canonical and legacy forms separately. Do not let compatibility become an informal second grammar.
  3. Specify exact binary cases for empty markers, unsupported values, duplicate agreeing markers, duplicate conflicting markers, punctuation and formatting decoration, narrative lines containing marker words, and canonical-plus-legacy coexistence.
  4. Use at least one fixture shaped like the real historical failure. Synthetic clean examples will miss the shape that actually broke production.
  5. Test the real parser through the real routing seam. Mocked normalised verdicts are not enough for a controller-boundary change. Include a direct alignment test: the producer’s permitted vocabulary must be read and accepted by the real consumer.
  6. Keep compatibility narrow and fail closed. An empty or malformed canonical marker should not be “rescued” by unrelated legacy text.
  7. Record adjacent defects as residual risks rather than silently expanding the current item.

The broader principle

Machine-consumed governed artifacts need more than a documented format. They need an explicit marker, a closed vocabulary, deterministic normalisation, conflict rules, and fail-closed malformed-input behaviour — proven against the real consumer, not only against helpers.

Producer and consumer contracts should be tested against each other. Documentation that each side looks correct in isolation is not proof that dispositions survive the boundary.

Authority boundaries still matter after the contract is fixed. Auditors review. Durable state updates record current truth without erasing review history. Verification reruns the broadest affected seam rather than restating earlier claims. Human gates authorise what humans own. Merge verification confirms shared identity and checks; it does not silently become merge authority.

The operational complement is synchronisation: when branch or worktree authority changes, durable item state must update in the same transition. Governance checks that compare durable truth to repository truth are valuable only when that truth is allowed to stay current.

How to apply it

Before changing routing on a “disposition ignored” bug:

  • Ask whether the route already recognises the disposition you believe was emitted.
  • Inventory every form the producer may emit and every form the consumer will accept.
  • Decide empty-marker, conflict, decoration, and legacy-precedence behaviour in the specification, not in code review.
  • Require one unmocked artifact → parser → disposition-routing test.
  • Keep durable execution-context fields in lockstep with repository transitions so audits block real errors without manufacturing avoidable re-reviews.

Verdict-contract items are deceptively small. The code change can be local. Correct handling of malformed, decorated, conflicting, and legacy artifacts still requires precise specification — and that precision is what keeps you from redesigning a route that was never the defect.