mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-07 01:15:47 +01:00
fix(review-pass): an ended outside voice with no findings is collected unavailable from its own reply, so verify keeps no NOT_REVIEWED row (GST-1104)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
b0a8abd55d
commit
2b5034dcc4
4 files changed
+16
-11
No files matched your search
@@ -1,6 +1,6 @@
|
||||
**/ship on Capy stops an outside voice that hangs and records it as missing, never as converged or as an unavailable Codex CLI (GST-1104).** TRUT-7's /ship run had two outside voices end with no result (gpt-6.1-sol stopped after 20 minutes; grok-4.7 used 16.9M tokens before it was stopped), and its Step 11 row read `outside_provider: codex`, `outside_status: unavailable`, `converged: true`.
|
||||
|
||||
### Fixed
|
||||
- **`lib/capy-review-pass.js`**: an outside voice still running with no output after `input.stuckMs` (20 minutes by default) is stopped. One that hangs, finishes empty, or stops or fails without leaving a review is collected as unavailable coverage (`COLLECT_UNAVAILABLE:`); one that stops or fails after writing its review is collected normally, so its findings are kept, and Step 11 goes on to judge the native voice instead of stopping the pass with nothing recorded. Other readers still stop the pass as before.
|
||||
- **`lib/capy-review-pass.js`**: an outside voice still running with no output after `input.stuckMs` (20 minutes by default) is stopped. One that hangs, finishes empty, or stops or fails without leaving findings is collected as unavailable coverage (`COLLECT_UNAVAILABLE:`); one that stops or fails after writing findings is collected normally, so its findings are kept, and Step 11 goes on to judge the native voice instead of stopping the pass with nothing recorded. Other readers still stop the pass as before.
|
||||
- **`lib/capy-review-pass.js`**: Step 11 returns an `OUTSIDE_RECORD:` line with `outside_provider: host-subagent`, the model that ran and its status, for the review-log rows to copy instead of the template's `codex`. When the voice was unavailable, the line tells the parent to write that voice its own row first (`status: unavailable`, `completed` and `converged` false), so the native row's convergence no longer stands in for it.
|
||||
- **`docs/unattended.md`**: "The outside voice on Capy" describes the stop, the unavailable record and the new fields.
|
||||
+3
-3
@@ -240,11 +240,11 @@ provider as the lane model or the native voice stops the pass (`STOP:`) before
|
||||
either voice is created, including on a rerun, and each outside voice prints an
|
||||
`OUTSIDE_VOICE:` line saying whether it is the certified configuration. An
|
||||
outside voice still running with no output after `input.stuckMs` (20 minutes by
|
||||
default) is stopped, and one that stops or fails without leaving a review, or
|
||||
default) is stopped, and one that stops or fails without leaving findings, or
|
||||
finishes empty, is collected as unavailable coverage (`COLLECT_UNAVAILABLE:`)
|
||||
instead of stopping the pass, so Step 11 goes on to judge the native voice. A
|
||||
stopped or failed outside voice that did leave a review is collected like any
|
||||
finished one, so its findings are kept. That outside voice then gets its own
|
||||
stopped or failed outside voice whose reply holds findings is collected like any
|
||||
finished one, so its findings are kept; one whose reply holds none is unavailable. That outside voice then gets its own
|
||||
row (`source: "host-subagent"`, `status: "unavailable"`, `completed` and
|
||||
`converged` false, written without `--finish` before the native row), so a
|
||||
missing voice never reads as converged or as an unavailable Codex CLI.
|
||||
|
||||
+10
-6
@@ -6,7 +6,7 @@
|
||||
// (input.startedAt) at most; the next call resumes from the manifest. A failed, stuck or ambiguous
|
||||
// reader, a brief mismatch or a failing bin returns `STOP:` for the parent; nothing is dropped. The outside
|
||||
// voice is the exception (GST-1104): one that hangs past stuckMs is stopped, and one that hangs, ends empty, or
|
||||
// stops or fails without a review is collected as unavailable coverage (one that left a review is collected); Step 11 returns OUTSIDE_RECORD: for its review-log rows.
|
||||
// stops or fails without findings is collected as unavailable coverage (one that left findings is collected); Step 11 returns OUTSIDE_RECORD: for its review-log rows.
|
||||
// Once Phase 2 has started <run dir>/parts (the parent of input.out), each poll or finished pass also
|
||||
// returns the next phase's section parts that fit its 32 KB (`gstack-ship-parts next`, GSTA-167 C5);
|
||||
// after a return that never reached the parent, rerun with input.resendParts to get that batch again.
|
||||
@@ -226,13 +226,17 @@ try {
|
||||
}
|
||||
const file = m.out + '/out/' + safe(r.reader) + '.txt';
|
||||
await tools.capy.write({ path: file, content: g.summary });
|
||||
if (ended) {
|
||||
const why = t.status + ' before finishing its review';
|
||||
const u = await sh(bin + ' collect --ledger ' + q(m.ledger) + roleArgs(r) + ' --output ' + q(file) + ' --merged ' + q(m.merged) + ' --manifest ' + q(mf) + ' --unavailable ' + q(why));
|
||||
if (u.code === 0) {
|
||||
lines.push(u.out.trim());
|
||||
await mark(r.reader, 'collected', { output: file, error: UNAVAILABLE + why });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const c = await sh(r.prompt_file ? bin + ' mark --manifest ' + q(mf) + ' --reader ' + q(r.reader) + ' --state collected --output ' + q(file)
|
||||
: bin + ' collect --ledger ' + q(m.ledger) + roleArgs(r) + ' --output ' + q(file) + ' --merged ' + q(m.merged) + ' --manifest ' + q(mf));
|
||||
if (c.code !== 0 && ended && c.out.includes('COLLECT_NOT_REVIEWED')) {
|
||||
const s = await recordUnavailable(m, r, t.status + ' with no review');
|
||||
if (s) return s;
|
||||
continue;
|
||||
}
|
||||
if (c.code !== 0) return stop((r.prompt_file ? 'mark ' : 'collect ') + r.reader + ' exited ' + c.code + ': ' + c.out.slice(-500));
|
||||
if (!r.prompt_file) {
|
||||
lines.push(c.out.trim());
|
||||
|
||||
@@ -539,7 +539,7 @@ describe('Capy pass program (fake host, real bins)', () => {
|
||||
for (const [pass, o] of [['x33p1', { stopped: (t: string) => t.endsWith('outside-voice') }], ['x34p1', { fail: (t: string) => t.endsWith('outside-voice') }]] as const) {
|
||||
const { dir, env } = repo();
|
||||
const out = path.join(dir, '.state/run/p1');
|
||||
const h = host(dir, env, { ...o, keepSummary: true, epoch: Date.now() });
|
||||
const h = host(dir, env, { ...o, keepSummary: true, output: (t: string) => (t.endsWith('outside-voice') ? '1. **A guard fails open** - `src/a.ts:1` accepts any input. Severity: Medium. Blocks: no.\n\nRecommendation: fix the guard because it fails open' : cleanReply(t)), epoch: Date.now() });
|
||||
const base = { base: 'main', out, pass, planFlags: ['--depth-floor', 'full'] };
|
||||
expect(await runPass(h, base)).toContain('NEXT: judge (Phase 4)');
|
||||
let res = '';
|
||||
@@ -574,6 +574,7 @@ describe('Capy pass program (fake host, real bins)', () => {
|
||||
const v = bin(dir, env, ['verify', '--ledger', path.join(out, 'review-ledger.jsonl')]);
|
||||
expect(v.stdout).toMatch(/^BRIEF outside-voice UNAVAILABLE/m);
|
||||
expect(v.stdout).not.toContain('MISMATCH');
|
||||
expect(v.stdout).not.toContain('NOT_REVIEWED');
|
||||
}, 300_000);
|
||||
|
||||
test('an audit reader marked first settles before any specialist starts; its output is returned, never merged', async () => {
|
||||
|
||||
Reference in new issue
Block a user