254 lines
12 KiB
Markdown
254 lines
12 KiB
Markdown
---
|
|
status: draft
|
|
system: workspaces
|
|
requirements:
|
|
- REQ-WORKSPACES-WORKTREE-BASE-REFRESH-001
|
|
---
|
|
|
|
# Worktree Base Refresh System Design
|
|
|
|
## Purpose and boundaries
|
|
|
|
The workspace system owns the repository checkout and the worktree base ref.
|
|
This design keeps host worktrees local-first while remote materialization stays
|
|
strict.
|
|
|
|
The task system owns session launch state and error presentation. The
|
|
integration system owns provider credentials. Executors own their Git transport
|
|
and remote workspace preparation.
|
|
|
|
This design does not refresh a valid worktree that Kandev reuses. It does not
|
|
change Git commands that an agent runs after launch.
|
|
|
|
## Requirement mapping
|
|
|
|
| Acceptance criterion | Design section |
|
|
| --- | --- |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.1` | [Refresh policy](#refresh-policy) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.2` | [Refresh policy](#refresh-policy) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.3` | [Local fallback](#local-fallback) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.4` | [Base-ref selection](#base-ref-selection) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.5` | [Base-ref selection](#base-ref-selection) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.6` | [Required materialization](#required-materialization) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.7` | [Multi-repository launch](#multi-repository-launch) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.8` | [Required materialization](#required-materialization) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.9` | [Empty remote](#empty-remote) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.10` | [Failure and recovery](#failure-and-recovery) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.11` | [Pull-request base reconciliation](#pull-request-base-reconciliation) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.12` | [Pull-request base reconciliation](#pull-request-base-reconciliation) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.13` | [Missing-base fallback](#missing-base-fallback) |
|
|
| `AC-WORKSPACES-WORKTREE-BASE-REFRESH-001.14` | [Safe refresh diagnostics](#safe-refresh-diagnostics) |
|
|
|
|
## Components and responsibilities
|
|
|
|
- `internal/orchestrator/executor` resolves repository paths, refresh routes,
|
|
credentials, and executor capabilities. It also resolves live GitHub pull-
|
|
request bases through an injected provider seam.
|
|
- `internal/worktree.Manager` verifies local refs, attempts host refresh, and
|
|
selects the worktree base. Pull-request base refresh remains strict except
|
|
for the verified missing-remote-ref fallback.
|
|
- `internal/repoclone.Cloner` materializes provider-managed repositories when
|
|
no user-owned local checkout supplies the branch.
|
|
- `internal/github.Service` reconciles changed pull-request bases through
|
|
narrow injected interfaces. Provider lookup and task-repository propagation
|
|
are best-effort.
|
|
- `internal/agent/runtime/lifecycle` stops launch only when no usable base
|
|
remains.
|
|
- The task projection shows bounded warnings and required-materialization
|
|
errors.
|
|
|
|
## Data and contracts
|
|
|
|
`repositories.pull_before_worktree` remains a persisted user policy. No schema
|
|
change is necessary.
|
|
|
|
The preparation fields have these contracts:
|
|
|
|
| Field | Contract |
|
|
| --- | --- |
|
|
| `PullBeforeWorktree` | Kandev attempts remote refresh before local worktree creation. |
|
|
| `RemoteSyncHandled` | The configured remote route completed a refresh for this checkout. |
|
|
| `RefreshRepository` | An optional provider refresh for worktree materialization. |
|
|
|
|
`PullBeforeWorktree` is not a universal admission gate. Local-ref availability
|
|
determines whether a failed refresh is recoverable.
|
|
|
|
The task repository's stored base remains the durable comparison target and
|
|
offline launch fallback. The GitHub task-PR row tracks the provider observation;
|
|
polling propagates a changed non-empty base to the matching task repository.
|
|
|
|
## Pull-request base reconciliation
|
|
|
|
The executor reads the pull-request number from task-repository metadata and
|
|
the owner and repository name from the repository entity. For GitHub repository
|
|
rows with a positive pull-request number, it asks an injected resolver for the
|
|
current base before materialization. A successful non-empty result overrides
|
|
the launch request only. A missing resolver, lookup error, or empty result keeps
|
|
the stored base and does not stop launch.
|
|
|
|
Once the pull-request task reaches Git refresh, its base remains strict for
|
|
unproven failures. A proven missing remote base can use only the fallback
|
|
described below.
|
|
|
|
The GitHub polling service compares the previous `TaskPR.BaseBranch` with the
|
|
incoming non-empty base. On change, it updates the task repository whose task
|
|
and repository IDs match. When more than one association matches, the checkout
|
|
branch must also match the pull-request head. Update failures are logged and do
|
|
not fail the authoritative task-PR sync.
|
|
|
|
## Refresh policy
|
|
|
|
If `PullBeforeWorktree` is false, Kandev uses the selected local base without a
|
|
remote request.
|
|
|
|
If `PullBeforeWorktree` is true, Kandev attempts the configured refresh before
|
|
new or recreated host worktrees. A valid reusable worktree bypasses this
|
|
attempt.
|
|
|
|
The refresh route still follows the task Git credential policy. A host checkout
|
|
keeps its configured origin and transport. A provider-managed checkout keeps
|
|
its exact credential scope.
|
|
|
|
## Local fallback
|
|
|
|
Before refresh, the worktree manager verifies the selected local ref. If that
|
|
ref exists, refresh is best effort.
|
|
|
|
Authentication, network, timeout, Git, and missing-remote-ref errors return the
|
|
selected local ref. The manager emits a bounded warning and does not include
|
|
raw credential output.
|
|
|
|
The fallback does not change local or remote refs. It does not push the local
|
|
branch.
|
|
|
|
Pull-request base refresh does not use this local fallback. It remains strict
|
|
for unproven failures and uses only the verified missing-base fallback below.
|
|
|
|
## Base-ref selection
|
|
|
|
After a successful fetch, the worktree manager compares local base `L` and
|
|
remote base `R`:
|
|
|
|
| Relationship | Start ref | Result |
|
|
| --- | --- | --- |
|
|
| `R` contains `L` | `R` | The worktree includes current remote commits. |
|
|
| `L` contains `R` | `L` | The worktree preserves local-only commits. |
|
|
| Refs diverge | `L` | The worktree preserves the selected local history and shows a warning. |
|
|
| Ancestry is unknown | `L` | The worktree preserves the selected local history and shows a warning. |
|
|
|
|
The manager never resets, rebases, merges, removes, or hides either ref during
|
|
selection.
|
|
|
|
## Required materialization
|
|
|
|
Remote access is required when Kandev cannot verify a usable local base. This
|
|
case includes an explicit remote-only ref and an executor that must clone the
|
|
repository.
|
|
|
|
If materialization fails, lifecycle stops launch. The task error identifies the
|
|
repository and uses a bounded failure class.
|
|
|
|
Provider selection still follows the existing Git credential policy. A managed
|
|
HTTPS route must not replace an executor-owned SSH route.
|
|
|
|
## Missing-base fallback
|
|
|
|
A fetch error that explicitly reports a missing remote ref is the only failed
|
|
fetch eligible for fallback. When the request carries a different non-empty
|
|
fallback base, the manager fetches that branch through the same non-interactive
|
|
route and applies the normal containing-ref checks. Success completes sync,
|
|
uses the fallback ref, and records a warning naming the requested and fallback
|
|
branches.
|
|
|
|
If the fallback is absent or its refresh fails, preparation remains failed. A
|
|
missing requested or fallback remote ref uses `missing_remote_ref`; auth,
|
|
network, timeout, cancellation, and other Git failures keep their existing
|
|
fail-closed classifications.
|
|
|
|
## Empty remote
|
|
|
|
An authenticated remote with zero refs uses the marked local baseline from the
|
|
[Empty Remote Repository System Design](empty-remote-repositories.md).
|
|
|
|
This path creates no remote ref during launch. Publication remains an explicit
|
|
user action.
|
|
|
|
## Multi-repository launch
|
|
|
|
Each repository resolves its base independently before agent startup. A local
|
|
fallback counts as a prepared repository.
|
|
|
|
If one repository needs remote materialization and has no usable base, the
|
|
whole task stops before agent startup. The error names that repository.
|
|
|
|
## Failure and recovery
|
|
|
|
- A refresh error with a valid local base produces a warning and continues.
|
|
- A missing local and remote base produces a launch error.
|
|
- Caller cancellation stops preparation without a fallback worktree.
|
|
- A retry repeats remote materialization only when the task still needs it.
|
|
- A valid reused worktree does not refresh or materialize its base again.
|
|
- A pull-request base refresh failure stops preparation unless the missing
|
|
remote ref has a separately refreshed configured fallback.
|
|
- A pull-request fallback reports the requested and selected branches and uses
|
|
`missing_remote_ref` when no usable fallback exists.
|
|
|
|
## Security
|
|
|
|
Git stays non-interactive. Host refresh uses the host credential route.
|
|
Provider refresh uses exact task and repository scope.
|
|
|
|
Warnings contain a failure class, branch name, and repository identity. They do
|
|
not contain tokens, credential-helper output, or secret URLs.
|
|
|
|
## Observability
|
|
|
|
The existing sync progress callback reports a running event and then either a
|
|
completed event or a failed event. A failed event contains the bounded failure
|
|
class and repository identity. A successful missing-base fallback reports a
|
|
completed event and records the branch substitution warning on the worktree.
|
|
Local fallback reports completion with a warning, not a launch error.
|
|
|
|
Structured logs record repository identity, refresh route, failure class, and
|
|
selected fallback ref. Logs exclude credential material and raw remote URLs.
|
|
|
|
### Safe refresh diagnostics
|
|
|
|
AC .14 extends the existing observability contract. `manager_git.go` captures
|
|
combined command output, but failure paths retain only coarse reasons or an
|
|
exit status. Inspect that output in memory and emit fixed diagnostic values.
|
|
Do not append raw output to logs, returned errors, or progress events.
|
|
|
|
Keep the existing policy `reason` separate from a diagnostic-only
|
|
`diagnostic_code`. Add an allow-listed classifier in
|
|
`internal/worktree/refresh_diagnostics.go`. It recognizes authentication,
|
|
SSH public-key rejection, SSH host verification, DNS, connection failure,
|
|
TLS verification, missing remote ref, non-fast-forward rejection, repository
|
|
access failure, and lock contention. Unknown output maps to `unknown`.
|
|
Context cancellation and execution timeout take precedence over text matches.
|
|
Each code maps to a fixed English explanation. No explanation includes
|
|
substrings from Git output. Repository access failure does not assert that
|
|
credentials are invalid, and lock contention does not assert a stale lock.
|
|
|
|
Emit one structured diagnostic per failed fetch or pull with `operation`,
|
|
`repository_path`, `branch`, `reason`, `diagnostic_code`, `detail`, and an exit
|
|
code when available. The local repository path identifies the checkout without
|
|
reading its remote URL. Existing failure or fallback logs can carry these fields
|
|
instead of adding a duplicate diagnostic. Cover both required and best-effort
|
|
paths, including a failed configured fallback branch.
|
|
|
|
Classification is observational. Do not feed new diagnostic categories into
|
|
missing-base fallback eligibility or alter `syncFailureCause` identities.
|
|
Preserve context errors and current progress/error payloads. Correct the
|
|
`syncFailureCause` comment that implies raw output reaches internal logs.
|
|
No new logging configuration, subprocess, retry, or persistence is required.
|
|
|
|
Delivery: [Recovery diagnostic fixes](../../../plans/recovery-diagnostics/plan.md).
|
|
|
|
## Related decisions
|
|
|
|
- [Local Worktree Refresh Is Best Effort](../../../decisions/2026-08-31-local-worktree-refresh-best-effort.md)
|
|
- [Required Worktree Refresh Fails Closed](../../../decisions/2026-08-25-required-worktree-refresh-fails-closed.md)
|
|
- [Separate GitHub Automation From Task Git Credential Policy](../../../decisions/2026-07-27-task-git-credential-policy.md)
|
|
- [Provider-Neutral Git Credential Broker](../../../decisions/2026-07-31-provider-neutral-git-credential-broker.md)
|