Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
12e96ba
F6a: push the rewritten head behind a durable marker
mchwang Oct 9, 2026
a63e5cf
Settle pushes cancel-safely and wait for the pushed head
mchwang Oct 9, 2026
b1eaaae
Wait for the pushed head even when the base moves
mchwang Oct 9, 2026
9e98ad7
State the pushed-head wait bound exactly
mchwang Oct 9, 2026
50ba765
Settle a stranded push on cancel; wait for the PR head after a refusal
mchwang Oct 9, 2026
e5ce306
Bind a post-push visibility failure; test startup settlement faithfully
mchwang Oct 9, 2026
496d4b3
Retry push settlement on a repeated cancel; test the push boundary
mchwang Oct 9, 2026
d0639c9
Trace a moved PR head through the whole rewrite lineage
mchwang Oct 9, 2026
6ab346d
Test the push boundary's state check and every settlement bound
mchwang Oct 9, 2026
0be7462
Cover cancel-safe settlement, admission and shutdown reads
mchwang Oct 9, 2026
7cc5477
Test that a failed push is reported as current
mchwang Oct 9, 2026
cedd594
Bind a failed push to the state its own settlement left
mchwang Oct 9, 2026
a0ecaf0
Bind every push exit to the state that push left
mchwang Oct 9, 2026
fc26627
Refuse checks after a push if the task changed while it was in flight
mchwang Oct 9, 2026
365cda0
Describe the pusher's expected-head rule in its doc comments
mchwang Oct 9, 2026
87dd3bf
Test the push claim's admission guards and the busy settlement skip
mchwang Oct 10, 2026
58e63e0
Address Copilot: capture the claim version, finalize at Git start, re…
mchwang Oct 10, 2026
9e1f695
State that a deadline-ended push leaves its marker unread
mchwang Oct 10, 2026
143b5f6
Merge origin/main (22e76a8) into F6a
mchwang Oct 12, 2026
93af176
Name schema version 18 in the architecture doc
mchwang Oct 12, 2026
f3bc3e1
Address Copilot: a branch read never waits on an escaped pipe holder
mchwang Oct 12, 2026
97b6305
Address Copilot: bound only escaped pipe holders on branch reads
mchwang Oct 12, 2026
230e5b6
Say which branch-read case the settlement bound excludes
mchwang Oct 12, 2026
33450c8
Name both unbounded subprocess cases, and an inspection's full settle…
mchwang Oct 12, 2026
57b7ccb
Bound cat-file reads like ls-remote; correct stale rebaser doc line
mchwang Oct 12, 2026
749a977
Address Copilot: review a collaborator head found after an unknown pu…
mchwang Oct 12, 2026
702d11e
Address Copilot: recheck the push binding before reviewing a refused …
mchwang Oct 13, 2026
f7754db
Test the binding check after the fetch; document the stale outcome
mchwang Oct 13, 2026
a734646
Test that the binding check before the fetch runs first
mchwang Oct 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions agents/process-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ export interface ProcessGroupOptions {
* Callers without durable ownership wait fail-closed instead of returning while unknown work is still live.
*/
readonly allowUnsettledReturn?: boolean;
/**
* Bound only the wait for the output pipes once the whole group is gone: a process that left the group but still holds
* them makes the call return failed (`ESTDIOHELD`) after the pipe drain, instead of waiting for it. Members of the
* group are still waited for, fail-closed. For callers whose command changes nothing (a read), so a process that
* escaped the group cannot affect anything they decide.
*/
readonly boundEscapedOutput?: boolean;
}

const DEFAULT_GRACE_MS = 5_000;
Expand Down Expand Up @@ -94,7 +101,8 @@ export const drainProcessGroup = (group: ProcessGroup): Promise<boolean> => drai
* returns control to the event loop. On abort or at the deadline the whole group gets SIGTERM, then SIGKILL after the
* grace period, so a child that ignores SIGTERM is still bounded. The promise settles only after the leader has
* exited and every other member of its group is gone, unless a caller with durable recovery ownership explicitly
* allows an unsettled return. Never rejects: failures are in the outcome, shaped like `runDocker`'s, so `status` is a
* allows an unsettled return. `boundEscapedOutput` bounds only the wait for a process outside the group that holds the
* output pipes. Never rejects: failures are in the outcome, shaped like `runDocker`'s, so `status` is a
* number only when the command ran to completion.
*/
export function runInProcessGroup(file: string, args: readonly string[],
Expand Down Expand Up @@ -175,7 +183,7 @@ export function runInProcessGroup(file: string, args: readonly string[],
options.signal?.removeEventListener('abort', onAbort);
void drainGroup(group, options.allowUnsettledReturn === true).then(async drained => {
let stdioTimer: ReturnType<typeof setTimeout> | undefined;
if (options.allowUnsettledReturn) {
if (options.allowUnsettledReturn || options.boundEscapedOutput) {
await Promise.race([closed, new Promise<void>(done => { stdioTimer = setTimeout(done, STDIO_CLOSE_MS); })]);
} else await closed;
// Cleared so a finished call never keeps the process alive.
Expand Down
12 changes: 6 additions & 6 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ Writing standard: plain language, ISO 24495-1:2023
- **Build state:**
- **In use:** plan and linking library, SQLite store, review screen, plan display/import/authoring screen, Ask (questions to a Claude agent), guarded merge with merge-queue support, ranked Issues screen, agent isolation boundary, and the single-runner lock.
- **In use for a non-demo review with a `github` block:** planning. `/api/plan/suggestions` and `/api/plan/drafts` ask Claude, through lane D, for suggestion cards or a whole next plan revision (#117, #124). Nothing becomes a revision until you apply it.
- **In use with an opt-in `runner` block in `review.json`:** the whole loop after planning. `start` and `resume` on `/api/runner` run a task's plan item by item with a Claude agent (#91). They require current plan-item approvals, bind both the task state and review version, recheck approvals before each later item, and refuse when a completed prefix no longer ends at the task's current head (#107). After an out-of-scope pause, an approved amended plan can reconcile the completed prefix and resume only the unfinished suffix (#88, PR #134). When a run ends, codeboost publishes the task's pull request, a draft if the task needs a person (#103). `prepare-merge` refreshes the published PR's exact base/head, rebases through the durable F3/F4 path, recomputes attribution and approvals, and runs allowlisted `cmd:` checks in a credential-free read-only container bound to the resulting head (#22). Approve & merge merges that pull request (#121). Cancelling a task closes its pull requests (#111). These are API actions; the screen has no buttons for them yet.
- **Partly built for pre-merge automation:** the trusted local rebase engine records one-to-one commit mappings, verifies the resulting checkout byte for byte, owns its subprocesses durably, and recovers interrupted work (#22, PR #136). Its production conflict path gives a sandboxed agent only the bounded conflict files and imports only audited results for both foreign and plan-owned commits (PRs #138, #140 and #144). `prepare-merge` now invokes that path after current pull-request base/head reads, then refreshes attribution and approvals and runs exact-head `cmd:` checks; required-check refresh, the already-fixed rerun, rewritten-head push, and guarded merge handoff remain for F6.
- **Not built:** screens for the runner actions, remote push/check refresh, review rounds, and the Queue and Learning screens.
- **In use with an opt-in `runner` block in `review.json`:** the whole loop after planning. `start` and `resume` on `/api/runner` run a task's plan item by item with a Claude agent (#91). They require current plan-item approvals, bind both the task state and review version, recheck approvals before each later item, and refuse when a completed prefix no longer ends at the task's current head (#107). After an out-of-scope pause, an approved amended plan can reconcile the completed prefix and resume only the unfinished suffix (#88, PR #134). When a run ends, codeboost publishes the task's pull request, a draft if the task needs a person (#103). `prepare-merge` refreshes the published PR's exact base/head, rebases through the durable F3/F4 path, recomputes attribution and approvals, pushes a rewritten head whose review is still clear over exactly the PR head it rewrote, and runs allowlisted `cmd:` checks in a credential-free read-only container bound to the resulting head (#22). Approve & merge merges that pull request (#121). Cancelling a task closes its pull requests (#111). These are API actions; the screen has no buttons for them yet.
- **Partly built for pre-merge automation:** the trusted local rebase engine records one-to-one commit mappings, verifies the resulting checkout byte for byte, owns its subprocesses durably, and recovers interrupted work (#22, PR #136). Its production conflict path gives a sandboxed agent only the bounded conflict files and imports only audited results for both foreign and plan-owned commits (PRs #138, #140 and #144). `prepare-merge` now invokes that path after current pull-request base/head reads, refreshes attribution and approvals, pushes the rewritten head behind a durable push marker (F6a), and runs exact-head `cmd:` checks; required-check refresh, the already-fixed rerun, and the one-click guarded merge handoff remain for F6b.
- **Not built:** screens for the runner actions, the required-check refresh and one-click merge handoff (F6b), review rounds, and the Queue and Learning screens.

## Terms used

Expand Down Expand Up @@ -357,7 +357,7 @@ The diagram shows the designed transitions. With the `runner` block, running, ne

### Stored data

All state is in one SQLite file, opened with WAL and full synchronization. Each write takes an immediate write lock. The schema version is `PRAGMA user_version`, and migrations run in explicit steps (version 17 today); an unknown version fails.
All state is in one SQLite file, opened with WAL and full synchronization. Each write takes an immediate write lock. The schema version is `PRAGMA user_version`, and migrations run in explicit steps (version 18 today); an unknown version fails.

| Group | Tables | Notes |
|---|---|---|
Expand Down Expand Up @@ -410,7 +410,7 @@ These are the decisions that shape the structure. The full list, with the reason
| C, K | Guarded merge, merge queue | Done |
| D | Agent isolation boundary | Done. Since the isolation gate: bounded diff export (#76), task-change inspection and the runner's commit as a bounded `git bundle` (#66, PRs #85 and #90), owner-scoped recovery for Ask (#95), the plan schema for Claude (#97), gitlink mounts and the pre-launch tree check (#81, #99, PR #100), and Codex refused in every phase (#93, PR #106). The runner-contract follow-ups (#51) are closed: the last one tests export and removal through a recovery handle after a real restart (PR #109). |
| E | Planning library and suggestions | Done. E4 (PR #45) added the acceptance fixtures and recorded Claude output that replays through validation and the Store. |
| F | Runner | Merged: F1 (lifecycle store, coordinator, shutdown wiring, startup recovery and the lock, planning API, feedback events, and stops that wait for their commit: #53, #74, #57, #59, #60, #96); F2a–F2b (execute prompts, audit, per-item execution, real workspace, durable safety findings: #67, #68, #92, #94, #128); F2d (already-fixed check, PR opening, branch pusher, production publishing, closing PRs on cancel: #84, #101, #110, #115, #116, #119, #123); production wiring and `start`/`resume` (#91, PRs #102, #105); planning through lane D (#117, #124, PRs #118, #120, #125); merging the task's own PR (#121, PR #126); start/resume approval, review-version, recovery and completed-prefix guards (#107, PR #130); continuation after a scope pause (#88, PR #134); the F3 durable trusted local-rebase foundation (#22, PR #136); the F4 foreign-conflict engine plus production sandbox adapter (#22, PRs #138 and #140); and sandboxed owned-commit conflict resolution (#22, PR #144). This branch adds F5: current base/head refresh, rebase admission, post-rebase attribution and approval refresh, and head-bound `cmd:` execution. F6 owns rewritten-head push, fresh required-check refresh, the already-fixed rerun, and exact-pair guarded-merge handoff. |
| F | Runner | Merged: F1 (lifecycle store, coordinator, shutdown wiring, startup recovery and the lock, planning API, feedback events, and stops that wait for their commit: #53, #74, #57, #59, #60, #96); F2a–F2b (execute prompts, audit, per-item execution, real workspace, durable safety findings: #67, #68, #92, #94, #128); F2d (already-fixed check, PR opening, branch pusher, production publishing, closing PRs on cancel: #84, #101, #110, #115, #116, #119, #123); production wiring and `start`/`resume` (#91, PRs #102, #105); planning through lane D (#117, #124, PRs #118, #120, #125); merging the task's own PR (#121, PR #126); start/resume approval, review-version, recovery and completed-prefix guards (#107, PR #130); continuation after a scope pause (#88, PR #134); the F3 durable trusted local-rebase foundation (#22, PR #136); the F4 foreign-conflict engine plus production sandbox adapter (#22, PRs #138 and #140); and sandboxed owned-commit conflict resolution (#22, PR #144). F5 (PR #149) added current base/head refresh, rebase admission, post-rebase attribution and approval refresh, and head-bound `cmd:` execution. This branch adds F6a: the durable push of the rewritten head. F6b owns fresh required-check refresh, the already-fixed rerun, and the one-click exact-pair guarded-merge handoff; F6c owns common CI. |
| Hardening | Allowlisted subprocess environments | Done: every Git command (#82, #83) and every `gh` runner (#84, #86). |
| Tests | Test suite reliability | Done. The full suite runs ordinary tests first and Docker-backed files serially; CI and the real-Docker gate passed on PR #132 (#129). |
| G | Planning screen | Done (#145). The screen displays/imports the current plan, requests and renders a next-revision draft or suggestion cards, and applies results with revision-bound exact ambiguous replay. G4 composes the browser with the production planning setup, D-backed provider boundary, F endpoints and SQLite persistence; focused adapter, recorded-output and exact-head real-Docker planning suites cover the lower boundary. |
Expand All @@ -419,7 +419,7 @@ These are the decisions that shape the structure. The full list, with the reason

## Known limits

- `prepare-merge` invokes the trusted rebase and sandboxed conflict machinery, refreshes the review, and runs head-bound `cmd:` checks. Until F6 of #22 lands, it does not push a rewritten head, wait for fresh required checks, rerun the already-fixed check, or hand the exact pair to guarded merge.
- `prepare-merge` invokes the trusted rebase and sandboxed conflict machinery, refreshes the review, pushes the rewritten head, and runs head-bound `cmd:` checks. Until F6b of #22 lands, it does not wait for fresh required checks, rerun the already-fixed check, or hand the exact pair to guarded merge; a person clicks Merge once GitHub's checks on the pushed head pass.
- Linking is line-based. It cannot see an unrelated edit inside a declared file; only the review agent and you can catch that.
- The container's guarantees are those of Docker. The agent can always reach its own vendor account.
- `cmd:` acceptance checks run during `prepare-merge`, and their passing evidence is bound to the exact reviewed head. A plan item without current passing evidence still blocks merging.
Expand Down
Loading
Loading