chainlesschain 0.163.5 → 0.163.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/completions/_cc +1 -1
  2. package/completions/cc.bash +1 -1
  3. package/completions/cc.fish +1 -1
  4. package/completions/cc.ps1 +1 -1
  5. package/package.json +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-Cv2EE1Zy.js → AIOps-axqQfwv8.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-CcpxMLuW.js → ActionButton-DV-Ro0By.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DE8mJoMI.js → Analytics-Bt72WEVu.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-DKn9dk9L.js → AppLayout-BtepG55E.js} +5 -5
  10. package/src/assets/web-panel/assets/{Artifacts-Cp3mG3Py.js → Artifacts-B7KaJ7S_.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-zvsQfF2C.js → Audit-FNFaUg7A.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-Bi85SsjN.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-Cw47Dj6s.js → Backup-CC7YBhyj.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-C5Ip2afH.js → BaseInput-RkgTNVtj.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-C0iAFkJv.js → Chat-hHEmGZy-.js} +5 -5
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BV9Wv7wO.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-3KZZCiAF.js → Checkbox-z5wXOQP1.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-C8OMbBAz.js → Codegen-DVaDJnxy.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-B-MB84LJ.js → Col-CJmaRDR5.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-DpR7Dxao.js → Community-DF29_2bI.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-CyDtz0OS.js → Compact-DtkQkBzn.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-5tiRa2An.js → Compliance-xoHDxp7I.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-DnQaKZ5x.js → Cowork-BUm-R7ht.js} +4 -4
  24. package/src/assets/web-panel/assets/{Cron-BaWfoEDf.js → Cron-Cgl14ZkB.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-Bu0HoHhb.js → Crosschain-BNcbmoqh.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-31zk8upX.js → DID-Bwja0Zo9.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-BmVne-e2.js → Dashboard-Bk5XjEdJ.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-B4PkoOWd.js → Dropdown-Ci36Oitq.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-BZ6tyFtI.js → EmailListRenderer--U04ZN4q.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Osozn4Ds.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-DaH8TOAP.js → Federation-C4KghYn7.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-tbU0uwis.js → FormItemContext-QL7x_Q_0.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-Df8LQBrL.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-DsMB6-14.js → Git-2HassK5k.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-BnH0dZzd.js → Governance-DoBIp7oe.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-DhnN1N_P.js → Inference--W01QhZ-.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-DnNshAgn.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BmBFX3_N.js → Logs-BBJ4yAel.js} +2 -2
  39. package/src/assets/web-panel/assets/{MarkdownRenderer-DCslfQU3.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
  40. package/src/assets/web-panel/assets/{Marketplace-CPEgWDc3.js → Marketplace-D5lNngSI.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-C36qjgF9.js → McpTools-CkEVOMKH.js} +5 -5
  42. package/src/assets/web-panel/assets/{Memory-tNHvrgqN.js → Memory-B1Ow1xVE.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-Xaf6cKjc.js → MobileBridge-SgYbUfb6.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-D2E5TTeO.js → MobileProjects-BWdOvQAj.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-D4AJ2dwV.js → Mtc-DcTXSSma.js} +2 -2
  46. package/src/assets/web-panel/assets/{MtcAudit-Df-gtn2_.js → MtcAudit-Dt3WG4oj.js} +4 -4
  47. package/src/assets/web-panel/assets/{Multisig-DTCHLWld.js → Multisig-B9B38UZx.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-DruZ_1Jb.js → NLProgramming-B0S4GGvG.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-Dx3pdGDs.js → Notes-DZ5U58Ng.js} +4 -4
  50. package/src/assets/web-panel/assets/{NotificationSettings-Bi3L69oL.js → NotificationSettings-PjL5ZW34.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-BHY33m7C.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-BG2ZVEV-.js → Organization-Cv1GYs-c.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-Viwtg4Mn.js → Overflow-Bbkp_PL3.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-Clm_yc1Z.js → P2P-BD8QUgoZ.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-NKmmDiOw.js → PdhVaultBrowser-BO-2PeaB.js} +5 -5
  56. package/src/assets/web-panel/assets/{Permissions-DIL8u9Nk.js → Permissions-D_OPVa02.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-B-jM4wt-.js → PersonalDataHub-mh-pEmaB.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-627I__H5.js → Pipeline-6JC2EDdb.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-2DN2qrkW.js → Privacy-CUshzUAG.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-CN4wC0sF.js → ProjectInit-Cs2Aalri.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-K35vsh5H.js → ProjectSettings-1ak49tcj.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-B_wQadhX.js → Projects-BV4oApsm.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-MckaQUhQ.js → Providers-CXXxvafH.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-BS3mtA_O.js → QrScannerModal-PTcDsF58.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-BHp_nofL.js → QuickAsk-CDwucrJw.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-DlmnPuZj.js → Recommend-CJlLVRXR.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-tsyFbvpy.js → RemoteSession-DlN-ETsO.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-Ba1iWv2D.js → Reputation-DI23LR6m.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-NnlGC--f.js → Row-Cl7UQMrM.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-CjThSIRv.js → RssFeed-CexHNfh8.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-CojUyC5x.js → Search-BneBWvRN.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-c-sxeJEc.js → Security-DX7Phgrp.js} +4 -4
  73. package/src/assets/web-panel/assets/{Services-CtcKEEmF.js → Services-BWXO8Mte.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-CIBBXflN.js → Skeleton-DCyOjKPJ.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-D1XOOK9n.js → Skills-CXqERsTB.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-hRfEp5Sn.js → Sla-bhbrbz8E.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-Dexb1f_6.js → SpeechSettings-3IPU0HBZ.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-BJsSFGYy.js → SyncSettings-DBNTQLbM.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-CfSZ8A1L.js → Tasks-quP0bkN-.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-idDf47W-.js → Templates-BouWmgho.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-C-UsX6w6.js → Tenant-qv0D9s7z.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-BgJtX5n1.js → Terminal-BxvbE8BB.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-DP0BLKTl.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-LwCzqETp.js → Tokens-CFMKKyVG.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-D_9tVch0.js → Trigger-L8as3UuJ.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-BH2UlEWh.js → Trust-MxmA4VZG.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-CIpD0nYb.js → UkeySign-C0VvJYRi.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-9ShhYxK_.js → VideoEditing-Bf5QZj8N.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-C7afGNIG.js → Wallet-BIJ9riRz.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-bUKn6i5R.js → WebAuthn-B8p9Mhkw.js} +4 -4
  91. package/src/assets/web-panel/assets/{WorkflowEditor-CxgQaGyp.js → WorkflowEditor-BVsuaPen.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-DMVkmJwx.js → chat-BfkjoKpV.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-CDzQh5nS.js → colors-DeAlsWK8.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-C51kEF4D.js → compact-item-Cs97bz5j.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-BTk9bXBn.js → createContext-BqbG60LH.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-CMje33rb.js → hasIn-i_RY0b1X.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DpcrFxMI.js → index-0-agYlWL.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BW4F5YDF.js → index-79V_zv1s.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DRG-aqOs.js → index-B-3tEpTc.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BCRvG9lq.js → index-B-JVTrpr.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DgivOKHA.js → index-B1CbOH1i.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BzCkNylB.js → index-B5UkyA3c.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-x53Vf7la.js → index-BESLFt4q.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CCLj9hcD.js → index-BOVkf1K3.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CypsfcEq.js → index-BYUVuwKt.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BtlEqHLP.js → index-BcUXAB2s.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Ld_4mFmC.js → index-BkBi5TmC.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DqU9njjT.js → index-Bm7lLR4c.js} +1 -1
  110. package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
  111. package/src/assets/web-panel/assets/{index-CwGPwjUu.js → index-BuLQobzW.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C5IWTMVl.js → index-CE2VtTlp.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DJfnCuMq.js → index-CED2PL_y.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-hg_emBsi.js → index-CG3PRyAL.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-togc_uYn.js → index-CIG8fQ2_.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C4rQCS8T.js → index-COdGA-8n.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-0Auljb10.js → index-CeWoNHau.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Cd0hmPxD.js → index-Cf3WCLmy.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BXQBPLyE.js → index-CgsqdnRV.js} +1 -1
  120. package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
  121. package/src/assets/web-panel/assets/{index-h31MwrME.js → index-D2B4VPyP.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C1oZReaY.js → index-D9evtvg2.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CfC-piWZ.js → index-DUUrv141.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D9rC-fGW.js → index-DiSkx4UB.js} +3 -3
  125. package/src/assets/web-panel/assets/{index-CvSeVzqe.js → index-Dk2qouAz.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CtM8OT_7.js → index-Dk7-v4hP.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BBz9LIMS.js → index-DmbfhJUP.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BAfY7m7i.js → index-DqBgBpgp.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BVePe_Rf.js → index-PT_5sBMA.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CDLkNkLj.js → index-U4f5izHz.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BTlsE8Df.js → index-YXgLng0K.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BeEaAEIp.js → index-ZxO_-E8a.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-B0-zMtKA.js → index-dIHr4B_u.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-C1oZQmAA.js → index-gyFpvL7l.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-DRLQyp2A.js → index-s1opAL-M.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-OS-vfbTA.js → index-yObj1O4n.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-vrA8K-ac.js → initDefaultProps-C9h9yKL3.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-ByEzwIAl.js → motion-BvmLsPdu.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-Ck4AWjw-.js → move-CoL4ekU1.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-6p0l9SrB.js → mtc-parser-C4pMy6xV.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-B1CUPQ6R.js → omit-DPuJfNxx.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-B1STg1iW.js → pickAttrs-D0KEeaK2.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-DTTllkBV.js → placementArrow-BpkyINxt.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-AIrrCfBb.js → responsiveObserve-DQI_wOq8.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-C_fPMjdR.js → slide-DYfdDiRg.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-D5ow2yIP.js → statusUtils-B5dtvb0D.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-Dy3hEA--.js → styleChecker-CF310mSj.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-WfzcfSh4.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-CKSejfas.js → useFs-C7_iyvJK.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-od3zpPBN.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-DRD_2DFA.js → vnode-D3dsi0lZ.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-DmNrEtgf.js → zoom-WdbOlvdQ.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +5 -5
  155. package/src/command-manifest.json +1 -1
  156. package/src/commands/agenda.js +76 -4
  157. package/src/commands/agent.js +90 -1
  158. package/src/commands/automation.js +462 -3
  159. package/src/commands/background-session.js +102 -1
  160. package/src/commands/chat.js +11 -2
  161. package/src/commands/compact.js +106 -23
  162. package/src/commands/cowork.js +6 -1
  163. package/src/commands/loop.js +187 -38
  164. package/src/commands/routine.js +12 -5
  165. package/src/commands/scheduler-daemon.js +807 -0
  166. package/src/commands/session-observability.js +305 -0
  167. package/src/commands/session.js +38 -0
  168. package/src/data/changelog.json +28 -2
  169. package/src/gateways/ws/ws-agent-handler.js +439 -5
  170. package/src/harness/jsonl-session-store.js +463 -125
  171. package/src/harness/prompt-compressor.js +86 -13
  172. package/src/harness/provider-backed-compaction.js +100 -27
  173. package/src/lib/advisor-runtime.js +3 -1
  174. package/src/lib/agent-schedule-store.js +737 -47
  175. package/src/lib/agent-team/team-adjudication.js +40 -1
  176. package/src/lib/automation-center-routines.js +363 -0
  177. package/src/lib/automation-center.js +435 -0
  178. package/src/lib/automation-engine.js +568 -19
  179. package/src/lib/automation-execution-authority.js +736 -0
  180. package/src/lib/background-agent-supervisor.js +871 -215
  181. package/src/lib/background-command-argv.js +24 -0
  182. package/src/lib/background-interaction-journal.js +361 -52
  183. package/src/lib/background-interaction-resolver.js +32 -0
  184. package/src/lib/background-launch-profile.js +2 -0
  185. package/src/lib/background-needs-input-incident.js +221 -0
  186. package/src/lib/causal-observability.js +2661 -0
  187. package/src/lib/chat-session-admission.js +76 -0
  188. package/src/lib/cost-budget.js +5 -7
  189. package/src/lib/cowork/ab-comparator-cli.js +11 -0
  190. package/src/lib/cowork/debate-review-cli.js +12 -0
  191. package/src/lib/cowork-adapter.js +159 -38
  192. package/src/lib/cowork-cron.js +673 -19
  193. package/src/lib/cowork-observe.js +5 -1
  194. package/src/lib/cowork-task-runner.js +112 -17
  195. package/src/lib/delivery-coordinator.js +142 -0
  196. package/src/lib/direct-model-usage.js +121 -0
  197. package/src/lib/file-lines.js +183 -47
  198. package/src/lib/llm-pricing.js +12 -17
  199. package/src/lib/loop.js +62 -3
  200. package/src/lib/permission-engine.js +14 -7
  201. package/src/lib/public-ide-capability-manifest.js +2 -0
  202. package/src/lib/routine-store.js +546 -86
  203. package/src/lib/runtime-usage-ledger.js +182 -0
  204. package/src/lib/scheduler-kernel/agenda-adapter.js +604 -29
  205. package/src/lib/scheduler-kernel/authority-resolver.js +163 -0
  206. package/src/lib/scheduler-kernel/automation-adapter.js +994 -0
  207. package/src/lib/scheduler-kernel/automation-event-adapter.js +757 -0
  208. package/src/lib/scheduler-kernel/contract.js +11 -3
  209. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +343 -15
  210. package/src/lib/scheduler-kernel/loop-adapter.js +913 -0
  211. package/src/lib/scheduler-kernel/migration-admin.js +346 -0
  212. package/src/lib/scheduler-kernel/migration-rollback-admin.js +932 -0
  213. package/src/lib/scheduler-kernel/routine-adapter.js +691 -22
  214. package/src/lib/scheduler-kernel/runtime.js +63 -1
  215. package/src/lib/scheduler-kernel/service.js +328 -0
  216. package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
  217. package/src/lib/scheduler-kernel/store.js +3439 -206
  218. package/src/lib/session-host-lease.js +171 -2
  219. package/src/lib/session-host-snapshot.js +14 -3
  220. package/src/lib/session-persistence-failure.js +1 -0
  221. package/src/lib/session-tail.js +179 -34
  222. package/src/lib/session-transcript-structure.js +96 -0
  223. package/src/lib/sub-agent-context.js +328 -22
  224. package/src/lib/ws-agent-handler.js +4 -1
  225. package/src/repl/agent-repl.js +732 -91
  226. package/src/repl/chat-repl.js +5 -1
  227. package/src/runtime/agent-core.js +1274 -208
  228. package/src/runtime/coding-agent-contract-shared.cjs +5 -0
  229. package/src/runtime/fallback-model.js +5 -0
  230. package/src/runtime/headless-runner.js +324 -82
  231. package/src/runtime/headless-stream.js +232 -26
  232. package/src/runtime/policies/agent-policy.js +4 -0
  233. package/src/workers/background-agent-worker.js +468 -98
  234. package/src/assets/web-panel/assets/devWarning-CdwPwYj8.js +0 -1
  235. package/src/assets/web-panel/assets/index-BEeOdfEe.js +0 -1
  236. package/src/assets/web-panel/assets/index-BTe0TOu6.js +0 -1
@@ -0,0 +1,2661 @@
1
+ /**
2
+ * Bounded, secret-free causal observability projections.
3
+ *
4
+ * Session projections are reduced while readVerifiedProjection() holds the
5
+ * transcript lock. They never retain prompts, tool arguments/results or raw
6
+ * events. Delivery snapshots must already have passed restoreDeliveryFlow().
7
+ */
8
+
9
+ import crypto from "node:crypto";
10
+ import { PRICE_TABLE, priceRollup } from "./llm-pricing.js";
11
+ import { canonicalDeliveryJson } from "./delivery-evidence.js";
12
+ import { createSessionTranscriptStructureProjection } from "./session-transcript-structure.js";
13
+
14
+ export const CAUSAL_OBSERVABILITY_SCHEMA =
15
+ "chainlesschain.causal-observability-report";
16
+ export const CAUSAL_OBSERVABILITY_VERSION = 1;
17
+ export const CAUSAL_OBSERVABILITY_REQUEST_SCHEMA =
18
+ "chainlesschain.causal-observability-request";
19
+
20
+ const MAX_DURATION_MS = 7 * 24 * 60 * 60 * 1000;
21
+ const MAX_ID_LENGTH = 256;
22
+ const MAX_MODEL_LENGTH = 128;
23
+ const MAX_PROVIDER_LENGTH = 64;
24
+ const MAX_TOOL_LENGTH = 256;
25
+ const MAX_REQUEST_DELIVERIES = 128;
26
+ const MAX_DELIVERY_ASSOCIATIONS = 128;
27
+ const MAX_REPORT_SESSIONS = 512;
28
+ const MAX_REPORT_MODEL_ROWS = 4096;
29
+ const MAX_REPORT_TOOL_ROWS = 16384;
30
+ const MAX_DELIVERY_SELECTED_GATES = 256;
31
+ const MAX_DELIVERY_GATE_RESULTS = 256;
32
+ const MAX_DELIVERY_GATE_MATRIX_CELLS = 1024;
33
+ const MAX_DELIVERY_PREVIEW_ARTIFACTS = 256;
34
+ const MAX_REPORT_GATE_ROWS = 4096;
35
+ const MAX_REPORT_GATE_MATRIX_CELLS = 16384;
36
+ const MAX_REPORT_PREVIEW_ARTIFACTS = 4096;
37
+ const MAX_DISTINCT_MODELS = 1024;
38
+ const MAX_DISTINCT_TOOLS = 4096;
39
+ const MAX_TOOL_DURATION_SAMPLES = 100_000;
40
+ const MAX_SAFE_TOKEN_BUDGET = Number.MAX_SAFE_INTEGER;
41
+ const SCOPE_KEYS = Object.freeze(["workspaceId", "teamId", "policyId"]);
42
+ const REQUEST_KEYS = Object.freeze([
43
+ "schema",
44
+ "version",
45
+ "deliveryStates",
46
+ "filter",
47
+ "budgets",
48
+ ]);
49
+ const BUDGET_KEYS = Object.freeze([
50
+ "maxTokens",
51
+ "maxUsd",
52
+ "maxRetries",
53
+ "maxRetryRatio",
54
+ "maxToolP95Ms",
55
+ ]);
56
+ const DELIVERY_STATUSES = new Set([
57
+ "active",
58
+ "blocked",
59
+ "stopped",
60
+ "completed",
61
+ ]);
62
+ const DELIVERY_PHASES = new Set([
63
+ "gates",
64
+ "preview",
65
+ "review",
66
+ "fix",
67
+ "pr",
68
+ "ci",
69
+ "evidence",
70
+ "merge",
71
+ "archive",
72
+ "completed",
73
+ ]);
74
+ const TOKEN_FIELD_ALIASES = Object.freeze([
75
+ "input_tokens",
76
+ "prompt_tokens",
77
+ "inputTokens",
78
+ "output_tokens",
79
+ "completion_tokens",
80
+ "outputTokens",
81
+ "cache_read_input_tokens",
82
+ "cacheReadTokens",
83
+ "cache_creation_input_tokens",
84
+ "cacheCreationTokens",
85
+ "total_tokens",
86
+ "totalTokens",
87
+ ]);
88
+ const USAGE_SOURCES = new Set(["model", "semantic-compaction", "subagent"]);
89
+ const RETRY_REASONS = new Set([
90
+ "timeout",
91
+ "dns",
92
+ "connection_refused",
93
+ "network_unreachable",
94
+ "connection_reset",
95
+ "unknown",
96
+ ]);
97
+
98
+ function digest(value) {
99
+ return `sha256:${crypto
100
+ .createHash("sha256")
101
+ .update(canonicalDeliveryJson(value))
102
+ .digest("hex")}`;
103
+ }
104
+
105
+ function compareStrings(left, right) {
106
+ const a = String(left);
107
+ const b = String(right);
108
+ return a < b ? -1 : a > b ? 1 : 0;
109
+ }
110
+
111
+ function assertKnownOwnKeys(value, allowed, field) {
112
+ const allowedKeys = new Set(allowed);
113
+ for (const key of Reflect.ownKeys(value)) {
114
+ if (typeof key !== "string" || !allowedKeys.has(key)) {
115
+ throw new Error(`${field} contains unsupported key: ${String(key)}`);
116
+ }
117
+ }
118
+ }
119
+
120
+ function safeInteger(value, field, { min = 0, required = true } = {}) {
121
+ if (value == null && !required) return null;
122
+ if (!Number.isSafeInteger(value) || value < min) {
123
+ throw new Error(`${field} must be a safe integer >= ${min}`);
124
+ }
125
+ return value;
126
+ }
127
+
128
+ function finiteNonnegative(value, field) {
129
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
130
+ throw new Error(`${field} must be a finite non-negative number`);
131
+ }
132
+ return value;
133
+ }
134
+
135
+ function exactCommit(value, field, { required = false } = {}) {
136
+ if (value == null || value === "") {
137
+ if (required) throw new Error(`${field} is required`);
138
+ return null;
139
+ }
140
+ if (
141
+ typeof value !== "string" ||
142
+ !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(value)
143
+ ) {
144
+ throw new Error(`${field} must be an exact commit SHA`);
145
+ }
146
+ return value.toLowerCase();
147
+ }
148
+
149
+ function sha256Digest(value, field, { required = false } = {}) {
150
+ if (value == null || value === "") {
151
+ if (required) throw new Error(`${field} is required`);
152
+ return null;
153
+ }
154
+ if (typeof value !== "string" || !/^sha256:[0-9a-f]{64}$/i.test(value)) {
155
+ throw new Error(`${field} must be a SHA-256 digest`);
156
+ }
157
+ return value.toLowerCase();
158
+ }
159
+
160
+ function hasControlCharacters(value) {
161
+ for (const character of value) {
162
+ const codePoint = character.codePointAt(0);
163
+ if (codePoint <= 0x1f || codePoint === 0x7f) return true;
164
+ }
165
+ return false;
166
+ }
167
+
168
+ function asId(
169
+ value,
170
+ field,
171
+ { required = false, maxLength = MAX_ID_LENGTH } = {},
172
+ ) {
173
+ if (value == null) {
174
+ if (required) throw new Error(`${field} is required`);
175
+ return null;
176
+ }
177
+ if (typeof value !== "string") throw new Error(`${field} must be a string`);
178
+ const normalized = value.trim();
179
+ if (!normalized) throw new Error(`${field} must not be blank`);
180
+ if (normalized.length > maxLength || hasControlCharacters(normalized)) {
181
+ throw new Error(`${field} is invalid`);
182
+ }
183
+ return normalized;
184
+ }
185
+
186
+ function boundedLabel(value, field, max, { fallback = null } = {}) {
187
+ if (value == null || value === "") return fallback;
188
+ if (typeof value !== "string") throw new Error(`${field} must be a string`);
189
+ const normalized = value.trim();
190
+ if (
191
+ !normalized ||
192
+ normalized.length > max ||
193
+ hasControlCharacters(normalized)
194
+ ) {
195
+ throw new Error(`${field} is invalid`);
196
+ }
197
+ return normalized;
198
+ }
199
+
200
+ function normalizePricingTable(table = PRICE_TABLE) {
201
+ if (!table || typeof table !== "object" || Array.isArray(table)) {
202
+ throw new Error("pricing table must be an object");
203
+ }
204
+ const providers = Object.entries(table);
205
+ if (providers.length > 256) {
206
+ throw new Error("pricing table exceeds the provider limit");
207
+ }
208
+ let entryCount = 0;
209
+ const normalized = new Map();
210
+ for (const [provider, entries] of providers) {
211
+ const providerKey = boundedLabel(
212
+ provider,
213
+ "pricing provider",
214
+ MAX_PROVIDER_LENGTH,
215
+ ).toLowerCase();
216
+ if (normalized.has(providerKey)) {
217
+ throw new Error(`pricing table has duplicate provider: ${providerKey}`);
218
+ }
219
+ if (!Array.isArray(entries)) {
220
+ throw new Error(`pricing entries for ${providerKey} must be an array`);
221
+ }
222
+ entryCount += entries.length;
223
+ if (entryCount > 4096) {
224
+ throw new Error("pricing table exceeds the entry limit");
225
+ }
226
+ const seenMatches = new Set();
227
+ const normalizedEntries = entries.map((entry) => {
228
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
229
+ throw new Error("pricing entry must be an object");
230
+ }
231
+ const match = boundedLabel(
232
+ entry.match,
233
+ "pricing match",
234
+ MAX_MODEL_LENGTH,
235
+ ).toLowerCase();
236
+ if (seenMatches.has(match)) {
237
+ throw new Error(
238
+ `pricing entries for ${providerKey} duplicate match: ${match}`,
239
+ );
240
+ }
241
+ seenMatches.add(match);
242
+ const input = entry.in;
243
+ const output = entry.out;
244
+ if (
245
+ typeof input !== "number" ||
246
+ !Number.isFinite(input) ||
247
+ input < 0 ||
248
+ typeof output !== "number" ||
249
+ !Number.isFinite(output) ||
250
+ output < 0
251
+ ) {
252
+ throw new Error("pricing rates must be finite non-negative numbers");
253
+ }
254
+ return { match, in: input, out: output };
255
+ });
256
+ normalized.set(providerKey, normalizedEntries);
257
+ }
258
+ return Object.fromEntries(
259
+ [...normalized.entries()].sort(([left], [right]) =>
260
+ compareStrings(left, right),
261
+ ),
262
+ );
263
+ }
264
+
265
+ function normalizeScope(scope, field = "scope", { requireAny = false } = {}) {
266
+ if (scope != null && (typeof scope !== "object" || Array.isArray(scope))) {
267
+ throw new Error(`${field} must be an object`);
268
+ }
269
+ const value = scope || {};
270
+ assertKnownOwnKeys(value, SCOPE_KEYS, field);
271
+ const normalized = Object.fromEntries(
272
+ SCOPE_KEYS.map((key) => [key, asId(value[key], `${field}.${key}`)]),
273
+ );
274
+ if (requireAny && SCOPE_KEYS.every((key) => normalized[key] === null)) {
275
+ throw new Error(`${field} must define at least one scope dimension`);
276
+ }
277
+ return normalized;
278
+ }
279
+
280
+ function sameScope(left, right) {
281
+ const a = normalizeScope(left, "delivery.scope", { requireAny: true });
282
+ const b = normalizeScope(right, "session.scope", { requireAny: true });
283
+ return SCOPE_KEYS.every((key) => a[key] === b[key]);
284
+ }
285
+
286
+ function normalizeBudgetNumber(value, field, { max = Number.MAX_VALUE } = {}) {
287
+ if (value == null) return null;
288
+ if (
289
+ typeof value !== "number" ||
290
+ !Number.isFinite(value) ||
291
+ value < 0 ||
292
+ value > max
293
+ ) {
294
+ throw new Error(`${field} must be a finite non-negative number`);
295
+ }
296
+ return value;
297
+ }
298
+
299
+ function normalizeBudgetInteger(value, field) {
300
+ const normalized = normalizeBudgetNumber(value, field, {
301
+ max: MAX_SAFE_TOKEN_BUDGET,
302
+ });
303
+ if (normalized != null && !Number.isSafeInteger(normalized)) {
304
+ throw new Error(`${field} must be a non-negative safe integer`);
305
+ }
306
+ return normalized;
307
+ }
308
+
309
+ export function normalizeCausalBudgets(budgets) {
310
+ if (
311
+ budgets != null &&
312
+ (typeof budgets !== "object" || Array.isArray(budgets))
313
+ ) {
314
+ throw new Error("budgets must be an object");
315
+ }
316
+ const value = budgets || {};
317
+ assertKnownOwnKeys(value, BUDGET_KEYS, "budgets");
318
+ return {
319
+ maxTokens: normalizeBudgetInteger(value.maxTokens, "budgets.maxTokens"),
320
+ maxUsd: normalizeBudgetNumber(value.maxUsd, "budgets.maxUsd"),
321
+ maxRetries: normalizeBudgetInteger(value.maxRetries, "budgets.maxRetries"),
322
+ maxRetryRatio: normalizeBudgetNumber(
323
+ value.maxRetryRatio,
324
+ "budgets.maxRetryRatio",
325
+ { max: 1 },
326
+ ),
327
+ maxToolP95Ms: normalizeBudgetNumber(
328
+ value.maxToolP95Ms,
329
+ "budgets.maxToolP95Ms",
330
+ { max: MAX_DURATION_MS },
331
+ ),
332
+ };
333
+ }
334
+
335
+ export function normalizeCausalRequest(request = {}) {
336
+ if (!request || typeof request !== "object" || Array.isArray(request)) {
337
+ throw new Error("observability request must be an object");
338
+ }
339
+ assertKnownOwnKeys(request, REQUEST_KEYS, "observability request");
340
+ if (
341
+ request.schema != null &&
342
+ request.schema !== CAUSAL_OBSERVABILITY_REQUEST_SCHEMA
343
+ ) {
344
+ throw new Error("observability request schema is unsupported");
345
+ }
346
+ if (request.version != null && request.version !== 1) {
347
+ throw new Error("observability request version is unsupported");
348
+ }
349
+ if (
350
+ !Array.isArray(request.deliveryStates) ||
351
+ request.deliveryStates.length === 0
352
+ ) {
353
+ throw new Error("deliveryStates must contain at least one snapshot path");
354
+ }
355
+ if (request.deliveryStates.length > MAX_REQUEST_DELIVERIES) {
356
+ throw new Error(
357
+ `deliveryStates exceeds the ${MAX_REQUEST_DELIVERIES} snapshot limit`,
358
+ );
359
+ }
360
+ const deliveryStates = [
361
+ ...new Set(
362
+ request.deliveryStates.map((value) =>
363
+ asId(value, "deliveryStates entry", {
364
+ required: true,
365
+ maxLength: 4096,
366
+ }),
367
+ ),
368
+ ),
369
+ ].sort(compareStrings);
370
+ return {
371
+ schema: CAUSAL_OBSERVABILITY_REQUEST_SCHEMA,
372
+ version: 1,
373
+ deliveryStates,
374
+ filter: normalizeScope(request.filter, "filter"),
375
+ budgets: normalizeCausalBudgets(request.budgets),
376
+ };
377
+ }
378
+
379
+ export function scopeMatches(scope, filter) {
380
+ const actual = normalizeScope(scope, "scope");
381
+ const wanted = normalizeScope(filter, "filter");
382
+ return SCOPE_KEYS.every(
383
+ (key) => wanted[key] == null || actual[key] === wanted[key],
384
+ );
385
+ }
386
+
387
+ function observedDuration(event) {
388
+ const data = event?.data;
389
+ if (data == null || typeof data !== "object" || Array.isArray(data)) {
390
+ return null;
391
+ }
392
+ const observed = [];
393
+ for (const key of ["duration_ms", "durationMs"]) {
394
+ if (Object.hasOwn(data, key)) observed.push(data[key]);
395
+ }
396
+ const telemetry = data.result?.toolTelemetryRecord;
397
+ if (
398
+ telemetry &&
399
+ typeof telemetry === "object" &&
400
+ !Array.isArray(telemetry) &&
401
+ Object.hasOwn(telemetry, "durationMs")
402
+ ) {
403
+ observed.push(telemetry.durationMs);
404
+ }
405
+ if (observed.length === 0) return null;
406
+ for (const value of observed) {
407
+ if (
408
+ typeof value !== "number" ||
409
+ !Number.isFinite(value) ||
410
+ value < 0 ||
411
+ value > MAX_DURATION_MS
412
+ ) {
413
+ throw new Error(
414
+ `session duration must be a finite number between 0 and ${MAX_DURATION_MS}`,
415
+ );
416
+ }
417
+ }
418
+ if (new Set(observed).size > 1) {
419
+ throw new Error("session duration aliases are inconsistent");
420
+ }
421
+ return Math.round(observed[0]);
422
+ }
423
+
424
+ export function nearestRankPercentile(values, percentile) {
425
+ const finite = (values || [])
426
+ .map(Number)
427
+ .filter((value) => Number.isFinite(value) && value >= 0)
428
+ .sort((a, b) => a - b);
429
+ if (finite.length === 0) return null;
430
+ const rank = Math.max(1, Math.ceil(finite.length * Number(percentile)));
431
+ return finite[Math.min(finite.length, rank) - 1];
432
+ }
433
+
434
+ function nearestRankLowerBoundWithMissingZeros(
435
+ values,
436
+ missingCount,
437
+ percentile,
438
+ ) {
439
+ if (!Number.isSafeInteger(missingCount) || missingCount < 0) {
440
+ throw new Error("missing percentile samples must be a safe integer");
441
+ }
442
+ const finite = (values || [])
443
+ .map(Number)
444
+ .filter((value) => Number.isFinite(value) && value >= 0)
445
+ .sort((a, b) => a - b);
446
+ const population = checkedAdd(
447
+ finite.length,
448
+ missingCount,
449
+ "percentile lower-bound population",
450
+ );
451
+ if (population === 0) return null;
452
+ const rank = Math.max(1, Math.ceil(population * Number(percentile)));
453
+ const observedRank = rank - missingCount;
454
+ return observedRank <= 0 ? 0 : finite[observedRank - 1];
455
+ }
456
+
457
+ function tokenField(raw, aliases, field, { required = false } = {}) {
458
+ const observed = [];
459
+ for (const alias of aliases) {
460
+ if (!Object.hasOwn(raw, alias)) continue;
461
+ const value = raw[alias];
462
+ if (
463
+ typeof value !== "number" ||
464
+ !Number.isSafeInteger(value) ||
465
+ value < 0
466
+ ) {
467
+ throw new Error(
468
+ `session usage ${field} must be a non-negative safe integer`,
469
+ );
470
+ }
471
+ observed.push(value);
472
+ }
473
+ if (observed.length > 1) {
474
+ throw new Error(
475
+ `session usage ${field} aliases are inconsistent or ambiguous`,
476
+ );
477
+ }
478
+ if (required && observed.length === 0) {
479
+ throw new Error(`session usage ${field} is required`);
480
+ }
481
+ return observed[0] ?? 0;
482
+ }
483
+
484
+ function usageIdentity(data, raw, key, field, maxLength) {
485
+ const outerOwn = Object.hasOwn(data, key);
486
+ const innerOwn = raw !== data && Object.hasOwn(raw, key);
487
+ const outer = outerOwn
488
+ ? boundedLabel(data[key], `${field} outer`, maxLength)
489
+ : null;
490
+ const inner = innerOwn
491
+ ? boundedLabel(raw[key], `${field} inner`, maxLength)
492
+ : null;
493
+ if (
494
+ outerOwn &&
495
+ innerOwn &&
496
+ (outer ?? "").toLowerCase() !== (inner ?? "").toLowerCase()
497
+ ) {
498
+ throw new Error(`${field} authorities are inconsistent`);
499
+ }
500
+ return outerOwn ? outer : inner;
501
+ }
502
+
503
+ function normalizedUsageSource(value, field, { missing = "model" } = {}) {
504
+ if (value === undefined) return missing;
505
+ if (typeof value !== "string" || !USAGE_SOURCES.has(value)) {
506
+ throw new Error(`${field} is unsupported`);
507
+ }
508
+ return value;
509
+ }
510
+
511
+ function usageSourceIdentity(data, raw) {
512
+ const outerOwn = Object.hasOwn(data, "source");
513
+ const innerOwn = raw !== data && Object.hasOwn(raw, "source");
514
+ const outer = outerOwn
515
+ ? normalizedUsageSource(data.source, "session usage source")
516
+ : null;
517
+ const inner = innerOwn
518
+ ? normalizedUsageSource(raw.source, "session usage source")
519
+ : null;
520
+ if (outerOwn && innerOwn && outer !== inner) {
521
+ throw new Error("session usage source authorities are inconsistent");
522
+ }
523
+ return outerOwn ? outer : innerOwn ? inner : "model";
524
+ }
525
+
526
+ function modelUsageBoundaryIdentity(data, field) {
527
+ return {
528
+ provider: boundedLabel(
529
+ data.provider,
530
+ `${field} provider`,
531
+ MAX_PROVIDER_LENGTH,
532
+ ),
533
+ model: boundedLabel(data.model, `${field} model`, MAX_MODEL_LENGTH),
534
+ source: normalizedUsageSource(data.source, `${field} source`),
535
+ };
536
+ }
537
+
538
+ function sameModelUsageIdentity(left, right) {
539
+ return (
540
+ left.provider === right.provider &&
541
+ left.model === right.model &&
542
+ left.source === right.source
543
+ );
544
+ }
545
+
546
+ function strictUsage(event) {
547
+ if (
548
+ !["token_usage", "assistant_message", "llm_call", "llm_response"].includes(
549
+ event?.type,
550
+ )
551
+ ) {
552
+ return null;
553
+ }
554
+ if (
555
+ event.data != null &&
556
+ (typeof event.data !== "object" || Array.isArray(event.data))
557
+ ) {
558
+ throw new Error("session usage event data must be an object");
559
+ }
560
+ const data = event.data || {};
561
+ const hasUsage = Object.hasOwn(data, "usage");
562
+ const hasTokenUsage = Object.hasOwn(data, "tokenUsage");
563
+ if (hasUsage && hasTokenUsage) {
564
+ throw new Error("session usage payload aliases are ambiguous");
565
+ }
566
+ for (const [key, present] of [
567
+ ["usage", hasUsage],
568
+ ["tokenUsage", hasTokenUsage],
569
+ ]) {
570
+ if (
571
+ present &&
572
+ (!data[key] || typeof data[key] !== "object" || Array.isArray(data[key]))
573
+ ) {
574
+ throw new Error(`session ${key} payload must be an object`);
575
+ }
576
+ }
577
+ const hasOuterTokens = TOKEN_FIELD_ALIASES.some((alias) =>
578
+ Object.hasOwn(data, alias),
579
+ );
580
+ if ((hasUsage || hasTokenUsage) && hasOuterTokens) {
581
+ throw new Error("session usage outer token fields are ambiguous");
582
+ }
583
+ const raw = hasUsage
584
+ ? data.usage
585
+ : hasTokenUsage
586
+ ? data.tokenUsage
587
+ : event.type === "token_usage"
588
+ ? data
589
+ : null;
590
+ if (raw == null) return null;
591
+ if (typeof raw !== "object" || Array.isArray(raw)) {
592
+ throw new Error("session usage payload must be an object");
593
+ }
594
+ const inputTokens = tokenField(
595
+ raw,
596
+ ["input_tokens", "prompt_tokens", "inputTokens"],
597
+ "inputTokens",
598
+ { required: true },
599
+ );
600
+ const outputTokens = tokenField(
601
+ raw,
602
+ ["output_tokens", "completion_tokens", "outputTokens"],
603
+ "outputTokens",
604
+ { required: true },
605
+ );
606
+ const cacheReadTokens = tokenField(
607
+ raw,
608
+ ["cache_read_input_tokens", "cacheReadTokens"],
609
+ "cacheReadTokens",
610
+ );
611
+ const cacheCreationTokens = tokenField(
612
+ raw,
613
+ ["cache_creation_input_tokens", "cacheCreationTokens"],
614
+ "cacheCreationTokens",
615
+ );
616
+ const expectedTotal = inputTokens + outputTokens;
617
+ if (!Number.isSafeInteger(expectedTotal)) {
618
+ throw new Error("session usage totalTokens exceeds the safe integer limit");
619
+ }
620
+ const totalAliases = ["total_tokens", "totalTokens"];
621
+ const hasSuppliedTotal = totalAliases.some((alias) =>
622
+ Object.hasOwn(raw, alias),
623
+ );
624
+ if (hasSuppliedTotal) {
625
+ const suppliedTotal = tokenField(raw, totalAliases, "totalTokens");
626
+ if (suppliedTotal !== expectedTotal) {
627
+ throw new Error("session usage totalTokens is inconsistent");
628
+ }
629
+ }
630
+ const callId = Object.hasOwn(data, "callId")
631
+ ? asId(data.callId, "session usage callId", { required: true })
632
+ : null;
633
+ if (
634
+ callId == null &&
635
+ inputTokens === 0 &&
636
+ outputTokens === 0 &&
637
+ cacheReadTokens === 0 &&
638
+ cacheCreationTokens === 0
639
+ ) {
640
+ return null;
641
+ }
642
+ return {
643
+ callId,
644
+ provider: usageIdentity(
645
+ data,
646
+ raw,
647
+ "provider",
648
+ "session usage provider",
649
+ MAX_PROVIDER_LENGTH,
650
+ ),
651
+ model: usageIdentity(
652
+ data,
653
+ raw,
654
+ "model",
655
+ "session usage model",
656
+ MAX_MODEL_LENGTH,
657
+ ),
658
+ source: usageSourceIdentity(data, raw),
659
+ inputTokens,
660
+ outputTokens,
661
+ totalTokens: expectedTotal,
662
+ cacheReadTokens,
663
+ cacheCreationTokens,
664
+ };
665
+ }
666
+
667
+ function checkedAdd(left, right, field) {
668
+ const value = left + right;
669
+ if (!Number.isSafeInteger(value) || value < 0) {
670
+ throw new Error(`${field} exceeds the safe integer limit`);
671
+ }
672
+ return value;
673
+ }
674
+
675
+ function nestedRow(rowMap, provider, model, createRow) {
676
+ const providerKey = provider ?? null;
677
+ const modelKey = model ?? null;
678
+ let models = rowMap.get(providerKey);
679
+ if (!models) {
680
+ models = new Map();
681
+ rowMap.set(providerKey, models);
682
+ }
683
+ let row = models.get(modelKey);
684
+ if (!row) {
685
+ row = createRow();
686
+ models.set(modelKey, row);
687
+ }
688
+ return row;
689
+ }
690
+
691
+ function nestedRows(rowMap) {
692
+ return [...rowMap.values()].flatMap((models) => [...models.values()]);
693
+ }
694
+
695
+ function emptyUsageSums() {
696
+ return {
697
+ inputTokens: 0,
698
+ outputTokens: 0,
699
+ totalTokens: 0,
700
+ cacheReadTokens: 0,
701
+ cacheCreationTokens: 0,
702
+ budgetTokens: 0,
703
+ calls: 0,
704
+ };
705
+ }
706
+
707
+ function addUsageSums(target, usage, field) {
708
+ for (const key of [
709
+ "inputTokens",
710
+ "outputTokens",
711
+ "totalTokens",
712
+ "cacheReadTokens",
713
+ "cacheCreationTokens",
714
+ ]) {
715
+ target[key] = checkedAdd(target[key], usage[key], `${field}.${key}`);
716
+ }
717
+ target.budgetTokens = checkedAdd(
718
+ target.budgetTokens,
719
+ checkedAdd(
720
+ usage.totalTokens,
721
+ checkedAdd(
722
+ usage.cacheReadTokens,
723
+ usage.cacheCreationTokens,
724
+ `${field}.cacheTokens`,
725
+ ),
726
+ `${field}.budgetTokens`,
727
+ ),
728
+ `${field}.budgetTokens`,
729
+ );
730
+ target.calls = checkedAdd(target.calls, 1, `${field}.calls`);
731
+ }
732
+
733
+ function firstAndLastTime(current, timestamp) {
734
+ const value = Number(timestamp);
735
+ if (!Number.isFinite(value)) return current;
736
+ return {
737
+ first: current.first == null ? value : Math.min(current.first, value),
738
+ last: current.last == null ? value : Math.max(current.last, value),
739
+ };
740
+ }
741
+
742
+ function observedIsoTime(value, field) {
743
+ if (value == null) return null;
744
+ const date = new Date(value);
745
+ if (!Number.isFinite(date.getTime())) {
746
+ throw new Error(`${field} is outside the supported timestamp range`);
747
+ }
748
+ return date.toISOString();
749
+ }
750
+
751
+ /** Create a bounded streaming reducer for readVerifiedProjection(). */
752
+ export function createVerifiedSessionObservabilityProjection(
753
+ sessionId,
754
+ { pricingTable } = {},
755
+ ) {
756
+ const id = asId(sessionId, "sessionId", { required: true });
757
+ const effectivePricingTable = normalizePricingTable(pricingTable);
758
+ const usageTotal = emptyUsageSums();
759
+ const usageByModel = new Map();
760
+ const toolRows = new Map();
761
+ const durations = [];
762
+ const retryByReason = new Map();
763
+ const retryByModel = new Map();
764
+ const unknownUsageByCode = new Map();
765
+ const modelUsageCalls = new Map();
766
+ const toolUsageCalls = new Map();
767
+ const transcriptStructure = createSessionTranscriptStructureProjection(id, {
768
+ failFast: true,
769
+ });
770
+ let scope = null;
771
+ let sessionUsageDefaults = { provider: null, model: null };
772
+ let callLedgerRequired = false;
773
+ let sawModelUsageStarted = false;
774
+ let times = { first: null, last: null };
775
+ let totalRetries = 0;
776
+ let retryDurationMs = 0;
777
+ let totalToolCalls = 0;
778
+ let totalToolErrors = 0;
779
+ let totalToolDurationMs = 0;
780
+ let timedToolCalls = 0;
781
+ let retryToolCalls = 0;
782
+ let pendingTool = null;
783
+ let toolFailures = new Map();
784
+ let acceptedEvents = 0;
785
+ let finished = false;
786
+ let toolTelemetryComplete = true;
787
+ let unknownToolEvents = 0;
788
+
789
+ const recordUnknownUsage = (code, count = 1) => {
790
+ unknownUsageByCode.set(
791
+ code,
792
+ checkedAdd(
793
+ unknownUsageByCode.get(code) || 0,
794
+ count,
795
+ `session ${id} unknown usage evidence`,
796
+ ),
797
+ );
798
+ };
799
+
800
+ const toolRow = (name) => {
801
+ let row = toolRows.get(name);
802
+ if (!row) {
803
+ if (toolRows.size >= MAX_DISTINCT_TOOLS) {
804
+ throw new Error(`session ${id} exceeds the distinct tool limit`);
805
+ }
806
+ row = { tool: name, calls: 0, errors: 0, retries: 0 };
807
+ toolRows.set(name, row);
808
+ }
809
+ return row;
810
+ };
811
+
812
+ const recordDuration = (row, duration) => {
813
+ if (duration == null) return;
814
+ if (durations.length >= MAX_TOOL_DURATION_SAMPLES) {
815
+ throw new Error(
816
+ `session ${id} exceeds the ${MAX_TOOL_DURATION_SAMPLES} tool timing sample limit`,
817
+ );
818
+ }
819
+ durations.push(duration);
820
+ totalToolDurationMs = checkedAdd(
821
+ totalToolDurationMs,
822
+ duration,
823
+ "session tools.totalDurationMs",
824
+ );
825
+ timedToolCalls = checkedAdd(timedToolCalls, 1, "session tools.timedCalls");
826
+ row.durationMs = checkedAdd(
827
+ row.durationMs || 0,
828
+ duration,
829
+ "session tool.durationMs",
830
+ );
831
+ row.timedCalls = checkedAdd(
832
+ row.timedCalls || 0,
833
+ 1,
834
+ "session tool.timedCalls",
835
+ );
836
+ };
837
+
838
+ const recordUnknownToolEvent = () => {
839
+ toolTelemetryComplete = false;
840
+ unknownToolEvents = checkedAdd(
841
+ unknownToolEvents,
842
+ 1,
843
+ "session unknown tool events",
844
+ );
845
+ };
846
+
847
+ const markToolFailure = (name) => {
848
+ const previous = toolFailures.get(name);
849
+ toolFailures.set(name, {
850
+ active: true,
851
+ generation: checkedAdd(
852
+ previous?.generation || 0,
853
+ 1,
854
+ "session tool failure generation",
855
+ ),
856
+ });
857
+ };
858
+
859
+ const clearToolFailure = (name, retryGeneration) => {
860
+ if (retryGeneration == null) return;
861
+ const current = toolFailures.get(name);
862
+ if (current?.active === true && current.generation === retryGeneration) {
863
+ toolFailures.set(name, { ...current, active: false });
864
+ }
865
+ };
866
+
867
+ return {
868
+ accept(event) {
869
+ transcriptStructure.accept(event);
870
+ acceptedEvents = checkedAdd(
871
+ acceptedEvents,
872
+ 1,
873
+ `session ${id} observed event count`,
874
+ );
875
+ times = firstAndLastTime(times, event?.timestamp);
876
+ if (event?.type === "session_start" && scope === null) {
877
+ if (!event.data?.observabilityScope) {
878
+ throw new Error(`session ${id} has no observabilityScope authority`);
879
+ }
880
+ scope = normalizeScope(
881
+ event.data.observabilityScope,
882
+ "session_start.observabilityScope",
883
+ { requireAny: true },
884
+ );
885
+ const provider = boundedLabel(
886
+ event.data.provider,
887
+ "session_start provider",
888
+ MAX_PROVIDER_LENGTH,
889
+ );
890
+ const model = boundedLabel(
891
+ event.data.model,
892
+ "session_start model",
893
+ MAX_MODEL_LENGTH,
894
+ );
895
+ sessionUsageDefaults = { provider, model };
896
+ const hasTelemetryProtocol = Object.hasOwn(
897
+ event.data,
898
+ "usageTelemetryProtocol",
899
+ );
900
+ const hasTelemetryVersion = Object.hasOwn(
901
+ event.data,
902
+ "usageTelemetryVersion",
903
+ );
904
+ if (hasTelemetryProtocol !== hasTelemetryVersion) {
905
+ throw new Error(
906
+ "session usage telemetry protocol marker is incomplete",
907
+ );
908
+ }
909
+ if (hasTelemetryProtocol) {
910
+ if (
911
+ event.data.usageTelemetryProtocol !== "call-ledger" ||
912
+ event.data.usageTelemetryVersion !== 1
913
+ ) {
914
+ throw new Error("session usage telemetry protocol is unsupported");
915
+ }
916
+ callLedgerRequired = true;
917
+ }
918
+ } else if (event?.type === "session_start") {
919
+ throw new Error(`session ${id} contains multiple session_start events`);
920
+ }
921
+
922
+ if (event?.type === "model_usage_started") {
923
+ if (
924
+ !event.data ||
925
+ typeof event.data !== "object" ||
926
+ Array.isArray(event.data)
927
+ ) {
928
+ throw new Error("model_usage_started data must be an object");
929
+ }
930
+ const callId = asId(event.data.callId, "model_usage_started callId", {
931
+ required: true,
932
+ });
933
+ const identity = modelUsageBoundaryIdentity(
934
+ event.data,
935
+ "model_usage_started",
936
+ );
937
+ if (modelUsageCalls.has(callId)) {
938
+ throw new Error(`model usage callId already exists: ${callId}`);
939
+ }
940
+ if (modelUsageCalls.size >= MAX_TOOL_DURATION_SAMPLES) {
941
+ throw new Error(
942
+ `session ${id} exceeds the pending model usage limit`,
943
+ );
944
+ }
945
+ modelUsageCalls.set(callId, { status: "started", ...identity });
946
+ sawModelUsageStarted = true;
947
+ } else if (event?.type === "model_usage_unknown") {
948
+ if (
949
+ !event.data ||
950
+ typeof event.data !== "object" ||
951
+ Array.isArray(event.data)
952
+ ) {
953
+ throw new Error("model_usage_unknown data must be an object");
954
+ }
955
+ const callId = asId(event.data.callId, "model_usage_unknown callId", {
956
+ required: true,
957
+ });
958
+ const identity = modelUsageBoundaryIdentity(
959
+ event.data,
960
+ "model_usage_unknown",
961
+ );
962
+ const previous = modelUsageCalls.get(callId);
963
+ if (previous && previous.status !== "started") {
964
+ throw new Error(
965
+ `model usage callId has conflicting settlement: ${callId}`,
966
+ );
967
+ }
968
+ if (previous && !sameModelUsageIdentity(previous, identity)) {
969
+ throw new Error(
970
+ `model usage callId changed provider/model/source identity: ${callId}`,
971
+ );
972
+ }
973
+ if (!previous && callLedgerRequired) {
974
+ throw new Error(`model usage settlement has no start: ${callId}`);
975
+ }
976
+ if (!previous && modelUsageCalls.size >= MAX_TOOL_DURATION_SAMPLES) {
977
+ throw new Error(`session ${id} exceeds the model usage call limit`);
978
+ }
979
+ modelUsageCalls.set(callId, { status: "unknown", ...identity });
980
+ recordUnknownUsage("model-usage-unknown");
981
+ } else if (
982
+ event?.type === "compaction_usage_unknown" ||
983
+ event?.type === "compaction-usage-unknown"
984
+ ) {
985
+ if (
986
+ event.data != null &&
987
+ (typeof event.data !== "object" || Array.isArray(event.data))
988
+ ) {
989
+ throw new Error("compaction usage unknown data must be an object");
990
+ }
991
+ recordUnknownUsage("compaction-usage-unknown");
992
+ }
993
+
994
+ if (event?.type === "tool_call_started") {
995
+ if (
996
+ !event.data ||
997
+ typeof event.data !== "object" ||
998
+ Array.isArray(event.data)
999
+ ) {
1000
+ throw new Error("tool_call_started data must be an object");
1001
+ }
1002
+ const toolCallId = asId(event.data.id, "tool_call_started id", {
1003
+ required: true,
1004
+ });
1005
+ const tool = boundedLabel(
1006
+ event.data.tool,
1007
+ "tool_call_started tool",
1008
+ MAX_TOOL_LENGTH,
1009
+ { fallback: "?" },
1010
+ );
1011
+ if (toolUsageCalls.has(toolCallId)) {
1012
+ throw new Error(`tool call id already exists: ${toolCallId}`);
1013
+ }
1014
+ if (toolUsageCalls.size >= MAX_TOOL_DURATION_SAMPLES) {
1015
+ throw new Error(`session ${id} exceeds the tool call ledger limit`);
1016
+ }
1017
+ const failure = toolFailures.get(tool);
1018
+ toolUsageCalls.set(toolCallId, {
1019
+ status: "started",
1020
+ tool,
1021
+ retryGeneration: failure?.active === true ? failure.generation : null,
1022
+ });
1023
+ }
1024
+
1025
+ let usage = strictUsage(event);
1026
+ if (usage) {
1027
+ if (
1028
+ usage.provider == null &&
1029
+ usage.model == null &&
1030
+ sessionUsageDefaults.provider != null &&
1031
+ sessionUsageDefaults.model != null
1032
+ ) {
1033
+ usage = {
1034
+ ...usage,
1035
+ provider: sessionUsageDefaults.provider,
1036
+ model: sessionUsageDefaults.model,
1037
+ };
1038
+ }
1039
+ if (usage.callId) {
1040
+ const previous = modelUsageCalls.get(usage.callId);
1041
+ if (previous && previous.status !== "started") {
1042
+ throw new Error(
1043
+ `model usage callId has conflicting settlement: ${usage.callId}`,
1044
+ );
1045
+ }
1046
+ const identity = {
1047
+ provider: usage.provider,
1048
+ model: usage.model,
1049
+ source: usage.source,
1050
+ };
1051
+ if (previous && !sameModelUsageIdentity(previous, identity)) {
1052
+ throw new Error(
1053
+ `model usage callId changed provider/model/source identity: ${usage.callId}`,
1054
+ );
1055
+ }
1056
+ if (!previous && callLedgerRequired) {
1057
+ throw new Error(
1058
+ `model usage settlement has no start: ${usage.callId}`,
1059
+ );
1060
+ }
1061
+ if (!previous && modelUsageCalls.size >= MAX_TOOL_DURATION_SAMPLES) {
1062
+ throw new Error(`session ${id} exceeds the model usage call limit`);
1063
+ }
1064
+ modelUsageCalls.set(usage.callId, {
1065
+ status: "known",
1066
+ ...identity,
1067
+ });
1068
+ } else if (callLedgerRequired) {
1069
+ throw new Error("call-ledger token usage has no callId");
1070
+ }
1071
+ addUsageSums(usageTotal, usage, "session usage total");
1072
+ const existingRow = usageByModel
1073
+ .get(usage.provider ?? null)
1074
+ ?.get(usage.model ?? null);
1075
+ if (
1076
+ !existingRow &&
1077
+ nestedRows(usageByModel).length >= MAX_DISTINCT_MODELS
1078
+ ) {
1079
+ throw new Error(`session ${id} exceeds the distinct model limit`);
1080
+ }
1081
+ const row = nestedRow(
1082
+ usageByModel,
1083
+ usage.provider,
1084
+ usage.model,
1085
+ () => ({
1086
+ provider: usage.provider,
1087
+ model: usage.model,
1088
+ ...emptyUsageSums(),
1089
+ }),
1090
+ );
1091
+ addUsageSums(row, usage, "session usage model");
1092
+ }
1093
+
1094
+ if (event?.type === "tool_call") {
1095
+ const name = boundedLabel(
1096
+ event.data?.tool,
1097
+ "session tool name",
1098
+ MAX_TOOL_LENGTH,
1099
+ { fallback: "?" },
1100
+ );
1101
+ const toolCallId = Object.hasOwn(event.data || {}, "id")
1102
+ ? asId(event.data.id, "session tool call id", { required: true })
1103
+ : null;
1104
+ let startedCall = null;
1105
+ if (toolCallId) {
1106
+ const previous = toolUsageCalls.get(toolCallId);
1107
+ if (previous && previous.status !== "started") {
1108
+ throw new Error(
1109
+ `tool call id has conflicting settlement: ${toolCallId}`,
1110
+ );
1111
+ }
1112
+ if (previous && previous.tool !== name) {
1113
+ throw new Error(
1114
+ `tool call id changed tool identity: ${toolCallId}`,
1115
+ );
1116
+ }
1117
+ if (!previous) recordUnknownToolEvent();
1118
+ if (!previous && toolUsageCalls.size >= MAX_TOOL_DURATION_SAMPLES) {
1119
+ throw new Error(`session ${id} exceeds the tool call ledger limit`);
1120
+ }
1121
+ startedCall = previous || null;
1122
+ toolUsageCalls.set(toolCallId, {
1123
+ status: "settled",
1124
+ tool: name,
1125
+ retryGeneration: previous?.retryGeneration ?? null,
1126
+ });
1127
+ } else {
1128
+ recordUnknownToolEvent();
1129
+ }
1130
+ const row = toolRow(name);
1131
+ const retryGeneration = startedCall?.retryGeneration ?? null;
1132
+ const isRetry = retryGeneration != null;
1133
+ row.calls = checkedAdd(row.calls, 1, "session tool.calls");
1134
+ totalToolCalls = checkedAdd(
1135
+ totalToolCalls,
1136
+ 1,
1137
+ "session tools.totalCalls",
1138
+ );
1139
+ const isError = event.data?.is_error === true;
1140
+ if (isRetry) {
1141
+ row.retries = checkedAdd(row.retries, 1, "session tool.retries");
1142
+ retryToolCalls = checkedAdd(
1143
+ retryToolCalls,
1144
+ 1,
1145
+ "session tools.retryCalls",
1146
+ );
1147
+ }
1148
+ if (isError) {
1149
+ row.errors = checkedAdd(row.errors, 1, "session tool.errors");
1150
+ totalToolErrors = checkedAdd(
1151
+ totalToolErrors,
1152
+ 1,
1153
+ "session tools.totalErrors",
1154
+ );
1155
+ markToolFailure(name);
1156
+ } else {
1157
+ clearToolFailure(name, retryGeneration);
1158
+ }
1159
+ const duration = observedDuration(event);
1160
+ recordDuration(row, duration);
1161
+ pendingTool = {
1162
+ name,
1163
+ durationCounted: duration != null,
1164
+ errorCounted: isError,
1165
+ retryGeneration,
1166
+ };
1167
+ } else if (event?.type === "tool_result") {
1168
+ const name = boundedLabel(
1169
+ event.data?.tool,
1170
+ "session tool name",
1171
+ MAX_TOOL_LENGTH,
1172
+ { fallback: "?" },
1173
+ );
1174
+ const row = toolRow(name);
1175
+ const paired = pendingTool?.name === name ? pendingTool : null;
1176
+ const isError = Boolean(
1177
+ event.data?.error ||
1178
+ event.data?.result?.error ||
1179
+ event.data?.result?.is_error ||
1180
+ event.data?.result?.isError,
1181
+ );
1182
+ if (paired && isError && !paired.errorCounted) {
1183
+ row.errors = checkedAdd(row.errors, 1, "session tool.errors");
1184
+ totalToolErrors = checkedAdd(
1185
+ totalToolErrors,
1186
+ 1,
1187
+ "session tools.totalErrors",
1188
+ );
1189
+ }
1190
+ if (paired && !paired.errorCounted) {
1191
+ if (isError) markToolFailure(name);
1192
+ else clearToolFailure(name, paired.retryGeneration);
1193
+ }
1194
+ const duration = observedDuration(event);
1195
+ if (duration != null && paired && !paired.durationCounted) {
1196
+ recordDuration(row, duration);
1197
+ }
1198
+ pendingTool = null;
1199
+ } else if (event?.type === "user_message") {
1200
+ toolFailures = new Map();
1201
+ pendingTool = null;
1202
+ }
1203
+
1204
+ if (event?.type === "llm_retry") {
1205
+ recordUnknownUsage("llm-retry-usage-unknown");
1206
+ const reason = RETRY_REASONS.has(event.data?.reason)
1207
+ ? event.data.reason
1208
+ : "unknown";
1209
+ const provider = boundedLabel(
1210
+ event.data?.provider,
1211
+ "session retry provider",
1212
+ MAX_PROVIDER_LENGTH,
1213
+ );
1214
+ const model = boundedLabel(
1215
+ event.data?.model,
1216
+ "session retry model",
1217
+ MAX_MODEL_LENGTH,
1218
+ );
1219
+ const duration = observedDuration(event) || 0;
1220
+ totalRetries = checkedAdd(
1221
+ totalRetries,
1222
+ 1,
1223
+ "session retries.totalRetries",
1224
+ );
1225
+ retryDurationMs = checkedAdd(
1226
+ retryDurationMs,
1227
+ duration,
1228
+ "session retries.durationMs",
1229
+ );
1230
+ const reasonRow = retryByReason.get(reason) || {
1231
+ reason,
1232
+ retries: 0,
1233
+ durationMs: 0,
1234
+ };
1235
+ reasonRow.retries = checkedAdd(
1236
+ reasonRow.retries,
1237
+ 1,
1238
+ "session retry reason.retries",
1239
+ );
1240
+ reasonRow.durationMs = checkedAdd(
1241
+ reasonRow.durationMs,
1242
+ duration,
1243
+ "session retry reason.durationMs",
1244
+ );
1245
+ retryByReason.set(reason, reasonRow);
1246
+ const existingModelRow = retryByModel
1247
+ .get(provider ?? null)
1248
+ ?.get(model ?? null);
1249
+ if (
1250
+ !existingModelRow &&
1251
+ nestedRows(retryByModel).length >= MAX_DISTINCT_MODELS
1252
+ ) {
1253
+ throw new Error(
1254
+ `session ${id} exceeds the distinct retry model limit`,
1255
+ );
1256
+ }
1257
+ const modelRow = nestedRow(retryByModel, provider, model, () => ({
1258
+ provider,
1259
+ model,
1260
+ retries: 0,
1261
+ durationMs: 0,
1262
+ }));
1263
+ modelRow.retries = checkedAdd(
1264
+ modelRow.retries,
1265
+ 1,
1266
+ "session retry model.retries",
1267
+ );
1268
+ modelRow.durationMs = checkedAdd(
1269
+ modelRow.durationMs,
1270
+ duration,
1271
+ "session retry model.durationMs",
1272
+ );
1273
+ }
1274
+ },
1275
+ finish(authority = {}) {
1276
+ if (finished)
1277
+ throw new Error(`session ${id} projection is already finished`);
1278
+ finished = true;
1279
+ if (
1280
+ typeof authority.headHash !== "string" ||
1281
+ !/^[0-9a-f]{64}$/i.test(authority.headHash) ||
1282
+ !Number.isSafeInteger(authority.eventCount) ||
1283
+ authority.eventCount <= 0
1284
+ ) {
1285
+ throw new Error(`session ${id} has no verified transcript authority`);
1286
+ }
1287
+ if (authority.eventCount !== acceptedEvents) {
1288
+ throw new Error(
1289
+ `session ${id} verified event count does not match projection`,
1290
+ );
1291
+ }
1292
+ transcriptStructure.finish();
1293
+ if (!scope) {
1294
+ throw new Error(`session ${id} has no observabilityScope authority`);
1295
+ }
1296
+ const unsettledModelUsage = [...modelUsageCalls.values()].filter(
1297
+ (entry) => entry.status === "started",
1298
+ ).length;
1299
+ if (unsettledModelUsage > 0) {
1300
+ recordUnknownUsage("model-usage-unsettled", unsettledModelUsage);
1301
+ }
1302
+ if (callLedgerRequired && !sawModelUsageStarted) {
1303
+ recordUnknownUsage("call-ledger-evidence-missing");
1304
+ } else if (!callLedgerRequired) {
1305
+ recordUnknownUsage("call-ledger-protocol-undeclared");
1306
+ }
1307
+ const unsettledToolCalls = [...toolUsageCalls.values()].filter(
1308
+ (entry) => entry.status === "started",
1309
+ ).length;
1310
+ if (unsettledToolCalls > 0) {
1311
+ toolTelemetryComplete = false;
1312
+ unknownToolEvents = checkedAdd(
1313
+ unknownToolEvents,
1314
+ unsettledToolCalls,
1315
+ "session unknown tool events",
1316
+ );
1317
+ }
1318
+ const toolTimingPopulation = checkedAdd(
1319
+ totalToolCalls,
1320
+ unsettledToolCalls,
1321
+ "session tool timing population",
1322
+ );
1323
+ const missingToolDurations = toolTimingPopulation - timedToolCalls;
1324
+ const p95DurationLowerBoundMs = nearestRankLowerBoundWithMissingZeros(
1325
+ durations,
1326
+ missingToolDurations,
1327
+ 0.95,
1328
+ );
1329
+ const usage = {
1330
+ total: usageTotal,
1331
+ byModel: nestedRows(usageByModel).sort(
1332
+ (a, b) => b.totalTokens - a.totalTokens,
1333
+ ),
1334
+ };
1335
+ const priced = priceRollup(usage, { table: effectivePricingTable });
1336
+ for (const row of priced.byModel) {
1337
+ for (const field of [
1338
+ "cost",
1339
+ "inputCost",
1340
+ "outputCost",
1341
+ "cacheReadCost",
1342
+ "cacheCreationCost",
1343
+ ]) {
1344
+ if (!Number.isFinite(row[field]) || row[field] < 0) {
1345
+ throw new Error(`session ${id} has invalid priced ${field}`);
1346
+ }
1347
+ }
1348
+ }
1349
+ if (
1350
+ !Number.isFinite(priced.cost.totalCost) ||
1351
+ priced.cost.totalCost < 0
1352
+ ) {
1353
+ throw new Error(`session ${id} has invalid aggregate estimated cost`);
1354
+ }
1355
+ const unpriced = priced.byModel
1356
+ .filter((row) => !row.matched && row.budgetTokens > 0)
1357
+ .map((row) => ({
1358
+ provider: row.provider,
1359
+ model: row.model,
1360
+ totalTokens: row.totalTokens,
1361
+ cacheReadTokens: row.cacheReadTokens,
1362
+ cacheCreationTokens: row.cacheCreationTokens,
1363
+ budgetTokens: row.budgetTokens,
1364
+ }))
1365
+ .sort(
1366
+ (a, b) =>
1367
+ b.budgetTokens - a.budgetTokens ||
1368
+ compareStrings(
1369
+ `${a.provider || ""}/${a.model || ""}`,
1370
+ `${b.provider || ""}/${b.model || ""}`,
1371
+ ),
1372
+ );
1373
+ const byCalls = (a, b) =>
1374
+ b.calls - a.calls || compareStrings(a.tool, b.tool);
1375
+ const byRetries = (a, b) =>
1376
+ b.retries - a.retries ||
1377
+ b.durationMs - a.durationMs ||
1378
+ compareStrings(
1379
+ `${a.reason || a.provider || ""}/${a.model || ""}`,
1380
+ `${b.reason || b.provider || ""}/${b.model || ""}`,
1381
+ );
1382
+ return {
1383
+ id,
1384
+ authority: {
1385
+ verified: true,
1386
+ headHash: authority.headHash.toLowerCase(),
1387
+ eventCount: authority.eventCount,
1388
+ usageTelemetry: {
1389
+ protocolDeclared: callLedgerRequired,
1390
+ protocol: callLedgerRequired ? "call-ledger" : null,
1391
+ version: callLedgerRequired ? 1 : null,
1392
+ assurance: callLedgerRequired
1393
+ ? "call-ledger-bound"
1394
+ : "recorded-events-only",
1395
+ },
1396
+ },
1397
+ scope,
1398
+ startedAt: observedIsoTime(times.first, `session ${id} startedAt`),
1399
+ endedAt: observedIsoTime(times.last, `session ${id} endedAt`),
1400
+ usage: {
1401
+ ...priced.total,
1402
+ estimatedUsd: priced.cost.totalCost,
1403
+ currency: priced.cost.currency,
1404
+ pricingTableDigest: digest(effectivePricingTable),
1405
+ telemetryComplete: unknownUsageByCode.size === 0,
1406
+ unknownEvidence: {
1407
+ count: [...unknownUsageByCode.values()].reduce(
1408
+ (total, count) =>
1409
+ checkedAdd(total, count, `session ${id} unknown usage count`),
1410
+ 0,
1411
+ ),
1412
+ byCode: [...unknownUsageByCode.entries()]
1413
+ .map(([code, count]) => ({ code, count }))
1414
+ .sort((a, b) => compareStrings(a.code, b.code)),
1415
+ },
1416
+ unpriced,
1417
+ byModel: priced.byModel,
1418
+ },
1419
+ retries: {
1420
+ totalRetries,
1421
+ durationMs: retryDurationMs,
1422
+ byReason: [...retryByReason.values()].sort(byRetries),
1423
+ byModel: nestedRows(retryByModel).sort(byRetries),
1424
+ },
1425
+ tools: {
1426
+ totalCalls: totalToolCalls,
1427
+ totalErrors: totalToolErrors,
1428
+ totalDurationMs: totalToolDurationMs,
1429
+ timedCalls: timedToolCalls,
1430
+ retryCalls: retryToolCalls,
1431
+ byTool: [...toolRows.values()].sort(byCalls),
1432
+ p50DurationMs: nearestRankPercentile(durations, 0.5),
1433
+ p95DurationMs: nearestRankPercentile(durations, 0.95),
1434
+ p95DurationLowerBoundMs,
1435
+ timingCoverage:
1436
+ totalToolCalls > 0 ? timedToolCalls / totalToolCalls : 1,
1437
+ telemetryComplete: toolTelemetryComplete,
1438
+ unknownEvents: unknownToolEvents,
1439
+ percentileMethod: "nearest-rank",
1440
+ },
1441
+ };
1442
+ },
1443
+ };
1444
+ }
1445
+
1446
+ export function projectVerifiedSession(
1447
+ sessionId,
1448
+ events,
1449
+ authority = {},
1450
+ options = {},
1451
+ ) {
1452
+ const projection = createVerifiedSessionObservabilityProjection(
1453
+ sessionId,
1454
+ options,
1455
+ );
1456
+ for (const event of events || []) projection.accept(event);
1457
+ return projection.finish(authority);
1458
+ }
1459
+
1460
+ function projectGates(state) {
1461
+ const selectedSource = state.gateSelection?.selectedGateIds;
1462
+ if (!Array.isArray(selectedSource)) {
1463
+ throw new Error("delivery selected gates must be an array");
1464
+ }
1465
+ if (selectedSource.length > MAX_DELIVERY_SELECTED_GATES) {
1466
+ throw new Error(
1467
+ `delivery exceeds the ${MAX_DELIVERY_SELECTED_GATES} selected gate limit`,
1468
+ );
1469
+ }
1470
+ if (!Array.isArray(state.gateResults)) {
1471
+ throw new Error("delivery gate results must be an array");
1472
+ }
1473
+ if (state.gateResults.length > MAX_DELIVERY_GATE_RESULTS) {
1474
+ throw new Error(
1475
+ `delivery exceeds the ${MAX_DELIVERY_GATE_RESULTS} gate result limit`,
1476
+ );
1477
+ }
1478
+ const selectedIds = selectedSource
1479
+ .map((id) =>
1480
+ boundedLabel(id, "delivery selected gate id", MAX_ID_LENGTH, {
1481
+ fallback: "unknown",
1482
+ }),
1483
+ )
1484
+ .sort(compareStrings);
1485
+ let matrixCells = 0;
1486
+ const results = state.gateResults
1487
+ .map((result) => ({
1488
+ id: boundedLabel(result?.id, "delivery gate id", MAX_ID_LENGTH, {
1489
+ fallback: "unknown",
1490
+ }),
1491
+ status: boundedLabel(
1492
+ result?.status,
1493
+ "delivery gate status",
1494
+ MAX_ID_LENGTH,
1495
+ { fallback: "unknown" },
1496
+ ),
1497
+ commitSha: exactCommit(result?.commitSha, "delivery gate commitSha"),
1498
+ matrix: (() => {
1499
+ if (result?.matrix == null) return [];
1500
+ if (!Array.isArray(result.matrix)) {
1501
+ throw new Error("delivery gate matrix must be an array");
1502
+ }
1503
+ matrixCells += result.matrix.length;
1504
+ if (matrixCells > MAX_DELIVERY_GATE_MATRIX_CELLS) {
1505
+ throw new Error(
1506
+ `delivery exceeds the ${MAX_DELIVERY_GATE_MATRIX_CELLS} gate matrix cell limit`,
1507
+ );
1508
+ }
1509
+ return result.matrix;
1510
+ })()
1511
+ .map((cell) => ({
1512
+ id: boundedLabel(cell?.id, "delivery gate matrix id", MAX_ID_LENGTH, {
1513
+ fallback: "unknown",
1514
+ }),
1515
+ status: boundedLabel(
1516
+ cell?.status,
1517
+ "delivery gate matrix status",
1518
+ MAX_ID_LENGTH,
1519
+ { fallback: "unknown" },
1520
+ ),
1521
+ commitSha: exactCommit(
1522
+ cell?.commitSha,
1523
+ "delivery gate matrix commitSha",
1524
+ ),
1525
+ }))
1526
+ .sort((a, b) => compareStrings(a.id, b.id)),
1527
+ }))
1528
+ .sort((a, b) => compareStrings(a.id, b.id));
1529
+ return { selectedIds, results };
1530
+ }
1531
+
1532
+ function projectArtifacts(state) {
1533
+ if (!Array.isArray(state.previewArtifacts)) {
1534
+ throw new Error("delivery preview artifacts must be an array");
1535
+ }
1536
+ if (state.previewArtifacts.length > MAX_DELIVERY_PREVIEW_ARTIFACTS) {
1537
+ throw new Error(
1538
+ `delivery exceeds the ${MAX_DELIVERY_PREVIEW_ARTIFACTS} preview artifact limit`,
1539
+ );
1540
+ }
1541
+ return {
1542
+ preview: state.previewArtifacts.map((artifact, index) => ({
1543
+ id: `preview:${index + 1}`,
1544
+ kind: boundedLabel(artifact?.kind, "artifact kind", MAX_ID_LENGTH, {
1545
+ fallback: "unknown",
1546
+ }),
1547
+ tier: boundedLabel(artifact?.tier, "artifact tier", MAX_ID_LENGTH),
1548
+ })),
1549
+ evidence: state.evidence
1550
+ ? {
1551
+ artifactId: boundedLabel(
1552
+ state.evidence.artifact?.id,
1553
+ "evidence artifact id",
1554
+ MAX_ID_LENGTH,
1555
+ ),
1556
+ recordDigest: sha256Digest(
1557
+ state.evidence.record?.recordDigest,
1558
+ "evidence recordDigest",
1559
+ ),
1560
+ ready: state.evidence.readiness?.ready === true,
1561
+ }
1562
+ : null,
1563
+ };
1564
+ }
1565
+
1566
+ export function projectVerifiedDelivery(state) {
1567
+ if (!state || typeof state !== "object" || Array.isArray(state)) {
1568
+ throw new Error("delivery state must be an object");
1569
+ }
1570
+ const causality = state.causality || { scope: {}, sessions: [] };
1571
+ if (!Array.isArray(causality.sessions)) {
1572
+ throw new Error("delivery causal sessions must be an array");
1573
+ }
1574
+ if (causality.sessions.length > MAX_DELIVERY_ASSOCIATIONS) {
1575
+ throw new Error(
1576
+ `delivery exceeds the ${MAX_DELIVERY_ASSOCIATIONS} causal session limit`,
1577
+ );
1578
+ }
1579
+ const associationIds = new Set();
1580
+ const associations = causality.sessions
1581
+ .map((binding) => {
1582
+ const sessionId = asId(binding?.sessionId, "delivery sessionId", {
1583
+ required: true,
1584
+ });
1585
+ if (associationIds.has(sessionId)) {
1586
+ throw new Error(
1587
+ `delivery has duplicate session association: ${sessionId}`,
1588
+ );
1589
+ }
1590
+ associationIds.add(sessionId);
1591
+ return {
1592
+ sessionId,
1593
+ headHash:
1594
+ typeof binding?.headHash === "string" &&
1595
+ /^[0-9a-f]{64}$/i.test(binding.headHash)
1596
+ ? binding.headHash.toLowerCase()
1597
+ : (() => {
1598
+ throw new Error("delivery session headHash is invalid");
1599
+ })(),
1600
+ eventCount: safeInteger(
1601
+ binding?.eventCount,
1602
+ "delivery session eventCount",
1603
+ { min: 1 },
1604
+ ),
1605
+ };
1606
+ })
1607
+ .sort((a, b) => compareStrings(a.sessionId, b.sessionId));
1608
+ const status = boundedLabel(state.status, "delivery status", MAX_ID_LENGTH);
1609
+ if (!DELIVERY_STATUSES.has(status)) {
1610
+ throw new Error(`delivery status is unsupported: ${status}`);
1611
+ }
1612
+ const phase = boundedLabel(state.phase, "delivery phase", MAX_ID_LENGTH);
1613
+ if (!DELIVERY_PHASES.has(phase)) {
1614
+ throw new Error(`delivery phase is unsupported: ${phase}`);
1615
+ }
1616
+ return {
1617
+ id: asId(state.flowId, "delivery.flowId", { required: true }),
1618
+ authority: {
1619
+ verified: true,
1620
+ stateDigest: sha256Digest(state.stateDigest, "delivery stateDigest", {
1621
+ required: true,
1622
+ }),
1623
+ revision: safeInteger(state.revision, "delivery revision"),
1624
+ },
1625
+ scope: normalizeScope(causality.scope, "delivery.scope", {
1626
+ requireAny: associations.length > 0,
1627
+ }),
1628
+ associations,
1629
+ status,
1630
+ phase,
1631
+ commitSha: exactCommit(state.commitSha, "delivery commitSha", {
1632
+ required: true,
1633
+ }),
1634
+ diff: {
1635
+ baseCommitSha: exactCommit(
1636
+ state.diff?.baseCommitSha,
1637
+ "delivery diff baseCommitSha",
1638
+ ),
1639
+ headCommitSha: exactCommit(
1640
+ state.diff?.headCommitSha,
1641
+ "delivery diff headCommitSha",
1642
+ ),
1643
+ digest: sha256Digest(state.diff?.digest, "delivery diff digest"),
1644
+ changedFileCount: safeInteger(
1645
+ (Array.isArray(state.diff?.changedFiles) ? state.diff.changedFiles : [])
1646
+ .length,
1647
+ "delivery changedFileCount",
1648
+ ),
1649
+ },
1650
+ gates: projectGates(state),
1651
+ artifacts: projectArtifacts(state),
1652
+ pr: state.pr
1653
+ ? {
1654
+ number: safeInteger(state.pr.number, "delivery PR number", {
1655
+ min: 1,
1656
+ }),
1657
+ headCommitSha: exactCommit(
1658
+ state.pr.headCommitSha,
1659
+ "delivery PR headCommitSha",
1660
+ ),
1661
+ ciCommitSha: exactCommit(
1662
+ state.pr.ciCommitSha,
1663
+ "delivery PR ciCommitSha",
1664
+ ),
1665
+ branchProtectionSatisfied:
1666
+ state.pr.branchProtectionSatisfied === true,
1667
+ reviewApproved: state.pr.reviewApproved === true,
1668
+ requiredMatrixComplete: state.pr.requiredMatrixComplete === true,
1669
+ mergeAllowed: state.mergeDecision?.allow === true,
1670
+ }
1671
+ : null,
1672
+ merge: state.merge
1673
+ ? {
1674
+ merged: state.merge.merged === true,
1675
+ headCommitSha: exactCommit(
1676
+ state.merge.headCommitSha,
1677
+ "delivery merge headCommitSha",
1678
+ ),
1679
+ mergeCommitSha: exactCommit(
1680
+ state.merge.mergeCommitSha,
1681
+ "delivery merge mergeCommitSha",
1682
+ ),
1683
+ }
1684
+ : null,
1685
+ };
1686
+ }
1687
+
1688
+ function emptyTotals() {
1689
+ return {
1690
+ sessions: 0,
1691
+ deliveries: 0,
1692
+ inputTokens: 0,
1693
+ outputTokens: 0,
1694
+ totalTokens: 0,
1695
+ cacheReadTokens: 0,
1696
+ cacheCreationTokens: 0,
1697
+ budgetTokens: 0,
1698
+ calls: 0,
1699
+ estimatedUsd: 0,
1700
+ unpricedTokens: 0,
1701
+ unknownUsageEvents: 0,
1702
+ usageTelemetryComplete: true,
1703
+ llmRetries: 0,
1704
+ llmRetryDurationMs: 0,
1705
+ toolCalls: 0,
1706
+ toolErrors: 0,
1707
+ toolRetryCalls: 0,
1708
+ toolTimedCalls: 0,
1709
+ toolDurationMs: 0,
1710
+ toolP95DurationMs: null,
1711
+ toolP95DurationLowerBoundMs: null,
1712
+ toolTimingCoverage: 1,
1713
+ toolTelemetryComplete: true,
1714
+ unknownToolEvents: 0,
1715
+ retryRatio: null,
1716
+ retryRatioApproximate: true,
1717
+ retryRatioDenominator: "recorded-usage-events",
1718
+ toolP95Aggregation: "conservative-max-session-p95",
1719
+ };
1720
+ }
1721
+
1722
+ function exactUsd(value) {
1723
+ const number = finiteNonnegative(value, "estimated USD total");
1724
+ // Do not round authority-bearing budget values. A single token on a cheap
1725
+ // model can cost less than one microdollar; rounding would make maxUsd=0 pass.
1726
+ return number;
1727
+ }
1728
+
1729
+ function projectedToolDuration(value, field) {
1730
+ if (value == null) return null;
1731
+ const duration = safeInteger(value, field);
1732
+ if (duration > MAX_DURATION_MS) {
1733
+ throw new Error(`${field} exceeds the supported duration range`);
1734
+ }
1735
+ return duration;
1736
+ }
1737
+
1738
+ function validateProjectedSessionTools(session) {
1739
+ const tools = session.tools;
1740
+ for (const field of [
1741
+ "p50DurationMs",
1742
+ "p95DurationMs",
1743
+ "p95DurationLowerBoundMs",
1744
+ "timingCoverage",
1745
+ "percentileMethod",
1746
+ ]) {
1747
+ if (!Object.hasOwn(tools, field)) {
1748
+ throw new Error(`session tool timing field is missing: ${field}`);
1749
+ }
1750
+ }
1751
+ const toolCalls = safeInteger(tools.totalCalls, "session toolCalls");
1752
+ const toolErrors = safeInteger(tools.totalErrors, "session toolErrors");
1753
+ const toolRetryCalls = safeInteger(
1754
+ tools.retryCalls,
1755
+ "session toolRetryCalls",
1756
+ );
1757
+ const toolTimedCalls = safeInteger(
1758
+ tools.timedCalls,
1759
+ "session toolTimedCalls",
1760
+ );
1761
+ const toolDurationMs = safeInteger(
1762
+ tools.totalDurationMs,
1763
+ "session toolDurationMs",
1764
+ );
1765
+ const unknownToolEvents = safeInteger(
1766
+ tools.unknownEvents,
1767
+ "session unknownToolEvents",
1768
+ );
1769
+ if (tools.telemetryComplete !== (unknownToolEvents === 0)) {
1770
+ throw new Error("session tool telemetry completeness is inconsistent");
1771
+ }
1772
+ if (
1773
+ toolErrors > toolCalls ||
1774
+ toolRetryCalls > toolCalls ||
1775
+ toolTimedCalls > toolCalls
1776
+ ) {
1777
+ throw new Error("session tool call totals are inconsistent");
1778
+ }
1779
+ if (!Array.isArray(tools.byTool)) {
1780
+ throw new Error("session tool rows must be an array");
1781
+ }
1782
+
1783
+ const rowTotals = {
1784
+ calls: 0,
1785
+ errors: 0,
1786
+ retries: 0,
1787
+ timedCalls: 0,
1788
+ durationMs: 0,
1789
+ };
1790
+ const toolNames = new Set();
1791
+ for (const row of tools.byTool) {
1792
+ if (!row || typeof row !== "object" || Array.isArray(row)) {
1793
+ throw new Error("session tool row must be an object");
1794
+ }
1795
+ const name = boundedLabel(
1796
+ row.tool,
1797
+ "session tool row name",
1798
+ MAX_TOOL_LENGTH,
1799
+ { fallback: "?" },
1800
+ );
1801
+ if (name !== row.tool || toolNames.has(name)) {
1802
+ throw new Error("session tool row name is duplicate or noncanonical");
1803
+ }
1804
+ toolNames.add(name);
1805
+ const calls = safeInteger(row.calls, "session tool row calls");
1806
+ const errors = safeInteger(row.errors, "session tool row errors");
1807
+ const retries = safeInteger(row.retries, "session tool row retries");
1808
+ const hasTimedCalls = Object.hasOwn(row, "timedCalls");
1809
+ const hasDuration = Object.hasOwn(row, "durationMs");
1810
+ const timedCalls = hasTimedCalls
1811
+ ? safeInteger(row.timedCalls, "session tool row timedCalls")
1812
+ : 0;
1813
+ const durationMs = hasDuration
1814
+ ? safeInteger(row.durationMs, "session tool row durationMs")
1815
+ : 0;
1816
+ if (
1817
+ errors > calls ||
1818
+ retries > calls ||
1819
+ timedCalls > calls ||
1820
+ hasTimedCalls !== hasDuration ||
1821
+ hasTimedCalls !== timedCalls > 0
1822
+ ) {
1823
+ throw new Error("session tool row totals are inconsistent");
1824
+ }
1825
+ for (const [field, value] of [
1826
+ ["calls", calls],
1827
+ ["errors", errors],
1828
+ ["retries", retries],
1829
+ ["timedCalls", timedCalls],
1830
+ ["durationMs", durationMs],
1831
+ ]) {
1832
+ rowTotals[field] = checkedAdd(
1833
+ rowTotals[field],
1834
+ value,
1835
+ `session tool row ${field} total`,
1836
+ );
1837
+ }
1838
+ }
1839
+ for (const [field, expected] of [
1840
+ ["calls", toolCalls],
1841
+ ["errors", toolErrors],
1842
+ ["retries", toolRetryCalls],
1843
+ ["timedCalls", toolTimedCalls],
1844
+ ["durationMs", toolDurationMs],
1845
+ ]) {
1846
+ if (rowTotals[field] !== expected) {
1847
+ throw new Error(`session tool ${field} disagrees with tool rows`);
1848
+ }
1849
+ }
1850
+
1851
+ const expectedCoverage = toolCalls > 0 ? toolTimedCalls / toolCalls : 1;
1852
+ if (
1853
+ typeof tools.timingCoverage !== "number" ||
1854
+ !Number.isFinite(tools.timingCoverage) ||
1855
+ tools.timingCoverage !== expectedCoverage
1856
+ ) {
1857
+ throw new Error("session tool timing coverage is inconsistent");
1858
+ }
1859
+ if (tools.percentileMethod !== "nearest-rank") {
1860
+ throw new Error("session tool percentile method is unsupported");
1861
+ }
1862
+
1863
+ const p50 = projectedToolDuration(
1864
+ tools.p50DurationMs,
1865
+ "session tools.p50DurationMs",
1866
+ );
1867
+ const p95 = projectedToolDuration(
1868
+ tools.p95DurationMs,
1869
+ "session tools.p95DurationMs",
1870
+ );
1871
+ const p95LowerBound = projectedToolDuration(
1872
+ tools.p95DurationLowerBoundMs,
1873
+ "session tools.p95DurationLowerBoundMs",
1874
+ );
1875
+ if (toolTimedCalls === 0) {
1876
+ if (p50 !== null || p95 !== null || toolDurationMs !== 0) {
1877
+ throw new Error("session tool percentiles require timed calls");
1878
+ }
1879
+ const expectedLowerBound =
1880
+ toolCalls > 0 || unknownToolEvents > 0 ? 0 : null;
1881
+ if (p95LowerBound !== expectedLowerBound) {
1882
+ throw new Error("session tool P95 lower bound is inconsistent");
1883
+ }
1884
+ } else {
1885
+ if (p50 === null || p95 === null || p95LowerBound === null) {
1886
+ throw new Error("session timed tools require complete percentile fields");
1887
+ }
1888
+ if (p50 > p95 || p95 > toolDurationMs || p95LowerBound > p95) {
1889
+ throw new Error("session tool percentile ordering is inconsistent");
1890
+ }
1891
+ if (
1892
+ tools.telemetryComplete === true &&
1893
+ toolTimedCalls === toolCalls &&
1894
+ p95LowerBound !== p95
1895
+ ) {
1896
+ throw new Error(
1897
+ "session complete tool timing requires an exact P95 lower bound",
1898
+ );
1899
+ }
1900
+ }
1901
+
1902
+ return {
1903
+ toolCalls,
1904
+ toolErrors,
1905
+ toolRetryCalls,
1906
+ toolTimedCalls,
1907
+ toolDurationMs,
1908
+ unknownToolEvents,
1909
+ p95,
1910
+ p95LowerBound,
1911
+ };
1912
+ }
1913
+
1914
+ function sumTotals(sessions, deliveries) {
1915
+ const totals = emptyTotals();
1916
+ const p95Values = [];
1917
+ const p95LowerBoundValues = [];
1918
+ totals.sessions = sessions.length;
1919
+ totals.deliveries = deliveries.length;
1920
+ for (const session of sessions) {
1921
+ if (!session?.usage || !session?.retries || !session?.tools) {
1922
+ throw new Error(
1923
+ `session projection schema is incomplete: ${session?.id}`,
1924
+ );
1925
+ }
1926
+ const usageInteger = (field) =>
1927
+ safeInteger(session.usage[field], `session usage.${field}`);
1928
+ const inputTokens = usageInteger("inputTokens");
1929
+ const outputTokens = usageInteger("outputTokens");
1930
+ const totalTokens = usageInteger("totalTokens");
1931
+ const cacheReadTokens = usageInteger("cacheReadTokens");
1932
+ const cacheCreationTokens = usageInteger("cacheCreationTokens");
1933
+ const budgetTokens = usageInteger("budgetTokens");
1934
+ const expectedTotalTokens = checkedAdd(
1935
+ inputTokens,
1936
+ outputTokens,
1937
+ "session usage expected totalTokens",
1938
+ );
1939
+ const expectedBudgetTokens = checkedAdd(
1940
+ expectedTotalTokens,
1941
+ checkedAdd(
1942
+ cacheReadTokens,
1943
+ cacheCreationTokens,
1944
+ "session usage expected cache tokens",
1945
+ ),
1946
+ "session usage expected budgetTokens",
1947
+ );
1948
+ if (
1949
+ totalTokens !== expectedTotalTokens ||
1950
+ budgetTokens !== expectedBudgetTokens
1951
+ ) {
1952
+ throw new Error("session usage token totals are inconsistent");
1953
+ }
1954
+ totals.inputTokens = checkedAdd(
1955
+ totals.inputTokens,
1956
+ inputTokens,
1957
+ "report totals.inputTokens",
1958
+ );
1959
+ totals.outputTokens = checkedAdd(
1960
+ totals.outputTokens,
1961
+ outputTokens,
1962
+ "report totals.outputTokens",
1963
+ );
1964
+ totals.totalTokens = checkedAdd(
1965
+ totals.totalTokens,
1966
+ totalTokens,
1967
+ "report totals.totalTokens",
1968
+ );
1969
+ totals.cacheReadTokens = checkedAdd(
1970
+ totals.cacheReadTokens,
1971
+ cacheReadTokens,
1972
+ "report totals.cacheReadTokens",
1973
+ );
1974
+ totals.cacheCreationTokens = checkedAdd(
1975
+ totals.cacheCreationTokens,
1976
+ cacheCreationTokens,
1977
+ "report totals.cacheCreationTokens",
1978
+ );
1979
+ totals.budgetTokens = checkedAdd(
1980
+ totals.budgetTokens,
1981
+ budgetTokens,
1982
+ "report totals.budgetTokens",
1983
+ );
1984
+ totals.calls = checkedAdd(
1985
+ totals.calls,
1986
+ usageInteger("calls"),
1987
+ "report totals.calls",
1988
+ );
1989
+ totals.estimatedUsd = exactUsd(
1990
+ totals.estimatedUsd +
1991
+ finiteNonnegative(
1992
+ session.usage.estimatedUsd,
1993
+ "session usage.estimatedUsd",
1994
+ ),
1995
+ );
1996
+ if (!Array.isArray(session.usage.unpriced)) {
1997
+ throw new Error("session usage.unpriced must be an array");
1998
+ }
1999
+ for (const row of session.usage.unpriced) {
2000
+ totals.unpricedTokens = checkedAdd(
2001
+ totals.unpricedTokens,
2002
+ safeInteger(row?.budgetTokens, "session usage.unpriced.budgetTokens"),
2003
+ "report totals.unpricedTokens",
2004
+ );
2005
+ }
2006
+ totals.unknownUsageEvents = checkedAdd(
2007
+ totals.unknownUsageEvents,
2008
+ safeInteger(
2009
+ session.usage.unknownEvidence?.count,
2010
+ "session usage.unknownEvidence.count",
2011
+ ),
2012
+ "report totals.unknownUsageEvents",
2013
+ );
2014
+ if (session.usage.telemetryComplete !== true) {
2015
+ totals.usageTelemetryComplete = false;
2016
+ }
2017
+ const {
2018
+ toolCalls,
2019
+ toolErrors,
2020
+ toolRetryCalls,
2021
+ toolTimedCalls,
2022
+ toolDurationMs,
2023
+ unknownToolEvents,
2024
+ p95,
2025
+ p95LowerBound,
2026
+ } = validateProjectedSessionTools(session);
2027
+ if (!session.tools.telemetryComplete) {
2028
+ totals.toolTelemetryComplete = false;
2029
+ }
2030
+ totals.unknownToolEvents = checkedAdd(
2031
+ totals.unknownToolEvents,
2032
+ unknownToolEvents,
2033
+ "report totals.unknownToolEvents",
2034
+ );
2035
+ for (const [field, value] of [
2036
+ ["llmRetries", session.retries.totalRetries],
2037
+ ["llmRetryDurationMs", session.retries.durationMs],
2038
+ ["toolCalls", toolCalls],
2039
+ ["toolErrors", toolErrors],
2040
+ ["toolRetryCalls", toolRetryCalls],
2041
+ ["toolTimedCalls", toolTimedCalls],
2042
+ ["toolDurationMs", toolDurationMs],
2043
+ ]) {
2044
+ totals[field] = checkedAdd(
2045
+ totals[field],
2046
+ safeInteger(value, `session ${field}`),
2047
+ `report totals.${field}`,
2048
+ );
2049
+ }
2050
+ if (p95 != null) p95Values.push(p95);
2051
+ if (p95LowerBound != null) p95LowerBoundValues.push(p95LowerBound);
2052
+ }
2053
+ totals.toolP95DurationMs = p95Values.length ? Math.max(...p95Values) : null;
2054
+ totals.toolP95DurationLowerBoundMs = p95LowerBoundValues.length
2055
+ ? Math.max(...p95LowerBoundValues)
2056
+ : null;
2057
+ totals.toolTimingCoverage =
2058
+ totals.toolCalls > 0 ? totals.toolTimedCalls / totals.toolCalls : 1;
2059
+ totals.retryRatio =
2060
+ totals.calls > 0
2061
+ ? totals.llmRetries / totals.calls
2062
+ : totals.llmRetries > 0
2063
+ ? null
2064
+ : 0;
2065
+ return totals;
2066
+ }
2067
+
2068
+ function assessBudgets(totals, budgets, { completeness, noData }) {
2069
+ const alerts = [];
2070
+ const check = (code, metric, actual, limit, unit) => {
2071
+ if (limit == null) return;
2072
+ alerts.push({
2073
+ code,
2074
+ metric,
2075
+ status: actual > limit ? "exceeded" : "within_budget",
2076
+ actual,
2077
+ limit,
2078
+ unit,
2079
+ });
2080
+ };
2081
+ if (budgets.maxTokens != null && !totals.usageTelemetryComplete) {
2082
+ alerts.push({
2083
+ code: "token-budget-usage-unknown",
2084
+ metric: "budgetTokens",
2085
+ status: totals.budgetTokens > budgets.maxTokens ? "exceeded" : "unknown",
2086
+ actual: totals.budgetTokens,
2087
+ limit: budgets.maxTokens,
2088
+ unit: "tokens",
2089
+ unknownUsageEvents: totals.unknownUsageEvents,
2090
+ });
2091
+ } else {
2092
+ check(
2093
+ "token-budget",
2094
+ "budgetTokens",
2095
+ totals.budgetTokens,
2096
+ budgets.maxTokens,
2097
+ "tokens",
2098
+ );
2099
+ }
2100
+ if (
2101
+ budgets.maxUsd != null &&
2102
+ (totals.unpricedTokens > 0 || !totals.usageTelemetryComplete)
2103
+ ) {
2104
+ alerts.push({
2105
+ code: totals.usageTelemetryComplete
2106
+ ? "usd-budget-unpriced"
2107
+ : "usd-budget-usage-unknown",
2108
+ metric: "estimatedUsd",
2109
+ status: totals.estimatedUsd > budgets.maxUsd ? "exceeded" : "unknown",
2110
+ actual: totals.estimatedUsd,
2111
+ limit: budgets.maxUsd,
2112
+ unit: "USD",
2113
+ unpricedTokens: totals.unpricedTokens,
2114
+ unknownUsageEvents: totals.unknownUsageEvents,
2115
+ });
2116
+ } else {
2117
+ check(
2118
+ "usd-budget",
2119
+ "estimatedUsd",
2120
+ totals.estimatedUsd,
2121
+ budgets.maxUsd,
2122
+ "USD",
2123
+ );
2124
+ }
2125
+ check(
2126
+ "retry-budget",
2127
+ "llmRetries",
2128
+ totals.llmRetries,
2129
+ budgets.maxRetries,
2130
+ "retries",
2131
+ );
2132
+ if (budgets.maxRetryRatio != null && !totals.usageTelemetryComplete) {
2133
+ alerts.push({
2134
+ code: "retry-ratio-budget-usage-unknown",
2135
+ metric: "retryRatio",
2136
+ status: "unknown",
2137
+ actual: totals.retryRatio,
2138
+ limit: budgets.maxRetryRatio,
2139
+ unit: "ratio",
2140
+ denominator: totals.retryRatioDenominator,
2141
+ unknownUsageEvents: totals.unknownUsageEvents,
2142
+ });
2143
+ } else if (budgets.maxRetryRatio != null && totals.retryRatio == null) {
2144
+ alerts.push({
2145
+ code: "retry-ratio-budget-unobserved",
2146
+ metric: "retryRatio",
2147
+ status: "unknown",
2148
+ actual: null,
2149
+ limit: budgets.maxRetryRatio,
2150
+ unit: "ratio",
2151
+ denominator: totals.retryRatioDenominator,
2152
+ });
2153
+ } else {
2154
+ check(
2155
+ "retry-ratio-budget",
2156
+ "retryRatio",
2157
+ totals.retryRatio,
2158
+ budgets.maxRetryRatio,
2159
+ "ratio",
2160
+ );
2161
+ }
2162
+ if (
2163
+ budgets.maxToolP95Ms != null &&
2164
+ (totals.toolP95DurationMs == null ||
2165
+ totals.toolTimingCoverage < 1 ||
2166
+ !totals.toolTelemetryComplete)
2167
+ ) {
2168
+ alerts.push({
2169
+ code: "tool-latency-budget-unobserved",
2170
+ metric: "toolP95DurationMs",
2171
+ status:
2172
+ totals.toolP95DurationLowerBoundMs != null &&
2173
+ totals.toolP95DurationLowerBoundMs > budgets.maxToolP95Ms
2174
+ ? "exceeded"
2175
+ : "unknown",
2176
+ actual: totals.toolP95DurationMs,
2177
+ lowerBound: totals.toolP95DurationLowerBoundMs,
2178
+ limit: budgets.maxToolP95Ms,
2179
+ unit: "ms",
2180
+ timingCoverage: totals.toolTimingCoverage,
2181
+ unknownToolEvents: totals.unknownToolEvents,
2182
+ });
2183
+ } else {
2184
+ check(
2185
+ "tool-latency-budget",
2186
+ "toolP95DurationMs",
2187
+ totals.toolP95DurationMs,
2188
+ budgets.maxToolP95Ms,
2189
+ "ms",
2190
+ );
2191
+ }
2192
+ if (noData) {
2193
+ alerts.push({
2194
+ code: "causal-selection-empty",
2195
+ metric: "deliveries",
2196
+ status: "unknown",
2197
+ actual: 0,
2198
+ limit: null,
2199
+ unit: "deliveries",
2200
+ });
2201
+ } else if (completeness !== "complete") {
2202
+ alerts.push({
2203
+ code: "causal-evidence-incomplete",
2204
+ metric: "completeness",
2205
+ status: "unknown",
2206
+ actual: completeness,
2207
+ limit: "complete",
2208
+ unit: "state",
2209
+ });
2210
+ }
2211
+ if (alerts.length === 0) {
2212
+ return { status: "not_evaluated", limits: budgets, alerts: [] };
2213
+ }
2214
+ const hasExceeded = alerts.some((alert) => alert.status === "exceeded");
2215
+ const hasUnknown = alerts.some((alert) => alert.status === "unknown");
2216
+ return {
2217
+ status: hasExceeded ? "exceeded" : hasUnknown ? "unknown" : "within_budget",
2218
+ limits: budgets,
2219
+ alerts,
2220
+ };
2221
+ }
2222
+
2223
+ function graphFor(deliveries, sessions) {
2224
+ const nodes = [];
2225
+ const edges = [];
2226
+ for (const session of sessions) {
2227
+ nodes.push({ id: `session:${session.id}`, type: "session" });
2228
+ }
2229
+ for (const delivery of deliveries) {
2230
+ const deliveryId = `delivery:${delivery.id}`;
2231
+ nodes.push({ id: deliveryId, type: "delivery" });
2232
+ for (const [type, present] of [
2233
+ ["diff", Boolean(delivery.diff.digest)],
2234
+ ["gate", delivery.gates.selectedIds.length > 0],
2235
+ [
2236
+ "artifact",
2237
+ delivery.artifacts.preview.length > 0 ||
2238
+ Boolean(delivery.artifacts.evidence),
2239
+ ],
2240
+ ["pr", Boolean(delivery.pr)],
2241
+ ["merge", Boolean(delivery.merge)],
2242
+ ]) {
2243
+ if (!present) continue;
2244
+ const id = `${type}:${delivery.id}`;
2245
+ nodes.push({ id, type });
2246
+ edges.push({ from: deliveryId, to: id, relation: "produced" });
2247
+ }
2248
+ for (const association of delivery.associations) {
2249
+ edges.push({
2250
+ from: `session:${association.sessionId}`,
2251
+ to: deliveryId,
2252
+ relation: "contributed_to",
2253
+ });
2254
+ }
2255
+ }
2256
+ return { nodes, edges };
2257
+ }
2258
+
2259
+ export function selectCausalDeliveries(deliveries = [], filter = {}) {
2260
+ const normalizedFilter = normalizeScope(filter, "filter");
2261
+ const byId = new Map();
2262
+ for (const delivery of deliveries) {
2263
+ if (!scopeMatches(delivery.scope, normalizedFilter)) continue;
2264
+ const previous = byId.get(delivery.id);
2265
+ if (previous) {
2266
+ if (previous.authority.stateDigest !== delivery.authority.stateDigest) {
2267
+ throw new Error(
2268
+ `delivery flow id is bound to conflicting state: ${delivery.id}`,
2269
+ );
2270
+ }
2271
+ continue;
2272
+ }
2273
+ byId.set(delivery.id, delivery);
2274
+ }
2275
+ return [...byId.values()].sort((a, b) => compareStrings(a.id, b.id));
2276
+ }
2277
+
2278
+ export function causalSessionIds(deliveries = []) {
2279
+ const ids = [
2280
+ ...new Set(
2281
+ deliveries.flatMap((delivery) =>
2282
+ delivery.associations.map((association) => association.sessionId),
2283
+ ),
2284
+ ),
2285
+ ].sort(compareStrings);
2286
+ if (ids.length > MAX_REPORT_SESSIONS) {
2287
+ throw new Error(
2288
+ `causal report exceeds the ${MAX_REPORT_SESSIONS} session limit`,
2289
+ );
2290
+ }
2291
+ return ids;
2292
+ }
2293
+
2294
+ /** Track cross-delivery/session projection limits without retaining projections. */
2295
+ export function createCausalObservabilityLimitTracker() {
2296
+ let gateRows = 0;
2297
+ let matrixCells = 0;
2298
+ let previewArtifacts = 0;
2299
+ let modelRows = 0;
2300
+ let retryModelRows = 0;
2301
+ let toolRows = 0;
2302
+
2303
+ const rows = (value, field) => {
2304
+ if (!Array.isArray(value)) throw new Error(`${field} must be an array`);
2305
+ return value.length;
2306
+ };
2307
+ const enforce = (actual, maximum, field) => {
2308
+ if (actual > maximum) {
2309
+ throw new Error(`causal report exceeds the ${maximum} ${field} limit`);
2310
+ }
2311
+ };
2312
+
2313
+ return {
2314
+ acceptDelivery(delivery) {
2315
+ const selected = rows(
2316
+ delivery?.gates?.selectedIds,
2317
+ "delivery selected gates",
2318
+ );
2319
+ const results = rows(delivery?.gates?.results, "delivery gate results");
2320
+ const preview = rows(
2321
+ delivery?.artifacts?.preview,
2322
+ "delivery preview artifacts",
2323
+ );
2324
+ enforce(selected, MAX_DELIVERY_SELECTED_GATES, "selected gate");
2325
+ enforce(results, MAX_DELIVERY_GATE_RESULTS, "gate result");
2326
+ enforce(preview, MAX_DELIVERY_PREVIEW_ARTIFACTS, "preview artifact");
2327
+ let deliveryMatrixCells = 0;
2328
+ for (const result of delivery.gates.results) {
2329
+ deliveryMatrixCells += rows(result?.matrix, "delivery gate matrix");
2330
+ }
2331
+ enforce(
2332
+ deliveryMatrixCells,
2333
+ MAX_DELIVERY_GATE_MATRIX_CELLS,
2334
+ "gate matrix cell",
2335
+ );
2336
+ gateRows += selected + results;
2337
+ matrixCells += deliveryMatrixCells;
2338
+ previewArtifacts += preview;
2339
+ enforce(gateRows, MAX_REPORT_GATE_ROWS, "gate row");
2340
+ enforce(matrixCells, MAX_REPORT_GATE_MATRIX_CELLS, "gate matrix cell");
2341
+ enforce(
2342
+ previewArtifacts,
2343
+ MAX_REPORT_PREVIEW_ARTIFACTS,
2344
+ "preview artifact",
2345
+ );
2346
+ },
2347
+ acceptSession(session) {
2348
+ const sessionModelRows = rows(
2349
+ session?.usage?.byModel,
2350
+ "session usage model rows",
2351
+ );
2352
+ const sessionRetryModelRows = rows(
2353
+ session?.retries?.byModel,
2354
+ "session retry model rows",
2355
+ );
2356
+ const sessionToolRows = rows(session?.tools?.byTool, "session tool rows");
2357
+ enforce(sessionModelRows, MAX_DISTINCT_MODELS, "session model row");
2358
+ enforce(
2359
+ sessionRetryModelRows,
2360
+ MAX_DISTINCT_MODELS,
2361
+ "session retry model row",
2362
+ );
2363
+ enforce(sessionToolRows, MAX_DISTINCT_TOOLS, "session tool row");
2364
+ modelRows += sessionModelRows;
2365
+ retryModelRows += sessionRetryModelRows;
2366
+ toolRows += sessionToolRows;
2367
+ enforce(modelRows, MAX_REPORT_MODEL_ROWS, "model row");
2368
+ enforce(retryModelRows, MAX_REPORT_MODEL_ROWS, "retry model row");
2369
+ enforce(toolRows, MAX_REPORT_TOOL_ROWS, "tool row");
2370
+ },
2371
+ };
2372
+ }
2373
+
2374
+ export function buildCausalObservabilityReport({
2375
+ deliveries = [],
2376
+ sessionsById = new Map(),
2377
+ filter = {},
2378
+ budgets = {},
2379
+ pricingTable = PRICE_TABLE,
2380
+ generatedAt = new Date().toISOString(),
2381
+ } = {}) {
2382
+ if (!(sessionsById instanceof Map)) {
2383
+ throw new Error("sessionsById must be a Map");
2384
+ }
2385
+ const normalizedFilter = normalizeScope(filter, "filter");
2386
+ const normalizedBudgets = normalizeCausalBudgets(budgets);
2387
+ const effectivePricingTable = normalizePricingTable(pricingTable);
2388
+ const pricingTableDigest = digest(effectivePricingTable);
2389
+ const selectedDeliveries = selectCausalDeliveries(
2390
+ deliveries,
2391
+ normalizedFilter,
2392
+ );
2393
+ const limitTracker = createCausalObservabilityLimitTracker();
2394
+ for (const delivery of selectedDeliveries) {
2395
+ if (delivery?.authority?.verified !== true) {
2396
+ throw new Error(`delivery authority is not verified: ${delivery?.id}`);
2397
+ }
2398
+ if (asId(delivery.id, "delivery.id", { required: true }) !== delivery.id) {
2399
+ throw new Error(`delivery id is not canonical: ${delivery.id}`);
2400
+ }
2401
+ sha256Digest(
2402
+ delivery.authority.stateDigest,
2403
+ "delivery authority stateDigest",
2404
+ { required: true },
2405
+ );
2406
+ safeInteger(delivery.authority.revision, "delivery authority revision");
2407
+ limitTracker.acceptDelivery(delivery);
2408
+ }
2409
+ const selectedSessionIds = causalSessionIds(selectedDeliveries);
2410
+ const selectedSessionIdSet = new Set(selectedSessionIds);
2411
+ for (const sessionId of sessionsById.keys()) {
2412
+ const session = sessionsById.get(sessionId);
2413
+ if (session?.id !== sessionId) {
2414
+ throw new Error(
2415
+ `session map key does not match projection id: ${sessionId}`,
2416
+ );
2417
+ }
2418
+ if (session?.authority?.verified !== true) {
2419
+ throw new Error(`session authority is not verified: ${sessionId}`);
2420
+ }
2421
+ if (asId(session.id, "session.id", { required: true }) !== session.id) {
2422
+ throw new Error(`session id is not canonical: ${session.id}`);
2423
+ }
2424
+ if (
2425
+ typeof session.authority.headHash !== "string" ||
2426
+ !/^[0-9a-f]{64}$/i.test(session.authority.headHash)
2427
+ ) {
2428
+ throw new Error(`session authority headHash is invalid: ${sessionId}`);
2429
+ }
2430
+ safeInteger(session.authority.eventCount, "session authority eventCount", {
2431
+ min: 1,
2432
+ });
2433
+ if (!selectedSessionIdSet.has(sessionId)) {
2434
+ throw new Error(
2435
+ `sessionsById contains an unselected session: ${sessionId}`,
2436
+ );
2437
+ }
2438
+ }
2439
+ const missingSessionIds = selectedSessionIds.filter(
2440
+ (sessionId) => !sessionsById.has(sessionId),
2441
+ );
2442
+ if (missingSessionIds.length > 0) {
2443
+ throw new Error(
2444
+ `verified causal sessions are missing: ${missingSessionIds.join(", ")}`,
2445
+ );
2446
+ }
2447
+ const sessions = selectedSessionIds.map((sessionId) =>
2448
+ sessionsById.get(sessionId),
2449
+ );
2450
+ for (const session of sessions) {
2451
+ if (session?.usage?.pricingTableDigest !== pricingTableDigest) {
2452
+ throw new Error(
2453
+ `session pricing table mismatch: ${session?.id || "unknown"}`,
2454
+ );
2455
+ }
2456
+ if (!Array.isArray(session.usage.unknownEvidence?.byCode)) {
2457
+ throw new Error("session usage unknown evidence must be an array");
2458
+ }
2459
+ if (!Array.isArray(session.usage.byModel)) {
2460
+ throw new Error("session usage model rows must be an array");
2461
+ }
2462
+ let modelInputTokens = 0;
2463
+ let modelOutputTokens = 0;
2464
+ let modelTotalTokens = 0;
2465
+ let modelCacheReadTokens = 0;
2466
+ let modelCacheCreationTokens = 0;
2467
+ let modelBudgetTokens = 0;
2468
+ let modelCalls = 0;
2469
+ let modelEstimatedUsd = 0;
2470
+ for (const row of session.usage.byModel) {
2471
+ const rowInputTokens = safeInteger(
2472
+ row?.inputTokens,
2473
+ "session model inputTokens",
2474
+ );
2475
+ const rowOutputTokens = safeInteger(
2476
+ row?.outputTokens,
2477
+ "session model outputTokens",
2478
+ );
2479
+ const rowTotalTokens = safeInteger(
2480
+ row?.totalTokens,
2481
+ "session model totalTokens",
2482
+ );
2483
+ const rowCacheReadTokens = safeInteger(
2484
+ row?.cacheReadTokens,
2485
+ "session model cacheReadTokens",
2486
+ );
2487
+ const rowCacheCreationTokens = safeInteger(
2488
+ row?.cacheCreationTokens,
2489
+ "session model cacheCreationTokens",
2490
+ );
2491
+ const rowBudgetTokens = safeInteger(
2492
+ row?.budgetTokens,
2493
+ "session model budgetTokens",
2494
+ );
2495
+ if (
2496
+ rowTotalTokens !== rowInputTokens + rowOutputTokens ||
2497
+ rowBudgetTokens !==
2498
+ rowTotalTokens + rowCacheReadTokens + rowCacheCreationTokens
2499
+ ) {
2500
+ throw new Error("session model token totals are inconsistent");
2501
+ }
2502
+ for (const [field, accumulator] of [
2503
+ ["inputTokens", "input"],
2504
+ ["outputTokens", "output"],
2505
+ ["totalTokens", "total"],
2506
+ ["cacheReadTokens", "cacheRead"],
2507
+ ["cacheCreationTokens", "cacheCreation"],
2508
+ ["budgetTokens", "budget"],
2509
+ ["calls", "calls"],
2510
+ ]) {
2511
+ const value = safeInteger(row?.[field], `session model ${field}`);
2512
+ if (accumulator === "input") modelInputTokens += value;
2513
+ else if (accumulator === "output") modelOutputTokens += value;
2514
+ else if (accumulator === "total") modelTotalTokens += value;
2515
+ else if (accumulator === "cacheRead") modelCacheReadTokens += value;
2516
+ else if (accumulator === "cacheCreation")
2517
+ modelCacheCreationTokens += value;
2518
+ else if (accumulator === "budget") modelBudgetTokens += value;
2519
+ else modelCalls += value;
2520
+ if (
2521
+ !Number.isSafeInteger(
2522
+ accumulator === "input"
2523
+ ? modelInputTokens
2524
+ : accumulator === "output"
2525
+ ? modelOutputTokens
2526
+ : accumulator === "total"
2527
+ ? modelTotalTokens
2528
+ : accumulator === "cacheRead"
2529
+ ? modelCacheReadTokens
2530
+ : accumulator === "cacheCreation"
2531
+ ? modelCacheCreationTokens
2532
+ : accumulator === "budget"
2533
+ ? modelBudgetTokens
2534
+ : modelCalls,
2535
+ )
2536
+ ) {
2537
+ throw new Error(
2538
+ "session model usage totals exceed the safe integer limit",
2539
+ );
2540
+ }
2541
+ }
2542
+ modelEstimatedUsd = exactUsd(
2543
+ modelEstimatedUsd +
2544
+ finiteNonnegative(row?.cost, "session model estimated cost"),
2545
+ );
2546
+ }
2547
+ for (const [field, actual] of [
2548
+ ["inputTokens", modelInputTokens],
2549
+ ["outputTokens", modelOutputTokens],
2550
+ ["totalTokens", modelTotalTokens],
2551
+ ["cacheReadTokens", modelCacheReadTokens],
2552
+ ["cacheCreationTokens", modelCacheCreationTokens],
2553
+ ["budgetTokens", modelBudgetTokens],
2554
+ ["calls", modelCalls],
2555
+ ]) {
2556
+ if (session.usage[field] !== actual) {
2557
+ throw new Error(`session usage ${field} disagrees with model rows`);
2558
+ }
2559
+ }
2560
+ if (exactUsd(session.usage.estimatedUsd) !== modelEstimatedUsd) {
2561
+ throw new Error("session estimated USD disagrees with model rows");
2562
+ }
2563
+ let unknownEvidenceCount = 0;
2564
+ for (const row of session.usage.unknownEvidence.byCode) {
2565
+ boundedLabel(
2566
+ row?.code,
2567
+ "session usage unknown evidence code",
2568
+ MAX_ID_LENGTH,
2569
+ );
2570
+ unknownEvidenceCount = checkedAdd(
2571
+ unknownEvidenceCount,
2572
+ safeInteger(row?.count, "session usage unknown evidence count", {
2573
+ min: 1,
2574
+ }),
2575
+ "session usage unknown evidence count",
2576
+ );
2577
+ }
2578
+ if (unknownEvidenceCount !== session.usage.unknownEvidence.count) {
2579
+ throw new Error("session usage unknown evidence count is inconsistent");
2580
+ }
2581
+ if (
2582
+ session.usage.telemetryComplete !==
2583
+ (session.usage.unknownEvidence.count === 0)
2584
+ ) {
2585
+ throw new Error("session usage telemetry completeness is inconsistent");
2586
+ }
2587
+ limitTracker.acceptSession(session);
2588
+ }
2589
+ for (const delivery of selectedDeliveries) {
2590
+ for (const association of delivery.associations) {
2591
+ const session = sessionsById.get(association.sessionId);
2592
+ if (
2593
+ association.headHash !== session.authority.headHash ||
2594
+ association.eventCount !== session.authority.eventCount
2595
+ ) {
2596
+ throw new Error(
2597
+ `session authority binding changed: ${association.sessionId}`,
2598
+ );
2599
+ }
2600
+ if (!sameScope(delivery.scope, session.scope)) {
2601
+ throw new Error(`session scope mismatch: ${association.sessionId}`);
2602
+ }
2603
+ }
2604
+ }
2605
+ const evidenceGaps = selectedDeliveries
2606
+ .filter((delivery) => delivery.associations.length === 0)
2607
+ .map((delivery) => ({
2608
+ code: "delivery-session-link-missing",
2609
+ deliveryId: delivery.id,
2610
+ }));
2611
+ const noData = selectedDeliveries.length === 0;
2612
+ const completeness = noData
2613
+ ? "no_data"
2614
+ : evidenceGaps.length === 0
2615
+ ? "complete"
2616
+ : "partial";
2617
+ const totals = sumTotals(sessions, selectedDeliveries);
2618
+ const material = {
2619
+ schema: CAUSAL_OBSERVABILITY_SCHEMA,
2620
+ version: CAUSAL_OBSERVABILITY_VERSION,
2621
+ generatedAt: new Date(generatedAt).toISOString(),
2622
+ filter: normalizedFilter,
2623
+ authority: {
2624
+ assurance: "declared-association-bound-to-verified-inputs",
2625
+ completeness,
2626
+ verifiedSessionCount: sessions.length,
2627
+ verifiedDeliveryCount: selectedDeliveries.length,
2628
+ usageTelemetry: {
2629
+ complete: totals.usageTelemetryComplete,
2630
+ protocolDeclaredSessionCount: sessions.filter(
2631
+ (session) =>
2632
+ session.authority?.usageTelemetry?.protocolDeclared === true,
2633
+ ).length,
2634
+ recordedEventsOnlySessionCount: sessions.filter(
2635
+ (session) =>
2636
+ session.authority?.usageTelemetry?.assurance ===
2637
+ "recorded-events-only",
2638
+ ).length,
2639
+ },
2640
+ toolTelemetry: {
2641
+ complete: totals.toolTelemetryComplete,
2642
+ unknownEvents: totals.unknownToolEvents,
2643
+ },
2644
+ evidenceGaps,
2645
+ },
2646
+ pricing: {
2647
+ kind: "estimated-list-price",
2648
+ currency: "USD",
2649
+ tableDigest: pricingTableDigest,
2650
+ },
2651
+ totals,
2652
+ budget: assessBudgets(totals, normalizedBudgets, {
2653
+ completeness,
2654
+ noData,
2655
+ }),
2656
+ deliveries: selectedDeliveries,
2657
+ sessions,
2658
+ graph: graphFor(selectedDeliveries, sessions),
2659
+ };
2660
+ return { ...material, reportDigest: digest(material) };
2661
+ }