Context
Sub-leaf of #13015 (FM MVP) — the third and final leaf of the read → decide → act repo-provisioning trio: #13145 / PR #13146 derives the path (the "where"), #13148 / PR #13149 inspects the checkout state (the "what's there" → a provisioningAction of clone / reuse / conflict), and this leaf executes that action — materializing the managed checkout under the hood, the operator's "repos managed under the hood."
The Problem
The inspector returns a provisioningAction but does not act. The executor must: clone into an absent / empty path; reuse an existing valid checkout as-is (no reclone — path stability is load-bearing for the path-keyed auto-memory); and on a conflict (a foreign occupant, including a symlink) refuse — never clobber. The git clone is a side effect (subprocess + network), so the leaf needs a test seam so the clone / reuse / conflict contract is unit-testable without a real remote.
The Architectural Reality
ai/services/fleet/provisionAgentRepo.mjs (Brain side, beside deriveAgentRepoPath / inspectAgentRepo / FleetLifecycleService).
- Follows
FleetLifecycleService's default-real + injectable seam idiom: the clone executor defaults to a real child_process git clone but is injectable (a cloneRepo parameter), so the spec asserts the contract with a recording stub — no git binary / network in tests. This is the exact pattern FleetLifecycleService.spawnFn (defaulting to child_process.spawn) and its spawn-stub spec already use.
- Decoupled: takes the resolved
repoPath + the inspector's provisioningAction (does not import the sibling leaves) — single responsibility; the composing "ensure" orchestrator (a later step) wires derive → inspect → provision.
The Fix
export async function provisionAgentRepo({repoPath, cloneUrl, provisioningAction, cloneRepo}) → {repoPath, action, cloned}:
provisioningAction: 'clone' → require a non-empty cloneUrl; await cloneRepo(cloneUrl, repoPath); return {action: 'cloned', cloned: true}.
'reuse' → no-op (the checkout already exists); return {action: 'reused', cloned: false}.
'conflict' → throw (never clobber a foreign occupant).
- unknown action / invalid inputs → throw.
cloneRepo defaults to a real git clone (git clone -- <url> <dest> via execFile); injectable for tests.
Acceptance Criteria
Out of Scope
- The composing derive → inspect → provision orchestrator (a later leaf).
- Repair of a wrong-remote checkout (a later refinement, once the inspector adds remote-match).
- Credential / PAT injection into the clone URL — the registry's
resolveCredential boundary; the caller supplies the already-resolved cloneUrl.
Decision Record impact
none — a new Brain-side helper consistent with the existing fleet-service conventions.
Related
- Parent #13015; grandparent #13012. Siblings: #13145 / PR #13146 (derive), #13148 / PR #13149 (inspect). Pattern precedent:
FleetLifecycleService's injectable spawnFn seam + its spawn-stub spec (the subprocess test idiom).
Live latest-open sweep: checked latest open issues + a provision / clone / ensure-repo scan at 2026-06-13T23:42Z; no equivalent found.
Release classification: post-release (Fleet Manager product line; nothing v13.x-blocking).
Origin Session ID: 4c598c8f-d8a7-4288-9420-e825a45d310e
Context
Sub-leaf of #13015 (FM MVP) — the third and final leaf of the read → decide → act repo-provisioning trio: #13145 / PR #13146 derives the path (the "where"), #13148 / PR #13149 inspects the checkout state (the "what's there" → a
provisioningActionof clone / reuse / conflict), and this leaf executes that action — materializing the managed checkout under the hood, the operator's "repos managed under the hood."The Problem
The inspector returns a
provisioningActionbut does not act. The executor must: clone into an absent / empty path; reuse an existing valid checkout as-is (no reclone — path stability is load-bearing for the path-keyed auto-memory); and on a conflict (a foreign occupant, including a symlink) refuse — never clobber. The git clone is a side effect (subprocess + network), so the leaf needs a test seam so the clone / reuse / conflict contract is unit-testable without a real remote.The Architectural Reality
ai/services/fleet/provisionAgentRepo.mjs(Brain side, besidederiveAgentRepoPath/inspectAgentRepo/FleetLifecycleService).FleetLifecycleService's default-real + injectable seam idiom: the clone executor defaults to a realchild_processgit clone but is injectable (acloneRepoparameter), so the spec asserts the contract with a recording stub — no git binary / network in tests. This is the exact patternFleetLifecycleService.spawnFn(defaulting tochild_process.spawn) and its spawn-stub spec already use.repoPath+ the inspector'sprovisioningAction(does not import the sibling leaves) — single responsibility; the composing "ensure" orchestrator (a later step) wires derive → inspect → provision.The Fix
export async function provisionAgentRepo({repoPath, cloneUrl, provisioningAction, cloneRepo})→{repoPath, action, cloned}:provisioningAction: 'clone'→ require a non-emptycloneUrl;await cloneRepo(cloneUrl, repoPath); return{action: 'cloned', cloned: true}.'reuse'→ no-op (the checkout already exists); return{action: 'reused', cloned: false}.'conflict'→ throw (never clobber a foreign occupant).cloneRepodefaults to a real git clone (git clone -- <url> <dest>viaexecFile); injectable for tests.Acceptance Criteria
provisionAgentRepo({repoPath, cloneUrl, provisioningAction, cloneRepo}); async; the defaultcloneRepoperforms a realgit clone.'clone'invokescloneRepo(cloneUrl, repoPath)exactly once and reportscloned: true; requires a non-emptycloneUrl(throws otherwise).'reuse'does NOT invokecloneRepoand reportscloned: false(no reclone of an existing checkout).'conflict'throws and does NOT invokecloneRepo(never clobber).repoPath, throw.test/playwright/unit/ai/provisionAgentRepo.spec.mjsinjects a recordingcloneRepostub (no git binary), covering each branch; green under the custom unit config.Out of Scope
resolveCredentialboundary; the caller supplies the already-resolvedcloneUrl.Decision Record impact
none— a new Brain-side helper consistent with the existing fleet-service conventions.Related
FleetLifecycleService's injectablespawnFnseam + its spawn-stub spec (the subprocess test idiom).Live latest-open sweep: checked latest open issues + a provision / clone / ensure-repo scan at 2026-06-13T23:42Z; no equivalent found.
Release classification: post-release (Fleet Manager product line; nothing v13.x-blocking).
Origin Session ID: 4c598c8f-d8a7-4288-9420-e825a45d310e