ADR 0090: setup proves the project runs in a worktree, and smoke joins the known capabilities
Amendments.
- Vocabulary: current spellings are
done(formerlyfinish),[jobs]/[jobs.<name>](formerly[capabilities]/[checks.<name>]), known/customjob(formerly gatecapability/ customcheck), andconfig set-job(formerlyconfig set-capability/config set-check); the decision and reasoning are unchanged.- ADR 0152 — readiness clarification:
smokealso covers the essential shared config/runtime dependencies needed for that fast boot, and remains the project's configurable readiness check; discern-owned predictable writes are probed internally rather than adding a second[preflight]concept, and prerequisites unique to one custom command stay with that command.- ADR 0313 — probe identity and order: the structural probe now starts from the committed completion-marker
HEAD, retains Proof for that commit, and precedes the final main-checkout Gate. An uncreatable probe blocks proven completion; only the explicit--unprovenpath skips it and persists that distinction.- ADR 0317 — applicable denominator: a new known job still auto-enrols in setup assurance as applicable and absent. A project may explicitly declare an absent lifecycle not applicable; that declaration changes setup coverage only and never skips the job when configured.
- ADR 0322, one probe: the normal brief no longer creates a manual worktree. Readiness is configured and smoke-tested in the setup checkout;
setup doneremains the sole structural current-HEAD probe and owns teardown. The current readiness page is Step 7; Step 8 references below describe the former brief.
Status: accepted; builds on ADR 0075 (the staged handshake, stateless derived progress), ADR 0078 (stateless pages, derived per-step proof, the two-lane rule), ADR 0065 (setup done is a proven gate), ADR 0052 (worktree sibling placement), and ADR 0017 (the closed known-job vocabulary, tied to its satellites by the forcing functions of ADR 0051).
Context
§discern setup done proves the gate green (ADR 0065) — but it proves it in the main checkout, the one place discern tells every agent never to work. Every real task after setup happens in a linked worktree (ADR 0052/0058), and nothing guaranteed the project actually functions there.
Clean-room onboarding runs surfaced the gap concretely: setup completes green while runtime anchoring breaks silently in a copy. A worktree is a fresh checkout of the committed tree plus whatever [worktree.setup] produces — so anything a project needs that is not tracked in git does not travel: an untracked .env and its generated APP_KEY, an untracked dependency directory (vendor/, node_modules/), a dev server pointed at the original path, an app that assumes an absolute location. The machinery to fix any given project already existed — [worktree].steps (one-shot at creation), [worktree].ensure (convergent, every pass), [worktree.resources.<name>] (create/destroy), and [worktree].inherit_env — but setup neither configured it per-stack nor proved it. So "my app broke in the copy" could arrive weeks after a green setup, and the first real task was the moment the user discovered it — long after the capable setup agent was gone.
A second, smaller gap travelled with it: the gate had no known job meaning "the app boots." format / lint / typecheck / test / build all inspect code; none proves the thing runs. A boot check is also precisely what makes a worktree probe sharp — a fast command that fails when the app cannot start in a copy is exactly the anchoring detector the probe needs.
Decision
§Setup proves the project is viable inside a worktree, structurally; and smoke — a fast "does it boot?" check — joins the known jobs. One decision pair, because each half completes the other: the probe needs something that proves boot, and a boot check is only fully valuable if it is re-proven where the work lives.
smokeis a sixth known job, on theteststage. Its semantics are distinct from every other known job: not "run tool X" but "prove the app boots in THIS checkout" — a FAST, side-effect-light command (a framework's inspire/about, a CLI--version, a script that loads config and exits), never an e2e suite (that belongs in[jobs.<name>]). It rides theteststage rather than a dedicated one, so everydiscern done— which already runs inside worktrees during normal work — re-proves boot wherever the gate runs, at minimal blast radius. The forcing functions (ADR 0051) auto-enrol it into assurance,doctor, derived progress,config set-job, and the config codegen; a sixth known job honestly turns a five-wired config'sfullcoverage verdict intopartial(5 of 6).
setup donegains a fourth completion-proof leg: a worktree probe. A new lifecycle core,probeWorktreeViability, creates a THROWAWAY worktree exactly asdiscern startdoes, runs a caller-supplied probe inside it, and tears it down win or lose — reusing the same create (addWorktree+worktreeSetup) and removal (removeWorktreeSafely) cores the rest of the lifecycle uses, so the probe exercises precisely what a real worktree does: its[worktree.setup]steps/ensure, its resources, its env inheritance. Afterrefresh → doctor → donepasses in the main checkout, and when[features].worktreesis on,setup doneprobes a worktree and runs the gate core inside it. A red probe blocksdonewith a namedworktree_probestage and leaves[meta].bootstrappedunrecorded;worktree_provenrides the--jsonenvelope and the human render.
- The brief gains a Step 8 — "Prove the project runs in a worktree" — walking the agent through the same probe (
discern start→discern donein the copy) and the[worktree]wiring for whatever a fresh copy is missing, with a stack-keyed readiness table. The old Step 8 (record/summarise) becomes Step 9.
The load-bearing design choices, and the explicit nos:
- Derived proof, not self-report (ADR 0075/0078). The probe is run BY the engine and cannot be faked. The brief's Step 8 gets no derived per-step predicate in
setup_checks.ts: the agent's manual probe is a throwaway with no on-disk residue to check, sodone's structural probe is the real, unfakeable gate — the manual step is the agent's chance to get the wiring right first.
- The probe branches from the CURRENT HEAD, never
main. During setup the work sits on the unlandeddiscern-setupbranch; a probe frommainwould see a repo without discern at all. Branching from HEAD (the default ofgit worktree add) captures the agent's own just-authored config.
- Honest skips, never false claims. Worktrees-off skips the probe because there is no enabled worktree contract to prove. An uncreatable probe blocks ordinary completion.
--unprovenskips the proof legs and recordssetup_completion = "unproven";worktree_provenis true only when the probe actually ran green.
- Per-vendor provisioning stays out of discern's ethos. No Herd, vhost, or DNS integrations. A project's worktree needs are
[worktree.resources.<name>]the agent authors per-project, or a TODO for the user. Anything with cost or data implications (a database, a paid service) is a genuine decision left with the user, never wired silently — the two-lane rule (ADR 0078) applied to provisioning.
smokeis not mandatory. An absent known job stays "knowably absent" (ADR 0017); the probe runs the whole gate, so a project with nosmokestill has itsformat/lint/testre-proven in a worktree.
Consequences
§- The first real task can no longer be the moment the app breaks in a copy. A project whose app is env-anchored either genuinely works in its first real worktree, or its user was told during setup — while a capable agent was present to fix or record it — exactly what remains.
- The guarantee is continuously maintained, not asserted once. Normal work already runs
discern donein worktrees, andsmokenow re-proves boot there on every run — so a later change that breaks worktree viability is caught by the gate, not months later by a user.
- More surface, held by existing disciplines. A new lifecycle core, a fourth proof leg, a sixth known job, and a new brief step are new moving parts — kept coherent by the forcing functions (which auto-enrol
smoke), the probe reusing the create/remove cores, and the parser-validated brief spine.
setup donecreates and destroys one probe worktree. The bounded probe is skipped when worktrees are off or completion is explicitly--unproven.
Alternatives considered
§- A dedicated
smokegate stage instead of ridingtest. Rejected: adding a stage ripples throughSTAGES, every stage-group builder,prepare/testinclusion, and the forcing functions, for no behavioral gain —smokeon theteststage already runs on everydone, in and out of worktrees.
- Have the agent attest worktree viability (self-report). Rejected by the governing lesson of ADR 0075/0077/0078: everything structurally enforced happened reliably; everything merely advised degraded. The probe is engine-run.
- Branch the probe from
main. Rejected: pre-land,mainhas no discern at all, so the probe must branch from the current unlanded HEAD to see the agent's own config.
- Provision per-vendor (Herd, a vhost, a database) inside discern. Rejected as outside discern's stack-neutral ethos; those become
[worktree.resources.<name>]the agent authors, or a TODO for the user.
- A derived per-step predicate for the brief's Step 8. Rejected: the manual probe is a throwaway with no on-disk residue to check, and
done's structural probe is the real, unfakeable gate — a Step 8 predicate would be redundant and unprovable.