pi-background-tasks 1.0.6 → 1.0.7
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/README.md +1 -1
- package/docs/manifest.json +2 -2
- package/docs/reference/runtime-contracts.md +28 -27
- package/docs/subsystems/docs-freshness-gate.md +2 -2
- package/docs/subsystems/fusion.md +2 -2
- package/package.json +1 -1
- package/src/core/fusion/artifacts.ts +41 -0
- package/src/core/fusion/budget.ts +12 -4
- package/src/core/fusion/evaluation.ts +61 -0
- package/src/core/fusion/orchestrator.ts +126 -15
- package/src/core/fusion/pi-child.ts +6 -0
- package/src/core/fusion/prompts.ts +1 -0
- package/src/core/fusion/types.ts +10 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
| Fact | Value |
|
|
27
27
|
| --- | --- |
|
|
28
28
|
| Package | `pi-background-tasks` |
|
|
29
|
-
| Version | `1.0.
|
|
29
|
+
| Version | `1.0.7` |
|
|
30
30
|
| Node engine | `>=22.19.0` |
|
|
31
31
|
| Pi entrypoint | `./extensions/background-tasks.ts` |
|
|
32
32
|
| Package image | [logo.png](https://raw.githubusercontent.com/ismailsaleekh/pi-background-tasks/main/logo.png) |
|
package/docs/manifest.json
CHANGED
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"state": "pass"
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
|
-
"authored_body_sha256": "sha256:
|
|
92
|
+
"authored_body_sha256": "sha256:2cea3292dac7fcb890bb05eb0c0783310671e11c1953c981251ec433b1099cb9",
|
|
93
93
|
"covers_sources": [
|
|
94
94
|
"extensions/fusion-child.ts",
|
|
95
95
|
"src/core/fusion/artifacts.ts",
|
|
@@ -788,7 +788,7 @@
|
|
|
788
788
|
"image": "https://raw.githubusercontent.com/ismailsaleekh/pi-background-tasks/main/logo.png",
|
|
789
789
|
"name": "pi-background-tasks",
|
|
790
790
|
"type": "module",
|
|
791
|
-
"version": "1.0.
|
|
791
|
+
"version": "1.0.7"
|
|
792
792
|
},
|
|
793
793
|
"public_surface_ids": [
|
|
794
794
|
"command:bg",
|
|
@@ -49,10 +49,10 @@ This generated registry lists production environment-variable references, runtim
|
|
|
49
49
|
| `PI_BG_SHELL` | read | `src/core/common.ts:679` |
|
|
50
50
|
| `PI_BG_SHELL_PATH` | read | `src/core/common.ts:680` |
|
|
51
51
|
| `PI_CACHE_RETENTION` | read | `src/core/fusion/claude-cache.ts:56` |
|
|
52
|
-
| `PI_FUSION_RESEARCH_ENABLED` | read, remove, write | `src/core/fusion/pi-child.ts:
|
|
53
|
-
| `PI_FUSION_SOURCE_POLICY_PATH` | read, remove, write | `src/core/fusion/pi-child.ts:
|
|
54
|
-
| `PI_FUSION_SOURCE_POLICY_SHA256` | read, remove, write | `src/core/fusion/pi-child.ts:
|
|
55
|
-
| `PI_FUSION_TOOL_CALL_LOG_PATH` | read, remove, write | `src/core/fusion/pi-child.ts:
|
|
52
|
+
| `PI_FUSION_RESEARCH_ENABLED` | read, remove, write | `src/core/fusion/pi-child.ts:1811`<br>`src/core/fusion/pi-child.ts:92`<br>`src/fusion-child-extension.ts:673` |
|
|
53
|
+
| `PI_FUSION_SOURCE_POLICY_PATH` | read, remove, write | `src/core/fusion/pi-child.ts:1812`<br>`src/core/fusion/pi-child.ts:92`<br>`src/fusion-child-extension.ts:629` |
|
|
54
|
+
| `PI_FUSION_SOURCE_POLICY_SHA256` | read, remove, write | `src/core/fusion/pi-child.ts:1813`<br>`src/core/fusion/pi-child.ts:92`<br>`src/fusion-child-extension.ts:630` |
|
|
55
|
+
| `PI_FUSION_TOOL_CALL_LOG_PATH` | read, remove, write | `src/core/fusion/pi-child.ts:1800`<br>`src/core/fusion/pi-child.ts:92`<br>`src/fusion-child-extension.ts:672` |
|
|
56
56
|
| `PI_MODEL` | remove | `src/core/delegate/launch.ts:301`<br>`src/core/fusion/pi-child.ts:92` |
|
|
57
57
|
| `PI_OFFLINE` | read | `src/extension.ts:445` |
|
|
58
58
|
| `PI_PROVIDER` | remove | `src/core/delegate/launch.ts:301`<br>`src/core/fusion/pi-child.ts:92` |
|
|
@@ -81,26 +81,26 @@ This generated registry lists production environment-variable references, runtim
|
|
|
81
81
|
| delegate-artifact | `seed.json` | `src/core/delegate/artifacts.ts:40` |
|
|
82
82
|
| delegate-artifact | `spill/<receipt-named-file>` | `src/core/delegate/artifacts.ts:53` |
|
|
83
83
|
| directory | `.pi/delegate/<session-id>-<pid>/<task-id>/` | `src/core/delegate/artifacts.ts:157` |
|
|
84
|
-
| directory | `.pi/fusion/<session-id>-<pid>/<run-id>/` | `src/core/fusion/artifacts.ts:
|
|
84
|
+
| directory | `.pi/fusion/<session-id>-<pid>/<run-id>/` | `src/core/fusion/artifacts.ts:277` |
|
|
85
85
|
| directory | `.pi/tasks/<session-id>-<pid>/` | `src/core/registry.ts:780` |
|
|
86
|
-
| fusion-artifact | `<attempt-prefix> = candidate-<slot>.attempt-<n> \| evaluation.attempt-<n> \| merge.attempt-<n>` | `src/core/fusion/artifacts.ts:
|
|
87
|
-
| fusion-artifact | `<attempt-prefix>.calibration-violation.json` | `src/core/fusion/artifacts.ts:
|
|
88
|
-
| fusion-artifact | `<attempt-prefix>.events.jsonl` | `src/core/fusion/artifacts.ts:
|
|
89
|
-
| fusion-artifact | `<attempt-prefix>.prompt.txt` | `src/core/fusion/artifacts.ts:
|
|
90
|
-
| fusion-artifact | `<attempt-prefix>.stderr.txt` | `src/core/fusion/artifacts.ts:
|
|
91
|
-
| fusion-artifact | `blind-candidates.json` | `src/core/fusion/artifacts.ts:
|
|
92
|
-
| fusion-artifact | `budget-plan.json` | `src/core/fusion/artifacts.ts:
|
|
93
|
-
| fusion-artifact | `candidate-<slot>.attempt-<n>.response.md \| candidate-<slot>.attempt-<n>.response.partial.md` | `src/core/fusion/artifacts.ts:
|
|
94
|
-
| fusion-artifact | `candidate-<slot>.attempt-<n>.tool-calls.jsonl` | `src/core/fusion/artifacts.ts:
|
|
86
|
+
| fusion-artifact | `<attempt-prefix> = candidate-<slot>.attempt-<n> \| evaluation.attempt-<n> \| merge.attempt-<n>` | `src/core/fusion/artifacts.ts:219` |
|
|
87
|
+
| fusion-artifact | `<attempt-prefix>.calibration-violation.json` | `src/core/fusion/artifacts.ts:234` |
|
|
88
|
+
| fusion-artifact | `<attempt-prefix>.events.jsonl` | `src/core/fusion/artifacts.ts:429` |
|
|
89
|
+
| fusion-artifact | `<attempt-prefix>.prompt.txt` | `src/core/fusion/artifacts.ts:428` |
|
|
90
|
+
| fusion-artifact | `<attempt-prefix>.stderr.txt` | `src/core/fusion/artifacts.ts:430` |
|
|
91
|
+
| fusion-artifact | `blind-candidates.json` | `src/core/fusion/artifacts.ts:399` |
|
|
92
|
+
| fusion-artifact | `budget-plan.json` | `src/core/fusion/artifacts.ts:395` |
|
|
93
|
+
| fusion-artifact | `candidate-<slot>.attempt-<n>.response.md \| candidate-<slot>.attempt-<n>.response.partial.md` | `src/core/fusion/artifacts.ts:229` |
|
|
94
|
+
| fusion-artifact | `candidate-<slot>.attempt-<n>.tool-calls.jsonl` | `src/core/fusion/artifacts.ts:327` |
|
|
95
95
|
| fusion-artifact | `candidate-<slot>.attempt-<n>.tool-calls.jsonl.seal.json` | `src/core/fusion/child-protocol.ts:17` |
|
|
96
|
-
| fusion-artifact | `canonical-input.json` | `src/core/fusion/artifacts.ts:
|
|
97
|
-
| fusion-artifact | `context-omission-ledger.json` | `src/core/fusion/artifacts.ts:
|
|
98
|
-
| fusion-artifact | `error.json` | `src/core/fusion/artifacts.ts:
|
|
99
|
-
| fusion-artifact | `evaluation.attempt-<n>.response.txt \| evaluation.attempt-<n>.response.partial.txt` | `src/core/fusion/artifacts.ts:
|
|
100
|
-
| fusion-artifact | `evaluation.json` | `src/core/fusion/artifacts.ts:
|
|
101
|
-
| fusion-artifact | `merge.attempt-<n>.response.md \| merge.attempt-<n>.response.partial.md` | `src/core/fusion/artifacts.ts:
|
|
102
|
-
| fusion-artifact | `merged.md` | `src/core/fusion/artifacts.ts:
|
|
103
|
-
| fusion-artifact | `source-policy.private.json` | `src/core/fusion/artifacts.ts:
|
|
96
|
+
| fusion-artifact | `canonical-input.json` | `src/core/fusion/artifacts.ts:365` |
|
|
97
|
+
| fusion-artifact | `context-omission-ledger.json` | `src/core/fusion/artifacts.ts:374` |
|
|
98
|
+
| fusion-artifact | `error.json` | `src/core/fusion/artifacts.ts:411` |
|
|
99
|
+
| fusion-artifact | `evaluation.attempt-<n>.response.txt \| evaluation.attempt-<n>.response.partial.txt` | `src/core/fusion/artifacts.ts:229` |
|
|
100
|
+
| fusion-artifact | `evaluation.json` | `src/core/fusion/artifacts.ts:403` |
|
|
101
|
+
| fusion-artifact | `merge.attempt-<n>.response.md \| merge.attempt-<n>.response.partial.md` | `src/core/fusion/artifacts.ts:229` |
|
|
102
|
+
| fusion-artifact | `merged.md` | `src/core/fusion/artifacts.ts:342` |
|
|
103
|
+
| fusion-artifact | `source-policy.private.json` | `src/core/fusion/artifacts.ts:381` |
|
|
104
104
|
| task-file | `.pi/tasks/<session-id>-<pid>/<task-id>.attestation.json` | `src/core/attested-pi-run.ts:583` |
|
|
105
105
|
| task-file | `.pi/tasks/<session-id>-<pid>/<task-id>.json` | `src/core/registry.ts:808` |
|
|
106
106
|
| task-file | `.pi/tasks/<session-id>-<pid>/<task-id>.output` | `src/core/registry.ts:807` |
|
|
@@ -127,20 +127,20 @@ This generated registry lists production environment-variable references, runtim
|
|
|
127
127
|
| `pi-background-tasks.extension-request.v1` | `src/core/extension-api.ts:15` |
|
|
128
128
|
| `pi-background-tasks.extension-response.v1` | `src/core/extension-api.ts:16` |
|
|
129
129
|
| `pi-background-tasks.extension-terminal.v1` | `src/core/extension-api.ts:17` |
|
|
130
|
-
| `pi-background-tasks.fusion-blind-candidates.v1` | `src/core/fusion/prompts.ts:
|
|
130
|
+
| `pi-background-tasks.fusion-blind-candidates.v1` | `src/core/fusion/prompts.ts:300` |
|
|
131
131
|
| `pi-background-tasks.fusion-budget-plan.v4` | `src/core/fusion/types.ts:25` |
|
|
132
|
-
| `pi-background-tasks.fusion-calibration-violation.
|
|
132
|
+
| `pi-background-tasks.fusion-calibration-violation.v2` | `src/core/fusion/types.ts:27` |
|
|
133
133
|
| `pi-background-tasks.fusion-child-result.v3` | `src/core/fusion/child-protocol.ts:6` |
|
|
134
134
|
| `pi-background-tasks.fusion-child-settlement.v2` | `src/core/fusion/child-protocol.ts:9` |
|
|
135
135
|
| `pi-background-tasks.fusion-claude-cache-observation.v1` | `src/core/fusion/claude-cache.ts:4` |
|
|
136
136
|
| `pi-background-tasks.fusion-context-ledger.v2` | `src/core/fusion/types.ts:23` |
|
|
137
|
-
| `pi-background-tasks.fusion-evaluation-repair-input.v1` | `src/core/fusion/prompts.ts:
|
|
137
|
+
| `pi-background-tasks.fusion-evaluation-repair-input.v1` | `src/core/fusion/prompts.ts:280` |
|
|
138
138
|
| `pi-background-tasks.fusion-evaluation.v1` | `src/core/fusion/types.ts:16` |
|
|
139
139
|
| `pi-background-tasks.fusion-input.v4` | `src/core/fusion/types.ts:14` |
|
|
140
140
|
| `pi-background-tasks.fusion-input.v5` | `src/core/fusion/types.ts:15` |
|
|
141
141
|
| `pi-background-tasks.fusion-manifest.v3` | `src/core/fusion/types.ts:21` |
|
|
142
142
|
| `pi-background-tasks.fusion-manifest.v4` | `src/core/fusion/types.ts:22` |
|
|
143
|
-
| `pi-background-tasks.fusion-merge-input.v1` | `src/core/fusion/prompts.ts:
|
|
143
|
+
| `pi-background-tasks.fusion-merge-input.v1` | `src/core/fusion/prompts.ts:326` |
|
|
144
144
|
| `pi-background-tasks.fusion-models.v1` | `src/core/fusion/types.ts:13` |
|
|
145
145
|
| `pi-background-tasks.fusion-progress.v1` | `src/fusion-extension.ts:57` |
|
|
146
146
|
| `pi-background-tasks.fusion-request.v1` | `src/fusion-extension.ts:58` |
|
|
@@ -149,7 +149,8 @@ This generated registry lists production environment-variable references, runtim
|
|
|
149
149
|
| `pi-background-tasks.fusion-runtime-guard.v1` | `src/core/fusion/child-protocol.ts:19` |
|
|
150
150
|
| `pi-background-tasks.fusion-source-policy.v1` | `src/core/fusion/types.ts:24` |
|
|
151
151
|
| `pi-background-tasks.fusion-tool-call-seal.v1` | `src/core/fusion/child-protocol.ts:16` |
|
|
152
|
-
| `pi-background-tasks.fusion-tool-call.v1` | `src/core/fusion/types.ts:
|
|
152
|
+
| `pi-background-tasks.fusion-tool-call.v1` | `src/core/fusion/types.ts:30` |
|
|
153
|
+
| `pi-background-tasks.fusion-validation-candidate-contract-event.v1` | `src/core/fusion/types.ts:29` |
|
|
153
154
|
| `pi-background-tasks.fusion-validation-candidate.v1` | `src/core/fusion/types.ts:18` |
|
|
154
155
|
| `pi-background-tasks.input-token-calibration.v1` | `src/core/context/token-budget.ts:18` |
|
|
155
156
|
|
|
@@ -12,12 +12,12 @@ covers_sources: []
|
|
|
12
12
|
This authored section defines the boundary: documentation facts are extracted from package metadata and TypeScript ASTs, then generated into docs and the manifest. Unsupported syntax fails the gate rather than falling back to regex or stale hand-maintained inventories. Public registrations must remain unconditional top-level direct calls or use the one validated local tool-wrapper shape; host/method aliases, computed access, nested or conditional registration, wrapper chaining/passing, constructor helpers, ambiguous public metadata, destructured Pi parameters, and repeated imported registrars are rejected.
|
|
13
13
|
|
|
14
14
|
<!-- pi-docs:begin name="docs-freshness-gate" generator="scripts/docs/generate.mjs" -->
|
|
15
|
-
- Canonical package version: `1.0.
|
|
15
|
+
- Canonical package version: `1.0.7`
|
|
16
16
|
- Governed markdown docs: 40
|
|
17
17
|
- Public surfaces extracted: 30
|
|
18
18
|
- Governed production sources: 45
|
|
19
19
|
- Tool contracts extracted: 11
|
|
20
|
-
- Schema IDs extracted:
|
|
20
|
+
- Schema IDs extracted: 41
|
|
21
21
|
- Environment variable references extracted: 47
|
|
22
22
|
- Behavioral attestation receipts not passing: 1
|
|
23
23
|
- Receipt store: `docs/attestations.json`
|
|
@@ -72,7 +72,7 @@ Evaluator, evaluator-repair, and merger always use capability `reason` and empty
|
|
|
72
72
|
|
|
73
73
|
## Validation specifics
|
|
74
74
|
|
|
75
|
-
`fusion_validate` enforces a strict public verification contract: `provided` requires non-empty evidence and no reason; `not_run` requires a reason and empty/omitted evidence. Reviewers return closed candidate-report JSON. The host assigns stable finding ids after anonymization, the evaluator must account for every source finding exactly once, and the host renders the final report from validated accounting after the merger. Validation is advisory and read-only: it never edits files, runs tests, gates a release, or replaces builds, linters, scanners, or human review.
|
|
75
|
+
`fusion_validate` enforces a strict public verification contract: `provided` requires non-empty evidence and no reason; `not_run` requires a reason and empty/omitted evidence. Reviewers return exactly one bare, closed candidate-report JSON object. The host keeps its shared JSON parser strict; a single complete `json` fence can be removed only by the validation-specific audited recovery path, which writes a contract-event artifact and surfaces a limitation. One irrecoverable minority report is also recorded and surfaced as a limitation, while two invalid reports fail the workflow. The host assigns stable finding ids after anonymization, the evaluator must account for every source finding exactly once, and the host renders the final report from validated accounting after the merger. Validation is advisory and read-only: it never edits files, runs tests, gates a release, or replaces builds, linters, scanners, or human review.
|
|
76
76
|
|
|
77
77
|
## Research specifics
|
|
78
78
|
|
|
@@ -96,7 +96,7 @@ Fusion child environments strip session/model/provider variables plus metered cr
|
|
|
96
96
|
|
|
97
97
|
## Budgets and output contracts
|
|
98
98
|
|
|
99
|
-
Budget planning is per route and per stage. Every configured candidate, evaluator, and merger route must have a usable context window. The affine estimator from the shared token-budget layer accounts for byte classes plus a 512-token intercept; backed model-family calibrations are used only where applicable, unknown/unbacked providers are reported in artifacts/result details, and multibyte/dense ASCII diagnostics are preserved.
|
|
99
|
+
Budget planning is per route and per stage. Every configured candidate, evaluator, and merger route must have a usable context window. The affine estimator from the shared token-budget layer accounts for byte classes plus a 512-token intercept; backed model-family calibrations are used only where applicable, unknown/unbacked providers are reported in artifacts/result details, and multibyte/dense ASCII diagnostics are preserved. Post-run calibration compares that one-request forecast only with the first provider request; cumulative agent-loop and cache usage is retained as total usage but is never misclassified as a prompt under-forecast.
|
|
100
100
|
|
|
101
101
|
`budget-plan.json` uses `pi-background-tasks.fusion-budget-plan.v4` and records route capacities, stage forecasts for candidate/evaluation/evaluation-repair/merge, conditional repair reservation, warnings, blockers, empty-request counterfactuals, and remediation. Each route reserves the larger of Fusion's 32,768-token output contract reserve and the resolved model's declared maximum output; a model advertising a 128,000-token maximum therefore receives the full 128,000-token reserve. Fatal preflight blockers launch zero children. High utilization or worst-case reservation pressure is a warning when input still fits. Exact rendered prompt checks happen again immediately before candidate, evaluation, repair, and merge launches.
|
|
102
102
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-background-tasks",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Pi extension for durable background shell tasks, read-only delegated agents, local attested Pi runs, and fixed-purpose Fusion workflows through child Pi processes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "ISC",
|
|
@@ -7,6 +7,7 @@ import { replaceFileDurable } from '../durable-fs.js';
|
|
|
7
7
|
import {
|
|
8
8
|
EMPTY_FUSION_USAGE,
|
|
9
9
|
FUSION_MANIFEST_SCHEMA_VERSION,
|
|
10
|
+
FUSION_VALIDATE_CANDIDATE_CONTRACT_EVENT_SCHEMA_VERSION,
|
|
10
11
|
FusionError,
|
|
11
12
|
cloneFusionUsage,
|
|
12
13
|
type FusionArtifactManifest,
|
|
@@ -83,14 +84,39 @@ export interface CreateFusionArtifactStoreOptions {
|
|
|
83
84
|
|
|
84
85
|
export interface RecordFusionChildAttemptInput {
|
|
85
86
|
result: FusionChildRunResult;
|
|
87
|
+
systemPrompt: string;
|
|
86
88
|
prompt: string;
|
|
87
89
|
responseKind: 'md' | 'txt';
|
|
88
90
|
}
|
|
89
91
|
|
|
92
|
+
export type RecordValidationCandidateContractEventInput =
|
|
93
|
+
| {
|
|
94
|
+
candidateId: FusionCandidateId;
|
|
95
|
+
slot: 1 | 2 | 3;
|
|
96
|
+
status: 'normalized';
|
|
97
|
+
detail: {
|
|
98
|
+
normalization: 'markdown_json_fence' | 'prose_then_markdown_json_fence';
|
|
99
|
+
original_sha256: string;
|
|
100
|
+
forwarded_sha256: string;
|
|
101
|
+
warning: string;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
| {
|
|
105
|
+
candidateId: FusionCandidateId;
|
|
106
|
+
slot: 1 | 2 | 3;
|
|
107
|
+
status: 'dropped';
|
|
108
|
+
detail: {
|
|
109
|
+
response_sha256: string;
|
|
110
|
+
error: string;
|
|
111
|
+
warning: string;
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
90
115
|
export interface RecordFusionFailedAttemptInput {
|
|
91
116
|
stage: FusionStage;
|
|
92
117
|
slot?: 1 | 2 | 3;
|
|
93
118
|
attempt: number;
|
|
119
|
+
systemPrompt: string;
|
|
94
120
|
prompt: string;
|
|
95
121
|
events: Buffer;
|
|
96
122
|
partialResponse: Buffer;
|
|
@@ -398,6 +424,7 @@ export class FusionArtifactStore {
|
|
|
398
424
|
|
|
399
425
|
async recordChildAttempt(input: RecordFusionChildAttemptInput): Promise<void> {
|
|
400
426
|
const prefix = attemptPrefix(input.result.stage, input.result.slot, input.result.attempt);
|
|
427
|
+
await this.writeArtifact(`${prefix}.system-prompt.txt`, input.systemPrompt);
|
|
401
428
|
const promptRef = await this.writeArtifact(`${prefix}.prompt.txt`, input.prompt);
|
|
402
429
|
const eventsRef = await this.writeArtifact(`${prefix}.events.jsonl`, input.result.events);
|
|
403
430
|
const stderrRef = await this.writeArtifact(`${prefix}.stderr.txt`, input.result.stderr);
|
|
@@ -442,8 +469,22 @@ export class FusionArtifactStore {
|
|
|
442
469
|
return this.writeArtifact(calibrationViolationName(prefix), `${canonicalJson(input.violation)}\n`);
|
|
443
470
|
}
|
|
444
471
|
|
|
472
|
+
async recordValidationCandidateContractEvent(
|
|
473
|
+
input: RecordValidationCandidateContractEventInput,
|
|
474
|
+
): Promise<FusionArtifactRef> {
|
|
475
|
+
const name = `candidate-${String(input.slot)}.output-contract-${input.status}.json`;
|
|
476
|
+
return this.writeArtifact(name, `${canonicalJson({
|
|
477
|
+
schema_version: FUSION_VALIDATE_CANDIDATE_CONTRACT_EVENT_SCHEMA_VERSION,
|
|
478
|
+
...input.detail,
|
|
479
|
+
candidate_id: input.candidateId,
|
|
480
|
+
slot: input.slot,
|
|
481
|
+
status: input.status,
|
|
482
|
+
})}\n`);
|
|
483
|
+
}
|
|
484
|
+
|
|
445
485
|
async recordFailedAttempt(input: RecordFusionFailedAttemptInput): Promise<void> {
|
|
446
486
|
const prefix = attemptPrefix(input.stage, input.slot, input.attempt);
|
|
487
|
+
await this.writeArtifact(`${prefix}.system-prompt.txt`, input.systemPrompt);
|
|
447
488
|
const promptRef = await this.writeArtifact(`${prefix}.prompt.txt`, input.prompt);
|
|
448
489
|
const eventsRef = await this.writeArtifact(`${prefix}.events.jsonl`, input.events);
|
|
449
490
|
const stderrRef = await this.writeArtifact(`${prefix}.stderr.txt`, input.stderr);
|
|
@@ -1122,7 +1122,13 @@ export class FusionBudget {
|
|
|
1122
1122
|
const inputSegments = [knownTextSegment(systemPrompt), knownTextSegment(userPrompt)];
|
|
1123
1123
|
const promptUtf8Bytes = inputSegments.reduce((sum, segment) => sum + segment.bytes, 0);
|
|
1124
1124
|
const estimate = estimateRouteInput(route, inputSegments);
|
|
1125
|
-
|
|
1125
|
+
// The forecast is a one-request admission estimate. Compare it only with
|
|
1126
|
+
// the first provider request, never with aggregate agent-loop/cache usage.
|
|
1127
|
+
// Custom child runners predating this observation field remain compatible,
|
|
1128
|
+
// but cannot produce a calibration verdict without like-for-like evidence.
|
|
1129
|
+
const observedUsage = result.firstRequestUsage;
|
|
1130
|
+
if (observedUsage === undefined) return undefined;
|
|
1131
|
+
const billedInput = observedUsage.input + observedUsage.cacheRead + observedUsage.cacheWrite;
|
|
1126
1132
|
if (billedInput <= estimate.tokens) return undefined;
|
|
1127
1133
|
const violation: FusionCalibrationViolation = {
|
|
1128
1134
|
schema_version: FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION,
|
|
@@ -1137,12 +1143,14 @@ export class FusionBudget {
|
|
|
1137
1143
|
rate_source: estimate.rateSource,
|
|
1138
1144
|
prompt_utf8_bytes: promptUtf8Bytes,
|
|
1139
1145
|
prompt_sha256: sha256Hex(`${systemPrompt}\u0000${userPrompt}`),
|
|
1146
|
+
observation_scope: 'first_provider_request',
|
|
1147
|
+
provider_request_count: result.providerRequestCount ?? 1,
|
|
1140
1148
|
forecast_input_tokens: estimate.tokens,
|
|
1141
1149
|
billed_input_tokens: billedInput,
|
|
1142
1150
|
billed_input_breakdown: {
|
|
1143
|
-
input:
|
|
1144
|
-
cache_read:
|
|
1145
|
-
cache_write:
|
|
1151
|
+
input: observedUsage.input,
|
|
1152
|
+
cache_read: observedUsage.cacheRead,
|
|
1153
|
+
cache_write: observedUsage.cacheWrite,
|
|
1146
1154
|
},
|
|
1147
1155
|
under_forecast_tokens: billedInput - estimate.tokens,
|
|
1148
1156
|
byte_class_breakdown: estimate.byte_class_breakdown,
|
|
@@ -525,6 +525,67 @@ export interface ParsedFusionValidationCandidateReport {
|
|
|
525
525
|
limitations: readonly string[];
|
|
526
526
|
}
|
|
527
527
|
|
|
528
|
+
export type FusionValidationCandidateNormalization =
|
|
529
|
+
| 'markdown_json_fence'
|
|
530
|
+
| 'prose_then_markdown_json_fence';
|
|
531
|
+
|
|
532
|
+
export interface RecoveredFusionValidationCandidateReport {
|
|
533
|
+
report: ParsedFusionValidationCandidateReport;
|
|
534
|
+
/** Bare JSON forwarded to the evaluator after explicit, audited recovery. */
|
|
535
|
+
response: string;
|
|
536
|
+
normalization: FusionValidationCandidateNormalization;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Recognize exactly one complete Markdown JSON fence, optionally preceded by a
|
|
541
|
+
* short prose preamble. This is deliberately narrower than generic substring
|
|
542
|
+
* extraction: trailing prose, nested fences, unlabelled fences, and oversized
|
|
543
|
+
* preambles remain contract failures.
|
|
544
|
+
*/
|
|
545
|
+
function fencedValidationCandidateJson(text: string): {
|
|
546
|
+
payload: string;
|
|
547
|
+
normalization: FusionValidationCandidateNormalization;
|
|
548
|
+
} | undefined {
|
|
549
|
+
const trimmed = text.trim();
|
|
550
|
+
const openingPattern = /```json[ \t]*\r?\n/giu;
|
|
551
|
+
const openings = [...trimmed.matchAll(openingPattern)];
|
|
552
|
+
if (openings.length !== 1) return undefined;
|
|
553
|
+
const opening = openings[0];
|
|
554
|
+
if (opening === undefined) return undefined;
|
|
555
|
+
const headerEnd = opening.index + opening[0].length;
|
|
556
|
+
const closing = trimmed.indexOf('```', headerEnd);
|
|
557
|
+
if (closing < 0 || trimmed.slice(closing + 3).includes('```')) return undefined;
|
|
558
|
+
if (trimmed.slice(closing + 3).trim().length > 0) return undefined;
|
|
559
|
+
const preamble = trimmed.slice(0, opening.index).trim();
|
|
560
|
+
if (Buffer.byteLength(preamble, 'utf8') > 2_000 || preamble.includes('```')) return undefined;
|
|
561
|
+
const payload = trimmed.slice(headerEnd, closing).trim();
|
|
562
|
+
if (payload.length === 0 || payload.includes('```')) return undefined;
|
|
563
|
+
return {
|
|
564
|
+
payload,
|
|
565
|
+
normalization: preamble.length === 0
|
|
566
|
+
? 'markdown_json_fence'
|
|
567
|
+
: 'prose_then_markdown_json_fence',
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Defensive recovery for the one observed contract violation shape. Callers
|
|
573
|
+
* must persist/surface the returned normalization; this function intentionally
|
|
574
|
+
* does not make the strict parser permissive.
|
|
575
|
+
*/
|
|
576
|
+
export function recoverFencedFusionValidationCandidateReport(
|
|
577
|
+
text: string,
|
|
578
|
+
candidateId: FusionCandidateId,
|
|
579
|
+
): RecoveredFusionValidationCandidateReport | undefined {
|
|
580
|
+
const recovered = fencedValidationCandidateJson(text);
|
|
581
|
+
if (recovered === undefined) return undefined;
|
|
582
|
+
return {
|
|
583
|
+
report: parseFusionValidationCandidateReport(recovered.payload, candidateId),
|
|
584
|
+
response: recovered.payload,
|
|
585
|
+
normalization: recovered.normalization,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
528
589
|
export function parseFusionValidationCandidateReport(text: string, candidateId: FusionCandidateId): ParsedFusionValidationCandidateReport {
|
|
529
590
|
let parsed: unknown;
|
|
530
591
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomBytes as nodeRandomBytes } from 'node:crypto';
|
|
1
|
+
import { createHash, randomBytes as nodeRandomBytes } from 'node:crypto';
|
|
2
2
|
import { canonicalJson } from '../attested-pi-run.js';
|
|
3
3
|
import { parseJsonText } from '../common.js';
|
|
4
4
|
import { FUSION_BUDGET_POLICY, FusionBudget, assertChildOutputWithinContract } from './budget.js';
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
boundedEvaluationErrors,
|
|
12
12
|
formatEvaluationErrors,
|
|
13
13
|
parseFusionValidationCandidateReport,
|
|
14
|
+
recoverFencedFusionValidationCandidateReport,
|
|
14
15
|
renderValidatedFusionValidationReport,
|
|
15
16
|
validateFusionEvaluation,
|
|
16
17
|
validateFusionFindingAccounting,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
FUSION_INPUT_SCHEMA_VERSION,
|
|
36
37
|
FUSION_NO_TOOLS_CAPABILITY,
|
|
37
38
|
FUSION_RESULT_SCHEMA_VERSION,
|
|
39
|
+
FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION,
|
|
38
40
|
FusionError,
|
|
39
41
|
addFusionUsage,
|
|
40
42
|
createEmptyFusionUsage,
|
|
@@ -169,6 +171,7 @@ function recordFailureInput(
|
|
|
169
171
|
stage: FusionStage,
|
|
170
172
|
slot: CandidateSlot | undefined,
|
|
171
173
|
attempt: number,
|
|
174
|
+
systemPrompt: string,
|
|
172
175
|
prompt: string,
|
|
173
176
|
responseKind: 'md' | 'txt',
|
|
174
177
|
): RecordFusionFailedAttemptInput {
|
|
@@ -176,6 +179,7 @@ function recordFailureInput(
|
|
|
176
179
|
const base: RecordFusionFailedAttemptInput = {
|
|
177
180
|
stage,
|
|
178
181
|
attempt,
|
|
182
|
+
systemPrompt,
|
|
179
183
|
prompt,
|
|
180
184
|
events: error.events,
|
|
181
185
|
partialResponse: error.response,
|
|
@@ -194,6 +198,7 @@ function recordFailureInput(
|
|
|
194
198
|
const base: RecordFusionFailedAttemptInput = {
|
|
195
199
|
stage,
|
|
196
200
|
attempt,
|
|
201
|
+
systemPrompt,
|
|
197
202
|
prompt,
|
|
198
203
|
events: Buffer.alloc(0),
|
|
199
204
|
partialResponse: Buffer.alloc(0),
|
|
@@ -371,22 +376,123 @@ function anonymousCandidates(
|
|
|
371
376
|
}
|
|
372
377
|
|
|
373
378
|
interface ValidationSourceData {
|
|
379
|
+
candidates: readonly [AnonymousFusionCandidate, AnonymousFusionCandidate, AnonymousFusionCandidate];
|
|
374
380
|
findings: readonly FusionValidationFindingRecord[];
|
|
375
381
|
verified: readonly string[];
|
|
376
382
|
limitations: readonly string[];
|
|
377
383
|
}
|
|
378
384
|
|
|
379
|
-
function
|
|
385
|
+
function sha256Text(value: string): string {
|
|
386
|
+
return createHash('sha256').update(value, 'utf8').digest('hex');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function boundedContractError(error: unknown): string {
|
|
390
|
+
const value = errorText(error);
|
|
391
|
+
return value.length <= 1_000 ? value : `${value.slice(0, 999)}…`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Enforce the validation-candidate contract without making the shared JSON
|
|
396
|
+
* parser permissive. A single, tightly recognized fenced response is recovered
|
|
397
|
+
* with a durable warning. One irrecoverable minority report is represented as
|
|
398
|
+
* an explicit limitation; two or more still fail the workflow loudly.
|
|
399
|
+
*/
|
|
400
|
+
async function prepareValidationSourceData(
|
|
401
|
+
candidates: readonly [AnonymousFusionCandidate, AnonymousFusionCandidate, AnonymousFusionCandidate],
|
|
402
|
+
anonymousMap: Record<FusionCandidateId, CandidateSlot>,
|
|
403
|
+
store: FusionArtifactStore,
|
|
404
|
+
): Promise<ValidationSourceData> {
|
|
405
|
+
const prepared = candidates.map((candidate) => ({ ...candidate })) as [
|
|
406
|
+
AnonymousFusionCandidate,
|
|
407
|
+
AnonymousFusionCandidate,
|
|
408
|
+
AnonymousFusionCandidate,
|
|
409
|
+
];
|
|
380
410
|
const findings: FusionValidationFindingRecord[] = [];
|
|
381
411
|
const verified: string[] = [];
|
|
382
412
|
const limitations: string[] = [];
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
413
|
+
let normalizationCount = 0;
|
|
414
|
+
const failures: Array<{ candidate: AnonymousFusionCandidate; error: string }> = [];
|
|
415
|
+
|
|
416
|
+
for (const candidate of prepared) {
|
|
417
|
+
try {
|
|
418
|
+
const report = parseFusionValidationCandidateReport(candidate.response, candidate.candidate_id);
|
|
419
|
+
findings.push(...report.findings);
|
|
420
|
+
verified.push(...report.verified);
|
|
421
|
+
limitations.push(...report.limitations);
|
|
422
|
+
continue;
|
|
423
|
+
} catch (strictError) {
|
|
424
|
+
try {
|
|
425
|
+
const recovered = recoverFencedFusionValidationCandidateReport(
|
|
426
|
+
candidate.response,
|
|
427
|
+
candidate.candidate_id,
|
|
428
|
+
);
|
|
429
|
+
if (recovered === undefined) throw strictError;
|
|
430
|
+
await store.recordValidationCandidateContractEvent({
|
|
431
|
+
candidateId: candidate.candidate_id,
|
|
432
|
+
slot: anonymousMap[candidate.candidate_id],
|
|
433
|
+
status: 'normalized',
|
|
434
|
+
detail: {
|
|
435
|
+
normalization: recovered.normalization,
|
|
436
|
+
original_sha256: sha256Text(candidate.response),
|
|
437
|
+
forwarded_sha256: sha256Text(recovered.response),
|
|
438
|
+
warning: 'Candidate output violated the bare-JSON contract; a single complete JSON fence was removed and recorded.',
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
candidate.response = recovered.response;
|
|
442
|
+
findings.push(...recovered.report.findings);
|
|
443
|
+
verified.push(...recovered.report.verified);
|
|
444
|
+
limitations.push(...recovered.report.limitations);
|
|
445
|
+
normalizationCount += 1;
|
|
446
|
+
continue;
|
|
447
|
+
} catch (recoveryError) {
|
|
448
|
+
failures.push({
|
|
449
|
+
candidate,
|
|
450
|
+
error: boundedContractError(recoveryError === strictError ? strictError : recoveryError),
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (normalizationCount > 0) {
|
|
457
|
+
limitations.push(
|
|
458
|
+
`${String(normalizationCount)} validation report${normalizationCount === 1 ? '' : 's'} required audited removal of a Markdown JSON wrapper; JSON content was unchanged.`,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
for (const failure of failures) {
|
|
463
|
+
await store.recordValidationCandidateContractEvent({
|
|
464
|
+
candidateId: failure.candidate.candidate_id,
|
|
465
|
+
slot: anonymousMap[failure.candidate.candidate_id],
|
|
466
|
+
status: 'dropped',
|
|
467
|
+
detail: {
|
|
468
|
+
response_sha256: sha256Text(failure.candidate.response),
|
|
469
|
+
error: failure.error,
|
|
470
|
+
warning: 'Candidate output could not be parsed under the strict or fenced-JSON contract.',
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
if (failures.length > 1) {
|
|
475
|
+
throw new FusionError(
|
|
476
|
+
`fusion_validate cannot continue: ${String(failures.length)} of 3 candidate reports violated the structured-output contract`,
|
|
477
|
+
{ code: 'evaluation_invalid', stage: 'candidate' },
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
const failure = failures[0];
|
|
481
|
+
if (failure !== undefined) {
|
|
482
|
+
const synthetic = canonicalJson({
|
|
483
|
+
schema_version: FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION,
|
|
484
|
+
findings: [],
|
|
485
|
+
verified: [],
|
|
486
|
+
limitations: [
|
|
487
|
+
'This validation report could not be parsed after strict contract checks; no findings or verification claims from it were included.',
|
|
488
|
+
],
|
|
489
|
+
});
|
|
490
|
+
failure.candidate.response = synthetic;
|
|
491
|
+
const report = parseFusionValidationCandidateReport(synthetic, failure.candidate.candidate_id);
|
|
387
492
|
limitations.push(...report.limitations);
|
|
388
493
|
}
|
|
389
|
-
|
|
494
|
+
|
|
495
|
+
return { candidates: prepared, findings, verified, limitations };
|
|
390
496
|
}
|
|
391
497
|
|
|
392
498
|
function validateEvaluationAccountsForSourceFindings(
|
|
@@ -558,11 +664,16 @@ export class FusionOrchestrator {
|
|
|
558
664
|
input.onProgress?.({ type: 'state', state: 'candidates_complete' });
|
|
559
665
|
|
|
560
666
|
const shuffled = anonymousCandidates(candidateResults, shuffledSlots(this.randomBytes));
|
|
561
|
-
|
|
667
|
+
// Persist the blind mapping before workflow-specific contract parsing
|
|
668
|
+
// so a failed validation remains attributable to its durable slot artifact.
|
|
562
669
|
await store.setAnonymousMap(shuffled.map);
|
|
670
|
+
const validationData = profile.id === 'validate'
|
|
671
|
+
? await prepareValidationSourceData(shuffled.candidates, shuffled.map, store)
|
|
672
|
+
: undefined;
|
|
673
|
+
const evaluationCandidates = validationData?.candidates ?? shuffled.candidates;
|
|
563
674
|
const blindInput = buildBlindEvaluationInput(
|
|
564
675
|
input.canonicalInput,
|
|
565
|
-
|
|
676
|
+
evaluationCandidates,
|
|
566
677
|
validationData?.findings,
|
|
567
678
|
);
|
|
568
679
|
await store.writeBlindCandidates(buildEvaluationPrompt(blindInput));
|
|
@@ -585,7 +696,7 @@ export class FusionOrchestrator {
|
|
|
585
696
|
|
|
586
697
|
await store.transition('merging');
|
|
587
698
|
input.onProgress?.({ type: 'state', state: 'merging' });
|
|
588
|
-
const mergeInput = buildMergeInput(input.canonicalInput,
|
|
699
|
+
const mergeInput = buildMergeInput(input.canonicalInput, evaluationCandidates, evaluation);
|
|
589
700
|
const mergePrompt = buildMergePrompt(mergeInput);
|
|
590
701
|
budget.assertStagePrompt('merge', profile.mergerSystemPrompt, mergePrompt);
|
|
591
702
|
input.onProgress?.({ type: 'merge_started' });
|
|
@@ -604,7 +715,7 @@ export class FusionOrchestrator {
|
|
|
604
715
|
'md',
|
|
605
716
|
);
|
|
606
717
|
addFusionUsage(usage, merged.usage);
|
|
607
|
-
await store.recordChildAttempt({ result: merged, prompt: mergePrompt, responseKind: 'md' });
|
|
718
|
+
await store.recordChildAttempt({ result: merged, systemPrompt: profile.mergerSystemPrompt, prompt: mergePrompt, responseKind: 'md' });
|
|
608
719
|
await this.recordCalibrationObservation(
|
|
609
720
|
input,
|
|
610
721
|
store,
|
|
@@ -729,9 +840,9 @@ export class FusionOrchestrator {
|
|
|
729
840
|
controller.signal,
|
|
730
841
|
candidateCapability,
|
|
731
842
|
slot,
|
|
732
|
-
'md',
|
|
843
|
+
profile.id === 'validate' ? 'txt' : 'md',
|
|
733
844
|
).then(async (result) => {
|
|
734
|
-
await store.recordChildAttempt({ result, prompt, responseKind: 'md' });
|
|
845
|
+
await store.recordChildAttempt({ result, systemPrompt, prompt, responseKind: profile.id === 'validate' ? 'txt' : 'md' });
|
|
735
846
|
await this.recordCalibrationObservation(
|
|
736
847
|
input,
|
|
737
848
|
store,
|
|
@@ -866,7 +977,7 @@ export class FusionOrchestrator {
|
|
|
866
977
|
attempt,
|
|
867
978
|
);
|
|
868
979
|
addFusionUsage(usage, result.usage);
|
|
869
|
-
await store.recordChildAttempt({ result, prompt, responseKind: 'txt' });
|
|
980
|
+
await store.recordChildAttempt({ result, systemPrompt, prompt, responseKind: 'txt' });
|
|
870
981
|
await this.recordCalibrationObservation(
|
|
871
982
|
input,
|
|
872
983
|
store,
|
|
@@ -966,7 +1077,7 @@ export class FusionOrchestrator {
|
|
|
966
1077
|
if (!signal.aborted && retryableSpawn(error, launchTry) && launchTry === 1) continue;
|
|
967
1078
|
addFailedChildUsage(usage, error);
|
|
968
1079
|
await store.recordFailedAttempt(
|
|
969
|
-
recordFailureInput(error, stage, slot, logicalAttempt, userPrompt, responseKind),
|
|
1080
|
+
recordFailureInput(error, stage, slot, logicalAttempt, systemPrompt, userPrompt, responseKind),
|
|
970
1081
|
);
|
|
971
1082
|
await store.setUsage(usage);
|
|
972
1083
|
throw error;
|
|
@@ -1440,6 +1440,8 @@ export class FusionPiCompactResultParser {
|
|
|
1440
1440
|
): {
|
|
1441
1441
|
text: string;
|
|
1442
1442
|
usage: FusionUsage;
|
|
1443
|
+
firstRequestUsage: FusionUsage;
|
|
1444
|
+
providerRequestCount: number;
|
|
1443
1445
|
provider: string;
|
|
1444
1446
|
model: string;
|
|
1445
1447
|
qualifiedId: string;
|
|
@@ -1474,6 +1476,8 @@ export class FusionPiCompactResultParser {
|
|
|
1474
1476
|
return {
|
|
1475
1477
|
text: reconstructFinalText(response, final),
|
|
1476
1478
|
usage: observed.usage,
|
|
1479
|
+
firstRequestUsage: cloneFusionUsage(parsed.records[0]?.usage ?? createEmptyFusionUsage()),
|
|
1480
|
+
providerRequestCount: parsed.records.length,
|
|
1477
1481
|
provider: final.provider,
|
|
1478
1482
|
model: final.model,
|
|
1479
1483
|
qualifiedId: `${final.provider}/${final.model}`,
|
|
@@ -2119,6 +2123,8 @@ export async function runPiChild(options: RunPiChildOptions): Promise<FusionChil
|
|
|
2119
2123
|
qualifiedId: parsed.qualifiedId,
|
|
2120
2124
|
text: parsed.text,
|
|
2121
2125
|
usage: parsed.usage,
|
|
2126
|
+
firstRequestUsage: parsed.firstRequestUsage,
|
|
2127
|
+
providerRequestCount: parsed.providerRequestCount,
|
|
2122
2128
|
events: parsed.events,
|
|
2123
2129
|
stderr: parsed.diagnostics,
|
|
2124
2130
|
exitCode: close.code,
|
|
@@ -190,6 +190,7 @@ Return only JSON matching this exact closed schema:
|
|
|
190
190
|
"limitations": ["non-blank statement of what you could not cover"]
|
|
191
191
|
}
|
|
192
192
|
Use an empty findings array when no issues were found; do not omit verified or limitations.
|
|
193
|
+
Do not wrap the JSON in Markdown fences or prose. Emit exactly one bare JSON object.
|
|
193
194
|
|
|
194
195
|
Do not inflate severity and do not invent issues to appear thorough. If the work is correct, say so plainly in verified/limitations. A report with no findings that names the evidence behind that conclusion is a valid and valuable result; a padded report is not.
|
|
195
196
|
|
package/src/core/fusion/types.ts
CHANGED
|
@@ -24,7 +24,9 @@ export const FUSION_CONTEXT_LEDGER_SCHEMA_VERSION = 'pi-background-tasks.fusion-
|
|
|
24
24
|
export const FUSION_SOURCE_POLICY_SCHEMA_VERSION = 'pi-background-tasks.fusion-source-policy.v1';
|
|
25
25
|
export const FUSION_BUDGET_PLAN_SCHEMA_VERSION = 'pi-background-tasks.fusion-budget-plan.v4';
|
|
26
26
|
export const FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION =
|
|
27
|
-
'pi-background-tasks.fusion-calibration-violation.
|
|
27
|
+
'pi-background-tasks.fusion-calibration-violation.v2';
|
|
28
|
+
export const FUSION_VALIDATE_CANDIDATE_CONTRACT_EVENT_SCHEMA_VERSION =
|
|
29
|
+
'pi-background-tasks.fusion-validation-candidate-contract-event.v1';
|
|
28
30
|
export const FUSION_TOOL_CALL_LOG_SCHEMA_VERSION = 'pi-background-tasks.fusion-tool-call.v1';
|
|
29
31
|
|
|
30
32
|
/**
|
|
@@ -769,7 +771,12 @@ export interface FusionChildRunResult {
|
|
|
769
771
|
model: string;
|
|
770
772
|
qualifiedId: string;
|
|
771
773
|
text: string;
|
|
774
|
+
/** Aggregate usage across the complete child agent loop. */
|
|
772
775
|
usage: FusionUsage;
|
|
776
|
+
/** First provider request, used for like-for-like prompt forecast calibration. */
|
|
777
|
+
firstRequestUsage?: FusionUsage;
|
|
778
|
+
/** Number of provider requests represented by aggregate usage. */
|
|
779
|
+
providerRequestCount?: number;
|
|
773
780
|
events: Buffer;
|
|
774
781
|
stderr: Buffer;
|
|
775
782
|
exitCode: number;
|
|
@@ -962,6 +969,8 @@ export interface FusionCalibrationViolation {
|
|
|
962
969
|
rate_source: TokenBudgetRateSource;
|
|
963
970
|
prompt_utf8_bytes: number;
|
|
964
971
|
prompt_sha256: string;
|
|
972
|
+
observation_scope: 'first_provider_request';
|
|
973
|
+
provider_request_count: number;
|
|
965
974
|
forecast_input_tokens: number;
|
|
966
975
|
billed_input_tokens: number;
|
|
967
976
|
billed_input_breakdown: {
|