feat(contract): lanes run gstack-pregate (and the sealed guard where configured) before done; handoff carries the gstack trailer and PREGATE: line

The coordinator contract gains a 'Before done' paragraph and the handoff
order GSTACK_RESULT, gstack: ok|fail <rev>, PREGATE:, gate summary; the
spec section is regenerated and docs/unattended.md states the same rule.
A new test pins the terms and their order; the byte-identity pins stay.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-13 01:19:22 +00:00
1 parent 22737a8f66
commit 6aeea57aec
4 files changed
+26 -2

No files matched your search

+11
View File
@@ -265,6 +265,17 @@ lines whose decision is still pending stay and are reported as
`DRAFT_DIRECTION_UNRESOLVED` (exit 1). `gstack-autoplan answer` runs the same
rewrite when a reply completes the gate (journal event `draft_directions_closed`).
## Lane done: pregate, the sealed guard and the handoff lines
A lane under a coordinator runs `gstack-pregate run --allow-repo-commands` on
its branch before it reports done (the unit tier on the changed files plus
the repo's guards, [docs/pregate.md#guards](pregate.md#guards)). In a repo
whose `.gstack/pregate.json` has a `sealed` key it runs `gstack-pregate sealed`
before any push or PR creation, since a PR tool may not run local hooks. Its
handoff carries, right after the `GSTACK_RESULT` line, the `gstack: ok|fail
<rev>` line and then the `PREGATE:` line. The coordinator contract
(`gstack-autoplan contract`) states the same rule word for word.
## Timing
`/autoplan` prints `ESTIMATE: ...` at Phase 0 (`gstack-autoplan-timing
+3 -1
View File
@@ -19,4 +19,6 @@ Behavior-change notices. Every user-visible behavior change outside the plan's o
Spend caps. Each lane runs under the \`--spend-cap\` the coordinator names; a lane that reaches it stops with \`status=budget_exhausted\` and reports spent, reserved and unknown charges. Paid evals are never retried to change a verdict; a rerun needs a named repair. Host-subagent spend is reported as \`unknown\`, never zero.
Handoff format. A lane reports in this order, identifiers first: PR URL (or \`no PR\` with branch and SHA), \`GSTACK_RESULT\` line, gate summary (lanes passed/failed/skipped), behavior changes (or \`none\`), spend (spent/reserved/unknown), then what it could not verify. Full thread ids (\`jam_…\`), never short codes, in every cross-thread message. Prose after the identifiers may be truncated; nothing before them may be.`;
Before done. A lane runs \`gstack-pregate run --allow-repo-commands\` on its branch (the unit tier on the changed files plus the repo's guards) and reports done only after it passes. In a repo whose \`.gstack/pregate.json\` has a \`sealed\` key, it runs \`gstack-pregate sealed\` before any push or PR creation; a refusal stops the push, because a PR tool may not run local hooks.
Handoff format. A lane reports in this order, identifiers first: PR URL (or \`no PR\` with branch and SHA), \`GSTACK_RESULT\` line, the \`gstack: ok|fail <rev>\` line, the \`PREGATE:\` line, gate summary (lanes passed/failed/skipped), behavior changes (or \`none\`), spend (spent/reserved/unknown), then what it could not verify. Full thread ids (\`jam_…\`), never short codes, in every cross-thread message. Prose after the identifiers may be truncated; nothing before them may be.`;
+3 -1
View File
@@ -445,7 +445,9 @@ Behavior-change notices. Every user-visible behavior change outside the plan's o
Spend caps. Each lane runs under the `--spend-cap` the coordinator names; a lane that reaches it stops with `status=budget_exhausted` and reports spent, reserved and unknown charges. Paid evals are never retried to change a verdict; a rerun needs a named repair. Host-subagent spend is reported as `unknown`, never zero.
Handoff format. A lane reports in this order, identifiers first: PR URL (or `no PR` with branch and SHA), `GSTACK_RESULT` line, gate summary (lanes passed/failed/skipped), behavior changes (or `none`), spend (spent/reserved/unknown), then what it could not verify. Full thread ids (`jam_…`), never short codes, in every cross-thread message. Prose after the identifiers may be truncated; nothing before them may be.
Before done. A lane runs `gstack-pregate run --allow-repo-commands` on its branch (the unit tier on the changed files plus the repo's guards) and reports done only after it passes. In a repo whose `.gstack/pregate.json` has a `sealed` key, it runs `gstack-pregate sealed` before any push or PR creation; a refusal stops the push, because a PR tool may not run local hooks.
Handoff format. A lane reports in this order, identifiers first: PR URL (or `no PR` with branch and SHA), `GSTACK_RESULT` line, the `gstack: ok|fail <rev>` line, the `PREGATE:` line, gate summary (lanes passed/failed/skipped), behavior changes (or `none`), spend (spent/reserved/unknown), then what it could not verify. Full thread ids (`jam_…`), never short codes, in every cross-thread message. Prose after the identifiers may be truncated; nothing before them may be.
```bash
[ -r "${ARCHIVE_PATH:?ARCHIVE_PATH is not set: substitute the archived spec path}" ] || { echo "ERROR: cannot read $ARCHIVE_PATH; nothing was spawned." >&2; exit 1; }
+9
View File
@@ -129,6 +129,15 @@ describe('coordinator contract', () => {
expect(rendered).toContain('gstack-autoplan contract) || {');
});
test('field wave CD: a lane runs pregate (and the sealed guard where configured) before done; the handoff carries the gstack trailer and the PREGATE: line after GSTACK_RESULT', () => {
expect(COORDINATOR_CONTRACT).toContain('`gstack-pregate run --allow-repo-commands`');
expect(COORDINATOR_CONTRACT).toContain('`gstack-pregate sealed` before any push or PR creation');
const handoff = COORDINATOR_CONTRACT.slice(COORDINATOR_CONTRACT.indexOf('Handoff format.'));
const order = ['`GSTACK_RESULT` line', '`gstack: ok|fail <rev>` line', '`PREGATE:` line', 'gate summary'].map(t => handoff.indexOf(t));
expect(order.every(i => i > 0)).toBe(true);
expect([...order].sort((a, b) => a - b)).toEqual(order);
});
test('both reviewer prompts carry the contract after the plan, labelled as data', async () => {
const { writePrompts } = await import('../lib/autoplan-prompts');
const dir = fs.mkdtempSync(path.join(require('node:os').tmpdir(), 'gstack-contract-'));