Documentation
¶
Index ¶
Constants ¶
const ( StatePipelineOwned = "pipeline_owned" StatePushInProgress = "push_in_progress" StateBehind = "behind" StateSynchronized = "synchronized" StateLocalAhead = "local_ahead" StateDiverged = "diverged" StateDirty = "dirty" StateRemoteAdvanced = "remote_advanced" StateRemoteRewritten = "remote_rewritten" StateRemoteMissing = "remote_missing" StateMergedRemoteRetained = "merged_remote_retained" StateMergedRemoteRemoved = "merged_remote_removed" StateClosed = "closed" StateOffline = "offline" StateTargetChanged = "target_changed" StateAmbiguousContext = "ambiguous_context" StateLegacyUnbound = "legacy_unbound" StateCustodyReturned = "custody_returned" // StateUserOwned reports a branch released by its terminal outcome: the // run ended before the pipeline changed the submitted head, so no // pipeline-created content exists to recover and the exact branch and head // are the operator's, immediately usable with no sync action. StateUserOwned = "user_owned" )
const ( RelationEqual = "equal" RelationBehind = "behind" RelationAhead = "ahead" RelationDiverged = "diverged" RelationUnknown = "unknown" )
const ( SafetySafeFastForward = "safe_fast_forward" SafetySafeEquivalentAdvance = "safe_equivalent_advance" )
Variables ¶
This section is empty.
Functions ¶
func CanApply ¶
CanApply reports whether Apply may advance the clean checked-out branch for a freshly verified plan. It includes strict fast-forwards and the narrower equivalent-diverged advance that first anchors the pre-sync head.
func RunHeadUnmoved ¶
RunHeadUnmoved reports whether the classified run's pipeline head still equals the submitted head, i.e. the run holds no pipeline-authored commits whose loss a fresh gate push could cause.
func TargetFingerprint ¶
TargetFingerprint returns a stable one-way identity for a credential-free, canonical target. No URL is persisted by callers.
Types ¶
type NextAction ¶
type PipelineState ¶
type RemoteState ¶
type Service ¶
type Service struct {
DB *db.DB
Repo *db.Repo
WorkDir string
GateDir string
Paths *paths.Paths
// contains filtered or unexported fields
}
Service synchronizes only the invoking worktree. Repo is the registered repository record, while WorkDir may be its main or a linked worktree. GateDir is the repo's local bare gate; selection may read its exact branch head and ancestry as provenance evidence, while Recover is the only method that mutates it.
func OpenCurrent ¶
OpenCurrent opens a service for the invoking registered worktree. The caller owns the returned close function.
func (*Service) Apply ¶
Apply repeats remote and mutable-precondition checks, then advances the clean checked-out branch to the exact pipeline-bound SHA. Ordinary behind branches use a strict fast-forward. Equivalent-diverged branches first anchor the pre-sync head, then move to the verified equivalent pipeline head.
func (*Service) InspectCached ¶
InspectCached reads local Git, persisted provenance, and read-only gate ancestry evidence without fetching or mutating refs, the index, or the worktree.
func (*Service) Recover ¶
Recover returns custody of a branch stranded by a TERMINAL run whose MOVED pipeline head was never published: cancelled or failed before the push with pipeline commits in the gate, or terminal after a push with additional unpublished commits. While such a run was active the pipeline_owned block was correct; once it is terminal nothing will ever publish the head, so an explicit guarded exit must exist. A terminal run whose verified worktree head never changed from the submitted head needs no recovery at all, so Recover treats that user_owned state as an idempotent no-op success.
The decision matrix, by worktree relation to the preserved pipeline head P (the gate branch head recorded as the run's head_sha):
relation worktree default --keep-local
equal any anchor locally; return custody same
ahead any anchor locally; return custody same
behind clean strict fast-forward to P, custody at local head;
then return custody gate reset to it (CAS)
behind dirty refuse (commit/stash first) custody at local head;
gate reset to it (CAS)
diverged, clean anchor the pre-recovery local custody at local head;
P contains head, then move to P with gate reset to it (CAS)
all local fail-closed ops; return custody
work
diverged, dirty refuse (commit/stash first) custody at local head;
P contains gate reset to it (CAS)
all local
work
diverged any refuse (anchor named, manual custody at local head;
reconcile / rerun offered) gate reset to it (CAS)
P missing any refuse refuse
A gate branch still frozen at the run's submitted head is its own row: the pipeline adopted nothing past submission, so P was never published and can never be fetched from the gate branch. P is still anchored when the gate's object store or the invoking worktree can still reach it, and custody then returns as a bookkeeping move (or, under --keep-local, at the local head with the usual gate CAS) instead of deadlocking on a head the gate never held.
The containment row exists because a cancelled validation routinely leaves P as a REBASE of the local branch onto a newer base: the same logical commits with new SHAs, so equality and ancestry alone see only divergence and escalated a case where nothing could be lost. The row applies only where preservedContainsLocalWork proves, by executable three-way merge, that P already carries every local change. That proof is deliberately narrow, and everything it cannot decide - including a rebase whose fix rounds also rewrote the operator's lines - falls through to the plain diverged refusal. No-data-loss outranks convenience here: when nothing can distinguish a deliberate pipeline fix from a dropped change, the operator decides.
Fail-safe rules, in the same spirit as Refresh/Apply:
- An active run always refuses: only terminal runs are recoverable.
- The preserved commits must be provably safe before custody moves: when already reachable from the local branch (equal/ahead), recovery pins the private anchor ref refs/no-slop/recover/<runID> locally without gate access; otherwise the preserved head is verified at the gate branch head and fetched into that anchor. The anchor keeps them reachable locally no matter what later happens to the gate.
- The only possible worktree mutation is a guarded move of a clean checked-out branch: a strict fast-forward, or an anchored move to a proven-containing head performed by Git operations that refuse on their own rather than by a preceding observation (see recoverAdoptPreserved). When the operator explicitly keeps a behind or diverged local head instead of taking P, --keep-local never touches the worktree and moves the gate branch to the kept head with an atomic compare-and-swap, so a concurrent gate push wins and recovery refuses.
- That compare-and-swap abandons whatever the gate branch pointed at, so unless the kept head already contains it or the recover anchor already pins it, it is first pinned at refs/no-slop/recover-abandoned/<runID>. The frozen-gate row is where the two differ: the anchor holds P while the commit being let go is the submitted head.
- Anything unverifiable (missing gate where required, moved gate branch, failed anchor write or fetch, changed assumptions) refuses with a reason and leaves the worktree, the branch, and the gate branch exactly as they were. The only thing a refusal can leave behind is a private anchor ref holding a commit that already existed, and every refusal that can follow an anchor write names that ref rather than claiming nothing was written.
Recovery ends with a persisted custody-return stamp on the run; inspection then reports custody_returned (never-pushed runs) or the ordinary classification against the last push binding (pushed runs), both pointing at run_pipeline as the next step. `no-slop rerun` remains the alternative exit: it starts a fresh run from the current gate branch head, which is the pipeline head only when the pipeline adopted one there.
Whenever a recovered branch does NOT carry a commit this recovery anchored (the frozen-gate row, and --keep-local anywhere), the holding ref is reported as PreservedAnchorRef or AbandonedAnchorRef so the success output names the only remaining reference to that work; a recorded pipeline head that survived nowhere is reported as LostPipelineHead instead of silently absent. Re-running the idempotent recovery reports exactly the same facts.
type State ¶
type State struct {
State string
Changed bool
Local LocalState
Pipeline PipelineState
Target TargetState
Remote RemoteState
Relation string
Safety string
PRState string
// Recovered is set only by Recover and reports that the operator owns the
// branch when the call returns: custody of a stranded terminal run was
// returned (by this call or an earlier, idempotent one), or the terminal
// outcome had already released the branch (user_owned), making recovery an
// idempotent no-op.
Recovered bool
// PreservedAnchorRef and AbandonedAnchorRef name the private refs holding
// commits the returned branch does NOT contain, and are set only when
// custody returns that way: the frozen-gate row anchors a stranded pipeline
// head the branch never reaches, --keep-local deliberately abandons the
// preserved head, and its gate compare-and-swap separately abandons the
// commit the gate branch pointed at. Those returns report changed: false, so
// without these the only record of that work (an agent's conflict
// resolutions, or a submitted head the operator reset past) is a ref no
// output names. Each stays empty whenever the branch carries that commit.
PreservedAnchorRef string
AbandonedAnchorRef string
// LostPipelineHead is the recorded pipeline head of a returned run that the
// branch does not contain and that no object store still holds. The
// frozen-gate row rescues that commit whenever it survives; when it does
// not, an empty anchor set alone cannot distinguish a pipeline that produced
// nothing from one whose work was destroyed, so the lost commit is named.
LostPipelineHead string
NextAction *NextAction
Error string
}
State is the shared branch synchronization contract rendered by CLI, AXI, and TUI presenters. Cached inspection never contacts a remote.