WorkspaceProvider when a run must not trust a checkout it didn’t make,
and when two writers must never collide. For the idea, read
Workspace; for the whole cycle in a disposable
repository, Workspace example. The built-in provider,
createGitWorktreeProvider, works with a single Git repository.
The safe order is capture, verify, acquire the lease, record the planned
child branch yourself, then fork:
examples/workspace.ts (excerpt)
repositoryPath, the one Git repository it captures.- An owner and a scope name for the lease, and the anchor it covers.
- A child identifier for the fork, a safe Git branch and directory name.
Capture and verify
capture() records the repository revision and the file state, as an
anchor. verify(anchor) reads the repository again and reports changed
revisions or paths, so you can pause a run when it names paths changed by a
write you didn’t make. Capture reads file states and the content fingerprint
at the same time; a write between those reads can mix evidence from two
moments, so keep the workspace still during capture. A dirty file’s state
keeps one digest per line, so a large dirty file can produce a large anchor.
Pass Git pathspecs to capture(scope) to limit the file states stored in
the anchor; the anchor stores the scope and verify uses the same
pathspecs. The provider doesn’t check that a pathspec matches a file, so a
scope with no matches stores no file states. The scope doesn’t limit
HEAD: a commit outside the scope still invalidates the anchor. verify
compares the repository identity, HEAD and stored file states, not the
anchor fingerprint, so ignored files you select explicitly can change the
fingerprint without appearing in the stored states, and verification can
miss changes to them.
Take the lease
A lease allows one writer for one repository. It records the owner, scope, a digest of the workspace anchor and the acquisition token, andfork
accepts only the token for the exact anchor being forked. The Git provider
stores the complete record in Git itself, with one private ref pointing at
it. Acquisition creates the ref only when it doesn’t exist and writes a
complete record before it publishes the ref. Release requires the token and
removes the ref only if it still points to the same record, so a delayed
release or a recovery can’t remove a later owner’s record.
recoverIncompleteLease() can remove an unreadable stored record, or a
readable incomplete record after its age limit. A failed Git blob read
marks the record as corrupt, so recovery can remove its ref even when the
unread record held a completed lease. Recovery never removes a completed
lease it can read.
Fork a child worktree
The Git provider creates child worktrees beside the repository, outside its working tree: for/work/project, child review-1 is created at
/work/project.obversa-worktrees/review-1, and you can’t choose another
location. A successful fork returns a full capture of the child worktree;
its anchor has scope: null, even when the parent anchor had a scope.
Failure
A fork that doesn’t happen says why:Limits
- One repository. The built-in provider captures one Git repository. A multi-repository provider is not included.
- Caller-owned run control. Verify reports drift. The caller decides when to pause a run and records any branch or merge events.
- No automatic executor binding. The graph executor does not capture, verify or lease a workspace for the caller.
- No forced lease takeover. A readable completed lease remains held until its owner releases it.
- No orphan cleanup. The provider can retry an incomplete fork, but it doesn’t remove abandoned child refs or worktrees.
- Cancellation reaches the snapshot helpers only. The provider’s own Git calls for leases, refs and worktrees don’t use the signal.
Next steps
- Workspace example: the whole cycle run in a temporary repository, with the report it prints.
- Workspace: why a worktree per writer, and
isolated()for a step that lands its work back. - Supervised local runs: the watchdog that holds the lease while a worker runs.