pi-background-tasks 1.0.3 → 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.
@@ -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,
@@ -64,7 +86,8 @@ export const FUSION_CHILD_TIMEOUT_MS = 30 * 60 * 1000;
64
86
  export const FUSION_CHILD_IDLE_TIMEOUT_MS = 20 * 60 * 1000;
65
87
  export const FUSION_CHILD_KILL_GRACE_MS = 3000;
66
88
  export const FUSION_CHILD_SIGKILL_WAIT_MS = 5000;
67
- const FUSION_PI_CHILD_O_NOFOLLOW = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
89
+ const FUSION_PI_CHILD_O_NOFOLLOW =
90
+ typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
68
91
 
69
92
  export const FUSION_CHILD_REMOVED_ENV_KEYS = [
70
93
  'PI_SESSION_ID',
@@ -404,8 +427,9 @@ function fusionToolArgv(capability: FusionCapability): string[] {
404
427
  *
405
428
  * `--no-extensions` disables discovery but still honours explicit `--extension`
406
429
  * paths, so this list is the complete set a child receives. The metadata
407
- * extension is always present; the Anthropic sanitizer is appended only for
408
- * Claude routes, keeping non-Anthropic child argv byte-identical to before.
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.
409
433
  */
410
434
  export function fusionChildExtensionPaths(
411
435
  model: ResolvedFusionModel,
@@ -413,7 +437,7 @@ export function fusionChildExtensionPaths(
413
437
  resolveSanitizer: () => string = resolveAnthropicSanitizerExtensionPath,
414
438
  ): readonly string[] {
415
439
  if (model.provider !== FUSION_SANITIZED_PROVIDER) return [childExtensionPath];
416
- return [childExtensionPath, resolveSanitizer()];
440
+ return [resolveSanitizer(), childExtensionPath];
417
441
  }
418
442
 
419
443
  export function buildFusionPiChildArgv(
@@ -452,6 +476,9 @@ export function buildFusionPiChildArgv(
452
476
 
453
477
  const SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
454
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');
481
+ const PI_EXTENSION_ERROR_PREFIX_BYTES = Buffer.from('Extension error (', 'utf8');
455
482
 
456
483
  interface ParsedFusionChildStderr {
457
484
  records: FusionChildResultMetadata[];
@@ -525,6 +552,16 @@ function requireUsageInteger(
525
552
  return value;
526
553
  }
527
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
+
528
565
  function requireCostNumber(
529
566
  record: Record<PropertyKey, unknown>,
530
567
  key: string,
@@ -563,10 +600,124 @@ function parseCompactUsage(value: unknown): FusionUsage {
563
600
  };
564
601
  }
565
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
+
566
708
  function parseChildResultMetadata(value: unknown): FusionChildResultMetadata {
567
709
  const record = assertClosedRecord(
568
710
  value,
569
- ['schema_version', 'provider', 'model', 'stop_reason', 'text_blocks', 'text_sha256', 'usage'],
711
+ [
712
+ 'schema_version',
713
+ 'provider',
714
+ 'model',
715
+ 'stop_reason',
716
+ 'text_blocks',
717
+ 'text_sha256',
718
+ 'usage',
719
+ 'cache_observation',
720
+ ],
570
721
  'fusion child result',
571
722
  );
572
723
  if (record['schema_version'] !== FUSION_CHILD_RESULT_SCHEMA_VERSION)
@@ -583,18 +734,346 @@ function parseChildResultMetadata(value: unknown): FusionChildResultMetadata {
583
734
  };
584
735
  });
585
736
  const usage = parseCompactUsage(record['usage']);
737
+ const provider = requireNonBlankString(record, 'provider', 'fusion child result');
586
738
  return {
587
739
  schema_version: FUSION_CHILD_RESULT_SCHEMA_VERSION,
588
- provider: requireNonBlankString(record, 'provider', 'fusion child result'),
740
+ provider,
589
741
  model: requireNonBlankString(record, 'model', 'fusion child result'),
590
742
  stop_reason: requireNonBlankString(record, 'stop_reason', 'fusion child result'),
591
743
  text_blocks: textBlocks,
592
744
  text_sha256: requireSha256(record, 'text_sha256', 'fusion child result'),
593
745
  usage,
746
+ cache_observation: parseFusionClaudeCacheObservation(record['cache_observation'], provider),
594
747
  };
595
748
  }
596
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
+
597
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);
598
1077
  const records: FusionChildResultMetadata[] = [];
599
1078
  const diagnostics: Buffer[] = [];
600
1079
  let cursor = 0;
@@ -623,11 +1102,16 @@ export function parseFusionChildStderr(stderr: Buffer): ParsedFusionChildStderr
623
1102
  records.push(parseChildResultMetadata(parsed));
624
1103
  cursor = newline + 1;
625
1104
  }
626
- const events = Buffer.from(
627
- records.length === 0 ? '' : `${records.map((record) => JSON.stringify(record)).join('\n')}\n`,
628
- 'utf8',
629
- );
630
- return { records, events, diagnostics: Buffer.concat(diagnostics) };
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
+ };
631
1115
  }
632
1116
 
633
1117
  function parseToolCallLogRecord(value: unknown, label: string): FusionToolCallLogRecord {
@@ -728,36 +1212,53 @@ export function parseFusionToolCallLog(bytes: Buffer): FusionToolCallTrace {
728
1212
  };
729
1213
  }
730
1214
 
731
-
732
1215
  async function assertCompletedToolPolicy(
733
1216
  trace: FusionToolCallTrace,
734
1217
  capability: FusionCapability,
735
1218
  sourcePolicy: { path: string; sha256: string } | undefined,
736
1219
  ): Promise<void> {
737
- const allowed = capability === 'inspect' ? FUSION_INSPECT_TOOLS : capability === 'research' ? FUSION_RESEARCH_TOOLS : [];
1220
+ const allowed =
1221
+ capability === 'inspect'
1222
+ ? FUSION_INSPECT_TOOLS
1223
+ : capability === 'research'
1224
+ ? FUSION_RESEARCH_TOOLS
1225
+ : [];
738
1226
  const allowedSet = new Set<string>(allowed);
739
1227
  const declared =
740
1228
  capability === 'research' && sourcePolicy !== undefined
741
- ? new Set((await readFusionSourcePolicyFile(sourcePolicy.path, sourcePolicy.sha256)).sources.map((source) => source.canonical_url))
1229
+ ? new Set(
1230
+ (await readFusionSourcePolicyFile(sourcePolicy.path, sourcePolicy.sha256)).sources.map(
1231
+ (source) => source.canonical_url,
1232
+ ),
1233
+ )
742
1234
  : undefined;
743
1235
  for (const record of trace.records) {
744
1236
  if (!allowedSet.has(record.tool_name)) {
745
1237
  throw new Error(`fusion child used non-allowlisted tool ${record.tool_name}`);
746
1238
  }
747
1239
  if (capability === 'research' && record.tool_name === FUSION_WEB_FETCH_TOOL_NAME) {
748
- if (sourcePolicy === undefined || declared === undefined) throw new Error('fusion research source policy missing during audit');
1240
+ if (sourcePolicy === undefined || declared === undefined)
1241
+ throw new Error('fusion research source policy missing during audit');
749
1242
  if (record.status === 'ok') {
750
1243
  if (record.url === undefined) throw new Error('fusion research fetch audit is missing url');
751
1244
  const canonicalUrl = canonicalizeFusionPublicUrl(record.url);
752
- if (record.url !== canonicalUrl) throw new Error('fusion research fetch audit URL was not canonical');
753
- if (!declared.has(canonicalUrl)) throw new Error('fusion research fetch audit URL was not declared');
1245
+ if (record.url !== canonicalUrl)
1246
+ throw new Error('fusion research fetch audit URL was not canonical');
1247
+ if (!declared.has(canonicalUrl))
1248
+ throw new Error('fusion research fetch audit URL was not declared');
754
1249
  if (record.rejected_url_sha256 !== undefined) {
755
- throw new Error('fusion research successful fetch audit must not include rejected_url_sha256');
1250
+ throw new Error(
1251
+ 'fusion research successful fetch audit must not include rejected_url_sha256',
1252
+ );
756
1253
  }
757
- if (record.final_url === undefined) throw new Error('fusion research fetch audit is missing final_url');
758
- if (record.http_status === undefined) throw new Error('fusion research fetch audit is missing http_status');
759
- if (record.response_bytes === undefined) throw new Error('fusion research fetch audit is missing response_bytes');
760
- if (record.content_sha256 === undefined) throw new Error('fusion research fetch audit is missing content_sha256');
1254
+ if (record.final_url === undefined)
1255
+ throw new Error('fusion research fetch audit is missing final_url');
1256
+ if (record.http_status === undefined)
1257
+ throw new Error('fusion research fetch audit is missing http_status');
1258
+ if (record.response_bytes === undefined)
1259
+ throw new Error('fusion research fetch audit is missing response_bytes');
1260
+ if (record.content_sha256 === undefined)
1261
+ throw new Error('fusion research fetch audit is missing content_sha256');
761
1262
  } else {
762
1263
  if (record.url !== undefined || record.final_url !== undefined) {
763
1264
  throw new Error('fusion research rejected fetch audit must not persist raw URL');
@@ -828,7 +1329,8 @@ async function assertFusionToolCallLogSeal(
828
1329
  }
829
1330
  try {
830
1331
  const stats = await handle.stat();
831
- if (!stats.isFile()) throw new Error('fusion tool-call audit completion seal is not a regular file');
1332
+ if (!stats.isFile())
1333
+ throw new Error('fusion tool-call audit completion seal is not a regular file');
832
1334
  if (stats.size > 4096) throw new Error('fusion tool-call audit completion seal is oversized');
833
1335
  const bytes = await handle.readFile();
834
1336
  if (bytes.at(-1) !== 10) throw new Error('fusion tool-call audit completion seal is partial');
@@ -841,7 +1343,13 @@ async function assertFusionToolCallLogSeal(
841
1343
  throw new Error('fusion tool-call audit completion seal must be an object');
842
1344
  }
843
1345
  const keys = Object.keys(parsed).sort();
844
- const expected = ['log_sha256', 'record_count', 'schema_version', 'status', 'total_result_bytes'];
1346
+ const expected = [
1347
+ 'log_sha256',
1348
+ 'record_count',
1349
+ 'schema_version',
1350
+ 'status',
1351
+ 'total_result_bytes',
1352
+ ];
845
1353
  if (keys.join('\0') !== expected.join('\0')) {
846
1354
  throw new Error('fusion tool-call audit completion seal keys mismatch');
847
1355
  }
@@ -939,10 +1447,29 @@ export class FusionPiCompactResultParser {
939
1447
  diagnostics: Buffer;
940
1448
  } {
941
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
+ }
1460
+ if (parsed.diagnostics.includes(PI_EXTENSION_ERROR_PREFIX_BYTES)) {
1461
+ throw new Error('Pi child reported an extension error diagnostic');
1462
+ }
942
1463
  const final = parsed.records.at(-1);
943
1464
  if (final === undefined) throw new Error('Pi child emitted no compact result metadata');
944
1465
  for (const record of parsed.records) this.assertModel(record);
1466
+ this.assertCacheObservationOrdinals(parsed.records);
945
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
+ }
946
1473
  const observed = this.observedFromRecords(parsed.records);
947
1474
  return {
948
1475
  text: reconstructFinalText(response, final),
@@ -963,6 +1490,19 @@ export class FusionPiCompactResultParser {
963
1490
  }
964
1491
  }
965
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
+
966
1506
  private assertTranscriptStopReasons(records: readonly FusionChildResultMetadata[]): void {
967
1507
  for (const [index, record] of records.entries()) {
968
1508
  const isFinal = index === records.length - 1;
@@ -970,9 +1510,14 @@ export class FusionPiCompactResultParser {
970
1510
  if (record.stop_reason !== 'stop') {
971
1511
  throw new Error(this.stopReasonError('final', 'stop', record.stop_reason, true));
972
1512
  }
973
- } else if (record.stop_reason !== 'toolUse') {
1513
+ } else if (record.stop_reason !== 'toolUse' && !isRecoverableFusionChildErrorRecord(record)) {
974
1514
  throw new Error(
975
- this.stopReasonError(`non-final record ${index}`, 'toolUse', record.stop_reason, true),
1515
+ this.stopReasonError(
1516
+ `non-final record ${index}`,
1517
+ 'toolUse or a settled zero-usage retry marker',
1518
+ record.stop_reason,
1519
+ true,
1520
+ ),
976
1521
  );
977
1522
  }
978
1523
  }
@@ -1251,7 +1796,13 @@ export async function runPiChild(options: RunPiChildOptions): Promise<FusionChil
1251
1796
  env[FUSION_TOOL_CALL_LOG_PATH_ENV] = options.toolCallLogPath;
1252
1797
  if (capability === 'research') {
1253
1798
  if (options.sourcePolicy === undefined) {
1254
- throw childError('fusion research child requires a source-policy path and hash', 'orchestration_failed', options, false, false);
1799
+ throw childError(
1800
+ 'fusion research child requires a source-policy path and hash',
1801
+ 'orchestration_failed',
1802
+ options,
1803
+ false,
1804
+ false,
1805
+ );
1255
1806
  }
1256
1807
  env[FUSION_RESEARCH_ENABLED_ENV] = '1';
1257
1808
  env[FUSION_SOURCE_POLICY_PATH_ENV] = options.sourcePolicy.path;
@@ -1335,7 +1886,15 @@ export async function runPiChild(options: RunPiChildOptions): Promise<FusionChil
1335
1886
  options,
1336
1887
  );
1337
1888
  }
1338
- terminateChild(child, state, platform, killProcess, killGraceMs, sigkillWaitMs, settleClose);
1889
+ terminateChild(
1890
+ child,
1891
+ state,
1892
+ platform,
1893
+ killProcess,
1894
+ killGraceMs,
1895
+ sigkillWaitMs,
1896
+ settleClose,
1897
+ );
1339
1898
  }, idleTimeoutMs),
1340
1899
  );
1341
1900
  };
@@ -1452,11 +2011,39 @@ export async function runPiChild(options: RunPiChildOptions): Promise<FusionChil
1452
2011
  observed,
1453
2012
  );
1454
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
+ }
1455
2037
  throw new FusionChildRunError(
1456
2038
  withCleanupErrors(
1457
2039
  childError(
1458
- `Pi child exited with code ${close.code === null ? 'null' : String(close.code)}${close.signal === null ? '' : ` (${close.signal})`}`,
1459
- 'child_exit_failed',
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',
1460
2047
  options,
1461
2048
  ),
1462
2049
  state.cleanupErrors,