@pi-unipi/background-tasks 2.6.3 → 2.9.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/package.json +6 -5
- package/src/__tests__/anthropic-attribution.test.ts +0 -195
- package/src/__tests__/config.test.ts +0 -137
- package/src/__tests__/core.test.ts +0 -493
- package/src/__tests__/delegate-artifacts.test.ts +0 -528
- package/src/__tests__/delegate-budget.test.ts +0 -456
- package/src/__tests__/delegate-launch.test.ts +0 -676
- package/src/__tests__/delegate-result-package.test.ts +0 -350
- package/src/__tests__/delegate-seed.test.ts +0 -392
- package/src/__tests__/durable-fs.test.ts +0 -559
- package/src/__tests__/extension-api.test.ts +0 -579
- package/src/__tests__/fusion-artifacts.test.ts +0 -1039
- package/src/__tests__/fusion-budget.test.ts +0 -1356
- package/src/__tests__/fusion-claude-cache.test.ts +0 -320
- package/src/__tests__/fusion-config.test.ts +0 -335
- package/src/__tests__/fusion-context-prompts.test.ts +0 -670
- package/src/__tests__/fusion-evaluation.test.ts +0 -315
- package/src/__tests__/fusion-extraction-equivalence.test.ts +0 -58
- package/src/__tests__/fusion-golden-bytes.test.ts +0 -35
- package/src/__tests__/fusion-high-cardinality.test.ts +0 -192
- package/src/__tests__/fusion-model-selector.test.ts +0 -205
- package/src/__tests__/fusion-orchestrator.test.ts +0 -1194
- package/src/__tests__/fusion-rpc.test.ts +0 -369
- package/src/__tests__/fusion-sdk.test.ts +0 -1226
- package/src/__tests__/fusion-v5-core.test.ts +0 -219
- package/src/__tests__/fusion-validate-orchestrator.test.ts +0 -240
- package/src/__tests__/fusion-web-fetch.test.ts +0 -485
- package/src/__tests__/fusion-workflows.test.ts +0 -59
- package/src/__tests__/helpers/delegate-deterministic-seed.ts +0 -109
- package/src/__tests__/helpers/delegate-seed-subprocess.ts +0 -10
- package/src/__tests__/helpers/fusion-canonical-subprocess.ts +0 -21
- package/src/__tests__/helpers/fusion-canonical.ts +0 -140
- package/src/__tests__/helpers/fusion-fake-pi.ts +0 -279
- package/src/__tests__/helpers/fusion-golden-corpus.ts +0 -500
- package/src/__tests__/helpers/fusion-high-cardinality.ts +0 -140
- package/src/__tests__/helpers/normalize.ts +0 -22
- package/src/__tests__/helpers/pi-hook-contract-evidence.json +0 -18
- package/src/__tests__/pi-launch.test.ts +0 -202
- package/src/__tests__/registry.test.ts +0 -1580
- package/src/__tests__/scripted-provider/delegate-ambient-provider.test.ts +0 -130
- package/src/__tests__/scripted-provider/delegate-child-guard.test.ts +0 -631
- package/src/__tests__/scripted-provider/delegate-guard-provider.ts +0 -403
- package/src/__tests__/scripted-provider/follow-up.test.ts +0 -448
- package/src/__tests__/scripted-provider/fusion-output-recovery.test.ts +0 -132
- package/src/__tests__/scripted-provider/fusion-reason.test.ts +0 -310
- package/src/__tests__/scripted-provider/fusion-runtime-guard.test.ts +0 -163
- package/src/__tests__/scripted-provider/hook-contract-provider.ts +0 -179
- package/src/__tests__/scripted-provider/hook-probe-a.ts +0 -3
- package/src/__tests__/scripted-provider/hook-probe-b.ts +0 -3
- package/src/__tests__/scripted-provider/hook-probe-extension.ts +0 -126
- package/src/__tests__/scripted-provider/output-recovery-provider.ts +0 -153
- package/src/__tests__/scripted-provider/pi-hook-contract-evidence.json +0 -18
- package/src/__tests__/scripted-provider/pi-hook-contract.test.ts +0 -477
- package/src/__tests__/scripted-provider/runtime-guard-probe.ts +0 -28
- package/src/__tests__/scripted-provider/runtime-guard-provider.ts +0 -49
- package/src/__tests__/scripted-provider/scripted-provider-extension.ts +0 -408
- package/src/__tests__/task-manager.test.ts +0 -479
- package/src/__tests__/windows-taskkill.test.ts +0 -161
|
@@ -1,1039 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
|
-
import { sha256Buffer } from '../attested-pi-run.js';
|
|
5
|
-
import { existsSync } from 'node:fs';
|
|
6
|
-
import { mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
|
|
7
|
-
import { join } from 'node:path';
|
|
8
|
-
import { tmpdir } from 'node:os';
|
|
9
|
-
import { canonicalJson } from '../attested-pi-run.js';
|
|
10
|
-
import { parseJsonText } from '../types.js';
|
|
11
|
-
import {
|
|
12
|
-
FUSION_FAILURE_SUMMARY_ATTEMPT_CAP,
|
|
13
|
-
FUSION_FAILURE_SUMMARY_EVIDENCE_CAP,
|
|
14
|
-
FusionArtifactStore,
|
|
15
|
-
buildFusionFailureSummary,
|
|
16
|
-
buildFusionRunProgress,
|
|
17
|
-
} from '../fusion/artifacts.js';
|
|
18
|
-
import { defaultFusionModelConfig } from '../fusion/config.js';
|
|
19
|
-
import { readFusionCommittedResult, readFusionFailureResult } from '../fusion/result-package.js';
|
|
20
|
-
import {
|
|
21
|
-
FUSION_FAILURE_SUMMARY_SCHEMA_VERSION,
|
|
22
|
-
FUSION_RESULT_SCHEMA_VERSION,
|
|
23
|
-
FUSION_TOOL_CALL_LOG_SCHEMA_VERSION,
|
|
24
|
-
FusionError,
|
|
25
|
-
addFusionUsage,
|
|
26
|
-
cloneFusionUsage,
|
|
27
|
-
createEmptyFusionUsage,
|
|
28
|
-
type FusionChildRunResult,
|
|
29
|
-
type FusionResultDetails,
|
|
30
|
-
type ResolvedFusionModel,
|
|
31
|
-
type ResolvedFusionModels,
|
|
32
|
-
} from '../fusion/types.js';
|
|
33
|
-
|
|
34
|
-
function resolved(qualifiedId: string): ResolvedFusionModel {
|
|
35
|
-
const slash = qualifiedId.indexOf('/');
|
|
36
|
-
const provider = qualifiedId.slice(0, slash);
|
|
37
|
-
const model = qualifiedId.slice(slash + 1);
|
|
38
|
-
return {
|
|
39
|
-
selection: '$current',
|
|
40
|
-
source: 'current',
|
|
41
|
-
provider,
|
|
42
|
-
model,
|
|
43
|
-
qualifiedId,
|
|
44
|
-
thinkingLevel: 'medium',
|
|
45
|
-
contextWindow: 1000,
|
|
46
|
-
maxOutputTokens: 128,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function models(): ResolvedFusionModels {
|
|
51
|
-
return {
|
|
52
|
-
candidates: [resolved('p/a'), resolved('p/b'), resolved('p/c')],
|
|
53
|
-
evaluator: resolved('p/e'),
|
|
54
|
-
merger: resolved('p/m'),
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function childResult(
|
|
59
|
-
stage: 'candidate' | 'evaluation' | 'merge',
|
|
60
|
-
text: string,
|
|
61
|
-
): FusionChildRunResult {
|
|
62
|
-
const result: FusionChildRunResult = {
|
|
63
|
-
stage,
|
|
64
|
-
attempt: 1,
|
|
65
|
-
provider: 'p',
|
|
66
|
-
model: 'a',
|
|
67
|
-
qualifiedId: 'p/a',
|
|
68
|
-
text,
|
|
69
|
-
usage: {
|
|
70
|
-
input: 1,
|
|
71
|
-
output: 2,
|
|
72
|
-
cacheRead: 0,
|
|
73
|
-
cacheWrite: 0,
|
|
74
|
-
totalTokens: 3,
|
|
75
|
-
cost: { input: 0.01, output: 0.02, cacheRead: 0, cacheWrite: 0, total: 0.03 },
|
|
76
|
-
},
|
|
77
|
-
events: Buffer.from('{"schema_version":"pi-background-tasks.fusion-child-result.v4"}\n'),
|
|
78
|
-
stderr: Buffer.from('stderr'),
|
|
79
|
-
exitCode: 0,
|
|
80
|
-
signal: null,
|
|
81
|
-
};
|
|
82
|
-
if (stage === 'candidate') result.slot = 1;
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function parseManifest(text: string): object {
|
|
87
|
-
const parsed = parseJsonText(text);
|
|
88
|
-
assert.ok(typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed));
|
|
89
|
-
return parsed;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function field(record: object, key: string): unknown {
|
|
93
|
-
return Reflect.get(record, key);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function record(value: unknown, label: string): Record<string, unknown> {
|
|
97
|
-
assert.ok(typeof value === 'object' && value !== null && !Array.isArray(value), label);
|
|
98
|
-
return value as Record<string, unknown>;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function createVerifiedFailureStore(
|
|
102
|
-
root: string,
|
|
103
|
-
terminalState: 'failed' | 'cancelled' = 'failed',
|
|
104
|
-
): Promise<{ store: FusionArtifactStore; summary: ReturnType<typeof buildFusionFailureSummary> }> {
|
|
105
|
-
const store = await FusionArtifactStore.create({
|
|
106
|
-
cwd: root,
|
|
107
|
-
runId: `reason-${terminalState === 'failed' ? '4' : '5'}`.padEnd(39, terminalState === 'failed' ? '4' : '5'),
|
|
108
|
-
source: 'tool',
|
|
109
|
-
config: defaultFusionModelConfig(),
|
|
110
|
-
models: models(),
|
|
111
|
-
now: () => new Date('2026-01-01T00:00:00.000Z'),
|
|
112
|
-
});
|
|
113
|
-
await store.transition('candidates_running');
|
|
114
|
-
await store.recordFailedAttempt({
|
|
115
|
-
stage: 'candidate',
|
|
116
|
-
slot: 1,
|
|
117
|
-
attempt: 1,
|
|
118
|
-
systemPrompt: 'system',
|
|
119
|
-
prompt: 'prompt',
|
|
120
|
-
events: Buffer.from('STAGE_OUTPUT_BODY_MUST_NOT_BE_READ'),
|
|
121
|
-
partialResponse: Buffer.from('PARTIAL_STAGE_OUTPUT_MUST_NOT_BE_READ'),
|
|
122
|
-
stderr: Buffer.from('stderr'),
|
|
123
|
-
error: 'candidate failed',
|
|
124
|
-
status: terminalState,
|
|
125
|
-
childCreated: true,
|
|
126
|
-
responseKind: 'md',
|
|
127
|
-
});
|
|
128
|
-
const message = `${terminalState} terminal error`;
|
|
129
|
-
await store.writeError(terminalState, message);
|
|
130
|
-
const manifest = store.snapshot();
|
|
131
|
-
const summary = buildFusionFailureSummary({
|
|
132
|
-
manifest,
|
|
133
|
-
terminalError: new FusionError(message, {
|
|
134
|
-
code: terminalState === 'cancelled' ? 'child_cancelled' : 'child_exit_failed',
|
|
135
|
-
stage: 'candidate',
|
|
136
|
-
slot: 1,
|
|
137
|
-
attempt: 1,
|
|
138
|
-
childCreated: true,
|
|
139
|
-
}),
|
|
140
|
-
progress: buildFusionRunProgress(manifest),
|
|
141
|
-
terminalState,
|
|
142
|
-
createdAt: manifest.updated_at,
|
|
143
|
-
});
|
|
144
|
-
await store.writeFailureSummary(summary);
|
|
145
|
-
return { store, summary };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async function rewriteBoundFailureSummary(
|
|
149
|
-
store: FusionArtifactStore,
|
|
150
|
-
value: Buffer | unknown,
|
|
151
|
-
): Promise<void> {
|
|
152
|
-
const bytes = Buffer.isBuffer(value)
|
|
153
|
-
? value
|
|
154
|
-
: Buffer.from(`${JSON.stringify(value)}\n`, 'utf8');
|
|
155
|
-
await writeFile(join(store.artifactDirAbs, 'failure-summary.json'), bytes);
|
|
156
|
-
const manifest = record(
|
|
157
|
-
parseManifest(await readFile(join(store.artifactDirAbs, 'manifest.json'), 'utf8')),
|
|
158
|
-
'manifest',
|
|
159
|
-
);
|
|
160
|
-
const artifacts = record(manifest['artifacts'], 'artifacts');
|
|
161
|
-
artifacts['failure-summary.json'] = {
|
|
162
|
-
path: 'failure-summary.json',
|
|
163
|
-
byte_length: bytes.length,
|
|
164
|
-
sha256: sha256Buffer(bytes),
|
|
165
|
-
};
|
|
166
|
-
await writeFile(
|
|
167
|
-
join(store.artifactDirAbs, 'manifest.json'),
|
|
168
|
-
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
169
|
-
'utf8',
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
void describe('fusion artifacts', () => {
|
|
174
|
-
void it('preserves and aggregates one-hour cache writes and reasoning subsets', () => {
|
|
175
|
-
const usage = {
|
|
176
|
-
input: 10,
|
|
177
|
-
output: 8,
|
|
178
|
-
cacheRead: 6,
|
|
179
|
-
cacheWrite: 4,
|
|
180
|
-
cacheWrite1h: 3,
|
|
181
|
-
reasoning: 5,
|
|
182
|
-
totalTokens: 28,
|
|
183
|
-
cost: { input: 0.1, output: 0.2, cacheRead: 0.03, cacheWrite: 0.04, total: 0.37 },
|
|
184
|
-
};
|
|
185
|
-
assert.deepEqual(cloneFusionUsage(usage), usage);
|
|
186
|
-
|
|
187
|
-
const total = createEmptyFusionUsage();
|
|
188
|
-
addFusionUsage(total, usage);
|
|
189
|
-
addFusionUsage(total, {
|
|
190
|
-
input: 2,
|
|
191
|
-
output: 3,
|
|
192
|
-
cacheRead: 5,
|
|
193
|
-
cacheWrite: 7,
|
|
194
|
-
cacheWrite1h: 6,
|
|
195
|
-
reasoning: 2,
|
|
196
|
-
totalTokens: 17,
|
|
197
|
-
cost: { input: 0.02, output: 0.03, cacheRead: 0.05, cacheWrite: 0.07, total: 0.17 },
|
|
198
|
-
});
|
|
199
|
-
assert.equal(total.cacheWrite1h, 9);
|
|
200
|
-
assert.equal(total.reasoning, 7);
|
|
201
|
-
assert.equal(total.cacheWrite, 11);
|
|
202
|
-
assert.equal(total.output, 11);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
void it('creates private run files and records child attempt artifacts', async () => {
|
|
206
|
-
const root = await mkdtemp(join(tmpdir(), 'pi-fusion-artifacts-'));
|
|
207
|
-
try {
|
|
208
|
-
const store = await FusionArtifactStore.create({
|
|
209
|
-
cwd: root,
|
|
210
|
-
sessionId: 'session/id',
|
|
211
|
-
runId: 'reason-00000000000000000000000000000000',
|
|
212
|
-
source: 'command',
|
|
213
|
-
config: defaultFusionModelConfig(),
|
|
214
|
-
models: models(),
|
|
215
|
-
capabilities: { candidate: 'inspect', evaluation: 'reason', merge: 'reason' },
|
|
216
|
-
now: () => new Date('2026-01-01T00:00:00.000Z'),
|
|
217
|
-
});
|
|
218
|
-
// Normalize separators: the artifact dir uses native path separators, so
|
|
219
|
-
// it is backslash-delimited on Windows.
|
|
220
|
-
assert.match(store.artifactDir.replaceAll('\\', '/'), /^\.unipi\/fusion\/session-id-/);
|
|
221
|
-
const dirMode = (await stat(store.artifactDirAbs)).mode & 0o777;
|
|
222
|
-
// Windows has no POSIX permission bits; NTFS ACLs are not modelled here.
|
|
223
|
-
if (process.platform !== 'win32') assert.equal(dirMode, 0o700);
|
|
224
|
-
await store.writeCanonicalInput('{"request":"x"}');
|
|
225
|
-
await store.transition('candidates_running');
|
|
226
|
-
await store.recordChildAttempt({
|
|
227
|
-
result: childResult('candidate', 'answer'),
|
|
228
|
-
systemPrompt: 'system prompt',
|
|
229
|
-
prompt: 'prompt',
|
|
230
|
-
responseKind: 'md',
|
|
231
|
-
});
|
|
232
|
-
const responsePath = join(store.artifactDirAbs, 'candidate-1.attempt-1.response.md');
|
|
233
|
-
assert.equal(await readFile(responsePath, 'utf8'), 'answer');
|
|
234
|
-
if (process.platform !== 'win32')
|
|
235
|
-
assert.equal((await stat(responsePath)).mode & 0o777, 0o600);
|
|
236
|
-
const manifest = parseManifest(
|
|
237
|
-
await readFile(join(store.artifactDirAbs, 'manifest.json'), 'utf8'),
|
|
238
|
-
);
|
|
239
|
-
assert.equal(field(manifest, 'state'), 'candidates_running');
|
|
240
|
-
assert.deepEqual(field(manifest, 'capabilities'), {
|
|
241
|
-
candidate: 'inspect',
|
|
242
|
-
evaluation: 'reason',
|
|
243
|
-
merge: 'reason',
|
|
244
|
-
});
|
|
245
|
-
const attempts = field(manifest, 'attempts');
|
|
246
|
-
assert.ok(Array.isArray(attempts));
|
|
247
|
-
assert.equal(attempts.length, 1);
|
|
248
|
-
const firstAttempt = attempts[0];
|
|
249
|
-
assert.ok(typeof firstAttempt === 'object' && firstAttempt !== null);
|
|
250
|
-
assert.equal(field(firstAttempt, 'response_path'), 'candidate-1.attempt-1.response.md');
|
|
251
|
-
assert.equal(field(firstAttempt, 'tool_calls_path'), undefined);
|
|
252
|
-
assert.equal(field(firstAttempt, 'tool_calls'), undefined);
|
|
253
|
-
assert.equal(field(firstAttempt, 'provider'), 'p');
|
|
254
|
-
assert.equal(field(firstAttempt, 'qualifiedId'), 'p/a');
|
|
255
|
-
const usageRecord = field(firstAttempt, 'usage');
|
|
256
|
-
assert.ok(typeof usageRecord === 'object' && usageRecord !== null);
|
|
257
|
-
assert.equal(field(usageRecord, 'totalTokens'), 3);
|
|
258
|
-
assert.deepEqual(field(usageRecord, 'cost'), {
|
|
259
|
-
input: 0.01,
|
|
260
|
-
output: 0.02,
|
|
261
|
-
cacheRead: 0,
|
|
262
|
-
cacheWrite: 0,
|
|
263
|
-
total: 0.03,
|
|
264
|
-
});
|
|
265
|
-
assert.deepEqual(
|
|
266
|
-
(await readdir(store.artifactDirAbs)).filter((entry) => entry.endsWith('.tmp')),
|
|
267
|
-
[],
|
|
268
|
-
);
|
|
269
|
-
const artifacts = field(manifest, 'artifacts');
|
|
270
|
-
assert.ok(typeof artifacts === 'object' && artifacts !== null);
|
|
271
|
-
assert.ok(Reflect.has(artifacts, 'canonical-input.json'));
|
|
272
|
-
assert.equal(Reflect.has(artifacts, 'candidate-1.attempt-1.tool-calls.jsonl'), false);
|
|
273
|
-
} finally {
|
|
274
|
-
await rm(root, { recursive: true, force: true });
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
void it('records same-session output recovery without putting original text in the manifest', async () => {
|
|
279
|
-
const root = await mkdtemp(join(tmpdir(), 'pi-fusion-artifacts-output-recovery-'));
|
|
280
|
-
try {
|
|
281
|
-
const store = await FusionArtifactStore.create({
|
|
282
|
-
cwd: root,
|
|
283
|
-
runId: 'reason-00000000000000000000000000000002',
|
|
284
|
-
source: 'command',
|
|
285
|
-
config: defaultFusionModelConfig(),
|
|
286
|
-
models: models(),
|
|
287
|
-
});
|
|
288
|
-
const result = childResult('candidate', 'compressed answer');
|
|
289
|
-
const original = 'o'.repeat(50_000);
|
|
290
|
-
result.outputRecovery = {
|
|
291
|
-
kind: 'same_session_compression',
|
|
292
|
-
limit_bytes: 49_152,
|
|
293
|
-
original_record_index: 0,
|
|
294
|
-
replacement_record_index: 1,
|
|
295
|
-
original_json_rendered_bytes: 50_002,
|
|
296
|
-
replacement_json_rendered_bytes: 19,
|
|
297
|
-
original_text_sha256: createHash('sha256').update(original).digest('hex'),
|
|
298
|
-
original_text: original,
|
|
299
|
-
status: 'completed',
|
|
300
|
-
};
|
|
301
|
-
await store.recordChildAttempt({
|
|
302
|
-
result,
|
|
303
|
-
systemPrompt: 'system prompt',
|
|
304
|
-
prompt: 'prompt',
|
|
305
|
-
responseKind: 'md',
|
|
306
|
-
});
|
|
307
|
-
assert.equal(
|
|
308
|
-
await readFile(
|
|
309
|
-
join(store.artifactDirAbs, 'candidate-1.attempt-1.response.oversized.md'),
|
|
310
|
-
'utf8',
|
|
311
|
-
),
|
|
312
|
-
original,
|
|
313
|
-
);
|
|
314
|
-
const manifest = parseManifest(
|
|
315
|
-
await readFile(join(store.artifactDirAbs, 'manifest.json'), 'utf8'),
|
|
316
|
-
);
|
|
317
|
-
const attempts = field(manifest, 'attempts');
|
|
318
|
-
assert.ok(Array.isArray(attempts));
|
|
319
|
-
const attempt: unknown = attempts[0];
|
|
320
|
-
assert.ok(typeof attempt === 'object' && attempt !== null);
|
|
321
|
-
assert.equal(field(attempt, 'child_created'), true);
|
|
322
|
-
assert.deepEqual(field(attempt, 'output_recovery'), {
|
|
323
|
-
kind: 'same_session_compression',
|
|
324
|
-
status: 'completed',
|
|
325
|
-
limit_bytes: 49_152,
|
|
326
|
-
original_response_path: 'candidate-1.attempt-1.response.oversized.md',
|
|
327
|
-
original_record_index: 0,
|
|
328
|
-
replacement_record_index: 1,
|
|
329
|
-
original_json_rendered_bytes: 50_002,
|
|
330
|
-
replacement_json_rendered_bytes: 19,
|
|
331
|
-
original_text_sha256: createHash('sha256').update(original).digest('hex'),
|
|
332
|
-
});
|
|
333
|
-
assert.equal(JSON.stringify(manifest).includes(original), false);
|
|
334
|
-
} finally {
|
|
335
|
-
await rm(root, { recursive: true, force: true });
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
void it('records completed child tool-call logs and summaries on attempts', async () => {
|
|
340
|
-
const root = await mkdtemp(join(tmpdir(), 'pi-fusion-artifacts-tools-'));
|
|
341
|
-
try {
|
|
342
|
-
const store = await FusionArtifactStore.create({
|
|
343
|
-
cwd: root,
|
|
344
|
-
runId: 'reason-00000000000000000000000000000001',
|
|
345
|
-
source: 'command',
|
|
346
|
-
config: defaultFusionModelConfig(),
|
|
347
|
-
models: models(),
|
|
348
|
-
capabilities: { candidate: 'inspect', evaluation: 'reason', merge: 'reason' },
|
|
349
|
-
});
|
|
350
|
-
const result = childResult('candidate', 'answer');
|
|
351
|
-
const logText =
|
|
352
|
-
`${JSON.stringify({
|
|
353
|
-
schema_version: FUSION_TOOL_CALL_LOG_SCHEMA_VERSION,
|
|
354
|
-
ordinal: 0,
|
|
355
|
-
tool_name: 'read',
|
|
356
|
-
arguments_sha256: 'a'.repeat(64),
|
|
357
|
-
arguments_bytes: 17,
|
|
358
|
-
result_bytes: 23,
|
|
359
|
-
result_sha256: 'b'.repeat(64),
|
|
360
|
-
status: 'ok',
|
|
361
|
-
duration_ms: 4,
|
|
362
|
-
})}\n` +
|
|
363
|
-
`${JSON.stringify({
|
|
364
|
-
schema_version: FUSION_TOOL_CALL_LOG_SCHEMA_VERSION,
|
|
365
|
-
ordinal: 1,
|
|
366
|
-
tool_name: 'grep',
|
|
367
|
-
arguments_sha256: 'c'.repeat(64),
|
|
368
|
-
arguments_bytes: 19,
|
|
369
|
-
result_bytes: 29,
|
|
370
|
-
result_sha256: 'd'.repeat(64),
|
|
371
|
-
status: 'error',
|
|
372
|
-
duration_ms: 8,
|
|
373
|
-
})}\n`;
|
|
374
|
-
result.toolCallTrace = {
|
|
375
|
-
bytes: Buffer.from(logText, 'utf8'),
|
|
376
|
-
records: [],
|
|
377
|
-
summary: { count: 2, total_result_bytes: 52, trace_complete: true },
|
|
378
|
-
};
|
|
379
|
-
await store.recordChildAttempt({
|
|
380
|
-
result,
|
|
381
|
-
systemPrompt: 'system prompt',
|
|
382
|
-
prompt: 'prompt',
|
|
383
|
-
responseKind: 'md',
|
|
384
|
-
});
|
|
385
|
-
assert.equal(
|
|
386
|
-
await readFile(
|
|
387
|
-
join(store.artifactDirAbs, 'candidate-1.attempt-1.tool-calls.jsonl'),
|
|
388
|
-
'utf8',
|
|
389
|
-
),
|
|
390
|
-
logText,
|
|
391
|
-
);
|
|
392
|
-
const manifest = parseManifest(
|
|
393
|
-
await readFile(join(store.artifactDirAbs, 'manifest.json'), 'utf8'),
|
|
394
|
-
);
|
|
395
|
-
const attempts = field(manifest, 'attempts');
|
|
396
|
-
assert.ok(Array.isArray(attempts));
|
|
397
|
-
const firstAttempt = attempts[0];
|
|
398
|
-
assert.ok(typeof firstAttempt === 'object' && firstAttempt !== null);
|
|
399
|
-
assert.equal(
|
|
400
|
-
field(firstAttempt, 'tool_calls_path'),
|
|
401
|
-
'candidate-1.attempt-1.tool-calls.jsonl',
|
|
402
|
-
);
|
|
403
|
-
assert.deepEqual(field(firstAttempt, 'tool_calls'), {
|
|
404
|
-
count: 2,
|
|
405
|
-
total_result_bytes: 52,
|
|
406
|
-
trace_complete: true,
|
|
407
|
-
});
|
|
408
|
-
const artifacts = field(manifest, 'artifacts');
|
|
409
|
-
assert.ok(typeof artifacts === 'object' && artifacts !== null);
|
|
410
|
-
assert.ok(Reflect.has(artifacts, 'candidate-1.attempt-1.tool-calls.jsonl'));
|
|
411
|
-
} finally {
|
|
412
|
-
await rm(root, { recursive: true, force: true });
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
void it('enforces lifecycle ordering and durable merge before completion', async () => {
|
|
417
|
-
const root = await mkdtemp(join(tmpdir(), 'pi-fusion-state-'));
|
|
418
|
-
try {
|
|
419
|
-
const store = await FusionArtifactStore.create({
|
|
420
|
-
cwd: root,
|
|
421
|
-
runId: 'reason-11111111111111111111111111111111',
|
|
422
|
-
source: 'tool',
|
|
423
|
-
config: defaultFusionModelConfig(),
|
|
424
|
-
models: models(),
|
|
425
|
-
});
|
|
426
|
-
await assert.rejects(store.transition('evaluating'), /illegal fusion state transition/);
|
|
427
|
-
await store.transition('candidates_running');
|
|
428
|
-
await store.transition('candidates_complete');
|
|
429
|
-
await store.transition('evaluating');
|
|
430
|
-
await store.transition('evaluation_complete');
|
|
431
|
-
await store.transition('merging');
|
|
432
|
-
await assert.rejects(store.transition('completed'), /merged\.md/);
|
|
433
|
-
const merged = await store.writeMerged('final');
|
|
434
|
-
await assert.rejects(store.transition('completed'), /result\.json/);
|
|
435
|
-
const details: FusionResultDetails = {
|
|
436
|
-
schema_version: FUSION_RESULT_SCHEMA_VERSION,
|
|
437
|
-
run_id: store.runId,
|
|
438
|
-
workflow: 'reason',
|
|
439
|
-
source: 'tool',
|
|
440
|
-
status: 'completed',
|
|
441
|
-
context: { kind: 'session_projection', policy_id: 'test-policy' },
|
|
442
|
-
tool_policy: { candidate_tools: [], evaluation_tools: [], merge_tools: [] },
|
|
443
|
-
artifact_dir: store.artifactDir,
|
|
444
|
-
models: store.snapshot().models,
|
|
445
|
-
evaluator_attempts: 1,
|
|
446
|
-
usage: {
|
|
447
|
-
input: 2,
|
|
448
|
-
output: 13,
|
|
449
|
-
cacheRead: 3,
|
|
450
|
-
cacheWrite: 11,
|
|
451
|
-
cacheWrite1h: 7,
|
|
452
|
-
reasoning: 5,
|
|
453
|
-
totalTokens: 29,
|
|
454
|
-
cost: { input: 0.02, output: 0.13, cacheRead: 0.003, cacheWrite: 0.066, total: 0.219 },
|
|
455
|
-
},
|
|
456
|
-
budget: {
|
|
457
|
-
policy_id: 'test-policy',
|
|
458
|
-
calibration_version: 'test',
|
|
459
|
-
route_table: [],
|
|
460
|
-
rate_sources: [],
|
|
461
|
-
unknown_provider_warnings: [],
|
|
462
|
-
calibration_warnings: [],
|
|
463
|
-
},
|
|
464
|
-
};
|
|
465
|
-
await store.writeCommittedResult(merged, details);
|
|
466
|
-
await store.transition('completed');
|
|
467
|
-
const manifest = parseManifest(
|
|
468
|
-
await readFile(join(store.artifactDirAbs, 'manifest.json'), 'utf8'),
|
|
469
|
-
);
|
|
470
|
-
assert.equal(field(manifest, 'state'), 'completed');
|
|
471
|
-
assert.equal(await readFile(join(store.artifactDirAbs, 'merged.md'), 'utf8'), 'final');
|
|
472
|
-
const verified = await readFusionCommittedResult({
|
|
473
|
-
artifactDirAbs: store.artifactDirAbs,
|
|
474
|
-
artifactDir: store.artifactDir,
|
|
475
|
-
runId: store.runId,
|
|
476
|
-
workflow: 'reason',
|
|
477
|
-
});
|
|
478
|
-
assert.equal(verified.mergedText, 'final');
|
|
479
|
-
assert.equal(verified.details.usage.cacheWrite1h, 7);
|
|
480
|
-
assert.equal(verified.details.usage.reasoning, 5);
|
|
481
|
-
await writeFile(join(store.artifactDirAbs, 'merged.md'), 'tampered', 'utf8');
|
|
482
|
-
await assert.rejects(
|
|
483
|
-
readFusionCommittedResult({
|
|
484
|
-
artifactDirAbs: store.artifactDirAbs,
|
|
485
|
-
artifactDir: store.artifactDir,
|
|
486
|
-
runId: store.runId,
|
|
487
|
-
workflow: 'reason',
|
|
488
|
-
}),
|
|
489
|
-
/does not match its committed hash and length/,
|
|
490
|
-
);
|
|
491
|
-
} finally {
|
|
492
|
-
await rm(root, { recursive: true, force: true });
|
|
493
|
-
}
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
void it('writes a canonical manifest-bound failure summary without stage-output bodies', async () => {
|
|
497
|
-
const root = await mkdtemp(join(tmpdir(), 'pi-fusion-failure-summary-'));
|
|
498
|
-
try {
|
|
499
|
-
const store = await FusionArtifactStore.create({
|
|
500
|
-
cwd: root,
|
|
501
|
-
runId: 'reason-33333333333333333333333333333333',
|
|
502
|
-
source: 'tool',
|
|
503
|
-
config: defaultFusionModelConfig(),
|
|
504
|
-
models: models(),
|
|
505
|
-
now: () => new Date('2026-01-01T00:00:00.000Z'),
|
|
506
|
-
});
|
|
507
|
-
await store.transition('candidates_running');
|
|
508
|
-
await store.recordChildAttempt({
|
|
509
|
-
result: childResult('candidate', 'COMPLETE_RESPONSE_SENTINEL'),
|
|
510
|
-
systemPrompt: 'system', prompt: 'prompt', responseKind: 'md',
|
|
511
|
-
});
|
|
512
|
-
const oversized = `OVERSIZED_RESPONSE_SENTINEL${'o'.repeat(50_000)}`;
|
|
513
|
-
await store.recordFailedAttempt({
|
|
514
|
-
stage: 'candidate', slot: 2, attempt: 1, systemPrompt: 'system', prompt: 'prompt',
|
|
515
|
-
events: Buffer.from('TOOL_OUTPUT_SENTINEL'), stderr: Buffer.from('stderr'),
|
|
516
|
-
partialResponse: Buffer.from('PARTIAL_RESPONSE_SENTINEL'),
|
|
517
|
-
error: 'candidate failure', status: 'failed', childCreated: true, responseKind: 'md',
|
|
518
|
-
outputRecovery: {
|
|
519
|
-
kind: 'same_session_compression', limit_bytes: 49_152, original_record_index: 0,
|
|
520
|
-
replacement_record_index: null,
|
|
521
|
-
original_json_rendered_bytes: Buffer.byteLength(JSON.stringify(oversized), 'utf8'),
|
|
522
|
-
replacement_json_rendered_bytes: null,
|
|
523
|
-
original_text_sha256: createHash('sha256').update(oversized).digest('hex'),
|
|
524
|
-
original_text: oversized, status: 'failed',
|
|
525
|
-
},
|
|
526
|
-
});
|
|
527
|
-
await store.writeError('failed', 'terminal error');
|
|
528
|
-
const manifest = store.snapshot();
|
|
529
|
-
const summary = buildFusionFailureSummary({
|
|
530
|
-
manifest,
|
|
531
|
-
terminalError: new FusionError('terminal error', {
|
|
532
|
-
code: 'child_output_cap', stage: 'candidate', slot: 2, attempt: 1, childCreated: true,
|
|
533
|
-
}),
|
|
534
|
-
progress: {
|
|
535
|
-
manifest_state: 'failed',
|
|
536
|
-
candidates: { status: 'incomplete', attempts_recorded: 2, children_created: 2, children_completed: 1, children_failed: 1, children_cancelled: 0, not_started_slots: 1 },
|
|
537
|
-
evaluation: { status: 'not_started', attempts_recorded: 0, children_created: 0, children_completed: 0, children_failed: 0, children_cancelled: 0 },
|
|
538
|
-
merge: { status: 'not_started', attempts_recorded: 0, children_created: 0, children_completed: 0, children_failed: 0, children_cancelled: 0 },
|
|
539
|
-
usage_so_far: manifest.usage,
|
|
540
|
-
},
|
|
541
|
-
terminalState: 'failed', createdAt: manifest.updated_at,
|
|
542
|
-
});
|
|
543
|
-
const summaryRef = await store.writeFailureSummary(summary);
|
|
544
|
-
const summaryBytes = await readFile(join(store.artifactDirAbs, 'failure-summary.json'));
|
|
545
|
-
assert.equal(sha256Buffer(summaryBytes), summaryRef.sha256);
|
|
546
|
-
assert.equal(summaryBytes.length, summaryRef.byte_length);
|
|
547
|
-
assert.doesNotMatch(summaryBytes.toString('utf8'), /COMPLETE_RESPONSE_SENTINEL|PARTIAL_RESPONSE_SENTINEL|OVERSIZED_RESPONSE_SENTINEL|TOOL_OUTPUT_SENTINEL/);
|
|
548
|
-
const finalManifest = parseManifest(await readFile(join(store.artifactDirAbs, 'manifest.json'), 'utf8'));
|
|
549
|
-
const artifacts = field(finalManifest, 'artifacts');
|
|
550
|
-
assert.ok(typeof artifacts === 'object' && artifacts !== null);
|
|
551
|
-
assert.deepEqual(Reflect.get(artifacts, 'failure-summary.json'), summaryRef);
|
|
552
|
-
const verified = await readFusionFailureResult({
|
|
553
|
-
artifactDirAbs: store.artifactDirAbs, artifactDir: store.artifactDir, runId: store.runId, workflow: 'reason',
|
|
554
|
-
});
|
|
555
|
-
assert.equal(verified.schema_version, FUSION_FAILURE_SUMMARY_SCHEMA_VERSION);
|
|
556
|
-
assert.equal(verified.summary_status, 'verified');
|
|
557
|
-
assert.equal(verified.failure_summary_ref?.sha256, summaryRef.sha256);
|
|
558
|
-
const classes = new Map(verified.evidence_artifacts?.listed.map((entry) => [entry.name, entry.classification]));
|
|
559
|
-
assert.equal(classes.get('candidate-1.attempt-1.response.md'), 'complete_stage_output');
|
|
560
|
-
assert.equal(classes.get('candidate-2.attempt-1.response.partial.md'), 'partial_stage_output');
|
|
561
|
-
assert.equal(classes.get('candidate-2.attempt-1.response.oversized.md'), 'oversized_original');
|
|
562
|
-
assert.equal(classes.get('candidate-2.attempt-1.response.md'), 'empty_rejected_output');
|
|
563
|
-
assert.equal(classes.get('candidate-2.attempt-1.events.jsonl'), 'evidence_only');
|
|
564
|
-
await writeFile(join(store.artifactDirAbs, 'failure-summary.json'), '{"corrupt":true}\n', 'utf8');
|
|
565
|
-
const corrupt = await readFusionFailureResult({
|
|
566
|
-
artifactDirAbs: store.artifactDirAbs, artifactDir: store.artifactDir, runId: store.runId, workflow: 'reason',
|
|
567
|
-
});
|
|
568
|
-
assert.equal(corrupt.summary_status, 'integrity_failed');
|
|
569
|
-
assert.equal(corrupt.evidence_artifacts, undefined);
|
|
570
|
-
} finally {
|
|
571
|
-
await rm(root, { recursive: true, force: true });
|
|
572
|
-
}
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
void it('rejects invalid summary lifecycle, identity, no-answer, and duplicate-write inputs at runtime', async () => {
|
|
576
|
-
const root = await mkdtemp(join(tmpdir(), 'pi-fusion-summary-guards-'));
|
|
577
|
-
try {
|
|
578
|
-
const unstarted = await FusionArtifactStore.create({
|
|
579
|
-
cwd: root,
|
|
580
|
-
runId: 'reason-66666666666666666666666666666666',
|
|
581
|
-
source: 'tool',
|
|
582
|
-
config: defaultFusionModelConfig(),
|
|
583
|
-
models: models(),
|
|
584
|
-
});
|
|
585
|
-
await assert.rejects(
|
|
586
|
-
unstarted.writeFailureSummary({} as never),
|
|
587
|
-
/failed\/cancelled terminal manifest/,
|
|
588
|
-
);
|
|
589
|
-
|
|
590
|
-
const terminalRoot = await mkdtemp(join(root, 'terminal-'));
|
|
591
|
-
const { store, summary } = await createVerifiedFailureStore(terminalRoot);
|
|
592
|
-
await assert.rejects(
|
|
593
|
-
store.writeFailureSummary({ ...summary, answer: { present: true } } as never),
|
|
594
|
-
/no answer was committed/,
|
|
595
|
-
);
|
|
596
|
-
await assert.rejects(
|
|
597
|
-
store.writeFailureSummary({ ...summary, run_id: 'reason-foreign' }),
|
|
598
|
-
/identity does not match/,
|
|
599
|
-
);
|
|
600
|
-
await assert.rejects(
|
|
601
|
-
store.writeFailureSummary({ ...summary, terminal_state: 'cancelled' }),
|
|
602
|
-
/terminal state does not match/,
|
|
603
|
-
);
|
|
604
|
-
await assert.rejects(store.writeFailureSummary(summary), /already bound/);
|
|
605
|
-
await assert.rejects(
|
|
606
|
-
Promise.resolve().then(() =>
|
|
607
|
-
buildFusionFailureSummary({
|
|
608
|
-
manifest: store.snapshot(),
|
|
609
|
-
terminalError: new FusionError('different terminal error', {
|
|
610
|
-
code: 'child_exit_failed',
|
|
611
|
-
childCreated: true,
|
|
612
|
-
}),
|
|
613
|
-
progress: buildFusionRunProgress(store.snapshot()),
|
|
614
|
-
terminalState: 'failed',
|
|
615
|
-
createdAt: store.snapshot().updated_at,
|
|
616
|
-
}),
|
|
617
|
-
),
|
|
618
|
-
/terminal error does not match/,
|
|
619
|
-
);
|
|
620
|
-
} finally {
|
|
621
|
-
await rm(root, { recursive: true, force: true });
|
|
622
|
-
}
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
void it('keeps failure summaries deterministic and records exact source caps', () => {
|
|
626
|
-
const manifest = {
|
|
627
|
-
state: 'failed',
|
|
628
|
-
run_id: 'reason-77777777777777777777777777777777',
|
|
629
|
-
workflow: 'reason',
|
|
630
|
-
source: 'tool',
|
|
631
|
-
error: 'terminal error',
|
|
632
|
-
usage: createEmptyFusionUsage(),
|
|
633
|
-
attempts: Array.from({ length: FUSION_FAILURE_SUMMARY_ATTEMPT_CAP + 1 }, (_, index) => ({
|
|
634
|
-
stage: 'candidate' as const,
|
|
635
|
-
slot: ((index % 3) + 1) as 1 | 2 | 3,
|
|
636
|
-
attempt: index + 1,
|
|
637
|
-
status: 'failed' as const,
|
|
638
|
-
child_created: true,
|
|
639
|
-
})),
|
|
640
|
-
artifacts: Object.fromEntries(
|
|
641
|
-
Array.from({ length: FUSION_FAILURE_SUMMARY_EVIDENCE_CAP + 1 }, (_, index) => [
|
|
642
|
-
`evidence-${String(index).padStart(2, '0')}.json`,
|
|
643
|
-
{
|
|
644
|
-
path: `evidence-${String(index).padStart(2, '0')}.json`,
|
|
645
|
-
byte_length: index,
|
|
646
|
-
sha256: `sha256:${String(index).padStart(64, '0')}`,
|
|
647
|
-
},
|
|
648
|
-
]),
|
|
649
|
-
),
|
|
650
|
-
} as never;
|
|
651
|
-
const terminalError = new FusionError('terminal error', {
|
|
652
|
-
code: 'child_exit_failed',
|
|
653
|
-
childCreated: true,
|
|
654
|
-
});
|
|
655
|
-
const input = {
|
|
656
|
-
manifest,
|
|
657
|
-
terminalError,
|
|
658
|
-
progress: buildFusionRunProgress(manifest),
|
|
659
|
-
terminalState: 'failed' as const,
|
|
660
|
-
createdAt: '2026-01-01T00:00:00.000Z',
|
|
661
|
-
};
|
|
662
|
-
const first = buildFusionFailureSummary(input);
|
|
663
|
-
const second = buildFusionFailureSummary(input);
|
|
664
|
-
assert.equal(canonicalJson(first), canonicalJson(second));
|
|
665
|
-
assert.equal(first.attempts.listed.length, FUSION_FAILURE_SUMMARY_ATTEMPT_CAP);
|
|
666
|
-
assert.equal(first.attempts.omitted_count, 1);
|
|
667
|
-
assert.equal(first.evidence_artifacts.listed.length, FUSION_FAILURE_SUMMARY_EVIDENCE_CAP);
|
|
668
|
-
assert.equal(first.evidence_artifacts.omitted_count, 1);
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
void it('returns verified failed/cancelled evidence without reading stage-output bodies', async () => {
|
|
672
|
-
const root = await mkdtemp(join(tmpdir(), 'pi-fusion-summary-terminal-views-'));
|
|
673
|
-
try {
|
|
674
|
-
for (const terminalState of ['failed', 'cancelled'] as const) {
|
|
675
|
-
const runRoot = await mkdtemp(join(root, `${terminalState}-`));
|
|
676
|
-
const { store } = await createVerifiedFailureStore(runRoot, terminalState);
|
|
677
|
-
await writeFile(
|
|
678
|
-
join(store.artifactDirAbs, 'candidate-1.attempt-1.response.md'),
|
|
679
|
-
Buffer.from([0xff]),
|
|
680
|
-
);
|
|
681
|
-
const view = await readFusionFailureResult({
|
|
682
|
-
artifactDirAbs: store.artifactDirAbs,
|
|
683
|
-
artifactDir: store.artifactDir,
|
|
684
|
-
runId: store.runId,
|
|
685
|
-
workflow: 'reason',
|
|
686
|
-
});
|
|
687
|
-
assert.equal(view.summary_status, 'verified');
|
|
688
|
-
assert.equal(view.terminal_state, terminalState);
|
|
689
|
-
assert.deepEqual(view.answer, { present: false, reason: 'run_did_not_commit' });
|
|
690
|
-
}
|
|
691
|
-
} finally {
|
|
692
|
-
await rm(root, { recursive: true, force: true });
|
|
693
|
-
}
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
void it('fails closed for summary corruption, divergence, unsafe refs, and legacy manifests', async () => {
|
|
697
|
-
const root = await mkdtemp(join(tmpdir(), 'pi-fusion-summary-integrity-'));
|
|
698
|
-
try {
|
|
699
|
-
const cases: Array<{
|
|
700
|
-
name: string;
|
|
701
|
-
mutate: (store: FusionArtifactStore) => Promise<void>;
|
|
702
|
-
expected: 'integrity_failed' | 'unavailable' | 'legacy_manifest_only';
|
|
703
|
-
}> = [
|
|
704
|
-
{
|
|
705
|
-
name: 'missing historical summary',
|
|
706
|
-
async mutate(store) {
|
|
707
|
-
const manifest = record(
|
|
708
|
-
parseManifest(await readFile(join(store.artifactDirAbs, 'manifest.json'), 'utf8')),
|
|
709
|
-
'manifest',
|
|
710
|
-
);
|
|
711
|
-
manifest['schema_version'] = 'pi-background-tasks.fusion-manifest.v3';
|
|
712
|
-
delete record(manifest['artifacts'], 'artifacts')['failure-summary.json'];
|
|
713
|
-
await writeFile(
|
|
714
|
-
join(store.artifactDirAbs, 'manifest.json'),
|
|
715
|
-
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
716
|
-
'utf8',
|
|
717
|
-
);
|
|
718
|
-
},
|
|
719
|
-
expected: 'legacy_manifest_only',
|
|
720
|
-
},
|
|
721
|
-
{
|
|
722
|
-
name: 'missing current summary file',
|
|
723
|
-
async mutate(store) {
|
|
724
|
-
await rm(join(store.artifactDirAbs, 'failure-summary.json'));
|
|
725
|
-
},
|
|
726
|
-
expected: 'integrity_failed',
|
|
727
|
-
},
|
|
728
|
-
{
|
|
729
|
-
name: 'invalid UTF-8',
|
|
730
|
-
mutate: (store) => rewriteBoundFailureSummary(store, Buffer.from([0xff])),
|
|
731
|
-
expected: 'integrity_failed',
|
|
732
|
-
},
|
|
733
|
-
{
|
|
734
|
-
name: 'invalid JSON',
|
|
735
|
-
mutate: (store) => rewriteBoundFailureSummary(store, Buffer.from('{', 'utf8')),
|
|
736
|
-
expected: 'integrity_failed',
|
|
737
|
-
},
|
|
738
|
-
{
|
|
739
|
-
name: 'manifest summary hash mismatch',
|
|
740
|
-
async mutate(store) {
|
|
741
|
-
const manifest = record(
|
|
742
|
-
parseManifest(await readFile(join(store.artifactDirAbs, 'manifest.json'), 'utf8')),
|
|
743
|
-
'manifest',
|
|
744
|
-
);
|
|
745
|
-
record(manifest['artifacts'], 'artifacts')['failure-summary.json'] = {
|
|
746
|
-
path: 'failure-summary.json',
|
|
747
|
-
byte_length: 1,
|
|
748
|
-
sha256: `sha256:${'0'.repeat(64)}`,
|
|
749
|
-
};
|
|
750
|
-
await writeFile(
|
|
751
|
-
join(store.artifactDirAbs, 'manifest.json'),
|
|
752
|
-
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
753
|
-
'utf8',
|
|
754
|
-
);
|
|
755
|
-
},
|
|
756
|
-
expected: 'integrity_failed',
|
|
757
|
-
},
|
|
758
|
-
{
|
|
759
|
-
name: 'manifest summary length mismatch',
|
|
760
|
-
async mutate(store) {
|
|
761
|
-
const manifest = record(
|
|
762
|
-
parseManifest(await readFile(join(store.artifactDirAbs, 'manifest.json'), 'utf8')),
|
|
763
|
-
'manifest',
|
|
764
|
-
);
|
|
765
|
-
const ref = record(
|
|
766
|
-
record(manifest['artifacts'], 'artifacts')['failure-summary.json'],
|
|
767
|
-
'summary ref',
|
|
768
|
-
);
|
|
769
|
-
ref['byte_length'] = Number(ref['byte_length']) + 1;
|
|
770
|
-
await writeFile(
|
|
771
|
-
join(store.artifactDirAbs, 'manifest.json'),
|
|
772
|
-
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
773
|
-
'utf8',
|
|
774
|
-
);
|
|
775
|
-
},
|
|
776
|
-
expected: 'integrity_failed',
|
|
777
|
-
},
|
|
778
|
-
{
|
|
779
|
-
name: 'summary no-answer assertion mismatch',
|
|
780
|
-
async mutate(store) {
|
|
781
|
-
const value = parseJsonText(
|
|
782
|
-
await readFile(join(store.artifactDirAbs, 'failure-summary.json'), 'utf8'),
|
|
783
|
-
);
|
|
784
|
-
record(value, 'summary')['answer'] = { present: true, reason: 'run_did_not_commit' };
|
|
785
|
-
await rewriteBoundFailureSummary(store, value);
|
|
786
|
-
},
|
|
787
|
-
expected: 'integrity_failed',
|
|
788
|
-
},
|
|
789
|
-
{
|
|
790
|
-
name: 'summary identity mismatch',
|
|
791
|
-
async mutate(store) {
|
|
792
|
-
const value = parseJsonText(
|
|
793
|
-
await readFile(join(store.artifactDirAbs, 'failure-summary.json'), 'utf8'),
|
|
794
|
-
);
|
|
795
|
-
record(value, 'summary')['run_id'] = 'reason-foreign';
|
|
796
|
-
await rewriteBoundFailureSummary(store, value);
|
|
797
|
-
},
|
|
798
|
-
expected: 'integrity_failed',
|
|
799
|
-
},
|
|
800
|
-
{
|
|
801
|
-
name: 'summary state mismatch',
|
|
802
|
-
async mutate(store) {
|
|
803
|
-
const value = parseJsonText(
|
|
804
|
-
await readFile(join(store.artifactDirAbs, 'failure-summary.json'), 'utf8'),
|
|
805
|
-
);
|
|
806
|
-
record(value, 'summary')['terminal_state'] = 'cancelled';
|
|
807
|
-
await rewriteBoundFailureSummary(store, value);
|
|
808
|
-
},
|
|
809
|
-
expected: 'integrity_failed',
|
|
810
|
-
},
|
|
811
|
-
{
|
|
812
|
-
name: 'evidence ref divergence',
|
|
813
|
-
async mutate(store) {
|
|
814
|
-
const value = record(
|
|
815
|
-
parseJsonText(await readFile(join(store.artifactDirAbs, 'failure-summary.json'), 'utf8')),
|
|
816
|
-
'summary',
|
|
817
|
-
);
|
|
818
|
-
const listed = record(value['evidence_artifacts'], 'evidence')['listed'];
|
|
819
|
-
assert.ok(Array.isArray(listed));
|
|
820
|
-
const first = record(listed[0], 'evidence row');
|
|
821
|
-
record(first['ref'], 'evidence ref')['byte_length'] = 999;
|
|
822
|
-
await rewriteBoundFailureSummary(store, value);
|
|
823
|
-
},
|
|
824
|
-
expected: 'integrity_failed',
|
|
825
|
-
},
|
|
826
|
-
{
|
|
827
|
-
name: 'unsafe summary ref never escapes the run directory',
|
|
828
|
-
async mutate(store) {
|
|
829
|
-
const outside = join(store.artifactDirAbs, '..', 'crafted-summary.json');
|
|
830
|
-
await writeFile(outside, '{"outside":true}\n', 'utf8');
|
|
831
|
-
const manifest = record(
|
|
832
|
-
parseManifest(await readFile(join(store.artifactDirAbs, 'manifest.json'), 'utf8')),
|
|
833
|
-
'manifest',
|
|
834
|
-
);
|
|
835
|
-
record(manifest['artifacts'], 'artifacts')['failure-summary.json'] = {
|
|
836
|
-
path: '../crafted-summary.json',
|
|
837
|
-
byte_length: 17,
|
|
838
|
-
sha256: `sha256:${'0'.repeat(64)}`,
|
|
839
|
-
};
|
|
840
|
-
await writeFile(
|
|
841
|
-
join(store.artifactDirAbs, 'manifest.json'),
|
|
842
|
-
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
843
|
-
'utf8',
|
|
844
|
-
);
|
|
845
|
-
},
|
|
846
|
-
expected: 'unavailable',
|
|
847
|
-
},
|
|
848
|
-
];
|
|
849
|
-
for (const item of cases) {
|
|
850
|
-
const runRoot = await mkdtemp(join(root, `${item.name.replaceAll(' ', '-')}-`));
|
|
851
|
-
const { store } = await createVerifiedFailureStore(runRoot);
|
|
852
|
-
await item.mutate(store);
|
|
853
|
-
const view = await readFusionFailureResult({
|
|
854
|
-
artifactDirAbs: store.artifactDirAbs,
|
|
855
|
-
artifactDir: store.artifactDir,
|
|
856
|
-
runId: store.runId,
|
|
857
|
-
workflow: 'reason',
|
|
858
|
-
});
|
|
859
|
-
assert.equal(view.summary_status, item.expected, item.name);
|
|
860
|
-
if (item.expected !== 'legacy_manifest_only') {
|
|
861
|
-
assert.equal(view.failure, undefined, item.name);
|
|
862
|
-
assert.equal(view.evidence_artifacts, undefined, item.name);
|
|
863
|
-
assert.equal(view.failure_summary_ref, undefined, item.name);
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
} finally {
|
|
867
|
-
await rm(root, { recursive: true, force: true });
|
|
868
|
-
}
|
|
869
|
-
});
|
|
870
|
-
|
|
871
|
-
void it('bounds the actual failure view with whole-row omission receipts for multibyte diagnostics', async () => {
|
|
872
|
-
const root = await mkdtemp(join(tmpdir(), 'pi-fusion-summary-bound-'));
|
|
873
|
-
try {
|
|
874
|
-
const store = await FusionArtifactStore.create({
|
|
875
|
-
cwd: root,
|
|
876
|
-
runId: 'reason-88888888888888888888888888888888',
|
|
877
|
-
source: 'tool',
|
|
878
|
-
config: defaultFusionModelConfig(),
|
|
879
|
-
models: models(),
|
|
880
|
-
});
|
|
881
|
-
await store.transition('candidates_running');
|
|
882
|
-
for (let index = 0; index < FUSION_FAILURE_SUMMARY_ATTEMPT_CAP + 1; index += 1) {
|
|
883
|
-
await store.recordFailedAttempt({
|
|
884
|
-
stage: 'candidate',
|
|
885
|
-
slot: ((index % 3) + 1) as 1 | 2 | 3,
|
|
886
|
-
attempt: index + 1,
|
|
887
|
-
systemPrompt: 'system',
|
|
888
|
-
prompt: 'prompt',
|
|
889
|
-
events: Buffer.alloc(0),
|
|
890
|
-
partialResponse: Buffer.alloc(0),
|
|
891
|
-
stderr: Buffer.alloc(0),
|
|
892
|
-
error: 'failed',
|
|
893
|
-
status: 'failed',
|
|
894
|
-
childCreated: true,
|
|
895
|
-
responseKind: 'md',
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
const terminalMessage = '💥'.repeat(256);
|
|
899
|
-
await store.writeError('failed', terminalMessage);
|
|
900
|
-
const manifest = store.snapshot();
|
|
901
|
-
const summary = buildFusionFailureSummary({
|
|
902
|
-
manifest,
|
|
903
|
-
terminalError: new FusionError(terminalMessage, {
|
|
904
|
-
code: 'child_exit_failed',
|
|
905
|
-
childCreated: true,
|
|
906
|
-
}),
|
|
907
|
-
progress: buildFusionRunProgress(manifest),
|
|
908
|
-
terminalState: 'failed',
|
|
909
|
-
createdAt: manifest.updated_at,
|
|
910
|
-
});
|
|
911
|
-
await store.writeFailureSummary(summary);
|
|
912
|
-
const view = await readFusionFailureResult({
|
|
913
|
-
artifactDirAbs: store.artifactDirAbs,
|
|
914
|
-
artifactDir: store.artifactDir,
|
|
915
|
-
runId: store.runId,
|
|
916
|
-
workflow: 'reason',
|
|
917
|
-
});
|
|
918
|
-
assert.ok(Buffer.byteLength(canonicalJson(view), 'utf8') <= 6 * 1024);
|
|
919
|
-
assert.equal(view.attempts?.omitted_count, summary.attempts.omitted_count);
|
|
920
|
-
assert.ok((view.evidence_artifacts?.omitted_count ?? 0) > summary.evidence_artifacts.omitted_count);
|
|
921
|
-
assert.equal(
|
|
922
|
-
(view.evidence_artifacts?.listed.length ?? 0) +
|
|
923
|
-
(view.evidence_artifacts?.omitted_count ?? 0),
|
|
924
|
-
summary.evidence_artifacts.listed.length + summary.evidence_artifacts.omitted_count,
|
|
925
|
-
);
|
|
926
|
-
assert.equal(view.failure?.message.inline_message, undefined);
|
|
927
|
-
assert.equal(view.failure?.message.omission_reason, 'result_view_byte_budget');
|
|
928
|
-
const modelVisibleResult = {
|
|
929
|
-
content: [
|
|
930
|
-
{
|
|
931
|
-
type: 'text',
|
|
932
|
-
text: 'Fusion task-0123456789abcdef0123456789abcdef failed; no answer was committed. Terminal evidence status: verified. Delivery is none; use only the manifest-bound artifact references in details.',
|
|
933
|
-
},
|
|
934
|
-
],
|
|
935
|
-
details: {
|
|
936
|
-
schema_version: 'pi-background-tasks.fusion-result-view.v1',
|
|
937
|
-
task_id: 'task-0123456789abcdef0123456789abcdef',
|
|
938
|
-
state: 'failed',
|
|
939
|
-
delivery: 'none',
|
|
940
|
-
workflow: 'reason',
|
|
941
|
-
artifact_dir: store.artifactDir,
|
|
942
|
-
answer: view.answer,
|
|
943
|
-
summary_status: view.summary_status,
|
|
944
|
-
failure_summary_ref: view.failure_summary_ref,
|
|
945
|
-
failure: view.failure,
|
|
946
|
-
progress: view.progress,
|
|
947
|
-
usage_so_far: view.usage_so_far,
|
|
948
|
-
attempts: view.attempts,
|
|
949
|
-
evidence_artifacts: view.evidence_artifacts,
|
|
950
|
-
remediation_ids: view.remediation_ids,
|
|
951
|
-
},
|
|
952
|
-
};
|
|
953
|
-
assert.ok(Buffer.byteLength(JSON.stringify(modelVisibleResult), 'utf8') <= 8 * 1024);
|
|
954
|
-
} finally {
|
|
955
|
-
await rm(root, { recursive: true, force: true });
|
|
956
|
-
}
|
|
957
|
-
});
|
|
958
|
-
|
|
959
|
-
void it('writes terminal failure evidence loudly', async () => {
|
|
960
|
-
const root = await mkdtemp(join(tmpdir(), 'pi-fusion-failed-'));
|
|
961
|
-
try {
|
|
962
|
-
const store = await FusionArtifactStore.create({
|
|
963
|
-
cwd: root,
|
|
964
|
-
runId: 'reason-22222222222222222222222222222222',
|
|
965
|
-
source: 'command',
|
|
966
|
-
config: defaultFusionModelConfig(),
|
|
967
|
-
models: models(),
|
|
968
|
-
});
|
|
969
|
-
await store.transition('candidates_running');
|
|
970
|
-
await store.recordFailedAttempt({
|
|
971
|
-
stage: 'candidate',
|
|
972
|
-
slot: 2,
|
|
973
|
-
attempt: 1,
|
|
974
|
-
systemPrompt: 'system prompt',
|
|
975
|
-
prompt: 'prompt',
|
|
976
|
-
events: Buffer.from('compact-event'),
|
|
977
|
-
partialResponse: Buffer.from('partial response'),
|
|
978
|
-
stderr: Buffer.from('err'),
|
|
979
|
-
error: 'boom',
|
|
980
|
-
status: 'failed',
|
|
981
|
-
childCreated: true,
|
|
982
|
-
responseKind: 'md',
|
|
983
|
-
provider: 'p',
|
|
984
|
-
model: 'b',
|
|
985
|
-
qualifiedId: 'p/b',
|
|
986
|
-
usage: {
|
|
987
|
-
input: 2,
|
|
988
|
-
output: 3,
|
|
989
|
-
cacheRead: 0,
|
|
990
|
-
cacheWrite: 0,
|
|
991
|
-
totalTokens: 5,
|
|
992
|
-
cost: { input: 0.02, output: 0.03, cacheRead: 0, cacheWrite: 0, total: 0.05 },
|
|
993
|
-
},
|
|
994
|
-
});
|
|
995
|
-
await store.writeError('failed', 'boom');
|
|
996
|
-
assert.ok(existsSync(join(store.artifactDirAbs, 'error.json')));
|
|
997
|
-
const manifest = parseManifest(
|
|
998
|
-
await readFile(join(store.artifactDirAbs, 'manifest.json'), 'utf8'),
|
|
999
|
-
);
|
|
1000
|
-
assert.equal(field(manifest, 'state'), 'failed');
|
|
1001
|
-
assert.equal(field(manifest, 'error'), 'boom');
|
|
1002
|
-
const attempts = field(manifest, 'attempts');
|
|
1003
|
-
assert.ok(Array.isArray(attempts));
|
|
1004
|
-
const firstAttempt: unknown = attempts[0];
|
|
1005
|
-
assert.ok(typeof firstAttempt === 'object' && firstAttempt !== null);
|
|
1006
|
-
assert.equal(field(firstAttempt, 'status'), 'failed');
|
|
1007
|
-
assert.equal(field(firstAttempt, 'child_created'), true);
|
|
1008
|
-
assert.equal(field(firstAttempt, 'response_path'), 'candidate-2.attempt-1.response.md');
|
|
1009
|
-
assert.equal(
|
|
1010
|
-
field(firstAttempt, 'partial_response_path'),
|
|
1011
|
-
'candidate-2.attempt-1.response.partial.md',
|
|
1012
|
-
);
|
|
1013
|
-
assert.equal(
|
|
1014
|
-
await readFile(join(store.artifactDirAbs, 'candidate-2.attempt-1.response.md'), 'utf8'),
|
|
1015
|
-
'',
|
|
1016
|
-
);
|
|
1017
|
-
assert.equal(
|
|
1018
|
-
await readFile(
|
|
1019
|
-
join(store.artifactDirAbs, 'candidate-2.attempt-1.response.partial.md'),
|
|
1020
|
-
'utf8',
|
|
1021
|
-
),
|
|
1022
|
-
'partial response',
|
|
1023
|
-
);
|
|
1024
|
-
assert.equal(field(firstAttempt, 'qualifiedId'), 'p/b');
|
|
1025
|
-
const failedUsage = field(firstAttempt, 'usage');
|
|
1026
|
-
assert.ok(typeof failedUsage === 'object' && failedUsage !== null);
|
|
1027
|
-
assert.equal(field(failedUsage, 'totalTokens'), 5);
|
|
1028
|
-
assert.deepEqual(field(failedUsage, 'cost'), {
|
|
1029
|
-
input: 0.02,
|
|
1030
|
-
output: 0.03,
|
|
1031
|
-
cacheRead: 0,
|
|
1032
|
-
cacheWrite: 0,
|
|
1033
|
-
total: 0.05,
|
|
1034
|
-
});
|
|
1035
|
-
} finally {
|
|
1036
|
-
await rm(root, { recursive: true, force: true });
|
|
1037
|
-
}
|
|
1038
|
-
});
|
|
1039
|
-
});
|