deepline 0.3.407 → 0.3.409

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +8 -1
  2. package/dist/bundling-sources/sdk/src/play.ts +13 -0
  3. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  4. package/dist/bundling-sources/sdk/src/types.ts +23 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/admission-timings.ts +64 -0
  6. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +11 -1
  7. package/dist/bundling-sources/shared_libs/play-runtime/modal-runtime-config.ts +1 -4
  8. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +2 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +15 -6
  10. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +188 -45
  11. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +22 -4
  12. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +10 -4
  13. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +72 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/webhook-receipt-source-commit-retry.ts +90 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +5 -0
  16. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +6 -0
  17. package/dist/cli/{auth-WURWNNLE.mjs → auth-5HIIACF7.mjs} +4 -4
  18. package/dist/cli/auth-F6BX32XZ.js +40 -0
  19. package/dist/cli/{billing-2QJGXGJX.js → billing-4275N7FL.js} +92 -85
  20. package/dist/cli/{billing-BP3E6WIM.mjs → billing-EVZ3ZWKC.mjs} +23 -16
  21. package/dist/cli/{chunk-WXLOW623.mjs → chunk-264PC2RN.mjs} +14 -7
  22. package/dist/cli/{chunk-RMZAOYDN.js → chunk-2GI3QD3L.js} +53 -53
  23. package/dist/cli/{chunk-ZBCTXQ3P.mjs → chunk-2PRO2JTX.mjs} +1 -1
  24. package/dist/cli/{chunk-GAMFK2ZP.mjs → chunk-44ZTYHMQ.mjs} +10 -3
  25. package/dist/cli/{chunk-G4LJS2RE.js → chunk-53HN7WZN.js} +8 -8
  26. package/dist/cli/{chunk-URWF7NGQ.js → chunk-5MLCBXXU.js} +36 -15
  27. package/dist/cli/{chunk-XT6GG3QO.mjs → chunk-7AALNGTA.mjs} +3 -3
  28. package/dist/cli/{chunk-WJYPBHTE.js → chunk-ALD4COBH.js} +11 -4
  29. package/dist/cli/{chunk-RZLXVHNO.js → chunk-AU4OFDFX.js} +26 -26
  30. package/dist/cli/{chunk-N6OAMAO5.mjs → chunk-BEVLCKXG.mjs} +1 -1
  31. package/dist/cli/{chunk-7LKYHZVB.js → chunk-DEC3JECC.js} +1 -1
  32. package/dist/cli/{chunk-UFFSOV6I.mjs → chunk-FHPCKULR.mjs} +2 -2
  33. package/dist/cli/{chunk-THHM4DPE.js → chunk-FJCIUF34.js} +4 -4
  34. package/dist/cli/{chunk-H6EWQFPQ.js → chunk-GAICVLKL.js} +2 -2
  35. package/dist/cli/{chunk-4TV43THC.mjs → chunk-IT3XBJQR.mjs} +4 -4
  36. package/dist/cli/{chunk-UFS752TU.mjs → chunk-J6DCKJNM.mjs} +5 -1
  37. package/dist/cli/{chunk-AYHLUHE5.js → chunk-JGHG4NBA.js} +6 -2
  38. package/dist/cli/{chunk-BKC6VMYZ.mjs → chunk-JRAVVJ3T.mjs} +73 -4
  39. package/dist/cli/{chunk-ZI2ZNDJK.mjs → chunk-LFSHGNT2.mjs} +36 -15
  40. package/dist/cli/{chunk-L25GBGQU.mjs → chunk-N44JHMKY.mjs} +6 -6
  41. package/dist/cli/{chunk-QJAX7QN7.mjs → chunk-NZSF57UO.mjs} +2 -2
  42. package/dist/cli/{chunk-IY6GKJ4O.js → chunk-O3VSXP2F.js} +50 -50
  43. package/dist/cli/{chunk-J67NU4MQ.mjs → chunk-PBZYY7VZ.mjs} +5 -2
  44. package/dist/cli/{chunk-G6U74WNM.js → chunk-RTCDW2GU.js} +15 -15
  45. package/dist/cli/{chunk-UPEOEYZ3.mjs → chunk-TQNHMWUR.mjs} +1 -1
  46. package/dist/cli/{chunk-KUQAU5MG.js → chunk-V76R6HMV.js} +10 -7
  47. package/dist/cli/{chunk-6JZZNP5G.mjs → chunk-VWBTTJYQ.mjs} +2 -2
  48. package/dist/cli/{chunk-SOQIPKF2.js → chunk-WHG43Z67.js} +8 -8
  49. package/dist/cli/{chunk-Y6CX4QPR.js → chunk-WLOCMJWV.js} +67 -67
  50. package/dist/cli/{chunk-QJBJZVG6.js → chunk-WNUR6VP5.js} +15 -8
  51. package/dist/cli/{chunk-P7HOTKOK.mjs → chunk-WYQO2CBS.mjs} +4 -4
  52. package/dist/cli/{chunk-D2ZRMZKF.js → chunk-YD7B53RW.js} +99 -30
  53. package/dist/cli/command-docs.js +2 -2
  54. package/dist/cli/command-docs.mjs +1 -1
  55. package/dist/cli/{csv-N6KYTG7L.mjs → csv-A2MP6EFV.mjs} +3 -3
  56. package/dist/cli/{csv-7E5BE23B.js → csv-BBOTCGQY.js} +12 -12
  57. package/dist/cli/{db-XMBJNVBD.mjs → db-3W5DMTUX.mjs} +4 -4
  58. package/dist/cli/{db-4VLZP33O.js → db-G4CODDCA.js} +25 -25
  59. package/dist/cli/{describe-4I6EIVXD.js → describe-PQTQ5B6F.js} +11 -11
  60. package/dist/cli/{describe-WERH4TD2.mjs → describe-XYJ2ZT4M.mjs} +7 -7
  61. package/dist/cli/describe-core.d.mts +2 -2
  62. package/dist/cli/describe-core.d.ts +2 -2
  63. package/dist/cli/{executions-W7TWJRBK.js → executions-TLANVR7Y.js} +6 -6
  64. package/dist/cli/{executions-JWQXE7G6.mjs → executions-UR67CA5H.mjs} +4 -4
  65. package/dist/cli/{feedback-FMGRC6KO.js → feedback-6C2YTRJL.js} +7 -7
  66. package/dist/cli/{feedback-OEJFX4F7.mjs → feedback-WHCMGEIL.mjs} +3 -3
  67. package/dist/cli/{git-IVC4KHXX.mjs → git-GWLB6VSA.mjs} +4 -4
  68. package/dist/cli/git-VQFF6D7C.js +24 -0
  69. package/dist/cli/index.js +204 -127
  70. package/dist/cli/index.mjs +124 -47
  71. package/dist/cli/{legacy-noops-LT36CLWZ.mjs → legacy-noops-KF4XIFEF.mjs} +4 -4
  72. package/dist/cli/{legacy-noops-X3BQ7MUK.js → legacy-noops-ZUFA7RWI.js} +7 -7
  73. package/dist/cli/{monitors-MNLCELF7.mjs → monitors-6UOIDIMB.mjs} +4 -4
  74. package/dist/cli/{monitors-DLIIAVLW.js → monitors-TB3K2IVE.js} +84 -84
  75. package/dist/cli/{org-CYHDEW7P.js → org-CKWMBPIR.js} +64 -64
  76. package/dist/cli/{org-QV5LMUPT.mjs → org-WGHHMVDZ.mjs} +4 -4
  77. package/dist/cli/{play-5Z4ROZ3P.mjs → play-LYPAXCLD.mjs} +70 -18
  78. package/dist/cli/{play-MEFXC2QA.js → play-N2B3KAVS.js} +345 -293
  79. package/dist/cli/{providers-FJLYSHB3.mjs → providers-6DI5BVZJ.mjs} +4 -4
  80. package/dist/cli/{providers-2MKUNFBQ.js → providers-UR42MD6Z.js} +7 -7
  81. package/dist/cli/{quickstart-ULWXKAOT.js → quickstart-FCJG4XGB.js} +5 -5
  82. package/dist/cli/{quickstart-PQO3OQRT.mjs → quickstart-MNA2S6PO.mjs} +3 -3
  83. package/dist/cli/search-NCEEONMQ.js +30 -0
  84. package/dist/cli/{search-KGAAXMDG.mjs → search-VPPYUJ2M.mjs} +5 -5
  85. package/dist/cli/{secrets-PD5I3KAF.mjs → secrets-DRI4W7Z5.mjs} +4 -4
  86. package/dist/cli/{secrets-X2VGG2PO.js → secrets-QOSMVO36.js} +13 -13
  87. package/dist/cli/{sessions-R3XAKTBQ.js → sessions-3WYKQABW.js} +5 -5
  88. package/dist/cli/{sessions-3WVEU4PE.mjs → sessions-DQNUKP43.mjs} +4 -4
  89. package/dist/cli/{settings-645RSFQX.mjs → settings-DWDK3Y2R.mjs} +8 -8
  90. package/dist/cli/{settings-ZIDO3IP6.js → settings-JUGGCOPS.js} +75 -75
  91. package/dist/cli/{setup-LH5HOZ3D.js → setup-AWBHNYXK.js} +42 -42
  92. package/dist/cli/{setup-LISKIJKP.mjs → setup-D5DWVNY4.mjs} +7 -7
  93. package/dist/cli/skills-AKPAJVSO.js +29 -0
  94. package/dist/cli/{skills-VVQJHLSF.mjs → skills-YV2XR35J.mjs} +5 -5
  95. package/dist/cli/{tools-FTPJCNAM.js → tools-RBKEK6PM.js} +8 -8
  96. package/dist/cli/{tools-Y3LOMXFT.mjs → tools-VFJYHNV2.mjs} +7 -7
  97. package/dist/cli/{update-DOBNQNJI.mjs → update-IM6VOTGL.mjs} +7 -7
  98. package/dist/cli/update-VFNUBLDA.js +29 -0
  99. package/dist/cli/{workflow-ZMAA5VCN.mjs → workflow-26URBWPV.mjs} +5 -5
  100. package/dist/cli/{workflow-LGENKZBV.js → workflow-3BS6U6SL.js} +15 -15
  101. package/dist/{client-IrPHUE5J.d.ts → client-BMjJFpan.d.ts} +28 -2
  102. package/dist/{client-6rEB46Lv.d.mts → client-BfInrZPB.d.mts} +28 -2
  103. package/dist/{compiler-manifest-BHnXE43X.d.ts → compiler-manifest-Ycx-hoAr.d.mts} +2 -0
  104. package/dist/{compiler-manifest-BHnXE43X.d.mts → compiler-manifest-Ycx-hoAr.d.ts} +2 -0
  105. package/dist/{index-Da_Y8z5e.d.mts → index-CZXgiN3E.d.mts} +1 -1
  106. package/dist/{index-SODb2ROS.d.ts → index-NPG5pqxQ.d.ts} +1 -1
  107. package/dist/index.d.mts +5 -5
  108. package/dist/index.d.ts +5 -5
  109. package/dist/index.js +26 -9
  110. package/dist/index.mjs +23 -6
  111. package/dist/install-integrity.json +86 -84
  112. package/dist/plays/bundle-play-file.d.mts +3 -3
  113. package/dist/plays/bundle-play-file.d.ts +3 -3
  114. package/dist/plays/play-description.d.mts +2 -2
  115. package/dist/plays/play-description.d.ts +2 -2
  116. package/dist/plays/run-request.d.mts +2 -2
  117. package/dist/plays/run-request.d.ts +2 -2
  118. package/dist/release.d.mts +1 -1
  119. package/dist/release.d.ts +1 -1
  120. package/dist/release.js +2 -2
  121. package/dist/release.mjs +1 -1
  122. package/package.json +1 -1
  123. package/dist/cli/auth-4JTX6AGM.js +0 -40
  124. package/dist/cli/git-OFXE7G4X.js +0 -24
  125. package/dist/cli/search-ANKSJ224.js +0 -30
  126. package/dist/cli/skills-WLHYSVQA.js +0 -29
  127. package/dist/cli/update-A622H2DZ.js +0 -29
@@ -18598,8 +18598,8 @@ function runLedgerOutboxPrefixClaimSql(
18598
18598
  const lockFence = fence
18599
18599
  ? `current_setting('deepline.run_ledger_outbox_lock', true) = ${escapeLiteral(fence.runId)}`
18600
18600
  : undefined;
18601
- const pending = (alias: string) =>
18602
- `${alias}.delivered_at IS NULL${
18601
+ const pending = (alias: string, primaryKeyLookup = false) =>
18602
+ `${primaryKeyLookup ? `coalesce(${alias}.delivered_at IS NULL, false)` : `${alias}.delivered_at IS NULL`}${
18603
18603
  acknowledgement ? ` AND NOT (${alias}.event_id = ANY($5::text[]))` : ''
18604
18604
  }`;
18605
18605
  // The dispatcher already chose this run. Seek its oldest pending fact
@@ -18628,11 +18628,20 @@ function runLedgerOutboxPrefixClaimSql(
18628
18628
  ? `(head.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents})`
18629
18629
  : 'false';
18630
18630
  const findSearchBlockersOnce = seekRunHead && input.searchOnly === true;
18631
- const searchFactColumnsSql = (alias: string) =>
18631
+ // Only ordinary scoped lifecycle enumeration changes. Its existing limit
18632
+ // bounds candidate keys before the original eligibility/fence predicates;
18633
+ // a frozen terminal in that window retains complete receipt membership.
18634
+ // Carry the locked event type instead of fetching it again by primary key.
18635
+ // Recheck pending/run identity without inviting stale partial-index scans.
18636
+ // Predecessor checks share snapshot metadata; bounded keys own live row access.
18637
+ const inspectLifecycleCandidates = seekRunHead && input.searchOnly !== true;
18638
+ const claimFactColumnsSql = (alias: string) =>
18632
18639
  `${alias}.event_id, ${alias}.run_id, ${alias}.event_type,
18633
18640
  ${alias}.created_at, ${alias}.failed_at, ${alias}.next_attempt_at,
18634
18641
  ${alias}.lease_owner, ${alias}.lease_expires_at,
18635
18642
  ${supportsTerminalBatchMarker ? `${alias}.delivery_batch_started_at` : 'NULL::timestamptz AS delivery_batch_started_at'}`;
18643
+ const lifecycleFactColumnsSql = (alias: string) =>
18644
+ `${claimFactColumnsSql(alias)}, ${alias}.delivered_at`;
18636
18645
  const blockingPredecessorSql = (alias: string) => `(
18637
18646
  (${alias}.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents}
18638
18647
  AND NOT (${ingressPrefixSql}))
@@ -18646,6 +18655,27 @@ function runLedgerOutboxPrefixClaimSql(
18646
18655
  AND ${alias}.lease_owner IS DISTINCT FROM $3::text
18647
18656
  )
18648
18657
  )`;
18658
+ // A modern frozen head cannot cross an unmarked predecessor. A witness in
18659
+ // this snapshot's window therefore blocks every later key, even if a live
18660
+ // candidate is rejected after locking. Keep full membership without that
18661
+ // witness: an all-marked window can refill after a concurrent ACK.
18662
+ const lifecycleRequiresFullMembershipSql = inspectLifecycleCandidates
18663
+ ? `EXISTS (
18664
+ SELECT 1 FROM lifecycle_window AS terminal
18665
+ WHERE terminal.delivery_batch_started_at IS NOT NULL
18666
+ AND terminal.event_type IN (${RUN_EVENT_KIND_SQL_LITERAL.runCompleted}, ${RUN_EVENT_KIND_SQL_LITERAL.runFailed}, ${RUN_EVENT_KIND_SQL_LITERAL.runCancelled})${
18667
+ supportsTerminalBatchMarker && input.allowIngressPrefix === true
18668
+ ? `
18669
+ AND NOT EXISTS (
18670
+ SELECT 1 FROM head AS frozen_head
18671
+ CROSS JOIN lifecycle_window AS unmarked
18672
+ WHERE frozen_head.delivery_batch_started_at IS NOT NULL
18673
+ AND unmarked.delivery_batch_started_at IS NULL
18674
+ )`
18675
+ : ''
18676
+ }
18677
+ )`
18678
+ : 'false';
18649
18679
  return `
18650
18680
  WITH ${
18651
18681
  acknowledgement
@@ -18681,9 +18711,8 @@ function runLedgerOutboxPrefixClaimSql(
18681
18711
  ${findSearchBlockersOnce ? 'candidate.created_at,' : ''}
18682
18712
  ${supportsTerminalBatchMarker ? 'candidate.delivery_batch_started_at' : 'NULL::timestamptz'} AS delivery_batch_started_at
18683
18713
  FROM ${tableName('outbox', options)} AS candidate
18684
- ${seekRunHead ? 'JOIN pending_head ON pending_head.event_id = candidate.event_id' : ''}
18685
- ${input.extendOwnedHead ? 'JOIN owned_run ON owned_run.run_id = candidate.run_id' : ''}
18686
- WHERE ${pending('candidate')}
18714
+ ${seekRunHead ? '' : '\n '}${input.extendOwnedHead ? 'JOIN owned_run ON owned_run.run_id = candidate.run_id' : ''}
18715
+ WHERE ${seekRunHead ? `candidate.event_id = (SELECT event_id FROM pending_head) AND ${pending('candidate')}` : pending('candidate')}
18687
18716
  ${lockFence ? `AND (${lockFence})` : ''}
18688
18717
  ${fence?.expectedHeadEventId ? `AND candidate.event_id = ${escapeLiteral(fence.expectedHeadEventId)}` : ''}
18689
18718
  AND ${searchLaneSql('candidate')} = ${input.searchOnly === true ? 'true' : 'false'}
@@ -18766,14 +18795,16 @@ function runLedgerOutboxPrefixClaimSql(
18766
18795
  ), ${
18767
18796
  findSearchBlockersOnce
18768
18797
  ? `search_prefix AS MATERIALIZED (
18769
- SELECT ${searchFactColumnsSql('pending')}
18798
+ SELECT prefix.* FROM head
18799
+ CROSS JOIN LATERAL (
18800
+ SELECT ${claimFactColumnsSql('pending')}
18770
18801
  FROM ${tableName('outbox', options)} AS pending
18771
- JOIN head ON head.run_id = pending.run_id
18772
- WHERE pending.run_id = $2::text
18802
+ WHERE pending.run_id = head.run_id
18773
18803
  AND ${pending('pending')}
18774
18804
  AND ${searchLaneSql('pending')} = true
18775
18805
  ORDER BY ${runLedgerOrderSql('pending')}
18776
18806
  LIMIT $1
18807
+ ) AS prefix
18777
18808
  ), frozen_search_prefix AS MATERIALIZED (
18778
18809
  SELECT EXISTS (
18779
18810
  SELECT 1 FROM search_prefix AS fact
@@ -18791,7 +18822,7 @@ function runLedgerOutboxPrefixClaimSql(
18791
18822
  -- A frozen terminal receipt can contain marked facts beyond newer
18792
18823
  -- unmarked rows. Retain that full membership scan only for this case;
18793
18824
  -- limiting its raw window would strand an already attempted receipt.
18794
- SELECT ${searchFactColumnsSql('pending')}
18825
+ SELECT ${claimFactColumnsSql('pending')}
18795
18826
  FROM ${tableName('outbox', options)} AS pending
18796
18827
  JOIN head ON head.run_id = pending.run_id
18797
18828
  WHERE (SELECT has_terminal FROM frozen_search_prefix)
@@ -18826,13 +18857,49 @@ function runLedgerOutboxPrefixClaimSql(
18826
18857
  LIMIT 1
18827
18858
  ),`
18828
18859
  : ''
18860
+ }${
18861
+ inspectLifecycleCandidates
18862
+ ? `lifecycle_window AS MATERIALIZED (
18863
+ SELECT prefix.* FROM head
18864
+ CROSS JOIN LATERAL (
18865
+ SELECT ${lifecycleFactColumnsSql('pending')}
18866
+ FROM ${tableName('outbox', options)} AS pending
18867
+ WHERE pending.run_id = head.run_id
18868
+ AND pending.run_id = $2::text
18869
+ AND ${pending('pending')}
18870
+ AND ${searchLaneSql('pending')} = false
18871
+ ORDER BY ${runLedgerOrderSql('pending')}
18872
+ LIMIT $1
18873
+ ) AS prefix
18874
+ ), lifecycle_facts AS MATERIALIZED (
18875
+ SELECT prefix.* FROM lifecycle_window AS prefix
18876
+ WHERE NOT ${lifecycleRequiresFullMembershipSql}
18877
+ UNION ALL
18878
+ SELECT ${lifecycleFactColumnsSql('pending')}
18879
+ FROM ${tableName('outbox', options)} AS pending
18880
+ JOIN head ON head.run_id = pending.run_id
18881
+ WHERE ${lifecycleRequiresFullMembershipSql}
18882
+ AND pending.run_id = $2::text
18883
+ AND ${pending('pending')}
18884
+ AND ${searchLaneSql('pending')} = false
18885
+ ), `
18886
+ : ''
18829
18887
  }candidates AS MATERIALIZED (
18830
- SELECT outbox.event_id
18888
+ ${
18889
+ runId !== null
18890
+ ? `SELECT candidate.event_id${inspectLifecycleCandidates ? ', candidate.event_type' : ''} FROM head
18891
+ CROSS JOIN LATERAL (
18892
+ SELECT outbox.event_id${inspectLifecycleCandidates ? ', outbox.event_type' : ''}
18893
+ FROM ${tableName('outbox', options)} AS outbox
18894
+ WHERE ${pending('outbox', inspectLifecycleCandidates)}
18895
+ AND ${inspectLifecycleCandidates ? 'coalesce(outbox.run_id = head.run_id, false)' : 'outbox.run_id = head.run_id'}`
18896
+ : `SELECT outbox.event_id
18831
18897
  FROM ${tableName('outbox', options)} AS outbox
18832
18898
  JOIN head ON head.run_id = outbox.run_id
18833
18899
  WHERE ${pending('outbox')}
18834
- ${runId !== null ? 'AND outbox.run_id = $2::text' : ''}
18835
- ${findSearchBlockersOnce ? 'AND outbox.event_id = ANY(ARRAY(SELECT event_id FROM search_facts))' : ''}
18900
+ `
18901
+ }
18902
+ ${findSearchBlockersOnce ? 'AND outbox.event_id = ANY(ARRAY(SELECT event_id FROM search_facts))' : ''}${inspectLifecycleCandidates ? '\n AND outbox.event_id = ANY(ARRAY(SELECT event_id FROM lifecycle_facts))' : ''}
18836
18903
  AND ${searchLaneSql('outbox')} = ${input.searchOnly === true ? 'true' : 'false'}
18837
18904
  AND ${outboxNotFailedSql('outbox')}
18838
18905
  -- Admission is causally first and must be projected alone. A later
@@ -18881,8 +18948,8 @@ function runLedgerOutboxPrefixClaimSql(
18881
18948
  )`
18882
18949
  : ` AND NOT EXISTS (
18883
18950
  SELECT 1
18884
- FROM ${tableName('outbox', options)} AS terminal_predecessor
18885
- WHERE terminal_predecessor.run_id = outbox.run_id
18951
+ FROM ${inspectLifecycleCandidates ? 'lifecycle_facts' : tableName('outbox', options)} AS terminal_predecessor
18952
+ WHERE terminal_predecessor.run_id = outbox.run_id${inspectLifecycleCandidates ? '\n AND terminal_predecessor.run_id = $2::text' : ''}
18886
18953
  AND ${pending('terminal_predecessor')}
18887
18954
  AND ${
18888
18955
  supportsTerminalBatchMarker
@@ -18897,16 +18964,16 @@ function runLedgerOutboxPrefixClaimSql(
18897
18964
  AND terminal_predecessor.event_type IN (
18898
18965
  ${RUN_EVENT_KIND_SQL_LITERAL.runCompleted}, ${RUN_EVENT_KIND_SQL_LITERAL.runFailed}, ${RUN_EVENT_KIND_SQL_LITERAL.runCancelled}
18899
18966
  )
18900
- AND ${runLedgerPrecedesSql('terminal_predecessor', 'outbox', false, input.searchOnly === true)}
18967
+ AND ${inspectLifecycleCandidates ? `${runLedgerPositionSql('terminal_predecessor')} < ${runLedgerPositionSql('outbox')}` : runLedgerPrecedesSql('terminal_predecessor', 'outbox', false, input.searchOnly === true)}
18901
18968
  )
18902
18969
  -- Claim only the contiguous due prefix. A delayed, leased, or failed
18903
18970
  -- predecessor retains ownership of everything that follows it.
18904
18971
  AND NOT EXISTS (
18905
18972
  SELECT 1
18906
- FROM ${tableName('outbox', options)} AS predecessor
18907
- WHERE predecessor.run_id = outbox.run_id
18973
+ FROM ${inspectLifecycleCandidates ? 'lifecycle_facts' : tableName('outbox', options)} AS predecessor
18974
+ WHERE predecessor.run_id = outbox.run_id${inspectLifecycleCandidates ? '\n AND predecessor.run_id = $2::text' : ''}
18908
18975
  AND ${pending('predecessor')}
18909
- AND ${runLedgerPrecedesSql('predecessor', 'outbox', false, input.searchOnly === true)}
18976
+ AND ${inspectLifecycleCandidates ? `${runLedgerPositionSql('predecessor')} < ${runLedgerPositionSql('outbox')}` : runLedgerPrecedesSql('predecessor', 'outbox', false, input.searchOnly === true)}
18910
18977
  AND ${blockingPredecessorSql('predecessor')}
18911
18978
  )
18912
18979
  `
@@ -18914,14 +18981,18 @@ function runLedgerOutboxPrefixClaimSql(
18914
18981
  ORDER BY ${runLedgerOrderSql('outbox')}
18915
18982
  FOR UPDATE OF outbox
18916
18983
  LIMIT $1
18917
- ), terminal_batch AS (
18984
+ ${runId !== null ? ' ) AS candidate\n ' : ''}), ${
18985
+ inspectLifecycleCandidates
18986
+ ? ''
18987
+ : `terminal_batch AS (
18918
18988
  SELECT EXISTS (
18919
18989
  SELECT 1
18920
18990
  FROM ${tableName('outbox', options)} AS candidate
18921
18991
  JOIN candidates ON candidates.event_id = candidate.event_id
18922
18992
  WHERE candidate.event_type IN (${RUN_EVENT_KIND_SQL_LITERAL.runCompleted}, ${RUN_EVENT_KIND_SQL_LITERAL.runFailed}, ${RUN_EVENT_KIND_SQL_LITERAL.runCancelled})
18923
18993
  ) AS has_terminal
18924
- ), claimed AS (
18994
+ ), `
18995
+ }claimed AS (
18925
18996
  UPDATE ${tableName('outbox', options)} outbox
18926
18997
  SET lease_owner = $3,
18927
18998
  lease_expires_at = CASE
@@ -18931,14 +19002,14 @@ function runLedgerOutboxPrefixClaimSql(
18931
19002
  ${
18932
19003
  supportsTerminalBatchMarker
18933
19004
  ? `, delivery_batch_started_at = CASE
18934
- WHEN terminal_batch.has_terminal ${input.allowIngressPrefix === true ? `OR ${urgentLifecyclePendingSql('outbox')} OR EXISTS (SELECT 1 FROM head WHERE head.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents})` : ''}
19005
+ WHEN ${inspectLifecycleCandidates ? `EXISTS (SELECT 1 FROM candidates AS terminal_candidate WHERE terminal_candidate.event_type IN (${RUN_EVENT_KIND_SQL_LITERAL.runCompleted}, ${RUN_EVENT_KIND_SQL_LITERAL.runFailed}, ${RUN_EVENT_KIND_SQL_LITERAL.runCancelled}))` : 'terminal_batch.has_terminal'} ${input.allowIngressPrefix === true ? `OR ${urgentLifecyclePendingSql('outbox')} OR EXISTS (SELECT 1 FROM head WHERE head.event_type = ${RUN_SCHEDULER_OUTBOX_EVENT_TYPE_SQL_LITERAL.runEvents})` : ''}
18935
19006
  THEN COALESCE(outbox.delivery_batch_started_at, statement_timestamp())
18936
19007
  ELSE outbox.delivery_batch_started_at
18937
19008
  END`
18938
19009
  : ''
18939
19010
  }
18940
19011
  FROM candidates
18941
- CROSS JOIN terminal_batch
19012
+ ${inspectLifecycleCandidates ? '' : 'CROSS JOIN terminal_batch'}
18942
19013
  WHERE outbox.event_id = candidates.event_id
18943
19014
  RETURNING outbox.event_id, outbox.run_id, outbox.event_type,
18944
19015
  outbox.payload_json, outbox.created_at, outbox.lease_owner,
@@ -19002,9 +19073,34 @@ function runLedgerOutboxPrefixClaimBatchSql(input: {
19002
19073
  lockStrength?: 'no_key_update';
19003
19074
  transportHeadSql?: string;
19004
19075
  }): string {
19005
- const encoded = escapeLiteral(JSON.stringify(input.params));
19076
+ // Ordinary claims have four bounded primitive arguments. Give PostgreSQL
19077
+ // constants instead of a scalar InitPlan for every repeated argument. Keep
19078
+ // the JSON renderer for ACK arrays, transport/extension claims and values
19079
+ // whose existing JSON conversion or cast failure must remain unchanged.
19080
+ const directScalars =
19081
+ input.params.length === 4 &&
19082
+ !input.transportHeadSql &&
19083
+ input.params.every((value, index) =>
19084
+ index === 0 || index === 3
19085
+ ? typeof value === 'number' &&
19086
+ Number.isInteger(value) &&
19087
+ value >= -2_147_483_648 &&
19088
+ value <= 2_147_483_647
19089
+ : value === null ||
19090
+ (typeof value === 'string' &&
19091
+ /^(?:[\u0001-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF])*$/.test(
19092
+ value,
19093
+ )),
19094
+ );
19095
+ const encoded = directScalars
19096
+ ? undefined
19097
+ : escapeLiteral(JSON.stringify(input.params));
19006
19098
  const parameter = (index: number) => {
19007
19099
  const value = input.params[index - 1];
19100
+ if (directScalars) {
19101
+ const type = typeof value === 'number' ? 'integer' : 'text';
19102
+ return `(${value === null ? 'NULL' : escapeLiteral(String(value))}::${type})`;
19103
+ }
19008
19104
  if (Array.isArray(value)) {
19009
19105
  return `(SELECT ARRAY(SELECT jsonb_array_elements_text(values_json->${index - 1})) FROM run_ledger_parameters)`;
19010
19106
  }
@@ -19022,9 +19118,11 @@ function runLedgerOutboxPrefixClaimBatchSql(input: {
19022
19118
  (match, index: string | undefined) =>
19023
19119
  index ? parameter(Number(index)) : match,
19024
19120
  )
19025
- .replace(
19026
- /^\s*WITH\s/,
19027
- `WITH run_ledger_parameters AS MATERIALIZED (SELECT ${encoded}::jsonb AS values_json), `,
19121
+ // Use a callback so replacement metacharacters inside generated IDs stay literal.
19122
+ .replace(/^\s*WITH\s/, (match) =>
19123
+ directScalars
19124
+ ? match
19125
+ : `WITH run_ledger_parameters AS MATERIALIZED (SELECT ${encoded}::jsonb AS values_json), `,
19028
19126
  );
19029
19127
  return `BEGIN;
19030
19128
  SELECT set_config('lock_timeout', '${RUNTIME_SCHEDULER_CANCEL_LOCK_TIMEOUT_MS}ms', true),
@@ -20025,8 +20123,9 @@ export async function markPostgresSchedulerOutboxDelivered(
20025
20123
 
20026
20124
  /**
20027
20125
  * Shared lease-fenced ACK, including the canonical retained-payload cleanup.
20028
- * Known-run callers keep that scope visible to the planner: a materialized ID
20029
- * array alone can make stale pending-index statistics favor a cross-run scan.
20126
+ * Known-run callers recheck scope and pending state without offering a second
20127
+ * index access path beside their exact IDs. Stale statistics otherwise favor a
20128
+ * full run bitmap or pending partial-index scan before filtering those IDs.
20030
20129
  */
20031
20130
  function runLedgerAcknowledgeSql(
20032
20131
  options: Pick<PostgresSchedulerOptions, 'schema'> | undefined,
@@ -20047,9 +20146,9 @@ function runLedgerAcknowledgeSql(
20047
20146
  AND run.run_event_store = 'planet_scale'
20048
20147
  ) THEN '{}'::jsonb ELSE outbox.payload_json END
20049
20148
  WHERE outbox.event_id = ANY(${eventIdsParameter}::text[])
20050
- ${runIdParameter ? `AND outbox.run_id = ${runIdParameter}::text` : ''}
20149
+ ${runIdParameter ? `AND coalesce(outbox.run_id = ${runIdParameter}::text, false)` : ''}
20051
20150
  ${lockFence ? `AND (${lockFence})` : ''}
20052
- AND outbox.delivered_at IS NULL
20151
+ AND ${runIdParameter ? 'coalesce(outbox.delivered_at IS NULL, false)' : 'outbox.delivered_at IS NULL'}
20053
20152
  AND (${leaseOwnerParameter}::text IS NULL OR outbox.lease_owner = ${leaseOwnerParameter})
20054
20153
  RETURNING outbox.event_id,
20055
20154
  outbox.event_type,
@@ -21452,6 +21551,14 @@ async function dispatchRunLedgerOutboxBatchWithScope(
21452
21551
  }
21453
21552
  }
21454
21553
  let events = claimed.events;
21554
+ const searchOnlyTerminalLogCarrier =
21555
+ input.searchOnly === true &&
21556
+ events.some(
21557
+ (event) =>
21558
+ event.eventType === RUN_SCHEDULER_OUTBOX_EVENT_TYPE.runnerLogs &&
21559
+ (event.eventId.startsWith('runner-terminal-logs:') ||
21560
+ event.eventId.startsWith('terminal-log-chunk:')),
21561
+ );
21455
21562
  const unconfirmedTransportError = claimed.transportOwnershipUnconfirmed
21456
21563
  ? new RunLedgerProjectionLeaseLostError('delivery_lease_not_current')
21457
21564
  : null;
@@ -21521,6 +21628,10 @@ async function dispatchRunLedgerOutboxBatchWithScope(
21521
21628
  const appendAttempt: { transportAttemptId: string | null } = {
21522
21629
  transportAttemptId: null,
21523
21630
  };
21631
+ // Fixed-shape diagnostic timings for the search-only terminal-log carrier.
21632
+ // These are process-local observations and never enter durable run state.
21633
+ let terminalLogMaterializeTransactionCommitMs: number | null = null;
21634
+ let terminalLogAcknowledgeMs: number | null = null;
21524
21635
  const callerOnRequestAttempt = input.runtimeApi?.onRequestAttempt;
21525
21636
  const materializeDeferredObservability = async () => {
21526
21637
  if (
@@ -21579,6 +21690,7 @@ async function dispatchRunLedgerOutboxBatchWithScope(
21579
21690
  },
21580
21691
  ),
21581
21692
  );
21693
+ const materializeStartedAtMs = performance.now();
21582
21694
  const checkoutStartedAtMs = performance.now();
21583
21695
  let materializeEnteredAtMs: number | null = null;
21584
21696
  try {
@@ -21639,6 +21751,14 @@ async function dispatchRunLedgerOutboxBatchWithScope(
21639
21751
  (searchPhaseTimingsMs.materializeAcquireAndRetry ?? 0) +
21640
21752
  (materializeEnteredAtMs ?? performance.now()) -
21641
21753
  checkoutStartedAtMs;
21754
+ if (searchOnlyTerminalLogCarrier) {
21755
+ const elapsedMs = Math.max(
21756
+ 0,
21757
+ performance.now() - materializeStartedAtMs,
21758
+ );
21759
+ terminalLogMaterializeTransactionCommitMs =
21760
+ (terminalLogMaterializeTransactionCommitMs ?? 0) + elapsedMs;
21761
+ }
21642
21762
  }
21643
21763
  };
21644
21764
  try {
@@ -21701,20 +21821,31 @@ async function dispatchRunLedgerOutboxBatchWithScope(
21701
21821
  )
21702
21822
  )
21703
21823
  throw new Error('Search lease includes a lifecycle fact.');
21704
- const delivered = await observeSearchPhase('acknowledge', () =>
21705
- withClient(
21706
- (client) =>
21707
- markPostgresSchedulerOutboxDeliveredRows(
21708
- client,
21709
- events.map((event) => event.eventId),
21710
- { ...options, leaseOwner },
21711
- ),
21712
- {
21713
- runId: events[0]!.runId,
21714
- phase: 'run_ledger_outbox_search_acknowledge',
21715
- },
21716
- ),
21717
- );
21824
+ const acknowledgeStartedAtMs = performance.now();
21825
+ let delivered: RunLedgerOutboxDeliveredRow[];
21826
+ try {
21827
+ delivered = await observeSearchPhase('acknowledge', () =>
21828
+ withClient(
21829
+ (client) =>
21830
+ markPostgresSchedulerOutboxDeliveredRows(
21831
+ client,
21832
+ events.map((event) => event.eventId),
21833
+ { ...options, leaseOwner },
21834
+ ),
21835
+ {
21836
+ runId: events[0]!.runId,
21837
+ phase: 'run_ledger_outbox_search_acknowledge',
21838
+ },
21839
+ ),
21840
+ );
21841
+ } finally {
21842
+ if (searchOnlyTerminalLogCarrier) {
21843
+ terminalLogAcknowledgeMs = Math.max(
21844
+ 0,
21845
+ performance.now() - acknowledgeStartedAtMs,
21846
+ );
21847
+ }
21848
+ }
21718
21849
  if (delivered.length !== events.length)
21719
21850
  throw new RunLedgerProjectionLeaseLostError(
21720
21851
  'delivery_lease_not_current',
@@ -22441,6 +22572,18 @@ async function dispatchRunLedgerOutboxBatchWithScope(
22441
22572
  continuationMs:
22442
22573
  acknowledgedAtMs === null ? null : settledAtMs - acknowledgedAtMs,
22443
22574
  durationMs: settledAtMs - dispatchStartedAtMs,
22575
+ ...(searchOnlyTerminalLogCarrier
22576
+ ? {
22577
+ // Keep this fixed-shape scalar packet on the existing diagnostic;
22578
+ // no per-event payload, rows, or durable fields are added.
22579
+ searchOnlyTerminalLogPhaseTimingsMs: {
22580
+ claim: Math.max(0, claimElapsedMs),
22581
+ materializeTransactionCommit:
22582
+ terminalLogMaterializeTransactionCommitMs,
22583
+ acknowledge: terminalLogAcknowledgeMs,
22584
+ },
22585
+ }
22586
+ : {}),
22444
22587
  ...(input.searchOnly ? { searchPhaseTimingsMs } : {}),
22445
22588
  // Which platform requests served the two app hops, so a slow
22446
22589
  // authority or append stage can be found in the app's request log.
@@ -119,6 +119,21 @@ export type RunLedgerOutboxRecoveryDeps = {
119
119
  logger?: RunLedgerOutboxRecoveryLogger;
120
120
  };
121
121
 
122
+ /**
123
+ * Production runs two projector replicas at eight deliveries each. An
124
+ * isolated Preview estate has one replica, so use the existing bounded
125
+ * 32-delivery ceiling there to keep fixture admission and its catalog
126
+ * projection inside the same latency budget. This does not change Production
127
+ * capacity or the controller's hard maximum.
128
+ */
129
+ export function runLedgerOutboxRecoveryParallelism(
130
+ environment: 'preview' | 'production',
131
+ ): number {
132
+ return environment === 'preview'
133
+ ? RUN_LEDGER_OUTBOX_RECOVERY_MAX_IN_FLIGHT
134
+ : RUN_LEDGER_OUTBOX_RECOVERY_PRODUCTION_IN_FLIGHT;
135
+ }
136
+
122
137
  export type RunLedgerOutboxRecoveryController = {
123
138
  /**
124
139
  * Ask the singleton projector to re-read durable outbox work. This is
@@ -1011,6 +1026,7 @@ export async function deliverRunCreatedOutboxEventInline(input: {
1011
1026
  export function startRunLedgerOutboxRecoveryController(input: {
1012
1027
  poolOptions: RuntimeSchedulerPoolOptions;
1013
1028
  schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
1029
+ environment?: 'preview' | 'production';
1014
1030
  /** Direct LISTEN is a latency hint and may be disabled for isolated schemas. */
1015
1031
  useDirectNotify?: boolean;
1016
1032
  /** Observation hook for acknowledged rows (lag metrics). */
@@ -1071,10 +1087,12 @@ export function startRunLedgerOutboxRecoveryController(input: {
1071
1087
  dispatchObservabilityOnce: (runId) => dispatch('oldest_ready', true, runId),
1072
1088
  observabilityParallelism: RUN_LEDGER_OUTBOX_RECOVERY_INDEX_IN_FLIGHT,
1073
1089
  // `dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope` releases each
1074
- // short scheduler checkout before it calls the Vercel gateway. The two
1075
- // replicas therefore drain up to sixteen independent runs concurrently
1076
- // without turning one outbox notification into sixty-four head scans.
1077
- parallelism: RUN_LEDGER_OUTBOX_RECOVERY_PRODUCTION_IN_FLIGHT,
1090
+ // short scheduler checkout before it calls the Vercel gateway. Production
1091
+ // retains two replicas at eight slots each; one isolated Preview replica
1092
+ // uses the same controller's existing hard ceiling of 32.
1093
+ parallelism: runLedgerOutboxRecoveryParallelism(
1094
+ input.environment ?? 'production',
1095
+ ),
1078
1096
  notifyUnavailableSafetyWakeMs: runLedgerOutboxNotifySafetyWakeMs(
1079
1097
  schedulerOptions.schema,
1080
1098
  ),
@@ -335,7 +335,13 @@ function formatInsufficientCreditsMessage(input: {
335
335
  getStringField(input.billing, 'operation_id') ??
336
336
  getStringField(input.billing, 'operation') ??
337
337
  input.toolId;
338
- const balance = formatCreditAmount(input.billing.balance_credits);
338
+ // The explicit field is authoritative, including null. The older
339
+ // balance_credits field remains a fallback for already persisted errors.
340
+ const spendableBalance =
341
+ input.billing.spendable_balance_credits !== undefined
342
+ ? input.billing.spendable_balance_credits
343
+ : input.billing.balance_credits;
344
+ const balance = formatCreditAmount(spendableBalance);
339
345
  const required = formatCreditAmount(input.billing.required_credits);
340
346
  const recommended = formatCreditAmount(
341
347
  input.billing.recommended_add_credits ?? input.billing.needed_credits,
@@ -357,7 +363,7 @@ function formatInsufficientCreditsMessage(input: {
357
363
  typeof minimum === 'number' &&
358
364
  input.billing.required_credits === minimum
359
365
  ) {
360
- return `Workspace balance ${balance} is below the ${formatCreditAmount(minimum)}-credit workspace minimum for starting a billable call (${operation}); this is a workspace-wide minimum, separate from the tool's own price.${estimatedPrice}${addSuffix}`;
366
+ return `Spendable workspace balance ${balance} credits is below the ${formatCreditAmount(minimum)}-credit workspace minimum for starting a billable call (${operation}); this is a workspace-wide minimum, separate from the tool's own price.${estimatedPrice} Spendable credits exclude pending spending and can be lower than the posted balance shown by billing balance.${addSuffix}`;
361
367
  }
362
368
  // A required balance above the call's estimate is headroom, not a price.
363
369
  if (
@@ -365,9 +371,9 @@ function formatInsufficientCreditsMessage(input: {
365
371
  typeof input.billing.required_credits === 'number' &&
366
372
  input.billing.required_credits > estimate
367
373
  ) {
368
- return `Workspace balance ${balance} is below the ${required}-credit balance required to start ${operation}; this is a required balance, not the price.${estimatedPrice}${addSuffix}`;
374
+ return `Spendable workspace balance ${balance} credits is below the ${required}-credit balance required to start ${operation}; this is a required balance, not the price.${estimatedPrice} Spendable credits exclude pending spending and can be lower than the posted balance shown by billing balance.${addSuffix}`;
369
375
  }
370
- return `Workspace balance ${balance} < required ${required} for ${operation}.${addSuffix}`;
376
+ return `Spendable workspace balance ${balance} credits < required ${required} for ${operation}. Spendable credits exclude pending spending and can be lower than the posted balance shown by billing balance.${addSuffix}`;
371
377
  }
372
378
 
373
379
  /** An HTML document, as an edge proxy (Cloudflare, Vercel) serves for a 5xx. */
@@ -561,6 +561,16 @@ function parseExecuteToolMetadata(
561
561
  extractors: readExtractors(record.extractors),
562
562
  targetGetters: readGetters(record.targetGetters),
563
563
  listExtractorPaths,
564
+ ...(isRecord(record.listExtractorAliases)
565
+ ? {
566
+ listExtractorAliases: Object.fromEntries(
567
+ Object.entries(record.listExtractorAliases).filter(
568
+ (entry): entry is [string, string] =>
569
+ typeof entry[1] === 'string' && entry[1].length > 0,
570
+ ),
571
+ ),
572
+ }
573
+ : {}),
564
574
  listIdentityGetters: readGetters(record.listIdentityGetters),
565
575
  };
566
576
  }
@@ -725,6 +735,9 @@ function candidateResultPaths(path: string): string[] {
725
735
  providerPath,
726
736
  normalizeResultPath(path),
727
737
  toV2RawToolOutputPathPreservingProviderData(path),
738
+ ...(path.startsWith('result.data.')
739
+ ? [`toolResponse.raw.${path.slice('result.data.'.length)}`]
740
+ : []),
728
741
  ];
729
742
  return candidates.filter(
730
743
  (candidate, index, all) =>
@@ -1142,6 +1155,7 @@ function coerceToEnum(
1142
1155
  function resolveListRows(
1143
1156
  result: unknown,
1144
1157
  listExtractorPaths: readonly string[] | undefined,
1158
+ aliases?: Readonly<Record<string, string>>,
1145
1159
  ): Record<string, { path: string; rows: Record<string, unknown>[] }> {
1146
1160
  const lists: Record<
1147
1161
  string,
@@ -1178,6 +1192,9 @@ function resolveListRows(
1178
1192
  }
1179
1193
  lists[listName] = resolved;
1180
1194
  }
1195
+ for (const [alias, source] of Object.entries(aliases ?? {})) {
1196
+ if (!lists[alias] && lists[source]) lists[alias] = lists[source];
1197
+ }
1181
1198
  return lists;
1182
1199
  }
1183
1200
 
@@ -1546,6 +1563,7 @@ export function createToolExecuteResult<TResult = unknown>(input: {
1546
1563
  const resolvedLists = resolveListRows(
1547
1564
  resultRoot,
1548
1565
  input.metadata.listExtractorPaths,
1566
+ input.metadata.listExtractorAliases,
1549
1567
  );
1550
1568
  const lists = buildLists(resolvedLists, input.metadata);
1551
1569
  const metadata = {
@@ -1553,6 +1571,9 @@ export function createToolExecuteResult<TResult = unknown>(input: {
1553
1571
  execution: input.execution,
1554
1572
  targets,
1555
1573
  listExtractorPaths: [...(input.metadata.listExtractorPaths ?? [])],
1574
+ ...(input.metadata.listExtractorAliases
1575
+ ? { listExtractorAliases: { ...input.metadata.listExtractorAliases } }
1576
+ : {}),
1556
1577
  ...(input.metadata.extractors
1557
1578
  ? { extractors: input.metadata.extractors }
1558
1579
  : {}),
@@ -1849,6 +1870,7 @@ function serializedListRowsFromRaw(input: {
1849
1870
  const resolved = resolveListRows(
1850
1871
  { toolResponse: { raw: input.raw } },
1851
1872
  input.metadata.listExtractorPaths,
1873
+ input.metadata.listExtractorAliases,
1852
1874
  );
1853
1875
  const entries = Object.entries(resolved).map(([name, list]) => [
1854
1876
  name,
@@ -2134,6 +2156,9 @@ function metadataInputFromToolExecuteResult(
2134
2156
  listExtractorPaths:
2135
2157
  value._metadata.listExtractorPaths ??
2136
2158
  Object.values(value._metadata.lists).map((list) => list.path),
2159
+ ...(value._metadata.listExtractorAliases
2160
+ ? { listExtractorAliases: { ...value._metadata.listExtractorAliases } }
2161
+ : {}),
2137
2162
  listIdentityGetters: Object.fromEntries(
2138
2163
  Object.values(value._metadata.lists)
2139
2164
  .flatMap((list) => Object.entries(list.keys))
@@ -2479,6 +2504,53 @@ export function cloneToolExecuteResultWithExecution<TResult>(
2479
2504
  }) as ToolExecuteResult<TResult>;
2480
2505
  }
2481
2506
 
2507
+ /**
2508
+ * Add current list getter declarations when replaying a result written by an
2509
+ * older catalog. Stored declarations keep their precedence; the canonical
2510
+ * provider response remains the source for any newly exposed getters.
2511
+ */
2512
+ export function mergeToolExecuteResultListGetterMetadata(
2513
+ value: ToolExecuteResult,
2514
+ currentMetadata: Pick<
2515
+ ToolResultMetadataInput,
2516
+ 'listExtractorPaths' | 'listExtractorAliases'
2517
+ >,
2518
+ ): ToolExecuteResult {
2519
+ const serialized = serializeToolExecuteResult(value);
2520
+ const storedMetadata = serialized.metadata;
2521
+ const listExtractorPaths = [
2522
+ ...new Set([
2523
+ ...(storedMetadata.listExtractorPaths ?? []),
2524
+ ...(currentMetadata.listExtractorPaths ?? []),
2525
+ ]),
2526
+ ];
2527
+ const listExtractorAliases = {
2528
+ ...(currentMetadata.listExtractorAliases ?? {}),
2529
+ ...(storedMetadata.listExtractorAliases ?? {}),
2530
+ };
2531
+ const hasAliases = Object.keys(listExtractorAliases).length > 0;
2532
+ if (
2533
+ sameJsonValue(
2534
+ listExtractorPaths,
2535
+ storedMetadata.listExtractorPaths ?? [],
2536
+ ) &&
2537
+ sameJsonValue(
2538
+ hasAliases ? listExtractorAliases : undefined,
2539
+ storedMetadata.listExtractorAliases,
2540
+ )
2541
+ ) {
2542
+ return value;
2543
+ }
2544
+ return deserializeToolExecuteResult({
2545
+ ...serialized,
2546
+ metadata: {
2547
+ ...storedMetadata,
2548
+ listExtractorPaths,
2549
+ ...(hasAliases ? { listExtractorAliases } : {}),
2550
+ },
2551
+ });
2552
+ }
2553
+
2482
2554
  /**
2483
2555
  * Native splitters return the logical source response; the historical adapter
2484
2556
  * stores it inside an extra `{ data }` layer. Keep that immutable receipt and