Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions AUDIT_OPEN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Open audit items

## 2026-09-27 — coordination on a minimal installation

Coordination children inherited disabled change/merge gates, so successful PR
publication ended their runs without the merge receipt required by the pair.
The API was marked failed and held the client even after a separate forge merge.
New coordination children now explicitly request classification and the existing
merge boundary; operator approval and authority rechecks remain in that path.
Regression coverage reproduces the missing flags and checks both children.
Existing failed pairs are not rewritten or treated as merged; external merge
reconciliation and the full live producer/consumer run remain acceptance work.

## 2026-09-25 — database retry pressure

Fresh-connection retries bypassed the four-connection pool without a concurrency
Expand Down
22 changes: 22 additions & 0 deletions src/coordination.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -691,3 +691,25 @@ test("sweepCoordinations advances every coordination and collects per-record err
assert.deepEqual((await listCoordinations(runtime)).map((r) => r.id).sort(), [fake.record.id, second.id].sort(), "the broken record never lists");
assert.equal(third.errors.length, 0);
});


test("coordination requires a recorded merge even when deployment-wide change gates are off", async () => {
const saved = { change: process.env.SHIP_CHANGE_CLASS, merge: process.env.SHIP_MERGE_GATE };
process.env.SHIP_CHANGE_CLASS = "0";
process.env.SHIP_MERGE_GATE = "0";
try {
const fake = await newPair();
const api = await launchNext(fake.runtime, fake.record.id);
const input = recordedInput(fake, api.runId!) as unknown as { changeClass?: boolean; mergeGate?: boolean };
assert.equal(input.changeClass, true, "API change must reach classification and its merge boundary");
assert.equal(input.mergeGate, true, "publishing a PR alone cannot satisfy the dependency");
mergeRun(fake, api.runId!, "abc123def456");
const client = await launchNext(fake.runtime, fake.record.id);
const next = recordedInput(fake, client.runId!) as unknown as { changeClass?: boolean; mergeGate?: boolean };
assert.equal(next.changeClass, true);
assert.equal(next.mergeGate, true, "client completion also requires a recorded merge");
} finally {
if (saved.change === undefined) delete process.env.SHIP_CHANGE_CLASS; else process.env.SHIP_CHANGE_CLASS = saved.change;
if (saved.merge === undefined) delete process.env.SHIP_MERGE_GATE; else process.env.SHIP_MERGE_GATE = saved.merge;
}
});
4 changes: 4 additions & 0 deletions src/coordination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,10 @@ async function launchChild(
model: claimed.model,
repo: child.repo,
journey: "change",
// Coordination depends on merge receipts, so publishing a PR alone is
// insufficient even on deployments where standalone change gates are off.
changeClass: true,
mergeGate: true,
source: "manual",
// The repos were typed by an authenticated human on the coordination
// form; the allowlist was checked at creation and binds here too.
Expand Down
Loading