@pi-unipi/background-tasks 2.6.3 → 2.6.4

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.
Files changed (58) hide show
  1. package/package.json +3 -2
  2. package/src/__tests__/anthropic-attribution.test.ts +0 -195
  3. package/src/__tests__/config.test.ts +0 -137
  4. package/src/__tests__/core.test.ts +0 -493
  5. package/src/__tests__/delegate-artifacts.test.ts +0 -528
  6. package/src/__tests__/delegate-budget.test.ts +0 -456
  7. package/src/__tests__/delegate-launch.test.ts +0 -676
  8. package/src/__tests__/delegate-result-package.test.ts +0 -350
  9. package/src/__tests__/delegate-seed.test.ts +0 -392
  10. package/src/__tests__/durable-fs.test.ts +0 -559
  11. package/src/__tests__/extension-api.test.ts +0 -579
  12. package/src/__tests__/fusion-artifacts.test.ts +0 -1039
  13. package/src/__tests__/fusion-budget.test.ts +0 -1356
  14. package/src/__tests__/fusion-claude-cache.test.ts +0 -320
  15. package/src/__tests__/fusion-config.test.ts +0 -335
  16. package/src/__tests__/fusion-context-prompts.test.ts +0 -670
  17. package/src/__tests__/fusion-evaluation.test.ts +0 -315
  18. package/src/__tests__/fusion-extraction-equivalence.test.ts +0 -58
  19. package/src/__tests__/fusion-golden-bytes.test.ts +0 -35
  20. package/src/__tests__/fusion-high-cardinality.test.ts +0 -192
  21. package/src/__tests__/fusion-model-selector.test.ts +0 -205
  22. package/src/__tests__/fusion-orchestrator.test.ts +0 -1194
  23. package/src/__tests__/fusion-rpc.test.ts +0 -369
  24. package/src/__tests__/fusion-sdk.test.ts +0 -1226
  25. package/src/__tests__/fusion-v5-core.test.ts +0 -219
  26. package/src/__tests__/fusion-validate-orchestrator.test.ts +0 -240
  27. package/src/__tests__/fusion-web-fetch.test.ts +0 -485
  28. package/src/__tests__/fusion-workflows.test.ts +0 -59
  29. package/src/__tests__/helpers/delegate-deterministic-seed.ts +0 -109
  30. package/src/__tests__/helpers/delegate-seed-subprocess.ts +0 -10
  31. package/src/__tests__/helpers/fusion-canonical-subprocess.ts +0 -21
  32. package/src/__tests__/helpers/fusion-canonical.ts +0 -140
  33. package/src/__tests__/helpers/fusion-fake-pi.ts +0 -279
  34. package/src/__tests__/helpers/fusion-golden-corpus.ts +0 -500
  35. package/src/__tests__/helpers/fusion-high-cardinality.ts +0 -140
  36. package/src/__tests__/helpers/normalize.ts +0 -22
  37. package/src/__tests__/helpers/pi-hook-contract-evidence.json +0 -18
  38. package/src/__tests__/pi-launch.test.ts +0 -202
  39. package/src/__tests__/registry.test.ts +0 -1580
  40. package/src/__tests__/scripted-provider/delegate-ambient-provider.test.ts +0 -130
  41. package/src/__tests__/scripted-provider/delegate-child-guard.test.ts +0 -631
  42. package/src/__tests__/scripted-provider/delegate-guard-provider.ts +0 -403
  43. package/src/__tests__/scripted-provider/follow-up.test.ts +0 -448
  44. package/src/__tests__/scripted-provider/fusion-output-recovery.test.ts +0 -132
  45. package/src/__tests__/scripted-provider/fusion-reason.test.ts +0 -310
  46. package/src/__tests__/scripted-provider/fusion-runtime-guard.test.ts +0 -163
  47. package/src/__tests__/scripted-provider/hook-contract-provider.ts +0 -179
  48. package/src/__tests__/scripted-provider/hook-probe-a.ts +0 -3
  49. package/src/__tests__/scripted-provider/hook-probe-b.ts +0 -3
  50. package/src/__tests__/scripted-provider/hook-probe-extension.ts +0 -126
  51. package/src/__tests__/scripted-provider/output-recovery-provider.ts +0 -153
  52. package/src/__tests__/scripted-provider/pi-hook-contract-evidence.json +0 -18
  53. package/src/__tests__/scripted-provider/pi-hook-contract.test.ts +0 -477
  54. package/src/__tests__/scripted-provider/runtime-guard-probe.ts +0 -28
  55. package/src/__tests__/scripted-provider/runtime-guard-provider.ts +0 -49
  56. package/src/__tests__/scripted-provider/scripted-provider-extension.ts +0 -408
  57. package/src/__tests__/task-manager.test.ts +0 -479
  58. package/src/__tests__/windows-taskkill.test.ts +0 -161
@@ -1,528 +0,0 @@
1
- import { describe, it, afterEach } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { createHash } from 'node:crypto';
4
- import { existsSync } from 'node:fs';
5
- import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
6
- import { tmpdir } from 'node:os';
7
- import { join } from 'node:path';
8
- import { DelegateArtifactStore } from '../delegate/artifacts.js';
9
- import { evaluateDelegateTerminal, decideDelegateDelivery, inlineTooLarge } from '../delegate/runner.js';
10
- import {
11
- buildDelegateResultPackage,
12
- serializeDelegateResultPackage,
13
- } from '../delegate/result-package.js';
14
- import { DelegateError, type DelegateLimits, type DelegatePinnedRoute } from '../delegate/types.js';
15
- import { DELEGATE_INLINE_ANSWER_BYTES } from '../delegate/budget.js';
16
-
17
- const roots: string[] = [];
18
-
19
- const ROUTE: DelegatePinnedRoute = {
20
- provider: 'anthropic',
21
- model: 'claude-test',
22
- qualified_id: 'anthropic/claude-test',
23
- context_window_tokens: 200_000,
24
- thinking_level: 'medium',
25
- origin: 'parent_current',
26
- };
27
-
28
- const LIMITS: DelegateLimits = {
29
- max_turns: 24,
30
- max_tool_calls: 120,
31
- timeout_seconds: 900,
32
- max_tool_result_bytes: 1024,
33
- max_total_tool_output_bytes: 8192,
34
- max_answer_bytes: 4_194_304,
35
- allowed_input_tokens: 171_712,
36
- };
37
-
38
- const TASK_ID = 'd0123456789abcdef0123456789abcdef';
39
- const NONCE = 'ffeeddccbbaa99887766554433221100';
40
- const SEED_SHA = 'a'.repeat(64);
41
-
42
- async function makeStore(taskId = TASK_ID) {
43
- const root = await mkdtemp(join(tmpdir(), 'pi-bg-delegate-artifacts-'));
44
- roots.push(root);
45
- const store = await DelegateArtifactStore.create({
46
- cwd: root,
47
- taskId,
48
- launchNonce: NONCE,
49
- sessionId: 'unit-session',
50
- childSessionId: 'delegate-child-session',
51
- childSessionDir: '',
52
- extensionMode: 'isolated',
53
- route: ROUTE,
54
- limits: LIMITS,
55
- seedSha256: SEED_SHA,
56
- });
57
- return { root, store };
58
- }
59
-
60
- afterEach(async () => {
61
- for (const root of roots.splice(0)) await rm(root, { recursive: true, force: true });
62
- });
63
-
64
- void describe('delegate artifact store', () => {
65
- void it('creates a private task-owned directory with a spill subdirectory', async () => {
66
- const { store } = await makeStore();
67
- assert.ok(existsSync(store.artifactDirAbs));
68
- assert.ok(existsSync(store.spillDirAbs));
69
- assert.equal(store.snapshot().extension_mode, 'isolated');
70
- if (process.platform !== 'win32') {
71
- const stats = await stat(store.artifactDirAbs);
72
- assert.equal(stats.mode & 0o777, 0o700, 'artifact directory must be private');
73
- }
74
- });
75
-
76
- void it('refuses to reuse an existing task directory', async () => {
77
- const { root } = await makeStore();
78
- await assert.rejects(
79
- DelegateArtifactStore.create({
80
- cwd: root,
81
- taskId: TASK_ID,
82
- launchNonce: NONCE,
83
- sessionId: 'unit-session',
84
- childSessionId: 'x',
85
- childSessionDir: '',
86
- extensionMode: 'isolated',
87
- route: ROUTE,
88
- limits: LIMITS,
89
- seedSha256: SEED_SHA,
90
- }),
91
- (error: unknown) => error instanceof DelegateError && error.code === 'artifact_error',
92
- );
93
- });
94
-
95
- void it('gives concurrent delegates non-colliding directories', async () => {
96
- const first = await makeStore('d11111111111111111111111111111111');
97
- const second = await DelegateArtifactStore.create({
98
- cwd: first.root,
99
- taskId: 'd22222222222222222222222222222222',
100
- launchNonce: NONCE,
101
- sessionId: 'unit-session',
102
- childSessionId: 'x',
103
- childSessionDir: '',
104
- extensionMode: 'ambient',
105
- route: ROUTE,
106
- limits: LIMITS,
107
- seedSha256: SEED_SHA,
108
- });
109
- assert.notEqual(first.store.artifactDirAbs, second.artifactDirAbs);
110
- assert.ok(existsSync(first.store.artifactDirAbs));
111
- assert.ok(existsSync(second.artifactDirAbs));
112
- });
113
-
114
- void it('persists the seed bytes exactly as given', async () => {
115
- const { store } = await makeStore();
116
- const seed = '{"schema_version":"pi-background-tasks.delegate-seed.v2","x":"\u2028\u2029👩"}';
117
- const ref = await store.writeSeed(seed);
118
- const onDisk = await readFile(join(store.artifactDirAbs, 'seed.json'), 'utf8');
119
- assert.equal(onDisk, seed, 'persisted seed bytes must equal the bytes handed to the child');
120
- assert.equal(ref.sha256, createHash('sha256').update(seed, 'utf8').digest('hex'));
121
- });
122
-
123
- void it('readCommittedResult advertises only control artifacts that exist', async () => {
124
- const { store } = await makeStore();
125
- await assert.rejects(
126
- store.readCommittedResult(),
127
- (error: unknown) => {
128
- assert.ok(error instanceof DelegateError);
129
- assert.equal(error.code, 'result_unavailable');
130
- assert.doesNotMatch(error.describe(), /child\.stdout|child\.stderr/);
131
- assert.ok(error.preserved.length > 0);
132
- assert.ok(
133
- error.preserved.every((path) => existsSync(join(store.artifactDirAbs, path))),
134
- );
135
- return true;
136
- },
137
- );
138
- });
139
-
140
- void it('spills an oversized payload and returns a receipt naming its coordinates', async () => {
141
- const { store } = await makeStore();
142
- const payload = Buffer.alloc(2 * 1024 * 1024, 0x41);
143
- const receipt = await store.spillToolPayload({
144
- toolName: 'read',
145
- toolCallId: 'call-abc',
146
- turnSequence: 3,
147
- sourceCallIndex: 1,
148
- payload,
149
- maxTotalBytes: 64 * 1024 * 1024,
150
- });
151
- assert.equal(receipt.byte_length, payload.length);
152
- assert.equal(receipt.sha256, createHash('sha256').update(payload).digest('hex'));
153
- assert.equal(receipt.turn_sequence, 3);
154
- assert.equal(receipt.source_call_index, 1);
155
- const onDisk = await readFile(join(store.artifactDirAbs, receipt.artifact));
156
- assert.equal(onDisk.length, payload.length, 'the complete payload must be preserved');
157
- assert.ok(onDisk.equals(payload), 'nothing may be truncated on the way to disk');
158
- });
159
-
160
- void it('associates parallel spills with the right call even when they interleave', async () => {
161
- const { store } = await makeStore();
162
- // Coordinates are assigned before execution, so completion order cannot
163
- // mis-associate a receipt with another call's bytes.
164
- const payloads = [
165
- { id: 'call-a', index: 0, byte: 0x61 },
166
- { id: 'call-b', index: 1, byte: 0x62 },
167
- { id: 'call-c', index: 2, byte: 0x63 },
168
- ];
169
- const receipts = await Promise.all(
170
- // Reverse order deliberately, to emulate out-of-order completion.
171
- [...payloads].reverse().map((entry) =>
172
- store.spillToolPayload({
173
- toolName: 'read',
174
- toolCallId: entry.id,
175
- turnSequence: 1,
176
- sourceCallIndex: entry.index,
177
- payload: Buffer.alloc(4096, entry.byte),
178
- maxTotalBytes: 64 * 1024 * 1024,
179
- }),
180
- ),
181
- );
182
- for (const receipt of receipts) {
183
- const source = payloads.find((entry) => entry.id === receipt.tool_call_id);
184
- assert.ok(source);
185
- assert.equal(receipt.source_call_index, source.index);
186
- const bytes = await readFile(join(store.artifactDirAbs, receipt.artifact));
187
- assert.ok(
188
- bytes.every((value) => value === source.byte),
189
- `${receipt.tool_call_id} receipt must point at its own bytes`,
190
- );
191
- }
192
- assert.equal(new Set(receipts.map((receipt) => receipt.artifact)).size, 3);
193
- });
194
-
195
- void it('refuses a spill that would exceed the aggregate cap, and emits no receipt', async () => {
196
- const { store } = await makeStore();
197
- await store.spillToolPayload({
198
- toolName: 'read',
199
- toolCallId: 'a',
200
- turnSequence: 1,
201
- sourceCallIndex: 0,
202
- payload: Buffer.alloc(4096, 1),
203
- maxTotalBytes: 8192,
204
- });
205
- const before = await readdir(store.spillDirAbs);
206
- await assert.rejects(
207
- store.spillToolPayload({
208
- toolName: 'read',
209
- toolCallId: 'b',
210
- turnSequence: 1,
211
- sourceCallIndex: 1,
212
- payload: Buffer.alloc(8192, 2),
213
- maxTotalBytes: 8192,
214
- }),
215
- (error: unknown) =>
216
- error instanceof DelegateError && error.code === 'aggregate_tool_output_cap',
217
- );
218
- assert.deepEqual(await readdir(store.spillDirAbs), before, 'a refused spill writes nothing');
219
- });
220
-
221
- void it('returns exactly the requested byte range', async () => {
222
- const { store } = await makeStore();
223
- const payload = Buffer.from('0123456789abcdefghij', 'utf8');
224
- const receipt = await store.spillToolPayload({
225
- toolName: 'read',
226
- toolCallId: 'range',
227
- turnSequence: 1,
228
- sourceCallIndex: 0,
229
- payload,
230
- maxTotalBytes: 65_536,
231
- });
232
- const read = await store.readSpillRange(receipt.artifact, 5, 6);
233
- assert.equal(read.bytes.toString('utf8'), '56789a');
234
- assert.equal(read.totalBytes, payload.length);
235
- });
236
-
237
- void it('refuses a range past end-of-file instead of silently shortening it', async () => {
238
- const { store } = await makeStore();
239
- const receipt = await store.spillToolPayload({
240
- toolName: 'read',
241
- toolCallId: 'short',
242
- turnSequence: 1,
243
- sourceCallIndex: 0,
244
- payload: Buffer.from('0123456789', 'utf8'),
245
- maxTotalBytes: 65_536,
246
- });
247
- await assert.rejects(
248
- store.readSpillRange(receipt.artifact, 5, 100),
249
- (error: unknown) =>
250
- error instanceof DelegateError &&
251
- error.code === 'artifact_read_failed' &&
252
- /refused rather than silently shortened/.test(error.message),
253
- );
254
- });
255
-
256
- void it('refuses a range read that escapes the artifact directory', async () => {
257
- const { store } = await makeStore();
258
- for (const path of ['../escape.txt', '../../etc/passwd', 'spill/../../outside']) {
259
- await assert.rejects(
260
- store.readSpillRange(path, 0, 1),
261
- (error: unknown) => error instanceof DelegateError && error.code === 'artifact_read_failed',
262
- );
263
- }
264
- });
265
-
266
- void it('refuses non-positive lengths and negative offsets', async () => {
267
- const { store } = await makeStore();
268
- for (const [offset, length] of [
269
- [-1, 1],
270
- [0, 0],
271
- [0, -5],
272
- [1.5, 1],
273
- ] as const) {
274
- await assert.rejects(
275
- store.readSpillRange('spill/whatever.bin', offset, length),
276
- (error: unknown) => error instanceof DelegateError && error.code === 'artifact_read_failed',
277
- );
278
- }
279
- });
280
- });
281
-
282
- void describe('delegate terminal evaluation', () => {
283
- void it('accepts a committed package and reports verified answer facts', async () => {
284
- const { store } = await makeStore();
285
- const pkg = buildDelegateResultPackage({
286
- taskId: TASK_ID,
287
- launchNonce: NONCE,
288
- seedSha256: SEED_SHA,
289
- directiveSha256: 'b'.repeat(64),
290
- route: { provider: ROUTE.provider, model: ROUTE.model },
291
- routeAttestations: [
292
- { provider: ROUTE.provider, model: ROUTE.model, stop_reason: 'stop' },
293
- ],
294
- stopReason: 'stop',
295
- turns: 4,
296
- toolCalls: 9,
297
- usage: { status: 'unavailable', reason: 'no usage reported' },
298
- answerBlocks: ['the verified answer'],
299
- spilledArtifacts: [],
300
- });
301
- await store.commitResult(pkg);
302
- const evaluation = await evaluateDelegateTerminal({
303
- artifactDirAbs: store.artifactDirAbs,
304
- taskId: TASK_ID,
305
- launchNonce: NONCE,
306
- seedSha256: SEED_SHA,
307
- route: { provider: ROUTE.provider, model: ROUTE.model },
308
- taskStatus: 'completed',
309
- taskError: undefined,
310
- });
311
- assert.equal(evaluation.outcome.status, 'committed');
312
- assert.equal(evaluation.result?.answer, 'the verified answer');
313
- assert.equal(evaluation.outcome.turns, 4);
314
- assert.equal(evaluation.outcome.toolCalls, 9);
315
- });
316
-
317
- void it('treats a zero-exit child with no committed package as a typed failure', async () => {
318
- const { store } = await makeStore();
319
- const evaluation = await evaluateDelegateTerminal({
320
- artifactDirAbs: store.artifactDirAbs,
321
- taskId: TASK_ID,
322
- launchNonce: NONCE,
323
- seedSha256: SEED_SHA,
324
- route: { provider: ROUTE.provider, model: ROUTE.model },
325
- taskStatus: 'completed',
326
- taskError: undefined,
327
- });
328
- assert.equal(evaluation.outcome.status, 'failed');
329
- assert.equal(evaluation.outcome.errorCode, 'child_exited_without_commit');
330
- assert.match(evaluation.error?.message ?? '', /no committed answer/);
331
- assert.match(evaluation.error?.describe() ?? '', /Preserved:/);
332
- assert.doesNotMatch(
333
- evaluation.error?.describe() ?? '',
334
- /child\.stderr\.txt|child\.stdout\.txt|Inspect child-terminal\.json/,
335
- 'terminal reporting must never advertise evidence files that do not exist',
336
- );
337
- });
338
-
339
- void it('does not advertise a configured merged output path that does not exist', async () => {
340
- const { root, store } = await makeStore();
341
- const missingAbsPath = join(root, '.pi', 'tasks', 'missing.output');
342
- const evaluation = await evaluateDelegateTerminal({
343
- artifactDirAbs: store.artifactDirAbs,
344
- taskId: TASK_ID,
345
- launchNonce: NONCE,
346
- seedSha256: SEED_SHA,
347
- route: { provider: ROUTE.provider, model: ROUTE.model },
348
- taskStatus: 'failed',
349
- taskError: 'spawn failed before output creation',
350
- taskOutputPath: '.pi/tasks/missing.output',
351
- taskOutputAbsPath: missingAbsPath,
352
- });
353
- assert.doesNotMatch(evaluation.error?.describe() ?? '', /missing\.output/);
354
- assert.doesNotMatch(evaluation.error?.describe() ?? '', /Inspect child-terminal\.json/);
355
- });
356
-
357
- void it('reports only a real merged task output path as preserved diagnostics', async () => {
358
- const { root, store } = await makeStore();
359
- const outputAbsPath = join(root, '.pi', 'tasks', 'delegate.output');
360
- await mkdir(join(root, '.pi', 'tasks'), { recursive: true });
361
- await writeFile(outputAbsPath, 'merged stdout and stderr', 'utf8');
362
- const evaluation = await evaluateDelegateTerminal({
363
- artifactDirAbs: store.artifactDirAbs,
364
- taskId: TASK_ID,
365
- launchNonce: NONCE,
366
- seedSha256: SEED_SHA,
367
- route: { provider: ROUTE.provider, model: ROUTE.model },
368
- taskStatus: 'failed',
369
- taskError: 'Exited with code 1',
370
- taskOutputPath: '.pi/tasks/delegate.output',
371
- taskOutputAbsPath: outputAbsPath,
372
- });
373
- assert.match(evaluation.error?.describe() ?? '', /\.pi\/tasks\/delegate\.output/);
374
- assert.doesNotMatch(evaluation.error?.describe() ?? '', /child\.stderr\.txt/);
375
- });
376
-
377
- void it('reports the child-recorded terminal reason when one exists', async () => {
378
- const { store } = await makeStore();
379
- await writeFile(
380
- join(store.artifactDirAbs, 'child-terminal.json'),
381
- JSON.stringify({
382
- schema_version: 'pi-background-tasks.delegate-child-terminal.v1',
383
- code: 'provider_context_budget_exhausted',
384
- message: 'context exceeded the pinned route window',
385
- }),
386
- 'utf8',
387
- );
388
- const evaluation = await evaluateDelegateTerminal({
389
- artifactDirAbs: store.artifactDirAbs,
390
- taskId: TASK_ID,
391
- launchNonce: NONCE,
392
- seedSha256: SEED_SHA,
393
- route: { provider: ROUTE.provider, model: ROUTE.model },
394
- taskStatus: 'failed',
395
- taskError: 'Exited with code 1',
396
- });
397
- assert.equal(evaluation.outcome.errorCode, 'provider_context_budget_exhausted');
398
- assert.match(evaluation.error?.message ?? '', /context exceeded the pinned route window/);
399
- });
400
-
401
- void it('records the parent adjudication separately from the child package', async () => {
402
- const { store } = await makeStore();
403
- const pkg = buildDelegateResultPackage({
404
- taskId: TASK_ID,
405
- launchNonce: NONCE,
406
- seedSha256: SEED_SHA,
407
- directiveSha256: 'b'.repeat(64),
408
- route: { provider: ROUTE.provider, model: ROUTE.model },
409
- routeAttestations: [{ provider: ROUTE.provider, model: ROUTE.model, stop_reason: 'stop' }],
410
- stopReason: 'stop',
411
- turns: 1,
412
- toolCalls: 0,
413
- usage: { status: 'unavailable', reason: 'none' },
414
- answerBlocks: ['answer'],
415
- spilledArtifacts: [],
416
- });
417
- await store.commitResult(pkg);
418
- await evaluateDelegateTerminal({
419
- artifactDirAbs: store.artifactDirAbs,
420
- taskId: TASK_ID,
421
- launchNonce: NONCE,
422
- seedSha256: SEED_SHA,
423
- route: { provider: ROUTE.provider, model: ROUTE.model },
424
- taskStatus: 'completed',
425
- taskError: undefined,
426
- });
427
- // The child writes result.json; the parent writes outcome.json. Keeping the
428
- // two writers on separate artifacts means neither can claim a state it did
429
- // not observe, and a stale manifest field can never imply success.
430
- const outcome = JSON.parse(
431
- await readFile(join(store.artifactDirAbs, 'outcome.json'), 'utf8'),
432
- ) as Record<string, unknown>;
433
- assert.equal(outcome['schema_version'], 'pi-background-tasks.delegate-outcome.v1');
434
- assert.equal(outcome['task_id'], TASK_ID);
435
- assert.equal(outcome['observed_task_status'], 'completed');
436
- assert.equal(outcome['error_code'], null);
437
- const recorded = outcome['outcome'];
438
- assert.ok(typeof recorded === 'object' && recorded !== null);
439
- assert.equal(Reflect.get(recorded, 'status'), 'committed');
440
- });
441
-
442
- void it('records a typed adjudication for a child that never committed', async () => {
443
- const { store } = await makeStore();
444
- await evaluateDelegateTerminal({
445
- artifactDirAbs: store.artifactDirAbs,
446
- taskId: TASK_ID,
447
- launchNonce: NONCE,
448
- seedSha256: SEED_SHA,
449
- route: { provider: ROUTE.provider, model: ROUTE.model },
450
- taskStatus: 'completed',
451
- taskError: undefined,
452
- });
453
- const outcome = JSON.parse(
454
- await readFile(join(store.artifactDirAbs, 'outcome.json'), 'utf8'),
455
- ) as Record<string, unknown>;
456
- assert.equal(outcome['error_code'], 'child_exited_without_commit');
457
- });
458
-
459
- void it('classifies a killed task as cancelled', async () => {
460
- const { store } = await makeStore();
461
- const evaluation = await evaluateDelegateTerminal({
462
- artifactDirAbs: store.artifactDirAbs,
463
- taskId: TASK_ID,
464
- launchNonce: NONCE,
465
- seedSha256: SEED_SHA,
466
- route: { provider: ROUTE.provider, model: ROUTE.model },
467
- taskStatus: 'killed',
468
- taskError: 'killed by user',
469
- });
470
- assert.equal(evaluation.outcome.status, 'cancelled');
471
- assert.equal(evaluation.outcome.errorCode, 'child_cancelled');
472
- });
473
-
474
- void it('detects a corrupted committed package rather than returning its bytes', async () => {
475
- const { store } = await makeStore();
476
- const pkg = buildDelegateResultPackage({
477
- taskId: TASK_ID,
478
- launchNonce: NONCE,
479
- seedSha256: SEED_SHA,
480
- directiveSha256: 'b'.repeat(64),
481
- route: { provider: ROUTE.provider, model: ROUTE.model },
482
- routeAttestations: [{ provider: ROUTE.provider, model: ROUTE.model, stop_reason: 'stop' }],
483
- stopReason: 'stop',
484
- turns: 1,
485
- toolCalls: 0,
486
- usage: { status: 'unavailable', reason: 'none' },
487
- answerBlocks: ['original answer'],
488
- spilledArtifacts: [],
489
- });
490
- const serialized = serializeDelegateResultPackage(pkg);
491
- const corrupted = serialized.replace(
492
- pkg.answer.blocks[0]?.data_base64 ?? '',
493
- Buffer.from('substituted', 'utf8').toString('base64'),
494
- );
495
- await writeFile(join(store.artifactDirAbs, 'result.json'), corrupted, 'utf8');
496
- const evaluation = await evaluateDelegateTerminal({
497
- artifactDirAbs: store.artifactDirAbs,
498
- taskId: TASK_ID,
499
- launchNonce: NONCE,
500
- seedSha256: SEED_SHA,
501
- route: { provider: ROUTE.provider, model: ROUTE.model },
502
- taskStatus: 'completed',
503
- taskError: undefined,
504
- });
505
- assert.equal(evaluation.outcome.status, 'failed');
506
- assert.equal(evaluation.outcome.errorCode, 'answer_hash_mismatch');
507
- assert.equal(evaluation.result, undefined, 'a corrupted answer is never returned');
508
- });
509
- });
510
-
511
- void describe('delegate delivery decisions', () => {
512
- void it('inlines at exactly the cap and degrades one byte past it', () => {
513
- assert.equal(decideDelegateDelivery(DELEGATE_INLINE_ANSWER_BYTES, undefined).mode, 'inline');
514
- assert.equal(decideDelegateDelivery(DELEGATE_INLINE_ANSWER_BYTES + 1, undefined).mode, 'artifact');
515
- });
516
-
517
- void it('honours an explicit artifact request for a small answer', () => {
518
- assert.equal(decideDelegateDelivery(10, 'artifact').mode, 'artifact');
519
- });
520
-
521
- void it('never truncates: an explicit oversized inline request is a typed failure', () => {
522
- const error = inlineTooLarge('dtask', '.pi/delegate/x', DELEGATE_INLINE_ANSWER_BYTES + 1);
523
- assert.equal(error.code, 'result_too_large_for_inline');
524
- assert.match(error.message, /not truncated to fit/);
525
- assert.ok(error.remediation.length > 0);
526
- assert.ok(error.preserved.length > 0);
527
- });
528
- });