@sabaiway/agent-workflow-kit 5.11.2 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +117 -0
- package/README.md +3 -2
- package/SKILL.md +5 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review-await-guard.test.mjs +176 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +61 -14
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +606 -467
- package/bridges/antigravity-cli-bridge/references/review-prompt.md +42 -4
- package/bridges/codex-cli-bridge/SKILL.md +18 -5
- package/bridges/codex-cli-bridge/bin/codex-await-guard.test.mjs +161 -0
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +22 -17
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +356 -363
- package/bridges/codex-cli-bridge/bin/codex-review.sh +6 -6
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +275 -286
- package/bridges/codex-cli-bridge/capability.json +1 -1
- package/bridges/codex-cli-bridge/references/driving-codex.md +4 -2
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +3 -2
- package/bridges/codex-cli-bridge/setup/README.md +3 -1
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/hooks/gate-approve.mjs +1 -1
- package/references/modes/grounding.md +1 -1
- package/references/modes/mcp.md +37 -0
- package/references/modes/procedures.md +3 -3
- package/references/modes/recommendations.md +1 -0
- package/references/modes/uninstall.md +2 -1
- package/references/templates/agent_rules.md +4 -5
- package/tools/commands.mjs +7 -0
- package/tools/direct-run.mjs +3 -0
- package/tools/doc-parity.mjs +18 -2
- package/tools/grounding.mjs +10 -20
- package/tools/inject-methodology.mjs +2 -0
- package/tools/mcp-registration.mjs +283 -0
- package/tools/mcp-server.mjs +314 -0
- package/tools/mcp-stdio.mjs +229 -0
- package/tools/mcp.mjs +299 -0
- package/tools/procedures.mjs +7 -8
- package/tools/recommendations.mjs +90 -1
- package/tools/uninstall.mjs +356 -45
|
@@ -223,7 +223,12 @@ const makeSandbox = ({ clean = false } = {}) => {
|
|
|
223
223
|
// Capture files are per-INVOCATION: a second run() on the same sandbox must not inherit the first
|
|
224
224
|
// run's turn counter or per-turn prompt files (the fed lane reads them back by turn index).
|
|
225
225
|
let runSeq = 0;
|
|
226
|
-
|
|
226
|
+
// ASYNCHRONOUS on purpose. A blocking spawnSync here holds the event loop for the whole dispatch,
|
|
227
|
+
// which pinned this file to ONE core: 208 points in a serial chain, 91.4s solo at 103% CPU while
|
|
228
|
+
// the other seven cores idled. Awaiting the child instead lets a `{ concurrency }` describe
|
|
229
|
+
// overlap its tests. The per-test environment still rides the CHILD's options — `process.env` is
|
|
230
|
+
// never mutated, which is what keeps overlapping tests from reading each other's PATH.
|
|
231
|
+
const run = (sb, { args, env = {}, cwd, wrapper } = {}) => new Promise((settle) => {
|
|
227
232
|
const { home, bin, repo } = sb;
|
|
228
233
|
const farm = farmFor(['agy', 'agy-run']);
|
|
229
234
|
const tag = `cap-${++runSeq}`;
|
|
@@ -234,10 +239,11 @@ const run = (sb, { args, env = {}, cwd, wrapper } = {}) => {
|
|
|
234
239
|
artifactCopy: join(home, `${tag}-artifact-copy`), turns: join(home, `${tag}-turns`),
|
|
235
240
|
dispatchCwd: join(home, `${tag}-dispatch-cwd`),
|
|
236
241
|
};
|
|
237
|
-
const
|
|
242
|
+
const child = execFile('bash', [wrapper || WRAPPER, ...args], {
|
|
238
243
|
cwd: cwd || repo,
|
|
239
244
|
encoding: 'utf8',
|
|
240
245
|
timeout: 30000,
|
|
246
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
241
247
|
env: {
|
|
242
248
|
HOME: home,
|
|
243
249
|
PATH: `${bin}:${farm}`,
|
|
@@ -250,68 +256,42 @@ const run = (sb, { args, env = {}, cwd, wrapper } = {}) => {
|
|
|
250
256
|
AGY_FAKE_TURNS: cap.turns, AGY_FAKE_CWD: cap.dispatchCwd,
|
|
251
257
|
...env,
|
|
252
258
|
},
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
};
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
// Async twin of run() for the two sleep-bound timeout tests: spawnSync blocks the event loop
|
|
274
|
-
// for the whole deliberate wait, so a concurrent describe could not overlap them. Same spawn
|
|
275
|
-
// contract and captures.
|
|
276
|
-
const runAsync = (sb, { args, env = {}, cwd } = {}) =>
|
|
277
|
-
new Promise((done) => {
|
|
278
|
-
const { home, bin, repo } = sb;
|
|
279
|
-
const cap = {
|
|
280
|
-
argv: join(home, 'cap-argv'), env: join(home, 'cap-env'), prompt: join(home, 'cap-prompt'),
|
|
281
|
-
sentinel: join(home, 'cap-sentinel'), adddir: join(home, 'cap-adddir'),
|
|
282
|
-
adddirMode: join(home, 'cap-adddir-mode'), artifactMode: join(home, 'cap-artifact-mode'),
|
|
283
|
-
artifactCopy: join(home, 'cap-artifact-copy'),
|
|
284
|
-
};
|
|
285
|
-
const child = execFile('bash', [WRAPPER, ...args], {
|
|
286
|
-
cwd: cwd || repo,
|
|
287
|
-
encoding: 'utf8',
|
|
288
|
-
timeout: 30000,
|
|
289
|
-
env: {
|
|
290
|
-
HOME: home,
|
|
291
|
-
PATH: `${bin}:${farmFor(['agy', 'agy-run'])}`,
|
|
292
|
-
TMPDIR: process.env.TMPDIR ?? '/tmp',
|
|
293
|
-
AGY_FAKE_ARGV: cap.argv, AGY_FAKE_ENV: cap.env, AGY_FAKE_PROMPT: cap.prompt,
|
|
294
|
-
AGY_FAKE_SENTINEL: cap.sentinel, AGY_FAKE_ADDDIR: cap.adddir, AGY_FAKE_ADDDIR_MODE: cap.adddirMode,
|
|
295
|
-
AGY_FAKE_ARTIFACT_MODE: cap.artifactMode, AGY_FAKE_ARTIFACT_COPY: cap.artifactCopy,
|
|
296
|
-
...env,
|
|
297
|
-
},
|
|
298
|
-
}, (error, stdout, stderr) => {
|
|
299
|
-
const readIf = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : '');
|
|
300
|
-
done({
|
|
301
|
-
status: error ? (error.code ?? 1) : 0, stdout, stderr,
|
|
302
|
-
invoked: existsSync(cap.sentinel),
|
|
303
|
-
argv: readIf(cap.argv), capEnv: readIf(cap.env), prompt: readIf(cap.prompt),
|
|
304
|
-
adddir: readIf(cap.adddir).trim(), adddirMode: readIf(cap.adddirMode).trim(),
|
|
305
|
-
artifactMode: readIf(cap.artifactMode).trim(), artifactCopy: readIf(cap.artifactCopy),
|
|
306
|
-
});
|
|
259
|
+
}, (error, stdout, stderr) => {
|
|
260
|
+
const readIf = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : '');
|
|
261
|
+
// Per-turn captures are read EAGERLY: callers rmSync the sandbox before asserting.
|
|
262
|
+
const turns = existsSync(cap.turns) ? Number(readFileSync(cap.turns, 'utf8')) : 0;
|
|
263
|
+
const prompts = [];
|
|
264
|
+
const argvs = [];
|
|
265
|
+
for (let i = 1; i <= turns; i += 1) {
|
|
266
|
+
prompts.push(readIf(`${cap.prompt}.${i}`));
|
|
267
|
+
argvs.push(readIf(`${cap.argv}.${i}`));
|
|
268
|
+
}
|
|
269
|
+
settle({
|
|
270
|
+
status: error ? (error.code ?? 1) : 0, signal: error?.signal ?? null, stdout, stderr,
|
|
271
|
+
invoked: existsSync(cap.sentinel),
|
|
272
|
+
argv: readIf(cap.argv), capEnv: readIf(cap.env), prompt: readIf(cap.prompt),
|
|
273
|
+
adddir: readIf(cap.adddir).trim(), adddirMode: readIf(cap.adddirMode).trim(),
|
|
274
|
+
artifactMode: readIf(cap.artifactMode).trim(), artifactCopy: readIf(cap.artifactCopy),
|
|
275
|
+
dispatchCwd: readIf(cap.dispatchCwd).trim(), turns, prompts, argvs,
|
|
307
276
|
});
|
|
308
|
-
child.stdin.end();
|
|
309
277
|
});
|
|
278
|
+
// The wrapper refuses many inputs BEFORE it reads stdin, so the pipe can already be closed here.
|
|
279
|
+
// The blocking spawn swallowed that; an async one throws EPIPE at the test. A closed pipe is the
|
|
280
|
+
// refusal working — but ONLY EPIPE is: any other write failure is a real fault and must reach
|
|
281
|
+
// the test instead of passing as a green.
|
|
282
|
+
child.stdin.on('error', (err) => { if (err.code !== 'EPIPE') throw err; });
|
|
283
|
+
child.stdin.end();
|
|
284
|
+
});
|
|
310
285
|
|
|
311
|
-
|
|
312
|
-
|
|
286
|
+
// runAsync was the async twin kept for the two sleep-bound timeout tests, back when run() blocked.
|
|
287
|
+
// run() IS that twin now, with the fuller capture surface, so the twin is one name pointing at it
|
|
288
|
+
// — two spawn paths could only drift.
|
|
289
|
+
const runAsync = run;
|
|
290
|
+
|
|
291
|
+
describe('agy-review.sh — model policy advisory (1)', { concurrency: 2 }, () => {
|
|
292
|
+
it('warns for a non-frontier model but still runs', async () => {
|
|
313
293
|
const sb = makeSandbox();
|
|
314
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: 'Gemini 3.5 Flash (Low)' } });
|
|
294
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: 'Gemini 3.5 Flash (Low)' } });
|
|
315
295
|
rmSync(sb.home, { recursive: true, force: true });
|
|
316
296
|
assert.equal(r.status, 0, r.stderr);
|
|
317
297
|
assert.match(r.stderr, /non-frontier model 'Gemini 3.5 Flash \(Low\)'/);
|
|
@@ -319,26 +299,26 @@ describe('agy-review.sh — model policy advisory (1)', () => {
|
|
|
319
299
|
assert.match(r.argv, /Gemini 3\.5 Flash \(Low\)/, 'the chosen model reaches agy via --model');
|
|
320
300
|
});
|
|
321
301
|
|
|
322
|
-
it('AGY_PROBE=1 silences the advisory', () => {
|
|
302
|
+
it('AGY_PROBE=1 silences the advisory', async () => {
|
|
323
303
|
const sb = makeSandbox();
|
|
324
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: 'Gemini 3.5 Flash (Low)', AGY_PROBE: '1' } });
|
|
304
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: 'Gemini 3.5 Flash (Low)', AGY_PROBE: '1' } });
|
|
325
305
|
rmSync(sb.home, { recursive: true, force: true });
|
|
326
306
|
assert.equal(r.status, 0, r.stderr);
|
|
327
307
|
assert.doesNotMatch(r.stderr, /non-frontier model/);
|
|
328
308
|
});
|
|
329
309
|
|
|
330
|
-
it('the frontier default (no AGY_MODEL) earns no advisory', () => {
|
|
310
|
+
it('the frontier default (no AGY_MODEL) earns no advisory', async () => {
|
|
331
311
|
const sb = makeSandbox();
|
|
332
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'] });
|
|
312
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'] });
|
|
333
313
|
rmSync(sb.home, { recursive: true, force: true });
|
|
334
314
|
assert.equal(r.status, 0, r.stderr);
|
|
335
315
|
assert.doesNotMatch(r.stderr, /non-frontier model/);
|
|
336
316
|
assert.match(r.argv, /Gemini 3\.7 Flash \(High\)/, 'the frontier default reaches agy');
|
|
337
317
|
});
|
|
338
318
|
|
|
339
|
-
it('an explicit Gemini 3.7 Flash (High) is FRONTIER — no advisory (fork (a), maintainer 2026-08-14)', () => {
|
|
319
|
+
it('an explicit Gemini 3.7 Flash (High) is FRONTIER — no advisory (fork (a), maintainer 2026-08-14)', async () => {
|
|
340
320
|
const sb = makeSandbox();
|
|
341
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: 'Gemini 3.7 Flash (High)' } });
|
|
321
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: 'Gemini 3.7 Flash (High)' } });
|
|
342
322
|
rmSync(sb.home, { recursive: true, force: true });
|
|
343
323
|
assert.equal(r.status, 0, r.stderr);
|
|
344
324
|
assert.doesNotMatch(r.stderr, /non-frontier model/, 'a FRONTIER_SET member never warns');
|
|
@@ -354,11 +334,11 @@ describe('agy-review.sh — model policy advisory (1)', () => {
|
|
|
354
334
|
// the invocation sentinel, so a probe can never read as a spent run.
|
|
355
335
|
const FAKE_OLD_NODE = '#!/usr/bin/env bash\nprintf "v18.20.0\\n"\n';
|
|
356
336
|
|
|
357
|
-
describe('agy-review.sh — the pre-spend capability door (Decision 2)', () => {
|
|
337
|
+
describe('agy-review.sh — the pre-spend capability door (Decision 2)', { concurrency: 2 }, () => {
|
|
358
338
|
for (const flag of ['--output-format', '--disable-slash-commands']) {
|
|
359
|
-
it(`a --help that does not advertise ${flag} refuses pre-spend and names it`, () => {
|
|
339
|
+
it(`a --help that does not advertise ${flag} refuses pre-spend and names it`, async () => {
|
|
360
340
|
const sb = makeSandbox();
|
|
361
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_HELP_OMIT: flag } });
|
|
341
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_HELP_OMIT: flag } });
|
|
362
342
|
const receipts = readReceipts(sb.repo);
|
|
363
343
|
rmSync(sb.home, { recursive: true, force: true });
|
|
364
344
|
assert.notEqual(r.status, 0, r.stderr);
|
|
@@ -373,9 +353,9 @@ describe('agy-review.sh — the pre-spend capability door (Decision 2)', () => {
|
|
|
373
353
|
// is actually missing: a build lacking --disable-slash-commands answers perfectly readably and
|
|
374
354
|
// corrupts the delivered BODY instead, which the envelope explanation would send an operator
|
|
375
355
|
// hunting the wrong bug.
|
|
376
|
-
it('the refusal names what the MISSING flag buys, not one blanket envelope explanation', () => {
|
|
356
|
+
it('the refusal names what the MISSING flag buys, not one blanket envelope explanation', async () => {
|
|
377
357
|
const sb = makeSandbox();
|
|
378
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_HELP_OMIT: '--disable-slash-commands' } });
|
|
358
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_HELP_OMIT: '--disable-slash-commands' } });
|
|
379
359
|
rmSync(sb.home, { recursive: true, force: true });
|
|
380
360
|
assert.notEqual(r.status, 0, r.stderr);
|
|
381
361
|
assert.equal(r.invoked, false);
|
|
@@ -383,9 +363,9 @@ describe('agy-review.sh — the pre-spend capability door (Decision 2)', () => {
|
|
|
383
363
|
assert.doesNotMatch(r.stderr, /cannot read/, 'the unreadable-answer cost belongs to --output-format alone');
|
|
384
364
|
});
|
|
385
365
|
|
|
386
|
-
it('`agy --help` exiting non-zero refuses with a DISTINCT cause — never read as "capability present"', () => {
|
|
366
|
+
it('`agy --help` exiting non-zero refuses with a DISTINCT cause — never read as "capability present"', async () => {
|
|
387
367
|
const sb = makeSandbox();
|
|
388
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_HELP_EXIT: '3' } });
|
|
368
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_HELP_EXIT: '3' } });
|
|
389
369
|
rmSync(sb.home, { recursive: true, force: true });
|
|
390
370
|
assert.notEqual(r.status, 0, r.stderr);
|
|
391
371
|
assert.equal(r.invoked, false, 'a failed probe never becomes a paid dispatch');
|
|
@@ -393,9 +373,9 @@ describe('agy-review.sh — the pre-spend capability door (Decision 2)', () => {
|
|
|
393
373
|
assert.doesNotMatch(r.stderr, /does not advertise the flag/, 'a failed probe is not a missing-flag verdict');
|
|
394
374
|
});
|
|
395
375
|
|
|
396
|
-
it('node ABSENT refuses pre-spend, naming Node and the floor', () => {
|
|
376
|
+
it('node ABSENT refuses pre-spend, naming Node and the floor', async () => {
|
|
397
377
|
const sb = makeSandbox();
|
|
398
|
-
const r = run(sb, {
|
|
378
|
+
const r = await run(sb, {
|
|
399
379
|
args: ['code', '--facts', 'f'],
|
|
400
380
|
env: { PATH: `${sb.bin}:${farmFor(['agy', 'agy-run', 'node'])}` },
|
|
401
381
|
});
|
|
@@ -406,11 +386,11 @@ describe('agy-review.sh — the pre-spend capability door (Decision 2)', () => {
|
|
|
406
386
|
assert.match(r.stderr, /Node >= 22/);
|
|
407
387
|
});
|
|
408
388
|
|
|
409
|
-
it('node PRESENT but BELOW the floor refuses pre-spend — the version comparison really runs', () => {
|
|
389
|
+
it('node PRESENT but BELOW the floor refuses pre-spend — the version comparison really runs', async () => {
|
|
410
390
|
const sb = makeSandbox();
|
|
411
391
|
// Into $HOME/.local/bin, which the wrapper itself prepends — so the fake wins over the real node.
|
|
412
392
|
writeFileSync(join(sb.bin, 'node'), FAKE_OLD_NODE, { mode: 0o755 });
|
|
413
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
393
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
414
394
|
rmSync(sb.home, { recursive: true, force: true });
|
|
415
395
|
assert.notEqual(r.status, 0, r.stderr);
|
|
416
396
|
assert.equal(r.invoked, false, 'an existence-only check would have dispatched here');
|
|
@@ -427,9 +407,9 @@ describe('agy-review.sh — the pre-spend capability door (Decision 2)', () => {
|
|
|
427
407
|
['the flag named inside ANOTHER option`s description', ' --json-schema enforce structured output; see --output-format'],
|
|
428
408
|
['another option whose description BEGINS with the flag name', ' --other --output-format is unsupported on this build'],
|
|
429
409
|
]) {
|
|
430
|
-
it(`${name} does NOT open the door — an option declaration is required`, () => {
|
|
410
|
+
it(`${name} does NOT open the door — an option declaration is required`, async () => {
|
|
431
411
|
const sb = makeSandbox();
|
|
432
|
-
const r = run(sb, {
|
|
412
|
+
const r = await run(sb, {
|
|
433
413
|
args: ['code', '--facts', 'f'],
|
|
434
414
|
env: { AGY_FAKE_HELP_OMIT: '--output-format', AGY_FAKE_HELP_EXTRA: extraHelpLine },
|
|
435
415
|
});
|
|
@@ -449,9 +429,9 @@ describe('agy-review.sh — the pre-spend capability door (Decision 2)', () => {
|
|
|
449
429
|
['an =<value> rendering', ' --output-format=<fmt> the joined-value rendering'],
|
|
450
430
|
['a declaration with no description at all', ' --output-format'],
|
|
451
431
|
]) {
|
|
452
|
-
it(`${name} READS as declared — a capable build is never falsely refused`, () => {
|
|
432
|
+
it(`${name} READS as declared — a capable build is never falsely refused`, async () => {
|
|
453
433
|
const sb = makeSandbox();
|
|
454
|
-
const r = run(sb, {
|
|
434
|
+
const r = await run(sb, {
|
|
455
435
|
args: ['code', '--facts', 'f'],
|
|
456
436
|
env: { AGY_FAKE_HELP_OMIT: '--output-format', AGY_FAKE_HELP_EXTRA: extraHelpLine },
|
|
457
437
|
});
|
|
@@ -461,9 +441,9 @@ describe('agy-review.sh — the pre-spend capability door (Decision 2)', () => {
|
|
|
461
441
|
});
|
|
462
442
|
}
|
|
463
443
|
|
|
464
|
-
it('the agy-capability refusal carries the INSTALLED version and the upgrade command', () => {
|
|
444
|
+
it('the agy-capability refusal carries the INSTALLED version and the upgrade command', async () => {
|
|
465
445
|
const sb = makeSandbox();
|
|
466
|
-
const r = run(sb, {
|
|
446
|
+
const r = await run(sb, {
|
|
467
447
|
args: ['code', '--facts', 'f'],
|
|
468
448
|
env: { AGY_FAKE_HELP_OMIT: '--output-format', AGY_FAKE_VERSION: '1.0.9' },
|
|
469
449
|
});
|
|
@@ -473,9 +453,9 @@ describe('agy-review.sh — the pre-spend capability door (Decision 2)', () => {
|
|
|
473
453
|
assert.match(r.stderr, /agy update/, 'and the command that fixes it');
|
|
474
454
|
});
|
|
475
455
|
|
|
476
|
-
it('a capable host passes the door and still dispatches (the door is not a blanket refusal)', () => {
|
|
456
|
+
it('a capable host passes the door and still dispatches (the door is not a blanket refusal)', async () => {
|
|
477
457
|
const sb = makeSandbox();
|
|
478
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
458
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
479
459
|
rmSync(sb.home, { recursive: true, force: true });
|
|
480
460
|
assert.equal(r.status, 0, r.stderr);
|
|
481
461
|
assert.equal(r.invoked, true);
|
|
@@ -487,10 +467,10 @@ describe('agy-review.sh — the pre-spend capability door (Decision 2)', () => {
|
|
|
487
467
|
// change one byte of this: stdout carries the model's answer and NOTHING else (the posture banner
|
|
488
468
|
// is stderr), and the receipt records the verdict parsed out of that same answer. Green before the
|
|
489
469
|
// switch, green after — anything the transport quietly rewrites fails here.
|
|
490
|
-
describe('agy-review.sh — the operator-visible answer, byte-for-byte (characterization)', () => {
|
|
491
|
-
it('stdout is EXACTLY the model answer, and the receipt records the verdict parsed from it', () => {
|
|
470
|
+
describe('agy-review.sh — the operator-visible answer, byte-for-byte (characterization)', { concurrency: 2 }, () => {
|
|
471
|
+
it('stdout is EXACTLY the model answer, and the receipt records the verdict parsed from it', async () => {
|
|
492
472
|
const sb = makeSandbox();
|
|
493
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
473
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
494
474
|
const receipts = readReceipts(sb.repo);
|
|
495
475
|
rmSync(sb.home, { recursive: true, force: true });
|
|
496
476
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -500,7 +480,7 @@ describe('agy-review.sh — the operator-visible answer, byte-for-byte (characte
|
|
|
500
480
|
|
|
501
481
|
// The bytes a transport is most likely to mangle: blank lines, non-ASCII, quotes, a backslash, a
|
|
502
482
|
// tab. If the answer ever rides a JSON field, this is the test that catches a lossy round-trip.
|
|
503
|
-
it('blank lines, multibyte, quotes, backslashes and tabs all survive to stdout unchanged', () => {
|
|
483
|
+
it('blank lines, multibyte, quotes, backslashes and tabs all survive to stdout unchanged', async () => {
|
|
504
484
|
const answer = [
|
|
505
485
|
'### Verdict',
|
|
506
486
|
'SHIP — «clean», no nits.',
|
|
@@ -515,7 +495,7 @@ describe('agy-review.sh — the operator-visible answer, byte-for-byte (characte
|
|
|
515
495
|
'none',
|
|
516
496
|
].join('\n');
|
|
517
497
|
const sb = makeSandbox();
|
|
518
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: answer } });
|
|
498
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: answer } });
|
|
519
499
|
const receipts = readReceipts(sb.repo);
|
|
520
500
|
rmSync(sb.home, { recursive: true, force: true });
|
|
521
501
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -528,7 +508,7 @@ describe('agy-review.sh — the operator-visible answer, byte-for-byte (characte
|
|
|
528
508
|
// Every lane runs `--output-format json` and the wrapper reads the envelope's `response` instead of
|
|
529
509
|
// raw stdout. The review CONTRACT does not move: the same prompt, the same mandated shape, the same
|
|
530
510
|
// D4 arm. What changes is that a fact the wrapper used to guess at now arrives named.
|
|
531
|
-
describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', () => {
|
|
511
|
+
describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', { concurrency: 2 }, () => {
|
|
532
512
|
// The transport is BOTH flags: the envelope carries the answer, and --disable-slash-commands keeps
|
|
533
513
|
// a change-set line that happens to begin with a slash command as BODY rather than an instruction
|
|
534
514
|
// the CLI expands. The pre-spend door requires both, so the dispatch must pass both — a door that
|
|
@@ -539,16 +519,16 @@ describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', (
|
|
|
539
519
|
return at !== -1 && tokens[at + 1] === 'json' && tokens.includes('--disable-slash-commands');
|
|
540
520
|
};
|
|
541
521
|
|
|
542
|
-
it('all three lanes carry the transport flags (single, fed, resume)', () => {
|
|
522
|
+
it('all three lanes carry the transport flags (single, fed, resume)', async () => {
|
|
543
523
|
const single = makeSandbox();
|
|
544
|
-
const one = run(single, { args: ['code', '--facts', 'f'] });
|
|
524
|
+
const one = await run(single, { args: ['code', '--facts', 'f'] });
|
|
545
525
|
rmSync(single.home, { recursive: true, force: true });
|
|
546
526
|
assert.equal(one.status, 0, one.stderr);
|
|
547
527
|
assert.ok(argvCarriesTransport(one.argv), `single lane argv: ${one.argv}`);
|
|
548
528
|
|
|
549
529
|
const fedSb = makeSandbox();
|
|
550
530
|
seedFedChangeSet(fedSb);
|
|
551
|
-
const fed = fedRun(fedSb);
|
|
531
|
+
const fed = await fedRun(fedSb);
|
|
552
532
|
rmSync(fedSb.home, { recursive: true, force: true });
|
|
553
533
|
assert.equal(fed.status, 0, fed.stderr);
|
|
554
534
|
assert.ok(fed.argvs.length >= 3, 'the fixture really chunks');
|
|
@@ -557,16 +537,16 @@ describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', (
|
|
|
557
537
|
}
|
|
558
538
|
|
|
559
539
|
const resumeSb = makeSandbox();
|
|
560
|
-
const resumed = run(resumeSb, { args: ['--continue'] });
|
|
540
|
+
const resumed = await run(resumeSb, { args: ['--continue'] });
|
|
561
541
|
rmSync(resumeSb.home, { recursive: true, force: true });
|
|
562
542
|
assert.equal(resumed.status, 0, resumed.stderr);
|
|
563
543
|
assert.ok(argvCarriesTransport(resumed.argv), `resume lane argv: ${resumed.argv}`);
|
|
564
544
|
});
|
|
565
545
|
|
|
566
|
-
it('operator-visible stdout IS the envelope response — and a feed turn`s response is neither published nor parsed', () => {
|
|
546
|
+
it('operator-visible stdout IS the envelope response — and a feed turn`s response is neither published nor parsed', async () => {
|
|
567
547
|
const sb = makeSandbox();
|
|
568
548
|
seedFedChangeSet(sb);
|
|
569
|
-
const fed = fedRun(sb);
|
|
549
|
+
const fed = await fedRun(sb);
|
|
570
550
|
const receipts = readReceipts(sb.repo);
|
|
571
551
|
rmSync(sb.home, { recursive: true, force: true });
|
|
572
552
|
assert.equal(fed.status, 0, fed.stderr);
|
|
@@ -577,9 +557,9 @@ describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', (
|
|
|
577
557
|
assert.equal(receipts[0].verdict, 'SHIP', 'only the FINAL response is parsed');
|
|
578
558
|
});
|
|
579
559
|
|
|
580
|
-
it('a non-JSON blob on a ZERO exit is an UNREADABLE review: distinct exit, NO receipt, the cause names the envelope', () => {
|
|
560
|
+
it('a non-JSON blob on a ZERO exit is an UNREADABLE review: distinct exit, NO receipt, the cause names the envelope', async () => {
|
|
581
561
|
const sb = makeSandbox();
|
|
582
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_RAW_STDOUT: 'jetski: not an envelope at all\n' } });
|
|
562
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_RAW_STDOUT: 'jetski: not an envelope at all\n' } });
|
|
583
563
|
const receipts = readReceipts(sb.repo);
|
|
584
564
|
rmSync(sb.home, { recursive: true, force: true });
|
|
585
565
|
assert.equal(r.status, 5, `an unreadable envelope has its OWN code, not the D4 4: ${r.stderr}`);
|
|
@@ -588,9 +568,9 @@ describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', (
|
|
|
588
568
|
assert.match(r.stderr, /not a readable agy JSON envelope/, 'and the wrapper says what that means');
|
|
589
569
|
});
|
|
590
570
|
|
|
591
|
-
it('a non-SUCCESS status is unreadable too — its own named cause, NO receipt', () => {
|
|
571
|
+
it('a non-SUCCESS status is unreadable too — its own named cause, NO receipt', async () => {
|
|
592
572
|
const sb = makeSandbox();
|
|
593
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_STATUS: 'ERROR' } });
|
|
573
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_STATUS: 'ERROR' } });
|
|
594
574
|
const receipts = readReceipts(sb.repo);
|
|
595
575
|
rmSync(sb.home, { recursive: true, force: true });
|
|
596
576
|
assert.equal(r.status, 5, r.stderr);
|
|
@@ -600,9 +580,9 @@ describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', (
|
|
|
600
580
|
|
|
601
581
|
// Decision 4: the CLI's own failure WINS. The envelope is parsed only on a zero exit, so a
|
|
602
582
|
// non-zero run keeps its code and its message and never has a parse error layered over it.
|
|
603
|
-
it('a non-zero CLI exit keeps its code and message on the SINGLE lane — no envelope error replaces them', () => {
|
|
583
|
+
it('a non-zero CLI exit keeps its code and message on the SINGLE lane — no envelope error replaces them', async () => {
|
|
604
584
|
const sb = makeSandbox();
|
|
605
|
-
const r = run(sb, {
|
|
585
|
+
const r = await run(sb, {
|
|
606
586
|
args: ['code', '--facts', 'f'],
|
|
607
587
|
env: { AGY_FAKE_EXIT: '7', AGY_FAKE_RAW_STDOUT: 'partial plain text\n', AGY_FAKE_STDERR: 'AGY_CLI_OWN_FAILURE' },
|
|
608
588
|
});
|
|
@@ -615,10 +595,10 @@ describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', (
|
|
|
615
595
|
assert.equal(receipts.length, 0);
|
|
616
596
|
});
|
|
617
597
|
|
|
618
|
-
it('a non-zero CLI exit keeps its code and message on the FED lane too', () => {
|
|
598
|
+
it('a non-zero CLI exit keeps its code and message on the FED lane too', async () => {
|
|
619
599
|
const sb = makeSandbox();
|
|
620
600
|
seedFedChangeSet(sb);
|
|
621
|
-
const fed = fedRun(sb, { AGY_FAKE_FAIL_TURN: '2' });
|
|
601
|
+
const fed = await fedRun(sb, { AGY_FAKE_FAIL_TURN: '2' });
|
|
622
602
|
const receipts = readReceipts(sb.repo);
|
|
623
603
|
rmSync(sb.home, { recursive: true, force: true });
|
|
624
604
|
assert.equal(fed.status, 3, 'the failing turn`s own exit code survives');
|
|
@@ -630,9 +610,9 @@ describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', (
|
|
|
630
610
|
|
|
631
611
|
// The switch must not move WHICH arm fires: a readable envelope carrying a verdict-less answer is
|
|
632
612
|
// still the D4 failed review, exit 4 — not the transport failure.
|
|
633
|
-
it('a READABLE envelope with no verdict still exits 4 through the EXISTING D4 arm', () => {
|
|
613
|
+
it('a READABLE envelope with no verdict still exits 4 through the EXISTING D4 arm', async () => {
|
|
634
614
|
const sb = makeSandbox();
|
|
635
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: 'prose without the mandated section' } });
|
|
615
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: 'prose without the mandated section' } });
|
|
636
616
|
const receipts = readReceipts(sb.repo);
|
|
637
617
|
rmSync(sb.home, { recursive: true, force: true });
|
|
638
618
|
assert.equal(r.status, 4, 'the D4 arm owns this, not the envelope arm');
|
|
@@ -645,11 +625,11 @@ describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', (
|
|
|
645
625
|
// (~/.local/bin/agy-review -> <placed>/bin/agy-review.sh). An unresolved BASH_SOURCE names the
|
|
646
626
|
// LINK's directory, so every sibling payload — the envelope reader above all — would be looked
|
|
647
627
|
// for beside the link and EVERY review would refuse pre-spend on a correct install.
|
|
648
|
-
it('a review launched through a managed SYMLINK resolves its own sibling payload and still attests', () => {
|
|
628
|
+
it('a review launched through a managed SYMLINK resolves its own sibling payload and still attests', async () => {
|
|
649
629
|
const sb = makeSandbox();
|
|
650
630
|
const linked = join(sb.bin, 'agy-review');
|
|
651
631
|
symlinkSync(WRAPPER, linked);
|
|
652
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], wrapper: linked });
|
|
632
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], wrapper: linked });
|
|
653
633
|
const receipts = readReceipts(sb.repo);
|
|
654
634
|
rmSync(sb.home, { recursive: true, force: true });
|
|
655
635
|
assert.equal(r.status, 0, `a symlinked launch must behave identically: ${r.stderr}`);
|
|
@@ -663,7 +643,7 @@ describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', (
|
|
|
663
643
|
// shape is an absurd CHAIN — and it is testable exactly because the kernel tolerates a far longer
|
|
664
644
|
// chain than this bound. An unbounded walk spins until the harness kills it and prints nothing,
|
|
665
645
|
// so the assertion is on the MESSAGE: an infinite loop can never produce it.
|
|
666
|
-
it('a symlink chain past the hop bound refuses LOUDLY and spends nothing', () => {
|
|
646
|
+
it('a symlink chain past the hop bound refuses LOUDLY and spends nothing', async () => {
|
|
667
647
|
const sb = makeSandbox();
|
|
668
648
|
const chainDir = join(sb.home, 'link-chain');
|
|
669
649
|
mkdirSync(chainDir, { recursive: true });
|
|
@@ -672,7 +652,7 @@ describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', (
|
|
|
672
652
|
symlinkSync(previous, link);
|
|
673
653
|
return link;
|
|
674
654
|
}, WRAPPER);
|
|
675
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], wrapper: chainTip });
|
|
655
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], wrapper: chainTip });
|
|
676
656
|
rmSync(sb.home, { recursive: true, force: true });
|
|
677
657
|
assert.equal(r.status, 127, `the walk must refuse, not spin: ${r.stderr}`);
|
|
678
658
|
assert.match(r.stderr, /exceeded 16 symlink hops/, 'the refusal names the bound it hit');
|
|
@@ -680,10 +660,10 @@ describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', (
|
|
|
680
660
|
assert.equal(r.invoked, false, 'nothing is spent');
|
|
681
661
|
});
|
|
682
662
|
|
|
683
|
-
it('the resume lanes parse the envelope and mint the continuation receipt exactly as before', () => {
|
|
663
|
+
it('the resume lanes parse the envelope and mint the continuation receipt exactly as before', async () => {
|
|
684
664
|
for (const args of [['--continue'], ['--conversation', 'conv-xyz']]) {
|
|
685
665
|
const sb = makeSandbox();
|
|
686
|
-
const r = run(sb, { args, env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
666
|
+
const r = await run(sb, { args, env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
687
667
|
const receipts = readReceipts(sb.repo);
|
|
688
668
|
rmSync(sb.home, { recursive: true, force: true });
|
|
689
669
|
assert.equal(r.status, 0, `${args[0]}: ${r.stderr}`);
|
|
@@ -695,17 +675,17 @@ describe('agy-review.sh — the dispatch rides the JSON envelope (Phase 4.3)', (
|
|
|
695
675
|
});
|
|
696
676
|
});
|
|
697
677
|
|
|
698
|
-
describe('agy-review.sh — guard + grounding (2, 3)', () => {
|
|
699
|
-
it('the model/cutoff GUARD line is in the captured prompt', () => {
|
|
678
|
+
describe('agy-review.sh — guard + grounding (2, 3)', { concurrency: 2 }, () => {
|
|
679
|
+
it('the model/cutoff GUARD line is in the captured prompt', async () => {
|
|
700
680
|
const sb = makeSandbox();
|
|
701
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'] });
|
|
681
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'] });
|
|
702
682
|
rmSync(sb.home, { recursive: true, force: true });
|
|
703
683
|
assert.match(r.prompt, /Do NOT comment on AI model names\/versions or your own knowledge cutoff/);
|
|
704
684
|
});
|
|
705
685
|
|
|
706
|
-
it('--facts / --decided / --focus all reach the prompt', () => {
|
|
686
|
+
it('--facts / --decided / --focus all reach the prompt', async () => {
|
|
707
687
|
const sb = makeSandbox();
|
|
708
|
-
const r = run(sb, { args: [
|
|
688
|
+
const r = await run(sb, { args: [
|
|
709
689
|
'code', '--facts', 'GROUNDED_FACT_MARKER', '--decided', 'DECIDED_MARKER', '--focus', 'FOCUS_MARKER',
|
|
710
690
|
] });
|
|
711
691
|
rmSync(sb.home, { recursive: true, force: true });
|
|
@@ -717,29 +697,29 @@ describe('agy-review.sh — guard + grounding (2, 3)', () => {
|
|
|
717
697
|
assert.match(r.prompt, /## Focus\nFOCUS_MARKER/);
|
|
718
698
|
});
|
|
719
699
|
|
|
720
|
-
it('--facts @file reads the file; --decided @file too', () => {
|
|
700
|
+
it('--facts @file reads the file; --decided @file too', async () => {
|
|
721
701
|
const sb = makeSandbox();
|
|
722
702
|
writeFileSync(join(sb.repo, 'facts.md'), 'FILE_FACT_BODY\n');
|
|
723
703
|
writeFileSync(join(sb.repo, 'decided.md'), 'FILE_DECIDED_BODY\n');
|
|
724
|
-
const r = run(sb, { args: ['code', '--facts', '@facts.md', '--decided', '@decided.md'] });
|
|
704
|
+
const r = await run(sb, { args: ['code', '--facts', '@facts.md', '--decided', '@decided.md'] });
|
|
725
705
|
rmSync(sb.home, { recursive: true, force: true });
|
|
726
706
|
assert.equal(r.status, 0, r.stderr);
|
|
727
707
|
assert.match(r.prompt, /FILE_FACT_BODY/);
|
|
728
708
|
assert.match(r.prompt, /FILE_DECIDED_BODY/);
|
|
729
709
|
});
|
|
730
710
|
|
|
731
|
-
it('merges --focus and trailing focus words into one Focus block, in parse order', () => {
|
|
711
|
+
it('merges --focus and trailing focus words into one Focus block, in parse order', async () => {
|
|
732
712
|
const sb = makeSandbox();
|
|
733
|
-
const r = run(sb, { args: ['code', '--facts', 'f', '--focus', 'first', 'second', 'third'] });
|
|
713
|
+
const r = await run(sb, { args: ['code', '--facts', 'f', '--focus', 'first', 'second', 'third'] });
|
|
734
714
|
rmSync(sb.home, { recursive: true, force: true });
|
|
735
715
|
assert.equal(r.status, 0, r.stderr);
|
|
736
716
|
assert.match(r.prompt, /## Focus\nfirst second third/);
|
|
737
717
|
});
|
|
738
718
|
|
|
739
|
-
it('plan mode with no --facts keeps the warning and proceeds (unchanged contract)', () => {
|
|
719
|
+
it('plan mode with no --facts keeps the warning and proceeds (unchanged contract)', async () => {
|
|
740
720
|
const sb = makeSandbox();
|
|
741
721
|
writeFileSync(join(sb.repo, 'p.md'), '# plan body\n');
|
|
742
|
-
const r = run(sb, { args: ['plan', 'p.md'] });
|
|
722
|
+
const r = await run(sb, { args: ['plan', 'p.md'] });
|
|
743
723
|
rmSync(sb.home, { recursive: true, force: true });
|
|
744
724
|
assert.equal(r.status, 0, r.stderr);
|
|
745
725
|
assert.match(r.stderr, /no --facts supplied/);
|
|
@@ -753,10 +733,10 @@ describe('agy-review.sh — guard + grounding (2, 3)', () => {
|
|
|
753
733
|
// the run would be paid for and attest nothing. The wrapper refuses BEFORE the spend, keyed on the
|
|
754
734
|
// resolved CONTENT (an empty --facts payload refuses identically). Escapes: the explicit
|
|
755
735
|
// --ungrounded flag (throwaway opinion) and AGY_PROBE=1 (a probe receipt never attests anyway).
|
|
756
|
-
describe('agy-review.sh — code mode fails CLOSED without grounded facts (D4)', () => {
|
|
757
|
-
it('code mode with no --facts exits 2 before any agy invocation', () => {
|
|
736
|
+
describe('agy-review.sh — code mode fails CLOSED without grounded facts (D4)', { concurrency: 2 }, () => {
|
|
737
|
+
it('code mode with no --facts exits 2 before any agy invocation', async () => {
|
|
758
738
|
const sb = makeSandbox();
|
|
759
|
-
const r = run(sb, { args: ['code'] });
|
|
739
|
+
const r = await run(sb, { args: ['code'] });
|
|
760
740
|
rmSync(sb.home, { recursive: true, force: true });
|
|
761
741
|
assert.equal(r.status, 2, r.stderr);
|
|
762
742
|
assert.equal(r.invoked, false, 'the refusal must fire before any agy invocation — zero runs spent');
|
|
@@ -767,19 +747,19 @@ describe('agy-review.sh — code mode fails CLOSED without grounded facts (D4)',
|
|
|
767
747
|
assert.ok(existsSync(hint[1]), 'the resolved hint path exists on this layout');
|
|
768
748
|
});
|
|
769
749
|
|
|
770
|
-
it('code mode with --facts naming an EMPTY payload exits 2 before any agy invocation', () => {
|
|
750
|
+
it('code mode with --facts naming an EMPTY payload exits 2 before any agy invocation', async () => {
|
|
771
751
|
const sb = makeSandbox();
|
|
772
752
|
writeFileSync(join(sb.repo, 'empty-facts.md'), '');
|
|
773
|
-
const r = run(sb, { args: ['code', '--facts', '@empty-facts.md'] });
|
|
753
|
+
const r = await run(sb, { args: ['code', '--facts', '@empty-facts.md'] });
|
|
774
754
|
rmSync(sb.home, { recursive: true, force: true });
|
|
775
755
|
assert.equal(r.status, 2, 'the refusal keys on the CONTENT, not the flag');
|
|
776
756
|
assert.equal(r.invoked, false, 'an empty payload must not spend a run');
|
|
777
757
|
assert.match(r.stderr, /agy-review code --facts @/);
|
|
778
758
|
});
|
|
779
759
|
|
|
780
|
-
it('code --ungrounded proceeds and the receipt records grounded:false', () => {
|
|
760
|
+
it('code --ungrounded proceeds and the receipt records grounded:false', async () => {
|
|
781
761
|
const sb = makeSandbox();
|
|
782
|
-
const r = run(sb, { args: ['code', '--ungrounded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
762
|
+
const r = await run(sb, { args: ['code', '--ungrounded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
783
763
|
const receipts = readReceipts(sb.repo);
|
|
784
764
|
rmSync(sb.home, { recursive: true, force: true });
|
|
785
765
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -790,9 +770,9 @@ describe('agy-review.sh — code mode fails CLOSED without grounded facts (D4)',
|
|
|
790
770
|
assert.match(r.stderr, /no --facts supplied/, 'the escape path stays loud, never silent');
|
|
791
771
|
});
|
|
792
772
|
|
|
793
|
-
it('AGY_PROBE=1 code with no --facts proceeds and the receipt records probe:true', () => {
|
|
773
|
+
it('AGY_PROBE=1 code with no --facts proceeds and the receipt records probe:true', async () => {
|
|
794
774
|
const sb = makeSandbox();
|
|
795
|
-
const r = run(sb, { args: ['code'], env: { AGY_PROBE: '1', AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
775
|
+
const r = await run(sb, { args: ['code'], env: { AGY_PROBE: '1', AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
796
776
|
const receipts = readReceipts(sb.repo);
|
|
797
777
|
rmSync(sb.home, { recursive: true, force: true });
|
|
798
778
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -801,28 +781,28 @@ describe('agy-review.sh — code mode fails CLOSED without grounded facts (D4)',
|
|
|
801
781
|
assert.equal(receipts[0].grounded, false);
|
|
802
782
|
});
|
|
803
783
|
|
|
804
|
-
it('--ungrounded with --facts is a refusal (contradiction)', () => {
|
|
784
|
+
it('--ungrounded with --facts is a refusal (contradiction)', async () => {
|
|
805
785
|
const sb = makeSandbox();
|
|
806
|
-
const r = run(sb, { args: ['code', '--ungrounded', '--facts', 'f'] });
|
|
786
|
+
const r = await run(sb, { args: ['code', '--ungrounded', '--facts', 'f'] });
|
|
807
787
|
rmSync(sb.home, { recursive: true, force: true });
|
|
808
788
|
assert.equal(r.status, 2);
|
|
809
789
|
assert.equal(r.invoked, false);
|
|
810
790
|
assert.match(r.stderr, /--ungrounded contradicts --facts/);
|
|
811
791
|
});
|
|
812
792
|
|
|
813
|
-
it('--ungrounded outside code mode is a refusal', () => {
|
|
793
|
+
it('--ungrounded outside code mode is a refusal', async () => {
|
|
814
794
|
const sb = makeSandbox();
|
|
815
795
|
writeFileSync(join(sb.repo, 'p.md'), '# p\n');
|
|
816
|
-
const r = run(sb, { args: ['plan', 'p.md', '--ungrounded'] });
|
|
796
|
+
const r = await run(sb, { args: ['plan', 'p.md', '--ungrounded'] });
|
|
817
797
|
rmSync(sb.home, { recursive: true, force: true });
|
|
818
798
|
assert.equal(r.status, 2);
|
|
819
799
|
assert.equal(r.invoked, false);
|
|
820
800
|
assert.match(r.stderr, /--ungrounded is only valid in code mode/);
|
|
821
801
|
});
|
|
822
802
|
|
|
823
|
-
it('--ungrounded on a continuation is a refusal', () => {
|
|
803
|
+
it('--ungrounded on a continuation is a refusal', async () => {
|
|
824
804
|
const sb = makeSandbox();
|
|
825
|
-
const r = run(sb, { args: ['--continue', '--ungrounded'] });
|
|
805
|
+
const r = await run(sb, { args: ['--continue', '--ungrounded'] });
|
|
826
806
|
rmSync(sb.home, { recursive: true, force: true });
|
|
827
807
|
assert.equal(r.status, 2);
|
|
828
808
|
assert.equal(r.invoked, false);
|
|
@@ -830,11 +810,11 @@ describe('agy-review.sh — code mode fails CLOSED without grounded facts (D4)',
|
|
|
830
810
|
});
|
|
831
811
|
});
|
|
832
812
|
|
|
833
|
-
describe('agy-review.sh — code-mode precomputed diff (4, 5, 8)', () => {
|
|
834
|
-
it('assembles repo map + status + untracked CONTENTS', () => {
|
|
813
|
+
describe('agy-review.sh — code-mode precomputed diff (4, 5, 8)', { concurrency: 2 }, () => {
|
|
814
|
+
it('assembles repo map + status + untracked CONTENTS', async () => {
|
|
835
815
|
const sb = makeSandbox();
|
|
836
816
|
writeFileSync(join(sb.repo, 'untra.txt'), 'UNIQUE_UNTRACKED_BODY\n');
|
|
837
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
817
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
838
818
|
rmSync(sb.home, { recursive: true, force: true });
|
|
839
819
|
assert.equal(r.status, 0, r.stderr);
|
|
840
820
|
for (const sec of [/repo file map/, /git status/, /untracked: untra\.txt/, /UNIQUE_UNTRACKED_BODY/]) {
|
|
@@ -842,46 +822,46 @@ describe('agy-review.sh — code-mode precomputed diff (4, 5, 8)', () => {
|
|
|
842
822
|
}
|
|
843
823
|
});
|
|
844
824
|
|
|
845
|
-
it('skips a binary untracked file (noted; raw bytes not inlined)', () => {
|
|
825
|
+
it('skips a binary untracked file (noted; raw bytes not inlined)', async () => {
|
|
846
826
|
const sb = makeSandbox();
|
|
847
827
|
writeFileSync(join(sb.repo, 'blob.bin'), Buffer.from([0x00, 0x01, 0x02, 0x00, 0x42]));
|
|
848
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
828
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
849
829
|
rmSync(sb.home, { recursive: true, force: true });
|
|
850
830
|
assert.match(r.prompt, /binary, skipped\): blob\.bin/);
|
|
851
831
|
});
|
|
852
832
|
|
|
853
|
-
it('does not follow an untracked symlink (no out-of-tree leak)', () => {
|
|
833
|
+
it('does not follow an untracked symlink (no out-of-tree leak)', async () => {
|
|
854
834
|
const sb = makeSandbox();
|
|
855
835
|
const secret = join(sb.home, 'outside-secret.txt'); // OUTSIDE the repo
|
|
856
836
|
writeFileSync(secret, 'TOP_SECRET_LEAK_MARKER\n');
|
|
857
837
|
symlinkSync(secret, join(sb.repo, 'link-to-outside'));
|
|
858
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
838
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
859
839
|
rmSync(sb.home, { recursive: true, force: true });
|
|
860
840
|
assert.match(r.prompt, /untracked \(symlink\): link-to-outside -> /);
|
|
861
841
|
assert.doesNotMatch(r.prompt, /TOP_SECRET_LEAK_MARKER/, 'symlink target content must never leak');
|
|
862
842
|
});
|
|
863
843
|
|
|
864
|
-
it('handles untracked paths with spaces (NUL-safe)', () => {
|
|
844
|
+
it('handles untracked paths with spaces (NUL-safe)', async () => {
|
|
865
845
|
const sb = makeSandbox();
|
|
866
846
|
writeFileSync(join(sb.repo, 'a b c.txt'), 'SPACED_BODY\n');
|
|
867
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
847
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
868
848
|
rmSync(sb.home, { recursive: true, force: true });
|
|
869
849
|
assert.match(r.prompt, /untracked: a b c\.txt/);
|
|
870
850
|
assert.match(r.prompt, /SPACED_BODY/);
|
|
871
851
|
});
|
|
872
852
|
|
|
873
|
-
it('no-diff preflight: a clean tree exits 0 without invoking agy', () => {
|
|
853
|
+
it('no-diff preflight: a clean tree exits 0 without invoking agy', async () => {
|
|
874
854
|
const sb = makeSandbox({ clean: true });
|
|
875
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
855
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
876
856
|
rmSync(sb.home, { recursive: true, force: true });
|
|
877
857
|
assert.equal(r.status, 0);
|
|
878
858
|
assert.match(r.stderr, /no uncommitted changes to review/);
|
|
879
859
|
assert.equal(r.invoked, false, 'agy must NOT be invoked on a clean tree');
|
|
880
860
|
});
|
|
881
861
|
|
|
882
|
-
it('the strict output-shape footer is present in a fresh review', () => {
|
|
862
|
+
it('the strict output-shape footer is present in a fresh review', async () => {
|
|
883
863
|
const sb = makeSandbox();
|
|
884
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
864
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
885
865
|
rmSync(sb.home, { recursive: true, force: true });
|
|
886
866
|
for (const sec of [/### Verdict/, /### Blocking/, /### Non-blocking/, /### Questions/]) {
|
|
887
867
|
assert.match(r.prompt, sec);
|
|
@@ -914,11 +894,11 @@ const seedOversizedMap = (sb, count = 100) => {
|
|
|
914
894
|
const mapSectionOf = (prompt) =>
|
|
915
895
|
prompt.slice(prompt.indexOf(MAP_HEADER) + MAP_HEADER.length, prompt.indexOf('\n\n=== git status (porcelain) ==='));
|
|
916
896
|
|
|
917
|
-
describe('agy-review.sh — repo file map budget (Phase 2)', () => {
|
|
918
|
-
it('agy-review sets the map budget and degrades an over-budget map', () => {
|
|
897
|
+
describe('agy-review.sh — repo file map budget (Phase 2)', { concurrency: 2 }, () => {
|
|
898
|
+
it('agy-review sets the map budget and degrades an over-budget map', async () => {
|
|
919
899
|
const sb = makeSandbox();
|
|
920
900
|
const count = seedOversizedMap(sb);
|
|
921
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
901
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
922
902
|
rmSync(sb.home, { recursive: true, force: true });
|
|
923
903
|
assert.equal(r.status, 0, r.stderr);
|
|
924
904
|
const note = r.prompt.match(new RegExp(`=== repo file map TRUNCATED to the changed-path subset: (\\d+) of (\\d+) tracked paths shown, (\\d+) omitted \\(map budget ${AGY_MAP_BUDGET_BYTES} bytes\\) ===`));
|
|
@@ -931,10 +911,10 @@ describe('agy-review.sh — repo file map budget (Phase 2)', () => {
|
|
|
931
911
|
assert.ok(!r.prompt.includes(untouchedPath(0)), 'an untouched path is dropped, and it appears nowhere else in the payload');
|
|
932
912
|
});
|
|
933
913
|
|
|
934
|
-
it('the degraded changed-path subset itself stays inside the wrapper budget', () => {
|
|
914
|
+
it('the degraded changed-path subset itself stays inside the wrapper budget', async () => {
|
|
935
915
|
const sb = makeSandbox();
|
|
936
916
|
seedOversizedMap(sb);
|
|
937
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
917
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
938
918
|
rmSync(sb.home, { recursive: true, force: true });
|
|
939
919
|
assert.equal(r.status, 0, r.stderr);
|
|
940
920
|
const lines = mapSectionOf(r.prompt).split('\n');
|
|
@@ -945,9 +925,9 @@ describe('agy-review.sh — repo file map budget (Phase 2)', () => {
|
|
|
945
925
|
assert.ok(pathBytes <= AGY_MAP_BUDGET_BYTES, `the subset (${pathBytes} bytes) must stay inside the ${AGY_MAP_BUDGET_BYTES}-byte budget`);
|
|
946
926
|
});
|
|
947
927
|
|
|
948
|
-
it('an ordinary in-budget repo keeps the whole map, unnoted', () => {
|
|
928
|
+
it('an ordinary in-budget repo keeps the whole map, unnoted', async () => {
|
|
949
929
|
const sb = makeSandbox();
|
|
950
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
930
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
951
931
|
rmSync(sb.home, { recursive: true, force: true });
|
|
952
932
|
assert.equal(r.status, 0, r.stderr);
|
|
953
933
|
assert.match(r.prompt, /=== repo file map \(git ls-files\) ===\nbase\.txt\n/);
|
|
@@ -965,6 +945,18 @@ describe('agy-review.sh — repo file map budget (Phase 2)', () => {
|
|
|
965
945
|
const ARTIFACT_HEADER = '## The change set under review (assembled working-tree diff — repo-complete)';
|
|
966
946
|
const SHAPE_HEADER = '\n## Output — Markdown, this exact shape, nothing else';
|
|
967
947
|
const FED_CAP = 6000;
|
|
948
|
+
// The proof asks the model to COUNT to the address with no tool, so the address has to be reachable
|
|
949
|
+
// that way. The walk used to start at each part's middle: measured 847..1024 on a 701464-byte change
|
|
950
|
+
// set, which is where the three live false refusals sat. FED_WIDE_CAP gives the countability
|
|
951
|
+
// regression parts wide enough that a midpoint address is unmistakably out of reach — under the
|
|
952
|
+
// narrow FED_CAP the midpoint lands at 80, close enough to the bar that fixture drift could hide it.
|
|
953
|
+
const MAX_COUNTABLE_PROOF_ADDRESS = 40;
|
|
954
|
+
const FED_WIDE_CAP = 24000;
|
|
955
|
+
// The assembler's OWN section banners — the vocabulary the proof selector refuses, because it emits
|
|
956
|
+
// them for every change set and a model can rebuild the per-path forms from part 1's git-status
|
|
957
|
+
// block. A change set's own `=== … ===` line is NOT in this set and stays admissible.
|
|
958
|
+
const isAssemblerBanner = (line) =>
|
|
959
|
+
/^=== (repo file map|git status|staged diff|unstaged diff|untracked)/.test(line) && line.endsWith(' ===');
|
|
968
960
|
|
|
969
961
|
// A change set big enough to need several parts under FED_CAP.
|
|
970
962
|
const seedFedChangeSet = (sb, { lines = 400, multibyte = false } = {}) => {
|
|
@@ -997,12 +989,12 @@ const requestedOf = (finalPrompt) => requestedBlockOf(finalPrompt).map((item) =>
|
|
|
997
989
|
const fedRun = (sb, extraEnv = {}) =>
|
|
998
990
|
run(sb, { args: ['code', '--facts', 'grounded fact'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP), ...extraEnv } });
|
|
999
991
|
|
|
1000
|
-
describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)', () => {
|
|
1001
|
-
it('an over-cap code review feeds every part and the concatenated BODIES reproduce the change set exactly', () => {
|
|
992
|
+
describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)', { concurrency: 2 }, () => {
|
|
993
|
+
it('an over-cap code review feeds every part and the concatenated BODIES reproduce the change set exactly', async () => {
|
|
1002
994
|
const sb = makeSandbox();
|
|
1003
995
|
seedFedChangeSet(sb);
|
|
1004
|
-
const inline = run(sb, { args: ['code', '--facts', 'grounded fact'], env: { AGY_MAX_PROMPT_BYTES: '130000' } });
|
|
1005
|
-
const fed = fedRun(sb);
|
|
996
|
+
const inline = await run(sb, { args: ['code', '--facts', 'grounded fact'], env: { AGY_MAX_PROMPT_BYTES: '130000' } });
|
|
997
|
+
const fed = await fedRun(sb);
|
|
1006
998
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1007
999
|
assert.equal(inline.status, 0, inline.stderr);
|
|
1008
1000
|
assert.equal(fed.status, 0, fed.stderr);
|
|
@@ -1012,10 +1004,10 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1012
1004
|
assert.equal(bodies.join(''), inlineArtifactOf(inline.prompt), 'the bodies concatenate to the change set byte-for-byte');
|
|
1013
1005
|
});
|
|
1014
1006
|
|
|
1015
|
-
it('no envelope text appears in the reconstructed review artifact', () => {
|
|
1007
|
+
it('no envelope text appears in the reconstructed review artifact', async () => {
|
|
1016
1008
|
const sb = makeSandbox();
|
|
1017
1009
|
seedFedChangeSet(sb);
|
|
1018
|
-
const fed = fedRun(sb);
|
|
1010
|
+
const fed = await fedRun(sb);
|
|
1019
1011
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1020
1012
|
const reconstructed = fed.prompts.slice(0, -1).map(bodyOf).join('');
|
|
1021
1013
|
for (const envelope of ['--- BEGIN CHANGE-SET PART', '--- END CHANGE-SET PART', 'Chunked delivery', 'Reply with exactly OK', 'Grounded facts', 'Requested:']) {
|
|
@@ -1023,10 +1015,10 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1023
1015
|
}
|
|
1024
1016
|
});
|
|
1025
1017
|
|
|
1026
|
-
it('every fed turn prompt is under AGY_MAX_PROMPT_BYTES', () => {
|
|
1018
|
+
it('every fed turn prompt is under AGY_MAX_PROMPT_BYTES', async () => {
|
|
1027
1019
|
const sb = makeSandbox();
|
|
1028
1020
|
seedFedChangeSet(sb);
|
|
1029
|
-
const fed = fedRun(sb);
|
|
1021
|
+
const fed = await fedRun(sb);
|
|
1030
1022
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1031
1023
|
assert.equal(fed.status, 0, fed.stderr);
|
|
1032
1024
|
for (const [i, p] of fed.prompts.entries()) {
|
|
@@ -1034,10 +1026,10 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1034
1026
|
}
|
|
1035
1027
|
});
|
|
1036
1028
|
|
|
1037
|
-
it('the shape block appears only on the final turn, and every feed turn carries the acknowledge-only instruction', () => {
|
|
1029
|
+
it('the shape block appears only on the final turn, and every feed turn carries the acknowledge-only instruction', async () => {
|
|
1038
1030
|
const sb = makeSandbox();
|
|
1039
1031
|
seedFedChangeSet(sb);
|
|
1040
|
-
const fed = fedRun(sb);
|
|
1032
|
+
const fed = await fedRun(sb);
|
|
1041
1033
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1042
1034
|
const feed = fed.prompts.slice(0, -1);
|
|
1043
1035
|
const final = fed.prompts[fed.prompts.length - 1];
|
|
@@ -1056,10 +1048,10 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1056
1048
|
// lines, headless agy auto-denied it ("no output produced — a tool required the \"command\"
|
|
1057
1049
|
// permission"), and the whole answer was lost. Every turn must forbid tool use outright — the
|
|
1058
1050
|
// change set is already IN the conversation, so no tool can add anything.
|
|
1059
|
-
it('every turn forbids tool use — a denied tool loses the whole answer on this host', () => {
|
|
1051
|
+
it('every turn forbids tool use — a denied tool loses the whole answer on this host', async () => {
|
|
1060
1052
|
const sb = makeSandbox();
|
|
1061
1053
|
seedFedChangeSet(sb);
|
|
1062
|
-
const fed = fedRun(sb);
|
|
1054
|
+
const fed = await fedRun(sb);
|
|
1063
1055
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1064
1056
|
assert.equal(fed.status, 0, fed.stderr);
|
|
1065
1057
|
for (const [i, p] of fed.prompts.entries()) {
|
|
@@ -1071,10 +1063,10 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1071
1063
|
// The delivery verdict must not blame delivery for a run that produced no answer at all: the parts
|
|
1072
1064
|
// WERE fed, the model was blocked from replying. Reporting it as "the change set never arrived"
|
|
1073
1065
|
// sends the reader hunting the wrong bug.
|
|
1074
|
-
it('a final turn that produced NO answer reports that cause, never a delivery failure', () => {
|
|
1066
|
+
it('a final turn that produced NO answer reports that cause, never a delivery failure', async () => {
|
|
1075
1067
|
const sb = makeSandbox();
|
|
1076
1068
|
seedFedChangeSet(sb);
|
|
1077
|
-
const fed = fedRun(sb, { AGY_FAKE_OUTPUT: 'jetski: no output produced — a tool required the "command" permission that headless mode cannot prompt for, so it was auto-denied.' });
|
|
1069
|
+
const fed = await fedRun(sb, { AGY_FAKE_OUTPUT: 'jetski: no output produced — a tool required the "command" permission that headless mode cannot prompt for, so it was auto-denied.' });
|
|
1078
1070
|
const receipts = readReceipts(sb.repo);
|
|
1079
1071
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1080
1072
|
assert.equal(fed.status, 4, fed.stderr);
|
|
@@ -1086,31 +1078,31 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1086
1078
|
assert.equal(receipts.length, 0, 'still no receipt — an unanswered review attests nothing');
|
|
1087
1079
|
});
|
|
1088
1080
|
|
|
1089
|
-
it('an answerless final turn with NO recognizable diagnostic reports the cause as unknown', () => {
|
|
1081
|
+
it('an answerless final turn with NO recognizable diagnostic reports the cause as unknown', async () => {
|
|
1090
1082
|
const sb = makeSandbox();
|
|
1091
1083
|
seedFedChangeSet(sb);
|
|
1092
|
-
const fed = fedRun(sb, { AGY_FAKE_OUTPUT: 'something the wrapper has never seen before' });
|
|
1084
|
+
const fed = await fedRun(sb, { AGY_FAKE_OUTPUT: 'something the wrapper has never seen before' });
|
|
1093
1085
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1094
1086
|
assert.equal(fed.status, 4, fed.stderr);
|
|
1095
1087
|
assert.match(fed.stderr, /CAUSE: unknown/, 'an unrecognized failure is never dressed up as a known one');
|
|
1096
1088
|
assert.doesNotMatch(fed.stderr, /named by agy itself/);
|
|
1097
1089
|
});
|
|
1098
1090
|
|
|
1099
|
-
it('the grounding rides turn 1 only', () => {
|
|
1091
|
+
it('the grounding rides turn 1 only', async () => {
|
|
1100
1092
|
const sb = makeSandbox();
|
|
1101
1093
|
seedFedChangeSet(sb);
|
|
1102
|
-
const fed = fedRun(sb, { AGY_MAX_PROMPT_BYTES: String(FED_CAP) });
|
|
1094
|
+
const fed = await fedRun(sb, { AGY_MAX_PROMPT_BYTES: String(FED_CAP) });
|
|
1103
1095
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1104
1096
|
assert.match(fed.prompts[0], /## Grounded facts — review AGAINST these/);
|
|
1105
1097
|
assert.match(fed.prompts[0], /grounded fact/);
|
|
1106
1098
|
for (const p of fed.prompts.slice(1)) assert.ok(!p.includes('## Grounded facts'), 'the grounding is not re-sent');
|
|
1107
1099
|
});
|
|
1108
1100
|
|
|
1109
|
-
it('a multibyte body is cut at LINE boundaries — every part decodes cleanly and concatenation stays byte-exact', () => {
|
|
1101
|
+
it('a multibyte body is cut at LINE boundaries — every part decodes cleanly and concatenation stays byte-exact', async () => {
|
|
1110
1102
|
const sb = makeSandbox();
|
|
1111
1103
|
seedFedChangeSet(sb, { multibyte: true, lines: 400 });
|
|
1112
|
-
const inline = run(sb, { args: ['code', '--facts', 'grounded fact'], env: { AGY_MAX_PROMPT_BYTES: '130000' } });
|
|
1113
|
-
const fed = fedRun(sb);
|
|
1104
|
+
const inline = await run(sb, { args: ['code', '--facts', 'grounded fact'], env: { AGY_MAX_PROMPT_BYTES: '130000' } });
|
|
1105
|
+
const fed = await fedRun(sb);
|
|
1114
1106
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1115
1107
|
assert.equal(fed.status, 0, fed.stderr);
|
|
1116
1108
|
assert.ok(fed.turns >= 3, 'the multibyte fixture really chunks');
|
|
@@ -1125,12 +1117,12 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1125
1117
|
// The partitioner's two boundary defects, both caught at review: an invented separator byte on an
|
|
1126
1118
|
// unterminated last line (an extra part and an extra TURN), and an over-eager refusal for a line
|
|
1127
1119
|
// that is merely longer than the FIRST part's smaller budget.
|
|
1128
|
-
it('an artifact with NO trailing newline yields no extra or empty part, and reassembles byte-exactly', () => {
|
|
1120
|
+
it('an artifact with NO trailing newline yields no extra or empty part, and reassembles byte-exactly', async () => {
|
|
1129
1121
|
const sb = makeSandbox();
|
|
1130
1122
|
// An untracked file with no final newline: the assembled change set ends without one too.
|
|
1131
1123
|
writeFileSync(join(sb.repo, 'oversized.txt'), `${Array.from({ length: 400 }, (_, i) => `unique change-set line ${String(i).padStart(4, '0')} — a distinctive body marker ${'x'.repeat(20)}`).join('\n')}`);
|
|
1132
|
-
const inline = run(sb, { args: ['code', '--facts', 'grounded fact'], env: { AGY_MAX_PROMPT_BYTES: '130000' } });
|
|
1133
|
-
const fed = fedRun(sb);
|
|
1124
|
+
const inline = await run(sb, { args: ['code', '--facts', 'grounded fact'], env: { AGY_MAX_PROMPT_BYTES: '130000' } });
|
|
1125
|
+
const fed = await fedRun(sb);
|
|
1134
1126
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1135
1127
|
assert.equal(fed.status, 0, fed.stderr);
|
|
1136
1128
|
const bodies = fed.prompts.slice(0, -1).map(bodyOf);
|
|
@@ -1141,7 +1133,7 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1141
1133
|
assert.equal(Number(announced[2]), fed.turns, 'and no phantom turn');
|
|
1142
1134
|
});
|
|
1143
1135
|
|
|
1144
|
-
it('a line longer than the FIRST part budget but not the later one is placed, not refused', () => {
|
|
1136
|
+
it('a line longer than the FIRST part budget but not the later one is placed, not refused', async () => {
|
|
1145
1137
|
const sb = makeSandbox();
|
|
1146
1138
|
// Turn 1 carries the grounding too, so its body budget is SMALLER by exactly the grounding size.
|
|
1147
1139
|
// A fat grounding opens a real window between the two budgets; a line inside that window must be
|
|
@@ -1152,7 +1144,7 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1152
1144
|
const filler = (from, n) => Array.from({ length: n }, (_, i) => `unique change-set line ${String(from + i).padStart(4, '0')} — a distinctive body marker ${'x'.repeat(20)}`).join('\n');
|
|
1153
1145
|
const longLine = `unique long marker line ${'y'.repeat(4200)}`;
|
|
1154
1146
|
writeFileSync(join(sb.repo, 'oversized.txt'), `${filler(0, 20)}\n${longLine}\n${filler(20, 60)}\n`);
|
|
1155
|
-
const fed = run(sb, { args: ['code', '--facts', facts], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP) } });
|
|
1147
|
+
const fed = await run(sb, { args: ['code', '--facts', facts], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP) } });
|
|
1156
1148
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1157
1149
|
assert.equal(fed.status, 0, `a placeable long line must not refuse the run: ${fed.stderr}`);
|
|
1158
1150
|
const bodies = fed.prompts.slice(0, -1).map(bodyOf);
|
|
@@ -1160,20 +1152,20 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1160
1152
|
assert.ok(!bodies[0].includes(longLine), 'and it was moved OFF the smaller first part');
|
|
1161
1153
|
});
|
|
1162
1154
|
|
|
1163
|
-
it('a line that fits NO part refuses before any turn is spent', () => {
|
|
1155
|
+
it('a line that fits NO part refuses before any turn is spent', async () => {
|
|
1164
1156
|
const sb = makeSandbox();
|
|
1165
1157
|
writeFileSync(join(sb.repo, 'oversized.txt'), `head\n${'z'.repeat(FED_CAP * 2)}\ntail\n`);
|
|
1166
|
-
const fed = fedRun(sb);
|
|
1158
|
+
const fed = await fedRun(sb);
|
|
1167
1159
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1168
1160
|
assert.equal(fed.status, 2, fed.stderr);
|
|
1169
1161
|
assert.equal(fed.invoked, false, 'not one turn is spent');
|
|
1170
1162
|
assert.match(fed.stderr, /does not fit even an EMPTY fed part/);
|
|
1171
1163
|
});
|
|
1172
1164
|
|
|
1173
|
-
it('feed-turn output never reaches stdout or the parsed capture (a premature verdict is discarded)', () => {
|
|
1165
|
+
it('feed-turn output never reaches stdout or the parsed capture (a premature verdict is discarded)', async () => {
|
|
1174
1166
|
const sb = makeSandbox();
|
|
1175
1167
|
seedFedChangeSet(sb);
|
|
1176
|
-
const fed = fedRun(sb);
|
|
1168
|
+
const fed = await fedRun(sb);
|
|
1177
1169
|
const receipts = readReceipts(sb.repo);
|
|
1178
1170
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1179
1171
|
assert.equal(fed.status, 0, fed.stderr);
|
|
@@ -1183,10 +1175,10 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1183
1175
|
assert.equal(receipts[0].verdict, 'SHIP', 'only the FINAL turn is parsed into the receipt');
|
|
1184
1176
|
});
|
|
1185
1177
|
|
|
1186
|
-
it('a non-zero feed turn stops the run, spends no later turn, and writes NO receipt', () => {
|
|
1178
|
+
it('a non-zero feed turn stops the run, spends no later turn, and writes NO receipt', async () => {
|
|
1187
1179
|
const sb = makeSandbox();
|
|
1188
1180
|
seedFedChangeSet(sb);
|
|
1189
|
-
const fed = fedRun(sb, { AGY_FAKE_FAIL_TURN: '2' });
|
|
1181
|
+
const fed = await fedRun(sb, { AGY_FAKE_FAIL_TURN: '2' });
|
|
1190
1182
|
const receipts = readReceipts(sb.repo);
|
|
1191
1183
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1192
1184
|
assert.notEqual(fed.status, 0, 'a failed feed turn is a failed review');
|
|
@@ -1197,10 +1189,10 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1197
1189
|
// The hard cap is ONE wall-clock budget for the whole review. Handing each of the N+1 calls the
|
|
1198
1190
|
// full AGY_HARD_TIMEOUT multiplied the stated guarantee by the turn count — a 30m cap could run
|
|
1199
1191
|
// for hours. Each turn now gets only what is LEFT of a shared deadline.
|
|
1200
|
-
it('the hard cap is ONE budget for the whole review, not one per turn', () => {
|
|
1192
|
+
it('the hard cap is ONE budget for the whole review, not one per turn', async () => {
|
|
1201
1193
|
const sb = makeSandbox();
|
|
1202
1194
|
seedFedChangeSet(sb, { lines: 150 });
|
|
1203
|
-
const fed = run(sb, {
|
|
1195
|
+
const fed = await run(sb, {
|
|
1204
1196
|
args: ['code', '--facts', 'grounded fact'],
|
|
1205
1197
|
env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP), AGY_HARD_TIMEOUT: '600s', AGY_TIMEOUT: '600s', AGY_FAKE_SLEEP: '1' },
|
|
1206
1198
|
});
|
|
@@ -1223,10 +1215,10 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1223
1215
|
|
|
1224
1216
|
// council R2-M2: shrinking a turn to 1s does not save the cap — a TERM-ignoring process still runs
|
|
1225
1217
|
// for the SIGKILL grace on top. Too little budget left is a REFUSAL, never a tiny turn.
|
|
1226
|
-
it('a cap smaller than the SIGKILL grace refuses BEFORE spending a single turn', () => {
|
|
1218
|
+
it('a cap smaller than the SIGKILL grace refuses BEFORE spending a single turn', async () => {
|
|
1227
1219
|
const sb = makeSandbox();
|
|
1228
1220
|
seedFedChangeSet(sb, { lines: 150 });
|
|
1229
|
-
const fed = run(sb, {
|
|
1221
|
+
const fed = await run(sb, {
|
|
1230
1222
|
args: ['code', '--facts', 'grounded fact'],
|
|
1231
1223
|
env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP), AGY_HARD_TIMEOUT: '5s', AGY_TIMEOUT: '5s' },
|
|
1232
1224
|
});
|
|
@@ -1238,10 +1230,10 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1238
1230
|
assert.equal(receipts.length, 0, 'a refused review mints NO receipt');
|
|
1239
1231
|
});
|
|
1240
1232
|
|
|
1241
|
-
it('the fed lane announces part and turn counts before the first dispatch (D5 quota honesty)', () => {
|
|
1233
|
+
it('the fed lane announces part and turn counts before the first dispatch (D5 quota honesty)', async () => {
|
|
1242
1234
|
const sb = makeSandbox();
|
|
1243
1235
|
seedFedChangeSet(sb);
|
|
1244
|
-
const fed = fedRun(sb);
|
|
1236
|
+
const fed = await fedRun(sb);
|
|
1245
1237
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1246
1238
|
const announce = fed.stderr.match(/feeding the change set in (\d+) part\(s\) over (\d+) subscription turns/);
|
|
1247
1239
|
assert.ok(announce, `the cost must be stated before it is spent: ${fed.stderr}`);
|
|
@@ -1252,10 +1244,10 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1252
1244
|
// The ceiling is a SPENDING guard, so a value the operator sets and the wrapper cannot honour must
|
|
1253
1245
|
// never be silently ignored. `008000` used to make bash evaluate an invalid octal constant: both
|
|
1254
1246
|
// range tests errored to false and the ceiling simply stopped existing.
|
|
1255
|
-
it('a leading-zero ceiling is canonicalized, not read as octal — and it still refuses', () => {
|
|
1247
|
+
it('a leading-zero ceiling is canonicalized, not read as octal — and it still refuses', async () => {
|
|
1256
1248
|
const sb = makeSandbox();
|
|
1257
1249
|
seedFedChangeSet(sb);
|
|
1258
|
-
const fed = fedRun(sb, { AGY_REVIEW_MAX_TOTAL_BYTES: '008000' });
|
|
1250
|
+
const fed = await fedRun(sb, { AGY_REVIEW_MAX_TOTAL_BYTES: '008000' });
|
|
1259
1251
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1260
1252
|
assert.equal(fed.status, 2, fed.stderr);
|
|
1261
1253
|
assert.equal(fed.invoked, false, 'the ceiling really bound — not one turn was spent');
|
|
@@ -1263,11 +1255,11 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1263
1255
|
assert.doesNotMatch(fed.stderr, /value too great for base|invalid arithmetic/, 'no octal diagnostic anywhere');
|
|
1264
1256
|
});
|
|
1265
1257
|
|
|
1266
|
-
it('an explicit env ceiling the wrapper cannot honour REFUSES, never silently defaults', () => {
|
|
1258
|
+
it('an explicit env ceiling the wrapper cannot honour REFUSES, never silently defaults', async () => {
|
|
1267
1259
|
const sb = makeSandbox();
|
|
1268
1260
|
seedFedChangeSet(sb);
|
|
1269
1261
|
for (const bad of ['999999999999999999999', '200000000', 'lots']) {
|
|
1270
|
-
const fed = fedRun(sb, { AGY_REVIEW_MAX_TOTAL_BYTES: bad });
|
|
1262
|
+
const fed = await fedRun(sb, { AGY_REVIEW_MAX_TOTAL_BYTES: bad });
|
|
1271
1263
|
assert.equal(fed.status, 2, `${bad}: ${fed.stderr}`);
|
|
1272
1264
|
assert.equal(fed.invoked, false, `${bad}: no run is spent under an unhonoured ceiling`);
|
|
1273
1265
|
assert.match(fed.stderr, /not a valid byte ceiling/, `${bad}: the refusal names the cause`);
|
|
@@ -1275,20 +1267,20 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1275
1267
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1276
1268
|
});
|
|
1277
1269
|
|
|
1278
|
-
it('the DEFAULT ceiling still lets an ordinary fed review through', () => {
|
|
1270
|
+
it('the DEFAULT ceiling still lets an ordinary fed review through', async () => {
|
|
1279
1271
|
const sb = makeSandbox();
|
|
1280
1272
|
seedFedChangeSet(sb);
|
|
1281
|
-
const fed = fedRun(sb, { AGY_REVIEW_MAX_TOTAL_BYTES: '240000' });
|
|
1273
|
+
const fed = await fedRun(sb, { AGY_REVIEW_MAX_TOTAL_BYTES: '240000' });
|
|
1282
1274
|
const receipts = readReceipts(sb.repo);
|
|
1283
1275
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1284
1276
|
assert.equal(fed.status, 0, fed.stderr);
|
|
1285
1277
|
assert.equal(receipts[0].delivery, 'fed');
|
|
1286
1278
|
});
|
|
1287
1279
|
|
|
1288
|
-
it('a change set whose total outgoing prompt bytes exceed AGY_REVIEW_MAX_TOTAL_BYTES refuses before the first turn is spent', () => {
|
|
1280
|
+
it('a change set whose total outgoing prompt bytes exceed AGY_REVIEW_MAX_TOTAL_BYTES refuses before the first turn is spent', async () => {
|
|
1289
1281
|
const sb = makeSandbox();
|
|
1290
1282
|
seedFedChangeSet(sb);
|
|
1291
|
-
const fed = fedRun(sb, { AGY_REVIEW_MAX_TOTAL_BYTES: '9000' });
|
|
1283
|
+
const fed = await fedRun(sb, { AGY_REVIEW_MAX_TOTAL_BYTES: '9000' });
|
|
1292
1284
|
const receipts = readReceipts(sb.repo);
|
|
1293
1285
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1294
1286
|
assert.equal(fed.status, 2, fed.stderr);
|
|
@@ -1297,10 +1289,10 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1297
1289
|
assert.equal(receipts.length, 0);
|
|
1298
1290
|
});
|
|
1299
1291
|
|
|
1300
|
-
it('a fixed overhead that cannot fit refuses rather than emitting an empty body', () => {
|
|
1292
|
+
it('a fixed overhead that cannot fit refuses rather than emitting an empty body', async () => {
|
|
1301
1293
|
const sb = makeSandbox();
|
|
1302
1294
|
seedFedChangeSet(sb);
|
|
1303
|
-
const fed = fedRun(sb, { AGY_MAX_PROMPT_BYTES: '900' });
|
|
1295
|
+
const fed = await fedRun(sb, { AGY_MAX_PROMPT_BYTES: '900' });
|
|
1304
1296
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1305
1297
|
assert.equal(fed.status, 2, fed.stderr);
|
|
1306
1298
|
assert.equal(fed.invoked, false);
|
|
@@ -1311,20 +1303,20 @@ describe('agy-review.sh — chunked feed: the change set is DELIVERED (Phase 3)'
|
|
|
1311
1303
|
// 4.3: agy's own denial names the permission rule it wants. The kit SURFACES that fact and never
|
|
1312
1304
|
// applies it — granting read_file would widen a boundary for ALL agy use on the machine to re-arm
|
|
1313
1305
|
// the one lane whose failure mode is undetectable by construction.
|
|
1314
|
-
describe('agy-review.sh — the agy permission fact is surfaced, never applied', () => {
|
|
1306
|
+
describe('agy-review.sh — the agy permission fact is surfaced, never applied', { concurrency: 2 }, () => {
|
|
1315
1307
|
const BRIDGE_ROOT = resolve(HERE, '..');
|
|
1316
1308
|
|
|
1317
|
-
it('the over-cap path states why the change set is delivered rather than read', () => {
|
|
1309
|
+
it('the over-cap path states why the change set is delivered rather than read', async () => {
|
|
1318
1310
|
const sb = makeSandbox();
|
|
1319
1311
|
seedFedChangeSet(sb);
|
|
1320
|
-
const fed = fedRun(sb);
|
|
1312
|
+
const fed = await fedRun(sb);
|
|
1321
1313
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1322
1314
|
assert.equal(fed.status, 0, fed.stderr);
|
|
1323
1315
|
assert.match(fed.stderr, /read_file/, 'the notice names the denied tool');
|
|
1324
1316
|
assert.match(fed.stderr, /never (grants|writes)/, 'and states that the kit does not grant it');
|
|
1325
1317
|
});
|
|
1326
1318
|
|
|
1327
|
-
it('the bridge docs state the never-applied posture (doc contract)', () => {
|
|
1319
|
+
it('the bridge docs state the never-applied posture (doc contract)', async () => {
|
|
1328
1320
|
const prompt = readFileSync(join(BRIDGE_ROOT, 'references', 'review-prompt.md'), 'utf8');
|
|
1329
1321
|
assert.match(prompt, /read_file/, 'the denial is named');
|
|
1330
1322
|
assert.match(prompt, /never writes it|never applied/i, 'the never-applied posture is stated');
|
|
@@ -1332,7 +1324,7 @@ describe('agy-review.sh — the agy permission fact is surfaced, never applied',
|
|
|
1332
1324
|
assert.doesNotMatch(prompt, /grant (the )?read_file permission to (fix|enable)/i, 'the docs never RECOMMEND granting it');
|
|
1333
1325
|
});
|
|
1334
1326
|
|
|
1335
|
-
it('no wrapper or doc surface ever writes an agy permission rule', () => {
|
|
1327
|
+
it('no wrapper or doc surface ever writes an agy permission rule', async () => {
|
|
1336
1328
|
for (const rel of [join('bin', 'agy-review.sh'), join('bin', 'agy.sh')]) {
|
|
1337
1329
|
const text = readFileSync(join(BRIDGE_ROOT, rel), 'utf8');
|
|
1338
1330
|
assert.doesNotMatch(text, /--dangerously-skip-permissions/, `${rel} must never pass the blanket-permission flag`);
|
|
@@ -1345,11 +1337,11 @@ describe('agy-review.sh — the agy permission fact is surfaced, never applied',
|
|
|
1345
1337
|
// to `--continue` when it changed. It is now a NAMED envelope field the reader validates against the
|
|
1346
1338
|
// UUID grammar, so the pin that used to rot SILENTLY now fails LOUDLY: there is no `--continue`
|
|
1347
1339
|
// fallback left in this lane and no later turn is spent when turn 1 cannot name its conversation.
|
|
1348
|
-
describe('agy-review.sh — fed lane: turn targeting (D9)', () => {
|
|
1349
|
-
it('every turn after the first is ROUTED at the id turn 1`s envelope named', () => {
|
|
1340
|
+
describe('agy-review.sh — fed lane: turn targeting (D9)', { concurrency: 2 }, () => {
|
|
1341
|
+
it('every turn after the first is ROUTED at the id turn 1`s envelope named', async () => {
|
|
1350
1342
|
const sb = makeSandbox();
|
|
1351
1343
|
seedFedChangeSet(sb);
|
|
1352
|
-
const fed = fedRun(sb, { AGY_FAKE_CONV_ID: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' });
|
|
1344
|
+
const fed = await fedRun(sb, { AGY_FAKE_CONV_ID: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' });
|
|
1353
1345
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1354
1346
|
assert.equal(fed.status, 0, fed.stderr);
|
|
1355
1347
|
assert.ok(!fed.argvs[0].includes('--conversation'), 'turn 1 is fresh');
|
|
@@ -1370,10 +1362,10 @@ describe('agy-review.sh — fed lane: turn targeting (D9)', () => {
|
|
|
1370
1362
|
['not a string', { AGY_FAKE_CONV_SHAPE: 'number' }],
|
|
1371
1363
|
['failing the UUID grammar', { AGY_FAKE_CONV_ID: 'not-a-conversation-id' }],
|
|
1372
1364
|
]) {
|
|
1373
|
-
it(`a turn-1 conversation id ${name} fails LOUDLY before turn 2, with NO receipt`, () => {
|
|
1365
|
+
it(`a turn-1 conversation id ${name} fails LOUDLY before turn 2, with NO receipt`, async () => {
|
|
1374
1366
|
const sb = makeSandbox();
|
|
1375
1367
|
seedFedChangeSet(sb);
|
|
1376
|
-
const fed = fedRun(sb, env);
|
|
1368
|
+
const fed = await fedRun(sb, env);
|
|
1377
1369
|
const receipts = readReceipts(sb.repo);
|
|
1378
1370
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1379
1371
|
assert.equal(fed.status, 5, `an unusable envelope has the transport code: ${fed.stderr}`);
|
|
@@ -1390,10 +1382,10 @@ describe('agy-review.sh — fed lane: turn targeting (D9)', () => {
|
|
|
1390
1382
|
['whose payload is not an envelope', { AGY_FAKE_BAD_TURN: '2', AGY_FAKE_RAW_STDOUT: 'jetski: not an envelope\n' }, /agy-envelope: not-json/],
|
|
1391
1383
|
['reporting a non-SUCCESS status', { AGY_FAKE_BAD_TURN: '2', AGY_FAKE_STATUS: 'ERROR' }, /agy-envelope: status/],
|
|
1392
1384
|
]) {
|
|
1393
|
-
it(`a fed turn 2 ${name} stops the run: NO receipt, no turn 3`, () => {
|
|
1385
|
+
it(`a fed turn 2 ${name} stops the run: NO receipt, no turn 3`, async () => {
|
|
1394
1386
|
const sb = makeSandbox();
|
|
1395
1387
|
seedFedChangeSet(sb);
|
|
1396
|
-
const fed = fedRun(sb, env);
|
|
1388
|
+
const fed = await fedRun(sb, env);
|
|
1397
1389
|
const receipts = readReceipts(sb.repo);
|
|
1398
1390
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1399
1391
|
assert.equal(fed.status, 5, fed.stderr);
|
|
@@ -1406,7 +1398,7 @@ describe('agy-review.sh — fed lane: turn targeting (D9)', () => {
|
|
|
1406
1398
|
|
|
1407
1399
|
// The doc must not describe BOTH routings at once: a residual recording the retired log scrape
|
|
1408
1400
|
// beside the envelope statement leaves an operator unable to tell which lane their build runs.
|
|
1409
|
-
it('the fed-lane doc states envelope routing and keeps no log-scrape residual (doc contract)', () => {
|
|
1401
|
+
it('the fed-lane doc states envelope routing and keeps no log-scrape residual (doc contract)', async () => {
|
|
1410
1402
|
const prompt = readFileSync(resolve(HERE, '..', 'references', 'review-prompt.md'), 'utf8');
|
|
1411
1403
|
assert.match(prompt, /Later turns are routed by a NAMED field/, 'the live routing is stated');
|
|
1412
1404
|
assert.doesNotMatch(prompt, /conversation id is parsed from/i, 'the retired scrape is no longer described as live');
|
|
@@ -1414,11 +1406,11 @@ describe('agy-review.sh — fed lane: turn targeting (D9)', () => {
|
|
|
1414
1406
|
});
|
|
1415
1407
|
});
|
|
1416
1408
|
|
|
1417
|
-
describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1, D7)', () => {
|
|
1418
|
-
it('a fed review reproducing every selected line writes a fresh code receipt at the tree fingerprint', () => {
|
|
1409
|
+
describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1, D7)', { concurrency: 2 }, () => {
|
|
1410
|
+
it('a fed review reproducing every selected line writes a fresh code receipt at the tree fingerprint', async () => {
|
|
1419
1411
|
const sb = makeSandbox();
|
|
1420
1412
|
seedFedChangeSet(sb);
|
|
1421
|
-
const fed = fedRun(sb);
|
|
1413
|
+
const fed = await fedRun(sb);
|
|
1422
1414
|
const receipts = readReceipts(sb.repo);
|
|
1423
1415
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1424
1416
|
assert.equal(fed.status, 0, fed.stderr);
|
|
@@ -1429,10 +1421,10 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1429
1421
|
assert.equal(receipts[0].delivery, 'fed', 'the receipt declares HOW delivery was established');
|
|
1430
1422
|
});
|
|
1431
1423
|
|
|
1432
|
-
it('a fed review whose output omits a part`s echo exits 4 and writes NO receipt', () => {
|
|
1424
|
+
it('a fed review whose output omits a part`s echo exits 4 and writes NO receipt', async () => {
|
|
1433
1425
|
const sb = makeSandbox();
|
|
1434
1426
|
seedFedChangeSet(sb);
|
|
1435
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_OMIT: '2' });
|
|
1427
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_OMIT: '2' });
|
|
1436
1428
|
const receipts = readReceipts(sb.repo);
|
|
1437
1429
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1438
1430
|
assert.equal(fed.status, 4, fed.stderr);
|
|
@@ -1441,10 +1433,10 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1441
1433
|
assert.equal(receipts.length, 0);
|
|
1442
1434
|
});
|
|
1443
1435
|
|
|
1444
|
-
it('a fed review whose echo differs from the recorded line exits 4 and writes NO receipt', () => {
|
|
1436
|
+
it('a fed review whose echo differs from the recorded line exits 4 and writes NO receipt', async () => {
|
|
1445
1437
|
const sb = makeSandbox();
|
|
1446
1438
|
seedFedChangeSet(sb);
|
|
1447
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_CORRUPT: '2' });
|
|
1439
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_CORRUPT: '2' });
|
|
1448
1440
|
const receipts = readReceipts(sb.repo);
|
|
1449
1441
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1450
1442
|
assert.equal(fed.status, 4, fed.stderr);
|
|
@@ -1452,10 +1444,10 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1452
1444
|
assert.equal(receipts.length, 0);
|
|
1453
1445
|
});
|
|
1454
1446
|
|
|
1455
|
-
it('a fed review echoing one part`s line for two parts exits 4 and writes NO receipt', () => {
|
|
1447
|
+
it('a fed review echoing one part`s line for two parts exits 4 and writes NO receipt', async () => {
|
|
1456
1448
|
const sb = makeSandbox();
|
|
1457
1449
|
seedFedChangeSet(sb);
|
|
1458
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_DUP: '1' });
|
|
1450
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_DUP: '1' });
|
|
1459
1451
|
const receipts = readReceipts(sb.repo);
|
|
1460
1452
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1461
1453
|
assert.equal(fed.status, 4, fed.stderr);
|
|
@@ -1464,10 +1456,10 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1464
1456
|
|
|
1465
1457
|
// The proof GRAMMAR, pinned red→green (Test-as-spec). A substring search over the whole answer
|
|
1466
1458
|
// accepted every shape below except the bullet — which is the one shape that should pass.
|
|
1467
|
-
it('an echo placed OUTSIDE the proof section does not count', () => {
|
|
1459
|
+
it('an echo placed OUTSIDE the proof section does not count', async () => {
|
|
1468
1460
|
const sb = makeSandbox();
|
|
1469
1461
|
seedFedChangeSet(sb);
|
|
1470
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_OUTSIDE: '1' });
|
|
1462
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_OUTSIDE: '1' });
|
|
1471
1463
|
const receipts = readReceipts(sb.repo);
|
|
1472
1464
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1473
1465
|
assert.equal(fed.status, 4, fed.stderr);
|
|
@@ -1477,20 +1469,20 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1477
1469
|
// The proof comes FIRST so output truncation can never silently drop it. A block that arrives
|
|
1478
1470
|
// after a verdict is not that shape, so it is not searched for — otherwise the "first" in the
|
|
1479
1471
|
// contract would be decoration.
|
|
1480
|
-
it('a proof block placed AFTER the verdict does not count — the proof must be the first section', () => {
|
|
1472
|
+
it('a proof block placed AFTER the verdict does not count — the proof must be the first section', async () => {
|
|
1481
1473
|
const sb = makeSandbox();
|
|
1482
1474
|
seedFedChangeSet(sb);
|
|
1483
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_LATE: '1' });
|
|
1475
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_LATE: '1' });
|
|
1484
1476
|
const receipts = readReceipts(sb.repo);
|
|
1485
1477
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1486
1478
|
assert.equal(fed.status, 4, fed.stderr);
|
|
1487
1479
|
assert.equal(receipts.length, 0);
|
|
1488
1480
|
});
|
|
1489
1481
|
|
|
1490
|
-
it('an address echoed TWICE fails — one address, one line', () => {
|
|
1482
|
+
it('an address echoed TWICE fails — one address, one line', async () => {
|
|
1491
1483
|
const sb = makeSandbox();
|
|
1492
1484
|
seedFedChangeSet(sb);
|
|
1493
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_TWICE: '1' });
|
|
1485
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_TWICE: '1' });
|
|
1494
1486
|
const receipts = readReceipts(sb.repo);
|
|
1495
1487
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1496
1488
|
assert.equal(fed.status, 4, fed.stderr);
|
|
@@ -1498,10 +1490,10 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1498
1490
|
assert.equal(receipts.length, 0);
|
|
1499
1491
|
});
|
|
1500
1492
|
|
|
1501
|
-
it('an UNREQUESTED address in the proof section fails', () => {
|
|
1493
|
+
it('an UNREQUESTED address in the proof section fails', async () => {
|
|
1502
1494
|
const sb = makeSandbox();
|
|
1503
1495
|
seedFedChangeSet(sb);
|
|
1504
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_EXTRA: '1' });
|
|
1496
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_EXTRA: '1' });
|
|
1505
1497
|
const receipts = readReceipts(sb.repo);
|
|
1506
1498
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1507
1499
|
assert.equal(fed.status, 4, fed.stderr);
|
|
@@ -1509,10 +1501,10 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1509
1501
|
assert.equal(receipts.length, 0);
|
|
1510
1502
|
});
|
|
1511
1503
|
|
|
1512
|
-
it('a marker BURIED inside a sentence is not an echo (the anchor is real)', () => {
|
|
1504
|
+
it('a marker BURIED inside a sentence is not an echo (the anchor is real)', async () => {
|
|
1513
1505
|
const sb = makeSandbox();
|
|
1514
1506
|
seedFedChangeSet(sb);
|
|
1515
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_NESTED: '2' });
|
|
1507
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_NESTED: '2' });
|
|
1516
1508
|
const receipts = readReceipts(sb.repo);
|
|
1517
1509
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1518
1510
|
assert.equal(fed.status, 4, fed.stderr);
|
|
@@ -1523,10 +1515,10 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1523
1515
|
// and array indexing as `08`, which bash reads as OCTAL — the wrapper crashed with `value too
|
|
1524
1516
|
// great for base` instead of the contracted clean refusal, and a safely padded `01` also mismatched
|
|
1525
1517
|
// the unpadded `1`. Both die at the PARSE boundary now: awk hands bash plain decimals.
|
|
1526
|
-
it('a zero-padded proof address is normalized, never an octal crash and never a false refusal', () => {
|
|
1518
|
+
it('a zero-padded proof address is normalized, never an octal crash and never a false refusal', async () => {
|
|
1527
1519
|
const sb = makeSandbox();
|
|
1528
1520
|
seedFedChangeSet(sb);
|
|
1529
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_PAD: '1' });
|
|
1521
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_PAD: '1' });
|
|
1530
1522
|
const receipts = readReceipts(sb.repo);
|
|
1531
1523
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1532
1524
|
assert.equal(fed.status, 0, `a padded address must pass, not crash: ${fed.stderr}`);
|
|
@@ -1535,20 +1527,20 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1535
1527
|
assert.equal(receipts[0].delivery, 'fed');
|
|
1536
1528
|
});
|
|
1537
1529
|
|
|
1538
|
-
it('a capitalized heading and anchor still count — and the payload keeps its own case', () => {
|
|
1530
|
+
it('a capitalized heading and anchor still count — and the payload keeps its own case', async () => {
|
|
1539
1531
|
const sb = makeSandbox();
|
|
1540
1532
|
seedFedChangeSet(sb);
|
|
1541
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_CASE: '1' });
|
|
1533
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_CASE: '1' });
|
|
1542
1534
|
const receipts = readReceipts(sb.repo);
|
|
1543
1535
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1544
1536
|
assert.equal(fed.status, 0, `a capitalization must not fail a real delivery: ${fed.stderr}`);
|
|
1545
1537
|
assert.equal(receipts.length, 1, 'the review attests');
|
|
1546
1538
|
});
|
|
1547
1539
|
|
|
1548
|
-
it('an absurd proof address never reaches bash arithmetic — no crash, no impersonated address', () => {
|
|
1540
|
+
it('an absurd proof address never reaches bash arithmetic — no crash, no impersonated address', async () => {
|
|
1549
1541
|
const sb = makeSandbox();
|
|
1550
1542
|
seedFedChangeSet(sb);
|
|
1551
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_HUGE: '2' });
|
|
1543
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_HUGE: '2' });
|
|
1552
1544
|
const receipts = readReceipts(sb.repo);
|
|
1553
1545
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1554
1546
|
assert.equal(fed.status, 4, fed.stderr);
|
|
@@ -1558,10 +1550,10 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1558
1550
|
|
|
1559
1551
|
// Dropping an out-of-range address made it INVISIBLE: an answer with every correct echo plus one
|
|
1560
1552
|
// invented giant address then satisfied a grammar whose whole point is that it is closed.
|
|
1561
|
-
it('a VALID proof carrying one extra out-of-range address still fails — an invented address is never invisible', () => {
|
|
1553
|
+
it('a VALID proof carrying one extra out-of-range address still fails — an invented address is never invisible', async () => {
|
|
1562
1554
|
const sb = makeSandbox();
|
|
1563
1555
|
seedFedChangeSet(sb);
|
|
1564
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_HUGE_EXTRA: '1' });
|
|
1556
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_HUGE_EXTRA: '1' });
|
|
1565
1557
|
const receipts = readReceipts(sb.repo);
|
|
1566
1558
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1567
1559
|
assert.equal(fed.status, 4, fed.stderr);
|
|
@@ -1569,18 +1561,157 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1569
1561
|
assert.equal(receipts.length, 0, 'a closed grammar does not mint a receipt beside an invented address');
|
|
1570
1562
|
});
|
|
1571
1563
|
|
|
1572
|
-
//
|
|
1573
|
-
//
|
|
1574
|
-
|
|
1564
|
+
// A model told to count lines BY READING cannot reach line 1000, and a proof it cannot answer is a
|
|
1565
|
+
// false refusal on a correctly delivered change set — the failure that made this lane unusable for
|
|
1566
|
+
// exactly the payloads it exists for. The address must therefore stay near the part's start. The
|
|
1567
|
+
// banner guard is the other half: without it part 1 resolves to `=== repo file map (git ls-files)
|
|
1568
|
+
// ===`, which the wrapper emits for EVERY change set, so echoing it would prove nothing.
|
|
1569
|
+
it('proof addresses stay countable on a multi-part change set', async () => {
|
|
1570
|
+
const sb = makeSandbox();
|
|
1571
|
+
const body = Array.from({ length: 2400 }, (_, i) => `unique body marker line ${String(i).padStart(4, '0')}`);
|
|
1572
|
+
writeFileSync(join(sb.repo, 'oversized.txt'), `${body.join('\n')}\n`);
|
|
1573
|
+
const fed = await fedRun(sb, { AGY_MAX_PROMPT_BYTES: String(FED_WIDE_CAP) });
|
|
1574
|
+
const receipts = readReceipts(sb.repo);
|
|
1575
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1576
|
+
assert.equal(fed.status, 0, fed.stderr);
|
|
1577
|
+
assert.equal(receipts.length, 1, 'a countable proof still mints its receipt');
|
|
1578
|
+
assert.equal(receipts[0].delivery, 'fed');
|
|
1579
|
+
const bodies = fed.prompts.slice(0, -1).map(bodyOf);
|
|
1580
|
+
assert.ok(bodies.length >= 3, `the fixture must really chunk (got ${bodies.length} parts)`);
|
|
1581
|
+
for (const { part, line } of requestedOf(fed.prompts[fed.prompts.length - 1])) {
|
|
1582
|
+
assert.ok(line <= MAX_COUNTABLE_PROOF_ADDRESS,
|
|
1583
|
+
`part ${part} asks for line ${line} — a model counting by reading cannot reach it`);
|
|
1584
|
+
const chosen = bodies[part - 1].split('\n')[line - 1].trim();
|
|
1585
|
+
assert.ok(!isAssemblerBanner(chosen),
|
|
1586
|
+
`part ${part} proves delivery with a line the wrapper emits for every change set: ${chosen}`);
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1590
|
+
// The reject is the assembler's VOCABULARY, not the `=== … ===` shape. Refusing the whole class
|
|
1591
|
+
// discards a change set's own banner-shaped prose, and in a part where nothing else qualifies that
|
|
1592
|
+
// is the false refusal this lane exists to prevent — so an admissible one must still be CHOSEN.
|
|
1593
|
+
// The per-path `=== untracked: <p> ===` is in the reject set for the opposite reason: it is not a
|
|
1594
|
+
// fixed string, and part 1 carries the git-status block a model would rebuild it from.
|
|
1595
|
+
it('a change set may prove delivery with its own banner-shaped line, never with the assembler\'s', async () => {
|
|
1596
|
+
const sb = makeSandbox();
|
|
1597
|
+
const admissible = '=== Deployment configuration notes ===';
|
|
1598
|
+
const body = [admissible, ...Array.from({ length: 900 }, (_, i) => `unique body marker line ${String(i).padStart(4, '0')}`)];
|
|
1599
|
+
writeFileSync(join(sb.repo, 'oversized.txt'), `${body.join('\n')}\n`);
|
|
1600
|
+
const fed = await fedRun(sb);
|
|
1601
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1602
|
+
assert.equal(fed.status, 0, `an admissible banner-shaped line must not earn a refusal: ${fed.stderr}`);
|
|
1603
|
+
const bodies = fed.prompts.slice(0, -1).map(bodyOf);
|
|
1604
|
+
const requested = requestedOf(fed.prompts[fed.prompts.length - 1]);
|
|
1605
|
+
const part1 = requested.find((r) => r.part === 1);
|
|
1606
|
+
assert.equal(bodies[0].split('\n')[part1.line - 1].trim(), admissible,
|
|
1607
|
+
'the change set\'s own banner-shaped line is the first admissible candidate and must be chosen');
|
|
1608
|
+
for (const { part, line } of requested) {
|
|
1609
|
+
assert.ok(!isAssemblerBanner(bodies[part - 1].split('\n')[line - 1].trim()),
|
|
1610
|
+
`part ${part} proves delivery with an assembler banner`);
|
|
1611
|
+
}
|
|
1612
|
+
});
|
|
1613
|
+
|
|
1614
|
+
// A head-first walk lands where the DERIVABLE lines live. The repo map and the status block travel
|
|
1615
|
+
// in part 1, so a model holding part 1 can reproduce a tracked path, its `diff --git a/P b/P`
|
|
1616
|
+
// header and the `--- a/P` / `+++ b/P` pair for a LATER part it never received — and prove a
|
|
1617
|
+
// delivery that never happened. Enumerating those families as a blacklist was the wrong shape; the
|
|
1618
|
+
// wrapper feeds what it can COMPUTE into $nonbody and lets the existing filter reject it.
|
|
1619
|
+
it('no proof line is one a model could derive from the repo map or status block', async () => {
|
|
1620
|
+
const sb = makeSandbox();
|
|
1621
|
+
// Long tracked paths, so the map/header lines clear the 24-byte candidate floor and really compete.
|
|
1622
|
+
const tracked = Array.from({ length: 12 }, (_, i) => `src/deeply/nested/module-directory-${String(i).padStart(3, '0')}.mjs`);
|
|
1623
|
+
mkdirSync(join(sb.repo, 'src', 'deeply', 'nested'), { recursive: true });
|
|
1624
|
+
for (const rel of tracked) writeFileSync(join(sb.repo, rel), `export const seed = ${JSON.stringify(rel)};\n`);
|
|
1625
|
+
sb.g('add', '-A');
|
|
1626
|
+
sb.g('commit', '-qm', 'tracked seed');
|
|
1627
|
+
for (const [i, rel] of tracked.entries()) {
|
|
1628
|
+
const extra = Array.from({ length: 60 }, (_, j) => `changed body line ${i}-${String(j).padStart(3, '0')} in ${rel}`);
|
|
1629
|
+
writeFileSync(join(sb.repo, rel), `export const seed = ${JSON.stringify(rel)};\n${extra.join('\n')}\n`);
|
|
1630
|
+
}
|
|
1631
|
+
const fed = await fedRun(sb);
|
|
1632
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1633
|
+
assert.equal(fed.status, 0, fed.stderr);
|
|
1634
|
+
const bodies = fed.prompts.slice(0, -1).map(bodyOf);
|
|
1635
|
+
assert.ok(bodies.length >= 2, `the fixture must really chunk (got ${bodies.length} parts)`);
|
|
1636
|
+
const derivable = new Set(tracked.flatMap((p) => [p, `diff --git a/${p} b/${p}`, `--- a/${p}`, `+++ b/${p}`, `?? ${p}`, `M ${p}`]));
|
|
1637
|
+
for (const { part, line } of requestedOf(fed.prompts[fed.prompts.length - 1])) {
|
|
1638
|
+
const chosen = bodies[part - 1].split('\n')[line - 1].trim();
|
|
1639
|
+
assert.ok(!derivable.has(chosen),
|
|
1640
|
+
`part ${part} proves delivery with a line part 1 already reveals: ${chosen}`);
|
|
1641
|
+
assert.ok(!isAssemblerBanner(chosen), `part ${part} proves delivery with an assembler banner: ${chosen}`);
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1645
|
+
// A RENAME is the case no re-derivation could have covered: git writes `diff --git a/old b/new`
|
|
1646
|
+
// plus a `rename from`/`rename to` pair, none of which follows from the new path alone — and the
|
|
1647
|
+
// status block spells the rename out, so all of it is derivable. Slicing the assembled artifact
|
|
1648
|
+
// catches them because it never has to guess what git wrote.
|
|
1649
|
+
it('a renamed path leaks no proof line — the derivable set comes from the artifact, not a guess', async () => {
|
|
1650
|
+
const sb = makeSandbox();
|
|
1651
|
+
const from = 'src/original/long-enough-module-name-before-rename.mjs';
|
|
1652
|
+
const to = 'src/relocated/long-enough-module-name-after-rename.mjs';
|
|
1653
|
+
mkdirSync(join(sb.repo, 'src', 'original'), { recursive: true });
|
|
1654
|
+
mkdirSync(join(sb.repo, 'src', 'relocated'), { recursive: true });
|
|
1655
|
+
const seed = Array.from({ length: 400 }, (_, i) => `stable renamed body line ${String(i).padStart(4, '0')} carrying enough bytes`);
|
|
1656
|
+
writeFileSync(join(sb.repo, from), `${seed.join('\n')}\n`);
|
|
1657
|
+
sb.g('add', '-A');
|
|
1658
|
+
sb.g('commit', '-qm', 'seed for rename');
|
|
1659
|
+
sb.g('mv', from, to);
|
|
1660
|
+
writeFileSync(join(sb.repo, to), `${seed.join('\n')}\nappended line so the rename also carries a diff\n`);
|
|
1661
|
+
sb.g('add', '-A');
|
|
1662
|
+
// Rename detection collapses the diff to a few header lines, so the change set needs real bulk
|
|
1663
|
+
// elsewhere or the review never leaves the inline lane and there is no proof to inspect.
|
|
1664
|
+
seedFedChangeSet(sb);
|
|
1665
|
+
const fed = await fedRun(sb);
|
|
1666
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1667
|
+
assert.equal(fed.status, 0, fed.stderr);
|
|
1668
|
+
const bodies = fed.prompts.slice(0, -1).map(bodyOf);
|
|
1669
|
+
const renameLines = new Set([
|
|
1670
|
+
`diff --git a/${from} b/${to}`, `rename from ${from}`, `rename to ${to}`,
|
|
1671
|
+
`--- a/${from}`, `+++ b/${to}`, from, to, `R ${from} -> ${to}`,
|
|
1672
|
+
]);
|
|
1673
|
+
for (const { part, line } of requestedOf(fed.prompts[fed.prompts.length - 1])) {
|
|
1674
|
+
const chosen = bodies[part - 1].split('\n')[line - 1].trim();
|
|
1675
|
+
assert.ok(!renameLines.has(chosen), `part ${part} proves delivery with a rename header: ${chosen}`);
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
|
|
1679
|
+
// The derivable-set scan reads the artifact by section, and an untracked file's own content can
|
|
1680
|
+
// contain anything — including a line that looks exactly like a section banner. If that flipped the
|
|
1681
|
+
// scan back into metadata mode, every line after it would be swept into the set, starving the parts
|
|
1682
|
+
// that follow of candidates and refusing a change set that is perfectly provable.
|
|
1683
|
+
it('a banner-shaped line inside untracked CONTENT does not sweep the rest of the change set', async () => {
|
|
1684
|
+
const sb = makeSandbox();
|
|
1685
|
+
const body = Array.from({ length: 900 }, (_, i) => `unique body marker line ${String(i).padStart(4, '0')}`);
|
|
1686
|
+
body.splice(1, 0, '=== git status (porcelain) ===');
|
|
1687
|
+
writeFileSync(join(sb.repo, 'oversized.txt'), `${body.join('\n')}\n`);
|
|
1688
|
+
const fed = await fedRun(sb);
|
|
1689
|
+
const receipts = readReceipts(sb.repo);
|
|
1690
|
+
rmSync(sb.home, { recursive: true, force: true });
|
|
1691
|
+
assert.equal(fed.status, 0, `content that merely LOOKS like a banner must not starve the walk: ${fed.stderr}`);
|
|
1692
|
+
assert.equal(receipts.length, 1, 'the review still mints its receipt');
|
|
1693
|
+
const bodies = fed.prompts.slice(0, -1).map(bodyOf);
|
|
1694
|
+
for (const { part, line } of requestedOf(fed.prompts[fed.prompts.length - 1])) {
|
|
1695
|
+
const chosen = bodies[part - 1].split('\n')[line - 1].trim();
|
|
1696
|
+
assert.match(chosen, /^unique body marker line \d{4}$/,
|
|
1697
|
+
`part ${part} settled on ${chosen} instead of ordinary untracked content`);
|
|
1698
|
+
}
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
// The candidate cap was 25, so a change set whose first 25 candidates all fail the fixed-string
|
|
1702
|
+
// checks earned a false "no usable candidate" refusal while candidate 26 was fine. The decoys sit
|
|
1703
|
+
// FIRST in the fixture because the walk starts at the part's head — buried mid-file they would
|
|
1704
|
+
// never be reached, and this regression would pass while proving nothing.
|
|
1705
|
+
it('a part whose first 25 candidates are unusable still finds the one after them', async () => {
|
|
1575
1706
|
const sb = makeSandbox();
|
|
1576
1707
|
// Each decoy is a unique WHOLE line (so it survives the cheap prefilter) that also occurs as a
|
|
1577
1708
|
// SUBSTRING of a longer line — exactly the case the exact occurrence check must reject.
|
|
1578
1709
|
const decoys = Array.from({ length: 30 }, (_, i) => `decoy candidate ${String(i).padStart(3, '0')} — appears twice as a substring`);
|
|
1579
1710
|
const echoes = decoys.map((d) => `carrier line wrapping ${d} inside a longer line`);
|
|
1580
1711
|
const filler = (from, n) => Array.from({ length: n }, (_, i) => `unique change-set line ${String(from + i).padStart(4, '0')} — a distinctive body marker ${'x'.repeat(20)}`);
|
|
1581
|
-
const body = [...
|
|
1712
|
+
const body = [...decoys, ...filler(0, 40), ...echoes, ...filler(40, 100)];
|
|
1582
1713
|
writeFileSync(join(sb.repo, 'oversized.txt'), `${body.join('\n')}\n`);
|
|
1583
|
-
const fed = fedRun(sb);
|
|
1714
|
+
const fed = await fedRun(sb);
|
|
1584
1715
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1585
1716
|
assert.equal(fed.status, 0, `a usable candidate past position 25 must be found: ${fed.stderr}`);
|
|
1586
1717
|
const final = fed.prompts[fed.prompts.length - 1];
|
|
@@ -1589,12 +1720,20 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1589
1720
|
const chosen = bodies[part - 1].split('\n')[line - 1].trim();
|
|
1590
1721
|
assert.ok(!decoys.includes(chosen), `a twice-occurring decoy was chosen for part ${part}: ${chosen}`);
|
|
1591
1722
|
}
|
|
1723
|
+
// Not choosing a decoy is cheap to satisfy by never REACHING one, so the walk must be shown to
|
|
1724
|
+
// have crossed the whole unusable run — that, not the negative, is the no-cap property.
|
|
1725
|
+
const part1 = bodies[0].split('\n');
|
|
1726
|
+
const lastDecoyAt = part1.reduce((at, l, i) => (decoys.includes(l.trim()) ? i + 1 : at), 0);
|
|
1727
|
+
assert.ok(lastDecoyAt > 0, 'the decoy block must land in part 1, or this regression proves nothing');
|
|
1728
|
+
const part1Line = requestedOf(final).find((r) => r.part === 1).line;
|
|
1729
|
+
assert.ok(part1Line > lastDecoyAt,
|
|
1730
|
+
`part 1 settled on line ${part1Line}, before the decoy run ended at ${lastDecoyAt} — never traversed`);
|
|
1592
1731
|
});
|
|
1593
1732
|
|
|
1594
|
-
it('a harmless `- ` bullet still counts — the anchor is strict, not brittle', () => {
|
|
1733
|
+
it('a harmless `- ` bullet still counts — the anchor is strict, not brittle', async () => {
|
|
1595
1734
|
const sb = makeSandbox();
|
|
1596
1735
|
seedFedChangeSet(sb);
|
|
1597
|
-
const fed = fedRun(sb, { AGY_FAKE_PROOF_BULLET: '1' });
|
|
1736
|
+
const fed = await fedRun(sb, { AGY_FAKE_PROOF_BULLET: '1' });
|
|
1598
1737
|
const receipts = readReceipts(sb.repo);
|
|
1599
1738
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1600
1739
|
assert.equal(fed.status, 0, `a bulleted proof must not be a false refusal: ${fed.stderr}`);
|
|
@@ -1602,10 +1741,10 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1602
1741
|
assert.equal(receipts[0].delivery, 'fed');
|
|
1603
1742
|
});
|
|
1604
1743
|
|
|
1605
|
-
it('the selected lines never appear in any envelope the wrapper sends', () => {
|
|
1744
|
+
it('the selected lines never appear in any envelope the wrapper sends', async () => {
|
|
1606
1745
|
const sb = makeSandbox();
|
|
1607
1746
|
seedFedChangeSet(sb);
|
|
1608
|
-
const fed = fedRun(sb);
|
|
1747
|
+
const fed = await fedRun(sb);
|
|
1609
1748
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1610
1749
|
assert.equal(fed.status, 0, fed.stderr);
|
|
1611
1750
|
const final = fed.prompts[fed.prompts.length - 1];
|
|
@@ -1628,7 +1767,7 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1628
1767
|
// anywhere but the body it is being asked to prove — so it must occur exactly once across the
|
|
1629
1768
|
// bodies AND nowhere in what the wrapper itself sends, including the request line that names the
|
|
1630
1769
|
// addresses (which only exists once every address is chosen).
|
|
1631
|
-
it('a change-set line that duplicates the wrapper`s own framing is never chosen as a proof', () => {
|
|
1770
|
+
it('a change-set line that duplicates the wrapper`s own framing is never chosen as a proof', async () => {
|
|
1632
1771
|
const sb = makeSandbox();
|
|
1633
1772
|
// The change set contains lines copied verbatim out of the envelope the wrapper will send.
|
|
1634
1773
|
const framing = [
|
|
@@ -1639,7 +1778,7 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1639
1778
|
const filler = Array.from({ length: 400 }, (_, i) => `unique change-set line ${String(i).padStart(4, '0')} — a distinctive body marker ${'x'.repeat(20)}`);
|
|
1640
1779
|
const woven = filler.flatMap((l, i) => (i % 40 === 20 ? [framing[(i / 40) | 0 % framing.length] ?? framing[0], l] : [l]));
|
|
1641
1780
|
writeFileSync(join(sb.repo, 'oversized.txt'), `${woven.join('\n')}\n`);
|
|
1642
|
-
const fed = fedRun(sb);
|
|
1781
|
+
const fed = await fedRun(sb);
|
|
1643
1782
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1644
1783
|
assert.equal(fed.status, 0, fed.stderr);
|
|
1645
1784
|
const final = fed.prompts[fed.prompts.length - 1];
|
|
@@ -1658,7 +1797,7 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1658
1797
|
}
|
|
1659
1798
|
});
|
|
1660
1799
|
|
|
1661
|
-
it('a change-set line whose text IS a request address is never chosen (the request would reveal it)', () => {
|
|
1800
|
+
it('a change-set line whose text IS a request address is never chosen (the request would reveal it)', async () => {
|
|
1662
1801
|
const sb = makeSandbox();
|
|
1663
1802
|
// Seed every plausible address form the request line could carry, so a naive selector that
|
|
1664
1803
|
// filters only against the PRE-request envelope can pick one of them.
|
|
@@ -1666,7 +1805,7 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1666
1805
|
const filler = Array.from({ length: 400 }, (_, i) => `unique change-set line ${String(i).padStart(4, '0')} — a distinctive body marker ${'x'.repeat(20)}`);
|
|
1667
1806
|
const woven = filler.flatMap((l, i) => (i % 7 === 3 && addresses[(i / 7) | 0] ? [addresses[(i / 7) | 0], l] : [l]));
|
|
1668
1807
|
writeFileSync(join(sb.repo, 'oversized.txt'), `${woven.join('\n')}\n`);
|
|
1669
|
-
const fed = fedRun(sb);
|
|
1808
|
+
const fed = await fedRun(sb);
|
|
1670
1809
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1671
1810
|
assert.equal(fed.status, 0, fed.stderr);
|
|
1672
1811
|
const final = fed.prompts[fed.prompts.length - 1];
|
|
@@ -1681,9 +1820,9 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1681
1820
|
}
|
|
1682
1821
|
});
|
|
1683
1822
|
|
|
1684
|
-
it('an UNDER-cap single-turn review declares delivery `inline` and still attests', () => {
|
|
1823
|
+
it('an UNDER-cap single-turn review declares delivery `inline` and still attests', async () => {
|
|
1685
1824
|
const sb = makeSandbox();
|
|
1686
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
1825
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
1687
1826
|
const receipts = readReceipts(sb.repo);
|
|
1688
1827
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1689
1828
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -1691,13 +1830,13 @@ describe('agy-review.sh — fed lane: delivery is PROVEN or the review FAILS (D1
|
|
|
1691
1830
|
});
|
|
1692
1831
|
});
|
|
1693
1832
|
|
|
1694
|
-
describe('agy-review.sh — size ceiling + gated --add-dir escape (6)', () => {
|
|
1833
|
+
describe('agy-review.sh — size ceiling + gated --add-dir escape (6)', { concurrency: 2 }, () => {
|
|
1695
1834
|
// D2: chunking is CODE-mode only. A plan/diff artifact is an operator-supplied file the operator
|
|
1696
1835
|
// can split, so those modes keep today's refuse-over-cap behaviour verbatim.
|
|
1697
|
-
it('plan mode: an oversized prompt exits 2 with guidance, agy not invoked (chunking is code-only)', () => {
|
|
1836
|
+
it('plan mode: an oversized prompt exits 2 with guidance, agy not invoked (chunking is code-only)', async () => {
|
|
1698
1837
|
const sb = makeSandbox();
|
|
1699
1838
|
writeFileSync(join(sb.repo, 'big-plan.md'), `# plan\n${'a plan line that is long enough to matter\n'.repeat(400)}`);
|
|
1700
|
-
const r = run(sb, { args: ['plan', 'big-plan.md', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: '4000' } });
|
|
1839
|
+
const r = await run(sb, { args: ['plan', 'big-plan.md', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: '4000' } });
|
|
1701
1840
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1702
1841
|
assert.equal(r.status, 2, r.stderr);
|
|
1703
1842
|
assert.match(r.stderr, /over AGY_MAX_PROMPT_BYTES=4000/);
|
|
@@ -1708,10 +1847,10 @@ describe('agy-review.sh — size ceiling + gated --add-dir escape (6)', () => {
|
|
|
1708
1847
|
|
|
1709
1848
|
// D3: the offload is RETIRED, not removed. The key stays recognized (an existing settings line must
|
|
1710
1849
|
// never start warning as unknown) but it arms nothing, and setting it says so.
|
|
1711
|
-
it('a set AGY_REVIEW_ALLOW_ADDDIR prints the retirement notice and does not pass --add-dir', () => {
|
|
1850
|
+
it('a set AGY_REVIEW_ALLOW_ADDDIR prints the retirement notice and does not pass --add-dir', async () => {
|
|
1712
1851
|
const sb = makeSandbox();
|
|
1713
1852
|
seedFedChangeSet(sb);
|
|
1714
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP), AGY_REVIEW_ALLOW_ADDDIR: '1' } });
|
|
1853
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP), AGY_REVIEW_ALLOW_ADDDIR: '1' } });
|
|
1715
1854
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1716
1855
|
assert.equal(r.status, 0, r.stderr);
|
|
1717
1856
|
assert.match(r.stderr, /AGY_REVIEW_ALLOW_ADDDIR is set \(env\) but it is RETIRED/, 'the notice names the retirement AND where the dead value came from');
|
|
@@ -1721,19 +1860,19 @@ describe('agy-review.sh — size ceiling + gated --add-dir escape (6)', () => {
|
|
|
1721
1860
|
assert.match(r.stderr, /feeding the change set in \d+ part\(s\)/, 'the fed lane runs regardless of the retired knob');
|
|
1722
1861
|
});
|
|
1723
1862
|
|
|
1724
|
-
it('the settings registry still recognizes the retired key (an existing line never warns as unknown)', () => {
|
|
1863
|
+
it('the settings registry still recognizes the retired key (an existing line never warns as unknown)', async () => {
|
|
1725
1864
|
const sb = makeSandbox();
|
|
1726
1865
|
writeSettings(sb, 'AGY_REVIEW_ALLOW_ADDDIR=1\n');
|
|
1727
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
1866
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
1728
1867
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1729
1868
|
assert.equal(r.status, 0, r.stderr);
|
|
1730
1869
|
assert.doesNotMatch(r.stderr, /unknown key 'AGY_REVIEW_ALLOW_ADDDIR'/);
|
|
1731
1870
|
});
|
|
1732
1871
|
|
|
1733
|
-
it('the staging dir is trap-cleaned on exit (no leftover after the run)', () => {
|
|
1872
|
+
it('the staging dir is trap-cleaned on exit (no leftover after the run)', async () => {
|
|
1734
1873
|
const sb = makeSandbox();
|
|
1735
1874
|
seedFedChangeSet(sb);
|
|
1736
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP) } });
|
|
1875
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP) } });
|
|
1737
1876
|
// The fed lane dispatches every turn FROM the staging dir, so the fake's own cwd names it.
|
|
1738
1877
|
const stagingPath = r.dispatchCwd;
|
|
1739
1878
|
const stillThere = stagingPath ? existsSync(stagingPath) : false;
|
|
@@ -1743,11 +1882,11 @@ describe('agy-review.sh — size ceiling + gated --add-dir escape (6)', () => {
|
|
|
1743
1882
|
});
|
|
1744
1883
|
});
|
|
1745
1884
|
|
|
1746
|
-
describe('agy-review.sh — resume / round-2 delta (7)', () => {
|
|
1747
|
-
it('--continue takes NO mode, sends a delta (shape + focus + decided), never re-embeds the artifact', () => {
|
|
1885
|
+
describe('agy-review.sh — resume / round-2 delta (7)', { concurrency: 2 }, () => {
|
|
1886
|
+
it('--continue takes NO mode, sends a delta (shape + focus + decided), never re-embeds the artifact', async () => {
|
|
1748
1887
|
const sb = makeSandbox();
|
|
1749
1888
|
writeFileSync(join(sb.repo, 'decided.md'), 'ALREADY_DECIDED_ITEM\n');
|
|
1750
|
-
const r = run(sb, { args: ['--continue', '--decided', '@decided.md', '--focus', 'ROUND2_FOCUS'] });
|
|
1889
|
+
const r = await run(sb, { args: ['--continue', '--decided', '@decided.md', '--focus', 'ROUND2_FOCUS'] });
|
|
1751
1890
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1752
1891
|
assert.equal(r.status, 0, r.stderr);
|
|
1753
1892
|
assert.match(r.argv, /(^|\n)--continue(\n|$)/, 'agy is continued');
|
|
@@ -1758,10 +1897,10 @@ describe('agy-review.sh — resume / round-2 delta (7)', () => {
|
|
|
1758
1897
|
assert.doesNotMatch(r.prompt, /repo file map/, 'a continuation must NOT re-assemble the artifact');
|
|
1759
1898
|
});
|
|
1760
1899
|
|
|
1761
|
-
it('--continue rejects a mode token and rejects --facts', () => {
|
|
1900
|
+
it('--continue rejects a mode token and rejects --facts', async () => {
|
|
1762
1901
|
const sb = makeSandbox();
|
|
1763
|
-
const r1 = run(sb, { args: ['--continue', 'code'] });
|
|
1764
|
-
const r2 = run(sb, { args: ['--continue', '--facts', 'x'] });
|
|
1902
|
+
const r1 = await run(sb, { args: ['--continue', 'code'] });
|
|
1903
|
+
const r2 = await run(sb, { args: ['--continue', '--facts', 'x'] });
|
|
1765
1904
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1766
1905
|
assert.equal(r1.status, 2);
|
|
1767
1906
|
assert.match(r1.stderr, /takes no positional args/);
|
|
@@ -1769,9 +1908,9 @@ describe('agy-review.sh — resume / round-2 delta (7)', () => {
|
|
|
1769
1908
|
assert.match(r2.stderr, /--facts is not valid on a continuation/);
|
|
1770
1909
|
});
|
|
1771
1910
|
|
|
1772
|
-
it('--conversation <id> threads the id through to agy', () => {
|
|
1911
|
+
it('--conversation <id> threads the id through to agy', async () => {
|
|
1773
1912
|
const sb = makeSandbox();
|
|
1774
|
-
const r = run(sb, { args: ['--conversation', 'conv-xyz', '--focus', 'f'] });
|
|
1913
|
+
const r = await run(sb, { args: ['--conversation', 'conv-xyz', '--focus', 'f'] });
|
|
1775
1914
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1776
1915
|
assert.equal(r.status, 0, r.stderr);
|
|
1777
1916
|
assert.match(r.argv, /(^|\n)--conversation(\n|$)/);
|
|
@@ -1779,7 +1918,7 @@ describe('agy-review.sh — resume / round-2 delta (7)', () => {
|
|
|
1779
1918
|
});
|
|
1780
1919
|
});
|
|
1781
1920
|
|
|
1782
|
-
describe('agy-review.sh — delegated guards inherited via agy-run (9, 10)', { concurrency:
|
|
1921
|
+
describe('agy-review.sh — delegated guards inherited via agy-run (9, 10)', { concurrency: 2 }, () => {
|
|
1783
1922
|
it('hard timeout: a sleeping stub is killed at AGY_HARD_TIMEOUT', async () => {
|
|
1784
1923
|
const sb = makeSandbox();
|
|
1785
1924
|
const started = Date.now();
|
|
@@ -1791,9 +1930,9 @@ describe('agy-review.sh — delegated guards inherited via agy-run (9, 10)', { c
|
|
|
1791
1930
|
assert.match(r.stderr, /exceeded the hard cap/);
|
|
1792
1931
|
});
|
|
1793
1932
|
|
|
1794
|
-
it('subscription invariant: a stray FOO_API_KEY is unset for the agy subprocess', () => {
|
|
1933
|
+
it('subscription invariant: a stray FOO_API_KEY is unset for the agy subprocess', async () => {
|
|
1795
1934
|
const sb = makeSandbox();
|
|
1796
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { FOO_API_KEY: 'bar', ANTIGRAVITY_API_KEY: 'baz' } });
|
|
1935
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { FOO_API_KEY: 'bar', ANTIGRAVITY_API_KEY: 'baz' } });
|
|
1797
1936
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1798
1937
|
assert.equal(r.status, 0, r.stderr);
|
|
1799
1938
|
assert.match(r.capEnv, /^FOO_API_KEY=<unset>$/m);
|
|
@@ -1801,81 +1940,81 @@ describe('agy-review.sh — delegated guards inherited via agy-run (9, 10)', { c
|
|
|
1801
1940
|
});
|
|
1802
1941
|
});
|
|
1803
1942
|
|
|
1804
|
-
describe('agy-review.sh — mode / arg validation (11)', () => {
|
|
1805
|
-
it('unknown mode → usage + exit 2', () => {
|
|
1943
|
+
describe('agy-review.sh — mode / arg validation (11)', { concurrency: 2 }, () => {
|
|
1944
|
+
it('unknown mode → usage + exit 2', async () => {
|
|
1806
1945
|
const sb = makeSandbox();
|
|
1807
|
-
const r = run(sb, { args: ['bogus'] });
|
|
1946
|
+
const r = await run(sb, { args: ['bogus'] });
|
|
1808
1947
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1809
1948
|
assert.equal(r.status, 2);
|
|
1810
1949
|
assert.match(r.stderr, /usage:/);
|
|
1811
1950
|
assert.equal(r.invoked, false);
|
|
1812
1951
|
});
|
|
1813
1952
|
|
|
1814
|
-
it('plan mode with a missing file → exit 2', () => {
|
|
1953
|
+
it('plan mode with a missing file → exit 2', async () => {
|
|
1815
1954
|
const sb = makeSandbox();
|
|
1816
|
-
const r = run(sb, { args: ['plan', 'nope.md'] });
|
|
1955
|
+
const r = await run(sb, { args: ['plan', 'nope.md'] });
|
|
1817
1956
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1818
1957
|
assert.equal(r.status, 2);
|
|
1819
1958
|
assert.match(r.stderr, /plan file 'nope\.md' not found/);
|
|
1820
1959
|
});
|
|
1821
1960
|
|
|
1822
|
-
it('diff mode inlines the supplied file', () => {
|
|
1961
|
+
it('diff mode inlines the supplied file', async () => {
|
|
1823
1962
|
const sb = makeSandbox();
|
|
1824
1963
|
writeFileSync(join(sb.repo, 'change.diff'), 'DIFF_FILE_BODY_MARKER\n');
|
|
1825
|
-
const r = run(sb, { args: ['diff', 'change.diff', '--facts', 'f'] });
|
|
1964
|
+
const r = await run(sb, { args: ['diff', 'change.diff', '--facts', 'f'] });
|
|
1826
1965
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1827
1966
|
assert.equal(r.status, 0, r.stderr);
|
|
1828
1967
|
assert.match(r.prompt, /The diff under review/);
|
|
1829
1968
|
assert.match(r.prompt, /DIFF_FILE_BODY_MARKER/);
|
|
1830
1969
|
});
|
|
1831
1970
|
|
|
1832
|
-
it('rejects a stray -- passthrough (the wrapper owns the posture)', () => {
|
|
1971
|
+
it('rejects a stray -- passthrough (the wrapper owns the posture)', async () => {
|
|
1833
1972
|
const sb = makeSandbox();
|
|
1834
|
-
const r = run(sb, { args: ['code', '--', '--add-dir', '.'] });
|
|
1973
|
+
const r = await run(sb, { args: ['code', '--', '--add-dir', '.'] });
|
|
1835
1974
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1836
1975
|
assert.equal(r.status, 2);
|
|
1837
1976
|
assert.match(r.stderr, /this wrapper OWNS the review posture/);
|
|
1838
1977
|
});
|
|
1839
1978
|
|
|
1840
|
-
it('rejects a value-flag that swallows the NEXT flag as its value (--facts --focus x → exit 2)', () => {
|
|
1979
|
+
it('rejects a value-flag that swallows the NEXT flag as its value (--facts --focus x → exit 2)', async () => {
|
|
1841
1980
|
const sb = makeSandbox();
|
|
1842
|
-
const r = run(sb, { args: ['code', '--facts', '--focus', 'x'] });
|
|
1981
|
+
const r = await run(sb, { args: ['code', '--facts', '--focus', 'x'] });
|
|
1843
1982
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1844
1983
|
assert.equal(r.status, 2, r.stderr);
|
|
1845
1984
|
assert.match(r.stderr, /--facts needs a value/);
|
|
1846
1985
|
assert.equal(r.invoked, false, 'a misplaced flag must not be spent as bogus grounding');
|
|
1847
1986
|
});
|
|
1848
1987
|
|
|
1849
|
-
it('rejects a value-flag with no value at the end of args (--decided → exit 2)', () => {
|
|
1988
|
+
it('rejects a value-flag with no value at the end of args (--decided → exit 2)', async () => {
|
|
1850
1989
|
const sb = makeSandbox();
|
|
1851
|
-
const r = run(sb, { args: ['code', '--facts', 'f', '--decided'] });
|
|
1990
|
+
const r = await run(sb, { args: ['code', '--facts', 'f', '--decided'] });
|
|
1852
1991
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1853
1992
|
assert.equal(r.status, 2, r.stderr);
|
|
1854
1993
|
assert.match(r.stderr, /--decided needs a value/);
|
|
1855
1994
|
});
|
|
1856
1995
|
});
|
|
1857
1996
|
|
|
1858
|
-
describe('agy-review.sh — no-env run (12)', () => {
|
|
1859
|
-
it('a code review with NO AGY_* env vars runs cleanly (no unbound-var abort under set -u)', () => {
|
|
1997
|
+
describe('agy-review.sh — no-env run (12)', { concurrency: 2 }, () => {
|
|
1998
|
+
it('a code review with NO AGY_* env vars runs cleanly (no unbound-var abort under set -u)', async () => {
|
|
1860
1999
|
const sb = makeSandbox();
|
|
1861
2000
|
// run() sets only HOME/PATH + the AGY_FAKE_* capture vars (not AGY_* config) — so this exercises
|
|
1862
2001
|
// the all-defaults path.
|
|
1863
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
2002
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
1864
2003
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1865
2004
|
assert.equal(r.status, 0, r.stderr);
|
|
1866
2005
|
assert.equal(r.invoked, true);
|
|
1867
2006
|
});
|
|
1868
2007
|
});
|
|
1869
2008
|
|
|
1870
|
-
describe('agy-review.sh — subdir invocation is repo-complete (13)', () => {
|
|
1871
|
-
it('from a subdir, assembles a repo-complete change set AND reads a relative --facts path', () => {
|
|
2009
|
+
describe('agy-review.sh — subdir invocation is repo-complete (13)', { concurrency: 2 }, () => {
|
|
2010
|
+
it('from a subdir, assembles a repo-complete change set AND reads a relative --facts path', async () => {
|
|
1872
2011
|
const sb = makeSandbox();
|
|
1873
2012
|
// a change to a ROOT file (sibling of the subdir we invoke from)
|
|
1874
2013
|
writeFileSync(join(sb.repo, 'root-change.txt'), 'ROOT_SIBLING_CHANGE\n');
|
|
1875
2014
|
const sub = join(sb.repo, 'deep', 'nested');
|
|
1876
2015
|
mkdirSync(sub, { recursive: true });
|
|
1877
2016
|
writeFileSync(join(sub, 'local-facts.md'), 'SUBDIR_RELATIVE_FACT\n');
|
|
1878
|
-
const r = run(sb, { args: ['code', '--facts', '@local-facts.md'], cwd: sub });
|
|
2017
|
+
const r = await run(sb, { args: ['code', '--facts', '@local-facts.md'], cwd: sub });
|
|
1879
2018
|
rmSync(sb.home, { recursive: true, force: true });
|
|
1880
2019
|
assert.equal(r.status, 0, r.stderr);
|
|
1881
2020
|
assert.match(r.prompt, /ROOT_SIBLING_CHANGE/, 'the root/sibling change must appear (repo-complete via cd to toplevel)');
|
|
@@ -1987,8 +2126,8 @@ const consultsEnv = (source, name) =>
|
|
|
1987
2126
|
// `<facts-file>` behind a stray character) — the catalog declares the whole token a user types.
|
|
1988
2127
|
const SLOT_RE = /@?<[^<>]+>|\[[^[\]]*\]/g;
|
|
1989
2128
|
|
|
1990
|
-
describe('agy-review.sh — --help contract (manifest-pinned)', () => {
|
|
1991
|
-
it('--help and -h exit 0 pre-preflight (no agy, no git)', () => {
|
|
2129
|
+
describe('agy-review.sh — --help contract (manifest-pinned)', { concurrency: 2 }, () => {
|
|
2130
|
+
it('--help and -h exit 0 pre-preflight (no agy, no git)', async () => {
|
|
1992
2131
|
for (const arg of ['--help', '-h']) {
|
|
1993
2132
|
const r = runHelp(arg);
|
|
1994
2133
|
assert.equal(r.status, 0, `${arg}: ${r.stderr}`);
|
|
@@ -1997,39 +2136,39 @@ describe('agy-review.sh — --help contract (manifest-pinned)', () => {
|
|
|
1997
2136
|
}
|
|
1998
2137
|
});
|
|
1999
2138
|
|
|
2000
|
-
it('Usage set-EQUALS the manifest invocation descriptors (both directions)', () => {
|
|
2139
|
+
it('Usage set-EQUALS the manifest invocation descriptors (both directions)', async () => {
|
|
2001
2140
|
const help = runHelp('--help').stdout;
|
|
2002
2141
|
const got = helpSection(help, 'Usage:').filter((l) => l.startsWith('agy-review')).map(norm);
|
|
2003
2142
|
assert.ok(REVIEW_CONTRACT.invocations.length > 0, 'manifest invocations must be non-empty');
|
|
2004
2143
|
setEq(got, REVIEW_CONTRACT.invocations.map(norm), 'help Usage ⟷ manifest invocations');
|
|
2005
2144
|
});
|
|
2006
2145
|
|
|
2007
|
-
it('Flags set-EQUALS the manifest flag descriptors (both directions)', () => {
|
|
2146
|
+
it('Flags set-EQUALS the manifest flag descriptors (both directions)', async () => {
|
|
2008
2147
|
const help = runHelp('--help').stdout;
|
|
2009
2148
|
const got = helpSection(help, 'Flags:').filter((l) => l.startsWith('--')).map(norm);
|
|
2010
2149
|
assert.ok(REVIEW_CONTRACT.flags.length > 0, 'manifest flags must be non-empty');
|
|
2011
2150
|
setEq(got, REVIEW_CONTRACT.flags.map(norm), 'help Flags ⟷ manifest flags');
|
|
2012
2151
|
});
|
|
2013
2152
|
|
|
2014
|
-
it('Grounding renders the manifest grounding note verbatim', () => {
|
|
2153
|
+
it('Grounding renders the manifest grounding note verbatim', async () => {
|
|
2015
2154
|
const help = runHelp('--help').stdout;
|
|
2016
2155
|
assert.equal(norm(helpSection(help, 'Grounding:').join(' ')), norm(REVIEW_CONTRACT.grounding));
|
|
2017
2156
|
});
|
|
2018
2157
|
|
|
2019
|
-
it('Notes renders the manifest contract.notes verbatim (a typed contract key that MUST surface)', () => {
|
|
2158
|
+
it('Notes renders the manifest contract.notes verbatim (a typed contract key that MUST surface)', async () => {
|
|
2020
2159
|
const help = runHelp('--help').stdout;
|
|
2021
2160
|
assert.ok(REVIEW_CONTRACT.notes.length > 0, 'manifest notes must be non-empty');
|
|
2022
2161
|
assert.equal(norm(helpSection(help, 'Notes:').join(' ')), norm(REVIEW_CONTRACT.notes.join(' ')));
|
|
2023
2162
|
});
|
|
2024
2163
|
|
|
2025
|
-
it('Round-2 / resume set-EQUALS the manifest continue descriptors', () => {
|
|
2164
|
+
it('Round-2 / resume set-EQUALS the manifest continue descriptors', async () => {
|
|
2026
2165
|
const help = runHelp('--help').stdout;
|
|
2027
2166
|
const got = helpSection(help, 'Round-2 / resume:').filter((l) => l.startsWith('agy-review')).map(norm);
|
|
2028
2167
|
assert.ok(REVIEW_CONTRACT.continue.length > 0, 'manifest continue must be non-empty');
|
|
2029
2168
|
setEq(got, REVIEW_CONTRACT.continue.map(norm), 'help continue ⟷ manifest continue');
|
|
2030
2169
|
});
|
|
2031
2170
|
|
|
2032
|
-
it('Receipt renders the manifest receipt contract verbatim (AD-038 three-way lockstep)', () => {
|
|
2171
|
+
it('Receipt renders the manifest receipt contract verbatim (AD-038 three-way lockstep)', async () => {
|
|
2033
2172
|
const help = runHelp('--help').stdout;
|
|
2034
2173
|
assert.equal(norm(helpSection(help, 'Receipt:').join(' ')), norm(REVIEW_CONTRACT.receipt));
|
|
2035
2174
|
assert.match(REVIEW_CONTRACT.receipt, /sha256 over the canonical uncommitted-state payload/, 'the fingerprint definition lives in the manifest contract');
|
|
@@ -2037,10 +2176,10 @@ describe('agy-review.sh — --help contract (manifest-pinned)', () => {
|
|
|
2037
2176
|
});
|
|
2038
2177
|
});
|
|
2039
2178
|
|
|
2040
|
-
describe('agy-review.sh — source-level reverse guard (parser arms ⟷ manifest)', () => {
|
|
2179
|
+
describe('agy-review.sh — source-level reverse guard (parser arms ⟷ manifest)', { concurrency: 2 }, () => {
|
|
2041
2180
|
const arms = extractArgCaseArms(readFileSync(WRAPPER, 'utf8'));
|
|
2042
2181
|
|
|
2043
|
-
it('the real mode arms equal the manifest modes (adding a mode without the manifest fails here)', () => {
|
|
2182
|
+
it('the real mode arms equal the manifest modes (adding a mode without the manifest fails here)', async () => {
|
|
2044
2183
|
// Deliberately a UNION over every `case "$mode"` in the wrapper (the CLI dispatch AND the
|
|
2045
2184
|
// emit_artifact renderer): the union can only be conservative — a mode added to EITHER case
|
|
2046
2185
|
// without the manifest goes red; no renderer-only arm can make a missing manifest entry green.
|
|
@@ -2049,14 +2188,14 @@ describe('agy-review.sh — source-level reverse guard (parser arms ⟷ manifest
|
|
|
2049
2188
|
setEq(new Set(modes), MANIFEST.roles.review.modes, 'parser mode arms ⟷ manifest modes');
|
|
2050
2189
|
});
|
|
2051
2190
|
|
|
2052
|
-
it('the real flag arms equal the manifest flag set (closed grammar; catch-alls excluded)', () => {
|
|
2191
|
+
it('the real flag arms equal the manifest flag set (closed grammar; catch-alls excluded)', async () => {
|
|
2053
2192
|
const flagArms = splitArms(arms.get('"$1"')).filter((a) => !['--', '--*', '*'].includes(a));
|
|
2054
2193
|
const declared = REVIEW_CONTRACT.flags.map(leadingFlag);
|
|
2055
2194
|
assert.ok(declared.length > 0, 'manifest flag set must be non-empty');
|
|
2056
2195
|
setEq(new Set(flagArms), new Set(declared), 'parser flag arms ⟷ manifest flags');
|
|
2057
2196
|
});
|
|
2058
2197
|
|
|
2059
|
-
it('the first-arg entrypoints are exactly --help/-h + the manifest continue flags', () => {
|
|
2198
|
+
it('the first-arg entrypoints are exactly --help/-h + the manifest continue flags', async () => {
|
|
2060
2199
|
const declared = REVIEW_CONTRACT.continue.map(leadingFlag);
|
|
2061
2200
|
assert.ok(declared.length > 0, 'manifest continue set must be non-empty');
|
|
2062
2201
|
setEq(new Set(splitArms(arms.get('"${1:-}"'))), new Set(['--help', '-h', ...declared]));
|
|
@@ -2067,20 +2206,20 @@ describe('agy-review.sh — source-level reverse guard (parser arms ⟷ manifest
|
|
|
2067
2206
|
// The kit validator owns the catalog's INTERNAL shape; these arms pin the half only this wrapper's
|
|
2068
2207
|
// source can settle — the cataloged review modes ARE the real parser arms, every declared contract
|
|
2069
2208
|
// invocation is cataloged, and the env-hook the catalog aims at review is a real env var.
|
|
2070
|
-
describe('agy-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)', () => {
|
|
2209
|
+
describe('agy-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)', { concurrency: 2 }, () => {
|
|
2071
2210
|
const source = readFileSync(WRAPPER, 'utf8');
|
|
2072
2211
|
const arms = extractArgCaseArms(source);
|
|
2073
2212
|
const catalog = MANIFEST.modeCatalog ?? [];
|
|
2074
2213
|
const reviewEntries = catalog.filter((e) => e.role === 'review');
|
|
2075
2214
|
const reviewPrimaries = reviewEntries.filter((e) => e.kind === 'primary');
|
|
2076
2215
|
|
|
2077
|
-
it('the catalog submodes ARE the wrapper\'s real parser mode arms (both directions)', () => {
|
|
2216
|
+
it('the catalog submodes ARE the wrapper\'s real parser mode arms (both directions)', async () => {
|
|
2078
2217
|
const modes = splitArms(arms.get('"$mode"')).filter((a) => a !== '*');
|
|
2079
2218
|
assert.ok(reviewPrimaries.length > 0, 'the manifest must catalog its review modes');
|
|
2080
2219
|
setEq(new Set(reviewPrimaries.map((e) => e.submode)), new Set(modes), 'catalog submodes ⟷ real parser mode arms');
|
|
2081
2220
|
});
|
|
2082
2221
|
|
|
2083
|
-
it('every review entry composes BY REFERENCE and every reference resolves', () => {
|
|
2222
|
+
it('every review entry composes BY REFERENCE and every reference resolves', async () => {
|
|
2084
2223
|
for (const entry of reviewEntries) {
|
|
2085
2224
|
assert.ok(
|
|
2086
2225
|
Array.isArray(entry.invocationRefs) && entry.invocationRefs.length > 0,
|
|
@@ -2096,7 +2235,7 @@ describe('agy-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
2096
2235
|
}
|
|
2097
2236
|
});
|
|
2098
2237
|
|
|
2099
|
-
it('every review contract invocation is claimed by exactly ONE catalog entry (no uncataloged mode)', () => {
|
|
2238
|
+
it('every review contract invocation is claimed by exactly ONE catalog entry (no uncataloged mode)', async () => {
|
|
2100
2239
|
const claims = reviewEntries.flatMap((e) => e.invocationRefs.map((r) => `${r.contractField}[${r.index}]`));
|
|
2101
2240
|
assert.equal(new Set(claims).size, claims.length, 'a contract invocation is claimed at most once');
|
|
2102
2241
|
const declared = [
|
|
@@ -2106,7 +2245,7 @@ describe('agy-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
2106
2245
|
setEq(new Set(claims), declared, 'catalog claims ⟷ declared contract invocations');
|
|
2107
2246
|
});
|
|
2108
2247
|
|
|
2109
|
-
it('every env-hook the catalog aims at a review mode is a real EXECUTABLE guard, not a mention', () => {
|
|
2248
|
+
it('every env-hook the catalog aims at a review mode is a real EXECUTABLE guard, not a mention', async () => {
|
|
2110
2249
|
const hooks = catalog.filter((e) => e.kind === 'env-hook' && e.parents.some((p) => reviewPrimaries.some((r) => r.key === p)));
|
|
2111
2250
|
assert.ok(hooks.length > 0, 'AGY_PROBE must be cataloged as an env-hook over the review modes');
|
|
2112
2251
|
for (const hook of hooks) {
|
|
@@ -2117,7 +2256,7 @@ describe('agy-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
2117
2256
|
}
|
|
2118
2257
|
});
|
|
2119
2258
|
|
|
2120
|
-
it('the catalog operand slots set-EQUAL the slots its rendered forms really carry (both directions)', () => {
|
|
2259
|
+
it('the catalog operand slots set-EQUAL the slots its rendered forms really carry (both directions)', async () => {
|
|
2121
2260
|
for (const entry of reviewEntries) {
|
|
2122
2261
|
const forms = entry.invocationRefs.map((r) => REVIEW_CONTRACT[r.contractField][r.index]);
|
|
2123
2262
|
// The DEDUPLICATED UNION over every resolved form: a plural-ref entry legitimately spreads its
|
|
@@ -2127,7 +2266,7 @@ describe('agy-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
2127
2266
|
}
|
|
2128
2267
|
});
|
|
2129
2268
|
|
|
2130
|
-
it('an entry rendering a LITERAL descriptor is slot-checked too (env-hooks have no role to filter on)', () => {
|
|
2269
|
+
it('an entry rendering a LITERAL descriptor is slot-checked too (env-hooks have no role to filter on)', async () => {
|
|
2131
2270
|
// The contract-backed arm above filters by role — and an env-hook HAS no role, so its descriptor
|
|
2132
2271
|
// was never slot-checked at all. That is exactly how a hardcoded dead path can reach the
|
|
2133
2272
|
// discovery surface looking ready-to-run. Every literal-descriptor kind is covered here:
|
|
@@ -2140,7 +2279,7 @@ describe('agy-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
2140
2279
|
}
|
|
2141
2280
|
});
|
|
2142
2281
|
|
|
2143
|
-
it('AGY_PROBE really silences the advisory on EVERY review parent the catalog claims (behavioural)', () => {
|
|
2282
|
+
it('AGY_PROBE really silences the advisory on EVERY review parent the catalog claims (behavioural)', async () => {
|
|
2144
2283
|
// The catalog CLAIMS these modes are modified by the hook; prove it per parent rather than
|
|
2145
2284
|
// trusting a source scan: the off-frontier advisory fires without it, is silent with it.
|
|
2146
2285
|
const hook = catalog.find((e) => e.key === 'AGY_PROBE');
|
|
@@ -2157,14 +2296,14 @@ describe('agy-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
2157
2296
|
// Both runs must really REACH agy: asserting the diagnostic text alone would let an early
|
|
2158
2297
|
// failure that never dispatched pass the probe-on branch (its stderr simply lacks the string).
|
|
2159
2298
|
const noisy = makeSandbox();
|
|
2160
|
-
const off = run(noisy, { args: drive[parent](noisy), env: { AGY_MODEL: 'Some Weak Model' } });
|
|
2299
|
+
const off = await run(noisy, { args: drive[parent](noisy), env: { AGY_MODEL: 'Some Weak Model' } });
|
|
2161
2300
|
rmSync(noisy.home, { recursive: true, force: true });
|
|
2162
2301
|
assert.equal(off.status, 0, `${parent}: ${off.stderr}`);
|
|
2163
2302
|
assert.equal(off.invoked, true, `${parent}: the control run must reach agy`);
|
|
2164
2303
|
assert.match(off.stderr, /non-frontier model/, `${parent}: the advisory must fire without the hook`);
|
|
2165
2304
|
|
|
2166
2305
|
const quiet = makeSandbox();
|
|
2167
|
-
const on = run(quiet, { args: drive[parent](quiet), env: { AGY_MODEL: 'Some Weak Model', AGY_PROBE: '1' } });
|
|
2306
|
+
const on = await run(quiet, { args: drive[parent](quiet), env: { AGY_MODEL: 'Some Weak Model', AGY_PROBE: '1' } });
|
|
2168
2307
|
rmSync(quiet.home, { recursive: true, force: true });
|
|
2169
2308
|
assert.equal(on.status, 0, `${parent}: ${on.stderr}`);
|
|
2170
2309
|
assert.equal(on.invoked, true, `${parent}: AGY_PROBE=1 must still reach agy — silence must come from the hook, not from an early exit`);
|
|
@@ -2173,8 +2312,8 @@ describe('agy-review.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
2173
2312
|
});
|
|
2174
2313
|
});
|
|
2175
2314
|
|
|
2176
|
-
describe('agy-review.sh — declared contract is really accepted (forward guard)', () => {
|
|
2177
|
-
it('every manifest mode runs green', () => {
|
|
2315
|
+
describe('agy-review.sh — declared contract is really accepted (forward guard)', { concurrency: 2 }, () => {
|
|
2316
|
+
it('every manifest mode runs green', async () => {
|
|
2178
2317
|
const drive = {
|
|
2179
2318
|
code: () => ['code', '--facts', 'f'],
|
|
2180
2319
|
plan: (sb) => { writeFileSync(join(sb.repo, 'p.md'), '# p\n'); return ['plan', 'p.md', '--facts', 'f']; },
|
|
@@ -2183,14 +2322,14 @@ describe('agy-review.sh — declared contract is really accepted (forward guard)
|
|
|
2183
2322
|
for (const mode of MANIFEST.roles.review.modes) {
|
|
2184
2323
|
assert.ok(drive[mode], `no test drive for manifest mode "${mode}" — add one`);
|
|
2185
2324
|
const sb = makeSandbox();
|
|
2186
|
-
const r = run(sb, { args: drive[mode](sb) });
|
|
2325
|
+
const r = await run(sb, { args: drive[mode](sb) });
|
|
2187
2326
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2188
2327
|
assert.equal(r.status, 0, `mode ${mode}: ${r.stderr}`);
|
|
2189
2328
|
assert.equal(r.invoked, true, `mode ${mode} must reach agy`);
|
|
2190
2329
|
}
|
|
2191
2330
|
});
|
|
2192
2331
|
|
|
2193
|
-
it('every manifest flag is accepted in code mode', () => {
|
|
2332
|
+
it('every manifest flag is accepted in code mode', async () => {
|
|
2194
2333
|
for (const descriptor of REVIEW_CONTRACT.flags) {
|
|
2195
2334
|
const flag = leadingFlag(descriptor);
|
|
2196
2335
|
const sb = makeSandbox();
|
|
@@ -2199,24 +2338,24 @@ describe('agy-review.sh — declared contract is really accepted (forward guard)
|
|
|
2199
2338
|
const args = flag === '--facts' ? ['code', '--facts', 'f']
|
|
2200
2339
|
: flag === '--ungrounded' ? ['code', '--ungrounded']
|
|
2201
2340
|
: ['code', '--facts', 'f', flag, 'f'];
|
|
2202
|
-
const r = run(sb, { args });
|
|
2341
|
+
const r = await run(sb, { args });
|
|
2203
2342
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2204
2343
|
assert.equal(r.status, 0, `${flag}: ${r.stderr}`);
|
|
2205
2344
|
}
|
|
2206
2345
|
});
|
|
2207
2346
|
|
|
2208
|
-
it('an invented flag is rejected (closed grammar negative)', () => {
|
|
2347
|
+
it('an invented flag is rejected (closed grammar negative)', async () => {
|
|
2209
2348
|
const sb = makeSandbox();
|
|
2210
|
-
const r = run(sb, { args: ['code', '--facts', 'f', '--bogus-flag'] });
|
|
2349
|
+
const r = await run(sb, { args: ['code', '--facts', 'f', '--bogus-flag'] });
|
|
2211
2350
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2212
2351
|
assert.equal(r.status, 2);
|
|
2213
2352
|
assert.match(r.stderr, /unknown flag '--bogus-flag'/);
|
|
2214
2353
|
assert.equal(r.invoked, false, 'an unknown flag must not spend a run');
|
|
2215
2354
|
});
|
|
2216
2355
|
|
|
2217
|
-
it('--help NOT in first position is an unknown flag, never an intercepted help', () => {
|
|
2356
|
+
it('--help NOT in first position is an unknown flag, never an intercepted help', async () => {
|
|
2218
2357
|
const sb = makeSandbox();
|
|
2219
|
-
const r = run(sb, { args: ['code', '--facts', 'f', '--help'] });
|
|
2358
|
+
const r = await run(sb, { args: ['code', '--facts', 'f', '--help'] });
|
|
2220
2359
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2221
2360
|
assert.equal(r.status, 2, 'help is keyed on the FIRST argument only');
|
|
2222
2361
|
assert.doesNotMatch(r.stdout, /Usage:/);
|
|
@@ -2241,10 +2380,10 @@ const sha256HexOf = async (buf) => {
|
|
|
2241
2380
|
};
|
|
2242
2381
|
const VERDICT_OUTPUT = '### Verdict\nSHIP WITH NITS — solid, two nits.\n### Blocking\nnone\n### Non-blocking\n1. nit\n### Questions\nnone';
|
|
2243
2382
|
|
|
2244
|
-
describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
2383
|
+
describe('agy-review.sh — review receipts (AD-038)', { concurrency: 2 }, () => {
|
|
2245
2384
|
it('a fresh grounded code review appends ONE fixture-shaped receipt (verdict verbatim, factsHash real)', async () => {
|
|
2246
2385
|
const sb = makeSandbox();
|
|
2247
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2386
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2248
2387
|
const receipts = readReceipts(sb.repo);
|
|
2249
2388
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2250
2389
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2268,9 +2407,9 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2268
2407
|
// the kit's review-state gate rejects it. EVERY receipt carries the marker (true or false): it
|
|
2269
2408
|
// self-declares, so the gate reads the fact rather than inferring it from a version string that
|
|
2270
2409
|
// bumps in a different release phase. Silence is not a declaration.
|
|
2271
|
-
it('AGY_PROBE=1 stamps probe:true — a throwaway probe can never attest a tree (D3)', () => {
|
|
2410
|
+
it('AGY_PROBE=1 stamps probe:true — a throwaway probe can never attest a tree (D3)', async () => {
|
|
2272
2411
|
const sb = makeSandbox();
|
|
2273
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_PROBE: '1', AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2412
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_PROBE: '1', AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2274
2413
|
const receipts = readReceipts(sb.repo);
|
|
2275
2414
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2276
2415
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2280,17 +2419,17 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2280
2419
|
|
|
2281
2420
|
// Every receipt SELF-DECLARES: the kit's gate reads the marker, never the wrapper version — so
|
|
2282
2421
|
// the marker must not depend on a version bump landing in the same release phase.
|
|
2283
|
-
it('a normal review self-declares probe:false — the receipt states the fact, not a version', () => {
|
|
2422
|
+
it('a normal review self-declares probe:false — the receipt states the fact, not a version', async () => {
|
|
2284
2423
|
const sb = makeSandbox();
|
|
2285
|
-
run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2424
|
+
await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2286
2425
|
const receipts = readReceipts(sb.repo);
|
|
2287
2426
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2288
2427
|
assert.equal(receipts[0].probe, false, 'silence is not a declaration — the gate rejects an unmarked receipt');
|
|
2289
2428
|
});
|
|
2290
2429
|
|
|
2291
|
-
it('a probe CONTINUATION is marked too (it is doubly unable to attest — fresh:false AND probe)', () => {
|
|
2430
|
+
it('a probe CONTINUATION is marked too (it is doubly unable to attest — fresh:false AND probe)', async () => {
|
|
2292
2431
|
const sb = makeSandbox();
|
|
2293
|
-
const r = run(sb, { args: ['--continue'], env: { AGY_PROBE: '1', AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2432
|
+
const r = await run(sb, { args: ['--continue'], env: { AGY_PROBE: '1', AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2294
2433
|
const receipts = readReceipts(sb.repo);
|
|
2295
2434
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2296
2435
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2298,9 +2437,9 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2298
2437
|
assert.equal(receipts[0].probe, true, 'both write paths carry the marker — no unmarked probe lane');
|
|
2299
2438
|
});
|
|
2300
2439
|
|
|
2301
|
-
it('an --ungrounded fresh run records grounded:false + factsHash null (the vacuous-grounding hole stays visible)', () => {
|
|
2440
|
+
it('an --ungrounded fresh run records grounded:false + factsHash null (the vacuous-grounding hole stays visible)', async () => {
|
|
2302
2441
|
const sb = makeSandbox();
|
|
2303
|
-
const r = run(sb, { args: ['code', '--ungrounded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2442
|
+
const r = await run(sb, { args: ['code', '--ungrounded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2304
2443
|
const receipts = readReceipts(sb.repo);
|
|
2305
2444
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2306
2445
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2308,10 +2447,10 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2308
2447
|
assert.equal(receipts[0].factsHash, null);
|
|
2309
2448
|
});
|
|
2310
2449
|
|
|
2311
|
-
it('an EMPTY --facts file in code mode refuses pre-spend — no run, no receipt (D4 fail-closed)', () => {
|
|
2450
|
+
it('an EMPTY --facts file in code mode refuses pre-spend — no run, no receipt (D4 fail-closed)', async () => {
|
|
2312
2451
|
const sb = makeSandbox();
|
|
2313
2452
|
writeFileSync(join(sb.home, 'empty-facts.md'), '');
|
|
2314
|
-
const r = run(sb, { args: ['code', '--facts', `@${join(sb.home, 'empty-facts.md')}`], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2453
|
+
const r = await run(sb, { args: ['code', '--facts', `@${join(sb.home, 'empty-facts.md')}`], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2315
2454
|
const receipts = readReceipts(sb.repo);
|
|
2316
2455
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2317
2456
|
assert.equal(r.status, 2, 'vacuous grounding no longer spends a run');
|
|
@@ -2319,13 +2458,13 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2319
2458
|
assert.equal(receipts.length, 0, 'no run — no receipt');
|
|
2320
2459
|
});
|
|
2321
2460
|
|
|
2322
|
-
it('parses REWORK and plain SHIP verbatim (an absent section is a FAILED run — the D4 describe owns that arm)', () => {
|
|
2461
|
+
it('parses REWORK and plain SHIP verbatim (an absent section is a FAILED run — the D4 describe owns that arm)', async () => {
|
|
2323
2462
|
for (const [output, want] of [
|
|
2324
2463
|
['### Verdict\nREWORK — the contract is violated.', 'REWORK'],
|
|
2325
2464
|
['### Verdict\nSHIP — clean.', 'SHIP'],
|
|
2326
2465
|
]) {
|
|
2327
2466
|
const sb = makeSandbox();
|
|
2328
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: output } });
|
|
2467
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: output } });
|
|
2329
2468
|
const receipts = readReceipts(sb.repo);
|
|
2330
2469
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2331
2470
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2336,12 +2475,12 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2336
2475
|
// The wrapper-minted finding manifest (flow-orchestration Phase 4.2, Decision 2/P5/P24-25):
|
|
2337
2476
|
// nonce-supplied dispatches mint {schema, backend, nonce, fingerprint, findings} beside the
|
|
2338
2477
|
// receipt, atomic + no-clobber + ORDERED — a failed mint EXCLUDES the receipt append.
|
|
2339
|
-
describe('finding manifest (AW_REVIEW_NONCE)', () => {
|
|
2478
|
+
describe('finding manifest (AW_REVIEW_NONCE)', { concurrency: 2 }, () => {
|
|
2340
2479
|
const manifestPath = (repo, nonce) => join(repo, '.git', `agent-workflow-finding-manifest-agy-${nonce}.json`);
|
|
2341
2480
|
|
|
2342
|
-
it('a nonce-supplied grounded code dispatch mints the {backend, nonce}-named manifest carrying the captured findings + the receipt fingerprint', () => {
|
|
2481
|
+
it('a nonce-supplied grounded code dispatch mints the {backend, nonce}-named manifest carrying the captured findings + the receipt fingerprint', async () => {
|
|
2343
2482
|
const sb = makeSandbox();
|
|
2344
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'r1-d2' } });
|
|
2483
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'r1-d2' } });
|
|
2345
2484
|
const receipts = readReceipts(sb.repo);
|
|
2346
2485
|
const manifest = JSON.parse(readFileSync(manifestPath(sb.repo, 'r1-d2'), 'utf8'));
|
|
2347
2486
|
rmSync(sb.home, { recursive: true, force: true });
|
|
@@ -2356,9 +2495,9 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2356
2495
|
assert.equal(receipts[0].nonce, 'r1-d2', 'a nonce-supplied receipt carries the dispatch nonce — the flow round-land matcher requires exact equality (dispatch identity end-to-end)');
|
|
2357
2496
|
});
|
|
2358
2497
|
|
|
2359
|
-
it('a nonce-less invocation mints NO manifest and adds NO nonce field — the receipt field set is unchanged (Decision 2 both branches)', () => {
|
|
2498
|
+
it('a nonce-less invocation mints NO manifest and adds NO nonce field — the receipt field set is unchanged (Decision 2 both branches)', async () => {
|
|
2360
2499
|
const sb = makeSandbox();
|
|
2361
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2500
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2362
2501
|
const receipts = readReceipts(sb.repo);
|
|
2363
2502
|
const gitEntries = readdirSync(join(sb.repo, '.git')).filter((n) => n.startsWith('agent-workflow-finding-manifest-'));
|
|
2364
2503
|
rmSync(sb.home, { recursive: true, force: true });
|
|
@@ -2367,10 +2506,10 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2367
2506
|
assert.deepEqual(Object.keys(receipts[0]), Object.keys(RECEIPT_FIXTURE), 'the receipt line field set is unchanged');
|
|
2368
2507
|
});
|
|
2369
2508
|
|
|
2370
|
-
it('DIFFERENT bytes at the derived name refuse loudly AND EXCLUDE the receipt append (ordering, behaviorally)', () => {
|
|
2509
|
+
it('DIFFERENT bytes at the derived name refuse loudly AND EXCLUDE the receipt append (ordering, behaviorally)', async () => {
|
|
2371
2510
|
const sb = makeSandbox();
|
|
2372
2511
|
writeFileSync(manifestPath(sb.repo, 'r1-d2'), '{"schema":1,"backend":"agy","nonce":"r1-d2","fingerprint":null,"findings":"other bytes"}\n');
|
|
2373
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'r1-d2' } });
|
|
2512
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'r1-d2' } });
|
|
2374
2513
|
const receipts = readReceipts(sb.repo);
|
|
2375
2514
|
const manifest = readFileSync(manifestPath(sb.repo, 'r1-d2'), 'utf8');
|
|
2376
2515
|
rmSync(sb.home, { recursive: true, force: true });
|
|
@@ -2381,15 +2520,15 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2381
2520
|
assert.match(manifest, /other bytes/, 'the pre-existing manifest is never clobbered');
|
|
2382
2521
|
});
|
|
2383
2522
|
|
|
2384
|
-
it('a SYMLINK at the derived manifest path refuses and EXCLUDES the receipt — even when its target is byte-identical', () => {
|
|
2523
|
+
it('a SYMLINK at the derived manifest path refuses and EXCLUDES the receipt — even when its target is byte-identical', async () => {
|
|
2385
2524
|
const sb = makeSandbox();
|
|
2386
|
-
assert.equal(run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'sym2' } }).status, 0);
|
|
2525
|
+
assert.equal((await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'sym2' } })).status, 0);
|
|
2387
2526
|
const mPath = manifestPath(sb.repo, 'sym2');
|
|
2388
2527
|
const target = join(sb.repo, '.git', 'manifest-target-copy.json');
|
|
2389
2528
|
writeFileSync(target, readFileSync(mPath));
|
|
2390
2529
|
rmSync(mPath);
|
|
2391
2530
|
symlinkSync(target, mPath);
|
|
2392
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'sym2' } });
|
|
2531
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'sym2' } });
|
|
2393
2532
|
const receipts = readReceipts(sb.repo);
|
|
2394
2533
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2395
2534
|
assert.equal(r.status, 0, 'the review itself still succeeds (the artifact lane failed loudly)');
|
|
@@ -2397,11 +2536,11 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2397
2536
|
assert.equal(receipts.length, 1, 'the second receipt is EXCLUDED — a symlinked manifest is never read through as the idempotent no-op');
|
|
2398
2537
|
});
|
|
2399
2538
|
|
|
2400
|
-
it('a FIFO at the derived manifest path refuses fast and EXCLUDES the receipt (O_NONBLOCK — no hang; fix characterization)', () => {
|
|
2539
|
+
it('a FIFO at the derived manifest path refuses fast and EXCLUDES the receipt (O_NONBLOCK — no hang; fix characterization)', async () => {
|
|
2401
2540
|
const sb = makeSandbox();
|
|
2402
2541
|
const mPath = manifestPath(sb.repo, 'fifo2');
|
|
2403
2542
|
assert.equal(spawnSync('mkfifo', [mPath], { encoding: 'utf8' }).status, 0, 'mkfifo fixture');
|
|
2404
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'fifo2' } });
|
|
2543
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'fifo2' } });
|
|
2405
2544
|
const receipts = readReceipts(sb.repo);
|
|
2406
2545
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2407
2546
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2409,18 +2548,18 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2409
2548
|
assert.equal(receipts.length, 0, 'a FIFO manifest is never read (fstat-first) and the receipt is excluded');
|
|
2410
2549
|
});
|
|
2411
2550
|
|
|
2412
|
-
it('a BOM-prefixed captured output round-trips VERBATIM into the manifest (U+FEFF preserved)', () => {
|
|
2551
|
+
it('a BOM-prefixed captured output round-trips VERBATIM into the manifest (U+FEFF preserved)', async () => {
|
|
2413
2552
|
const sb = makeSandbox();
|
|
2414
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: `\uFEFFpreamble\n${VERDICT_OUTPUT}`, AW_REVIEW_NONCE: 'b2' } });
|
|
2553
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: `\uFEFFpreamble\n${VERDICT_OUTPUT}`, AW_REVIEW_NONCE: 'b2' } });
|
|
2415
2554
|
const manifest = JSON.parse(readFileSync(manifestPath(sb.repo, 'b2'), 'utf8'));
|
|
2416
2555
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2417
2556
|
assert.equal(r.status, 0, r.stderr);
|
|
2418
2557
|
assert.equal(manifest.findings, `\uFEFFpreamble\n${VERDICT_OUTPUT}\n`, 'the captured output is VERBATIM — a stripped BOM would move the findingDigest');
|
|
2419
2558
|
});
|
|
2420
2559
|
|
|
2421
|
-
it('an unsafe nonce refuses PRE-SPEND (exit 2, agy never runs) — a non-ASCII letter refuses under a UTF-8 locale too', () => {
|
|
2560
|
+
it('an unsafe nonce refuses PRE-SPEND (exit 2, agy never runs) — a non-ASCII letter refuses under a UTF-8 locale too', async () => {
|
|
2422
2561
|
const sb = makeSandbox();
|
|
2423
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AW_REVIEW_NONCE: 'a/b' } });
|
|
2562
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AW_REVIEW_NONCE: 'a/b' } });
|
|
2424
2563
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2425
2564
|
assert.equal(r.status, 2);
|
|
2426
2565
|
assert.match(r.stderr, /safe nonce grammar/);
|
|
@@ -2428,15 +2567,15 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2428
2567
|
// The grammar ENUMERATES the ASCII set (no ranges): a locale-collated [A-Za-z] could admit a
|
|
2429
2568
|
// non-ASCII letter the kit's JS reader then refuses, breaking correlation after a paid run.
|
|
2430
2569
|
const utf8 = makeSandbox();
|
|
2431
|
-
const r2 = run(utf8, { args: ['code', '--facts', 'a tiny fact'], env: { AW_REVIEW_NONCE: 'r\u00e91', LC_ALL: 'en_US.UTF-8', LANG: 'en_US.UTF-8' } });
|
|
2570
|
+
const r2 = await run(utf8, { args: ['code', '--facts', 'a tiny fact'], env: { AW_REVIEW_NONCE: 'r\u00e91', LC_ALL: 'en_US.UTF-8', LANG: 'en_US.UTF-8' } });
|
|
2432
2571
|
rmSync(utf8.home, { recursive: true, force: true });
|
|
2433
2572
|
assert.equal(r2.status, 2, 'a non-ASCII nonce letter refuses whatever the locale collation says');
|
|
2434
2573
|
assert.match(r2.stderr, /safe nonce grammar/);
|
|
2435
2574
|
});
|
|
2436
2575
|
|
|
2437
|
-
it('a nonce-supplied CONTINUATION mints its manifest with fingerprint null (the receipt identity is null too)', () => {
|
|
2576
|
+
it('a nonce-supplied CONTINUATION mints its manifest with fingerprint null (the receipt identity is null too)', async () => {
|
|
2438
2577
|
const sb = makeSandbox();
|
|
2439
|
-
const r = run(sb, { args: ['--continue'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'r2-c1' } });
|
|
2578
|
+
const r = await run(sb, { args: ['--continue'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'r2-c1' } });
|
|
2440
2579
|
const receipts = readReceipts(sb.repo);
|
|
2441
2580
|
const manifest = JSON.parse(readFileSync(manifestPath(sb.repo, 'r2-c1'), 'utf8'));
|
|
2442
2581
|
rmSync(sb.home, { recursive: true, force: true });
|
|
@@ -2448,9 +2587,9 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2448
2587
|
|
|
2449
2588
|
// The --nonce flag (FLOW-NONCE-DISPATCH-LANE): the plain-argument lane onto the SAME seam —
|
|
2450
2589
|
// for hosts whose dispatch policy has no env-prefix form.
|
|
2451
|
-
it('--nonce rides the AW_REVIEW_NONCE seam: manifest minted, receipt nonce-stamped (flag form ≡ env form)', () => {
|
|
2590
|
+
it('--nonce rides the AW_REVIEW_NONCE seam: manifest minted, receipt nonce-stamped (flag form ≡ env form)', async () => {
|
|
2452
2591
|
const sb = makeSandbox();
|
|
2453
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', 'f2-d2'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2592
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', 'f2-d2'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2454
2593
|
const receipts = readReceipts(sb.repo);
|
|
2455
2594
|
const manifest = JSON.parse(readFileSync(manifestPath(sb.repo, 'f2-d2'), 'utf8'));
|
|
2456
2595
|
rmSync(sb.home, { recursive: true, force: true });
|
|
@@ -2459,45 +2598,45 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2459
2598
|
assert.equal(receipts[0].nonce, 'f2-d2', 'the flag stamps the receipt exactly like the env form');
|
|
2460
2599
|
});
|
|
2461
2600
|
|
|
2462
|
-
it('an unsafe --nonce value refuses PRE-SPEND (exit 2, agy never runs) — same grammar as the env screen', () => {
|
|
2601
|
+
it('an unsafe --nonce value refuses PRE-SPEND (exit 2, agy never runs) — same grammar as the env screen', async () => {
|
|
2463
2602
|
const sb = makeSandbox();
|
|
2464
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', 'a/b'] });
|
|
2603
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', 'a/b'] });
|
|
2465
2604
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2466
2605
|
assert.equal(r.status, 2);
|
|
2467
2606
|
assert.match(r.stderr, /safe nonce grammar/);
|
|
2468
2607
|
assert.equal(r.invoked, false, 'the containment refusal fires before any CLI spend');
|
|
2469
2608
|
});
|
|
2470
2609
|
|
|
2471
|
-
it('a missing value, a duplicate flag, and a disagreeing env+flag pair each refuse (exit 2); an agreeing pair proceeds', () => {
|
|
2610
|
+
it('a missing value, a duplicate flag, and a disagreeing env+flag pair each refuse (exit 2); an agreeing pair proceeds', async () => {
|
|
2472
2611
|
const sb = makeSandbox();
|
|
2473
|
-
const missing = run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce'] });
|
|
2612
|
+
const missing = await run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce'] });
|
|
2474
2613
|
assert.equal(missing.status, 2);
|
|
2475
2614
|
assert.match(missing.stderr, /--nonce needs a value/);
|
|
2476
|
-
const dup = run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', 'n1', '--nonce', 'n2'] });
|
|
2615
|
+
const dup = await run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', 'n1', '--nonce', 'n2'] });
|
|
2477
2616
|
assert.equal(dup.status, 2);
|
|
2478
2617
|
assert.match(dup.stderr, /duplicate --nonce/);
|
|
2479
|
-
const clash = run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', 'n1'], env: { AW_REVIEW_NONCE: 'n2' } });
|
|
2618
|
+
const clash = await run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', 'n1'], env: { AW_REVIEW_NONCE: 'n2' } });
|
|
2480
2619
|
assert.equal(clash.status, 2);
|
|
2481
2620
|
assert.match(clash.stderr, /disagrees with the AW_REVIEW_NONCE environment value/);
|
|
2482
|
-
const agree = run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', 'n3'], env: { AW_REVIEW_NONCE: 'n3', AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2621
|
+
const agree = await run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', 'n3'], env: { AW_REVIEW_NONCE: 'n3', AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2483
2622
|
const exists = existsSync(manifestPath(sb.repo, 'n3'));
|
|
2484
2623
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2485
2624
|
assert.equal(agree.status, 0, agree.stderr);
|
|
2486
2625
|
assert.equal(exists, true, 'an agreeing pair is ONE seam value — the dispatch proceeds');
|
|
2487
2626
|
});
|
|
2488
2627
|
|
|
2489
|
-
it('--nonce is valid on a CONTINUATION too — the flag lane covers every seam-honoring form', () => {
|
|
2628
|
+
it('--nonce is valid on a CONTINUATION too — the flag lane covers every seam-honoring form', async () => {
|
|
2490
2629
|
const sb = makeSandbox();
|
|
2491
|
-
const r = run(sb, { args: ['--continue', '--nonce', 'r2-c2'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2630
|
+
const r = await run(sb, { args: ['--continue', '--nonce', 'r2-c2'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2492
2631
|
const manifest = JSON.parse(readFileSync(manifestPath(sb.repo, 'r2-c2'), 'utf8'));
|
|
2493
2632
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2494
2633
|
assert.equal(r.status, 0, r.stderr);
|
|
2495
2634
|
assert.equal(manifest.fingerprint, null, 'a continuation manifest carries no tree identity');
|
|
2496
2635
|
});
|
|
2497
2636
|
|
|
2498
|
-
it('a grammar-valid leading-dash --nonce value is ACCEPTED — the flag lane spans the whole declared grammar', () => {
|
|
2637
|
+
it('a grammar-valid leading-dash --nonce value is ACCEPTED — the flag lane spans the whole declared grammar', async () => {
|
|
2499
2638
|
const sb = makeSandbox();
|
|
2500
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', '--n1'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2639
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', '--n1'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2501
2640
|
const receipts = readReceipts(sb.repo);
|
|
2502
2641
|
const exists = existsSync(manifestPath(sb.repo, '--n1'));
|
|
2503
2642
|
rmSync(sb.home, { recursive: true, force: true });
|
|
@@ -2506,18 +2645,18 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2506
2645
|
assert.equal(receipts[0].nonce, '--n1', 'the receipt carries the exact grammar-valid value — flag lane ≡ env lane');
|
|
2507
2646
|
});
|
|
2508
2647
|
|
|
2509
|
-
it('an EMPTY --nonce value refuses pre-spend under the grammar screen (presence is tracked separately from the value)', () => {
|
|
2648
|
+
it('an EMPTY --nonce value refuses pre-spend under the grammar screen (presence is tracked separately from the value)', async () => {
|
|
2510
2649
|
const sb = makeSandbox();
|
|
2511
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', ''] });
|
|
2650
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', ''] });
|
|
2512
2651
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2513
2652
|
assert.equal(r.status, 2);
|
|
2514
2653
|
assert.match(r.stderr, /safe nonce grammar/);
|
|
2515
2654
|
assert.equal(r.invoked, false, 'the refusal fires before any CLI spend');
|
|
2516
2655
|
});
|
|
2517
2656
|
|
|
2518
|
-
it('a duplicate --nonce after an EMPTY first value still refuses as a duplicate — an empty value never erases presence', () => {
|
|
2657
|
+
it('a duplicate --nonce after an EMPTY first value still refuses as a duplicate — an empty value never erases presence', async () => {
|
|
2519
2658
|
const sb = makeSandbox();
|
|
2520
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', '', '--nonce', 'n2'] });
|
|
2659
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact', '--nonce', '', '--nonce', 'n2'] });
|
|
2521
2660
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2522
2661
|
assert.equal(r.status, 2);
|
|
2523
2662
|
assert.match(r.stderr, /duplicate --nonce/);
|
|
@@ -2525,9 +2664,9 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2525
2664
|
});
|
|
2526
2665
|
});
|
|
2527
2666
|
|
|
2528
|
-
it('a continuation receipt is fresh:false with null identity fields, and the wrapper prints the fresh-run notice', () => {
|
|
2667
|
+
it('a continuation receipt is fresh:false with null identity fields, and the wrapper prints the fresh-run notice', async () => {
|
|
2529
2668
|
const sb = makeSandbox();
|
|
2530
|
-
const r = run(sb, { args: ['--continue', '--decided', 'already folded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2669
|
+
const r = await run(sb, { args: ['--continue', '--decided', 'already folded'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2531
2670
|
const receipts = readReceipts(sb.repo);
|
|
2532
2671
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2533
2672
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2549,7 +2688,7 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2549
2688
|
it('plan mode: artifact "plan", fingerprint = the artifact-file sha256', async () => {
|
|
2550
2689
|
const sb = makeSandbox();
|
|
2551
2690
|
writeFileSync(join(sb.repo, 'p.md'), '# plan body\n');
|
|
2552
|
-
const r = run(sb, { args: ['plan', 'p.md', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2691
|
+
const r = await run(sb, { args: ['plan', 'p.md', '--facts', 'f'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2553
2692
|
const receipts = readReceipts(sb.repo);
|
|
2554
2693
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2555
2694
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2557,18 +2696,18 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2557
2696
|
assert.equal(receipts[0].fingerprint, await sha256HexOf('# plan body\n'), 'plan fingerprint = file sha256');
|
|
2558
2697
|
});
|
|
2559
2698
|
|
|
2560
|
-
it('plan/diff outside a git work tree: warn + skip the receipt (exit 0) unless AW_REVIEW_RECEIPTS is set', () => {
|
|
2699
|
+
it('plan/diff outside a git work tree: warn + skip the receipt (exit 0) unless AW_REVIEW_RECEIPTS is set', async () => {
|
|
2561
2700
|
const sb = makeSandbox();
|
|
2562
2701
|
const outside = join(sb.home, 'no-repo');
|
|
2563
2702
|
mkdirSync(outside, { recursive: true });
|
|
2564
2703
|
writeFileSync(join(outside, 'p.md'), '# plan outside git\n');
|
|
2565
2704
|
|
|
2566
|
-
const skipped = run(sb, { args: ['plan', 'p.md', '--facts', 'f'], cwd: outside, env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2705
|
+
const skipped = await run(sb, { args: ['plan', 'p.md', '--facts', 'f'], cwd: outside, env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT } });
|
|
2567
2706
|
assert.equal(skipped.status, 0, skipped.stderr);
|
|
2568
2707
|
assert.match(skipped.stderr, /not inside a git work tree and AW_REVIEW_RECEIPTS is unset — skipping/);
|
|
2569
2708
|
|
|
2570
2709
|
const override = join(sb.home, 'receipts-override.jsonl');
|
|
2571
|
-
const written = run(sb, {
|
|
2710
|
+
const written = await run(sb, {
|
|
2572
2711
|
args: ['plan', 'p.md', '--facts', 'f'],
|
|
2573
2712
|
cwd: outside,
|
|
2574
2713
|
env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_RECEIPTS: override },
|
|
@@ -2580,9 +2719,9 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2580
2719
|
assert.match(body, /"artifact":"plan"/);
|
|
2581
2720
|
});
|
|
2582
2721
|
|
|
2583
|
-
it('a receipt write failure warns loudly but never fails the review (fail-safe direction)', () => {
|
|
2722
|
+
it('a receipt write failure warns loudly but never fails the review (fail-safe direction)', async () => {
|
|
2584
2723
|
const sb = makeSandbox();
|
|
2585
|
-
const r = run(sb, {
|
|
2724
|
+
const r = await run(sb, {
|
|
2586
2725
|
args: ['code', '--facts', 'f'],
|
|
2587
2726
|
env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_RECEIPTS: join(sb.home, 'no-such-dir', 'r.jsonl') },
|
|
2588
2727
|
});
|
|
@@ -2592,18 +2731,18 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2592
2731
|
assert.match(r.stdout, /SHIP WITH NITS/, 'the findings still reach stdout');
|
|
2593
2732
|
});
|
|
2594
2733
|
|
|
2595
|
-
it('a failed agy run writes NO receipt (only a successful review attests)', () => {
|
|
2734
|
+
it('a failed agy run writes NO receipt (only a successful review attests)', async () => {
|
|
2596
2735
|
const sb = makeSandbox();
|
|
2597
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_EXIT: '7' } });
|
|
2736
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_FAKE_EXIT: '7' } });
|
|
2598
2737
|
const receipts = readReceipts(sb.repo);
|
|
2599
2738
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2600
2739
|
assert.notEqual(r.status, 0);
|
|
2601
2740
|
assert.equal(receipts.length, 0);
|
|
2602
2741
|
});
|
|
2603
2742
|
|
|
2604
|
-
it('the clean-tree preflight exits before any receipt is written', () => {
|
|
2743
|
+
it('the clean-tree preflight exits before any receipt is written', async () => {
|
|
2605
2744
|
const sb = makeSandbox({ clean: true });
|
|
2606
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
2745
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
2607
2746
|
const receipts = readReceipts(sb.repo);
|
|
2608
2747
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2609
2748
|
assert.equal(r.status, 0);
|
|
@@ -2614,7 +2753,7 @@ describe('agy-review.sh — review receipts (AD-038)', () => {
|
|
|
2614
2753
|
// MANIFEST-TMP-ORPHAN-ON-FAILURE: on a FAILURE exit of the finding-manifest mint whose temp
|
|
2615
2754
|
// unlink ALSO fails, the error names the orphan path — parameterized over BOTH failure codes
|
|
2616
2755
|
// (rc 3 no-clobber, rc 1 fs failure); a failure whose temp IS removable stays orphan-silent.
|
|
2617
|
-
describe('agy-review.sh — finding-manifest failure branches name the orphan (MANIFEST-TMP-ORPHAN-ON-FAILURE)', () => {
|
|
2756
|
+
describe('agy-review.sh — finding-manifest failure branches name the orphan (MANIFEST-TMP-ORPHAN-ON-FAILURE)', { concurrency: 2 }, () => {
|
|
2618
2757
|
const manifestPath = (repo, nonce) => join(repo, '.git', `agent-workflow-finding-manifest-agy-${nonce}.json`);
|
|
2619
2758
|
const UNLINK_FAIL = "const fs = require('node:fs');\nconst real = fs.unlinkSync;\nfs.unlinkSync = (p) => { if (String(p).includes('.tmp')) { const e = new Error('EPERM'); e.code = 'EPERM'; throw e; } return real(p); };\n";
|
|
2620
2759
|
const LINK_FAIL = "const fsLink = require('node:fs');\nfsLink.linkSync = () => { const e = new Error('EPERM'); e.code = 'EPERM'; throw e; };\n";
|
|
@@ -2622,12 +2761,12 @@ describe('agy-review.sh — finding-manifest failure branches name the orphan (M
|
|
|
2622
2761
|
{ rc: 3, name: 'no-clobber (rc 3)', preload: UNLINK_FAIL, plant: true, errRe: /DIFFERENT bytes or is not a regular file/ },
|
|
2623
2762
|
{ rc: 1, name: 'fs failure (rc 1)', preload: UNLINK_FAIL + LINK_FAIL, plant: false, errRe: /could not compose or write the finding manifest/ },
|
|
2624
2763
|
]) {
|
|
2625
|
-
it(`a FAILURE exit (${failure.name}) whose temp unlink also fails names the ORPHAN PATH in the error`, () => {
|
|
2764
|
+
it(`a FAILURE exit (${failure.name}) whose temp unlink also fails names the ORPHAN PATH in the error`, async () => {
|
|
2626
2765
|
const sb = makeSandbox();
|
|
2627
2766
|
if (failure.plant) writeFileSync(manifestPath(sb.repo, 'orphf1'), 'planted different bytes\n');
|
|
2628
2767
|
const preload = join(sb.home, 'orphan-fail-preload.cjs');
|
|
2629
2768
|
writeFileSync(preload, failure.preload);
|
|
2630
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'orphf1', NODE_OPTIONS: `--require ${preload}` } });
|
|
2769
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'orphf1', NODE_OPTIONS: `--require ${preload}` } });
|
|
2631
2770
|
const receipts = readReceipts(sb.repo);
|
|
2632
2771
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2633
2772
|
assert.equal(r.status, 0, 'the review itself still succeeds (the artifact lane failed loudly)');
|
|
@@ -2637,10 +2776,10 @@ describe('agy-review.sh — finding-manifest failure branches name the orphan (M
|
|
|
2637
2776
|
});
|
|
2638
2777
|
}
|
|
2639
2778
|
|
|
2640
|
-
it('a FAILURE exit whose temp IS removable stays orphan-silent (no leftover, no orphan line)', () => {
|
|
2779
|
+
it('a FAILURE exit whose temp IS removable stays orphan-silent (no leftover, no orphan line)', async () => {
|
|
2641
2780
|
const sb = makeSandbox();
|
|
2642
2781
|
writeFileSync(manifestPath(sb.repo, 'orphf2'), 'planted different bytes\n');
|
|
2643
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'orphf2' } });
|
|
2782
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: VERDICT_OUTPUT, AW_REVIEW_NONCE: 'orphf2' } });
|
|
2644
2783
|
const leftovers = readdirSync(join(sb.repo, '.git')).filter((n) => n.endsWith('.tmp'));
|
|
2645
2784
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2646
2785
|
assert.equal(r.status, 0, 'the review itself still succeeds (the artifact lane failed loudly)');
|
|
@@ -2666,12 +2805,12 @@ const writeSettings = (sb, text) => {
|
|
|
2666
2805
|
};
|
|
2667
2806
|
const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
|
|
2668
2807
|
|
|
2669
|
-
describe('agy-review.sh — bridge settings file (bridges 2.3.0)', { concurrency:
|
|
2670
|
-
it('a file-set AGY_REVIEW_ALLOW_ADDDIR=1 arms nothing and states its retirement', () => {
|
|
2808
|
+
describe('agy-review.sh — bridge settings file (bridges 2.3.0)', { concurrency: 2 }, () => {
|
|
2809
|
+
it('a file-set AGY_REVIEW_ALLOW_ADDDIR=1 arms nothing and states its retirement', async () => {
|
|
2671
2810
|
const sb = makeSandbox();
|
|
2672
2811
|
seedFedChangeSet(sb);
|
|
2673
2812
|
writeSettings(sb, 'AGY_REVIEW_ALLOW_ADDDIR=1\n');
|
|
2674
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP) } });
|
|
2813
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP) } });
|
|
2675
2814
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2676
2815
|
assert.equal(r.status, 0, r.stderr);
|
|
2677
2816
|
assert.match(r.stderr, /AGY_REVIEW_ALLOW_ADDDIR is set \(file\) but it is RETIRED/, 'a FILE-set value is named as such, not as an env override');
|
|
@@ -2681,32 +2820,32 @@ describe('agy-review.sh — bridge settings file (bridges 2.3.0)', { concurrency
|
|
|
2681
2820
|
|
|
2682
2821
|
// With the knob DISARMED an over-cap code review is no longer a refusal — it is the fed lane. So
|
|
2683
2822
|
// "env wins over file" is now proven by which LANE runs, not by which error prints.
|
|
2684
|
-
it('env overrides file: AGY_REVIEW_ALLOW_ADDDIR env=0 file=1 → the offload stays disarmed and the fed lane runs', () => {
|
|
2823
|
+
it('env overrides file: AGY_REVIEW_ALLOW_ADDDIR env=0 file=1 → the offload stays disarmed and the fed lane runs', async () => {
|
|
2685
2824
|
const sb = makeSandbox();
|
|
2686
2825
|
seedFedChangeSet(sb);
|
|
2687
2826
|
writeSettings(sb, 'AGY_REVIEW_ALLOW_ADDDIR=1\n');
|
|
2688
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP), AGY_REVIEW_ALLOW_ADDDIR: '0' } });
|
|
2827
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP), AGY_REVIEW_ALLOW_ADDDIR: '0' } });
|
|
2689
2828
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2690
2829
|
assert.equal(r.status, 0, r.stderr);
|
|
2691
2830
|
assert.ok(!r.argv.includes('--add-dir'), 'the file-set knob is overridden — no offload');
|
|
2692
2831
|
assert.match(r.stderr, /feeding the change set in \d+ part\(s\)/);
|
|
2693
2832
|
});
|
|
2694
2833
|
|
|
2695
|
-
it('an EXPLICITLY EMPTY env (AGY_REVIEW_ALLOW_ADDDIR=) disables the file knob', () => {
|
|
2834
|
+
it('an EXPLICITLY EMPTY env (AGY_REVIEW_ALLOW_ADDDIR=) disables the file knob', async () => {
|
|
2696
2835
|
const sb = makeSandbox();
|
|
2697
2836
|
seedFedChangeSet(sb);
|
|
2698
2837
|
writeSettings(sb, 'AGY_REVIEW_ALLOW_ADDDIR=1\n');
|
|
2699
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP), AGY_REVIEW_ALLOW_ADDDIR: '' } });
|
|
2838
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP), AGY_REVIEW_ALLOW_ADDDIR: '' } });
|
|
2700
2839
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2701
2840
|
assert.equal(r.status, 0, 'env wins over file — empty means knob off (built-in default 0)');
|
|
2702
2841
|
assert.ok(!r.argv.includes('--add-dir'));
|
|
2703
2842
|
});
|
|
2704
2843
|
|
|
2705
|
-
it('an invalid boolean warns and falls back to the built-in default (the offload stays disarmed)', () => {
|
|
2844
|
+
it('an invalid boolean warns and falls back to the built-in default (the offload stays disarmed)', async () => {
|
|
2706
2845
|
const sb = makeSandbox();
|
|
2707
2846
|
seedFedChangeSet(sb);
|
|
2708
2847
|
writeSettings(sb, 'AGY_REVIEW_ALLOW_ADDDIR=yes\n');
|
|
2709
|
-
const r = run(sb, { args: ['code', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP) } });
|
|
2848
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'], env: { AGY_MAX_PROMPT_BYTES: String(FED_CAP) } });
|
|
2710
2849
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2711
2850
|
assert.equal(r.status, 0, r.stderr);
|
|
2712
2851
|
assert.match(r.stderr, /invalid value 'yes'/);
|
|
@@ -2722,20 +2861,20 @@ describe('agy-review.sh — bridge settings file (bridges 2.3.0)', { concurrency
|
|
|
2722
2861
|
assert.match(r.stderr, /exceeded the hard cap AGY_HARD_TIMEOUT=2s/);
|
|
2723
2862
|
});
|
|
2724
2863
|
|
|
2725
|
-
it("another bridge's valid key is skipped silently", () => {
|
|
2864
|
+
it("another bridge's valid key is skipped silently", async () => {
|
|
2726
2865
|
const sb = makeSandbox();
|
|
2727
2866
|
writeSettings(sb, 'CODEX_SERVICE_TIER=priority\nCODEX_HARD_TIMEOUT=2\nCODEX_REVIEW_MAX_TOTAL_BYTES=100\n');
|
|
2728
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
2867
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
2729
2868
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2730
2869
|
assert.equal(r.status, 0, r.stderr);
|
|
2731
2870
|
assert.doesNotMatch(r.stderr, /bridge settings/, 'a recognized non-applied key earns NO warning');
|
|
2732
2871
|
assert.equal(r.invoked, true);
|
|
2733
2872
|
});
|
|
2734
2873
|
|
|
2735
|
-
it('a truly unknown key warns ONCE naming the file; the review is unaffected', () => {
|
|
2874
|
+
it('a truly unknown key warns ONCE naming the file; the review is unaffected', async () => {
|
|
2736
2875
|
const sb = makeSandbox();
|
|
2737
2876
|
writeSettings(sb, 'TOTALLY_UNKNOWN=1\nTOTALLY_UNKNOWN=2\n');
|
|
2738
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
2877
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
2739
2878
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2740
2879
|
assert.equal(r.status, 0, r.stderr);
|
|
2741
2880
|
const warns = r.stderr.match(/unknown key 'TOTALLY_UNKNOWN'/g) ?? [];
|
|
@@ -2744,10 +2883,10 @@ describe('agy-review.sh — bridge settings file (bridges 2.3.0)', { concurrency
|
|
|
2744
2883
|
assert.equal(r.invoked, true);
|
|
2745
2884
|
});
|
|
2746
2885
|
|
|
2747
|
-
it('malformed lines warn and are ignored; comments and blank lines are silent', () => {
|
|
2886
|
+
it('malformed lines warn and are ignored; comments and blank lines are silent', async () => {
|
|
2748
2887
|
const sb = makeSandbox();
|
|
2749
2888
|
writeSettings(sb, '# a comment\n\nNOT A KEY VALUE LINE\n');
|
|
2750
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
2889
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
2751
2890
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2752
2891
|
assert.equal(r.status, 0, r.stderr);
|
|
2753
2892
|
const malformed = r.stderr.match(/malformed line/g) ?? [];
|
|
@@ -2755,22 +2894,22 @@ describe('agy-review.sh — bridge settings file (bridges 2.3.0)', { concurrency
|
|
|
2755
2894
|
assert.equal(r.invoked, true);
|
|
2756
2895
|
});
|
|
2757
2896
|
|
|
2758
|
-
it('an existing-but-unreadable file warns loudly and falls back to built-ins', { skip: isRoot }, () => {
|
|
2897
|
+
it('an existing-but-unreadable file warns loudly and falls back to built-ins', { skip: isRoot }, async () => {
|
|
2759
2898
|
const sb = makeSandbox();
|
|
2760
2899
|
const file = writeSettings(sb, 'AGY_REVIEW_ALLOW_ADDDIR=1\n');
|
|
2761
2900
|
chmodSync(file, 0o000);
|
|
2762
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
2901
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
2763
2902
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2764
2903
|
assert.equal(r.status, 0, r.stderr);
|
|
2765
2904
|
assert.match(r.stderr, /unreadable/);
|
|
2766
2905
|
assert.equal(r.invoked, true);
|
|
2767
2906
|
});
|
|
2768
2907
|
|
|
2769
|
-
it('a settings line can NEVER execute code (command-substitution payload inert)', () => {
|
|
2908
|
+
it('a settings line can NEVER execute code (command-substitution payload inert)', async () => {
|
|
2770
2909
|
const sb = makeSandbox();
|
|
2771
2910
|
const pwned = join(sb.home, 'pwned');
|
|
2772
2911
|
writeSettings(sb, `AGY_HARD_TIMEOUT=$(touch ${pwned})\nEVIL_KEY=\`touch ${pwned}2\`\n`);
|
|
2773
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
2912
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
2774
2913
|
const executed = existsSync(pwned) || existsSync(`${pwned}2`);
|
|
2775
2914
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2776
2915
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2778,19 +2917,19 @@ describe('agy-review.sh — bridge settings file (bridges 2.3.0)', { concurrency
|
|
|
2778
2917
|
assert.equal(r.invoked, true);
|
|
2779
2918
|
});
|
|
2780
2919
|
|
|
2781
|
-
it('no file → byte-identical behaviour to today (no settings chatter)', () => {
|
|
2920
|
+
it('no file → byte-identical behaviour to today (no settings chatter)', async () => {
|
|
2782
2921
|
const sb = makeSandbox();
|
|
2783
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
2922
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
2784
2923
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2785
2924
|
assert.equal(r.status, 0, r.stderr);
|
|
2786
2925
|
assert.doesNotMatch(r.stderr, /bridge settings/);
|
|
2787
2926
|
assert.equal(r.invoked, true);
|
|
2788
2927
|
});
|
|
2789
2928
|
|
|
2790
|
-
it('a DIRECTORY at the settings path warns loudly and falls back to built-ins (no crash)', () => {
|
|
2929
|
+
it('a DIRECTORY at the settings path warns loudly and falls back to built-ins (no crash)', async () => {
|
|
2791
2930
|
const sb = makeSandbox();
|
|
2792
2931
|
mkdirSync(join(sb.home, '.config', 'agent-workflow', 'bridge-settings.conf'), { recursive: true });
|
|
2793
|
-
const r = run(sb, { args: ['code', '--facts', 'f'] });
|
|
2932
|
+
const r = await run(sb, { args: ['code', '--facts', 'f'] });
|
|
2794
2933
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2795
2934
|
assert.equal(r.status, 0, `a directory must degrade honestly, not kill the run: ${r.stderr}`);
|
|
2796
2935
|
assert.match(r.stderr, /unreadable or not a regular file/);
|
|
@@ -2805,8 +2944,8 @@ const SIBLING_MANIFEST = JSON.parse(readFileSync(join(HERE, '..', '..', 'codex-c
|
|
|
2805
2944
|
const ALL_SETTINGS = [...(MANIFEST.settings ?? []), ...(SIBLING_MANIFEST.settings ?? [])];
|
|
2806
2945
|
const SETTINGS_CMD = 'agy-review';
|
|
2807
2946
|
|
|
2808
|
-
describe('agy-review.sh — settings surface ⟷ manifest (D6, manifest-pinned)', () => {
|
|
2809
|
-
it('--help Settings section keys set-EQUAL the manifest appliesTo subset', () => {
|
|
2947
|
+
describe('agy-review.sh — settings surface ⟷ manifest (D6, manifest-pinned)', { concurrency: 2 }, () => {
|
|
2948
|
+
it('--help Settings section keys set-EQUAL the manifest appliesTo subset', async () => {
|
|
2810
2949
|
const help = runHelp('--help').stdout;
|
|
2811
2950
|
const section = helpSection(help, SETTINGS_HEADER);
|
|
2812
2951
|
const got = section.filter((l) => /^[A-Z][A-Z0-9_]+ —/.test(l)).map((l) => l.split(' ')[0]);
|
|
@@ -2818,14 +2957,14 @@ describe('agy-review.sh — settings surface ⟷ manifest (D6, manifest-pinned)'
|
|
|
2818
2957
|
|
|
2819
2958
|
const source = readFileSync(WRAPPER, 'utf8');
|
|
2820
2959
|
|
|
2821
|
-
it('aw_settings_known carries exactly the UNION of both bridges settings keys', () => {
|
|
2960
|
+
it('aw_settings_known carries exactly the UNION of both bridges settings keys', async () => {
|
|
2822
2961
|
const m = source.match(/aw_settings_known\(\) \{\n case " ([^"]+) " in/);
|
|
2823
2962
|
assert.ok(m, 'aw_settings_known registry case not found');
|
|
2824
2963
|
assert.ok(ALL_SETTINGS.length >= 5, 'both manifests must contribute settings');
|
|
2825
2964
|
setEq(m[1].trim().split(/\s+/), ALL_SETTINGS.map((s) => s.key), 'shell registry ⟷ manifest union');
|
|
2826
2965
|
});
|
|
2827
2966
|
|
|
2828
|
-
it('AW_SETTINGS_APPLIED equals the manifest appliesTo subset for this wrapper', () => {
|
|
2967
|
+
it('AW_SETTINGS_APPLIED equals the manifest appliesTo subset for this wrapper', async () => {
|
|
2829
2968
|
const m = source.match(/^AW_SETTINGS_APPLIED="([^"]*)"$/m);
|
|
2830
2969
|
assert.ok(m, 'AW_SETTINGS_APPLIED not found');
|
|
2831
2970
|
const want = ALL_SETTINGS.filter((s) => s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
|
|
@@ -2833,7 +2972,7 @@ describe('agy-review.sh — settings surface ⟷ manifest (D6, manifest-pinned)'
|
|
|
2833
2972
|
setEq(m[1].trim().split(/\s+/), want, 'applied subset ⟷ manifest appliesTo');
|
|
2834
2973
|
});
|
|
2835
2974
|
|
|
2836
|
-
it('aw_settings_valid arms carry the manifest typed constants per key', () => {
|
|
2975
|
+
it('aw_settings_valid arms carry the manifest typed constants per key', async () => {
|
|
2837
2976
|
const body = source.match(/aw_settings_valid\(\) \{[\s\S]*?\n\}/);
|
|
2838
2977
|
assert.ok(body, 'aw_settings_valid not found');
|
|
2839
2978
|
const armKeys = [...body[0].matchAll(/^ ([A-Z][A-Z0-9_]*)\)/gm)].map((x) => x[1]);
|
|
@@ -2857,10 +2996,10 @@ describe('agy-review.sh — settings surface ⟷ manifest (D6, manifest-pinned)'
|
|
|
2857
2996
|
});
|
|
2858
2997
|
|
|
2859
2998
|
// ── strip-the-kit Phase 4: wrapper honesty (D4) + dispatch-posture labeling (D5) ────────────────
|
|
2860
|
-
describe('agy-review.sh — wrapper honesty: a verdict-less run is a FAILED review (D4)', () => {
|
|
2861
|
-
it('a VERDICT-LESS review output: non-zero exit, NO receipt, the stated re-run recovery', () => {
|
|
2999
|
+
describe('agy-review.sh — wrapper honesty: a verdict-less run is a FAILED review (D4)', { concurrency: 2 }, () => {
|
|
3000
|
+
it('a VERDICT-LESS review output: non-zero exit, NO receipt, the stated re-run recovery', async () => {
|
|
2862
3001
|
const sb = makeSandbox();
|
|
2863
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: 'prose without the mandated section' } });
|
|
3002
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: 'prose without the mandated section' } });
|
|
2864
3003
|
const receipts = readReceipts(sb.repo);
|
|
2865
3004
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2866
3005
|
assert.notEqual(r.status, 0, 'a verdict-less review never exits 0');
|
|
@@ -2869,19 +3008,19 @@ describe('agy-review.sh — wrapper honesty: a verdict-less run is a FAILED revi
|
|
|
2869
3008
|
assert.match(r.stderr, /re-run/i, 'documented as a failed review — re-run, never fatal');
|
|
2870
3009
|
});
|
|
2871
3010
|
|
|
2872
|
-
it('EMPTY review output is the same failed run (non-zero, no receipt)', () => {
|
|
3011
|
+
it('EMPTY review output is the same failed run (non-zero, no receipt)', async () => {
|
|
2873
3012
|
const sb = makeSandbox();
|
|
2874
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: '' } });
|
|
3013
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: '' } });
|
|
2875
3014
|
const receipts = readReceipts(sb.repo);
|
|
2876
3015
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2877
3016
|
assert.notEqual(r.status, 0);
|
|
2878
3017
|
assert.equal(receipts.length, 0);
|
|
2879
3018
|
});
|
|
2880
3019
|
|
|
2881
|
-
it('the closed vocabulary still parses (SHIP WITH NITS before SHIP; REWORK) and a recognized run exits 0', () => {
|
|
3020
|
+
it('the closed vocabulary still parses (SHIP WITH NITS before SHIP; REWORK) and a recognized run exits 0', async () => {
|
|
2882
3021
|
for (const [out, want] of [[VERDICT_OUTPUT, 'SHIP WITH NITS'], ['### Verdict\nREWORK — reasons.\n', 'REWORK']]) {
|
|
2883
3022
|
const sb = makeSandbox();
|
|
2884
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: out } });
|
|
3023
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_FAKE_OUTPUT: out } });
|
|
2885
3024
|
const receipts = readReceipts(sb.repo);
|
|
2886
3025
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2887
3026
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2890,10 +3029,10 @@ describe('agy-review.sh — wrapper honesty: a verdict-less run is a FAILED revi
|
|
|
2890
3029
|
});
|
|
2891
3030
|
});
|
|
2892
3031
|
|
|
2893
|
-
describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
2894
|
-
it('ONE banner line carries the ACTUAL model and the receipt carries the SAME posture (agy has no tier)', () => {
|
|
3032
|
+
describe('agy-review.sh — dispatch-posture labeling (D5)', { concurrency: 2 }, () => {
|
|
3033
|
+
it('ONE banner line carries the ACTUAL model and the receipt carries the SAME posture (agy has no tier)', async () => {
|
|
2895
3034
|
const sb = makeSandbox();
|
|
2896
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'] });
|
|
3035
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'] });
|
|
2897
3036
|
const receipts = readReceipts(sb.repo);
|
|
2898
3037
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2899
3038
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2902,9 +3041,9 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
2902
3041
|
assert.deepEqual(Object.keys(receipts[0]), Object.keys(RECEIPT_FIXTURE), 'fixture key set + order');
|
|
2903
3042
|
});
|
|
2904
3043
|
|
|
2905
|
-
it('an ATTESTING review with AGY_MODEL explicitly emptied REFUSES pre-spend naming the fix', () => {
|
|
3044
|
+
it('an ATTESTING review with AGY_MODEL explicitly emptied REFUSES pre-spend naming the fix', async () => {
|
|
2906
3045
|
const sb = makeSandbox();
|
|
2907
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: '' } });
|
|
3046
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: '' } });
|
|
2908
3047
|
const receipts = readReceipts(sb.repo);
|
|
2909
3048
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2910
3049
|
assert.notEqual(r.status, 0);
|
|
@@ -2913,9 +3052,9 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
2913
3052
|
assert.match(r.stderr, /AGY_MODEL/, 'the fix is named');
|
|
2914
3053
|
});
|
|
2915
3054
|
|
|
2916
|
-
it('AGY_PROBE=1 with AGY_MODEL emptied still runs (probe exempt; posture model null on the probe receipt)', () => {
|
|
3055
|
+
it('AGY_PROBE=1 with AGY_MODEL emptied still runs (probe exempt; posture model null on the probe receipt)', async () => {
|
|
2917
3056
|
const sb = makeSandbox();
|
|
2918
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: '', AGY_PROBE: '1' } });
|
|
3057
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: '', AGY_PROBE: '1' } });
|
|
2919
3058
|
const receipts = readReceipts(sb.repo);
|
|
2920
3059
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2921
3060
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2923,10 +3062,10 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
2923
3062
|
assert.deepEqual(receipts[0].posture, { model: null }, 'an unknowable model is recorded null, never guessed');
|
|
2924
3063
|
});
|
|
2925
3064
|
|
|
2926
|
-
it('a HOSTILE model string (quotes + backslash) rides the receipt strictly JSON-encoded', () => {
|
|
3065
|
+
it('a HOSTILE model string (quotes + backslash) rides the receipt strictly JSON-encoded', async () => {
|
|
2927
3066
|
const hostile = 'we"ird \\ mo"del';
|
|
2928
3067
|
const sb = makeSandbox();
|
|
2929
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: hostile } });
|
|
3068
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: hostile } });
|
|
2930
3069
|
const receipts = readReceipts(sb.repo); // JSON.parse throwing here IS the encoding failure
|
|
2931
3070
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2932
3071
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2934,9 +3073,9 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
2934
3073
|
assert.match(r.stderr, /review posture: /, 'the banner still renders');
|
|
2935
3074
|
});
|
|
2936
3075
|
|
|
2937
|
-
it('a model string carrying CONTROL BYTES refuses pre-spend (never a broken banner or receipt)', () => {
|
|
3076
|
+
it('a model string carrying CONTROL BYTES refuses pre-spend (never a broken banner or receipt)', async () => {
|
|
2938
3077
|
const sb = makeSandbox();
|
|
2939
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: `bad${String.fromCharCode(1)}model` } });
|
|
3078
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: `bad${String.fromCharCode(1)}model` } });
|
|
2940
3079
|
const receipts = readReceipts(sb.repo);
|
|
2941
3080
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2942
3081
|
assert.notEqual(r.status, 0);
|
|
@@ -2945,9 +3084,9 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
2945
3084
|
assert.match(r.stderr, /control/i);
|
|
2946
3085
|
});
|
|
2947
3086
|
|
|
2948
|
-
it('the banner appends the RESOLVED hard timeout verbatim — banner-only, never in the receipt (AD-061)', () => {
|
|
3087
|
+
it('the banner appends the RESOLVED hard timeout verbatim — banner-only, never in the receipt (AD-061)', async () => {
|
|
2949
3088
|
const sb = makeSandbox();
|
|
2950
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'] });
|
|
3089
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'] });
|
|
2951
3090
|
const receipts = readReceipts(sb.repo);
|
|
2952
3091
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2953
3092
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2955,28 +3094,28 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
2955
3094
|
assert.deepEqual(Object.keys(receipts[0].posture), ['model'], 'timeout never enters the receipt posture');
|
|
2956
3095
|
});
|
|
2957
3096
|
|
|
2958
|
-
it('the banner prints the EFFECTIVE hard cap: an env override and a fractional duration ride verbatim', () => {
|
|
3097
|
+
it('the banner prints the EFFECTIVE hard cap: an env override and a fractional duration ride verbatim', async () => {
|
|
2959
3098
|
for (const [envValue, want] of [['90s', '90s'], ['1.5m', '1\\.5m']]) {
|
|
2960
3099
|
const sb = makeSandbox();
|
|
2961
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_HARD_TIMEOUT: envValue } });
|
|
3100
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_HARD_TIMEOUT: envValue } });
|
|
2962
3101
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2963
3102
|
assert.equal(r.status, 0, r.stderr);
|
|
2964
3103
|
assert.match(r.stderr, new RegExp(`^review posture: .* timeout=${want}$`, 'm'));
|
|
2965
3104
|
}
|
|
2966
3105
|
});
|
|
2967
3106
|
|
|
2968
|
-
it('AGY_TIMEOUT (soft print-timeout) alone never moves the banner — the hard cap governs (precedence pin)', () => {
|
|
3107
|
+
it('AGY_TIMEOUT (soft print-timeout) alone never moves the banner — the hard cap governs (precedence pin)', async () => {
|
|
2969
3108
|
const sb = makeSandbox();
|
|
2970
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_TIMEOUT: '5m' } });
|
|
3109
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_TIMEOUT: '5m' } });
|
|
2971
3110
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2972
3111
|
assert.equal(r.status, 0, r.stderr);
|
|
2973
3112
|
assert.match(r.stderr, /^review posture: .* timeout=30m$/m, 'the soft print-timeout is not the banner value');
|
|
2974
3113
|
});
|
|
2975
3114
|
|
|
2976
|
-
it('an INVALID / EMPTY / OVERFLOW effective AGY_HARD_TIMEOUT falls back to the built-in default (loud on invalid)', () => {
|
|
3115
|
+
it('an INVALID / EMPTY / OVERFLOW effective AGY_HARD_TIMEOUT falls back to the built-in default (loud on invalid)', async () => {
|
|
2977
3116
|
for (const [bad, wantWarn] of [['10x', true], ['', false], ['99999999m', true]]) {
|
|
2978
3117
|
const sb = makeSandbox();
|
|
2979
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_HARD_TIMEOUT: bad } });
|
|
3118
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_HARD_TIMEOUT: bad } });
|
|
2980
3119
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2981
3120
|
assert.equal(r.status, 0, r.stderr);
|
|
2982
3121
|
assert.match(r.stderr, /^review posture: .* timeout=30m$/m, `default must stand for ${JSON.stringify(bad)}`);
|
|
@@ -2984,9 +3123,9 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
2984
3123
|
}
|
|
2985
3124
|
});
|
|
2986
3125
|
|
|
2987
|
-
it('a timeout value carrying CONTROL BYTES refuses pre-spend (the banner-field screen)', () => {
|
|
3126
|
+
it('a timeout value carrying CONTROL BYTES refuses pre-spend (the banner-field screen)', async () => {
|
|
2988
3127
|
const sb = makeSandbox();
|
|
2989
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_HARD_TIMEOUT: `30m${String.fromCharCode(1)}` } });
|
|
3128
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_HARD_TIMEOUT: `30m${String.fromCharCode(1)}` } });
|
|
2990
3129
|
const receipts = readReceipts(sb.repo);
|
|
2991
3130
|
rmSync(sb.home, { recursive: true, force: true });
|
|
2992
3131
|
assert.notEqual(r.status, 0);
|
|
@@ -3000,12 +3139,12 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
3000
3139
|
// child that HONORS the seam. A stale installed agy-run that never reads it could run uncapped
|
|
3001
3140
|
// past the parent preflight, so the parent verifies the resolved child carries the seam token
|
|
3002
3141
|
// and refuses loudly naming the refresh recovery otherwise.
|
|
3003
|
-
it('a STALE agy-run child that does not honor the timeout seam refuses fail-closed (never a silently uncapped dispatch)', () => {
|
|
3142
|
+
it('a STALE agy-run child that does not honor the timeout seam refuses fail-closed (never a silently uncapped dispatch)', async () => {
|
|
3004
3143
|
const sb = makeSandbox();
|
|
3005
3144
|
const staleDir = join(sb.home, 'stale-bin');
|
|
3006
3145
|
mkdirSync(staleDir, { recursive: true });
|
|
3007
3146
|
writeFileSync(join(staleDir, 'agy-run'), '#!/usr/bin/env bash\nprintf "FAKE_AGY_REVIEW_OUTPUT\\n### Verdict\\nSHIP\\n"\n', { mode: 0o755 });
|
|
3008
|
-
const r = run(sb, {
|
|
3147
|
+
const r = await run(sb, {
|
|
3009
3148
|
args: ['code', '--facts', 'a tiny fact'],
|
|
3010
3149
|
env: { PATH: `${staleDir}:${sb.bin}:${farmFor(['agy-run'])}` },
|
|
3011
3150
|
});
|
|
@@ -3019,9 +3158,9 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
3019
3158
|
// Flow-orchestration Phase 4.2 (#26): the uncapped lane is CLOSED — without a capping binary the
|
|
3020
3159
|
// preflight refuses by name BEFORE any CLI run (the pre-fix wrapper printed timeout=uncapped and
|
|
3021
3160
|
// ran anyway). The shadow-proof resolver discipline now surfaces as the REFUSAL, not a banner.
|
|
3022
|
-
it('fails CLOSED when no timeout/gtimeout is on PATH — refuses by name, agy never runs', () => {
|
|
3161
|
+
it('fails CLOSED when no timeout/gtimeout is on PATH — refuses by name, agy never runs', async () => {
|
|
3023
3162
|
const sb = makeSandbox();
|
|
3024
|
-
const r = run(sb, {
|
|
3163
|
+
const r = await run(sb, {
|
|
3025
3164
|
args: ['code', '--facts', 'a tiny fact'],
|
|
3026
3165
|
env: { PATH: `${sb.bin}:${farmFor(['agy', 'agy-run', 'timeout', 'gtimeout'])}` },
|
|
3027
3166
|
});
|
|
@@ -3031,9 +3170,9 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
3031
3170
|
assert.equal(r.invoked, false, 'agy must NOT be invoked when the preflight refuses');
|
|
3032
3171
|
});
|
|
3033
3172
|
|
|
3034
|
-
it('an EXPORTED shell function shadowing timeout never fools the preflight (type -P discipline)', () => {
|
|
3173
|
+
it('an EXPORTED shell function shadowing timeout never fools the preflight (type -P discipline)', async () => {
|
|
3035
3174
|
const sb = makeSandbox();
|
|
3036
|
-
const r = run(sb, {
|
|
3175
|
+
const r = await run(sb, {
|
|
3037
3176
|
args: ['code', '--facts', 'a tiny fact'],
|
|
3038
3177
|
env: {
|
|
3039
3178
|
PATH: `${sb.bin}:${farmFor(['agy', 'agy-run', 'timeout', 'gtimeout'])}`,
|
|
@@ -3045,9 +3184,9 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
3045
3184
|
assert.match(r.stderr, /hard-timeout preflight fails CLOSED/);
|
|
3046
3185
|
});
|
|
3047
3186
|
|
|
3048
|
-
it('an EXPORTED `type` function faking a path never fools the resolver (builtin type discipline)', () => {
|
|
3187
|
+
it('an EXPORTED `type` function faking a path never fools the resolver (builtin type discipline)', async () => {
|
|
3049
3188
|
const sb = makeSandbox();
|
|
3050
|
-
const r = run(sb, {
|
|
3189
|
+
const r = await run(sb, {
|
|
3051
3190
|
args: ['code', '--facts', 'a tiny fact'],
|
|
3052
3191
|
env: {
|
|
3053
3192
|
PATH: `${sb.bin}:${farmFor(['agy', 'agy-run', 'timeout', 'gtimeout'])}`,
|
|
@@ -3059,9 +3198,9 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
3059
3198
|
assert.match(r.stderr, /hard-timeout preflight fails CLOSED/);
|
|
3060
3199
|
});
|
|
3061
3200
|
|
|
3062
|
-
it('a DEL (0x7f) byte in a banner field refuses pre-spend like the C0 range', () => {
|
|
3201
|
+
it('a DEL (0x7f) byte in a banner field refuses pre-spend like the C0 range', async () => {
|
|
3063
3202
|
const sb = makeSandbox();
|
|
3064
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: `bad${String.fromCharCode(127)}model` } });
|
|
3203
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_MODEL: `bad${String.fromCharCode(127)}model` } });
|
|
3065
3204
|
const receipts = readReceipts(sb.repo);
|
|
3066
3205
|
rmSync(sb.home, { recursive: true, force: true });
|
|
3067
3206
|
assert.notEqual(r.status, 0);
|
|
@@ -3070,10 +3209,10 @@ describe('agy-review.sh — dispatch-posture labeling (D5)', () => {
|
|
|
3070
3209
|
assert.match(r.stderr, /control/i);
|
|
3071
3210
|
});
|
|
3072
3211
|
|
|
3073
|
-
it('a control byte in AGY_TIMEOUT refuses pre-spawn — agy-review forwards it to the child agy-run', () => {
|
|
3212
|
+
it('a control byte in AGY_TIMEOUT refuses pre-spawn — agy-review forwards it to the child agy-run', async () => {
|
|
3074
3213
|
for (const c of [1, 127]) {
|
|
3075
3214
|
const sb = makeSandbox();
|
|
3076
|
-
const r = run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_TIMEOUT: `30m${String.fromCharCode(c)}` } });
|
|
3215
|
+
const r = await run(sb, { args: ['code', '--facts', 'a tiny fact'], env: { AGY_TIMEOUT: `30m${String.fromCharCode(c)}` } });
|
|
3077
3216
|
const receipts = readReceipts(sb.repo);
|
|
3078
3217
|
rmSync(sb.home, { recursive: true, force: true });
|
|
3079
3218
|
assert.notEqual(r.status, 0, `must refuse control byte ${c}`);
|