@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.
Files changed (58) hide show
  1. package/package.json +6 -5
  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,219 +0,0 @@
1
- import { describe, it } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { canonicalJson } from '../attested-pi-run.js';
4
- import {
5
- buildFusionCanonicalInput,
6
- } from '../fusion/context.js';
7
- import { buildFusionCleanTaskCanonicalInput } from '../fusion/clean-context.js';
8
- import {
9
- FUSION_INVESTIGATE_WORKFLOW,
10
- FUSION_REASON_WORKFLOW,
11
- FUSION_RESEARCH_WORKFLOW,
12
- FUSION_VALIDATE_WORKFLOW,
13
- FUSION_WORKFLOW_PROFILES,
14
- assertWorkflowCapability,
15
- } from '../fusion/workflows.js';
16
- import {
17
- FUSION_FORBIDDEN_TOOLS,
18
- FUSION_INPUT_SCHEMA_VERSION,
19
- FUSION_PUBLIC_WORKFLOW_NAMES,
20
- FUSION_RESEARCH_TOOLS,
21
- FUSION_WORKFLOW_IDS,
22
- } from '../fusion/types.js';
23
- import {
24
- buildFusionSourcePolicy,
25
- canonicalizeFusionPublicUrl,
26
- normalizeFusionDeclaredSources,
27
- parseFusionSourcePolicy,
28
- sourcePolicyCanonicalBytes,
29
- } from '../fusion/source-policy.js';
30
- import {
31
- assertMergerFindingCoverage,
32
- stableFusionFindingId,
33
- validateFusionFindingAccounting,
34
- } from '../fusion/evaluation.js';
35
- import { buildFusionPiChildArgv } from '../fusion/pi-child.js';
36
- import { FusionArtifactStore } from '../fusion/artifacts.js';
37
- import { userMessage, buildFrom } from './helpers/fusion-canonical.js';
38
-
39
- function model(id: string): ResolvedFusionModel {
40
- return {
41
- selection: id,
42
- source: 'configured',
43
- provider: 'openai-codex',
44
- model: id,
45
- qualifiedId: `openai-codex/${id}`,
46
- thinkingLevel: 'high',
47
- contextWindow: 200000,
48
- maxOutputTokens: 32_768,
49
- };
50
- }
51
-
52
- function models(): ResolvedFusionModels {
53
- return { candidates: [model('a'), model('b'), model('c')], evaluator: model('e'), merger: model('m') };
54
- }
55
-
56
- const config: FusionModelConfigV1 = {
57
- schema_version: 'pi-background-tasks.fusion-models.v1',
58
- candidates: ['openai-codex/a', 'openai-codex/b', 'openai-codex/c'],
59
- evaluator: 'openai-codex/e',
60
- merger: 'openai-codex/m',
61
- };
62
-
63
- import { mkdtemp, rm } from 'node:fs/promises';
64
- import { tmpdir } from 'node:os';
65
- import { join } from 'node:path';
66
- import type { ResolvedFusionModel, ResolvedFusionModels, FusionModelConfigV1 } from '../fusion/types.js';
67
-
68
- void describe('fusion v5 core workflow contracts', () => {
69
- void it('exports four fixed immutable workflow profiles and public names', () => {
70
- assert.deepEqual([...FUSION_WORKFLOW_IDS], ['reason', 'investigate', 'research', 'validate']);
71
- assert.deepEqual([...FUSION_PUBLIC_WORKFLOW_NAMES], [
72
- 'fusion_reason',
73
- 'fusion_investigate',
74
- 'fusion_research',
75
- 'fusion_validate',
76
- ]);
77
- assert.equal(FUSION_REASON_WORKFLOW.contextKind, 'session_projection');
78
- assert.equal(FUSION_REASON_WORKFLOW.candidateCapability, 'reason');
79
- assert.deepEqual(FUSION_REASON_WORKFLOW.candidateTools, []);
80
- assert.equal(FUSION_INVESTIGATE_WORKFLOW.contextKind, 'clean_task');
81
- assert.equal(FUSION_INVESTIGATE_WORKFLOW.candidateCapability, 'inspect');
82
- assert.deepEqual(FUSION_RESEARCH_WORKFLOW.candidateTools, FUSION_RESEARCH_TOOLS);
83
- assert.equal(FUSION_VALIDATE_WORKFLOW.contextKind, 'clean_task');
84
- for (const profile of FUSION_WORKFLOW_PROFILES) {
85
- assert.ok(profile.runIdPrefix.startsWith(`${profile.id}-`));
86
- assert.throws(() => ((profile.candidateTools as string[]).push('bash')), TypeError);
87
- assert.equal(assertWorkflowCapability(profile, undefined), profile.candidateCapability);
88
- assert.throws(() => assertWorkflowCapability(profile, profile.candidateCapability === 'reason' ? 'inspect' : 'reason'));
89
- }
90
- });
91
-
92
- void it('retains legacy and current fusion workflow names only in the child recursion denylist', () => {
93
- for (const name of ['fusion_brainstorm', 'fusion_reason', 'fusion_investigate', 'fusion_research', 'fusion_validate']) {
94
- assert.ok(FUSION_FORBIDDEN_TOOLS.includes(name as never), `${name} denied`);
95
- }
96
- });
97
-
98
- void it('builds session projection only for reason and clean bytes independent of parent accessors', () => {
99
- const reason = buildFrom([userMessage('hello')], { source: 'command', request: 'answer', workflow: 'reason' }, 'system');
100
- assert.equal(reason.input.schema_version, FUSION_INPUT_SCHEMA_VERSION);
101
- assert.equal(reason.input.workflow, 'reason');
102
- assert.equal(reason.input.context?.kind, 'session_projection');
103
- assert.ok(reason.ledger.entries.length === 0);
104
-
105
- const throwingParent = {
106
- get cwd(): string { throw new Error('clean builder touched cwd accessor'); },
107
- get sessionManager(): never { throw new Error('clean builder touched session'); },
108
- getSystemPrompt(): never { throw new Error('clean builder touched system prompt'); },
109
- };
110
- assert.throws(
111
- () => buildFusionCanonicalInput(throwingParent as never, { source: 'command', request: 'x', workflow: 'investigate' }),
112
- /parent session projection is available only to the reason workflow/,
113
- );
114
-
115
- const first = buildFusionCleanTaskCanonicalInput({
116
- cwd: '/repo',
117
- source: 'tool',
118
- request: 'Inspect src',
119
- workflow: 'investigate',
120
- });
121
- const second = buildFusionCleanTaskCanonicalInput({
122
- cwd: '/repo',
123
- source: 'tool',
124
- request: 'Inspect src',
125
- workflow: 'investigate',
126
- });
127
- assert.equal(first.serialized, second.serialized);
128
- assert.equal(canonicalJson(first.input), first.serialized);
129
- assert.equal(first.input.context?.kind, 'clean_task');
130
- assert.equal('conversation_projection' in first.input, false);
131
- assert.equal('system_prompt' in first.input, false);
132
- assert.equal('ledger' in first, false);
133
- });
134
-
135
- void it('normalizes and hashes research declared-source policies', () => {
136
- const sources = normalizeFusionDeclaredSources([
137
- { url: 'HTTPS://Example.COM:443/a?q=1#frag', purpose: 'primary source' },
138
- ]);
139
- assert.equal(sources[0]?.canonical_url, 'https://example.com/a?q=1');
140
- assert.throws(() => canonicalizeFusionPublicUrl('https://user:secret@example.com/'), /credentials/);
141
- assert.throws(() => canonicalizeFusionPublicUrl('http://127.0.0.1/'), /public/);
142
- assert.throws(
143
- () => canonicalizeFusionPublicUrl('http://[64:ff9b::a00:1]/'),
144
- /private\/reserved/,
145
- );
146
- const policy = buildFusionSourcePolicy('/repo', sources);
147
- const bytes = sourcePolicyCanonicalBytes(policy);
148
- const parsed = parseFusionSourcePolicy(JSON.parse(bytes));
149
- assert.equal(parsed.root_sha256, policy.root_sha256);
150
- assert.equal(parsed.sources[0]?.sha256, sources[0]?.sha256);
151
- const tampered = JSON.parse(bytes) as { root_sha256: string };
152
- tampered.root_sha256 = '0'.repeat(64);
153
- assert.throws(() => parseFusionSourcePolicy(tampered), /root_sha256 mismatch/);
154
- });
155
-
156
-
157
-
158
- void it('builds exact v5 child argv and run-id manifest metadata', async () => {
159
- const argv = buildFusionPiChildArgv(model('child'), 'system', '/ext/fusion-child.js', 'research', () => '/ext/anthropic-attribution.js');
160
- assert.deepEqual(argv.slice(0, 8), ['--mode', 'text', '--no-session', '--no-builtin-tools', '--tools', 'read,grep,find,ls,fusion_web_fetch', '--exclude-tools', FUSION_FORBIDDEN_TOOLS.join(',')]);
161
- assert.ok(argv.includes('--no-extensions'));
162
- assert.ok(argv.includes('--system-prompt'));
163
-
164
- const root = await mkdtemp(join(tmpdir(), 'fusion-v5-'));
165
- try {
166
- const store = await FusionArtifactStore.create({
167
- cwd: root,
168
- profile: FUSION_RESEARCH_WORKFLOW,
169
- source: 'tool',
170
- config,
171
- models: models(),
172
- runId: 'research-' + '1'.repeat(32),
173
- });
174
- const manifest = store.snapshot();
175
- assert.equal(manifest.run_id, 'research-' + '1'.repeat(32));
176
- assert.equal(manifest.workflow, 'research');
177
- assert.equal(manifest.context.kind, 'clean_task');
178
- assert.deepEqual(manifest.tool_policy.candidate_tools, [...FUSION_RESEARCH_TOOLS]);
179
- } finally {
180
- await rm(root, { recursive: true, force: true });
181
- }
182
- });
183
-
184
- void it('validates validation finding accounting and merger coverage mechanically', () => {
185
- assert.equal(stableFusionFindingId('A', 1), 'A-F001');
186
- const accounting = {
187
- findings: [
188
- {
189
- id: 'A-F001',
190
- candidate_id: 'A' as const,
191
- severity: 'high' as const,
192
- location: 'src/x.ts:1',
193
- evidence: 'read line 1',
194
- impact: 'breaks callers',
195
- summary: 'bug',
196
- },
197
- ],
198
- decisions: [
199
- { source_id: 'A-F001', disposition: 'include' as const, rationale: 'supported', group_id: 'G001' },
200
- ],
201
- groups: [
202
- {
203
- group_id: 'G001',
204
- source_ids: ['A-F001'],
205
- severity: 'high' as const,
206
- location: 'src/x.ts:1',
207
- evidence: 'read line 1',
208
- impact: 'breaks callers',
209
- summary: 'bug',
210
- rationale: 'supported',
211
- },
212
- ],
213
- };
214
- assert.deepEqual(validateFusionFindingAccounting(accounting), []);
215
- assert.doesNotThrow(() => assertMergerFindingCoverage(accounting, ['G001']));
216
- assert.throws(() => assertMergerFindingCoverage(accounting, []), /dropped included group/);
217
- assert.throws(() => assertMergerFindingCoverage(accounting, ['G001', 'G999']), /invented/);
218
- });
219
- });
@@ -1,240 +0,0 @@
1
- import { describe, it } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { createHash } from 'node:crypto';
4
- import { mkdtemp, readFile, readdir, rm } from 'node:fs/promises';
5
- import { join } from 'node:path';
6
- import { tmpdir } from 'node:os';
7
- import { canonicalJson } from '../attested-pi-run.js';
8
- import { FusionOrchestrator } from '../fusion/orchestrator.js';
9
- import { defaultFusionModelConfig } from '../fusion/config.js';
10
- import { buildFusionCleanTaskCanonicalInput } from '../fusion/clean-context.js';
11
- import {
12
- FUSION_VALIDATE_CANDIDATE_SYSTEM_PROMPT,
13
- FUSION_VALIDATE_EVALUATOR_SYSTEM_PROMPT,
14
- FUSION_VALIDATE_MERGER_SYSTEM_PROMPT,
15
- } from '../fusion/prompts.js';
16
- import { FUSION_VALIDATE_WORKFLOW } from '../fusion/workflows.js';
17
- import {
18
- FUSION_EVALUATION_SCHEMA_VERSION,
19
- FUSION_RESULT_SCHEMA_VERSION,
20
- FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION,
21
- FUSION_VALIDATE_CAPABILITY,
22
- FusionError,
23
- type FusionChildRunResult,
24
- type ResolvedFusionModel,
25
- type ResolvedFusionModels,
26
- } from '../fusion/types.js';
27
- import type { RunPiChildOptions } from '../fusion/pi-child.js';
28
-
29
- function resolved(qualifiedId: string): ResolvedFusionModel {
30
- const slash = qualifiedId.indexOf('/');
31
- return { selection: '$current', source: 'current', provider: qualifiedId.slice(0, slash), model: qualifiedId.slice(slash + 1), qualifiedId, thinkingLevel: 'high', contextWindow: 200_000, maxOutputTokens: 32_768 };
32
- }
33
-
34
- function models(): ResolvedFusionModels {
35
- return { candidates: [resolved('p/c1'), resolved('p/c2'), resolved('p/c3')], evaluator: resolved('p/eval'), merger: resolved('p/merge') };
36
- }
37
-
38
- function childResult(options: RunPiChildOptions, text: string): FusionChildRunResult {
39
- const result: FusionChildRunResult = {
40
- stage: options.stage,
41
- attempt: options.attempt,
42
- provider: options.model.provider,
43
- model: options.model.model,
44
- qualifiedId: options.model.qualifiedId,
45
- text,
46
- usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0, totalTokens: 2, cost: { input: 0.01, output: 0.02, cacheRead: 0, cacheWrite: 0, total: 0.03 } },
47
- events: Buffer.from('{"schema_version":"pi-background-tasks.fusion-child-result.v4"}\n'),
48
- stderr: Buffer.alloc(0),
49
- exitCode: 0,
50
- signal: null,
51
- };
52
- if (options.slot !== undefined) result.slot = options.slot;
53
- return result;
54
- }
55
-
56
- function candidateReport(slot: number): string {
57
- return JSON.stringify({
58
- schema_version: FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION,
59
- findings: [{ severity: slot === 1 ? 'critical' : 'minor', location: `src/file-${String(slot)}.ts:1`, evidence: `evidence ${String(slot)}`, impact: `impact ${String(slot)}`, summary: `finding ${String(slot)}` }],
60
- verified: [`verified ${String(slot)}`],
61
- limitations: ['none'],
62
- });
63
- }
64
-
65
- function evaluatorText(stdin: string): string {
66
- const blind = JSON.parse(stdin) as { candidates: Array<{ candidate_id: 'A' | 'B' | 'C'; response: string }> };
67
- const findings = blind.candidates.flatMap((candidate) => {
68
- const report = JSON.parse(candidate.response) as {
69
- findings: Array<{
70
- severity: 'critical' | 'high' | 'minor';
71
- location: string;
72
- evidence: string;
73
- impact: string;
74
- summary: string;
75
- }>;
76
- };
77
- return report.findings.map((finding, index) => ({ id: `${candidate.candidate_id}-F${String(index + 1).padStart(3, '0')}`, candidate_id: candidate.candidate_id, ...finding }));
78
- });
79
- return JSON.stringify({
80
- schema_version: FUSION_EVALUATION_SCHEMA_VERSION,
81
- candidate_assessments: ['A', 'B', 'C'].map((candidate_id) => ({ candidate_id, summary: 's', strengths: ['s'], limitations: ['l'], useful_contributions: ['u'], risks: ['r'] })),
82
- agreements: ['agree'],
83
- conflicts: [],
84
- synthesis_plan: { must_include: [{ candidate_id: 'A', contribution: 'preserve findings' }], must_resolve: [], must_avoid: [] },
85
- validation_accounting: {
86
- findings,
87
- decisions: findings.map((finding, index) => ({ source_id: finding.id, disposition: 'include', rationale: 'supported', group_id: `G${String(index + 1)}` })),
88
- groups: findings.map((finding, index) => ({
89
- group_id: `G${String(index + 1)}`,
90
- source_ids: [finding.id],
91
- severity: finding.severity,
92
- location: finding.location,
93
- evidence: finding.evidence,
94
- impact: finding.impact,
95
- summary: finding.summary,
96
- rationale: 'supported by source evidence',
97
- })),
98
- },
99
- });
100
- }
101
-
102
- async function withRoot<T>(fn: (root: string) => Promise<T>): Promise<T> {
103
- const root = await mkdtemp(join(tmpdir(), 'pi-fusion-validate-'));
104
- try { return await fn(root); } finally { await rm(root, { recursive: true, force: true }); }
105
- }
106
-
107
- void describe('fusion validate orchestration', () => {
108
- void it('uses clean input, inspect candidates, no-tool adjudicators, and deterministic validated rendering', async () => {
109
- await withRoot(async (root) => {
110
- const calls: RunPiChildOptions[] = [];
111
- const oversizedOriginal = 'o'.repeat(50_000);
112
- const built = buildFusionCleanTaskCanonicalInput({ cwd: root, source: 'tool', workflow: 'validate', request: canonicalJson({ objective: 'validate', background: [], changeSummary: 'changed', scope: ['src'], acceptanceCriteria: ['works'], verification: { status: 'not_run', evidence: [], reason: 'unit' }, knownLimitations: [], exclusions: [] }) });
113
- const orchestrator = new FusionOrchestrator({
114
- childRunner: (options) => {
115
- calls.push(options);
116
- if (options.stage === 'candidate') {
117
- const candidate = childResult(options, candidateReport(options.slot ?? 0));
118
- if (options.slot === 1) {
119
- candidate.outputRecovery = {
120
- kind: 'same_session_compression',
121
- limit_bytes: 49_152,
122
- original_record_index: 0,
123
- replacement_record_index: 1,
124
- original_json_rendered_bytes: 50_002,
125
- replacement_json_rendered_bytes: Buffer.byteLength(
126
- JSON.stringify(candidate.text),
127
- 'utf8',
128
- ),
129
- original_text_sha256: createHash('sha256').update(oversizedOriginal).digest('hex'),
130
- original_text: oversizedOriginal,
131
- status: 'completed',
132
- };
133
- }
134
- return Promise.resolve(candidate);
135
- }
136
- if (options.stage === 'evaluation')
137
- return Promise.resolve(childResult(options, evaluatorText(options.userPrompt)));
138
- return Promise.resolve(childResult(options, 'ignored free-form merger prose'));
139
- },
140
- });
141
- const result = await orchestrator.run({ source: 'tool', cwd: root, canonicalInput: built.input, canonicalInputSerialized: built.serialized, config: defaultFusionModelConfig(), models: models(), profile: FUSION_VALIDATE_WORKFLOW });
142
-
143
- assert.equal(calls.length, 5);
144
- assert.deepEqual(calls.filter((call) => call.stage === 'candidate').map((call) => call.capability), [FUSION_VALIDATE_CAPABILITY, FUSION_VALIDATE_CAPABILITY, FUSION_VALIDATE_CAPABILITY]);
145
- assert.deepEqual(calls.filter((call) => call.stage !== 'candidate').map((call) => call.capability), ['reason', 'reason']);
146
- assert.equal(calls[0]?.systemPrompt, FUSION_VALIDATE_CANDIDATE_SYSTEM_PROMPT);
147
- assert.equal(calls.find((call) => call.stage === 'evaluation')?.systemPrompt, FUSION_VALIDATE_EVALUATOR_SYSTEM_PROMPT);
148
- assert.equal(calls.find((call) => call.stage === 'merge')?.systemPrompt, FUSION_VALIDATE_MERGER_SYSTEM_PROMPT);
149
- assert.match(result.mergedText, /^# Validation report/);
150
- assert.match(result.mergedText, /Location: src\/file-/);
151
- assert.doesNotMatch(result.mergedText, /ignored free-form/);
152
- assert.equal(result.details.workflow, 'validate');
153
- assert.equal(result.details.schema_version, FUSION_RESULT_SCHEMA_VERSION);
154
- assert.equal(result.details.context.kind, 'clean_task');
155
- assert.equal(
156
- await readFile(
157
- join(root, result.details.artifact_dir, 'candidate-1.attempt-1.response.oversized.txt'),
158
- 'utf8',
159
- ),
160
- oversizedOriginal,
161
- );
162
- });
163
- });
164
-
165
- void it('audits and recovers the narrowly supported fenced-JSON violation', async () => {
166
- await withRoot(async (root) => {
167
- const built = buildFusionCleanTaskCanonicalInput({ cwd: root, source: 'tool', workflow: 'validate', request: 'validate' });
168
- const orchestrator = new FusionOrchestrator({ childRunner: (options) => {
169
- if (options.stage === 'candidate') {
170
- const report = candidateReport(options.slot ?? 0);
171
- if (options.slot === 1) return Promise.resolve(childResult(options, `\`\`\`json\n${report}\n\`\`\``));
172
- if (options.slot === 2) return Promise.resolve(childResult(options, `Validation complete.\n\`\`\`json\n${report}\n\`\`\``));
173
- return Promise.resolve(childResult(options, report));
174
- }
175
- if (options.stage === 'evaluation') return Promise.resolve(childResult(options, evaluatorText(options.userPrompt)));
176
- return Promise.resolve(childResult(options, 'ignored'));
177
- }});
178
- const result = await orchestrator.run({ source: 'tool', cwd: root, canonicalInput: built.input, canonicalInputSerialized: built.serialized, config: defaultFusionModelConfig(), models: models(), profile: FUSION_VALIDATE_WORKFLOW });
179
- assert.match(result.mergedText, /required audited removal of a Markdown JSON wrapper/);
180
- const artifactDir = join(root, result.details.artifact_dir);
181
- const names = await readdir(artifactDir);
182
- const normalizationArtifacts = names.filter((name) => name.includes('output-contract-normalized'));
183
- assert.equal(normalizationArtifacts.length, 2);
184
- const firstNormalization = normalizationArtifacts[0];
185
- assert.ok(firstNormalization);
186
- const normalizationEvent = JSON.parse(await readFile(join(artifactDir, firstNormalization), 'utf8')) as { schema_version?: string; status?: string };
187
- assert.equal(normalizationEvent.schema_version, 'pi-background-tasks.fusion-validation-candidate-contract-event.v1');
188
- assert.equal(normalizationEvent.status, 'normalized');
189
- assert.equal(names.includes('candidate-1.attempt-1.response.txt'), true);
190
- assert.equal(
191
- await readFile(join(artifactDir, 'candidate-1.attempt-1.system-prompt.txt'), 'utf8'),
192
- FUSION_VALIDATE_CANDIDATE_SYSTEM_PROMPT,
193
- );
194
- });
195
- });
196
-
197
- void it('continues with an explicit limitation when one report is irrecoverable', async () => {
198
- await withRoot(async (root) => {
199
- const built = buildFusionCleanTaskCanonicalInput({ cwd: root, source: 'tool', workflow: 'validate', request: 'validate' });
200
- const orchestrator = new FusionOrchestrator({ childRunner: (options) => {
201
- if (options.stage === 'candidate') return Promise.resolve(childResult(options, options.slot === 1 ? 'not JSON' : candidateReport(options.slot ?? 0)));
202
- if (options.stage === 'evaluation') return Promise.resolve(childResult(options, evaluatorText(options.userPrompt)));
203
- return Promise.resolve(childResult(options, 'ignored'));
204
- }});
205
- const result = await orchestrator.run({ source: 'tool', cwd: root, canonicalInput: built.input, canonicalInputSerialized: built.serialized, config: defaultFusionModelConfig(), models: models(), profile: FUSION_VALIDATE_WORKFLOW });
206
- assert.match(result.mergedText, /could not be parsed after strict contract checks/);
207
- const names = await readdir(join(root, result.details.artifact_dir));
208
- assert.equal(names.filter((name) => name.includes('output-contract-dropped')).length, 1);
209
- });
210
- });
211
-
212
- void it('fails loudly for two invalid reports while preserving the anonymous map', async () => {
213
- await withRoot(async (root) => {
214
- const built = buildFusionCleanTaskCanonicalInput({ cwd: root, source: 'tool', workflow: 'validate', request: 'validate' });
215
- const orchestrator = new FusionOrchestrator({ childRunner: (options) =>
216
- Promise.resolve(childResult(options, options.stage === 'candidate' && options.slot !== 3 ? 'not JSON' : candidateReport(options.slot ?? 3)))
217
- });
218
- let failure: FusionError | undefined;
219
- try {
220
- await orchestrator.run({ source: 'tool', cwd: root, canonicalInput: built.input, canonicalInputSerialized: built.serialized, config: defaultFusionModelConfig(), models: models(), profile: FUSION_VALIDATE_WORKFLOW });
221
- } catch (error) {
222
- if (error instanceof FusionError) failure = error;
223
- else throw error;
224
- }
225
- assert.ok(failure);
226
- assert.match(failure.message, /2 of 3 candidate reports/);
227
- assert.ok(failure.artifactDir);
228
- const manifest = JSON.parse(await readFile(join(root, failure.artifactDir, 'manifest.json'), 'utf8')) as { anonymous_map?: unknown };
229
- assert.ok(manifest.anonymous_map);
230
- });
231
- });
232
-
233
- void it('rejects validate runs that attempt to carry a parent ledger', async () => {
234
- await withRoot(async (root) => {
235
- const built = buildFusionCleanTaskCanonicalInput({ cwd: root, source: 'tool', workflow: 'validate', request: 'validate' });
236
- const orchestrator = new FusionOrchestrator({ childRunner: async (options) => childResult(options, '{}') });
237
- await assert.rejects(() => orchestrator.run({ source: 'tool', cwd: root, canonicalInput: built.input, canonicalInputSerialized: built.serialized, contextLedger: { schema_version: 'pi-background-tasks.fusion-context-ledger.v2', policy_id: 'x', transform: 'visible-conversation-ledger-v2', entries: [], projection_map: [], root_sha256: 'a'.repeat(64) }, config: defaultFusionModelConfig(), models: models(), profile: FUSION_VALIDATE_WORKFLOW }), /clean-task fusion input must not carry/);
238
- });
239
- });
240
- });