Lifecycle Facade Preflight Belongs at the Dispatch Boundary

cli preflight dispatch boundaries fail-closed behavior lifecycle facade operator experience Jul 01, 2026

A lifecycle entry command is where operators first touch a governed workflow. When it fails late — after scaffolding work has started, or after a missing worker CLI is only discovered at dispatch — the failure feels like a bug in the tool. It is often a boundary problem.

The fix is not simply "check whether the CLI exists." Preflight in a lifecycle facade must preserve cancellation semantics, avoid creating side effects before refusal, and derive executable resolution through the same policy chain the adapter already owns. Get any of that wrong and a small UX improvement becomes an architecture regression.

The problem

The surface symptoms were familiar: a missing worker CLI discovered too late, an editor fallback that defaulted to an unfamiliar tool, and title derivation that failed when intake had no explicit title marker.

Underneath, the work clustered around lifecycle-boundary hardening. A naive "check whether the worker CLI exists" fix could have duplicated route-to-CLI authority inside the facade, changed continuation semantics around human triage, or shifted adapter responsibility. The baseline audit treated the item as material precisely because it touched dispatch-path logic and a shared executable-resolution surface — not because the user-facing change looked large.

What actually happened

The most important design discovery was that placement mattered as much as the check itself.

Before human triage, the safe insertion point was after the confirmed-cancel branch and before the call that starts the item. Cancellation had to remain side-effect-free. A missing worker CLI must not create or scaffold an item before failing.

After human triage, preflight had hidden complexity. It had to apply only to delivery routes, skip blocked or intervention routes, and derive the executable slot through the existing route → stage → role → policy chain rather than a new hardcoded table. The facade checks availability; it does not become a second source of dispatch truth.

The fix stayed structural but bounded: PATH-only availability checks, a preferred lightweight editor fallback, syntactic title fallback with explicit rejection cases, and focused tests for both success and refusal paths. It deliberately avoided auth checks, auto-install, subprocess liveness redesign, route or gate semantics changes, and semantic title summarization.

Residual risks were named and accepted: environment variables for visual and editor preferences remain trusted without PATH probing; future custom-policy injection must keep facade preflight and adapter dispatch aligned; and first-stage-only post-triage preflight does not prove every downstream stage's CLI is available — that remains adapter responsibility.

The lesson

CLI preflight in a lifecycle facade is not just an availability check. It is a contract about when you refuse and who owns executable resolution.

Three invariants held the work narrow:

  1. Fail before side effects. If the operator cancels, nothing should have been created. If a worker CLI is missing, refuse before scaffolding.
  2. Reuse policy-derived resolution. Derive the executable through stage, role, and policy — never hardcode a post-triage CLI literal in the facade.
  3. Keep fallback contracts syntactic. Editor preference and title derivation are UX hardening, but title fallback must reject punctuation-only, non-ASCII-only, unsafe, or paragraph-like input. Stay syntactic; do not sneak in summarization.

Verification matched the contract: pre-triage missing-CLI refusal, cancel-skip behaviour, post-triage delivery preflight, blocked-route skip, no shadow CLI table, PATH-only helper behaviour, editor resolver order, and title acceptance/rejection cases.

The broader principle

Small developer-experience hardening in a lifecycle CLI can hide real architectural risk when it touches dispatch boundaries, adapter selection, and item scaffolding side effects.

The pattern that generalizes: classify facade UX fixes as boundary-sensitive until proven otherwise. If a change touches a lifecycle facade, shared execution surfaces, or route/dispatch boundaries, treat it like architecture work even when the diff looks small. Pin insertion points in the spec — especially around cancel and no-side-effect boundaries. Require tests for the positive path and the refusal path.

For CLI availability specifically, keep the contract narrow: PATH-only unless the item explicitly accepts auth, version probe, or install scope. For policy-derived behaviour, test that changing policy changes preflight behaviour without changing route literals.

How to apply it

When you add or move preflight in a lifecycle entry command:

  • Map the cancellation boundary first. Where does confirmed cancel exit? Preflight that creates state before that point is a regression.
  • Identify the policy chain for executable resolution. Stage → role → adapter → policy. The facade may check; it should not reimplement.
  • Scope post-triage preflight to the routes it owns. Delivery routes yes; blocked or intervention routes no. First dispatch stage only unless the item explicitly expands scope.
  • Pair editor and title fallbacks with rejection tests. Success cases alone are not enough for operator-facing intake.
  • Document accepted residual risks. What you are not checking (downstream stages, trusted env vars) should be explicit so the next change does not assume coverage that was never promised.

Helpful CLI UX and safe wrapper boundaries are related ideas. This piece is the dispatch-boundary slice: preflight belongs at the seam, must fail closed, and must not shadow the layer that actually runs the work.