Repository navigation
Gate: run a repo's provision and gate commands on a fresh checkout - #43
Merged
Merged
Conversation
…eckout Checks out the pull request head merged with the base tip in a standalone repository, runs provision then gate commands in it under a per-run lock file, records each step in the ledger, and removes the checkout on every path out. Server start clears gate locks a dead server left. Issue: #18
…put is parsed from stdout alone Issue: #18
…e ended in a pass or a fail Issue: #18
…st the cap Issue: #18
…comment body Issue: #18
… not only the first Issue: #18
rawphp
marked this pull request as ready for review
October 15, 2026 13:05
Author
|
Factory gate: passed (attempt 1, run by the factory in a fresh checkout of the pull request's head
|
A merge of a large base update prints a line per file and could pass the 1 MiB cap, which failed the gate on every pass though the merge had worked. Only calls whose stdout is parsed (rev-parse, status, diff) refuse output cut at the cap, after the exit code is checked, and the error no longer says the call was killed. Tests: a merge that exits 1 with no conflicted file is an error; a failing merge-base stops before any merge; a loud merge still merges; gateCount on a failing gate command; GH_CONFIG_DIR from the server's environment reaches only the fetch. The global-config test's own plain clone drops the suite's pinned GIT_CONFIG_* settings, which outranked its HOME and made it fail. The checkout and gate results are checked with `satisfies` instead of casts.
…leaves the first of them The ledger has no batched append, so the lines go in one at a time. GateError now says that such a failure counts the attempt (the commands ran) and that a retry under the same attempt number is not counted twice.
Author
|
Factory gate: passed again (attempt 2, run by the factory in a fresh checkout of the pull request's head
|
Author
|
Factory gate: passed again (attempt 3, run by the factory in a fresh checkout of the pull request's head
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #18
Contract
apps/server/src/factory/gate.tsexposes aFactoryGateservice (overFactoryProcess) whoserun()makes a standalone checkout at<data>/gate/<run id>/<attempt>/(git init --template=, fetch of the head and the base tip by sha from<remote>/<owner>/<name>.git,checkout --detach, clean-status check,merge --no-ffassoftfac/softfac@localhost, or "contained" when the base is already in the head); a conflict aborts the merge and givesconflictwith onegate_commandlinesoftfac: merge the base branch into the head, exit 1, duration 0; otherwise theprovisionthen thegatecommands run throughsh -cviaFactoryProcess(cleared environment, 30 minutes each), the first failure ends the attempt, and every command that ran is written asgate_command(causegate-command-ran, withcommandandattempt) once the loop has ended in a pass or a fail;gate_checkoutis written before the first command; the result ispass,failorconflict;<data>/gates/<run id>.lockis createdO_EXCLat the start and removed at the end (one there already →running;max_concurrent_gateslock files held →queued);clearGateLocksruns atFactoryRuntimestart; the checkout is removed after the result is recorded (gate_cleanup_failedwhen it cannot be); git trouble is a typed error with no attempt used.commits_touch_path/ project-config refusal, gate retries, send-back, state changes, PR comments, required checks (Gating: a passing gate opens the PR for review, a failing one goes back to the session #19), the cross-run checkout sweep,flock-based cross-process locks, process-group record files.vp test run apps/server/src/factory/gate.test.ts apps/server/src/factory/index.test.ts→ pass (69 tests + 4),vp run --filter t3 typecheck→ passSettled in this run, not by the issue: gates run inside the server process, so a lock is a plain file created
O_EXCL, not anflock; a dead server leaves files that the next start clears. The merge identity and the conflict line keep the literal wordsoftfac, as the issue asks for ledger wording to be ported as is. Non-fetch git calls getFactoryProcess's environment allowlist where softfac gave them onlyPATHandHOME; none of the extra names changes git's behaviour (noted in the doc comment).Acceptance criteria
<data>/gate/<run>/<attempt>/from the head and base shas, fetched by sha, base merged in; a conflict givesconflict—gate.test.ts"the gate runs in a fresh checkout of the pr head and never in the session's worktree", "a conflict with the base is a failed attempt with no command run", "the base is merged into the head with the daemon's identity", "a base that is already in the head is not merged", "a head that is behind the base gets a merge commit and not a fast-forward".provisionthengatecommands withsh -cthrough the process runner, 30 minutes each, cleared environment; each command's exit code and output tail asgate_command; resultpass,failorconflict— "provision runs before the gate and a failing provision command stops the attempt", "a failing gate command ends the attempt and the later commands do not run", "a command that runs past its limit is killed and counts as a failure", "a gate command that cannot start leaves no gate_command line and uses no attempt". The environment isFactoryProcess's allowlist (process.test.ts); the ledger line keeps exit code and duration, the result object carries the output tail.max_concurrent_gatesgates at once, a run never has two; lock files under<data>/gates/cleared at server start — "the lock file exists while a command runs and is gone after", "the lock is released when the gate fails with an error", "a second run for the same run while one is running returns running", "a gate is queued while max_concurrent_gates other locks are held", "clearGateLocks removes every lock file and nothing else";index.test.ts"clears the gate locks a dead server left and nothing else under gates".gate/<run id>/directory is empty after every gate-level test.run_commandsported; its fourremove_worktree/worktreestests are not ported because issue Adapter: send a message into a thread, archive it and remove its worktree #6 owns worktree removal. "gate: a fresh checkout of the pull request's head": both tests ported (gateResultsCommentexact text; the gate-level run with the ledger lines, the untouched primary clone and the empty gate directory; its state-change, PR-comment andworktree_removedassertions belong to Gating: a passing gate opens the PR for review, a failing one goes back to the session #19). "gate: the head merged with the current base": the conflict helpers and their behaviour are ported at gate level. The "standalone repository holding one commit" section that pins the checkout itself is ported too, exceptcommits_touch_path.Gate
vp i(provision) → exit 0vp test run apps/server/src/factory→ exit 0 (20 files, 522 tests)vp run --filter t3 typecheck→ exit 0Models
sonnet, host; nomodelsblock in.dw/config.yml, so Tom's roster)opus, host)Review
gate_commandlines are written only once the commands have ended in a pass or a fail, never on an error path (correctness-1); git calls parse stdout alone and keep stderr for the failure text (correctness-2); git output past the cap fails the call instead of being parsed from a cut front (correctness-3); checkout and leftover removal are asynchronous so a large tree does not block the server's event loop (reliability-1); doc note on the environment drift (security-1);resultsComment(verdict)shared shape for Gate again when a pr-open pull request's head or base moves #23's "passed again" (maintainability-2); the fast-forward test now asserts the second parent (testing-1).workspace.tssplit; dropped becausefactory/docs/design.mdfixesgate.tsas "checkout + provision + gate commands" and Gating: a passing gate opens the PR for review, a failing one goes back to the session #19's policy lands underrunner/. maintainability-3 (exportledgerCallfromledger/errors.ts) is outside this issue's files; Gating: a passing gate opens the PR for review, a failing one goes back to the session #19 can lift it. testing-2 (a test title that claims more than it checks; the other half is covered by the call-order test) and testing-3 (no test of a dirty tree after the merge being refused) are optional and left.Lessons
origin/mainshowed hundreds of deleted lines in files the issue never touched. Cause: two sibling PRs merged between Isolate and Check, so the branch was behind the base. Default:git fetchandgit merge origin/<base>right after the maker's first green, before dispatching reviewers, so they and the gate see the merged tree (field lesson 5 applied at Check rather than only at Ship).exitCode,timedOut,tail; the reviewer then found that parsed data had warnings in it. Cause:separateStderrlanded in a sibling PR after the packet was written. Default: after merging the base, re-read the changed shared modules and send their new fields to the maker as a packet amendment before review.Commits
5acb4cce6test(factory): the gate's checkout, merge, commands, locks and cleanup— Make slice (red:gate.tsabsent)7a122b802feat(factory): run a repo's provision and gate commands on a fresh checkout— Make slice6cf596e4aMerge remote-tracking branch 'origin/main' into t3/factory-issue-18— base merged in before Checke764b3543test(factory): a spawn error leaves no gate_command line, and git output is parsed from stdout alone— Check cycle 1 (red at runtime; does not typecheck until the fix, because it uses the newgitTailBytesoption)ce276d1fcfix(factory): write a gate's command lines only when its commands have ended in a pass or a fail— Check cycle 106f885e97fix(factory): git calls parse stdout only and fail when they print past the cap— Check cycle 1e979b8071fix(factory): remove a gate checkout without blocking the server's event loop— Check cycle 1101920402docs(factory): note the git environment drift, and share the results comment body— Check cycle 1848d2f6dftest(factory): a head behind the base is checked for a second parent, not only the first— Check testing nitFiles
apps/server/src/factory/gate.ts— the gate: workspace errors, results comment, lock files,makeWorkspace(hardened git checkout, merge, removal),runGateand theFactoryGateserviceapps/server/src/factory/gate.test.ts— the ported tests, against realgitwithfile://remotes and a fakegitonPATHfor argv and environment checksapps/server/src/factory/index.ts— clears gate locks when the factory startsapps/server/src/factory/index.test.ts— the clear-locks testNo seam added:
factory/SEAMSis unchanged. Labels: all sevenfactory:*labels existed on the repo; this run created none. The worktree was fast-forwarded frome4bcce9f6toorigin/mainat Isolate.