pi-background-tasks 1.0.4 → 1.0.6
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 +44 -40
- package/docs/subsystems/docs-freshness-gate.md +5 -5
- package/docs/subsystems/fusion.md +17 -12
- package/package.json +2 -1
- package/src/core/fusion/budget.ts +11 -4
- 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/pi-child.ts +558 -14
- package/src/core/fusion/types.ts +29 -7
- 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 {
|
|
@@ -965,13 +1447,29 @@ export class FusionPiCompactResultParser {
|
|
|
965
1447
|
diagnostics: Buffer;
|
|
966
1448
|
} {
|
|
967
1449
|
const parsed = parseFusionChildStderr(stderr);
|
|
1450
|
+
const runtimeGuard = parseFusionRuntimeGuard(stderr);
|
|
1451
|
+
if (runtimeGuard !== undefined) {
|
|
1452
|
+
throw new Error(`Pi child runtime guard refused the run: ${runtimeGuard.message}`);
|
|
1453
|
+
}
|
|
1454
|
+
const settlement = parseFusionChildSettlement(stderr);
|
|
1455
|
+
if (settlement === undefined) throw new Error('Pi child emitted no terminal result settlement');
|
|
1456
|
+
const expectedSettlement = buildFusionChildSettlement(parsed.records);
|
|
1457
|
+
if (JSON.stringify(settlement) !== JSON.stringify(expectedSettlement)) {
|
|
1458
|
+
throw new Error('Pi child terminal result settlement does not match the metadata stream');
|
|
1459
|
+
}
|
|
968
1460
|
if (parsed.diagnostics.includes(PI_EXTENSION_ERROR_PREFIX_BYTES)) {
|
|
969
1461
|
throw new Error('Pi child reported an extension error diagnostic');
|
|
970
1462
|
}
|
|
971
1463
|
const final = parsed.records.at(-1);
|
|
972
1464
|
if (final === undefined) throw new Error('Pi child emitted no compact result metadata');
|
|
973
1465
|
for (const record of parsed.records) this.assertModel(record);
|
|
1466
|
+
this.assertCacheObservationOrdinals(parsed.records);
|
|
974
1467
|
this.assertTranscriptStopReasons(parsed.records);
|
|
1468
|
+
if (settlement.status !== 'complete') {
|
|
1469
|
+
throw new Error(
|
|
1470
|
+
`Pi child terminal result settlement failed: ${settlement.failure_reason ?? 'unknown'}`,
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
975
1473
|
const observed = this.observedFromRecords(parsed.records);
|
|
976
1474
|
return {
|
|
977
1475
|
text: reconstructFinalText(response, final),
|
|
@@ -992,6 +1490,19 @@ export class FusionPiCompactResultParser {
|
|
|
992
1490
|
}
|
|
993
1491
|
}
|
|
994
1492
|
|
|
1493
|
+
private assertCacheObservationOrdinals(records: readonly FusionChildResultMetadata[]): void {
|
|
1494
|
+
let priorOrdinal = 0;
|
|
1495
|
+
for (const [index, record] of records.entries()) {
|
|
1496
|
+
const ordinal = record.cache_observation.request_ordinal;
|
|
1497
|
+
if (ordinal <= priorOrdinal) {
|
|
1498
|
+
throw new Error(
|
|
1499
|
+
`Pi child cache observation ordinal is not increasing at result ${String(index)}: previous ${String(priorOrdinal)}, observed ${String(ordinal)}`,
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
priorOrdinal = ordinal;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
995
1506
|
private assertTranscriptStopReasons(records: readonly FusionChildResultMetadata[]): void {
|
|
996
1507
|
for (const [index, record] of records.entries()) {
|
|
997
1508
|
const isFinal = index === records.length - 1;
|
|
@@ -999,9 +1510,14 @@ export class FusionPiCompactResultParser {
|
|
|
999
1510
|
if (record.stop_reason !== 'stop') {
|
|
1000
1511
|
throw new Error(this.stopReasonError('final', 'stop', record.stop_reason, true));
|
|
1001
1512
|
}
|
|
1002
|
-
} else if (record.stop_reason !== 'toolUse') {
|
|
1513
|
+
} else if (record.stop_reason !== 'toolUse' && !isRecoverableFusionChildErrorRecord(record)) {
|
|
1003
1514
|
throw new Error(
|
|
1004
|
-
this.stopReasonError(
|
|
1515
|
+
this.stopReasonError(
|
|
1516
|
+
`non-final record ${index}`,
|
|
1517
|
+
'toolUse or a settled zero-usage retry marker',
|
|
1518
|
+
record.stop_reason,
|
|
1519
|
+
true,
|
|
1520
|
+
),
|
|
1005
1521
|
);
|
|
1006
1522
|
}
|
|
1007
1523
|
}
|
|
@@ -1495,11 +2011,39 @@ export async function runPiChild(options: RunPiChildOptions): Promise<FusionChil
|
|
|
1495
2011
|
observed,
|
|
1496
2012
|
);
|
|
1497
2013
|
if (close.code !== 0 || close.signal !== null) {
|
|
2014
|
+
let runtimeGuard: FusionRuntimeGuardRecord | undefined;
|
|
2015
|
+
try {
|
|
2016
|
+
runtimeGuard = parseFusionRuntimeGuard(rawStderr);
|
|
2017
|
+
if (runtimeGuard !== undefined) {
|
|
2018
|
+
assertFusionRuntimeGuardMatchesModel(runtimeGuard, options.model);
|
|
2019
|
+
}
|
|
2020
|
+
} catch (error) {
|
|
2021
|
+
throw new FusionChildRunError(
|
|
2022
|
+
withCleanupErrors(
|
|
2023
|
+
childError(
|
|
2024
|
+
`Pi child runtime guard evidence invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
2025
|
+
'child_event_invalid',
|
|
2026
|
+
options,
|
|
2027
|
+
),
|
|
2028
|
+
state.cleanupErrors,
|
|
2029
|
+
),
|
|
2030
|
+
compactEvents,
|
|
2031
|
+
response,
|
|
2032
|
+
diagnostics,
|
|
2033
|
+
close,
|
|
2034
|
+
observed,
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
1498
2037
|
throw new FusionChildRunError(
|
|
1499
2038
|
withCleanupErrors(
|
|
1500
2039
|
childError(
|
|
1501
|
-
|
|
1502
|
-
|
|
2040
|
+
runtimeGuard?.message ??
|
|
2041
|
+
`Pi child exited with code ${close.code === null ? 'null' : String(close.code)}${close.signal === null ? '' : ` (${close.signal})`}`,
|
|
2042
|
+
runtimeGuard === undefined
|
|
2043
|
+
? 'child_exit_failed'
|
|
2044
|
+
: runtimeGuard.code === 'claude_cache_policy'
|
|
2045
|
+
? 'child_cache_policy_invalid'
|
|
2046
|
+
: 'child_runtime_budget_exceeded',
|
|
1503
2047
|
options,
|
|
1504
2048
|
),
|
|
1505
2049
|
state.cleanupErrors,
|