# Task-Worktree Provisioning Design

audience: AI coding agents first. Owner-readable plain English in Behavior lines.

**Goal:** every v2 task worktree is fully provisioned (dependencies installed, buildable) BEFORE any seat is dispatched into it — a task must never fail a gate because the harness handed it a bare checkout.

**Observed defect (2026-07-29, live run):** v2 creates task worktrees via `git worktree` and dispatches immediately. `node_modules` is not in git → `astro build` dies with `sh: 1: astro: not found`. Of seven live worktrees, 3 had `node_modules`, 4 didn't. Tasks touching only tests/scripts pass; any task needing the toolchain fails. Failure is misattributed to the agent and burns retry/fixer ladder attempts on an infra defect.

**Slug:** `worktree-provisioning` · target repo: overdeck, canonical engine source `modules/harness/v2/`.

---

## 1. Ground rules

- Fail-closed: an unprovisioned worktree is NEVER dispatched into. Provisioning failure is an INFRA failure, never charged to the agent.
- State on disk: provisioning success = receipt file in the worktree; absence = not provisioned.
- Surgical: change only worktree setup (`v2/scratch.js` / worktree-creation path in `v2/run.js`), failure classification, and tests. No daemon, no cache service.

## 2. Provision-command resolution ladder (stop at first rung)

1. Plan `meta.provision_cmd` — explicit per-plan override, run verbatim from worktree root.
2. Repo-owned hook `<repoRoot>/.harness/provision.sh` — project defines its own provisioning; run from worktree root.
3. Lockfile auto-detect at worktree root: `pnpm-lock.yaml` → `pnpm install --frozen-lockfile --prefer-offline` · `package-lock.json` → `npm ci` · `yarn.lock` → `yarn install --frozen-lockfile` · `Cargo.toml` → no-op (cargo fetches on build) · none matched → no-op (repo needs no provisioning).

Rationale (secondary): frozen-lockfile installs are deterministic; pnpm's content-addressed store makes per-worktree installs cheap (hardlinks, no re-download).

## 3. Behavior contract

- **When:** after `git worktree add`, before FIRST dispatch of ANY seat (coder/reviewer/fixer) into that worktree. Reviewer/fixer entering an existing worktree: receipt present → skip; absent → provision first (covers worktrees created before this feature).
- **Receipt:** on success write `<worktree>/.harness-provisioned` containing `{cmd, exitCode:0, at, durationMs}` (one JSON line). Dispatch precondition: receipt exists. Gate execution has the same precondition.
- **Timeout:** provision step bounded (default 600s, `meta.provision_timeout_ms` overrides). Timeout = failure.
- **On failure:** journal event `provision.failed` with `{taskId, cmd, exitCode, logTail}`; retry ONCE (transient network/store races are common); second failure → task ends `blocked` with failureClass `workspace-provision-failed`. This failureClass MUST NOT enter the gate-fix ladder, MUST NOT count in the agent retry ledger, and MUST NOT trigger seat fallback (it is not an engine/provider failure). `--retry-blocked` re-attempts such tasks in a FRESH worktree.
- **Logging:** provision stdout/stderr → `<rundir>/<task>-provision.log`, bounded tail in journal event only.
- **Concurrency:** provisioning counts toward run `--concurrency` (it is real load); two worktrees may provision in parallel, same as dispatch.

## 4. Non-goals (rejected)

- Copying/sharing `node_modules` between worktrees or from the main checkout — breaks native/postinstall assumptions, races live installs; lockfile install with warm store is nearly as fast and always correct.
- A provisioning cache daemon — violates no-daemon rule; pnpm store IS the cache.
- Auto-fixing a failed provision via the fixer seat — infra failures are not code failures.

## 5. Acceptance (plan tasks must carry these)

1. Unit: resolution ladder picks meta override > repo hook > lockfile detect > no-op, in that order.
2. Unit: dispatch/gate refuse a worktree without receipt (fail-closed), including reviewer/fixer entry paths.
3. Integration: fixture repo with `pnpm-lock.yaml` + a gate command needing an installed binary → task passes in a fresh worktree (proves the observed defect class is dead).
4. Integration: provision command that always fails → one retry, then task `blocked` with `workspace-provision-failed`; agent ladder untouched; `--retry-blocked` re-runs it in a fresh worktree.
5. Full v2 suite green.
