Landing and syncing work
How approved work reaches main; land commits the sealed handoff, merge conflicts and partial lands, then sync pushes or opens a PR, runs checks, and restarts smith only on its own source.
Approved work reaches main in two steps. A run lands its sealed handoff as commits in the work clone. A later sync carries those commits from the work clone to the project's checkout and origin. File:line citations are in parentheses; "not checked" means the code was not read for that fact.
01the handoff#
workspace_handoff seals what the work node left. It finds the work node's released workspace, runs any configured format commands on the changed files, then creates an immutable handoff with the tests, evidence and doubts from the baton. A node without a released owner fails. A snapshot target's run folder also gets its diff against the frozen version for the reviewer. Config keys are on nodes.
02land#
workspace_land commits the approved handoff in its workspace.
- It refuses a workspace that has moved since the handoff was sealed, so what lands is what review saw and the operator approved. Nothing is committed in that case.
- One commit per ticket: a batch whose changes split gets one commit per ticket, in the ticket list's order and each named for its ticket; otherwise one commit, named for the ticket or, for a batch whose changes overlap, for all of them. A sorted batch lands by its claims, and the files of tickets the gate dropped are put back as HEAD has them first.
- Nothing to land: a handoff with no changed files records
nothing_to_land. - Files that were already dirty when the run took the workspace are committed with the run's work and named separately as inherited.
- Target asset: a run on a snapshot target lands as the next version of its asset, with no commit and no git. The asset's own folder is written only when the user left it alone since go; otherwise both are kept.
- Merge: when the service has a merge hook, the landed branch is folded into its clone before land returns, and the baton gets the commits as they are on main. A rebase onto a moved main is recorded as
rebased(; hook at). merge_conflict: a commit that couldn't be merged into the work clone fails the run beforeticket_close, and the ticket stays open. The merge and what happens on a conflict are on land.rebased_red: a land that had to rebase onto a moved main re-runs the project's checks on the rebased tree before the fast-forward, scoped to the run's changed packages, their importers and the guard packages (whole ongo.modor ago listfailure; node checks by their dirs). Ago testcheck there runs without-race(keeping-p N): the run's own checks already raced the tree, and the re-check only asks whether the rebase broke the build or a test. On red the run fails withrebased_redand main is unchanged. See land.- Partly landed: if a later commit of a batch fails, the commits already made stay made. The node reports
workspace partly landedwith them, the run is not landed, and a continue reads them as landed. - A commit that fails for another reason leaves the approved change staged on disk and the run can be continued. See runs.
03git tools#
git_state, git_log and git_diff read the two repos of a project, the work clone runs build in and the checkout it was cloned from. They never write a branch, the index or a file; finding the workspace only refreshes its remote-tracking refs, as a launch card does.
git_stateanswers, for each repo, the branch, head, subject, uncommitted files, upstream and ahead/behind, plus how the workspace stands against the checkout, the running core's revision and whether it matches the checkout, and whether the checkout has no origin remote.git_loglists commits of one repo: sha, author, date and subject. Its cap constantgitLogMaxis 50; where it is applied is not checked.git_diffgives a range such asmain..HEAD, or the uncommitted changes when the range is empty. Long diffs are cut at 64 KiB.
04propose_sync and the sync#
propose_sync drafts a card that brings the checkout's branch up to date with the work clone: a fast-forward to the clone's head, or the commits you name cherry-picked onto it. It reads both repos and changes nothing.
- It needs a
why. It is refused for a project with no repo or no work clone yet, and while another sync card is running. Older open sync cards are replaced by the new one. - It refuses anything that isn't a clean step forward: nothing new to bring, a checkout with commits the clone lacks (name commits to cherry-pick then), or a commit not on the clone's branch.
- A clean work clone that no run uses and that is behind is lined up first, the way go would. A card that has gone stale can be proposed again against the current heads.
- The plan pins both repos as they were when drafted. A click on a stale card refuses.
the click
Nothing starts until the operator presses the card. The click runs these steps in the background, and the first failure stops it:
- Preflight: the project's checks and clone are still the card's, and both repos are where the card saw them.
- A worktree of its own at the checkout's commit, in the checkout's
.git, so uncommitted work in the checkout is neither blocked nor touched. Only commits ship. - Apply: fetch the commits, then fast-forward or cherry-pick. A failed cherry-pick is aborted and nothing changes.
- Checks, on exactly what will be pushed. A failing check stops the sync, nothing is pushed and the branch is where it was.
- Push to origin as a plain push, which origin refuses unless it is a fast-forward. The checkout then follows only when that can't touch anyone's work: it is left behind, with a note, if it moved, has uncommitted changes, or won't fast-forward.
- Line the work clone up with what was pushed, unless a run is using it or it has changes.
- Rebuild and restart, only when the checkout is smith's own source (see below).
A project with no origin remote skips the push: the checkout's branch moves locally and the card says "no remote: main moved locally, nothing pushed".
the lock
One sync runs per project at a time. Projects that share a checkout share its .git, so one sync runs per checkout, keyed by its git common dir. A second click is refused with "a sync for X is already running". Merges of landed run branches into a clone go one at a time per project.
the check-digest carry
A check that a run of the same project already passed on exactly the inputs the sync's tree has is not re-run: the card says it passed in that run. The service computes the digest the tree would have on each candidate base, and the desk looks for a passed result with the same digest. It tries at most 50 candidate bases (the checkout's head and the first-parent commits above it) against the 40 newest runs of the project; these limits are provisional. A failed, carried or digest-less result never counts. The scoped re-run of a rebased land is not a run check result, so it is never journalled as a check event and the carry compares exactly what it did before.
push mode and pr mode
A project's sync_mode is push or pr, and unset means push. pr needs a gitea https repo_remote.
- Push mode pushes to the branch, as above.
- PR mode does the same worktree, apply and checks, then pushes a branch of its own to origin and opens a pull request on gitea; nothing is ever pushed to the base branch and the checkout's branch never moves. If a sync PR is already open the commits are added to it, with a lease-checked force push, and no second PR is opened. The card then says "open pr" or "add to pr #N".
- The sync's PR mode never restarts smith.
- Gitea access goes through the desk's gitea client in
internal/desk(the login comes from git's credential helper). The exact API calls beyond open, edit, create and merge are not checked.
05propose_accept_pr#
propose_accept_pr drafts an accept card for the project's open sync PR. Only the operator's click merges or closes it.
- It is refused for a project with no repo or work clone, for a project that syncs by push, when no sync PR is open on gitea, and when an unsettled accept card for that PR already exists.
- The click is merge (the default) or close. It reads the PR first and acts only on an open one: already merged or closed is reported and nothing is done.
- A merge fails if the PR isn't mergeable ("the PR conflicts with main"; add to the PR after go lines the clone up).
- After a merge, the checkout is fast-forwarded when that can't touch anyone's work and the work clone is lined up unless a run is using it. Neither failing undoes the merge.
06restarting smith#
A push-mode sync rebuilds and restarts smith only when the checkout is smith's own source: the running executable is inside the checkout and its go.mod starts with module github.com/boxsie/smith.
- The new core is built to
<core>.nextfrom the synced worktree, checked withconfig checkagainst the live desk, harness and project files, and only then renamed over the old one. If either fails, smith keeps running the old core. - The restart waits for other projects' syncs to end, and nothing new starts meanwhile.
- A live run stops the restart: the core is rebuilt but not restarted, because the run would pause. Restart smith when it is done.
- Before the restart, smith takes a backup ("before sync restart") and a tickets db snapshot. A failure of either does not hold the restart.
- A checkout that is not smith's own source rebuilds nothing.
07when you're the conductor#
- A landed run is not on main. Use
git_stateto see how far the work clone is ahead of the checkout, andgit_logto read the commits, before proposing a sync. - Propose a sync only when the project's checks are the ones you want run. The click runs them on exactly what will be pushed, and carried results are not re-run.
- Name commits to cherry-pick when the checkout has commits the clone lacks. Leave the list empty to fast-forward.
- In a pr project,
propose_syncopens or updates the PR andpropose_accept_pris the separate merge card. Do not expect a sync alone to change main. - A sync that restarts smith ends your session. The desk tells you in a line before it does.
- When a sync stops, the desk tells you which step failed. Report the step and its message; do not retry blindly.