@sabaiway/agent-workflow-kit 6.0.0 → 7.1.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 +102 -0
- package/README.md +1 -0
- 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/mcp.md +37 -0
- package/references/modes/recommendations.md +1 -0
- package/references/modes/uninstall.md +2 -1
- package/references/templates/agent_rules.md +1 -0
- package/tools/commands.mjs +7 -0
- package/tools/direct-run.mjs +3 -0
- package/tools/doc-parity.mjs +18 -2
- package/tools/fold-scope-cli.mjs +93 -0
- package/tools/fold-scope.mjs +307 -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 +29 -4
- package/tools/recommendations.mjs +90 -1
- package/tools/uninstall.mjs +356 -45
|
@@ -139,15 +139,19 @@ const farmFor = (exclude) => {
|
|
|
139
139
|
return farms.get(key);
|
|
140
140
|
};
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
// ASYNCHRONOUS on purpose: a blocking dispatch holds the event loop for its whole duration, which
|
|
143
|
+
// is what used to pin this file to one core while the rest of the machine idled. Awaiting the child
|
|
144
|
+
// lets a `{ concurrency }` describe overlap its tests. Per-test environment rides the CHILD's
|
|
145
|
+
// options — `process.env` is never mutated, so overlapping tests cannot read each other's PATH.
|
|
146
|
+
const run = ({ repo, bin }, { args = ['-'], input = 'do the thing', env = {}, path, cwd, timeout = 30000 } = {}) => new Promise((settle) => {
|
|
143
147
|
const argvFile = join(repo, '.cap-argv');
|
|
144
148
|
const envFile = join(repo, '.cap-env');
|
|
145
149
|
const stdinFile = join(repo, '.cap-stdin');
|
|
146
|
-
const
|
|
150
|
+
const child = execFile('bash', [WRAPPER, ...args], {
|
|
147
151
|
cwd: cwd || repo,
|
|
148
|
-
input,
|
|
149
152
|
encoding: 'utf8',
|
|
150
|
-
timeout
|
|
153
|
+
timeout,
|
|
154
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
151
155
|
env: {
|
|
152
156
|
PATH: path || `${bin}:${process.env.PATH}`,
|
|
153
157
|
HOME: repo,
|
|
@@ -159,59 +163,46 @@ const run = ({ repo, bin }, { args = ['-'], input = 'do the thing', env = {}, pa
|
|
|
159
163
|
CODEX_FAKE_STDIN: stdinFile,
|
|
160
164
|
...env,
|
|
161
165
|
},
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// Async twin of run() for the sleep-bound timeout tests: spawnSync blocks the event loop for
|
|
168
|
-
// the whole deliberate wait, so a concurrent describe could not overlap them. Same contract.
|
|
169
|
-
const runAsync = ({ repo, bin }, { args = ['-'], input = 'do the thing', env = {}, path, cwd } = {}) =>
|
|
170
|
-
new Promise((done) => {
|
|
171
|
-
const argvFile = join(repo, '.cap-argv');
|
|
172
|
-
const envFile = join(repo, '.cap-env');
|
|
173
|
-
const stdinFile = join(repo, '.cap-stdin');
|
|
174
|
-
const child = execFile('bash', [WRAPPER, ...args], {
|
|
175
|
-
cwd: cwd || repo,
|
|
176
|
-
encoding: 'utf8',
|
|
177
|
-
timeout: 30000,
|
|
178
|
-
env: {
|
|
179
|
-
PATH: path || `${bin}:${process.env.PATH}`,
|
|
180
|
-
HOME: repo,
|
|
181
|
-
TMPDIR: process.env.TMPDIR ?? '/tmp',
|
|
182
|
-
CODEX_FAKE_ARGV: argvFile,
|
|
183
|
-
CODEX_FAKE_ENV: envFile,
|
|
184
|
-
CODEX_FAKE_STDIN: stdinFile,
|
|
185
|
-
...env,
|
|
186
|
-
},
|
|
187
|
-
}, (error, stdout, stderr) => {
|
|
188
|
-
const readIf = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : '');
|
|
189
|
-
done({ status: error ? (error.code ?? 1) : 0, stdout, stderr, argv: readIf(argvFile), capEnv: readIf(envFile), capStdin: readIf(stdinFile) });
|
|
166
|
+
}, (error, stdout, stderr) => {
|
|
167
|
+
const readIf = (p) => (existsSync(p) ? readFileSync(p, 'utf8') : '');
|
|
168
|
+
settle({
|
|
169
|
+
status: error ? (error.code ?? 1) : 0, signal: error?.signal ?? null, stdout, stderr,
|
|
170
|
+
argv: readIf(argvFile), capEnv: readIf(envFile), capStdin: readIf(stdinFile),
|
|
190
171
|
});
|
|
191
|
-
child.stdin.end(input);
|
|
192
172
|
});
|
|
173
|
+
// The wrapper refuses many inputs BEFORE it reads stdin, so the pipe can already be closed when
|
|
174
|
+
// the prompt is written. The blocking spawn swallowed that; an async one throws EPIPE at the
|
|
175
|
+
// test. A closed pipe is the refusal working — but ONLY EPIPE is: any other write failure is a
|
|
176
|
+
// real fault and must reach the test instead of passing as a green.
|
|
177
|
+
child.stdin.on('error', (err) => { if (err.code !== 'EPIPE') throw err; });
|
|
178
|
+
child.stdin.end(input);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// runAsync was the async twin kept for the sleep-bound timeout tests, back when run() blocked.
|
|
182
|
+
// run() IS that twin now, so the twin is one name pointing at it — two spawn paths could only drift.
|
|
183
|
+
const runAsync = run;
|
|
193
184
|
|
|
194
|
-
describe('codex-exec.sh — quality-first model/effort guard (1.1)', () => {
|
|
195
|
-
it('refuses a non-default CODEX_MODEL and never spends a run', () => {
|
|
185
|
+
describe('codex-exec.sh — quality-first model/effort guard (1.1)', { concurrency: 2 }, () => {
|
|
186
|
+
it('refuses a non-default CODEX_MODEL and never spends a run', async () => {
|
|
196
187
|
const sb = makeSandbox();
|
|
197
|
-
const r = run(sb, { env: { CODEX_MODEL: 'gpt-5.4-mini' } });
|
|
188
|
+
const r = await run(sb, { env: { CODEX_MODEL: 'gpt-5.4-mini' } });
|
|
198
189
|
rmSync(sb.root, { recursive: true, force: true });
|
|
199
190
|
assert.notEqual(r.status, 0);
|
|
200
|
-
assert.match(r.stderr, /not the pinned
|
|
191
|
+
assert.match(r.stderr, /not the pinned model/);
|
|
201
192
|
assert.equal(r.capStdin, '', 'codex must not be invoked when the guard fires');
|
|
202
193
|
});
|
|
203
194
|
|
|
204
|
-
it('refuses a non-default CODEX_EFFORT', () => {
|
|
195
|
+
it('refuses a non-default CODEX_EFFORT', async () => {
|
|
205
196
|
const sb = makeSandbox();
|
|
206
|
-
const r = run(sb, { env: { CODEX_EFFORT: 'high' } });
|
|
197
|
+
const r = await run(sb, { env: { CODEX_EFFORT: 'high' } });
|
|
207
198
|
rmSync(sb.root, { recursive: true, force: true });
|
|
208
199
|
assert.notEqual(r.status, 0);
|
|
209
200
|
assert.match(r.stderr, /not the pinned max effort/);
|
|
210
201
|
});
|
|
211
202
|
|
|
212
|
-
it('CODEX_PROBE=1 allows a non-default model and warns loudly', () => {
|
|
203
|
+
it('CODEX_PROBE=1 allows a non-default model and warns loudly', async () => {
|
|
213
204
|
const sb = makeSandbox();
|
|
214
|
-
const r = run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: 'gpt-5.4-mini' } });
|
|
205
|
+
const r = await run(sb, { env: { CODEX_PROBE: '1', CODEX_MODEL: 'gpt-5.4-mini' } });
|
|
215
206
|
rmSync(sb.root, { recursive: true, force: true });
|
|
216
207
|
assert.equal(r.status, 0, r.stderr);
|
|
217
208
|
assert.match(r.stderr, /THROWAWAY PROBE MODE/);
|
|
@@ -235,18 +226,18 @@ const PROBE_RELAXABLE = [
|
|
|
235
226
|
['--ignore-rules'], ['--enable', 'foo'], ['--disable', 'foo'],
|
|
236
227
|
];
|
|
237
228
|
|
|
238
|
-
describe('codex-exec.sh — passthrough guard, two tiers (1.1)', () => {
|
|
229
|
+
describe('codex-exec.sh — passthrough guard, two tiers (1.1)', { concurrency: 2 }, () => {
|
|
239
230
|
for (const flag of ALWAYS_BLOCKED) {
|
|
240
|
-
it(`always rejects ${flag[0]} (no probe)`, () => {
|
|
231
|
+
it(`always rejects ${flag[0]} (no probe)`, async () => {
|
|
241
232
|
const sb = makeSandbox();
|
|
242
|
-
const r = run(sb, { args: ['-', '--', ...flag] });
|
|
233
|
+
const r = await run(sb, { args: ['-', '--', ...flag] });
|
|
243
234
|
rmSync(sb.root, { recursive: true, force: true });
|
|
244
235
|
assert.notEqual(r.status, 0);
|
|
245
236
|
assert.match(r.stderr, /is not allowed/);
|
|
246
237
|
});
|
|
247
|
-
it(`still rejects ${flag[0]} even under CODEX_PROBE=1`, () => {
|
|
238
|
+
it(`still rejects ${flag[0]} even under CODEX_PROBE=1`, async () => {
|
|
248
239
|
const sb = makeSandbox();
|
|
249
|
-
const r = run(sb, { args: ['-', '--', ...flag], env: { CODEX_PROBE: '1' } });
|
|
240
|
+
const r = await run(sb, { args: ['-', '--', ...flag], env: { CODEX_PROBE: '1' } });
|
|
250
241
|
rmSync(sb.root, { recursive: true, force: true });
|
|
251
242
|
assert.notEqual(r.status, 0);
|
|
252
243
|
assert.match(r.stderr, /blocked even under CODEX_PROBE=1/);
|
|
@@ -254,18 +245,18 @@ describe('codex-exec.sh — passthrough guard, two tiers (1.1)', () => {
|
|
|
254
245
|
}
|
|
255
246
|
|
|
256
247
|
for (const flag of PROBE_RELAXABLE) {
|
|
257
|
-
it(`rejects ${flag[0]} for a real run (no probe)`, () => {
|
|
248
|
+
it(`rejects ${flag[0]} for a real run (no probe)`, async () => {
|
|
258
249
|
const sb = makeSandbox();
|
|
259
|
-
const r = run(sb, { args: ['-', '--', ...flag] });
|
|
250
|
+
const r = await run(sb, { args: ['-', '--', ...flag] });
|
|
260
251
|
rmSync(sb.root, { recursive: true, force: true });
|
|
261
252
|
assert.notEqual(r.status, 0);
|
|
262
253
|
assert.match(r.stderr, /is not allowed/);
|
|
263
254
|
});
|
|
264
255
|
}
|
|
265
256
|
|
|
266
|
-
it('CODEX_PROBE=1 lets a context flag (--add-dir) through and warns', () => {
|
|
257
|
+
it('CODEX_PROBE=1 lets a context flag (--add-dir) through and warns', async () => {
|
|
267
258
|
const sb = makeSandbox();
|
|
268
|
-
const r = run(sb, { args: ['-', '--', '--add-dir', '/x'], env: { CODEX_PROBE: '1' } });
|
|
259
|
+
const r = await run(sb, { args: ['-', '--', '--add-dir', '/x'], env: { CODEX_PROBE: '1' } });
|
|
269
260
|
rmSync(sb.root, { recursive: true, force: true });
|
|
270
261
|
assert.equal(r.status, 0, r.stderr);
|
|
271
262
|
assert.match(r.argv, /--add-dir/);
|
|
@@ -273,10 +264,10 @@ describe('codex-exec.sh — passthrough guard, two tiers (1.1)', () => {
|
|
|
273
264
|
});
|
|
274
265
|
});
|
|
275
266
|
|
|
276
|
-
describe('codex-exec.sh — subscription / config isolation (invariant)', () => {
|
|
277
|
-
it('clears every *_API_KEY + OPENAI_BASE_URL and passes --ignore-user-config', () => {
|
|
267
|
+
describe('codex-exec.sh — subscription / config isolation (invariant)', { concurrency: 2 }, () => {
|
|
268
|
+
it('clears every *_API_KEY + OPENAI_BASE_URL and passes --ignore-user-config', async () => {
|
|
278
269
|
const sb = makeSandbox();
|
|
279
|
-
const r = run(sb, { env: {
|
|
270
|
+
const r = await run(sb, { env: {
|
|
280
271
|
OPENAI_API_KEY: 'sk-should-be-cleared', OPENAI_BASE_URL: 'http://evil.example', FOO_API_KEY: 'bar',
|
|
281
272
|
} });
|
|
282
273
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -288,19 +279,19 @@ describe('codex-exec.sh — subscription / config isolation (invariant)', () =>
|
|
|
288
279
|
});
|
|
289
280
|
});
|
|
290
281
|
|
|
291
|
-
describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
292
|
-
it('prints ONLY the final message, not the JSON event stream', () => {
|
|
282
|
+
describe('codex-exec.sh — clean output + session capture (1.2)', { concurrency: 2 }, () => {
|
|
283
|
+
it('prints ONLY the final message, not the JSON event stream', async () => {
|
|
293
284
|
const sb = makeSandbox();
|
|
294
|
-
const r = run(sb);
|
|
285
|
+
const r = await run(sb);
|
|
295
286
|
rmSync(sb.root, { recursive: true, force: true });
|
|
296
287
|
assert.equal(r.status, 0, r.stderr);
|
|
297
288
|
assert.match(r.stdout, /FAKE_FINAL_MESSAGE/);
|
|
298
289
|
assert.doesNotMatch(r.stdout, /thread\.started/, 'the JSON trace must not leak to stdout');
|
|
299
290
|
});
|
|
300
291
|
|
|
301
|
-
it('passes the clean-capture flags to codex', () => {
|
|
292
|
+
it('passes the clean-capture flags to codex', async () => {
|
|
302
293
|
const sb = makeSandbox();
|
|
303
|
-
const r = run(sb);
|
|
294
|
+
const r = await run(sb);
|
|
304
295
|
rmSync(sb.root, { recursive: true, force: true });
|
|
305
296
|
for (const f of [/(^|\n)-o(\n|$)/, /(^|\n)--json(\n|$)/, /(^|\n)--color(\n|$)/,
|
|
306
297
|
/hide_agent_reasoning=true/, /model_reasoning_summary=none/]) {
|
|
@@ -308,9 +299,9 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
308
299
|
}
|
|
309
300
|
});
|
|
310
301
|
|
|
311
|
-
it('captures the session id to the default sidecar and stderr', () => {
|
|
302
|
+
it('captures the session id to the default sidecar and stderr', async () => {
|
|
312
303
|
const sb = makeSandbox();
|
|
313
|
-
const r = run(sb);
|
|
304
|
+
const r = await run(sb);
|
|
314
305
|
const sidecar = join(sb.repo, '.codex-last-session');
|
|
315
306
|
const got = existsSync(sidecar) ? readFileSync(sidecar, 'utf8').trim() : '';
|
|
316
307
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -318,10 +309,10 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
318
309
|
assert.match(r.stderr, /session: fake-thread-123/);
|
|
319
310
|
});
|
|
320
311
|
|
|
321
|
-
it('honours CODEX_SESSION_FILE and leaves the default sidecar untouched', () => {
|
|
312
|
+
it('honours CODEX_SESSION_FILE and leaves the default sidecar untouched', async () => {
|
|
322
313
|
const sb = makeSandbox();
|
|
323
314
|
const custom = join(sb.repo, 'my-session');
|
|
324
|
-
run(sb, { env: { CODEX_SESSION_FILE: custom } });
|
|
315
|
+
await run(sb, { env: { CODEX_SESSION_FILE: custom } });
|
|
325
316
|
const customGot = existsSync(custom) ? readFileSync(custom, 'utf8').trim() : '';
|
|
326
317
|
const defaultWritten = existsSync(join(sb.repo, '.codex-last-session'));
|
|
327
318
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -329,18 +320,18 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
329
320
|
assert.equal(defaultWritten, false, 'the default sidecar must not be written when CODEX_SESSION_FILE is set');
|
|
330
321
|
});
|
|
331
322
|
|
|
332
|
-
it('falls back to the trace tail when the final-message file is missing', () => {
|
|
323
|
+
it('falls back to the trace tail when the final-message file is missing', async () => {
|
|
333
324
|
const sb = makeSandbox();
|
|
334
|
-
const r = run(sb, { env: { CODEX_FAKE_NO_OUT: '1' } });
|
|
325
|
+
const r = await run(sb, { env: { CODEX_FAKE_NO_OUT: '1' } });
|
|
335
326
|
rmSync(sb.root, { recursive: true, force: true });
|
|
336
327
|
assert.equal(r.status, 0, r.stderr);
|
|
337
328
|
assert.match(r.stderr, /no final-message file/);
|
|
338
329
|
assert.match(r.stdout, /turn\.completed/, 'the trace tail should carry the event stream');
|
|
339
330
|
});
|
|
340
331
|
|
|
341
|
-
it('on a codex failure, prints the trace tail to stderr and exits codex code', () => {
|
|
332
|
+
it('on a codex failure, prints the trace tail to stderr and exits codex code', async () => {
|
|
342
333
|
const sb = makeSandbox();
|
|
343
|
-
const r = run(sb, { env: { CODEX_FAKE_EXIT: '7' } });
|
|
334
|
+
const r = await run(sb, { env: { CODEX_FAKE_EXIT: '7' } });
|
|
344
335
|
rmSync(sb.root, { recursive: true, force: true });
|
|
345
336
|
assert.equal(r.status, 7);
|
|
346
337
|
assert.match(r.stderr, /codex exec failed \(exit 7\)/);
|
|
@@ -348,9 +339,9 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
348
339
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'a plain failure (no bwrap signature) never triggers the hint');
|
|
349
340
|
});
|
|
350
341
|
|
|
351
|
-
it('on a NESTED-SANDBOX failure (bwrap/read-only trace) surfaces the stated recovery hint', () => {
|
|
342
|
+
it('on a NESTED-SANDBOX failure (bwrap/read-only trace) surfaces the stated recovery hint', async () => {
|
|
352
343
|
const sb = makeSandbox();
|
|
353
|
-
const r = run(sb, { env: { CODEX_FAKE_EXIT: '1', CODEX_FAKE_STDERR: 'bwrap: setting up sandbox: mkdir /newroot: Read-only file system' } });
|
|
344
|
+
const r = await run(sb, { env: { CODEX_FAKE_EXIT: '1', CODEX_FAKE_STDERR: 'bwrap: setting up sandbox: mkdir /newroot: Read-only file system' } });
|
|
354
345
|
rmSync(sb.root, { recursive: true, force: true });
|
|
355
346
|
assert.equal(r.status, 1);
|
|
356
347
|
assert.match(r.stderr, /NESTED-SANDBOX/, 'names the failure class');
|
|
@@ -358,33 +349,33 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
358
349
|
assert.match(r.stderr, /Do NOT blanket-disable/, 'warns against a preemptive blanket');
|
|
359
350
|
});
|
|
360
351
|
|
|
361
|
-
it('a NON-nested codex failure does NOT emit the nested-sandbox hint (no false positive)', () => {
|
|
352
|
+
it('a NON-nested codex failure does NOT emit the nested-sandbox hint (no false positive)', async () => {
|
|
362
353
|
const sb = makeSandbox();
|
|
363
|
-
const r = run(sb, { env: { CODEX_FAKE_EXIT: '3', CODEX_FAKE_STDERR: 'model error: rate limited, try again later' } });
|
|
354
|
+
const r = await run(sb, { env: { CODEX_FAKE_EXIT: '3', CODEX_FAKE_STDERR: 'model error: rate limited, try again later' } });
|
|
364
355
|
rmSync(sb.root, { recursive: true, force: true });
|
|
365
356
|
assert.equal(r.status, 3);
|
|
366
357
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'a generic failure never triggers the hint');
|
|
367
358
|
});
|
|
368
359
|
|
|
369
|
-
it('#6 fold: a mechanism+failure trace whose message contains the letter n (mkdir /newroot) still fires — the old [^\\n]* wrongly excluded n', () => {
|
|
360
|
+
it('#6 fold: a mechanism+failure trace whose message contains the letter n (mkdir /newroot) still fires — the old [^\\n]* wrongly excluded n', async () => {
|
|
370
361
|
const sb = makeSandbox();
|
|
371
|
-
const r = run(sb, { env: { CODEX_FAKE_EXIT: '1', CODEX_FAKE_STDERR: 'bwrap: mkdir /newroot: operation not permitted' } });
|
|
362
|
+
const r = await run(sb, { env: { CODEX_FAKE_EXIT: '1', CODEX_FAKE_STDERR: 'bwrap: mkdir /newroot: operation not permitted' } });
|
|
372
363
|
rmSync(sb.root, { recursive: true, force: true });
|
|
373
364
|
assert.equal(r.status, 1);
|
|
374
365
|
assert.match(r.stderr, /NESTED-SANDBOX/, 'bwrap (mechanism) + operation not permitted (failure) fire even through n-bearing words');
|
|
375
366
|
});
|
|
376
367
|
|
|
377
|
-
it('#6 fold: a LONE mechanism token (a bwrap banner, no failure) does NOT fire — a combination is required', () => {
|
|
368
|
+
it('#6 fold: a LONE mechanism token (a bwrap banner, no failure) does NOT fire — a combination is required', async () => {
|
|
378
369
|
const sb = makeSandbox();
|
|
379
|
-
const r = run(sb, { env: { CODEX_FAKE_EXIT: '2', CODEX_FAKE_STDERR: 'bwrap version 0.11.0' } });
|
|
370
|
+
const r = await run(sb, { env: { CODEX_FAKE_EXIT: '2', CODEX_FAKE_STDERR: 'bwrap version 0.11.0' } });
|
|
380
371
|
rmSync(sb.root, { recursive: true, force: true });
|
|
381
372
|
assert.equal(r.status, 2);
|
|
382
373
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'a mechanism token without a permission/read-only failure is not nested-sandbox proof');
|
|
383
374
|
});
|
|
384
375
|
|
|
385
|
-
it('#6 fold: a LONE failure token (permission denied, no sandbox mechanism) does NOT fire', () => {
|
|
376
|
+
it('#6 fold: a LONE failure token (permission denied, no sandbox mechanism) does NOT fire', async () => {
|
|
386
377
|
const sb = makeSandbox();
|
|
387
|
-
const r = run(sb, { env: { CODEX_FAKE_EXIT: '2', CODEX_FAKE_STDERR: 'curl: (7) permission denied' } });
|
|
378
|
+
const r = await run(sb, { env: { CODEX_FAKE_EXIT: '2', CODEX_FAKE_STDERR: 'curl: (7) permission denied' } });
|
|
388
379
|
rmSync(sb.root, { recursive: true, force: true });
|
|
389
380
|
assert.equal(r.status, 2);
|
|
390
381
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'a permission failure from unrelated code is not nested-sandbox proof');
|
|
@@ -401,9 +392,9 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
401
392
|
const FAILURE = 'mkdir /newroot: Read-only file system';
|
|
402
393
|
const SIGNATURE = `${MECHANISM}: ${FAILURE}\n`;
|
|
403
394
|
|
|
404
|
-
it('an rc == 0 run whose trace carries a command_execution with a NONZERO exit_code and the signature warns loudly and still prints the answer', () => {
|
|
395
|
+
it('an rc == 0 run whose trace carries a command_execution with a NONZERO exit_code and the signature warns loudly and still prints the answer', async () => {
|
|
405
396
|
const sb = makeSandbox();
|
|
406
|
-
const r = run(sb, { env: { CODEX_FAKE_EVENT: cmdItem({ output: SIGNATURE, exitCode: 1, status: 'completed' }) } });
|
|
397
|
+
const r = await run(sb, { env: { CODEX_FAKE_EVENT: cmdItem({ output: SIGNATURE, exitCode: 1, status: 'completed' }) } });
|
|
407
398
|
rmSync(sb.root, { recursive: true, force: true });
|
|
408
399
|
assert.equal(r.status, 0, 'the warning lane never changes the exit status');
|
|
409
400
|
assert.match(r.stdout, /FAKE_FINAL_MESSAGE/, 'the answer is printed FIRST, on stdout, unchanged');
|
|
@@ -412,17 +403,17 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
412
403
|
assert.match(r.stderr, /excludedCommands|per-run consented bypass/, 'names the reroute');
|
|
413
404
|
});
|
|
414
405
|
|
|
415
|
-
it('an rc == 0 run whose trace carries a command_execution with a null exit_code and an explicitly FAILED status warns', () => {
|
|
406
|
+
it('an rc == 0 run whose trace carries a command_execution with a null exit_code and an explicitly FAILED status warns', async () => {
|
|
416
407
|
const sb = makeSandbox();
|
|
417
|
-
const r = run(sb, { env: { CODEX_FAKE_EVENT: cmdItem({ output: SIGNATURE, exitCode: null, status: 'failed' }) } });
|
|
408
|
+
const r = await run(sb, { env: { CODEX_FAKE_EVENT: cmdItem({ output: SIGNATURE, exitCode: null, status: 'failed' }) } });
|
|
418
409
|
rmSync(sb.root, { recursive: true, force: true });
|
|
419
410
|
assert.equal(r.status, 0);
|
|
420
411
|
assert.match(r.stderr, /NESTED-SANDBOX/, 'the serialized failed status is the second failure proof');
|
|
421
412
|
});
|
|
422
413
|
|
|
423
|
-
it('plain non-JSON stderr lines before and after a matching failed command_execution do not suppress the warning', () => {
|
|
414
|
+
it('plain non-JSON stderr lines before and after a matching failed command_execution do not suppress the warning', async () => {
|
|
424
415
|
const sb = makeSandbox();
|
|
425
|
-
const r = run(sb, {
|
|
416
|
+
const r = await run(sb, {
|
|
426
417
|
env: {
|
|
427
418
|
CODEX_FAKE_STDERR: 'ERROR codex_core::session: failed to load skill /x/SKILL.md: missing field description',
|
|
428
419
|
CODEX_FAKE_EVENT: `not json at all\n${cmdItem({ output: SIGNATURE, exitCode: 2, status: 'failed' })}\nstill not json`,
|
|
@@ -433,9 +424,9 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
433
424
|
assert.match(r.stderr, /NESTED-SANDBOX/, 'the merged stream is judged line by line — noise is not evidence and never a stop');
|
|
434
425
|
});
|
|
435
426
|
|
|
436
|
-
it('the resume lane warns on an rc == 0 nested-sandbox signature — the lane the incident fired on', () => {
|
|
427
|
+
it('the resume lane warns on an rc == 0 nested-sandbox signature — the lane the incident fired on', async () => {
|
|
437
428
|
const sb = makeSandbox();
|
|
438
|
-
const r = run(sb, {
|
|
429
|
+
const r = await run(sb, {
|
|
439
430
|
args: ['--resume', 'sess-nested', '-'], input: 'continue',
|
|
440
431
|
env: { CODEX_FAKE_EVENT: cmdItem({ output: SIGNATURE, exitCode: 1, status: 'failed' }) },
|
|
441
432
|
});
|
|
@@ -445,36 +436,36 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
445
436
|
assert.match(r.stderr, /NESTED-SANDBOX/, 'the whole point of unifying the capture');
|
|
446
437
|
});
|
|
447
438
|
|
|
448
|
-
it('an rc == 0 run with a clean trace warns nothing', () => {
|
|
439
|
+
it('an rc == 0 run with a clean trace warns nothing', async () => {
|
|
449
440
|
const sb = makeSandbox();
|
|
450
|
-
const r = run(sb);
|
|
441
|
+
const r = await run(sb);
|
|
451
442
|
rmSync(sb.root, { recursive: true, force: true });
|
|
452
443
|
assert.equal(r.status, 0, r.stderr);
|
|
453
444
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'a clean run must stay silent');
|
|
454
445
|
});
|
|
455
446
|
|
|
456
|
-
it('a lone mechanism token and a lone failure token each warn nothing on the rc == 0 lane', () => {
|
|
447
|
+
it('a lone mechanism token and a lone failure token each warn nothing on the rc == 0 lane', async () => {
|
|
457
448
|
for (const output of [`${MECHANISM} version 0.11.0\n`, `curl: (7) ${FAILURE}\n`]) {
|
|
458
449
|
const sb = makeSandbox();
|
|
459
|
-
const r = run(sb, { env: { CODEX_FAKE_EVENT: cmdItem({ output, exitCode: 1, status: 'failed' }) } });
|
|
450
|
+
const r = await run(sb, { env: { CODEX_FAKE_EVENT: cmdItem({ output, exitCode: 1, status: 'failed' }) } });
|
|
460
451
|
rmSync(sb.root, { recursive: true, force: true });
|
|
461
452
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, `a lone token class is not proof: ${output}`);
|
|
462
453
|
}
|
|
463
454
|
});
|
|
464
455
|
|
|
465
|
-
it('nested-sandbox text appearing ONLY inside an agent_message item never warns', () => {
|
|
456
|
+
it('nested-sandbox text appearing ONLY inside an agent_message item never warns', async () => {
|
|
466
457
|
const sb = makeSandbox();
|
|
467
458
|
const event = JSON.stringify({ type: 'item.completed', item: { id: 'item_9', type: 'agent_message', text: `I hit ${SIGNATURE}` } });
|
|
468
|
-
const r = run(sb, { env: { CODEX_FAKE_EVENT: event } });
|
|
459
|
+
const r = await run(sb, { env: { CODEX_FAKE_EVENT: event } });
|
|
469
460
|
rmSync(sb.root, { recursive: true, force: true });
|
|
470
461
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'the model TALKING about a sandbox is not a failed tool call');
|
|
471
462
|
});
|
|
472
463
|
|
|
473
|
-
it('a SUCCESSFUL command_execution whose output merely QUOTES both tokens never warns', () => {
|
|
464
|
+
it('a SUCCESSFUL command_execution whose output merely QUOTES both tokens never warns', async () => {
|
|
474
465
|
const sb = makeSandbox();
|
|
475
466
|
// The concrete false positive: codex-exec.sh itself carries both token classes, so any
|
|
476
467
|
// successful grep over it would trip a loose whole-trace rule.
|
|
477
|
-
const r = run(sb, {
|
|
468
|
+
const r = await run(sb, {
|
|
478
469
|
env: { CODEX_FAKE_EVENT: cmdItem({ command: '/bin/bash -lc grep -n bwrap codex-exec.sh', output: SIGNATURE, exitCode: 0, status: 'completed' }) },
|
|
479
470
|
});
|
|
480
471
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -482,20 +473,20 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
482
473
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'a command that SUCCEEDED proves nothing failed');
|
|
483
474
|
});
|
|
484
475
|
|
|
485
|
-
it('a command_execution with a null exit_code and no proven failed status never warns', () => {
|
|
476
|
+
it('a command_execution with a null exit_code and no proven failed status never warns', async () => {
|
|
486
477
|
const sb = makeSandbox();
|
|
487
|
-
const r = run(sb, { env: { CODEX_FAKE_EVENT: cmdItem({ output: SIGNATURE, exitCode: null, status: 'in_progress' }) } });
|
|
478
|
+
const r = await run(sb, { env: { CODEX_FAKE_EVENT: cmdItem({ output: SIGNATURE, exitCode: null, status: 'in_progress' }) } });
|
|
488
479
|
rmSync(sb.root, { recursive: true, force: true });
|
|
489
480
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'a null exit_code is never failure by itself');
|
|
490
481
|
});
|
|
491
482
|
|
|
492
|
-
it('tokens split across two different items never warn', () => {
|
|
483
|
+
it('tokens split across two different items never warn', async () => {
|
|
493
484
|
const sb = makeSandbox();
|
|
494
485
|
const split = [
|
|
495
486
|
cmdItem({ id: 'item_1', output: `${MECHANISM} version 0.11.0\n`, exitCode: 1, status: 'failed' }),
|
|
496
487
|
cmdItem({ id: 'item_2', output: `curl: (7) ${FAILURE}\n`, exitCode: 1, status: 'failed' }),
|
|
497
488
|
].join('\n');
|
|
498
|
-
const r = run(sb, { env: { CODEX_FAKE_EVENT: split } });
|
|
489
|
+
const r = await run(sb, { env: { CODEX_FAKE_EVENT: split } });
|
|
499
490
|
rmSync(sb.root, { recursive: true, force: true });
|
|
500
491
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'the combination must sit in ONE item — two failures are not one nested sandbox');
|
|
501
492
|
});
|
|
@@ -504,28 +495,28 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
504
495
|
// Testing the four fields independently is not enough: position in the line is not membership in
|
|
505
496
|
// the item. The scan walks ONE contiguous chain of raw delimiters instead, and every gap in that
|
|
506
497
|
// chain is inside a JSON string, where a quote is escaped and cannot forge the next delimiter.
|
|
507
|
-
it('a decoy object carrying the type, with the failure fields on a DIFFERENT item, never warns', () => {
|
|
498
|
+
it('a decoy object carrying the type, with the failure fields on a DIFFERENT item, never warns', async () => {
|
|
508
499
|
const sb = makeSandbox();
|
|
509
500
|
const decoy = '{"type":"item.completed","decoy":{"type":"command_execution"},"item":{"type":"agent_message","aggregated_output":"bwrap: operation not permitted","exit_code":0,"status":"failed"}}';
|
|
510
|
-
const r = run(sb, { env: { CODEX_FAKE_EVENT: decoy } });
|
|
501
|
+
const r = await run(sb, { env: { CODEX_FAKE_EVENT: decoy } });
|
|
511
502
|
rmSync(sb.root, { recursive: true, force: true });
|
|
512
503
|
assert.equal(r.status, 0, r.stderr);
|
|
513
504
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'the type belongs to the decoy; the failure fields belong to an agent_message');
|
|
514
505
|
});
|
|
515
506
|
|
|
516
|
-
it('a decoy carrying BOTH the type and a command, with the failure fields on a DIFFERENT item, never warns', () => {
|
|
507
|
+
it('a decoy carrying BOTH the type and a command, with the failure fields on a DIFFERENT item, never warns', async () => {
|
|
517
508
|
const sb = makeSandbox();
|
|
518
509
|
// Anchoring on a longer literal is not enough: the skip between fields must itself be PROVEN to
|
|
519
510
|
// be one JSON string's content, or the walk leaves the decoy's command and lands in the
|
|
520
511
|
// agent_message's fields.
|
|
521
512
|
const decoy = '{"type":"item.completed","decoy":{"type":"command_execution","command":"x"},"item":{"type":"agent_message","aggregated_output":"bwrap: setting up sandbox: operation not permitted","exit_code":1,"status":"failed"}}';
|
|
522
|
-
const r = run(sb, { env: { CODEX_FAKE_EVENT: decoy } });
|
|
513
|
+
const r = await run(sb, { env: { CODEX_FAKE_EVENT: decoy } });
|
|
523
514
|
rmSync(sb.root, { recursive: true, force: true });
|
|
524
515
|
assert.equal(r.status, 0, r.stderr);
|
|
525
516
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'an unvalidated gap lets the walk cross an object boundary');
|
|
526
517
|
});
|
|
527
518
|
|
|
528
|
-
it('a genuinely failed item with a ~200KB aggregated_output still warns — and the scan does not hang', () => {
|
|
519
|
+
it('a genuinely failed item with a ~200KB aggregated_output still warns — and the scan does not hang', async () => {
|
|
529
520
|
const sb = makeSandbox();
|
|
530
521
|
// Two edges at once: the signature sits FIRST, so any early-exit consumer must not lose it, and
|
|
531
522
|
// the field is far larger than a pipe buffer. It also pins the cost: the quadratic bash string
|
|
@@ -534,65 +525,67 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
534
525
|
// The payload rides a FILE: 200KB in the environment is E2BIG on a normal host.
|
|
535
526
|
const payload = join(sb.repo, 'big-event.jsonl');
|
|
536
527
|
writeFileSync(payload, `${cmdItem({ output: big, exitCode: 1, status: 'failed' })}\n`);
|
|
537
|
-
|
|
528
|
+
// "does not hang" needs a NUMBER or it cannot fail: a shortest-match `#*` cut to the exit_code
|
|
529
|
+
// delimiter costs 17s here (measured), a linear pass costs milliseconds. The cap sits between.
|
|
530
|
+
const r = await run(sb, { env: { CODEX_FAKE_EVENT_FILE: payload }, timeout: 8000 });
|
|
538
531
|
rmSync(sb.root, { recursive: true, force: true });
|
|
539
532
|
assert.equal(r.status, 0, r.stderr);
|
|
540
533
|
assert.match(r.stderr, /NESTED-SANDBOX/, 'a large output must not silently drop a real signature');
|
|
541
534
|
});
|
|
542
535
|
|
|
543
|
-
it('a plain non-JSON log line carrying the same substrings never warns', () => {
|
|
536
|
+
it('a plain non-JSON log line carrying the same substrings never warns', async () => {
|
|
544
537
|
const sb = makeSandbox();
|
|
545
538
|
const lookalike = `ERROR codex_core: replaying "type":"command_execution","command":"x","aggregated_output":"${SIGNATURE.trim()}","exit_code":1,"status":"failed"`;
|
|
546
|
-
const r = run(sb, { env: { CODEX_FAKE_EVENT: lookalike } });
|
|
539
|
+
const r = await run(sb, { env: { CODEX_FAKE_EVENT: lookalike } });
|
|
547
540
|
rmSync(sb.root, { recursive: true, force: true });
|
|
548
541
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'prose ABOUT an event is not an event — an event line starts with {');
|
|
549
542
|
});
|
|
550
543
|
|
|
551
|
-
it('a FOREIGN "status":"failed" elsewhere on the line never proves a SUCCESSFUL item failed', () => {
|
|
544
|
+
it('a FOREIGN "status":"failed" elsewhere on the line never proves a SUCCESSFUL item failed', async () => {
|
|
552
545
|
const sb = makeSandbox();
|
|
553
546
|
const event = `${cmdItem({ output: SIGNATURE, exitCode: 0, status: 'completed' })}{"type":"turn.failed","status":"failed"}`;
|
|
554
|
-
const r = run(sb, { env: { CODEX_FAKE_EVENT: event } });
|
|
547
|
+
const r = await run(sb, { env: { CODEX_FAKE_EVENT: event } });
|
|
555
548
|
rmSync(sb.root, { recursive: true, force: true });
|
|
556
549
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'the failed status must sit immediately after THIS item exit_code');
|
|
557
550
|
});
|
|
558
551
|
|
|
559
|
-
it('an escaped delimiter inside aggregated_output never fools the slice', () => {
|
|
552
|
+
it('an escaped delimiter inside aggregated_output never fools the slice', async () => {
|
|
560
553
|
const sb = makeSandbox();
|
|
561
|
-
const r = run(sb, {
|
|
554
|
+
const r = await run(sb, {
|
|
562
555
|
env: { CODEX_FAKE_EVENT: cmdItem({ output: `${SIGNATURE}","exit_code":1,"status":"failed"`, exitCode: 0, status: 'completed' }) },
|
|
563
556
|
});
|
|
564
557
|
rmSync(sb.root, { recursive: true, force: true });
|
|
565
558
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'a quote inside a JSON string is escaped, so the raw delimiter cannot occur there');
|
|
566
559
|
});
|
|
567
560
|
|
|
568
|
-
it('only the FIRST command_execution item of a line is judged — a second item on the same line is missed (a STATED false negative)', () => {
|
|
561
|
+
it('only the FIRST command_execution item of a line is judged — a second item on the same line is missed (a STATED false negative)', async () => {
|
|
569
562
|
const sb = makeSandbox();
|
|
570
563
|
const glued = `${cmdItem({ id: 'item_1', output: 'all good\n', exitCode: 0, status: 'completed' })}${cmdItem({ id: 'item_2', output: SIGNATURE, exitCode: 1, status: 'failed' })}`;
|
|
571
|
-
const r = run(sb, { env: { CODEX_FAKE_EVENT: glued } });
|
|
564
|
+
const r = await run(sb, { env: { CODEX_FAKE_EVENT: glued } });
|
|
572
565
|
rmSync(sb.root, { recursive: true, force: true });
|
|
573
566
|
assert.doesNotMatch(r.stderr, /NESTED-SANDBOX/, 'under-firing is the deliberate direction on a warning lane; this pins it so it cannot change silently');
|
|
574
567
|
});
|
|
575
568
|
|
|
576
|
-
it('a trace of plain non-JSON lines alone carrying both tokens never warns on the rc == 0 arm — while the FAILED arm warns on exactly those bytes', () => {
|
|
569
|
+
it('a trace of plain non-JSON lines alone carrying both tokens never warns on the rc == 0 arm — while the FAILED arm warns on exactly those bytes', async () => {
|
|
577
570
|
const bytes = `${MECHANISM}: ${FAILURE}`;
|
|
578
571
|
const clean = makeSandbox();
|
|
579
|
-
const ok = run(clean, { env: { CODEX_FAKE_STDERR: bytes } });
|
|
572
|
+
const ok = await run(clean, { env: { CODEX_FAKE_STDERR: bytes } });
|
|
580
573
|
rmSync(clean.root, { recursive: true, force: true });
|
|
581
574
|
assert.equal(ok.status, 0, ok.stderr);
|
|
582
575
|
assert.doesNotMatch(ok.stderr, /NESTED-SANDBOX/, 'on a COMPLETED run only per-item evidence speaks');
|
|
583
576
|
const failed = makeSandbox();
|
|
584
|
-
const bad = run(failed, { env: { CODEX_FAKE_STDERR: bytes, CODEX_FAKE_EXIT: '1' } });
|
|
577
|
+
const bad = await run(failed, { env: { CODEX_FAKE_STDERR: bytes, CODEX_FAKE_EXIT: '1' } });
|
|
585
578
|
rmSync(failed.root, { recursive: true, force: true });
|
|
586
579
|
assert.equal(bad.status, 1);
|
|
587
580
|
assert.match(bad.stderr, /NESTED-SANDBOX/, 'the failed-run arm keeps its loose whole-trace rule — that is what makes the dual policy visible');
|
|
588
581
|
});
|
|
589
582
|
|
|
590
|
-
it('warns (never silently) when the session sidecar cannot be written', () => {
|
|
583
|
+
it('warns (never silently) when the session sidecar cannot be written', async () => {
|
|
591
584
|
const sb = makeSandbox();
|
|
592
585
|
const blocker = join(sb.repo, 'blocker');
|
|
593
586
|
writeFileSync(blocker, 'x'); // a regular file …
|
|
594
587
|
const bad = join(blocker, 'session'); // … so this path is unwritable (ENOTDIR)
|
|
595
|
-
const r = run(sb, { env: { CODEX_SESSION_FILE: bad } });
|
|
588
|
+
const r = await run(sb, { env: { CODEX_SESSION_FILE: bad } });
|
|
596
589
|
rmSync(sb.root, { recursive: true, force: true });
|
|
597
590
|
assert.equal(r.status, 0, r.stderr);
|
|
598
591
|
assert.match(r.stderr, /could not write the session sidecar/);
|
|
@@ -600,10 +593,10 @@ describe('codex-exec.sh — clean output + session capture (1.2)', () => {
|
|
|
600
593
|
});
|
|
601
594
|
});
|
|
602
595
|
|
|
603
|
-
describe('codex-exec.sh — leaner prompt (1.4)', () => {
|
|
604
|
-
it('directive obeys AGENTS.md from context without a read-AGENTS action', () => {
|
|
596
|
+
describe('codex-exec.sh — leaner prompt (1.4)', { concurrency: 2 }, () => {
|
|
597
|
+
it('directive obeys AGENTS.md from context without a read-AGENTS action', async () => {
|
|
605
598
|
const sb = makeSandbox();
|
|
606
|
-
const r = run(sb);
|
|
599
|
+
const r = await run(sb);
|
|
607
600
|
rmSync(sb.root, { recursive: true, force: true });
|
|
608
601
|
assert.match(r.capStdin, /Obey EVERY Hard Constraint declared in the project's root AGENTS\.md \(already/);
|
|
609
602
|
assert.doesNotMatch(r.capStdin, /Read the target project's root AGENTS\.md/);
|
|
@@ -611,7 +604,7 @@ describe('codex-exec.sh — leaner prompt (1.4)', () => {
|
|
|
611
604
|
});
|
|
612
605
|
});
|
|
613
606
|
|
|
614
|
-
describe('codex-exec.sh — hard timeout (1.3)', { concurrency:
|
|
607
|
+
describe('codex-exec.sh — hard timeout (1.3)', { concurrency: 2 }, () => {
|
|
615
608
|
it('kills a hung codex at CODEX_HARD_TIMEOUT and reports it', async () => {
|
|
616
609
|
const sb = makeSandbox();
|
|
617
610
|
const started = Date.now();
|
|
@@ -623,20 +616,20 @@ describe('codex-exec.sh — hard timeout (1.3)', { concurrency: true }, () => {
|
|
|
623
616
|
assert.match(r.stderr, /exceeded the hard cap/);
|
|
624
617
|
});
|
|
625
618
|
|
|
626
|
-
it('warns and runs uncapped when neither timeout nor gtimeout is on PATH', () => {
|
|
619
|
+
it('warns and runs uncapped when neither timeout nor gtimeout is on PATH', async () => {
|
|
627
620
|
const sb = makeSandbox();
|
|
628
621
|
const path = `${sb.bin}:${farmFor(['timeout', 'gtimeout'])}`;
|
|
629
|
-
const r = run(sb, { path });
|
|
622
|
+
const r = await run(sb, { path });
|
|
630
623
|
rmSync(sb.root, { recursive: true, force: true });
|
|
631
624
|
assert.equal(r.status, 0, r.stderr);
|
|
632
625
|
assert.match(r.stderr, /WITHOUT a hard wall-clock cap/);
|
|
633
626
|
assert.match(r.stdout, /FAKE_FINAL_MESSAGE/);
|
|
634
627
|
});
|
|
635
628
|
|
|
636
|
-
it('resume runs uncapped (and warns) when no timeout binary is on PATH', () => {
|
|
629
|
+
it('resume runs uncapped (and warns) when no timeout binary is on PATH', async () => {
|
|
637
630
|
const sb = makeSandbox();
|
|
638
631
|
const path = `${sb.bin}:${farmFor(['timeout', 'gtimeout'])}`;
|
|
639
|
-
const r = run(sb, { args: ['--resume', 'sess-1', '-'], input: 'go', path });
|
|
632
|
+
const r = await run(sb, { args: ['--resume', 'sess-1', '-'], input: 'go', path });
|
|
640
633
|
rmSync(sb.root, { recursive: true, force: true });
|
|
641
634
|
assert.equal(r.status, 0, r.stderr);
|
|
642
635
|
assert.match(r.stderr, /WITHOUT a hard wall-clock cap/);
|
|
@@ -644,18 +637,18 @@ describe('codex-exec.sh — hard timeout (1.3)', { concurrency: true }, () => {
|
|
|
644
637
|
});
|
|
645
638
|
});
|
|
646
639
|
|
|
647
|
-
describe('codex-exec.sh — preflight (unchanged invariants)', () => {
|
|
648
|
-
it('STOPs when there is no root AGENTS.md', () => {
|
|
640
|
+
describe('codex-exec.sh — preflight (unchanged invariants)', { concurrency: 2 }, () => {
|
|
641
|
+
it('STOPs when there is no root AGENTS.md', async () => {
|
|
649
642
|
const sb = makeSandbox();
|
|
650
643
|
rmSync(join(sb.repo, 'AGENTS.md'));
|
|
651
|
-
const r = run(sb);
|
|
644
|
+
const r = await run(sb);
|
|
652
645
|
rmSync(sb.root, { recursive: true, force: true });
|
|
653
646
|
assert.equal(r.status, 2);
|
|
654
647
|
assert.match(r.stderr, /no root AGENTS\.md/);
|
|
655
648
|
});
|
|
656
649
|
});
|
|
657
650
|
|
|
658
|
-
describe('codex-exec.sh — resume entrypoint restates every invariant (3.1)', () => {
|
|
651
|
+
describe('codex-exec.sh — resume entrypoint restates every invariant (3.1)', { concurrency: 2 }, () => {
|
|
659
652
|
const RESUME_INVARIANTS = [
|
|
660
653
|
/(^|\n)resume(\n|$)/, /(^|\n)--ignore-user-config(\n|$)/, /(^|\n)gpt-5\.6-sol(\n|$)/,
|
|
661
654
|
/model_reasoning_effort=xhigh/, /sandbox_mode=workspace-write/,
|
|
@@ -675,9 +668,9 @@ describe('codex-exec.sh — resume entrypoint restates every invariant (3.1)', (
|
|
|
675
668
|
'--output-schema', '--json', '-o', '--output-last-message', '-h', '--help',
|
|
676
669
|
]);
|
|
677
670
|
|
|
678
|
-
it('every flag the resume lane sends is one the REAL `codex exec resume` accepts', () => {
|
|
671
|
+
it('every flag the resume lane sends is one the REAL `codex exec resume` accepts', async () => {
|
|
679
672
|
const sb = makeSandbox();
|
|
680
|
-
const r = run(sb, { args: ['--resume', 'sess-flags', '-'], input: 'go' });
|
|
673
|
+
const r = await run(sb, { args: ['--resume', 'sess-flags', '-'], input: 'go' });
|
|
681
674
|
rmSync(sb.root, { recursive: true, force: true });
|
|
682
675
|
assert.equal(r.status, 0, r.stderr);
|
|
683
676
|
const sent = r.argv.split('\n').filter((a) => a.startsWith('-') && a !== '-');
|
|
@@ -689,9 +682,9 @@ describe('codex-exec.sh — resume entrypoint restates every invariant (3.1)', (
|
|
|
689
682
|
assert.equal(sent.includes('--color'), false, 'the regression this list exists to prevent');
|
|
690
683
|
});
|
|
691
684
|
|
|
692
|
-
it('--resume <id>: composes `exec resume <id>` with the full restated policy', () => {
|
|
685
|
+
it('--resume <id>: composes `exec resume <id>` with the full restated policy', async () => {
|
|
693
686
|
const sb = makeSandbox();
|
|
694
|
-
const r = run(sb, { args: ['--resume', 'sess-xyz', '-'], input: 'continue please' });
|
|
687
|
+
const r = await run(sb, { args: ['--resume', 'sess-xyz', '-'], input: 'continue please' });
|
|
695
688
|
rmSync(sb.root, { recursive: true, force: true });
|
|
696
689
|
assert.equal(r.status, 0, r.stderr);
|
|
697
690
|
assert.match(r.argv, /(^|\n)sess-xyz(\n|$)/, 'the session id is passed positionally');
|
|
@@ -702,9 +695,9 @@ describe('codex-exec.sh — resume entrypoint restates every invariant (3.1)', (
|
|
|
702
695
|
// The capture unification: resume used to be the odd mode out — no -o, no --json, its event
|
|
703
696
|
// stream nowhere — which is precisely why the lane the nested-sandbox incident fired on had no
|
|
704
697
|
// evidence surface. `codex exec resume` accepts both (live-probed, codex-cli 0.147.0).
|
|
705
|
-
it('resume composes the unified capture — -o and --json, and NOT --color (which it rejects)', () => {
|
|
698
|
+
it('resume composes the unified capture — -o and --json, and NOT --color (which it rejects)', async () => {
|
|
706
699
|
const sb = makeSandbox();
|
|
707
|
-
const r = run(sb, { args: ['--resume', 'sess-unified', '-'], input: 'continue please' });
|
|
700
|
+
const r = await run(sb, { args: ['--resume', 'sess-unified', '-'], input: 'continue please' });
|
|
708
701
|
rmSync(sb.root, { recursive: true, force: true });
|
|
709
702
|
assert.equal(r.status, 0, r.stderr);
|
|
710
703
|
assert.match(r.argv, /(^|\n)-o(\n|$)/, 'resume writes the final message through -o');
|
|
@@ -715,84 +708,84 @@ describe('codex-exec.sh — resume entrypoint restates every invariant (3.1)', (
|
|
|
715
708
|
assert.match(r.stdout, /FAKE_FINAL_MESSAGE/, 'resume stdout is still the final message');
|
|
716
709
|
});
|
|
717
710
|
|
|
718
|
-
it('resume falls back to the trace tail when the final-message file is missing', () => {
|
|
711
|
+
it('resume falls back to the trace tail when the final-message file is missing', async () => {
|
|
719
712
|
const sb = makeSandbox();
|
|
720
|
-
const r = run(sb, { args: ['--resume', 'sess-noout', '-'], input: 'go', env: { CODEX_FAKE_NO_OUT: '1' } });
|
|
713
|
+
const r = await run(sb, { args: ['--resume', 'sess-noout', '-'], input: 'go', env: { CODEX_FAKE_NO_OUT: '1' } });
|
|
721
714
|
rmSync(sb.root, { recursive: true, force: true });
|
|
722
715
|
assert.equal(r.status, 0, r.stderr);
|
|
723
716
|
assert.match(r.stderr, /no final-message file/, 'the fallback is loud, never silent');
|
|
724
717
|
assert.match(r.stdout, /turn\.completed/, 'the trace tail carries the event stream resume now captures');
|
|
725
718
|
});
|
|
726
719
|
|
|
727
|
-
it('--resume-last reads the session id from the sidecar', () => {
|
|
720
|
+
it('--resume-last reads the session id from the sidecar', async () => {
|
|
728
721
|
const sb = makeSandbox();
|
|
729
722
|
writeFileSync(join(sb.repo, '.codex-last-session'), 'sess-from-sidecar\n');
|
|
730
|
-
const r = run(sb, { args: ['--resume-last', '-'], input: 'continue' });
|
|
723
|
+
const r = await run(sb, { args: ['--resume-last', '-'], input: 'continue' });
|
|
731
724
|
rmSync(sb.root, { recursive: true, force: true });
|
|
732
725
|
assert.equal(r.status, 0, r.stderr);
|
|
733
726
|
assert.match(r.argv, /(^|\n)sess-from-sidecar(\n|$)/);
|
|
734
727
|
});
|
|
735
728
|
|
|
736
|
-
it('--resume-last honours CODEX_SESSION_FILE', () => {
|
|
729
|
+
it('--resume-last honours CODEX_SESSION_FILE', async () => {
|
|
737
730
|
const sb = makeSandbox();
|
|
738
731
|
const custom = join(sb.repo, 'mysess');
|
|
739
732
|
writeFileSync(custom, 'sess-custom\n');
|
|
740
|
-
const r = run(sb, { args: ['--resume-last', '-'], input: 'go', env: { CODEX_SESSION_FILE: custom } });
|
|
733
|
+
const r = await run(sb, { args: ['--resume-last', '-'], input: 'go', env: { CODEX_SESSION_FILE: custom } });
|
|
741
734
|
rmSync(sb.root, { recursive: true, force: true });
|
|
742
735
|
assert.match(r.argv, /(^|\n)sess-custom(\n|$)/);
|
|
743
736
|
});
|
|
744
737
|
|
|
745
|
-
it('--resume-last with no sidecar STOPs (never guesses)', () => {
|
|
738
|
+
it('--resume-last with no sidecar STOPs (never guesses)', async () => {
|
|
746
739
|
const sb = makeSandbox();
|
|
747
|
-
const r = run(sb, { args: ['--resume-last', '-'], input: 'go' });
|
|
740
|
+
const r = await run(sb, { args: ['--resume-last', '-'], input: 'go' });
|
|
748
741
|
rmSync(sb.root, { recursive: true, force: true });
|
|
749
742
|
assert.notEqual(r.status, 0);
|
|
750
743
|
assert.match(r.stderr, /no session sidecar/);
|
|
751
744
|
});
|
|
752
745
|
|
|
753
|
-
it('--resume with no id STOPs', () => {
|
|
746
|
+
it('--resume with no id STOPs', async () => {
|
|
754
747
|
const sb = makeSandbox();
|
|
755
|
-
const r = run(sb, { args: ['--resume', '-'], input: 'go' });
|
|
748
|
+
const r = await run(sb, { args: ['--resume', '-'], input: 'go' });
|
|
756
749
|
rmSync(sb.root, { recursive: true, force: true });
|
|
757
750
|
assert.notEqual(r.status, 0);
|
|
758
751
|
assert.match(r.stderr, /--resume needs a <session-id>/);
|
|
759
752
|
});
|
|
760
753
|
|
|
761
|
-
it('rejects an empty resumed instruction', () => {
|
|
754
|
+
it('rejects an empty resumed instruction', async () => {
|
|
762
755
|
const sb = makeSandbox();
|
|
763
|
-
const r = run(sb, { args: ['--resume', 'sess-1', '-'], input: ' \n' });
|
|
756
|
+
const r = await run(sb, { args: ['--resume', 'sess-1', '-'], input: ' \n' });
|
|
764
757
|
rmSync(sb.root, { recursive: true, force: true });
|
|
765
758
|
assert.notEqual(r.status, 0);
|
|
766
759
|
assert.match(r.stderr, /empty resumed/);
|
|
767
760
|
});
|
|
768
761
|
|
|
769
|
-
it('resume takes no passthrough flags', () => {
|
|
762
|
+
it('resume takes no passthrough flags', async () => {
|
|
770
763
|
const sb = makeSandbox();
|
|
771
|
-
const r = run(sb, { args: ['--resume', 'sess-1', '-', '--', '--add-dir', '/x'], input: 'go' });
|
|
764
|
+
const r = await run(sb, { args: ['--resume', 'sess-1', '-', '--', '--add-dir', '/x'], input: 'go' });
|
|
772
765
|
rmSync(sb.root, { recursive: true, force: true });
|
|
773
766
|
assert.notEqual(r.status, 0);
|
|
774
767
|
assert.match(r.stderr, /resume modes take no extra flags/);
|
|
775
768
|
});
|
|
776
769
|
|
|
777
|
-
it('resume never sets --ephemeral', () => {
|
|
770
|
+
it('resume never sets --ephemeral', async () => {
|
|
778
771
|
const sb = makeSandbox();
|
|
779
|
-
const r = run(sb, { args: ['--resume', 'sess-1', '-'], input: 'go' });
|
|
772
|
+
const r = await run(sb, { args: ['--resume', 'sess-1', '-'], input: 'go' });
|
|
780
773
|
rmSync(sb.root, { recursive: true, force: true });
|
|
781
774
|
assert.doesNotMatch(r.argv, /--ephemeral/);
|
|
782
775
|
});
|
|
783
776
|
|
|
784
|
-
it('--resume-last with an EMPTY sidecar STOPs (no blank id)', () => {
|
|
777
|
+
it('--resume-last with an EMPTY sidecar STOPs (no blank id)', async () => {
|
|
785
778
|
const sb = makeSandbox();
|
|
786
779
|
writeFileSync(join(sb.repo, '.codex-last-session'), ' \n');
|
|
787
|
-
const r = run(sb, { args: ['--resume-last', '-'], input: 'go' });
|
|
780
|
+
const r = await run(sb, { args: ['--resume-last', '-'], input: 'go' });
|
|
788
781
|
rmSync(sb.root, { recursive: true, force: true });
|
|
789
782
|
assert.notEqual(r.status, 0);
|
|
790
783
|
assert.match(r.stderr, /sidecar.*is empty/);
|
|
791
784
|
});
|
|
792
785
|
|
|
793
|
-
it('resume still clears every *_API_KEY/OPENAI_BASE_URL and keeps --ignore-user-config', () => {
|
|
786
|
+
it('resume still clears every *_API_KEY/OPENAI_BASE_URL and keeps --ignore-user-config', async () => {
|
|
794
787
|
const sb = makeSandbox();
|
|
795
|
-
const r = run(sb, { args: ['--resume', 'sess-1', '-'], input: 'go', env: {
|
|
788
|
+
const r = await run(sb, { args: ['--resume', 'sess-1', '-'], input: 'go', env: {
|
|
796
789
|
OPENAI_API_KEY: 'sk-x', OPENAI_BASE_URL: 'http://evil.example', FOO_API_KEY: 'bar',
|
|
797
790
|
} });
|
|
798
791
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -803,29 +796,29 @@ describe('codex-exec.sh — resume entrypoint restates every invariant (3.1)', (
|
|
|
803
796
|
assert.match(r.argv, /(^|\n)--ignore-user-config(\n|$)/);
|
|
804
797
|
});
|
|
805
798
|
|
|
806
|
-
it('resume keeps the FULL restated policy plus the tier when set (2.3.0)', () => {
|
|
799
|
+
it('resume keeps the FULL restated policy plus the tier when set (2.3.0)', async () => {
|
|
807
800
|
const sb = makeSandbox();
|
|
808
|
-
const r = run(sb, { args: ['--resume', 'sess-1', '-'], input: 'go', env: { CODEX_SERVICE_TIER: 'priority' } });
|
|
801
|
+
const r = await run(sb, { args: ['--resume', 'sess-1', '-'], input: 'go', env: { CODEX_SERVICE_TIER: 'priority' } });
|
|
809
802
|
rmSync(sb.root, { recursive: true, force: true });
|
|
810
803
|
assert.equal(r.status, 0, r.stderr);
|
|
811
804
|
for (const inv of RESUME_INVARIANTS) assert.match(r.argv, inv, `resume argv must include ${inv}`);
|
|
812
805
|
assert.match(r.argv, /(^|\n)service_tier=priority(\n|$)/, 'a resume must not silently drop the tier');
|
|
813
806
|
});
|
|
814
807
|
|
|
815
|
-
it('resume without the tier carries no service_tier flag (2.3.0)', () => {
|
|
808
|
+
it('resume without the tier carries no service_tier flag (2.3.0)', async () => {
|
|
816
809
|
const sb = makeSandbox();
|
|
817
|
-
const r = run(sb, { args: ['--resume', 'sess-1', '-'], input: 'go' });
|
|
810
|
+
const r = await run(sb, { args: ['--resume', 'sess-1', '-'], input: 'go' });
|
|
818
811
|
rmSync(sb.root, { recursive: true, force: true });
|
|
819
812
|
assert.equal(r.status, 0, r.stderr);
|
|
820
813
|
assert.doesNotMatch(r.argv, /service_tier/);
|
|
821
814
|
});
|
|
822
815
|
});
|
|
823
816
|
|
|
824
|
-
describe('codex-exec.sh — enforced git-write boundary shim (3.2)', () => {
|
|
825
|
-
it('passes read-only verbs, blocks writes/unknown/config-writes; no env bypass', () => {
|
|
817
|
+
describe('codex-exec.sh — enforced git-write boundary shim (3.2)', { concurrency: 2 }, () => {
|
|
818
|
+
it('passes read-only verbs, blocks writes/unknown/config-writes; no env bypass', async () => {
|
|
826
819
|
const sb = makeSandbox();
|
|
827
820
|
const result = join(sb.repo, 'git-probe-result');
|
|
828
|
-
const r = run(sb, { env: { CODEX_FAKE_GIT_PROBE: '1', CODEX_FAKE_GIT_RESULT: result } });
|
|
821
|
+
const r = await run(sb, { env: { CODEX_FAKE_GIT_PROBE: '1', CODEX_FAKE_GIT_RESULT: result } });
|
|
829
822
|
const probe = existsSync(result) ? readFileSync(result, 'utf8') : '';
|
|
830
823
|
rmSync(sb.root, { recursive: true, force: true });
|
|
831
824
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -849,121 +842,121 @@ describe('codex-exec.sh — enforced git-write boundary shim (3.2)', () => {
|
|
|
849
842
|
assert.match(probe, /reflog_write=13/, 'git reflog (has write modes) is blocked');
|
|
850
843
|
});
|
|
851
844
|
|
|
852
|
-
it('the codex env carries no CODEX_REAL_GIT (bypass vector closed)', () => {
|
|
845
|
+
it('the codex env carries no CODEX_REAL_GIT (bypass vector closed)', async () => {
|
|
853
846
|
const sb = makeSandbox();
|
|
854
|
-
const r = run(sb);
|
|
847
|
+
const r = await run(sb);
|
|
855
848
|
rmSync(sb.root, { recursive: true, force: true });
|
|
856
849
|
assert.match(r.capEnv, /^CODEX_REAL_GIT=<unset>$/m);
|
|
857
850
|
});
|
|
858
851
|
});
|
|
859
852
|
|
|
860
|
-
describe('codex-exec.sh — environment preflight (fail fast, before a run)', () => {
|
|
861
|
-
it('STOPs with 127 when codex is not on PATH', () => {
|
|
853
|
+
describe('codex-exec.sh — environment preflight (fail fast, before a run)', { concurrency: 2 }, () => {
|
|
854
|
+
it('STOPs with 127 when codex is not on PATH', async () => {
|
|
862
855
|
const sb = makeSandbox();
|
|
863
856
|
// PATH WITHOUT the fake codex bin and without any real codex.
|
|
864
857
|
const path = farmFor(['codex']);
|
|
865
|
-
const r = run(sb, { path });
|
|
858
|
+
const r = await run(sb, { path });
|
|
866
859
|
rmSync(sb.root, { recursive: true, force: true });
|
|
867
860
|
assert.equal(r.status, 127);
|
|
868
861
|
assert.match(r.stderr, /'codex'.*not found on PATH/);
|
|
869
862
|
assert.equal(r.capStdin, '', 'codex must never be invoked');
|
|
870
863
|
});
|
|
871
864
|
|
|
872
|
-
it('STOPs with 127 when git is not on PATH', () => {
|
|
865
|
+
it('STOPs with 127 when git is not on PATH', async () => {
|
|
873
866
|
const sb = makeSandbox();
|
|
874
867
|
// codex present (sb.bin) but git stripped — exercises the type -P git guard.
|
|
875
868
|
const path = `${sb.bin}:${farmFor(['git'])}`;
|
|
876
|
-
const r = run(sb, { path });
|
|
869
|
+
const r = await run(sb, { path });
|
|
877
870
|
rmSync(sb.root, { recursive: true, force: true });
|
|
878
871
|
assert.equal(r.status, 127);
|
|
879
872
|
assert.match(r.stderr, /'git' not found on PATH/);
|
|
880
873
|
});
|
|
881
874
|
|
|
882
|
-
it('STOPs (exit 1) when codex is not on a ChatGPT subscription', () => {
|
|
875
|
+
it('STOPs (exit 1) when codex is not on a ChatGPT subscription', async () => {
|
|
883
876
|
const sb = makeSandbox();
|
|
884
|
-
const r = run(sb, { env: { CODEX_FAKE_LOGIN: 'Logged in using API key' } });
|
|
877
|
+
const r = await run(sb, { env: { CODEX_FAKE_LOGIN: 'Logged in using API key' } });
|
|
885
878
|
rmSync(sb.root, { recursive: true, force: true });
|
|
886
879
|
assert.equal(r.status, 1);
|
|
887
880
|
assert.match(r.stderr, /not on a ChatGPT subscription/);
|
|
888
881
|
assert.equal(r.capStdin, '', 'a wrong login must never spend a run');
|
|
889
882
|
});
|
|
890
883
|
|
|
891
|
-
it('STOPs (exit 2) when not inside a git work tree', () => {
|
|
884
|
+
it('STOPs (exit 2) when not inside a git work tree', async () => {
|
|
892
885
|
const sb = makeSandbox();
|
|
893
886
|
const nongit = join(sb.root, 'nongit');
|
|
894
887
|
mkdirSync(nongit, { recursive: true });
|
|
895
888
|
writeFileSync(join(nongit, 'AGENTS.md'), '# AGENTS\n'); // present, but the work-tree check fires first
|
|
896
|
-
const r = run(sb, { cwd: nongit });
|
|
889
|
+
const r = await run(sb, { cwd: nongit });
|
|
897
890
|
rmSync(sb.root, { recursive: true, force: true });
|
|
898
891
|
assert.equal(r.status, 2);
|
|
899
892
|
assert.match(r.stderr, /must run inside a git working tree/);
|
|
900
893
|
});
|
|
901
894
|
});
|
|
902
895
|
|
|
903
|
-
describe('codex-exec.sh — argument & prompt-source dispatch', () => {
|
|
904
|
-
it('prints usage and STOPs (exit 2) with no arguments', () => {
|
|
896
|
+
describe('codex-exec.sh — argument & prompt-source dispatch', { concurrency: 2 }, () => {
|
|
897
|
+
it('prints usage and STOPs (exit 2) with no arguments', async () => {
|
|
905
898
|
const sb = makeSandbox();
|
|
906
|
-
const r = run(sb, { args: [] });
|
|
899
|
+
const r = await run(sb, { args: [] });
|
|
907
900
|
rmSync(sb.root, { recursive: true, force: true });
|
|
908
901
|
assert.equal(r.status, 2);
|
|
909
902
|
assert.match(r.stderr, /usage:/);
|
|
910
903
|
});
|
|
911
904
|
|
|
912
|
-
it('STOPs on a stray extra argument without the -- separator', () => {
|
|
905
|
+
it('STOPs on a stray extra argument without the -- separator', async () => {
|
|
913
906
|
const sb = makeSandbox();
|
|
914
|
-
const r = run(sb, { args: ['-', 'stray'], input: 'go' });
|
|
907
|
+
const r = await run(sb, { args: ['-', 'stray'], input: 'go' });
|
|
915
908
|
rmSync(sb.root, { recursive: true, force: true });
|
|
916
909
|
assert.equal(r.status, 2);
|
|
917
910
|
assert.match(r.stderr, /unexpected argument 'stray'/);
|
|
918
911
|
});
|
|
919
912
|
|
|
920
|
-
it('passes an allowed (non-blocked) passthrough flag through to codex', () => {
|
|
913
|
+
it('passes an allowed (non-blocked) passthrough flag through to codex', async () => {
|
|
921
914
|
const sb = makeSandbox();
|
|
922
|
-
const r = run(sb, { args: ['-', '--', '--foobar', 'val'], input: 'go' });
|
|
915
|
+
const r = await run(sb, { args: ['-', '--', '--foobar', 'val'], input: 'go' });
|
|
923
916
|
rmSync(sb.root, { recursive: true, force: true });
|
|
924
917
|
assert.equal(r.status, 0, r.stderr);
|
|
925
918
|
assert.match(r.argv, /(^|\n)--foobar(\n|$)/, 'an unguarded flag reaches codex argv');
|
|
926
919
|
});
|
|
927
920
|
|
|
928
|
-
it('reads the task from a prompt FILE (not just stdin)', () => {
|
|
921
|
+
it('reads the task from a prompt FILE (not just stdin)', async () => {
|
|
929
922
|
const sb = makeSandbox();
|
|
930
923
|
writeFileSync(join(sb.repo, 'task.md'), 'PROMPT_FROM_FILE_MARKER\n');
|
|
931
|
-
const r = run(sb, { args: ['task.md'], input: '' });
|
|
924
|
+
const r = await run(sb, { args: ['task.md'], input: '' });
|
|
932
925
|
rmSync(sb.root, { recursive: true, force: true });
|
|
933
926
|
assert.equal(r.status, 0, r.stderr);
|
|
934
927
|
assert.match(r.capStdin, /PROMPT_FROM_FILE_MARKER/);
|
|
935
928
|
});
|
|
936
929
|
|
|
937
|
-
it('STOPs (exit 2) when the prompt path is neither - nor a file', () => {
|
|
930
|
+
it('STOPs (exit 2) when the prompt path is neither - nor a file', async () => {
|
|
938
931
|
const sb = makeSandbox();
|
|
939
|
-
const r = run(sb, { args: ['no-such-file.md'], input: '' });
|
|
932
|
+
const r = await run(sb, { args: ['no-such-file.md'], input: '' });
|
|
940
933
|
rmSync(sb.root, { recursive: true, force: true });
|
|
941
934
|
assert.equal(r.status, 2);
|
|
942
935
|
assert.match(r.stderr, /'no-such-file\.md' is not a file/);
|
|
943
936
|
});
|
|
944
937
|
|
|
945
|
-
it('STOPs on an empty task in normal mode (no "resumed" wording)', () => {
|
|
938
|
+
it('STOPs on an empty task in normal mode (no "resumed" wording)', async () => {
|
|
946
939
|
const sb = makeSandbox();
|
|
947
|
-
const r = run(sb, { args: ['-'], input: ' \n' });
|
|
940
|
+
const r = await run(sb, { args: ['-'], input: ' \n' });
|
|
948
941
|
rmSync(sb.root, { recursive: true, force: true });
|
|
949
942
|
assert.equal(r.status, 2);
|
|
950
943
|
assert.match(r.stderr, /empty plan\/instruction/);
|
|
951
944
|
assert.doesNotMatch(r.stderr, /resumed/, 'normal mode must not say "resumed"');
|
|
952
945
|
});
|
|
953
946
|
|
|
954
|
-
it('--resume-last with no prompt argument STOPs (missing <plan-file>)', () => {
|
|
947
|
+
it('--resume-last with no prompt argument STOPs (missing <plan-file>)', async () => {
|
|
955
948
|
const sb = makeSandbox();
|
|
956
|
-
const r = run(sb, { args: ['--resume-last'], input: '' });
|
|
949
|
+
const r = await run(sb, { args: ['--resume-last'], input: '' });
|
|
957
950
|
rmSync(sb.root, { recursive: true, force: true });
|
|
958
951
|
assert.equal(r.status, 2);
|
|
959
952
|
assert.match(r.stderr, /missing <plan-file/);
|
|
960
953
|
});
|
|
961
954
|
});
|
|
962
955
|
|
|
963
|
-
describe('codex-exec.sh — session id absent', () => {
|
|
964
|
-
it('writes no sidecar and no session line when codex emits no thread id', () => {
|
|
956
|
+
describe('codex-exec.sh — session id absent', { concurrency: 2 }, () => {
|
|
957
|
+
it('writes no sidecar and no session line when codex emits no thread id', async () => {
|
|
965
958
|
const sb = makeSandbox();
|
|
966
|
-
const r = run(sb, { env: { CODEX_FAKE_NO_THREAD: '1' } });
|
|
959
|
+
const r = await run(sb, { env: { CODEX_FAKE_NO_THREAD: '1' } });
|
|
967
960
|
const wrote = existsSync(join(sb.repo, '.codex-last-session'));
|
|
968
961
|
rmSync(sb.root, { recursive: true, force: true });
|
|
969
962
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -1051,8 +1044,8 @@ const extractArgCaseArms = (source) => {
|
|
|
1051
1044
|
};
|
|
1052
1045
|
const splitArms = (labels) => (labels ?? []).flatMap((l) => l.split('|'));
|
|
1053
1046
|
|
|
1054
|
-
describe('codex-exec.sh — --help contract (manifest-pinned)', () => {
|
|
1055
|
-
it('--help and -h exit 0 pre-preflight (no codex, no git, no AGENTS.md)', () => {
|
|
1047
|
+
describe('codex-exec.sh — --help contract (manifest-pinned)', { concurrency: 2 }, () => {
|
|
1048
|
+
it('--help and -h exit 0 pre-preflight (no codex, no git, no AGENTS.md)', async () => {
|
|
1056
1049
|
for (const arg of ['--help', '-h']) {
|
|
1057
1050
|
const r = runHelp(arg);
|
|
1058
1051
|
assert.equal(r.status, 0, `${arg}: ${r.stderr}`);
|
|
@@ -1061,26 +1054,26 @@ describe('codex-exec.sh — --help contract (manifest-pinned)', () => {
|
|
|
1061
1054
|
}
|
|
1062
1055
|
});
|
|
1063
1056
|
|
|
1064
|
-
it('Usage set-EQUALS the manifest invocation descriptors (both directions)', () => {
|
|
1057
|
+
it('Usage set-EQUALS the manifest invocation descriptors (both directions)', async () => {
|
|
1065
1058
|
const help = runHelp('--help').stdout;
|
|
1066
1059
|
const got = helpSection(help, 'Usage:').filter((l) => l.startsWith('codex-exec')).map(norm);
|
|
1067
1060
|
assert.ok(EXEC_CONTRACT.invocations.length > 0, 'manifest invocations must be non-empty');
|
|
1068
1061
|
setEq(got, EXEC_CONTRACT.invocations.map(norm), 'help Usage ⟷ manifest invocations');
|
|
1069
1062
|
});
|
|
1070
1063
|
|
|
1071
|
-
it('Grounding renders the manifest grounding note verbatim', () => {
|
|
1064
|
+
it('Grounding renders the manifest grounding note verbatim', async () => {
|
|
1072
1065
|
const help = runHelp('--help').stdout;
|
|
1073
1066
|
assert.equal(norm(helpSection(help, 'Grounding:').join(' ')), norm(EXEC_CONTRACT.grounding));
|
|
1074
1067
|
});
|
|
1075
1068
|
|
|
1076
|
-
it('Round-2 / resume set-EQUALS the manifest continue descriptors', () => {
|
|
1069
|
+
it('Round-2 / resume set-EQUALS the manifest continue descriptors', async () => {
|
|
1077
1070
|
const help = runHelp('--help').stdout;
|
|
1078
1071
|
const got = helpSection(help, 'Round-2 / resume:').filter((l) => l.startsWith('codex-exec')).map(norm);
|
|
1079
1072
|
assert.ok(EXEC_CONTRACT.continue.length > 0, 'manifest continue must be non-empty');
|
|
1080
1073
|
setEq(got, EXEC_CONTRACT.continue.map(norm), 'help continue ⟷ manifest continue');
|
|
1081
1074
|
});
|
|
1082
1075
|
|
|
1083
|
-
it('the guarded-passthrough TIERS set-EQUAL the manifest tiers (never a flat set)', () => {
|
|
1076
|
+
it('the guarded-passthrough TIERS set-EQUAL the manifest tiers (never a flat set)', async () => {
|
|
1084
1077
|
const help = runHelp('--help').stdout;
|
|
1085
1078
|
const section = helpSection(help, "Guarded passthrough after '--':");
|
|
1086
1079
|
const tier = (prefix) => {
|
|
@@ -1094,15 +1087,15 @@ describe('codex-exec.sh — --help contract (manifest-pinned)', () => {
|
|
|
1094
1087
|
setEq(tier('relaxed only under CODEX_PROBE=1:'), EXEC_CONTRACT.passthrough.probeRelaxed, 'help tier-2 ⟷ manifest probeRelaxed');
|
|
1095
1088
|
});
|
|
1096
1089
|
|
|
1097
|
-
it('Notes renders the manifest contract.notes verbatim (a typed contract key that MUST surface)', () => {
|
|
1090
|
+
it('Notes renders the manifest contract.notes verbatim (a typed contract key that MUST surface)', async () => {
|
|
1098
1091
|
const help = runHelp('--help').stdout;
|
|
1099
1092
|
assert.ok(EXEC_CONTRACT.notes.length > 0, 'manifest notes must be non-empty');
|
|
1100
1093
|
assert.equal(norm(helpSection(help, 'Notes:').join(' ')), norm(EXEC_CONTRACT.notes.join(' ')));
|
|
1101
1094
|
});
|
|
1102
1095
|
|
|
1103
|
-
it('--help after the -- separator is passthrough payload, never intercepted', () => {
|
|
1096
|
+
it('--help after the -- separator is passthrough payload, never intercepted', async () => {
|
|
1104
1097
|
const sb = makeSandbox();
|
|
1105
|
-
const r = run(sb, { args: ['-', '--', '--help'], input: 'go' });
|
|
1098
|
+
const r = await run(sb, { args: ['-', '--', '--help'], input: 'go' });
|
|
1106
1099
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1107
1100
|
assert.equal(r.status, 0, r.stderr);
|
|
1108
1101
|
assert.doesNotMatch(r.stdout, /Usage:/, 'help is keyed on the FIRST argument only');
|
|
@@ -1110,23 +1103,23 @@ describe('codex-exec.sh — --help contract (manifest-pinned)', () => {
|
|
|
1110
1103
|
});
|
|
1111
1104
|
});
|
|
1112
1105
|
|
|
1113
|
-
describe('codex-exec.sh — source-level reverse guard (parser arms ⟷ manifest)', () => {
|
|
1106
|
+
describe('codex-exec.sh — source-level reverse guard (parser arms ⟷ manifest)', { concurrency: 2 }, () => {
|
|
1114
1107
|
const arms = extractArgCaseArms(readFileSync(WRAPPER, 'utf8'));
|
|
1115
1108
|
|
|
1116
|
-
it('the first-arg entrypoints are exactly --help/-h + the manifest resume flags', () => {
|
|
1109
|
+
it('the first-arg entrypoints are exactly --help/-h + the manifest resume flags', async () => {
|
|
1117
1110
|
const declared = EXEC_CONTRACT.continue.map(leadingFlag);
|
|
1118
1111
|
assert.ok(declared.length > 0, 'manifest resume set must be non-empty');
|
|
1119
1112
|
setEq(new Set(splitArms(arms.get('"${1:-}"'))), new Set(['--help', '-h', ...declared]));
|
|
1120
1113
|
});
|
|
1121
1114
|
|
|
1122
|
-
it('the real passthrough tier arms equal the manifest tiers (git-shim heredoc excluded)', () => {
|
|
1115
|
+
it('the real passthrough tier arms equal the manifest tiers (git-shim heredoc excluded)', async () => {
|
|
1123
1116
|
const tierArms = arms.get('"$_arg"') ?? [];
|
|
1124
1117
|
assert.equal(tierArms.length, 2, 'exactly two passthrough tiers: always-blocked, probe-relaxed');
|
|
1125
1118
|
setEq(tierArms[0].split('|'), EXEC_CONTRACT.passthrough.blocked, 'tier-1 arm ⟷ manifest blocked');
|
|
1126
1119
|
setEq(tierArms[1].split('|'), EXEC_CONTRACT.passthrough.probeRelaxed, 'tier-2 arm ⟷ manifest probeRelaxed');
|
|
1127
1120
|
});
|
|
1128
1121
|
|
|
1129
|
-
it('the in-test tier samples cover every manifest tier pattern (behavioural forward guard)', () => {
|
|
1122
|
+
it('the in-test tier samples cover every manifest tier pattern (behavioural forward guard)', async () => {
|
|
1130
1123
|
// ALWAYS_BLOCKED / PROBE_RELAXABLE drive the real behaviour suite above; pin them
|
|
1131
1124
|
// to the manifest so a tier edit cannot leave the behavioural samples stale.
|
|
1132
1125
|
const sample = (patterns) => patterns.map((p) => p.replace(/\*$/, ''));
|
|
@@ -1164,19 +1157,19 @@ const consultsEnv = (source, name) =>
|
|
|
1164
1157
|
// The optional `@` prefix rides WITH the slot — the catalog declares the whole token a user types.
|
|
1165
1158
|
const SLOT_RE = /@?<[^<>]+>|\[[^[\]]*\]/g;
|
|
1166
1159
|
|
|
1167
|
-
describe('codex-exec.sh — mode catalog ⟷ wrapper reality (manifest-pinned)', () => {
|
|
1160
|
+
describe('codex-exec.sh — mode catalog ⟷ wrapper reality (manifest-pinned)', { concurrency: 2 }, () => {
|
|
1168
1161
|
const source = readFileSync(WRAPPER, 'utf8');
|
|
1169
1162
|
const catalog = MANIFEST.modeCatalog ?? [];
|
|
1170
1163
|
const execEntries = catalog.filter((e) => e.role === 'execute');
|
|
1171
1164
|
|
|
1172
|
-
it('the execute role is cataloged: one primary plus a continuation per resume flag', () => {
|
|
1165
|
+
it('the execute role is cataloged: one primary plus a continuation per resume flag', async () => {
|
|
1173
1166
|
const primaries = execEntries.filter((e) => e.kind === 'primary');
|
|
1174
1167
|
const continuations = execEntries.filter((e) => e.kind === 'continuation');
|
|
1175
1168
|
assert.equal(primaries.length, 1, 'codex-exec has exactly one primary drive form');
|
|
1176
1169
|
assert.equal(continuations.length, EXEC_CONTRACT.continue.length, 'one continuation entry per declared resume descriptor');
|
|
1177
1170
|
});
|
|
1178
1171
|
|
|
1179
|
-
it('every execute entry composes BY REFERENCE and every reference resolves', () => {
|
|
1172
|
+
it('every execute entry composes BY REFERENCE and every reference resolves', async () => {
|
|
1180
1173
|
for (const entry of execEntries) {
|
|
1181
1174
|
assert.ok(
|
|
1182
1175
|
Array.isArray(entry.invocationRefs) && entry.invocationRefs.length > 0,
|
|
@@ -1192,7 +1185,7 @@ describe('codex-exec.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
1192
1185
|
}
|
|
1193
1186
|
});
|
|
1194
1187
|
|
|
1195
|
-
it('every execute contract invocation is claimed by exactly ONE catalog entry (no uncataloged form)', () => {
|
|
1188
|
+
it('every execute contract invocation is claimed by exactly ONE catalog entry (no uncataloged form)', async () => {
|
|
1196
1189
|
const claims = execEntries.flatMap((e) => e.invocationRefs.map((r) => `${r.contractField}[${r.index}]`));
|
|
1197
1190
|
assert.equal(new Set(claims).size, claims.length, 'a contract invocation is claimed at most once');
|
|
1198
1191
|
const declared = [
|
|
@@ -1202,7 +1195,7 @@ describe('codex-exec.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
1202
1195
|
setEq(new Set(claims), declared, 'catalog claims ⟷ declared contract invocations');
|
|
1203
1196
|
});
|
|
1204
1197
|
|
|
1205
|
-
it('every env-hook the catalog aims at an execute mode is a real EXECUTABLE guard, not a mention', () => {
|
|
1198
|
+
it('every env-hook the catalog aims at an execute mode is a real EXECUTABLE guard, not a mention', async () => {
|
|
1206
1199
|
const hooks = catalog.filter((e) => e.kind === 'env-hook' && e.parents.some((p) => execEntries.some((x) => x.key === p)));
|
|
1207
1200
|
assert.ok(hooks.length > 0, 'CODEX_PROBE must be cataloged as an env-hook over codex-exec');
|
|
1208
1201
|
for (const hook of hooks) {
|
|
@@ -1213,7 +1206,7 @@ describe('codex-exec.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
1213
1206
|
}
|
|
1214
1207
|
});
|
|
1215
1208
|
|
|
1216
|
-
it('the catalog operand slots set-EQUAL the slots its rendered forms really carry (both directions)', () => {
|
|
1209
|
+
it('the catalog operand slots set-EQUAL the slots its rendered forms really carry (both directions)', async () => {
|
|
1217
1210
|
for (const entry of execEntries) {
|
|
1218
1211
|
const forms = entry.invocationRefs.map((r) => EXEC_CONTRACT[r.contractField][r.index]);
|
|
1219
1212
|
// The DEDUPLICATED UNION over every resolved form: `exec` legitimately spreads its slots across
|
|
@@ -1223,7 +1216,7 @@ describe('codex-exec.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
1223
1216
|
}
|
|
1224
1217
|
});
|
|
1225
1218
|
|
|
1226
|
-
it('the catalog claims CODEX_PROBE over the resume modes because the guard really precedes resume parsing', () => {
|
|
1219
|
+
it('the catalog claims CODEX_PROBE over the resume modes because the guard really precedes resume parsing', async () => {
|
|
1227
1220
|
// Verified in source: the quality guard runs BEFORE the resume dispatch, so a resume run is
|
|
1228
1221
|
// relaxed too — the catalog must say so, and this pins the ORDER the claim rests on.
|
|
1229
1222
|
const lines = executableLines(source);
|
|
@@ -1237,7 +1230,7 @@ describe('codex-exec.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
1237
1230
|
}
|
|
1238
1231
|
});
|
|
1239
1232
|
|
|
1240
|
-
it('CODEX_PROBE really relaxes the guard on EVERY execute parent the catalog claims (behavioural)', () => {
|
|
1233
|
+
it('CODEX_PROBE really relaxes the guard on EVERY execute parent the catalog claims (behavioural)', async () => {
|
|
1241
1234
|
// Source ORDER alone is not the claim: a branch bug after resume parsing would keep it green.
|
|
1242
1235
|
// Drive each claimed parent for real — the guard must stop the dispatch without the hook, and
|
|
1243
1236
|
// codex must really be reached with it (r.argv is non-empty only on a real invocation).
|
|
@@ -1256,13 +1249,13 @@ describe('codex-exec.sh — mode catalog ⟷ wrapper reality (manifest-pinned)',
|
|
|
1256
1249
|
assert.ok(drive[parent], `no behavioural drive for claimed parent "${parent}" — add one`);
|
|
1257
1250
|
|
|
1258
1251
|
const guarded = makeSandbox();
|
|
1259
|
-
const off = run(guarded, { ...drive[parent](guarded), env: { CODEX_MODEL: 'not-the-pinned-model' } });
|
|
1252
|
+
const off = await run(guarded, { ...drive[parent](guarded), env: { CODEX_MODEL: 'not-the-pinned-model' } });
|
|
1260
1253
|
rmSync(guarded.root, { recursive: true, force: true });
|
|
1261
1254
|
assert.equal(off.status, 2, `${parent}: the quality guard must refuse an off-pin model without the hook`);
|
|
1262
1255
|
assert.equal(off.argv, '', `${parent}: the guard must refuse BEFORE spending a run`);
|
|
1263
1256
|
|
|
1264
1257
|
const probed = makeSandbox();
|
|
1265
|
-
const on = run(probed, { ...drive[parent](probed), env: { CODEX_MODEL: 'not-the-pinned-model', CODEX_PROBE: '1' } });
|
|
1258
|
+
const on = await run(probed, { ...drive[parent](probed), env: { CODEX_MODEL: 'not-the-pinned-model', CODEX_PROBE: '1' } });
|
|
1266
1259
|
rmSync(probed.root, { recursive: true, force: true });
|
|
1267
1260
|
assert.equal(on.status, 0, `${parent}: CODEX_PROBE=1 must really relax the guard — the catalog claims it does`);
|
|
1268
1261
|
assert.notEqual(on.argv, '', `${parent}: CODEX_PROBE=1 must really reach codex, not merely exit 0`);
|
|
@@ -1286,55 +1279,55 @@ const writeSettings = (sb, text) => {
|
|
|
1286
1279
|
// chmod-based unreadability is void for root (root reads anything) — skip there.
|
|
1287
1280
|
const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
|
|
1288
1281
|
|
|
1289
|
-
describe('codex-exec.sh — service tier knob (bridges 2.3.0)', () => {
|
|
1290
|
-
it('default: no env, no file → NO service_tier flag in codex argv', () => {
|
|
1282
|
+
describe('codex-exec.sh — service tier knob (bridges 2.3.0)', { concurrency: 2 }, () => {
|
|
1283
|
+
it('default: no env, no file → NO service_tier flag in codex argv', async () => {
|
|
1291
1284
|
const sb = makeSandbox();
|
|
1292
|
-
const r = run(sb);
|
|
1285
|
+
const r = await run(sb);
|
|
1293
1286
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1294
1287
|
assert.equal(r.status, 0, r.stderr);
|
|
1295
1288
|
assert.doesNotMatch(r.argv, /service_tier/, 'default OFF: the flag must be absent');
|
|
1296
1289
|
assert.doesNotMatch(r.stderr, /bridge settings/, 'no file → no settings chatter');
|
|
1297
1290
|
});
|
|
1298
1291
|
|
|
1299
|
-
it('env CODEX_SERVICE_TIER=priority → -c service_tier=priority reaches codex argv', () => {
|
|
1292
|
+
it('env CODEX_SERVICE_TIER=priority → -c service_tier=priority reaches codex argv', async () => {
|
|
1300
1293
|
const sb = makeSandbox();
|
|
1301
|
-
const r = run(sb, { env: { CODEX_SERVICE_TIER: 'priority' } });
|
|
1294
|
+
const r = await run(sb, { env: { CODEX_SERVICE_TIER: 'priority' } });
|
|
1302
1295
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1303
1296
|
assert.equal(r.status, 0, r.stderr);
|
|
1304
1297
|
assert.match(r.argv, /(^|\n)service_tier=priority(\n|$)/);
|
|
1305
1298
|
});
|
|
1306
1299
|
|
|
1307
|
-
it('a file-set tier lands (file wins over the built-in default)', () => {
|
|
1300
|
+
it('a file-set tier lands (file wins over the built-in default)', async () => {
|
|
1308
1301
|
const sb = makeSandbox();
|
|
1309
1302
|
writeSettings(sb, 'CODEX_SERVICE_TIER=priority\n');
|
|
1310
|
-
const r = run(sb);
|
|
1303
|
+
const r = await run(sb);
|
|
1311
1304
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1312
1305
|
assert.equal(r.status, 0, r.stderr);
|
|
1313
1306
|
assert.match(r.argv, /(^|\n)service_tier=priority(\n|$)/);
|
|
1314
1307
|
});
|
|
1315
1308
|
|
|
1316
|
-
it('an EXPLICITLY EMPTY env (CODEX_SERVICE_TIER=) disables a file-set tier for one run', () => {
|
|
1309
|
+
it('an EXPLICITLY EMPTY env (CODEX_SERVICE_TIER=) disables a file-set tier for one run', async () => {
|
|
1317
1310
|
const sb = makeSandbox();
|
|
1318
1311
|
writeSettings(sb, 'CODEX_SERVICE_TIER=priority\n');
|
|
1319
|
-
const r = run(sb, { env: { CODEX_SERVICE_TIER: '' } });
|
|
1312
|
+
const r = await run(sb, { env: { CODEX_SERVICE_TIER: '' } });
|
|
1320
1313
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1321
1314
|
assert.equal(r.status, 0, r.stderr);
|
|
1322
1315
|
assert.doesNotMatch(r.argv, /service_tier/, 'env wins over file — empty means knob off');
|
|
1323
1316
|
});
|
|
1324
1317
|
|
|
1325
|
-
it('an invalid env tier warns and runs on the standard tier (never passed to codex)', () => {
|
|
1318
|
+
it('an invalid env tier warns and runs on the standard tier (never passed to codex)', async () => {
|
|
1326
1319
|
const sb = makeSandbox();
|
|
1327
|
-
const r = run(sb, { env: { CODEX_SERVICE_TIER: 'turbo' } });
|
|
1320
|
+
const r = await run(sb, { env: { CODEX_SERVICE_TIER: 'turbo' } });
|
|
1328
1321
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1329
1322
|
assert.equal(r.status, 0, r.stderr);
|
|
1330
1323
|
assert.match(r.stderr, /not a supported service tier/);
|
|
1331
1324
|
assert.doesNotMatch(r.argv, /service_tier/, 'an unvalidated value must never reach codex');
|
|
1332
1325
|
});
|
|
1333
1326
|
|
|
1334
|
-
it('an invalid file tier warns and falls back to the built-in default', () => {
|
|
1327
|
+
it('an invalid file tier warns and falls back to the built-in default', async () => {
|
|
1335
1328
|
const sb = makeSandbox();
|
|
1336
1329
|
writeSettings(sb, 'CODEX_SERVICE_TIER=turbo\n');
|
|
1337
|
-
const r = run(sb);
|
|
1330
|
+
const r = await run(sb);
|
|
1338
1331
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1339
1332
|
assert.equal(r.status, 0, r.stderr);
|
|
1340
1333
|
assert.match(r.stderr, /invalid value 'turbo'/);
|
|
@@ -1343,7 +1336,7 @@ describe('codex-exec.sh — service tier knob (bridges 2.3.0)', () => {
|
|
|
1343
1336
|
|
|
1344
1337
|
});
|
|
1345
1338
|
|
|
1346
|
-
describe('codex-exec.sh — bridge settings file semantics (bridges 2.3.0)', { concurrency:
|
|
1339
|
+
describe('codex-exec.sh — bridge settings file semantics (bridges 2.3.0)', { concurrency: 2 }, () => {
|
|
1347
1340
|
it('env overrides file: CODEX_HARD_TIMEOUT env=2 file=9999 → killed at the env cap', async () => {
|
|
1348
1341
|
const sb = makeSandbox();
|
|
1349
1342
|
writeSettings(sb, 'CODEX_HARD_TIMEOUT=9999\n');
|
|
@@ -1362,20 +1355,20 @@ describe('codex-exec.sh — bridge settings file semantics (bridges 2.3.0)', { c
|
|
|
1362
1355
|
assert.match(r.stderr, /exceeded the hard cap CODEX_HARD_TIMEOUT=2s/);
|
|
1363
1356
|
});
|
|
1364
1357
|
|
|
1365
|
-
it("another wrapper's / another bridge's valid key is skipped silently", () => {
|
|
1358
|
+
it("another wrapper's / another bridge's valid key is skipped silently", async () => {
|
|
1366
1359
|
const sb = makeSandbox();
|
|
1367
1360
|
writeSettings(sb, 'CODEX_REVIEW_MAX_TOTAL_BYTES=100\nAGY_HARD_TIMEOUT=30m\nAGY_REVIEW_ALLOW_ADDDIR=1\n');
|
|
1368
|
-
const r = run(sb);
|
|
1361
|
+
const r = await run(sb);
|
|
1369
1362
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1370
1363
|
assert.equal(r.status, 0, r.stderr);
|
|
1371
1364
|
assert.doesNotMatch(r.stderr, /bridge settings/, 'a recognized non-applied key earns NO warning');
|
|
1372
1365
|
assert.match(r.stdout, /FAKE_FINAL_MESSAGE/);
|
|
1373
1366
|
});
|
|
1374
1367
|
|
|
1375
|
-
it('a truly unknown key warns ONCE naming the file; the run is unaffected', () => {
|
|
1368
|
+
it('a truly unknown key warns ONCE naming the file; the run is unaffected', async () => {
|
|
1376
1369
|
const sb = makeSandbox();
|
|
1377
1370
|
writeSettings(sb, 'TOTALLY_UNKNOWN=1\nTOTALLY_UNKNOWN=2\n');
|
|
1378
|
-
const r = run(sb);
|
|
1371
|
+
const r = await run(sb);
|
|
1379
1372
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1380
1373
|
assert.equal(r.status, 0, r.stderr);
|
|
1381
1374
|
const warns = r.stderr.match(/unknown key 'TOTALLY_UNKNOWN'/g) ?? [];
|
|
@@ -1384,30 +1377,30 @@ describe('codex-exec.sh — bridge settings file semantics (bridges 2.3.0)', { c
|
|
|
1384
1377
|
assert.match(r.stdout, /FAKE_FINAL_MESSAGE/);
|
|
1385
1378
|
});
|
|
1386
1379
|
|
|
1387
|
-
it('duplicate key → the LAST occurrence wins (invalid then valid → applied, no warning)', () => {
|
|
1380
|
+
it('duplicate key → the LAST occurrence wins (invalid then valid → applied, no warning)', async () => {
|
|
1388
1381
|
const sb = makeSandbox();
|
|
1389
1382
|
writeSettings(sb, 'CODEX_SERVICE_TIER=bogus\nCODEX_SERVICE_TIER=priority\n');
|
|
1390
|
-
const r = run(sb);
|
|
1383
|
+
const r = await run(sb);
|
|
1391
1384
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1392
1385
|
assert.equal(r.status, 0, r.stderr);
|
|
1393
1386
|
assert.match(r.argv, /(^|\n)service_tier=priority(\n|$)/);
|
|
1394
1387
|
assert.doesNotMatch(r.stderr, /invalid value/, 'only the LAST occurrence is the value');
|
|
1395
1388
|
});
|
|
1396
1389
|
|
|
1397
|
-
it('duplicate key → the LAST occurrence wins (valid then invalid → warned + default)', () => {
|
|
1390
|
+
it('duplicate key → the LAST occurrence wins (valid then invalid → warned + default)', async () => {
|
|
1398
1391
|
const sb = makeSandbox();
|
|
1399
1392
|
writeSettings(sb, 'CODEX_SERVICE_TIER=priority\nCODEX_SERVICE_TIER=bogus\n');
|
|
1400
|
-
const r = run(sb);
|
|
1393
|
+
const r = await run(sb);
|
|
1401
1394
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1402
1395
|
assert.equal(r.status, 0, r.stderr);
|
|
1403
1396
|
assert.match(r.stderr, /invalid value 'bogus'/);
|
|
1404
1397
|
assert.doesNotMatch(r.argv, /service_tier/);
|
|
1405
1398
|
});
|
|
1406
1399
|
|
|
1407
|
-
it('malformed lines warn and are ignored; comments and blank lines are silent', () => {
|
|
1400
|
+
it('malformed lines warn and are ignored; comments and blank lines are silent', async () => {
|
|
1408
1401
|
const sb = makeSandbox();
|
|
1409
1402
|
writeSettings(sb, '# a comment\n\nNOT A KEY VALUE LINE\nCODEX_SERVICE_TIER=priority\n');
|
|
1410
|
-
const r = run(sb);
|
|
1403
|
+
const r = await run(sb);
|
|
1411
1404
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1412
1405
|
assert.equal(r.status, 0, r.stderr);
|
|
1413
1406
|
assert.match(r.stderr, /malformed line/);
|
|
@@ -1416,18 +1409,18 @@ describe('codex-exec.sh — bridge settings file semantics (bridges 2.3.0)', { c
|
|
|
1416
1409
|
assert.match(r.argv, /(^|\n)service_tier=priority(\n|$)/, 'valid lines still apply');
|
|
1417
1410
|
});
|
|
1418
1411
|
|
|
1419
|
-
it('an existing-but-unreadable file warns loudly and falls back to built-ins', { skip: isRoot }, () => {
|
|
1412
|
+
it('an existing-but-unreadable file warns loudly and falls back to built-ins', { skip: isRoot }, async () => {
|
|
1420
1413
|
const sb = makeSandbox();
|
|
1421
1414
|
const file = writeSettings(sb, 'CODEX_SERVICE_TIER=priority\n');
|
|
1422
1415
|
chmodSync(file, 0o000);
|
|
1423
|
-
const r = run(sb);
|
|
1416
|
+
const r = await run(sb);
|
|
1424
1417
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1425
1418
|
assert.equal(r.status, 0, r.stderr);
|
|
1426
1419
|
assert.match(r.stderr, /unreadable/);
|
|
1427
1420
|
assert.doesNotMatch(r.argv, /service_tier/, 'an unreadable file must yield built-in defaults');
|
|
1428
1421
|
});
|
|
1429
1422
|
|
|
1430
|
-
it('a settings line can NEVER execute code (command-substitution payload inert)', () => {
|
|
1423
|
+
it('a settings line can NEVER execute code (command-substitution payload inert)', async () => {
|
|
1431
1424
|
const sb = makeSandbox();
|
|
1432
1425
|
const pwned = join(sb.repo, 'pwned');
|
|
1433
1426
|
const pwned2 = join(sb.repo, 'pwned2');
|
|
@@ -1435,7 +1428,7 @@ describe('codex-exec.sh — bridge settings file semantics (bridges 2.3.0)', { c
|
|
|
1435
1428
|
sb,
|
|
1436
1429
|
`CODEX_SERVICE_TIER=$(touch ${pwned})\nEVIL_KEY=\`touch ${pwned2}\`\n`,
|
|
1437
1430
|
);
|
|
1438
|
-
const r = run(sb);
|
|
1431
|
+
const r = await run(sb);
|
|
1439
1432
|
const executed = existsSync(pwned) || existsSync(pwned2);
|
|
1440
1433
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1441
1434
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -1443,10 +1436,10 @@ describe('codex-exec.sh — bridge settings file semantics (bridges 2.3.0)', { c
|
|
|
1443
1436
|
assert.doesNotMatch(r.argv, /service_tier/, 'the payload value must fail validation');
|
|
1444
1437
|
});
|
|
1445
1438
|
|
|
1446
|
-
it('a DIRECTORY at the settings path warns loudly and falls back to built-ins (no crash)', () => {
|
|
1439
|
+
it('a DIRECTORY at the settings path warns loudly and falls back to built-ins (no crash)', async () => {
|
|
1447
1440
|
const sb = makeSandbox();
|
|
1448
1441
|
mkdirSync(join(sb.repo, '.config', 'agent-workflow', 'bridge-settings.conf'), { recursive: true });
|
|
1449
|
-
const r = run(sb);
|
|
1442
|
+
const r = await run(sb);
|
|
1450
1443
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1451
1444
|
assert.equal(r.status, 0, `a directory must degrade honestly, not kill the run: ${r.stderr}`);
|
|
1452
1445
|
assert.match(r.stderr, /unreadable or not a regular file/);
|
|
@@ -1454,26 +1447,26 @@ describe('codex-exec.sh — bridge settings file semantics (bridges 2.3.0)', { c
|
|
|
1454
1447
|
assert.match(r.stdout, /FAKE_FINAL_MESSAGE/, 'the run must proceed on built-ins');
|
|
1455
1448
|
});
|
|
1456
1449
|
|
|
1457
|
-
it('a FIFO at the settings path warns and falls back (never opened — no pre-timeout hang)', () => {
|
|
1450
|
+
it('a FIFO at the settings path warns and falls back (never opened — no pre-timeout hang)', async () => {
|
|
1458
1451
|
const sb = makeSandbox();
|
|
1459
1452
|
const dir = join(sb.repo, '.config', 'agent-workflow');
|
|
1460
1453
|
mkdirSync(dir, { recursive: true });
|
|
1461
1454
|
const fifo = join(dir, 'bridge-settings.conf');
|
|
1462
1455
|
const mk = spawnSync('mkfifo', [fifo]);
|
|
1463
1456
|
if (mk.status !== 0) { rmSync(sb.root, { recursive: true, force: true }); return; } // no mkfifo here — the directory case covers the class
|
|
1464
|
-
const r = run(sb);
|
|
1457
|
+
const r = await run(sb);
|
|
1465
1458
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1466
1459
|
assert.equal(r.status, 0, r.stderr);
|
|
1467
1460
|
assert.match(r.stderr, /unreadable or not a regular file/);
|
|
1468
1461
|
assert.match(r.stdout, /FAKE_FINAL_MESSAGE/);
|
|
1469
1462
|
});
|
|
1470
1463
|
|
|
1471
|
-
it('XDG_CONFIG_HOME relocates the settings file', () => {
|
|
1464
|
+
it('XDG_CONFIG_HOME relocates the settings file', async () => {
|
|
1472
1465
|
const sb = makeSandbox();
|
|
1473
1466
|
const xdg = join(sb.root, 'xdg');
|
|
1474
1467
|
mkdirSync(join(xdg, 'agent-workflow'), { recursive: true });
|
|
1475
1468
|
writeFileSync(join(xdg, 'agent-workflow', 'bridge-settings.conf'), 'CODEX_SERVICE_TIER=priority\n');
|
|
1476
|
-
const r = run(sb, { env: { XDG_CONFIG_HOME: xdg } });
|
|
1469
|
+
const r = await run(sb, { env: { XDG_CONFIG_HOME: xdg } });
|
|
1477
1470
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1478
1471
|
assert.equal(r.status, 0, r.stderr);
|
|
1479
1472
|
assert.match(r.argv, /(^|\n)service_tier=priority(\n|$)/);
|
|
@@ -1491,8 +1484,8 @@ const SIBLING_MANIFEST = JSON.parse(readFileSync(join(HERE, '..', '..', 'antigra
|
|
|
1491
1484
|
const ALL_SETTINGS = [...(MANIFEST.settings ?? []), ...(SIBLING_MANIFEST.settings ?? [])];
|
|
1492
1485
|
const SETTINGS_CMD = 'codex-exec';
|
|
1493
1486
|
|
|
1494
|
-
describe('codex-exec.sh — settings surface ⟷ manifest (D6, manifest-pinned)', () => {
|
|
1495
|
-
it('--help Settings section keys set-EQUAL the manifest appliesTo subset', () => {
|
|
1487
|
+
describe('codex-exec.sh — settings surface ⟷ manifest (D6, manifest-pinned)', { concurrency: 2 }, () => {
|
|
1488
|
+
it('--help Settings section keys set-EQUAL the manifest appliesTo subset', async () => {
|
|
1496
1489
|
const help = runHelp('--help').stdout;
|
|
1497
1490
|
const section = helpSection(help, SETTINGS_HEADER);
|
|
1498
1491
|
const got = section.filter((l) => /^[A-Z][A-Z0-9_]+ —/.test(l)).map((l) => l.split(' ')[0]);
|
|
@@ -1504,14 +1497,14 @@ describe('codex-exec.sh — settings surface ⟷ manifest (D6, manifest-pinned)'
|
|
|
1504
1497
|
|
|
1505
1498
|
const source = readFileSync(WRAPPER, 'utf8');
|
|
1506
1499
|
|
|
1507
|
-
it('aw_settings_known carries exactly the UNION of both bridges settings keys', () => {
|
|
1500
|
+
it('aw_settings_known carries exactly the UNION of both bridges settings keys', async () => {
|
|
1508
1501
|
const m = source.match(/aw_settings_known\(\) \{\n case " ([^"]+) " in/);
|
|
1509
1502
|
assert.ok(m, 'aw_settings_known registry case not found');
|
|
1510
1503
|
assert.ok(ALL_SETTINGS.length >= 5, 'both manifests must contribute settings');
|
|
1511
1504
|
setEq(m[1].trim().split(/\s+/), ALL_SETTINGS.map((s) => s.key), 'shell registry ⟷ manifest union');
|
|
1512
1505
|
});
|
|
1513
1506
|
|
|
1514
|
-
it('AW_SETTINGS_APPLIED equals the manifest appliesTo subset for this wrapper', () => {
|
|
1507
|
+
it('AW_SETTINGS_APPLIED equals the manifest appliesTo subset for this wrapper', async () => {
|
|
1515
1508
|
const m = source.match(/^AW_SETTINGS_APPLIED="([^"]*)"$/m);
|
|
1516
1509
|
assert.ok(m, 'AW_SETTINGS_APPLIED not found');
|
|
1517
1510
|
const want = ALL_SETTINGS.filter((s) => s.appliesTo.includes(SETTINGS_CMD)).map((s) => s.key);
|
|
@@ -1519,7 +1512,7 @@ describe('codex-exec.sh — settings surface ⟷ manifest (D6, manifest-pinned)'
|
|
|
1519
1512
|
setEq(m[1].trim().split(/\s+/), want, 'applied subset ⟷ manifest appliesTo');
|
|
1520
1513
|
});
|
|
1521
1514
|
|
|
1522
|
-
it('aw_settings_valid arms carry the manifest typed constants per key', () => {
|
|
1515
|
+
it('aw_settings_valid arms carry the manifest typed constants per key', async () => {
|
|
1523
1516
|
const body = source.match(/aw_settings_valid\(\) \{[\s\S]*?\n\}/);
|
|
1524
1517
|
assert.ok(body, 'aw_settings_valid not found');
|
|
1525
1518
|
const armKeys = [...body[0].matchAll(/^ ([A-Z][A-Z0-9_]*)\)/gm)].map((x) => x[1]);
|
|
@@ -1542,12 +1535,12 @@ describe('codex-exec.sh — settings surface ⟷ manifest (D6, manifest-pinned)'
|
|
|
1542
1535
|
});
|
|
1543
1536
|
});
|
|
1544
1537
|
|
|
1545
|
-
describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
1538
|
+
describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', { concurrency: 2 }, () => {
|
|
1546
1539
|
const banners = (stderr) => stderr.split('\n').filter((l) => l.startsWith('exec posture: '));
|
|
1547
1540
|
|
|
1548
|
-
it('ONE banner line carries the ACTUAL {model, effort, tier, sandbox, session, timeout} on a fresh run', () => {
|
|
1541
|
+
it('ONE banner line carries the ACTUAL {model, effort, tier, sandbox, session, timeout} on a fresh run', async () => {
|
|
1549
1542
|
const sb = makeSandbox();
|
|
1550
|
-
const r = run(sb, {});
|
|
1543
|
+
const r = await run(sb, {});
|
|
1551
1544
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1552
1545
|
assert.equal(r.status, 0, r.stderr);
|
|
1553
1546
|
const lines = banners(r.stderr);
|
|
@@ -1556,17 +1549,17 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
|
1556
1549
|
'exec posture: model=gpt-5.6-sol effort=xhigh tier=standard sandbox=workspace-write session=fresh timeout=3600s');
|
|
1557
1550
|
});
|
|
1558
1551
|
|
|
1559
|
-
it('an ARMED Fast tier rides the banner (tier=priority)', () => {
|
|
1552
|
+
it('an ARMED Fast tier rides the banner (tier=priority)', async () => {
|
|
1560
1553
|
const sb = makeSandbox();
|
|
1561
|
-
const r = run(sb, { env: { CODEX_SERVICE_TIER: 'priority' } });
|
|
1554
|
+
const r = await run(sb, { env: { CODEX_SERVICE_TIER: 'priority' } });
|
|
1562
1555
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1563
1556
|
assert.equal(r.status, 0, r.stderr);
|
|
1564
1557
|
assert.match(r.stderr, /^exec posture: .* tier=priority .*$/m);
|
|
1565
1558
|
});
|
|
1566
1559
|
|
|
1567
|
-
it('a resume banner carries the RESOLVED session id (explicit --resume)', () => {
|
|
1560
|
+
it('a resume banner carries the RESOLVED session id (explicit --resume)', async () => {
|
|
1568
1561
|
const sb = makeSandbox();
|
|
1569
|
-
const r = run(sb, { args: ['--resume', 'sess-xyz', '-'] });
|
|
1562
|
+
const r = await run(sb, { args: ['--resume', 'sess-xyz', '-'] });
|
|
1570
1563
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1571
1564
|
assert.equal(r.status, 0, r.stderr);
|
|
1572
1565
|
const lines = banners(r.stderr);
|
|
@@ -1574,19 +1567,19 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
|
1574
1567
|
assert.match(lines[0], / session=resume:sess-xyz /, 'the banner names the resolved id');
|
|
1575
1568
|
});
|
|
1576
1569
|
|
|
1577
|
-
it('--resume-last resolves the sidecar id into the banner', () => {
|
|
1570
|
+
it('--resume-last resolves the sidecar id into the banner', async () => {
|
|
1578
1571
|
const sb = makeSandbox();
|
|
1579
1572
|
writeFileSync(join(sb.repo, '.codex-last-session'), 'sess-from-sidecar\n');
|
|
1580
|
-
const r = run(sb, { args: ['--resume-last', '-'] });
|
|
1573
|
+
const r = await run(sb, { args: ['--resume-last', '-'] });
|
|
1581
1574
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1582
1575
|
assert.equal(r.status, 0, r.stderr);
|
|
1583
1576
|
assert.match(r.stderr, / session=resume:sess-from-sidecar /);
|
|
1584
1577
|
});
|
|
1585
1578
|
|
|
1586
|
-
it('a HOSTILE/malformed EXPLICIT session id refuses pre-spend (no codex invocation)', () => {
|
|
1579
|
+
it('a HOSTILE/malformed EXPLICIT session id refuses pre-spend (no codex invocation)', async () => {
|
|
1587
1580
|
for (const hostile of ['evil;rm -rf /', 'a b', `x${String.fromCharCode(1)}y`]) {
|
|
1588
1581
|
const sb = makeSandbox();
|
|
1589
|
-
const r = run(sb, { args: ['--resume', hostile, '-'] });
|
|
1582
|
+
const r = await run(sb, { args: ['--resume', hostile, '-'] });
|
|
1590
1583
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1591
1584
|
assert.notEqual(r.status, 0, `must refuse: ${JSON.stringify(hostile)}`);
|
|
1592
1585
|
assert.equal(r.capStdin, '', 'codex is never invoked');
|
|
@@ -1594,21 +1587,21 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
|
1594
1587
|
}
|
|
1595
1588
|
});
|
|
1596
1589
|
|
|
1597
|
-
it('a HOSTILE SIDECAR-READ session id refuses pre-spend the same way', () => {
|
|
1590
|
+
it('a HOSTILE SIDECAR-READ session id refuses pre-spend the same way', async () => {
|
|
1598
1591
|
const sb = makeSandbox();
|
|
1599
1592
|
writeFileSync(join(sb.repo, '.codex-last-session'), 'evil$(touch pwned)\n');
|
|
1600
|
-
const r = run(sb, { args: ['--resume-last', '-'] });
|
|
1593
|
+
const r = await run(sb, { args: ['--resume-last', '-'] });
|
|
1601
1594
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1602
1595
|
assert.notEqual(r.status, 0);
|
|
1603
1596
|
assert.equal(r.capStdin, '', 'codex is never invoked');
|
|
1604
1597
|
assert.match(r.stderr, /session id/i);
|
|
1605
1598
|
});
|
|
1606
1599
|
|
|
1607
|
-
it('a FLAG-SHAPED sidecar id (leading dash) refuses at the grammar — never reaches codex as an option', () => {
|
|
1600
|
+
it('a FLAG-SHAPED sidecar id (leading dash) refuses at the grammar — never reaches codex as an option', async () => {
|
|
1608
1601
|
for (const bad of ['--last\n', '-x\n']) {
|
|
1609
1602
|
const sb = makeSandbox();
|
|
1610
1603
|
writeFileSync(join(sb.repo, '.codex-last-session'), bad);
|
|
1611
|
-
const r = run(sb, { args: ['--resume-last', '-'] });
|
|
1604
|
+
const r = await run(sb, { args: ['--resume-last', '-'] });
|
|
1612
1605
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1613
1606
|
assert.notEqual(r.status, 0, `must refuse: ${JSON.stringify(bad)}`);
|
|
1614
1607
|
assert.equal(r.capStdin, '', 'codex is never invoked');
|
|
@@ -1616,30 +1609,30 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
|
1616
1609
|
}
|
|
1617
1610
|
});
|
|
1618
1611
|
|
|
1619
|
-
it('a sidecar carrying a NUL byte refuses pre-spend — bash would silently repair it into a valid id', () => {
|
|
1612
|
+
it('a sidecar carrying a NUL byte refuses pre-spend — bash would silently repair it into a valid id', async () => {
|
|
1620
1613
|
const sb = makeSandbox();
|
|
1621
1614
|
writeFileSync(join(sb.repo, '.codex-last-session'), Buffer.from('sess-\0target\n', 'binary'));
|
|
1622
|
-
const r = run(sb, { args: ['--resume-last', '-'] });
|
|
1615
|
+
const r = await run(sb, { args: ['--resume-last', '-'] });
|
|
1623
1616
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1624
1617
|
assert.notEqual(r.status, 0);
|
|
1625
1618
|
assert.equal(r.capStdin, '', 'codex is never invoked');
|
|
1626
1619
|
assert.match(r.stderr, /NUL/i, 'named as the NUL class — the raw bytes are checked before the shell variable');
|
|
1627
1620
|
});
|
|
1628
1621
|
|
|
1629
|
-
it('a valid id containing the ASCII digit 0 gets no false NUL refusal', () => {
|
|
1622
|
+
it('a valid id containing the ASCII digit 0 gets no false NUL refusal', async () => {
|
|
1630
1623
|
const sb = makeSandbox();
|
|
1631
1624
|
writeFileSync(join(sb.repo, '.codex-last-session'), 'sess-01\n');
|
|
1632
|
-
const r = run(sb, { args: ['--resume-last', '-'] });
|
|
1625
|
+
const r = await run(sb, { args: ['--resume-last', '-'] });
|
|
1633
1626
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1634
1627
|
assert.equal(r.status, 0, r.stderr);
|
|
1635
1628
|
assert.match(r.stderr, / session=resume:sess-01 /);
|
|
1636
1629
|
});
|
|
1637
1630
|
|
|
1638
|
-
it('a sidecar id with inner WHITESPACE refuses — never silently repaired into a different id', () => {
|
|
1631
|
+
it('a sidecar id with inner WHITESPACE refuses — never silently repaired into a different id', async () => {
|
|
1639
1632
|
for (const bad of ['sess bad\n', 'sess\tbad\n']) {
|
|
1640
1633
|
const sb = makeSandbox();
|
|
1641
1634
|
writeFileSync(join(sb.repo, '.codex-last-session'), bad);
|
|
1642
|
-
const r = run(sb, { args: ['--resume-last', '-'] });
|
|
1635
|
+
const r = await run(sb, { args: ['--resume-last', '-'] });
|
|
1643
1636
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1644
1637
|
assert.notEqual(r.status, 0, `must refuse: ${JSON.stringify(bad)}`);
|
|
1645
1638
|
assert.equal(r.capStdin, '', 'codex is never invoked');
|
|
@@ -1647,7 +1640,7 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
|
1647
1640
|
}
|
|
1648
1641
|
});
|
|
1649
1642
|
|
|
1650
|
-
it('a banner field carrying CONTROL BYTES refuses pre-spend (model / effort / tier / timeout / DEL)', () => {
|
|
1643
|
+
it('a banner field carrying CONTROL BYTES refuses pre-spend (model / effort / tier / timeout / DEL)', async () => {
|
|
1651
1644
|
const cases = [
|
|
1652
1645
|
{ CODEX_MODEL: `gpt-5.6-sol${String.fromCharCode(1)}` },
|
|
1653
1646
|
{ CODEX_EFFORT: `xhigh${String.fromCharCode(2)}` },
|
|
@@ -1657,7 +1650,7 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
|
1657
1650
|
];
|
|
1658
1651
|
for (const env of cases) {
|
|
1659
1652
|
const sb = makeSandbox();
|
|
1660
|
-
const r = run(sb, { env });
|
|
1653
|
+
const r = await run(sb, { env });
|
|
1661
1654
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1662
1655
|
assert.notEqual(r.status, 0, `must refuse: ${JSON.stringify(env)}`);
|
|
1663
1656
|
assert.equal(r.capStdin, '', 'codex is never invoked');
|
|
@@ -1665,17 +1658,17 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
|
1665
1658
|
}
|
|
1666
1659
|
});
|
|
1667
1660
|
|
|
1668
|
-
it('timeout honesty: no timeout/gtimeout on PATH → timeout=uncapped, never a fabricated number', () => {
|
|
1661
|
+
it('timeout honesty: no timeout/gtimeout on PATH → timeout=uncapped, never a fabricated number', async () => {
|
|
1669
1662
|
const sb = makeSandbox();
|
|
1670
|
-
const r = run(sb, { path: `${sb.bin}:${farmFor(['timeout', 'gtimeout'])}` });
|
|
1663
|
+
const r = await run(sb, { path: `${sb.bin}:${farmFor(['timeout', 'gtimeout'])}` });
|
|
1671
1664
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1672
1665
|
assert.equal(r.status, 0, r.stderr);
|
|
1673
1666
|
assert.match(r.stderr, /^exec posture: .* timeout=uncapped$/m);
|
|
1674
1667
|
});
|
|
1675
1668
|
|
|
1676
|
-
it('an EXPORTED shell function shadowing timeout never fools the banner (type -P discipline)', () => {
|
|
1669
|
+
it('an EXPORTED shell function shadowing timeout never fools the banner (type -P discipline)', async () => {
|
|
1677
1670
|
const sb = makeSandbox();
|
|
1678
|
-
const r = run(sb, {
|
|
1671
|
+
const r = await run(sb, {
|
|
1679
1672
|
path: `${sb.bin}:${farmFor(['timeout', 'gtimeout'])}`,
|
|
1680
1673
|
env: { 'BASH_FUNC_timeout%%': '() { return 0; }' },
|
|
1681
1674
|
});
|
|
@@ -1684,9 +1677,9 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
|
1684
1677
|
assert.match(r.stderr, /^exec posture: .* timeout=uncapped$/m, 'a shell function is not a capping binary');
|
|
1685
1678
|
});
|
|
1686
1679
|
|
|
1687
|
-
it('an EXPORTED `type` function faking a path never fools the resolver (builtin type discipline)', () => {
|
|
1680
|
+
it('an EXPORTED `type` function faking a path never fools the resolver (builtin type discipline)', async () => {
|
|
1688
1681
|
const sb = makeSandbox();
|
|
1689
|
-
const r = run(sb, {
|
|
1682
|
+
const r = await run(sb, {
|
|
1690
1683
|
path: `${sb.bin}:${farmFor(['timeout', 'gtimeout'])}`,
|
|
1691
1684
|
env: { 'BASH_FUNC_type%%': '() { echo /fake/timeout; }' },
|
|
1692
1685
|
});
|
|
@@ -1695,14 +1688,14 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
|
1695
1688
|
assert.match(r.stderr, /^exec posture: .* timeout=uncapped$/m, 'builtin type bypasses an exported type function');
|
|
1696
1689
|
});
|
|
1697
1690
|
|
|
1698
|
-
it('a RELATIVE first PATH git entry + shadowed dirname/basename still bakes an ABSOLUTE real git into the shim', () => {
|
|
1691
|
+
it('a RELATIVE first PATH git entry + shadowed dirname/basename still bakes an ABSOLUTE real git into the shim', async () => {
|
|
1699
1692
|
const sb = makeSandbox();
|
|
1700
1693
|
const realGit = (process.env.PATH || '').split(':').filter(Boolean).map((d) => join(d, 'git')).find((p) => existsSync(p));
|
|
1701
1694
|
assert.ok(realGit, 'a real git exists on PATH');
|
|
1702
1695
|
mkdirSync(join(sb.repo, 'relgit'), { recursive: true });
|
|
1703
1696
|
writeFileSync(join(sb.repo, 'relgit', 'git'), `#!/usr/bin/env bash\nexec ${realGit} "$@"\n`, { mode: 0o755 });
|
|
1704
1697
|
const gitResult = join(sb.repo, '.cap-git');
|
|
1705
|
-
const r = run(sb, {
|
|
1698
|
+
const r = await run(sb, {
|
|
1706
1699
|
path: `relgit:${sb.bin}:${process.env.PATH}`,
|
|
1707
1700
|
env: {
|
|
1708
1701
|
CODEX_FAKE_GIT_PROBE: '1',
|
|
@@ -1717,7 +1710,7 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
|
1717
1710
|
assert.match(probe, /cdaway=0/, 'the shim git works from a different cwd — the embedded path is absolute, shadow-proof');
|
|
1718
1711
|
});
|
|
1719
1712
|
|
|
1720
|
-
it('a RELATIVE PATH entry still yields an ABSOLUTE capping binary (the stub sees an absolute $0)', () => {
|
|
1713
|
+
it('a RELATIVE PATH entry still yields an ABSOLUTE capping binary (the stub sees an absolute $0)', async () => {
|
|
1721
1714
|
const sb = makeSandbox();
|
|
1722
1715
|
mkdirSync(join(sb.repo, 'relbin'), { recursive: true });
|
|
1723
1716
|
const cap = join(sb.repo, '.stub-argv0');
|
|
@@ -1729,7 +1722,7 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
|
1729
1722
|
'exec "$@"',
|
|
1730
1723
|
'',
|
|
1731
1724
|
].join('\n'), { mode: 0o755 });
|
|
1732
|
-
const r = run(sb, {
|
|
1725
|
+
const r = await run(sb, {
|
|
1733
1726
|
path: `relbin:${sb.bin}:${farmFor(['timeout', 'gtimeout'])}`,
|
|
1734
1727
|
env: { TIMEOUT_STUB_CAP: cap },
|
|
1735
1728
|
});
|
|
@@ -1740,10 +1733,10 @@ describe('codex-exec.sh — dispatch-posture labeling (D5, AD-061)', () => {
|
|
|
1740
1733
|
assert.ok(argv0.startsWith('/'), `the stub must be invoked by ABSOLUTE path, got: ${JSON.stringify(argv0)}`);
|
|
1741
1734
|
});
|
|
1742
1735
|
|
|
1743
|
-
it('an INVALID effective CODEX_HARD_TIMEOUT (env — the closed aw_settings_valid bypass) warns and falls back to the default', () => {
|
|
1736
|
+
it('an INVALID effective CODEX_HARD_TIMEOUT (env — the closed aw_settings_valid bypass) warns and falls back to the default', async () => {
|
|
1744
1737
|
for (const bad of ['abc', '0', '999999999']) {
|
|
1745
1738
|
const sb = makeSandbox();
|
|
1746
|
-
const r = run(sb, { env: { CODEX_HARD_TIMEOUT: bad } });
|
|
1739
|
+
const r = await run(sb, { env: { CODEX_HARD_TIMEOUT: bad } });
|
|
1747
1740
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1748
1741
|
assert.equal(r.status, 0, r.stderr);
|
|
1749
1742
|
assert.match(r.stderr, new RegExp(`invalid value '${bad}' for CODEX_HARD_TIMEOUT`), 'the fallback is loud');
|
|
@@ -1798,11 +1791,11 @@ const clearCaptures = (sb) => {
|
|
|
1798
1791
|
for (const name of ['.cap-argv', '.cap-env', '.cap-stdin']) rmSync(join(sb.repo, name), { force: true });
|
|
1799
1792
|
};
|
|
1800
1793
|
|
|
1801
|
-
describe('codex-exec.sh — the nonce seam (D11)', () => {
|
|
1802
|
-
it('a nonce-LESS run writes NO artifact into the store directory', () => {
|
|
1794
|
+
describe('codex-exec.sh — the nonce seam (D11)', { concurrency: 2 }, () => {
|
|
1795
|
+
it('a nonce-LESS run writes NO artifact into the store directory', async () => {
|
|
1803
1796
|
const sb = makeSandbox();
|
|
1804
1797
|
const file = writeContract(sb, 'unused');
|
|
1805
|
-
const r = run(sb, { args: [file] });
|
|
1798
|
+
const r = await run(sb, { args: [file] });
|
|
1806
1799
|
const artifacts = execArtifacts(storeDirOf(sb));
|
|
1807
1800
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1808
1801
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -1810,7 +1803,7 @@ describe('codex-exec.sh — the nonce seam (D11)', () => {
|
|
|
1810
1803
|
assert.doesNotMatch(r.stderr, /exec receipt:/, 'and it says nothing about a receipt either');
|
|
1811
1804
|
});
|
|
1812
1805
|
|
|
1813
|
-
it('each of the three D11 argv forms is accepted and reaches the right mode', () => {
|
|
1806
|
+
it('each of the three D11 argv forms is accepted and reaches the right mode', async () => {
|
|
1814
1807
|
const cases = [
|
|
1815
1808
|
{ label: 'fresh', args: (f) => ['--nonce', 'nf', f], nonce: 'nf', wantSession: 'fake-thread-123', resume: false },
|
|
1816
1809
|
{ label: 'resume-last', args: (f) => ['--resume-last', '--nonce', 'nl', f], nonce: 'nl', wantSession: 'sess-from-sidecar', resume: true },
|
|
@@ -1820,7 +1813,7 @@ describe('codex-exec.sh — the nonce seam (D11)', () => {
|
|
|
1820
1813
|
const sb = makeSandbox();
|
|
1821
1814
|
writeFileSync(join(sb.repo, '.codex-last-session'), 'sess-from-sidecar\n');
|
|
1822
1815
|
const file = writeContract(sb, c.nonce);
|
|
1823
|
-
const r = run(sb, { args: c.args(file) });
|
|
1816
|
+
const r = await run(sb, { args: c.args(file) });
|
|
1824
1817
|
const receipt = r.status === 0 ? readReceipt(storeDirOf(sb), c.nonce) : null;
|
|
1825
1818
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1826
1819
|
assert.equal(r.status, 0, `${c.label}: ${r.stderr}`);
|
|
@@ -1832,11 +1825,11 @@ describe('codex-exec.sh — the nonce seam (D11)', () => {
|
|
|
1832
1825
|
}
|
|
1833
1826
|
});
|
|
1834
1827
|
|
|
1835
|
-
it('a --nonce AFTER the prompt operand or after -- is payload, never a flag', () => {
|
|
1828
|
+
it('a --nonce AFTER the prompt operand or after -- is payload, never a flag', async () => {
|
|
1836
1829
|
const sb = makeSandbox();
|
|
1837
1830
|
const file = writeContract(sb, 'late');
|
|
1838
|
-
const passthrough = run(sb, { args: [file, '--', '--nonce', 'late'] });
|
|
1839
|
-
const afterOperand = run(sb, { args: [file, '--nonce', 'late'] });
|
|
1831
|
+
const passthrough = await run(sb, { args: [file, '--', '--nonce', 'late'] });
|
|
1832
|
+
const afterOperand = await run(sb, { args: [file, '--nonce', 'late'] });
|
|
1840
1833
|
const artifacts = execArtifacts(storeDirOf(sb));
|
|
1841
1834
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1842
1835
|
assert.equal(passthrough.status, 0, passthrough.stderr);
|
|
@@ -1846,10 +1839,10 @@ describe('codex-exec.sh — the nonce seam (D11)', () => {
|
|
|
1846
1839
|
assert.match(afterOperand.stderr, /unexpected argument '--nonce'/);
|
|
1847
1840
|
});
|
|
1848
1841
|
|
|
1849
|
-
it('the flag and the environment value are ONE seam: agreeing runs, disagreeing refuses pre-spend', () => {
|
|
1842
|
+
it('the flag and the environment value are ONE seam: agreeing runs, disagreeing refuses pre-spend', async () => {
|
|
1850
1843
|
const agree = makeSandbox();
|
|
1851
1844
|
const agreeFile = writeContract(agree, 'same');
|
|
1852
|
-
const ok = run(agree, { args: ['--nonce', 'same', agreeFile], env: { AW_DISPATCH_NONCE: 'same' } });
|
|
1845
|
+
const ok = await run(agree, { args: ['--nonce', 'same', agreeFile], env: { AW_DISPATCH_NONCE: 'same' } });
|
|
1853
1846
|
const okState = ok.status === 0 ? readReceipt(storeDirOf(agree), 'same').state : null;
|
|
1854
1847
|
rmSync(agree.root, { recursive: true, force: true });
|
|
1855
1848
|
assert.equal(ok.status, 0, ok.stderr);
|
|
@@ -1857,7 +1850,7 @@ describe('codex-exec.sh — the nonce seam (D11)', () => {
|
|
|
1857
1850
|
|
|
1858
1851
|
const clash = makeSandbox();
|
|
1859
1852
|
const clashFile = writeContract(clash, 'flagnonce');
|
|
1860
|
-
const bad = run(clash, { args: ['--nonce', 'flagnonce', clashFile], env: { AW_DISPATCH_NONCE: 'envnonce' } });
|
|
1853
|
+
const bad = await run(clash, { args: ['--nonce', 'flagnonce', clashFile], env: { AW_DISPATCH_NONCE: 'envnonce' } });
|
|
1861
1854
|
const artifacts = execArtifacts(storeDirOf(clash));
|
|
1862
1855
|
rmSync(clash.root, { recursive: true, force: true });
|
|
1863
1856
|
assert.equal(bad.status, 2);
|
|
@@ -1866,11 +1859,11 @@ describe('codex-exec.sh — the nonce seam (D11)', () => {
|
|
|
1866
1859
|
assert.deepEqual(artifacts, [], 'and it reserves nothing');
|
|
1867
1860
|
});
|
|
1868
1861
|
|
|
1869
|
-
it('a nonce outside the safe grammar refuses pre-spend, from either source', () => {
|
|
1862
|
+
it('a nonce outside the safe grammar refuses pre-spend, from either source', async () => {
|
|
1870
1863
|
for (const bad of ['a/b', '../x', 'a b', 'x'.repeat(65), '']) {
|
|
1871
1864
|
const viaFlag = makeSandbox();
|
|
1872
1865
|
const file = writeContract(viaFlag, 'grammar');
|
|
1873
|
-
const f = run(viaFlag, { args: ['--nonce', bad, file] });
|
|
1866
|
+
const f = await run(viaFlag, { args: ['--nonce', bad, file] });
|
|
1874
1867
|
const flagArtifacts = execArtifacts(storeDirOf(viaFlag));
|
|
1875
1868
|
rmSync(viaFlag.root, { recursive: true, force: true });
|
|
1876
1869
|
assert.equal(f.status, 2, `--nonce "${bad}" must refuse`);
|
|
@@ -1880,18 +1873,18 @@ describe('codex-exec.sh — the nonce seam (D11)', () => {
|
|
|
1880
1873
|
|
|
1881
1874
|
if (bad === '') continue; // an EMPTY env value is "unset" to the seam, not a bad nonce
|
|
1882
1875
|
const viaEnv = makeSandbox();
|
|
1883
|
-
const e = run(viaEnv, { args: [writeContract(viaEnv, 'grammar')], env: { AW_DISPATCH_NONCE: bad } });
|
|
1876
|
+
const e = await run(viaEnv, { args: [writeContract(viaEnv, 'grammar')], env: { AW_DISPATCH_NONCE: bad } });
|
|
1884
1877
|
rmSync(viaEnv.root, { recursive: true, force: true });
|
|
1885
1878
|
assert.equal(e.status, 2, `AW_DISPATCH_NONCE "${bad}" must refuse`);
|
|
1886
1879
|
assert.match(e.stderr, /AW_DISPATCH_NONCE fails the safe nonce grammar/);
|
|
1887
1880
|
}
|
|
1888
1881
|
});
|
|
1889
1882
|
|
|
1890
|
-
it('a duplicate --nonce refuses, and a --nonce with no value refuses', () => {
|
|
1883
|
+
it('a duplicate --nonce refuses, and a --nonce with no value refuses', async () => {
|
|
1891
1884
|
const sb = makeSandbox();
|
|
1892
1885
|
const file = writeContract(sb, 'dup');
|
|
1893
|
-
const dup = run(sb, { args: ['--nonce', 'dup', '--nonce', 'other', file] });
|
|
1894
|
-
const bare = run(sb, { args: ['--nonce'] });
|
|
1886
|
+
const dup = await run(sb, { args: ['--nonce', 'dup', '--nonce', 'other', file] });
|
|
1887
|
+
const bare = await run(sb, { args: ['--nonce'] });
|
|
1895
1888
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1896
1889
|
assert.equal(dup.status, 2);
|
|
1897
1890
|
assert.match(dup.stderr, /duplicate --nonce — one dispatch carries one nonce/);
|
|
@@ -1899,12 +1892,12 @@ describe('codex-exec.sh — the nonce seam (D11)', () => {
|
|
|
1899
1892
|
assert.match(bare.stderr, /--nonce needs a value/);
|
|
1900
1893
|
});
|
|
1901
1894
|
|
|
1902
|
-
it('an accounted dispatch needs a contract FILE: stdin and a header-less file both refuse pre-spend', () => {
|
|
1895
|
+
it('an accounted dispatch needs a contract FILE: stdin and a header-less file both refuse pre-spend', async () => {
|
|
1903
1896
|
const sb = makeSandbox();
|
|
1904
|
-
const stdin = run(sb, { args: ['--nonce', 'n1', '-'], input: 'do the thing' });
|
|
1897
|
+
const stdin = await run(sb, { args: ['--nonce', 'n1', '-'], input: 'do the thing' });
|
|
1905
1898
|
writeFileSync(join(sb.repo, 'plain.md'), '# just a plan\n\nno contract block here\n');
|
|
1906
1899
|
clearCaptures(sb);
|
|
1907
|
-
const headerless = run(sb, { args: ['--nonce', 'n1', 'plain.md'] });
|
|
1900
|
+
const headerless = await run(sb, { args: ['--nonce', 'n1', 'plain.md'] });
|
|
1908
1901
|
const artifacts = execArtifacts(storeDirOf(sb));
|
|
1909
1902
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1910
1903
|
assert.equal(stdin.status, 2);
|
|
@@ -1916,8 +1909,8 @@ describe('codex-exec.sh — the nonce seam (D11)', () => {
|
|
|
1916
1909
|
});
|
|
1917
1910
|
});
|
|
1918
1911
|
|
|
1919
|
-
describe('codex-exec.sh — the pre-spend reservation (D1/D8)', () => {
|
|
1920
|
-
it('the reservation EXISTS, in state reserved, while the CLI is still running', () => {
|
|
1912
|
+
describe('codex-exec.sh — the pre-spend reservation (D1/D8)', { concurrency: 2 }, () => {
|
|
1913
|
+
it('the reservation EXISTS, in state reserved, while the CLI is still running', async () => {
|
|
1921
1914
|
// The ordering claim needs a mid-flight observation. Asserting it from a SECOND run would prove
|
|
1922
1915
|
// nothing: by then the first run has finished and left a TERMINAL artifact, so moving the
|
|
1923
1916
|
// reservation to after the CLI would keep such a test green. The fake copies the receipt while
|
|
@@ -1925,7 +1918,7 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', () => {
|
|
|
1925
1918
|
const sb = makeSandbox();
|
|
1926
1919
|
const file = writeContract(sb, 'midflight');
|
|
1927
1920
|
const snapshot = join(sb.root, 'receipt-during-the-run.json');
|
|
1928
|
-
const r = run(sb, {
|
|
1921
|
+
const r = await run(sb, {
|
|
1929
1922
|
args: ['--nonce', 'midflight', file],
|
|
1930
1923
|
env: { CODEX_FAKE_SNAPSHOT_SRC: join(storeDirOf(sb), receiptName('midflight')), CODEX_FAKE_SNAPSHOT_DST: snapshot },
|
|
1931
1924
|
});
|
|
@@ -1936,14 +1929,14 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', () => {
|
|
|
1936
1929
|
assert.equal(JSON.parse(seen).state, 'reserved', 'and it is the RESERVATION — the terminal receipt comes later');
|
|
1937
1930
|
});
|
|
1938
1931
|
|
|
1939
|
-
it('a second dispatch on the same nonce refuses unspent, leaving the first run\'s evidence untouched', () => {
|
|
1932
|
+
it('a second dispatch on the same nonce refuses unspent, leaving the first run\'s evidence untouched', async () => {
|
|
1940
1933
|
const sb = makeSandbox();
|
|
1941
1934
|
const file = writeContract(sb, 'once');
|
|
1942
|
-
const first = run(sb, { args: ['--nonce', 'once', file] });
|
|
1935
|
+
const first = await run(sb, { args: ['--nonce', 'once', file] });
|
|
1943
1936
|
const receiptPath = join(storeDirOf(sb), receiptName('once'));
|
|
1944
1937
|
const afterFirst = readFileSync(receiptPath, 'utf8');
|
|
1945
1938
|
clearCaptures(sb);
|
|
1946
|
-
const second = run(sb, { args: ['--nonce', 'once', file] });
|
|
1939
|
+
const second = await run(sb, { args: ['--nonce', 'once', file] });
|
|
1947
1940
|
const afterSecond = readFileSync(receiptPath, 'utf8');
|
|
1948
1941
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1949
1942
|
assert.equal(first.status, 0, first.stderr);
|
|
@@ -1953,12 +1946,12 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', () => {
|
|
|
1953
1946
|
assert.equal(afterSecond, afterFirst, 'the first dispatch\'s evidence is byte-untouched');
|
|
1954
1947
|
});
|
|
1955
1948
|
|
|
1956
|
-
it('the dispatch nonce must equal the contract header\'s nonce — a disagreement refuses unspent', () => {
|
|
1949
|
+
it('the dispatch nonce must equal the contract header\'s nonce — a disagreement refuses unspent', async () => {
|
|
1957
1950
|
// `dispatch open` COPIES the nonce from the header, so a disagreeing --nonce could only reserve
|
|
1958
1951
|
// an identity no return would ever absorb — after paying for the run.
|
|
1959
1952
|
const sb = makeSandbox();
|
|
1960
1953
|
const file = writeContract(sb, 'header');
|
|
1961
|
-
const r = run(sb, { args: ['--nonce', 'other', file] });
|
|
1954
|
+
const r = await run(sb, { args: ['--nonce', 'other', file] });
|
|
1962
1955
|
const artifacts = execArtifacts(storeDirOf(sb));
|
|
1963
1956
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1964
1957
|
assert.equal(r.status, 2);
|
|
@@ -1967,17 +1960,17 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', () => {
|
|
|
1967
1960
|
assert.deepEqual(artifacts, [], 'and precedes the reservation');
|
|
1968
1961
|
});
|
|
1969
1962
|
|
|
1970
|
-
it('a nonce-LESS run of a contract-bearing file never compares nonces — the accounted lane owns that rule', () => {
|
|
1963
|
+
it('a nonce-LESS run of a contract-bearing file never compares nonces — the accounted lane owns that rule', async () => {
|
|
1971
1964
|
const sb = makeSandbox();
|
|
1972
1965
|
const file = writeContract(sb, 'header');
|
|
1973
|
-
const r = run(sb, { args: [file] });
|
|
1966
|
+
const r = await run(sb, { args: [file] });
|
|
1974
1967
|
const artifacts = execArtifacts(storeDirOf(sb));
|
|
1975
1968
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1976
1969
|
assert.equal(r.status, 0, r.stderr);
|
|
1977
1970
|
assert.deepEqual(artifacts, [], 'an ordinary plan file that happens to carry a contract block still runs unaccounted');
|
|
1978
1971
|
});
|
|
1979
1972
|
|
|
1980
|
-
it('a store directory whose parent name ends in a NEWLINE resolves as the kit resolves it', () => {
|
|
1973
|
+
it('a store directory whose parent name ends in a NEWLINE resolves as the kit resolves it', async () => {
|
|
1981
1974
|
// `$( )` strips every trailing newline; the kit's own reader strips exactly ONE (git's
|
|
1982
1975
|
// terminator). Without a sentinel the two sides would resolve different directories here, and the
|
|
1983
1976
|
// wrapper would write beside a ledger the kit never reads.
|
|
@@ -1985,7 +1978,7 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', () => {
|
|
|
1985
1978
|
const weird = join(sb.root, 'ledger\n');
|
|
1986
1979
|
mkdirSync(weird, { recursive: true });
|
|
1987
1980
|
const file = writeContract(sb, 'nlstore');
|
|
1988
|
-
const r = run(sb, { args: ['--nonce', 'nlstore', file], env: { AW_DELEGATION_STORE: join(weird, STORE_BASENAME) } });
|
|
1981
|
+
const r = await run(sb, { args: ['--nonce', 'nlstore', file], env: { AW_DELEGATION_STORE: join(weird, STORE_BASENAME) } });
|
|
1989
1982
|
const landed = execArtifacts(weird);
|
|
1990
1983
|
rmSync(sb.root, { recursive: true, force: true });
|
|
1991
1984
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -1993,25 +1986,25 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', () => {
|
|
|
1993
1986
|
'the artifacts land in the directory the kit would compute, newline and all');
|
|
1994
1987
|
});
|
|
1995
1988
|
|
|
1996
|
-
it('an already-taken REPORT name refuses the reservation too — the kit refuses on either name', () => {
|
|
1989
|
+
it('an already-taken REPORT name refuses the reservation too — the kit refuses on either name', async () => {
|
|
1997
1990
|
const sb = makeSandbox();
|
|
1998
1991
|
const file = writeContract(sb, 'leftover');
|
|
1999
1992
|
writeFileSync(join(storeDirOf(sb), reportName('leftover')), 'a report from something else\n');
|
|
2000
|
-
const r = run(sb, { args: ['--nonce', 'leftover', file] });
|
|
1993
|
+
const r = await run(sb, { args: ['--nonce', 'leftover', file] });
|
|
2001
1994
|
rmSync(sb.root, { recursive: true, force: true });
|
|
2002
1995
|
assert.equal(r.status, 2);
|
|
2003
1996
|
assert.match(r.stderr, /already exists at .*agent-workflow-exec-report-5-codex-leftover\.txt/);
|
|
2004
1997
|
assert.equal(r.argv, '', 'nothing was spent');
|
|
2005
1998
|
});
|
|
2006
1999
|
|
|
2007
|
-
it('a NONCED run with no capping binary refuses pre-spend, while a nonce-less one still warns and runs', () => {
|
|
2000
|
+
it('a NONCED run with no capping binary refuses pre-spend, while a nonce-less one still warns and runs', async () => {
|
|
2008
2001
|
const sb = makeSandbox();
|
|
2009
2002
|
const file = writeContract(sb, 'uncapped');
|
|
2010
2003
|
const path = `${sb.bin}:${farmFor(['timeout', 'gtimeout'])}`;
|
|
2011
|
-
const nonced = run(sb, { args: ['--nonce', 'uncapped', file], path });
|
|
2004
|
+
const nonced = await run(sb, { args: ['--nonce', 'uncapped', file], path });
|
|
2012
2005
|
const artifacts = execArtifacts(storeDirOf(sb));
|
|
2013
2006
|
clearCaptures(sb);
|
|
2014
|
-
const plain = run(sb, { args: [file], path });
|
|
2007
|
+
const plain = await run(sb, { args: [file], path });
|
|
2015
2008
|
rmSync(sb.root, { recursive: true, force: true });
|
|
2016
2009
|
assert.equal(nonced.status, 2, nonced.stderr);
|
|
2017
2010
|
assert.match(nonced.stderr, /a nonced dispatch refuses to run uncapped/);
|
|
@@ -2021,7 +2014,7 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', () => {
|
|
|
2021
2014
|
assert.match(plain.stderr, /running codex WITHOUT a hard wall-clock cap/, 'the nonce-less lane is unchanged');
|
|
2022
2015
|
});
|
|
2023
2016
|
|
|
2024
|
-
it('a PREFLIGHT refusal leaves NO reservation (login guard, missing AGENTS.md, off-pin model)', () => {
|
|
2017
|
+
it('a PREFLIGHT refusal leaves NO reservation (login guard, missing AGENTS.md, off-pin model)', async () => {
|
|
2025
2018
|
const cases = [
|
|
2026
2019
|
{ label: 'login', env: { CODEX_FAKE_LOGIN: 'Not logged in' } },
|
|
2027
2020
|
{ label: 'model', env: { CODEX_MODEL: 'gpt-5.4-mini' } },
|
|
@@ -2031,7 +2024,7 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', () => {
|
|
|
2031
2024
|
const sb = makeSandbox();
|
|
2032
2025
|
const file = writeContract(sb, 'preflight');
|
|
2033
2026
|
if (c.drop) rmSync(join(sb.repo, 'AGENTS.md'), { force: true });
|
|
2034
|
-
const r = run(sb, { args: ['--nonce', 'preflight', file], env: c.env });
|
|
2027
|
+
const r = await run(sb, { args: ['--nonce', 'preflight', file], env: c.env });
|
|
2035
2028
|
const artifacts = execArtifacts(storeDirOf(sb));
|
|
2036
2029
|
rmSync(sb.root, { recursive: true, force: true });
|
|
2037
2030
|
assert.notEqual(r.status, 0, `${c.label}: the preflight must refuse`);
|
|
@@ -2039,13 +2032,13 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', () => {
|
|
|
2039
2032
|
}
|
|
2040
2033
|
});
|
|
2041
2034
|
|
|
2042
|
-
it('the reservation carries everything knowable PRE-SPEND and nulls every terminal-only field', () => {
|
|
2035
|
+
it('the reservation carries everything knowable PRE-SPEND and nulls every terminal-only field', async () => {
|
|
2043
2036
|
// Proven on the artifact the CLI-blocking refusal leaves behind: a second dispatch is refused
|
|
2044
2037
|
// pre-spend, so the FIRST run's reservation is the only shape a fixture can observe mid-flight —
|
|
2045
2038
|
// instead, block the terminal publication and read the surviving reservation.
|
|
2046
2039
|
const sb = makeSandbox();
|
|
2047
2040
|
const file = writeContract(sb, 'resv');
|
|
2048
|
-
const r = run(sb, {
|
|
2041
|
+
const r = await run(sb, {
|
|
2049
2042
|
args: ['--nonce', 'resv', file],
|
|
2050
2043
|
env: { CODEX_FAKE_MKDIR: join(storeDirOf(sb), reportName('resv')) },
|
|
2051
2044
|
});
|
|
@@ -2063,17 +2056,17 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', () => {
|
|
|
2063
2056
|
}
|
|
2064
2057
|
});
|
|
2065
2058
|
|
|
2066
|
-
it('the store directory resolves as the kit resolves it: absolute override wins, relative and trailing-separator refuse', () => {
|
|
2059
|
+
it('the store directory resolves as the kit resolves it: absolute override wins, relative and trailing-separator refuse', async () => {
|
|
2067
2060
|
const sb = makeSandbox();
|
|
2068
2061
|
const file = writeContract(sb, 'store');
|
|
2069
2062
|
const elsewhere = join(sb.root, 'ledger');
|
|
2070
2063
|
mkdirSync(elsewhere, { recursive: true });
|
|
2071
|
-
const ok = run(sb, { args: ['--nonce', 'store', file], env: { AW_DELEGATION_STORE: join(elsewhere, STORE_BASENAME) } });
|
|
2064
|
+
const ok = await run(sb, { args: ['--nonce', 'store', file], env: { AW_DELEGATION_STORE: join(elsewhere, STORE_BASENAME) } });
|
|
2072
2065
|
const landed = execArtifacts(elsewhere);
|
|
2073
2066
|
clearCaptures(sb);
|
|
2074
|
-
const rel = run(sb, { args: ['--nonce', 'store2', writeContract(sb, 'store2')], env: { AW_DELEGATION_STORE: `ledger/${STORE_BASENAME}` } });
|
|
2067
|
+
const rel = await run(sb, { args: ['--nonce', 'store2', writeContract(sb, 'store2')], env: { AW_DELEGATION_STORE: `ledger/${STORE_BASENAME}` } });
|
|
2075
2068
|
clearCaptures(sb);
|
|
2076
|
-
const trailing = run(sb, { args: ['--nonce', 'store3', writeContract(sb, 'store3')], env: { AW_DELEGATION_STORE: `${elsewhere}/` } });
|
|
2069
|
+
const trailing = await run(sb, { args: ['--nonce', 'store3', writeContract(sb, 'store3')], env: { AW_DELEGATION_STORE: `${elsewhere}/` } });
|
|
2077
2070
|
rmSync(sb.root, { recursive: true, force: true });
|
|
2078
2071
|
assert.equal(ok.status, 0, ok.stderr);
|
|
2079
2072
|
assert.deepEqual(landed, [receiptName('store'), reportName('store')].sort(), 'both artifacts land in the override\'s dirname');
|
|
@@ -2084,11 +2077,11 @@ describe('codex-exec.sh — the pre-spend reservation (D1/D8)', () => {
|
|
|
2084
2077
|
});
|
|
2085
2078
|
});
|
|
2086
2079
|
|
|
2087
|
-
describe('codex-exec.sh — the fail-closed terminal receipt (D1/D3/3.1.d)', () => {
|
|
2088
|
-
it('a SUCCESSFUL run publishes a complete report and a terminal receipt that describes it', () => {
|
|
2080
|
+
describe('codex-exec.sh — the fail-closed terminal receipt (D1/D3/3.1.d)', { concurrency: 2 }, () => {
|
|
2081
|
+
it('a SUCCESSFUL run publishes a complete report and a terminal receipt that describes it', async () => {
|
|
2089
2082
|
const sb = makeSandbox();
|
|
2090
2083
|
const file = writeContract(sb, 'ok');
|
|
2091
|
-
const r = run(sb, { args: ['--nonce', 'ok', file] });
|
|
2084
|
+
const r = await run(sb, { args: ['--nonce', 'ok', file] });
|
|
2092
2085
|
const dir = storeDirOf(sb);
|
|
2093
2086
|
const receipt = readReceipt(dir, 'ok');
|
|
2094
2087
|
const report = readFileSync(join(dir, reportName('ok')));
|
|
@@ -2103,10 +2096,10 @@ describe('codex-exec.sh — the fail-closed terminal receipt (D1/D3/3.1.d)', ()
|
|
|
2103
2096
|
assert.match(r.stderr, /exec receipt: nonce=ok outcome=success exit=0 session=fake-thread-123/);
|
|
2104
2097
|
});
|
|
2105
2098
|
|
|
2106
|
-
it('a FAILED run still publishes a terminal receipt — with its exit status, outcome and session id', () => {
|
|
2099
|
+
it('a FAILED run still publishes a terminal receipt — with its exit status, outcome and session id', async () => {
|
|
2107
2100
|
const sb = makeSandbox();
|
|
2108
2101
|
const file = writeContract(sb, 'boom');
|
|
2109
|
-
const r = run(sb, { args: ['--nonce', 'boom', file], env: { CODEX_FAKE_EXIT: '5' } });
|
|
2102
|
+
const r = await run(sb, { args: ['--nonce', 'boom', file], env: { CODEX_FAKE_EXIT: '5' } });
|
|
2110
2103
|
const receipt = readReceipt(storeDirOf(sb), 'boom');
|
|
2111
2104
|
rmSync(sb.root, { recursive: true, force: true });
|
|
2112
2105
|
assert.equal(r.status, 5, 'the wrapper still exits with the run\'s own status');
|
|
@@ -2118,7 +2111,7 @@ describe('codex-exec.sh — the fail-closed terminal receipt (D1/D3/3.1.d)', ()
|
|
|
2118
2111
|
assert.ok(r.stderr.indexOf('codex exec failed') < r.stderr.indexOf('exec receipt:'), 'a publication never swallows the trace tail');
|
|
2119
2112
|
});
|
|
2120
2113
|
|
|
2121
|
-
it('an ABSENT capture and an EXISTING EMPTY one both record zero bytes — and neither is a failed probe', () => {
|
|
2114
|
+
it('an ABSENT capture and an EXISTING EMPTY one both record zero bytes — and neither is a failed probe', async () => {
|
|
2122
2115
|
// `mktemp` used to pre-create the capture file, so ENOENT could never occur and "absent" was not
|
|
2123
2116
|
// a case the publisher could even see. It matters because of what it collides with: once an
|
|
2124
2117
|
// absent capture is possible, the fail-closed read-error branch has to tell it apart from a
|
|
@@ -2128,7 +2121,7 @@ describe('codex-exec.sh — the fail-closed terminal receipt (D1/D3/3.1.d)', ()
|
|
|
2128
2121
|
// stated rather than papered over, and the outcome stays D3's (rc 0 + a session id is success).
|
|
2129
2122
|
const absent = makeSandbox();
|
|
2130
2123
|
const absentFile = writeContract(absent, 'noout');
|
|
2131
|
-
const a = run(absent, { args: ['--nonce', 'noout', absentFile], env: { CODEX_FAKE_NO_OUT: '1' } });
|
|
2124
|
+
const a = await run(absent, { args: ['--nonce', 'noout', absentFile], env: { CODEX_FAKE_NO_OUT: '1' } });
|
|
2132
2125
|
const absentReceipt = readReceipt(storeDirOf(absent), 'noout');
|
|
2133
2126
|
const absentReport = readFileSync(join(storeDirOf(absent), reportName('noout')));
|
|
2134
2127
|
rmSync(absent.root, { recursive: true, force: true });
|
|
@@ -2141,7 +2134,7 @@ describe('codex-exec.sh — the fail-closed terminal receipt (D1/D3/3.1.d)', ()
|
|
|
2141
2134
|
|
|
2142
2135
|
const empty = makeSandbox();
|
|
2143
2136
|
const emptyFile = writeContract(empty, 'emptyout');
|
|
2144
|
-
const e = run(empty, { args: ['--nonce', 'emptyout', emptyFile], env: { CODEX_FAKE_EMPTY_OUT: '1' } });
|
|
2137
|
+
const e = await run(empty, { args: ['--nonce', 'emptyout', emptyFile], env: { CODEX_FAKE_EMPTY_OUT: '1' } });
|
|
2145
2138
|
const emptyReceipt = readReceipt(storeDirOf(empty), 'emptyout');
|
|
2146
2139
|
rmSync(empty.root, { recursive: true, force: true });
|
|
2147
2140
|
assert.equal(e.status, 0, e.stderr);
|
|
@@ -2152,7 +2145,7 @@ describe('codex-exec.sh — the fail-closed terminal receipt (D1/D3/3.1.d)', ()
|
|
|
2152
2145
|
'the existing-but-EMPTY capture takes the same -s fallback — it too has no answer to print');
|
|
2153
2146
|
});
|
|
2154
2147
|
|
|
2155
|
-
it('an UNREADABLE final message is a failed probe — exit 71, never a silent empty report', () => {
|
|
2148
|
+
it('an UNREADABLE final message is a failed probe — exit 71, never a silent empty report', async () => {
|
|
2156
2149
|
const sb = makeSandbox();
|
|
2157
2150
|
const file = writeContract(sb, 'unread');
|
|
2158
2151
|
// A DIRECTORY at the capture path: readable-as-a-path, unreadable as a file (EISDIR), and
|
|
@@ -2169,7 +2162,7 @@ fs.readFileSync = (target, ...rest) => {
|
|
|
2169
2162
|
return real(target, ...rest);
|
|
2170
2163
|
};
|
|
2171
2164
|
`);
|
|
2172
|
-
const r = run(sb, { args: ['--nonce', 'unread', file] });
|
|
2165
|
+
const r = await run(sb, { args: ['--nonce', 'unread', file] });
|
|
2173
2166
|
const receipt = readReceipt(storeDirOf(sb), 'unread');
|
|
2174
2167
|
rmSync(sb.root, { recursive: true, force: true });
|
|
2175
2168
|
assert.equal(r.status, 71, r.stderr);
|
|
@@ -2177,7 +2170,7 @@ fs.readFileSync = (target, ...rest) => {
|
|
|
2177
2170
|
assert.equal(receipt.state, 'reserved', 'nothing terminal was published over a probe that failed');
|
|
2178
2171
|
});
|
|
2179
2172
|
|
|
2180
|
-
it('a DANGLING SYMLINK at the capture path is a failed probe, never a clean empty report', () => {
|
|
2173
|
+
it('a DANGLING SYMLINK at the capture path is a failed probe, never a clean empty report', async () => {
|
|
2181
2174
|
// The trap the ENOENT split set for itself: readFileSync FOLLOWS the link, so a dangling one
|
|
2182
2175
|
// reports ENOENT — indistinguishable from "the delegate wrote nothing" — and a corrupt capture
|
|
2183
2176
|
// would be published as an empty report on a `success` receipt. lstat decides first.
|
|
@@ -2193,7 +2186,7 @@ fs.lstatSync = (target, ...rest) => {
|
|
|
2193
2186
|
return realLstat(target, ...rest);
|
|
2194
2187
|
};
|
|
2195
2188
|
`);
|
|
2196
|
-
const r = run(sb, { args: ['--nonce', 'dangle', file] });
|
|
2189
|
+
const r = await run(sb, { args: ['--nonce', 'dangle', file] });
|
|
2197
2190
|
const receipt = readReceipt(storeDirOf(sb), 'dangle');
|
|
2198
2191
|
rmSync(sb.root, { recursive: true, force: true });
|
|
2199
2192
|
assert.equal(r.status, 71, r.stderr);
|
|
@@ -2201,10 +2194,10 @@ fs.lstatSync = (target, ...rest) => {
|
|
|
2201
2194
|
assert.equal(receipt.state, 'reserved', 'nothing terminal was published over a corrupt capture');
|
|
2202
2195
|
});
|
|
2203
2196
|
|
|
2204
|
-
it('a run that identified no session records sessionId null with outcome missing-identity', () => {
|
|
2197
|
+
it('a run that identified no session records sessionId null with outcome missing-identity', async () => {
|
|
2205
2198
|
const sb = makeSandbox();
|
|
2206
2199
|
const file = writeContract(sb, 'anon');
|
|
2207
|
-
const r = run(sb, { args: ['--nonce', 'anon', file], env: { CODEX_FAKE_NO_THREAD: '1' } });
|
|
2200
|
+
const r = await run(sb, { args: ['--nonce', 'anon', file], env: { CODEX_FAKE_NO_THREAD: '1' } });
|
|
2208
2201
|
const receipt = readReceipt(storeDirOf(sb), 'anon');
|
|
2209
2202
|
rmSync(sb.root, { recursive: true, force: true });
|
|
2210
2203
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2227,11 +2220,11 @@ fs.lstatSync = (target, ...rest) => {
|
|
|
2227
2220
|
assert.match(r.stderr, /exceeded the hard cap/);
|
|
2228
2221
|
});
|
|
2229
2222
|
|
|
2230
|
-
it('a FOREIGN owner refuses with NOTHING published — both artifacts stay byte-unchanged', () => {
|
|
2223
|
+
it('a FOREIGN owner refuses with NOTHING published — both artifacts stay byte-unchanged', async () => {
|
|
2231
2224
|
const sb = makeSandbox();
|
|
2232
2225
|
const file = writeContract(sb, 'tamper');
|
|
2233
2226
|
const dir = storeDirOf(sb);
|
|
2234
|
-
const r = run(sb, {
|
|
2227
|
+
const r = await run(sb, {
|
|
2235
2228
|
args: ['--nonce', 'tamper', file],
|
|
2236
2229
|
env: { CODEX_FAKE_TAMPER: join(dir, receiptName('tamper')), CODEX_FAKE_TAMPER_NONCE: 'tamper' },
|
|
2237
2230
|
});
|
|
@@ -2245,11 +2238,11 @@ fs.lstatSync = (target, ...rest) => {
|
|
|
2245
2238
|
assert.match(r.stderr, /PARTIALLY EDITED/, 'and the tree is named dirtied, never silently accepted');
|
|
2246
2239
|
});
|
|
2247
2240
|
|
|
2248
|
-
it('a failed REPORT write exits 71 naming the report, and the reservation survives for --no-receipt', () => {
|
|
2241
|
+
it('a failed REPORT write exits 71 naming the report, and the reservation survives for --no-receipt', async () => {
|
|
2249
2242
|
const sb = makeSandbox();
|
|
2250
2243
|
const file = writeContract(sb, 'noreport');
|
|
2251
2244
|
const dir = storeDirOf(sb);
|
|
2252
|
-
const r = run(sb, {
|
|
2245
|
+
const r = await run(sb, {
|
|
2253
2246
|
args: ['--nonce', 'noreport', file],
|
|
2254
2247
|
env: { CODEX_FAKE_MKDIR: join(dir, reportName('noreport')) },
|
|
2255
2248
|
});
|
|
@@ -2261,7 +2254,7 @@ fs.lstatSync = (target, ...rest) => {
|
|
|
2261
2254
|
assert.match(r.stderr, /dispatch return --nonce noreport --no-receipt --exit-status 0 --outcome <o>/);
|
|
2262
2255
|
});
|
|
2263
2256
|
|
|
2264
|
-
it('an UNWRITABLE store directory stops the REPORT lane nonzero and says the tree is dirtied — NOT the review lane\'s warn-only receipt', () => {
|
|
2257
|
+
it('an UNWRITABLE store directory stops the REPORT lane nonzero and says the tree is dirtied — NOT the review lane\'s warn-only receipt', async () => {
|
|
2265
2258
|
// Named for the branch it really reaches. Both artifacts live in one directory, so a directory
|
|
2266
2259
|
// turned read-only stops the FIRST write — the report. The two POST-report branches need a
|
|
2267
2260
|
// failure injected between the writes, which the two tests below do.
|
|
@@ -2269,7 +2262,7 @@ fs.lstatSync = (target, ...rest) => {
|
|
|
2269
2262
|
const file = writeContract(sb, 'rodir');
|
|
2270
2263
|
const store = join(sb.root, 'ledger');
|
|
2271
2264
|
mkdirSync(store, { recursive: true });
|
|
2272
|
-
const r = run(sb, {
|
|
2265
|
+
const r = await run(sb, {
|
|
2273
2266
|
args: ['--nonce', 'rodir', file],
|
|
2274
2267
|
env: { AW_DELEGATION_STORE: join(store, STORE_BASENAME), CODEX_FAKE_RO_DIR: store },
|
|
2275
2268
|
});
|
|
@@ -2341,13 +2334,13 @@ fs.readFileSync = (target, ...rest) => {
|
|
|
2341
2334
|
};
|
|
2342
2335
|
`;
|
|
2343
2336
|
|
|
2344
|
-
describe('codex-exec.sh — the POST-report failure lanes never claim an untouched tree', () => {
|
|
2345
|
-
it('a terminal-receipt write that fails AFTER the report exits 71 and says the report IS published', () => {
|
|
2337
|
+
describe('codex-exec.sh — the POST-report failure lanes never claim an untouched tree', { concurrency: 2 }, () => {
|
|
2338
|
+
it('a terminal-receipt write that fails AFTER the report exits 71 and says the report IS published', async () => {
|
|
2346
2339
|
const sb = makeSandbox();
|
|
2347
2340
|
const file = writeContract(sb, 'postr');
|
|
2348
2341
|
nodeShimWith(sb, 'break-receipt.cjs', BREAK_RECEIPT_RENAME);
|
|
2349
2342
|
const dir = storeDirOf(sb);
|
|
2350
|
-
const r = run(sb, { args: ['--nonce', 'postr', file] });
|
|
2343
|
+
const r = await run(sb, { args: ['--nonce', 'postr', file] });
|
|
2351
2344
|
const receipt = readReceipt(dir, 'postr');
|
|
2352
2345
|
const report = readFileSync(join(dir, reportName('postr')), 'utf8');
|
|
2353
2346
|
rmSync(sb.root, { recursive: true, force: true });
|
|
@@ -2359,12 +2352,12 @@ describe('codex-exec.sh — the POST-report failure lanes never claim an untouch
|
|
|
2359
2352
|
assert.equal(receipt.state, 'reserved', 'the replace never happened, so the reservation is what survives here');
|
|
2360
2353
|
});
|
|
2361
2354
|
|
|
2362
|
-
it('a SECOND owner check that fails after the report exits 71 without claiming nothing was published', () => {
|
|
2355
|
+
it('a SECOND owner check that fails after the report exits 71 without claiming nothing was published', async () => {
|
|
2363
2356
|
const sb = makeSandbox();
|
|
2364
2357
|
const file = writeContract(sb, 'forge');
|
|
2365
2358
|
nodeShimWith(sb, 'forge-owner.cjs', FORGE_SECOND_CLAIM);
|
|
2366
2359
|
const dir = storeDirOf(sb);
|
|
2367
|
-
const r = run(sb, { args: ['--nonce', 'forge', file] });
|
|
2360
|
+
const r = await run(sb, { args: ['--nonce', 'forge', file] });
|
|
2368
2361
|
const reportExists = existsSync(join(dir, reportName('forge')));
|
|
2369
2362
|
rmSync(sb.root, { recursive: true, force: true });
|
|
2370
2363
|
assert.equal(r.status, 71, r.stderr);
|
|
@@ -2374,7 +2367,7 @@ describe('codex-exec.sh — the POST-report failure lanes never claim an untouch
|
|
|
2374
2367
|
assert.equal(reportExists, true, 'and the report really is there — which is what the message says');
|
|
2375
2368
|
});
|
|
2376
2369
|
|
|
2377
|
-
it('an inherited NODE_OPTIONS cannot reach the mint cores — the wrapper clears it', () => {
|
|
2370
|
+
it('an inherited NODE_OPTIONS cannot reach the mint cores — the wrapper clears it', async () => {
|
|
2378
2371
|
// The regression guard for the hole the two tests above used to lean on: a --require inherited
|
|
2379
2372
|
// from the caller's environment could rewrite `fs` inside the owner checks and the publication.
|
|
2380
2373
|
// Here the SAME preload that breaks the receipt rename is handed to the wrapper as NODE_OPTIONS
|
|
@@ -2383,7 +2376,7 @@ describe('codex-exec.sh — the POST-report failure lanes never claim an untouch
|
|
|
2383
2376
|
const file = writeContract(sb, 'envopt');
|
|
2384
2377
|
const hook = join(sb.root, 'break-receipt.cjs');
|
|
2385
2378
|
writeFileSync(hook, BREAK_RECEIPT_RENAME);
|
|
2386
|
-
const r = run(sb, { args: ['--nonce', 'envopt', file], env: { NODE_OPTIONS: `--require=${hook}` } });
|
|
2379
|
+
const r = await run(sb, { args: ['--nonce', 'envopt', file], env: { NODE_OPTIONS: `--require=${hook}` } });
|
|
2387
2380
|
const receipt = readReceipt(storeDirOf(sb), 'envopt');
|
|
2388
2381
|
rmSync(sb.root, { recursive: true, force: true });
|
|
2389
2382
|
assert.equal(r.status, 0, r.stderr);
|
|
@@ -2391,11 +2384,11 @@ describe('codex-exec.sh — the POST-report failure lanes never claim an untouch
|
|
|
2391
2384
|
assert.equal(receipt.outcome, 'success');
|
|
2392
2385
|
});
|
|
2393
2386
|
|
|
2394
|
-
it('a FIRST owner check that fails is the only lane that may claim NOTHING was published', () => {
|
|
2387
|
+
it('a FIRST owner check that fails is the only lane that may claim NOTHING was published', async () => {
|
|
2395
2388
|
const sb = makeSandbox();
|
|
2396
2389
|
const file = writeContract(sb, 'first');
|
|
2397
2390
|
const dir = storeDirOf(sb);
|
|
2398
|
-
const r = run(sb, {
|
|
2391
|
+
const r = await run(sb, {
|
|
2399
2392
|
args: ['--nonce', 'first', file],
|
|
2400
2393
|
env: { CODEX_FAKE_TAMPER: join(dir, receiptName('first')), CODEX_FAKE_TAMPER_NONCE: 'first' },
|
|
2401
2394
|
});
|
|
@@ -2408,8 +2401,8 @@ describe('codex-exec.sh — the POST-report failure lanes never claim an untouch
|
|
|
2408
2401
|
});
|
|
2409
2402
|
});
|
|
2410
2403
|
|
|
2411
|
-
describe('codex-exec.sh — the inline node mint cores stay intact', () => {
|
|
2412
|
-
it('the wrapper parses', () => {
|
|
2404
|
+
describe('codex-exec.sh — the inline node mint cores stay intact', { concurrency: 2 }, () => {
|
|
2405
|
+
it('the wrapper parses', async () => {
|
|
2413
2406
|
// The apostrophe SCANNER this replaced is gone with the class it policed: every mint core now
|
|
2414
2407
|
// rides a QUOTED heredoc, where an apostrophe is ordinary text. A scanner that had to model
|
|
2415
2408
|
// shell quoting was a second parser for a problem the quoting choice created — and it had its own
|
|
@@ -2418,7 +2411,7 @@ describe('codex-exec.sh — the inline node mint cores stay intact', () => {
|
|
|
2418
2411
|
assert.equal(syntax.status, 0, syntax.stderr);
|
|
2419
2412
|
});
|
|
2420
2413
|
|
|
2421
|
-
it('every mint core is read by a BUILTIN, run with NODE_OPTIONS cleared, and asserted non-empty', () => {
|
|
2414
|
+
it('every mint core is read by a BUILTIN, run with NODE_OPTIONS cleared, and asserted non-empty', async () => {
|
|
2422
2415
|
const source = readFileSync(WRAPPER, 'utf8');
|
|
2423
2416
|
assert.equal((source.match(/^ *IFS= read -r -d '' aw_js <<'AW_JS' \|\| true$/gm) ?? []).length, 4,
|
|
2424
2417
|
'the four cores: the store-dir resolver, the contract header, the reservation, the terminal publication');
|