pi-background-tasks 1.0.4 → 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/BACKGROUND-TASKS-INSTRUCTIONS.md +2 -1
- package/README.md +2 -1
- package/TESTING.md +2 -2
- package/TEST_PLAN.md +10 -9
- package/docs/manifest.json +8 -4
- package/docs/operations/configuration.md +10 -0
- package/docs/operations/testing.md +2 -1
- package/docs/operations/troubleshooting.md +3 -1
- package/docs/read-before-edit.md +1 -0
- package/docs/reference/runtime-contracts.md +66 -61
- package/docs/subsystems/docs-freshness-gate.md +5 -5
- package/docs/subsystems/fusion.md +19 -14
- package/package.json +2 -1
- package/src/core/fusion/artifacts.ts +41 -0
- package/src/core/fusion/budget.ts +23 -8
- package/src/core/fusion/child-protocol.ts +137 -12
- package/src/core/fusion/claude-cache.ts +186 -0
- package/src/core/fusion/config.ts +13 -0
- package/src/core/fusion/evaluation.ts +61 -0
- package/src/core/fusion/orchestrator.ts +126 -15
- package/src/core/fusion/pi-child.ts +564 -14
- package/src/core/fusion/prompts.ts +1 -0
- package/src/core/fusion/types.ts +39 -8
- package/src/fusion-child-extension.ts +413 -4
|
@@ -6,17 +6,39 @@ import { createRequire } from 'node:module';
|
|
|
6
6
|
import { dirname, resolve } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import {
|
|
9
|
+
FUSION_CHILD_MAX_PROVIDER_REQUESTS,
|
|
10
|
+
FUSION_CHILD_MAX_TOOL_CALLS,
|
|
11
|
+
FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS,
|
|
9
12
|
FUSION_CHILD_RESULT_PREFIX,
|
|
10
13
|
FUSION_CHILD_RESULT_SCHEMA_VERSION,
|
|
14
|
+
FUSION_CHILD_SAFETY_RESERVE_TOKENS,
|
|
15
|
+
FUSION_CHILD_SETTLEMENT_PREFIX,
|
|
16
|
+
FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION,
|
|
11
17
|
FUSION_RESEARCH_ENABLED_ENV,
|
|
18
|
+
FUSION_RUNTIME_GUARD_PREFIX,
|
|
19
|
+
FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
|
|
12
20
|
FUSION_SOURCE_POLICY_PATH_ENV,
|
|
13
21
|
FUSION_SOURCE_POLICY_SHA256_ENV,
|
|
14
22
|
FUSION_TOOL_CALL_LOG_PATH_ENV,
|
|
15
23
|
FUSION_TOOL_CALL_SEAL_SCHEMA_VERSION,
|
|
16
24
|
FUSION_TOOL_CALL_SEAL_SUFFIX,
|
|
17
25
|
FUSION_CHILD_MAX_TOTAL_TOOL_RESULT_BYTES,
|
|
26
|
+
buildFusionChildSettlement,
|
|
27
|
+
isRecoverableFusionChildErrorRecord,
|
|
28
|
+
serializeFusionChildResultRecords,
|
|
18
29
|
type FusionChildResultMetadata,
|
|
30
|
+
type FusionChildSettlementFailureReason,
|
|
31
|
+
type FusionChildSettlementRecord,
|
|
32
|
+
type FusionRuntimeGuardCode,
|
|
33
|
+
type FusionRuntimeGuardRecord,
|
|
19
34
|
} from './child-protocol.js';
|
|
35
|
+
import {
|
|
36
|
+
FUSION_CLAUDE_CACHE_BREAKPOINT_LIMIT,
|
|
37
|
+
FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION,
|
|
38
|
+
FUSION_CLAUDE_CACHE_RETENTION_ENV,
|
|
39
|
+
type FusionClaudeCacheObservation,
|
|
40
|
+
type FusionClaudeCacheRetention,
|
|
41
|
+
} from './claude-cache.js';
|
|
20
42
|
import {
|
|
21
43
|
FUSION_FORBIDDEN_TOOLS,
|
|
22
44
|
FUSION_NO_TOOLS_CAPABILITY,
|
|
@@ -405,8 +427,9 @@ function fusionToolArgv(capability: FusionCapability): string[] {
|
|
|
405
427
|
*
|
|
406
428
|
* `--no-extensions` disables discovery but still honours explicit `--extension`
|
|
407
429
|
* paths, so this list is the complete set a child receives. The metadata
|
|
408
|
-
* extension is always present
|
|
409
|
-
*
|
|
430
|
+
* extension is always present. For Claude routes the sanitizer loads first so
|
|
431
|
+
* the private runtime governor observes the final post-sanitizer payload; non-
|
|
432
|
+
* Anthropic child argv remains unchanged.
|
|
410
433
|
*/
|
|
411
434
|
export function fusionChildExtensionPaths(
|
|
412
435
|
model: ResolvedFusionModel,
|
|
@@ -414,7 +437,7 @@ export function fusionChildExtensionPaths(
|
|
|
414
437
|
resolveSanitizer: () => string = resolveAnthropicSanitizerExtensionPath,
|
|
415
438
|
): readonly string[] {
|
|
416
439
|
if (model.provider !== FUSION_SANITIZED_PROVIDER) return [childExtensionPath];
|
|
417
|
-
return [
|
|
440
|
+
return [resolveSanitizer(), childExtensionPath];
|
|
418
441
|
}
|
|
419
442
|
|
|
420
443
|
export function buildFusionPiChildArgv(
|
|
@@ -453,6 +476,8 @@ export function buildFusionPiChildArgv(
|
|
|
453
476
|
|
|
454
477
|
const SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
|
|
455
478
|
const FUSION_CHILD_RESULT_PREFIX_BYTES = Buffer.from(FUSION_CHILD_RESULT_PREFIX, 'utf8');
|
|
479
|
+
const FUSION_CHILD_SETTLEMENT_PREFIX_BYTES = Buffer.from(FUSION_CHILD_SETTLEMENT_PREFIX, 'utf8');
|
|
480
|
+
const FUSION_RUNTIME_GUARD_PREFIX_BYTES = Buffer.from(FUSION_RUNTIME_GUARD_PREFIX, 'utf8');
|
|
456
481
|
const PI_EXTENSION_ERROR_PREFIX_BYTES = Buffer.from('Extension error (', 'utf8');
|
|
457
482
|
|
|
458
483
|
interface ParsedFusionChildStderr {
|
|
@@ -527,6 +552,16 @@ function requireUsageInteger(
|
|
|
527
552
|
return value;
|
|
528
553
|
}
|
|
529
554
|
|
|
555
|
+
function requirePositiveSafeInteger(
|
|
556
|
+
record: Record<PropertyKey, unknown>,
|
|
557
|
+
key: string,
|
|
558
|
+
label: string,
|
|
559
|
+
): number {
|
|
560
|
+
const value = requireUsageInteger(record, key, label);
|
|
561
|
+
if (value === 0) throw new Error(`${label}.${key} must be a positive safe integer`);
|
|
562
|
+
return value;
|
|
563
|
+
}
|
|
564
|
+
|
|
530
565
|
function requireCostNumber(
|
|
531
566
|
record: Record<PropertyKey, unknown>,
|
|
532
567
|
key: string,
|
|
@@ -565,10 +600,124 @@ function parseCompactUsage(value: unknown): FusionUsage {
|
|
|
565
600
|
};
|
|
566
601
|
}
|
|
567
602
|
|
|
603
|
+
const FUSION_CLAUDE_CACHE_RETENTIONS = new Set<FusionClaudeCacheRetention>([
|
|
604
|
+
'none',
|
|
605
|
+
'short',
|
|
606
|
+
'long',
|
|
607
|
+
]);
|
|
608
|
+
|
|
609
|
+
function parseFusionClaudeCacheObservation(
|
|
610
|
+
value: unknown,
|
|
611
|
+
provider: string,
|
|
612
|
+
): FusionClaudeCacheObservation {
|
|
613
|
+
const record = assertClosedRecord(
|
|
614
|
+
value,
|
|
615
|
+
[
|
|
616
|
+
'schema_version',
|
|
617
|
+
'applicability',
|
|
618
|
+
'source',
|
|
619
|
+
'requested_retention',
|
|
620
|
+
'effective_retention',
|
|
621
|
+
'breakpoint_count',
|
|
622
|
+
'request_ordinal',
|
|
623
|
+
],
|
|
624
|
+
'fusion child cache observation',
|
|
625
|
+
);
|
|
626
|
+
if (record['schema_version'] !== FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION) {
|
|
627
|
+
throw new Error('fusion child cache observation schema_version mismatch');
|
|
628
|
+
}
|
|
629
|
+
const applicability = record['applicability'];
|
|
630
|
+
const source = record['source'];
|
|
631
|
+
const requested = record['requested_retention'];
|
|
632
|
+
const effective = record['effective_retention'];
|
|
633
|
+
const breakpointCount = requireUsageInteger(
|
|
634
|
+
record,
|
|
635
|
+
'breakpoint_count',
|
|
636
|
+
'fusion child cache observation',
|
|
637
|
+
);
|
|
638
|
+
const requestOrdinal = requirePositiveSafeInteger(
|
|
639
|
+
record,
|
|
640
|
+
'request_ordinal',
|
|
641
|
+
'fusion child cache observation',
|
|
642
|
+
);
|
|
643
|
+
if (requestOrdinal > FUSION_CHILD_MAX_PROVIDER_REQUESTS) {
|
|
644
|
+
throw new Error('fusion child cache observation exceeds the provider request limit');
|
|
645
|
+
}
|
|
646
|
+
if (breakpointCount > FUSION_CLAUDE_CACHE_BREAKPOINT_LIMIT) {
|
|
647
|
+
throw new Error('fusion child cache observation exceeds the Anthropic breakpoint limit');
|
|
648
|
+
}
|
|
649
|
+
if (provider !== 'anthropic') {
|
|
650
|
+
if (
|
|
651
|
+
applicability !== 'not_applicable' ||
|
|
652
|
+
source !== 'not_applicable' ||
|
|
653
|
+
requested !== null ||
|
|
654
|
+
effective !== null ||
|
|
655
|
+
breakpointCount !== 0
|
|
656
|
+
) {
|
|
657
|
+
throw new Error('non-Anthropic fusion child has contradictory cache observation');
|
|
658
|
+
}
|
|
659
|
+
return {
|
|
660
|
+
schema_version: FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION,
|
|
661
|
+
applicability: 'not_applicable',
|
|
662
|
+
source: 'not_applicable',
|
|
663
|
+
requested_retention: null,
|
|
664
|
+
effective_retention: null,
|
|
665
|
+
breakpoint_count: 0,
|
|
666
|
+
request_ordinal: requestOrdinal,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
if (applicability !== 'anthropic') {
|
|
670
|
+
throw new Error('Anthropic fusion child cache observation is not applicable');
|
|
671
|
+
}
|
|
672
|
+
if (source !== 'default' && source !== FUSION_CLAUDE_CACHE_RETENTION_ENV) {
|
|
673
|
+
throw new Error('Anthropic fusion child cache observation source is invalid');
|
|
674
|
+
}
|
|
675
|
+
if (
|
|
676
|
+
typeof requested !== 'string' ||
|
|
677
|
+
!FUSION_CLAUDE_CACHE_RETENTIONS.has(requested as FusionClaudeCacheRetention) ||
|
|
678
|
+
typeof effective !== 'string' ||
|
|
679
|
+
!FUSION_CLAUDE_CACHE_RETENTIONS.has(effective as FusionClaudeCacheRetention)
|
|
680
|
+
) {
|
|
681
|
+
throw new Error('Anthropic fusion child cache retention is invalid');
|
|
682
|
+
}
|
|
683
|
+
const requestedRetention = requested as FusionClaudeCacheRetention;
|
|
684
|
+
const effectiveRetention = effective as FusionClaudeCacheRetention;
|
|
685
|
+
if (source === 'default' && requestedRetention !== 'long') {
|
|
686
|
+
throw new Error('default Anthropic cache policy did not request long retention');
|
|
687
|
+
}
|
|
688
|
+
if ((effectiveRetention === 'none') !== (breakpointCount === 0)) {
|
|
689
|
+
throw new Error('Anthropic fusion child cache retention/breakpoint evidence mismatch');
|
|
690
|
+
}
|
|
691
|
+
if (requestedRetention === 'none' && effectiveRetention !== 'none') {
|
|
692
|
+
throw new Error('disabled Anthropic cache policy reported active breakpoints');
|
|
693
|
+
}
|
|
694
|
+
if (effectiveRetention === 'long' && requestedRetention !== 'long') {
|
|
695
|
+
throw new Error('Anthropic long cache retention was not requested');
|
|
696
|
+
}
|
|
697
|
+
return {
|
|
698
|
+
schema_version: FUSION_CLAUDE_CACHE_OBSERVATION_SCHEMA_VERSION,
|
|
699
|
+
applicability: 'anthropic',
|
|
700
|
+
source,
|
|
701
|
+
requested_retention: requestedRetention,
|
|
702
|
+
effective_retention: effectiveRetention,
|
|
703
|
+
breakpoint_count: breakpointCount,
|
|
704
|
+
request_ordinal: requestOrdinal,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
|
|
568
708
|
function parseChildResultMetadata(value: unknown): FusionChildResultMetadata {
|
|
569
709
|
const record = assertClosedRecord(
|
|
570
710
|
value,
|
|
571
|
-
[
|
|
711
|
+
[
|
|
712
|
+
'schema_version',
|
|
713
|
+
'provider',
|
|
714
|
+
'model',
|
|
715
|
+
'stop_reason',
|
|
716
|
+
'text_blocks',
|
|
717
|
+
'text_sha256',
|
|
718
|
+
'usage',
|
|
719
|
+
'cache_observation',
|
|
720
|
+
],
|
|
572
721
|
'fusion child result',
|
|
573
722
|
);
|
|
574
723
|
if (record['schema_version'] !== FUSION_CHILD_RESULT_SCHEMA_VERSION)
|
|
@@ -585,18 +734,346 @@ function parseChildResultMetadata(value: unknown): FusionChildResultMetadata {
|
|
|
585
734
|
};
|
|
586
735
|
});
|
|
587
736
|
const usage = parseCompactUsage(record['usage']);
|
|
737
|
+
const provider = requireNonBlankString(record, 'provider', 'fusion child result');
|
|
588
738
|
return {
|
|
589
739
|
schema_version: FUSION_CHILD_RESULT_SCHEMA_VERSION,
|
|
590
|
-
provider
|
|
740
|
+
provider,
|
|
591
741
|
model: requireNonBlankString(record, 'model', 'fusion child result'),
|
|
592
742
|
stop_reason: requireNonBlankString(record, 'stop_reason', 'fusion child result'),
|
|
593
743
|
text_blocks: textBlocks,
|
|
594
744
|
text_sha256: requireSha256(record, 'text_sha256', 'fusion child result'),
|
|
595
745
|
usage,
|
|
746
|
+
cache_observation: parseFusionClaudeCacheObservation(record['cache_observation'], provider),
|
|
596
747
|
};
|
|
597
748
|
}
|
|
598
749
|
|
|
750
|
+
const FUSION_RUNTIME_GUARD_CODES = new Set<FusionRuntimeGuardCode>([
|
|
751
|
+
'provider_request_limit',
|
|
752
|
+
'provider_request_budget',
|
|
753
|
+
'provider_payload_invalid',
|
|
754
|
+
'claude_cache_policy',
|
|
755
|
+
'tool_call_limit',
|
|
756
|
+
]);
|
|
757
|
+
|
|
758
|
+
export function parseFusionRuntimeGuard(stderr: Buffer): FusionRuntimeGuardRecord | undefined {
|
|
759
|
+
const frames: FusionRuntimeGuardRecord[] = [];
|
|
760
|
+
let cursor = 0;
|
|
761
|
+
for (;;) {
|
|
762
|
+
const frameStart = stderr.indexOf(FUSION_RUNTIME_GUARD_PREFIX_BYTES, cursor);
|
|
763
|
+
if (frameStart < 0) break;
|
|
764
|
+
const payloadStart = frameStart + FUSION_RUNTIME_GUARD_PREFIX_BYTES.length;
|
|
765
|
+
const newline = stderr.indexOf(10, payloadStart);
|
|
766
|
+
if (newline < 0) throw new Error('fusion runtime guard frame is not newline-terminated');
|
|
767
|
+
const bytes = stderr.subarray(payloadStart, newline);
|
|
768
|
+
const text = bytes.toString('utf8');
|
|
769
|
+
if (!Buffer.from(text, 'utf8').equals(bytes)) {
|
|
770
|
+
throw new Error('fusion runtime guard frame is not valid UTF-8');
|
|
771
|
+
}
|
|
772
|
+
const record = assertClosedRecord(
|
|
773
|
+
parseJsonText(text),
|
|
774
|
+
[
|
|
775
|
+
'schema_version',
|
|
776
|
+
'code',
|
|
777
|
+
'provider',
|
|
778
|
+
'model',
|
|
779
|
+
'request_ordinal',
|
|
780
|
+
'tool_call_count',
|
|
781
|
+
'payload_bytes',
|
|
782
|
+
'payload_sha256',
|
|
783
|
+
'estimated_input_tokens',
|
|
784
|
+
'context_window_tokens',
|
|
785
|
+
'reserved_output_tokens',
|
|
786
|
+
'safety_reserve_tokens',
|
|
787
|
+
'allowed_input_tokens',
|
|
788
|
+
'message',
|
|
789
|
+
],
|
|
790
|
+
'fusion runtime guard',
|
|
791
|
+
);
|
|
792
|
+
if (record['schema_version'] !== FUSION_RUNTIME_GUARD_SCHEMA_VERSION) {
|
|
793
|
+
throw new Error('fusion runtime guard schema_version mismatch');
|
|
794
|
+
}
|
|
795
|
+
const code = requireNonBlankString(record, 'code', 'fusion runtime guard');
|
|
796
|
+
if (!FUSION_RUNTIME_GUARD_CODES.has(code as FusionRuntimeGuardCode)) {
|
|
797
|
+
throw new Error(`fusion runtime guard code is unsupported: ${code}`);
|
|
798
|
+
}
|
|
799
|
+
const frame: FusionRuntimeGuardRecord = {
|
|
800
|
+
schema_version: FUSION_RUNTIME_GUARD_SCHEMA_VERSION,
|
|
801
|
+
code: code as FusionRuntimeGuardCode,
|
|
802
|
+
provider: requireNonBlankString(record, 'provider', 'fusion runtime guard'),
|
|
803
|
+
model: requireNonBlankString(record, 'model', 'fusion runtime guard'),
|
|
804
|
+
request_ordinal: requirePositiveSafeInteger(
|
|
805
|
+
record,
|
|
806
|
+
'request_ordinal',
|
|
807
|
+
'fusion runtime guard',
|
|
808
|
+
),
|
|
809
|
+
tool_call_count: requireUsageInteger(record, 'tool_call_count', 'fusion runtime guard'),
|
|
810
|
+
payload_bytes: requireUsageInteger(record, 'payload_bytes', 'fusion runtime guard'),
|
|
811
|
+
payload_sha256: requireSha256(record, 'payload_sha256', 'fusion runtime guard'),
|
|
812
|
+
estimated_input_tokens: requireUsageInteger(
|
|
813
|
+
record,
|
|
814
|
+
'estimated_input_tokens',
|
|
815
|
+
'fusion runtime guard',
|
|
816
|
+
),
|
|
817
|
+
context_window_tokens: requireUsageInteger(
|
|
818
|
+
record,
|
|
819
|
+
'context_window_tokens',
|
|
820
|
+
'fusion runtime guard',
|
|
821
|
+
),
|
|
822
|
+
reserved_output_tokens: requirePositiveSafeInteger(
|
|
823
|
+
record,
|
|
824
|
+
'reserved_output_tokens',
|
|
825
|
+
'fusion runtime guard',
|
|
826
|
+
),
|
|
827
|
+
safety_reserve_tokens: requirePositiveSafeInteger(
|
|
828
|
+
record,
|
|
829
|
+
'safety_reserve_tokens',
|
|
830
|
+
'fusion runtime guard',
|
|
831
|
+
),
|
|
832
|
+
allowed_input_tokens: requireUsageInteger(
|
|
833
|
+
record,
|
|
834
|
+
'allowed_input_tokens',
|
|
835
|
+
'fusion runtime guard',
|
|
836
|
+
),
|
|
837
|
+
message: requireNonBlankString(record, 'message', 'fusion runtime guard'),
|
|
838
|
+
};
|
|
839
|
+
if (frame.code !== 'provider_payload_invalid') {
|
|
840
|
+
if (frame.context_window_tokens === 0) {
|
|
841
|
+
throw new Error('fusion runtime guard.context_window_tokens must be positive');
|
|
842
|
+
}
|
|
843
|
+
const expectedAllowed =
|
|
844
|
+
frame.context_window_tokens - frame.reserved_output_tokens - frame.safety_reserve_tokens;
|
|
845
|
+
if (expectedAllowed < 0 || frame.allowed_input_tokens !== expectedAllowed) {
|
|
846
|
+
throw new Error('fusion runtime guard allowed-input arithmetic mismatch');
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (frame.code === 'claude_cache_policy') {
|
|
850
|
+
if (
|
|
851
|
+
frame.payload_bytes !== 0 ||
|
|
852
|
+
frame.payload_sha256 !== createHash('sha256').update(Buffer.alloc(0)).digest('hex') ||
|
|
853
|
+
frame.estimated_input_tokens !== 0
|
|
854
|
+
) {
|
|
855
|
+
throw new Error('fusion runtime guard Claude cache policy payload evidence mismatch');
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (
|
|
859
|
+
frame.code === 'provider_request_budget' &&
|
|
860
|
+
frame.estimated_input_tokens <= frame.allowed_input_tokens
|
|
861
|
+
) {
|
|
862
|
+
throw new Error('fusion runtime guard provider budget code has no token overage');
|
|
863
|
+
}
|
|
864
|
+
if (
|
|
865
|
+
frame.code === 'provider_request_limit' &&
|
|
866
|
+
frame.request_ordinal <= FUSION_CHILD_MAX_PROVIDER_REQUESTS
|
|
867
|
+
) {
|
|
868
|
+
throw new Error('fusion runtime guard provider request limit was not exceeded');
|
|
869
|
+
}
|
|
870
|
+
if (frame.code === 'tool_call_limit') {
|
|
871
|
+
if (frame.tool_call_count <= FUSION_CHILD_MAX_TOOL_CALLS) {
|
|
872
|
+
throw new Error('fusion runtime guard tool call limit was not exceeded');
|
|
873
|
+
}
|
|
874
|
+
if (
|
|
875
|
+
frame.payload_bytes !== 0 ||
|
|
876
|
+
frame.payload_sha256 !== createHash('sha256').update(Buffer.alloc(0)).digest('hex') ||
|
|
877
|
+
frame.estimated_input_tokens !== 0
|
|
878
|
+
) {
|
|
879
|
+
throw new Error('fusion runtime guard tool call limit payload evidence mismatch');
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
frames.push(frame);
|
|
883
|
+
cursor = newline + 1;
|
|
884
|
+
}
|
|
885
|
+
if (frames.length > 1) throw new Error('fusion child emitted multiple runtime guard frames');
|
|
886
|
+
return frames[0];
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const FUSION_CHILD_SETTLEMENT_FAILURE_REASONS = new Set<FusionChildSettlementFailureReason>([
|
|
890
|
+
'no_records',
|
|
891
|
+
'final_not_stop',
|
|
892
|
+
'invalid_non_final',
|
|
893
|
+
'runtime_guard',
|
|
894
|
+
'cache_observation',
|
|
895
|
+
]);
|
|
896
|
+
|
|
897
|
+
export function parseFusionChildSettlement(
|
|
898
|
+
stderr: Buffer,
|
|
899
|
+
): FusionChildSettlementRecord | undefined {
|
|
900
|
+
const frames: FusionChildSettlementRecord[] = [];
|
|
901
|
+
let cursor = 0;
|
|
902
|
+
for (;;) {
|
|
903
|
+
const frameStart = stderr.indexOf(FUSION_CHILD_SETTLEMENT_PREFIX_BYTES, cursor);
|
|
904
|
+
if (frameStart < 0) break;
|
|
905
|
+
const payloadStart = frameStart + FUSION_CHILD_SETTLEMENT_PREFIX_BYTES.length;
|
|
906
|
+
const newline = stderr.indexOf(10, payloadStart);
|
|
907
|
+
if (newline < 0) throw new Error('fusion child settlement frame is not newline-terminated');
|
|
908
|
+
const bytes = stderr.subarray(payloadStart, newline);
|
|
909
|
+
const text = bytes.toString('utf8');
|
|
910
|
+
if (!Buffer.from(text, 'utf8').equals(bytes)) {
|
|
911
|
+
throw new Error('fusion child settlement frame is not valid UTF-8');
|
|
912
|
+
}
|
|
913
|
+
const record = assertClosedRecord(
|
|
914
|
+
parseJsonText(text),
|
|
915
|
+
[
|
|
916
|
+
'schema_version',
|
|
917
|
+
'status',
|
|
918
|
+
'record_count',
|
|
919
|
+
'records_sha256',
|
|
920
|
+
'final_record_index',
|
|
921
|
+
'final_text_sha256',
|
|
922
|
+
'recovered_error_ordinals',
|
|
923
|
+
'failure_reason',
|
|
924
|
+
],
|
|
925
|
+
'fusion child settlement',
|
|
926
|
+
);
|
|
927
|
+
if (record['schema_version'] !== FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION) {
|
|
928
|
+
throw new Error('fusion child settlement schema_version mismatch');
|
|
929
|
+
}
|
|
930
|
+
const status = record['status'];
|
|
931
|
+
if (status !== 'complete' && status !== 'failed') {
|
|
932
|
+
throw new Error('fusion child settlement.status is invalid');
|
|
933
|
+
}
|
|
934
|
+
const recordCount = requireUsageInteger(record, 'record_count', 'fusion child settlement');
|
|
935
|
+
const finalIndexValue = record['final_record_index'];
|
|
936
|
+
const finalRecordIndex =
|
|
937
|
+
finalIndexValue === null
|
|
938
|
+
? null
|
|
939
|
+
: requireUsageInteger(record, 'final_record_index', 'fusion child settlement');
|
|
940
|
+
const finalHashValue = record['final_text_sha256'];
|
|
941
|
+
const finalTextSha256 =
|
|
942
|
+
finalHashValue === null
|
|
943
|
+
? null
|
|
944
|
+
: requireSha256(record, 'final_text_sha256', 'fusion child settlement');
|
|
945
|
+
const recoveredValue = record['recovered_error_ordinals'];
|
|
946
|
+
if (!Array.isArray(recoveredValue)) {
|
|
947
|
+
throw new Error('fusion child settlement.recovered_error_ordinals must be an array');
|
|
948
|
+
}
|
|
949
|
+
const recoveredErrorOrdinals = recoveredValue.map((value, index) => {
|
|
950
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
|
|
951
|
+
throw new Error(
|
|
952
|
+
`fusion child settlement.recovered_error_ordinals[${String(index)}] must be a non-negative safe integer`,
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
return value;
|
|
956
|
+
});
|
|
957
|
+
for (let index = 0; index < recoveredErrorOrdinals.length; index += 1) {
|
|
958
|
+
const ordinal = recoveredErrorOrdinals[index];
|
|
959
|
+
if (
|
|
960
|
+
ordinal === undefined ||
|
|
961
|
+
ordinal >= recordCount - 1 ||
|
|
962
|
+
(index > 0 && ordinal <= (recoveredErrorOrdinals[index - 1] ?? -1))
|
|
963
|
+
) {
|
|
964
|
+
throw new Error('fusion child settlement recovered-error ordinals are not canonical');
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
const failureValue = record['failure_reason'];
|
|
968
|
+
let failureReason: FusionChildSettlementFailureReason | null;
|
|
969
|
+
if (failureValue === null) failureReason = null;
|
|
970
|
+
else if (
|
|
971
|
+
typeof failureValue === 'string' &&
|
|
972
|
+
FUSION_CHILD_SETTLEMENT_FAILURE_REASONS.has(
|
|
973
|
+
failureValue as FusionChildSettlementFailureReason,
|
|
974
|
+
)
|
|
975
|
+
) {
|
|
976
|
+
failureReason = failureValue as FusionChildSettlementFailureReason;
|
|
977
|
+
} else {
|
|
978
|
+
throw new Error('fusion child settlement.failure_reason is invalid');
|
|
979
|
+
}
|
|
980
|
+
if ((status === 'complete') !== (failureReason === null)) {
|
|
981
|
+
throw new Error('fusion child settlement status/failure_reason mismatch');
|
|
982
|
+
}
|
|
983
|
+
if (recordCount === 0) {
|
|
984
|
+
if (finalRecordIndex !== null || finalTextSha256 !== null) {
|
|
985
|
+
throw new Error('fusion child settlement empty stream has final-record evidence');
|
|
986
|
+
}
|
|
987
|
+
} else if (finalRecordIndex !== recordCount - 1 || finalTextSha256 === null) {
|
|
988
|
+
throw new Error('fusion child settlement final-record evidence mismatch');
|
|
989
|
+
}
|
|
990
|
+
frames.push({
|
|
991
|
+
schema_version: FUSION_CHILD_SETTLEMENT_SCHEMA_VERSION,
|
|
992
|
+
status,
|
|
993
|
+
record_count: recordCount,
|
|
994
|
+
records_sha256: requireSha256(record, 'records_sha256', 'fusion child settlement'),
|
|
995
|
+
final_record_index: finalRecordIndex,
|
|
996
|
+
final_text_sha256: finalTextSha256,
|
|
997
|
+
recovered_error_ordinals: recoveredErrorOrdinals,
|
|
998
|
+
failure_reason: failureReason,
|
|
999
|
+
});
|
|
1000
|
+
cursor = newline + 1;
|
|
1001
|
+
}
|
|
1002
|
+
if (frames.length > 1) throw new Error('fusion child emitted multiple settlement frames');
|
|
1003
|
+
return frames[0];
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function assertFusionChildSettlementOrdering(stderr: Buffer): void {
|
|
1007
|
+
const settlementStart = stderr.indexOf(FUSION_CHILD_SETTLEMENT_PREFIX_BYTES);
|
|
1008
|
+
if (settlementStart < 0) return;
|
|
1009
|
+
if (stderr.indexOf(FUSION_CHILD_RESULT_PREFIX_BYTES, settlementStart) >= 0) {
|
|
1010
|
+
throw new Error('fusion child emitted result metadata after terminal settlement');
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function stripChildControlFrames(stderr: Buffer): Buffer {
|
|
1015
|
+
const prefixes = [FUSION_CHILD_SETTLEMENT_PREFIX_BYTES, FUSION_RUNTIME_GUARD_PREFIX_BYTES];
|
|
1016
|
+
const diagnostics: Buffer[] = [];
|
|
1017
|
+
let cursor = 0;
|
|
1018
|
+
for (;;) {
|
|
1019
|
+
let next = -1;
|
|
1020
|
+
let prefix: Buffer | undefined;
|
|
1021
|
+
for (const candidate of prefixes) {
|
|
1022
|
+
const found = stderr.indexOf(candidate, cursor);
|
|
1023
|
+
if (found >= 0 && (next < 0 || found < next)) {
|
|
1024
|
+
next = found;
|
|
1025
|
+
prefix = candidate;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
if (next < 0 || prefix === undefined) {
|
|
1029
|
+
if (cursor < stderr.length) diagnostics.push(stderr.subarray(cursor));
|
|
1030
|
+
break;
|
|
1031
|
+
}
|
|
1032
|
+
if (next > cursor) diagnostics.push(stderr.subarray(cursor, next));
|
|
1033
|
+
const newline = stderr.indexOf(10, next + prefix.length);
|
|
1034
|
+
if (newline < 0) throw new Error('fusion child control frame is not newline-terminated');
|
|
1035
|
+
cursor = newline + 1;
|
|
1036
|
+
}
|
|
1037
|
+
return Buffer.concat(diagnostics);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
export function assertFusionRuntimeGuardMatchesModel(
|
|
1041
|
+
guard: FusionRuntimeGuardRecord,
|
|
1042
|
+
model: ResolvedFusionModel,
|
|
1043
|
+
): void {
|
|
1044
|
+
const routeUnknown = guard.provider === 'unknown' && guard.model === 'unknown';
|
|
1045
|
+
if (!routeUnknown && (guard.provider !== model.provider || guard.model !== model.model)) {
|
|
1046
|
+
throw new Error(
|
|
1047
|
+
`fusion runtime guard route mismatch: expected ${model.qualifiedId}, observed ${guard.provider}/${guard.model}`,
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
if (routeUnknown && guard.code !== 'provider_payload_invalid') {
|
|
1051
|
+
throw new Error('fusion runtime guard omitted the route for a capacity-backed refusal');
|
|
1052
|
+
}
|
|
1053
|
+
if (guard.code === 'provider_payload_invalid') return;
|
|
1054
|
+
const expectedReservedOutput = Math.max(
|
|
1055
|
+
FUSION_CHILD_MIN_OUTPUT_RESERVE_TOKENS,
|
|
1056
|
+
model.maxOutputTokens,
|
|
1057
|
+
);
|
|
1058
|
+
const expectedAllowedInput =
|
|
1059
|
+
model.contextWindow - expectedReservedOutput - FUSION_CHILD_SAFETY_RESERVE_TOKENS;
|
|
1060
|
+
if (
|
|
1061
|
+
guard.context_window_tokens !== model.contextWindow ||
|
|
1062
|
+
guard.reserved_output_tokens !== expectedReservedOutput ||
|
|
1063
|
+
guard.safety_reserve_tokens !== FUSION_CHILD_SAFETY_RESERVE_TOKENS ||
|
|
1064
|
+
guard.allowed_input_tokens !== expectedAllowedInput
|
|
1065
|
+
) {
|
|
1066
|
+
throw new Error('fusion runtime guard capacity evidence does not match the resolved route');
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
599
1070
|
export function parseFusionChildStderr(stderr: Buffer): ParsedFusionChildStderr {
|
|
1071
|
+
// Validate every package-owned side frame even though result metadata is parsed
|
|
1072
|
+
// independently below. Malformed refusal/settlement evidence must never degrade
|
|
1073
|
+
// into opaque diagnostics.
|
|
1074
|
+
parseFusionRuntimeGuard(stderr);
|
|
1075
|
+
const settlement = parseFusionChildSettlement(stderr);
|
|
1076
|
+
assertFusionChildSettlementOrdering(stderr);
|
|
600
1077
|
const records: FusionChildResultMetadata[] = [];
|
|
601
1078
|
const diagnostics: Buffer[] = [];
|
|
602
1079
|
let cursor = 0;
|
|
@@ -625,11 +1102,16 @@ export function parseFusionChildStderr(stderr: Buffer): ParsedFusionChildStderr
|
|
|
625
1102
|
records.push(parseChildResultMetadata(parsed));
|
|
626
1103
|
cursor = newline + 1;
|
|
627
1104
|
}
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1105
|
+
const resultEvents = serializeFusionChildResultRecords(records);
|
|
1106
|
+
const events =
|
|
1107
|
+
settlement === undefined
|
|
1108
|
+
? resultEvents
|
|
1109
|
+
: Buffer.concat([resultEvents, Buffer.from(`${JSON.stringify(settlement)}\n`, 'utf8')]);
|
|
1110
|
+
return {
|
|
1111
|
+
records,
|
|
1112
|
+
events,
|
|
1113
|
+
diagnostics: stripChildControlFrames(Buffer.concat(diagnostics)),
|
|
1114
|
+
};
|
|
633
1115
|
}
|
|
634
1116
|
|
|
635
1117
|
function parseToolCallLogRecord(value: unknown, label: string): FusionToolCallLogRecord {
|
|
@@ -958,6 +1440,8 @@ export class FusionPiCompactResultParser {
|
|
|
958
1440
|
): {
|
|
959
1441
|
text: string;
|
|
960
1442
|
usage: FusionUsage;
|
|
1443
|
+
firstRequestUsage: FusionUsage;
|
|
1444
|
+
providerRequestCount: number;
|
|
961
1445
|
provider: string;
|
|
962
1446
|
model: string;
|
|
963
1447
|
qualifiedId: string;
|
|
@@ -965,17 +1449,35 @@ export class FusionPiCompactResultParser {
|
|
|
965
1449
|
diagnostics: Buffer;
|
|
966
1450
|
} {
|
|
967
1451
|
const parsed = parseFusionChildStderr(stderr);
|
|
1452
|
+
const runtimeGuard = parseFusionRuntimeGuard(stderr);
|
|
1453
|
+
if (runtimeGuard !== undefined) {
|
|
1454
|
+
throw new Error(`Pi child runtime guard refused the run: ${runtimeGuard.message}`);
|
|
1455
|
+
}
|
|
1456
|
+
const settlement = parseFusionChildSettlement(stderr);
|
|
1457
|
+
if (settlement === undefined) throw new Error('Pi child emitted no terminal result settlement');
|
|
1458
|
+
const expectedSettlement = buildFusionChildSettlement(parsed.records);
|
|
1459
|
+
if (JSON.stringify(settlement) !== JSON.stringify(expectedSettlement)) {
|
|
1460
|
+
throw new Error('Pi child terminal result settlement does not match the metadata stream');
|
|
1461
|
+
}
|
|
968
1462
|
if (parsed.diagnostics.includes(PI_EXTENSION_ERROR_PREFIX_BYTES)) {
|
|
969
1463
|
throw new Error('Pi child reported an extension error diagnostic');
|
|
970
1464
|
}
|
|
971
1465
|
const final = parsed.records.at(-1);
|
|
972
1466
|
if (final === undefined) throw new Error('Pi child emitted no compact result metadata');
|
|
973
1467
|
for (const record of parsed.records) this.assertModel(record);
|
|
1468
|
+
this.assertCacheObservationOrdinals(parsed.records);
|
|
974
1469
|
this.assertTranscriptStopReasons(parsed.records);
|
|
1470
|
+
if (settlement.status !== 'complete') {
|
|
1471
|
+
throw new Error(
|
|
1472
|
+
`Pi child terminal result settlement failed: ${settlement.failure_reason ?? 'unknown'}`,
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
975
1475
|
const observed = this.observedFromRecords(parsed.records);
|
|
976
1476
|
return {
|
|
977
1477
|
text: reconstructFinalText(response, final),
|
|
978
1478
|
usage: observed.usage,
|
|
1479
|
+
firstRequestUsage: cloneFusionUsage(parsed.records[0]?.usage ?? createEmptyFusionUsage()),
|
|
1480
|
+
providerRequestCount: parsed.records.length,
|
|
979
1481
|
provider: final.provider,
|
|
980
1482
|
model: final.model,
|
|
981
1483
|
qualifiedId: `${final.provider}/${final.model}`,
|
|
@@ -992,6 +1494,19 @@ export class FusionPiCompactResultParser {
|
|
|
992
1494
|
}
|
|
993
1495
|
}
|
|
994
1496
|
|
|
1497
|
+
private assertCacheObservationOrdinals(records: readonly FusionChildResultMetadata[]): void {
|
|
1498
|
+
let priorOrdinal = 0;
|
|
1499
|
+
for (const [index, record] of records.entries()) {
|
|
1500
|
+
const ordinal = record.cache_observation.request_ordinal;
|
|
1501
|
+
if (ordinal <= priorOrdinal) {
|
|
1502
|
+
throw new Error(
|
|
1503
|
+
`Pi child cache observation ordinal is not increasing at result ${String(index)}: previous ${String(priorOrdinal)}, observed ${String(ordinal)}`,
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
priorOrdinal = ordinal;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
|
|
995
1510
|
private assertTranscriptStopReasons(records: readonly FusionChildResultMetadata[]): void {
|
|
996
1511
|
for (const [index, record] of records.entries()) {
|
|
997
1512
|
const isFinal = index === records.length - 1;
|
|
@@ -999,9 +1514,14 @@ export class FusionPiCompactResultParser {
|
|
|
999
1514
|
if (record.stop_reason !== 'stop') {
|
|
1000
1515
|
throw new Error(this.stopReasonError('final', 'stop', record.stop_reason, true));
|
|
1001
1516
|
}
|
|
1002
|
-
} else if (record.stop_reason !== 'toolUse') {
|
|
1517
|
+
} else if (record.stop_reason !== 'toolUse' && !isRecoverableFusionChildErrorRecord(record)) {
|
|
1003
1518
|
throw new Error(
|
|
1004
|
-
this.stopReasonError(
|
|
1519
|
+
this.stopReasonError(
|
|
1520
|
+
`non-final record ${index}`,
|
|
1521
|
+
'toolUse or a settled zero-usage retry marker',
|
|
1522
|
+
record.stop_reason,
|
|
1523
|
+
true,
|
|
1524
|
+
),
|
|
1005
1525
|
);
|
|
1006
1526
|
}
|
|
1007
1527
|
}
|
|
@@ -1495,11 +2015,39 @@ export async function runPiChild(options: RunPiChildOptions): Promise<FusionChil
|
|
|
1495
2015
|
observed,
|
|
1496
2016
|
);
|
|
1497
2017
|
if (close.code !== 0 || close.signal !== null) {
|
|
2018
|
+
let runtimeGuard: FusionRuntimeGuardRecord | undefined;
|
|
2019
|
+
try {
|
|
2020
|
+
runtimeGuard = parseFusionRuntimeGuard(rawStderr);
|
|
2021
|
+
if (runtimeGuard !== undefined) {
|
|
2022
|
+
assertFusionRuntimeGuardMatchesModel(runtimeGuard, options.model);
|
|
2023
|
+
}
|
|
2024
|
+
} catch (error) {
|
|
2025
|
+
throw new FusionChildRunError(
|
|
2026
|
+
withCleanupErrors(
|
|
2027
|
+
childError(
|
|
2028
|
+
`Pi child runtime guard evidence invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
2029
|
+
'child_event_invalid',
|
|
2030
|
+
options,
|
|
2031
|
+
),
|
|
2032
|
+
state.cleanupErrors,
|
|
2033
|
+
),
|
|
2034
|
+
compactEvents,
|
|
2035
|
+
response,
|
|
2036
|
+
diagnostics,
|
|
2037
|
+
close,
|
|
2038
|
+
observed,
|
|
2039
|
+
);
|
|
2040
|
+
}
|
|
1498
2041
|
throw new FusionChildRunError(
|
|
1499
2042
|
withCleanupErrors(
|
|
1500
2043
|
childError(
|
|
1501
|
-
|
|
1502
|
-
|
|
2044
|
+
runtimeGuard?.message ??
|
|
2045
|
+
`Pi child exited with code ${close.code === null ? 'null' : String(close.code)}${close.signal === null ? '' : ` (${close.signal})`}`,
|
|
2046
|
+
runtimeGuard === undefined
|
|
2047
|
+
? 'child_exit_failed'
|
|
2048
|
+
: runtimeGuard.code === 'claude_cache_policy'
|
|
2049
|
+
? 'child_cache_policy_invalid'
|
|
2050
|
+
: 'child_runtime_budget_exceeded',
|
|
1503
2051
|
options,
|
|
1504
2052
|
),
|
|
1505
2053
|
state.cleanupErrors,
|
|
@@ -1575,6 +2123,8 @@ export async function runPiChild(options: RunPiChildOptions): Promise<FusionChil
|
|
|
1575
2123
|
qualifiedId: parsed.qualifiedId,
|
|
1576
2124
|
text: parsed.text,
|
|
1577
2125
|
usage: parsed.usage,
|
|
2126
|
+
firstRequestUsage: parsed.firstRequestUsage,
|
|
2127
|
+
providerRequestCount: parsed.providerRequestCount,
|
|
1578
2128
|
events: parsed.events,
|
|
1579
2129
|
stderr: parsed.diagnostics,
|
|
1580
2130
|
exitCode: close.code,
|