@iowarp/clio-coder 0.3.8 → 0.4.0

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 (552) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/NOTICE +33 -0
  3. package/README.md +12 -3
  4. package/dist/{acp-U67UHUK2.js → acp-G5WJBNCT.js} +13 -12
  5. package/dist/{agents-YU6SGALZ.js → agents-FMV2Q5G4.js} +41 -36
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-5ZPJOIVG.js → auth-3IDSJEIK.js} +18 -18
  8. package/dist/{builtins-C6JMZVV6.js → builtins-XCZWXSC7.js} +5 -5
  9. package/dist/{chunk-IFBNV6H6.js → chunk-2ANTL7MR.js} +3 -3
  10. package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
  11. package/dist/{chunk-XWSF374K.js → chunk-2OQE55CK.js} +3 -3
  12. package/dist/{chunk-5H3GB5BO.js → chunk-32KWKNSF.js} +8 -384
  13. package/dist/{chunk-KTYTFRMB.js → chunk-36EJLSQQ.js} +34 -36
  14. package/dist/{chunk-FHJEP5SW.js → chunk-3BT2XMV4.js} +19 -13
  15. package/dist/chunk-3DPEIQKN.js +113 -0
  16. package/dist/{chunk-GU2UIAFZ.js → chunk-3URVFKWK.js} +7 -7
  17. package/dist/{chunk-WEH5XRJQ.js → chunk-3XML7CDN.js} +3 -3
  18. package/dist/chunk-42FMPA75.js +101 -0
  19. package/dist/{chunk-A2NJGIB3.js → chunk-5LXZXPKX.js} +2 -2
  20. package/dist/{chunk-VCBR6CU7.js → chunk-5PSMVOLM.js} +2 -2
  21. package/dist/{chunk-NMPKI6XL.js → chunk-6DB53AJS.js} +203 -25
  22. package/dist/{chunk-3BINW3FP.js → chunk-76ONBSIA.js} +2 -2
  23. package/dist/{chunk-26LEYJZH.js → chunk-7MCTRUCE.js} +2 -2
  24. package/dist/{chunk-TYPGUK6W.js → chunk-AB44T6BB.js} +111 -5
  25. package/dist/{chunk-K4XHGFR5.js → chunk-B7OBL7PK.js} +317 -721
  26. package/dist/{chunk-B5CSFE7B.js → chunk-BBVJUZHB.js} +2 -2
  27. package/dist/{chunk-EQ63NRB7.js → chunk-BBVYXMFO.js} +2 -2
  28. package/dist/{chunk-ZVJ5BLO2.js → chunk-BKFJHQCA.js} +154 -16
  29. package/dist/chunk-BKFM6EJV.js +462 -0
  30. package/dist/chunk-BMS5RKQY.js +27 -0
  31. package/dist/{chunk-ZNLWCMVZ.js → chunk-BPKCPIL7.js} +2 -2
  32. package/dist/{chunk-TLQJPP24.js → chunk-BUMFYQFY.js} +1394 -1349
  33. package/dist/{chunk-BNAZZHFG.js → chunk-BYP5D4HI.js} +1 -1
  34. package/dist/chunk-C2LTL2W6.js +2447 -0
  35. package/dist/{chunk-ME6CCNFO.js → chunk-CODPRO7Q.js} +8 -8
  36. package/dist/{chunk-E77JEWSD.js → chunk-CTJ4RNAA.js} +7 -37
  37. package/dist/{chunk-ODFEOB4F.js → chunk-CY6FY24N.js} +26 -8
  38. package/dist/{chunk-7RFXX52T.js → chunk-DQITNCXG.js} +642 -172
  39. package/dist/{chunk-TTHACPOM.js → chunk-DYJP44XW.js} +578 -117
  40. package/dist/{chunk-2HFZQUHL.js → chunk-F4CKPOEQ.js} +18 -8
  41. package/dist/{chunk-DGSYXYMX.js → chunk-FEFIFZTL.js} +3 -3
  42. package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
  43. package/dist/{chunk-MV3K5QF2.js → chunk-GCSMB2KY.js} +2 -2
  44. package/dist/chunk-GKF55TAZ.js +391 -0
  45. package/dist/{chunk-VWZOAB7K.js → chunk-GR5G2PVF.js} +9 -8
  46. package/dist/{chunk-4DGYLA73.js → chunk-GXNLGKAB.js} +80 -9
  47. package/dist/chunk-HHV2GANA.js +88 -0
  48. package/dist/{chunk-IIZWH4XA.js → chunk-HI63TFOG.js} +5 -4
  49. package/dist/{chunk-WLFILSD5.js → chunk-HJJTYUHX.js} +113 -83
  50. package/dist/{chunk-TT36MB5S.js → chunk-HLW2MRKE.js} +3 -1
  51. package/dist/{chunk-PMDBGQSJ.js → chunk-HWHKMHUA.js} +7 -7
  52. package/dist/chunk-HZHHCK24.js +1631 -0
  53. package/dist/{chunk-WSB3FPX7.js → chunk-I5VEOC6I.js} +39 -143
  54. package/dist/chunk-IBEBSCYA.js +564 -0
  55. package/dist/chunk-IQ7KR472.js +362 -0
  56. package/dist/{chunk-A3WNZD3P.js → chunk-J4W7KFM7.js} +949 -972
  57. package/dist/{chunk-TB5666IT.js → chunk-JDG2WCRO.js} +5 -5
  58. package/dist/{chunk-N22QMJKY.js → chunk-K5C3NCBD.js} +4 -4
  59. package/dist/{chunk-CGKSTWHD.js → chunk-K6BSR66V.js} +2 -1
  60. package/dist/{chunk-5C3AQNDW.js → chunk-KFZI4NIL.js} +216 -38
  61. package/dist/chunk-KMVISBZR.js +132 -0
  62. package/dist/{chunk-WXY7KU3G.js → chunk-LDQ2ZF2M.js} +2 -2
  63. package/dist/{chunk-XN3L4EYL.js → chunk-LQ3DZAMX.js} +3 -3
  64. package/dist/{chunk-U6MBIEMB.js → chunk-LY4S7GJC.js} +173 -144
  65. package/dist/{chunk-4SPRNWDE.js → chunk-MLKNTWH2.js} +19 -19
  66. package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
  67. package/dist/chunk-NEKRRTYW.js +56 -0
  68. package/dist/{chunk-SPULKLCF.js → chunk-NHCZP4K7.js} +3 -3
  69. package/dist/chunk-NHLBIGRH.js +1506 -0
  70. package/dist/chunk-NQQH3YT7.js +302 -0
  71. package/dist/chunk-NYS75XW5.js +15 -0
  72. package/dist/{chunk-GN57SG4G.js → chunk-O4XIVISU.js} +10 -8
  73. package/dist/{chunk-EMYUUSFG.js → chunk-O6TL7WWY.js} +6 -6
  74. package/dist/chunk-OQBA45DZ.js +97 -0
  75. package/dist/{chunk-LU7P4LHA.js → chunk-P3FOHJT4.js} +2 -2
  76. package/dist/chunk-PMZCIOCJ.js +25 -0
  77. package/dist/{chunk-I4HZDVNP.js → chunk-PQEFIJ36.js} +2 -2
  78. package/dist/{chunk-J3YUBZWY.js → chunk-QBJA7R7N.js} +62 -6
  79. package/dist/chunk-QDC3K2U3.js +262 -0
  80. package/dist/{chunk-2HEJ2F35.js → chunk-QLFS5GO2.js} +22 -10
  81. package/dist/{chunk-7RGZWPB6.js → chunk-QLL7ILRG.js} +95 -32
  82. package/dist/{chunk-YS5VLNH5.js → chunk-QREDIESB.js} +6 -6
  83. package/dist/chunk-QSNYB6ZV.js +195 -0
  84. package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
  85. package/dist/chunk-RKKLTLYB.js +45 -0
  86. package/dist/{chunk-P43ETTHK.js → chunk-SJ5ZKQ4S.js} +2 -2
  87. package/dist/{chunk-GPIEI3LY.js → chunk-SP2RXXYO.js} +6 -54
  88. package/dist/chunk-SUCTJL45.js +45 -0
  89. package/dist/{chunk-DYIM5TJT.js → chunk-SUW5DORT.js} +263 -7
  90. package/dist/chunk-T56WDKA5.js +183 -0
  91. package/dist/chunk-TVHHYFHE.js +255 -0
  92. package/dist/{chunk-FJ3H4MN5.js → chunk-TZ3SGWZZ.js} +3 -3
  93. package/dist/{chunk-MXKJU4JB.js → chunk-U77AMWDL.js} +91 -10
  94. package/dist/{chunk-5DHKRSMQ.js → chunk-ULC6OTWO.js} +11 -7
  95. package/dist/{chunk-RWSI4YD7.js → chunk-UM7N4G5A.js} +33 -12
  96. package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
  97. package/dist/{chunk-IGWKHNIQ.js → chunk-UXMFQ54G.js} +44 -37
  98. package/dist/{chunk-HFSBBKSQ.js → chunk-V5DHCITQ.js} +171 -3
  99. package/dist/{chunk-5WIGXA4T.js → chunk-VAZSBTKF.js} +111 -4
  100. package/dist/{chunk-VHN4MY6O.js → chunk-VEO4AP2K.js} +2 -2
  101. package/dist/{chunk-IJ7RPIYJ.js → chunk-VFA6GDY5.js} +65 -4
  102. package/dist/{chunk-PT7HYKEM.js → chunk-VO2LKSTM.js} +2 -2
  103. package/dist/chunk-VO67MWHC.js +75 -0
  104. package/dist/{chunk-XK56QHLX.js → chunk-VPKWYKEY.js} +19 -5
  105. package/dist/{chunk-TANS5ZJS.js → chunk-VYMXRQI6.js} +36 -22
  106. package/dist/{chunk-WWCZ5F23.js → chunk-W5VSYASO.js} +77 -16
  107. package/dist/{chunk-WNIJTQQK.js → chunk-WZR7K7ZX.js} +72 -116
  108. package/dist/{chunk-5Q2VVUKB.js → chunk-X3YGUTOB.js} +4 -4
  109. package/dist/chunk-X75E3D2N.js +686 -0
  110. package/dist/{chunk-VAWNZU7Z.js → chunk-YDFRH54B.js} +4 -4
  111. package/dist/chunk-YJX4SHTD.js +40 -0
  112. package/dist/{chunk-ZI647VB5.js → chunk-YPI3QQCF.js} +2 -2
  113. package/dist/chunk-Z2RR6MAK.js +127 -0
  114. package/dist/{chunk-FBVTI2TJ.js → chunk-Z4TXYIEG.js} +12 -131
  115. package/dist/cli/index.js +47 -36
  116. package/dist/{clio-QVTYJ57A.js → clio-2JXHBBY5.js} +7 -7
  117. package/dist/{code-nav-FGGFIE7L.js → code-nav-3YYRMYNF.js} +8 -8
  118. package/dist/{compile-cache-CVJMMODC.js → compile-cache-7FPE6PS3.js} +3 -3
  119. package/dist/{components-ZFA3SAER.js → components-RYZV4JGP.js} +5 -5
  120. package/dist/{config-LW5IJFQN.js → config-QZPCMYSO.js} +99 -63
  121. package/dist/{configure-7XIZCOU4.js → configure-TEGEBYCA.js} +23 -22
  122. package/dist/{context-Y6Y7QPR6.js → context-AV7OEZ4D.js} +12 -12
  123. package/dist/{context-L3WL3X7K.js → context-E6H5RNMC.js} +56 -47
  124. package/dist/{context-N52ZA626.js → context-GSXUE4CT.js} +29 -27
  125. package/dist/{context-clear-MBQRLSDQ.js → context-clear-SHIBYK6T.js} +55 -46
  126. package/dist/{context-index-HVMFQHK3.js → context-index-HNG3MOME.js} +2 -2
  127. package/dist/{context-working-set-GS6DSO7F.js → context-working-set-5ZGKPGZQ.js} +13 -13
  128. package/dist/{dispatch-runner-22ZCNOM3.js → dispatch-runner-EFMJT4LD.js} +93 -64
  129. package/dist/{docs-7LQ23DLM.js → docs-23KQS3XK.js} +5 -5
  130. package/dist/doctor-QOA5FNY5.js +313 -0
  131. package/dist/{eval-BEC2WHDA.js → eval-TFBYQH4H.js} +2032 -156
  132. package/dist/eval-inventory-SXH7PDKX.js +316 -0
  133. package/dist/{evidence-REJUMSKM.js → evidence-ERGESKGN.js} +203 -48
  134. package/dist/{evolve-PY5ZBA5K.js → evolve-VDXTSYCJ.js} +52 -43
  135. package/dist/{extensions-HVKU65YU.js → extensions-7BGBHN57.js} +13 -7
  136. package/dist/{fleet-7WZEWRFA.js → fleet-2RRVDF2V.js} +228 -108
  137. package/dist/{fleet-commands-UVHWM76J.js → fleet-commands-VJ726XIA.js} +11 -11
  138. package/dist/fleet-decisions-EPAPM3XJ.js +157 -0
  139. package/dist/{fleet-graph-6ULH7PES.js → fleet-graph-JF5QOATM.js} +18 -15
  140. package/dist/fleet-inspect-VLY4S7QM.js +442 -0
  141. package/dist/{fleet-preflight-J53T6CCE.js → fleet-preflight-AIZUEJOY.js} +5 -5
  142. package/dist/{fleet-validate-72PC4SLA.js → fleet-validate-AJRPDMDV.js} +22 -19
  143. package/dist/fleet-verify-JFEL2L3H.js +175 -0
  144. package/dist/fleet-view-ZCON35AG.js +102 -0
  145. package/dist/{init-OG3TPGQG.js → init-DN2WWLFE.js} +72 -62
  146. package/dist/install-XGLBQY5E.js +13 -0
  147. package/dist/interop-OZBKXAYL.js +114 -0
  148. package/dist/{library-CNTMPLRF.js → library-YWZG7IMW.js} +21 -18
  149. package/dist/{memory-6IS7F275.js → memory-I4C4HMLW.js} +54 -45
  150. package/dist/{models-ENRJDA5W.js → models-CEYXJBO6.js} +33 -30
  151. package/dist/{monitor-XLDVO7TN.js → monitor-NZ6GCI3P.js} +59 -52
  152. package/dist/{orchestrator-6KSPYRHA.js → orchestrator-GCGQ4N5I.js} +7827 -7328
  153. package/dist/panes-HMABYVO4.js +58 -0
  154. package/dist/panes-KY6W3V2E.js +103 -0
  155. package/dist/{paths-DBXMZMDU.js → paths-II4K7DNR.js} +5 -5
  156. package/dist/{reset-RZ4ER727.js → reset-DQ6FGCSH.js} +13 -11
  157. package/dist/resources-BB3MVJMD.js +111 -0
  158. package/dist/{run-Y2CNK5RU.js → run-H2GQDUER.js} +119 -87
  159. package/dist/{share-A55GYP6Z.js → share-GTJN6A5O.js} +20 -17
  160. package/dist/{skills-ALC5J6AT.js → skills-L55TEW6R.js} +33 -24
  161. package/dist/{skills-eval-JPBEBYQU.js → skills-eval-XVXPH2JI.js} +67 -56
  162. package/dist/skills-inventory-S4MXPJFV.js +126 -0
  163. package/dist/slash-commands-ZSGASKJC.js +77 -0
  164. package/dist/{steer-GGWFUJUD.js → steer-RZGSCY4R.js} +4 -4
  165. package/dist/{support-MIETYA5E.js → support-PKEUNNQL.js} +6 -6
  166. package/dist/{targets-VGNXIR3S.js → targets-NCPZ644J.js} +68 -40
  167. package/dist/{terminal-lease-WOBR64YA.js → terminal-lease-44SV3YCN.js} +6 -4
  168. package/dist/tools-DAF3DI3C.js +27 -0
  169. package/dist/{trace-PNCASAXC.js → trace-FYVW2MQA.js} +207 -10
  170. package/dist/tui-primitives-2AKXQNZK.js +13 -0
  171. package/dist/{uninstall-ZJF5H5ZN.js → uninstall-DW2PNOIC.js} +5 -5
  172. package/dist/{upgrade-FUSUAGHR.js → upgrade-3XPP6OQL.js} +27 -24
  173. package/dist/{usage-N4MKVHKD.js → usage-3NLHGTU2.js} +114 -62
  174. package/dist/{verifiers-YAWOJ3H2.js → verifiers-SSQONKRT.js} +172 -13
  175. package/dist/{verify-LTDHYBGY.js → verify-3U6J7FZI.js} +10 -10
  176. package/dist/{web-fetch-2YHJ3KTG.js → web-fetch-S7RR6GZ7.js} +3 -3
  177. package/dist/{wiki-generate-6M7GHTBJ.js → wiki-generate-CEHYGPGQ.js} +75 -65
  178. package/dist/with-panes-MKB46MPQ.js +782 -0
  179. package/dist/worker/entry.js +106 -75
  180. package/docs/README.md +3 -2
  181. package/docs/acp.md +24 -3
  182. package/docs/alcf-provider.md +1 -1
  183. package/docs/architecture.md +2 -2
  184. package/docs/artifact-versions.md +9 -1
  185. package/docs/built-in-agents.md +1 -1
  186. package/docs/capacity-and-scheduling.md +62 -3
  187. package/docs/commands-and-modes.md +31 -2
  188. package/docs/configuration-and-targets.md +69 -12
  189. package/docs/context-engine.md +63 -4
  190. package/docs/development-pipeline.md +19 -0
  191. package/docs/dispatch-typed-intent.md +385 -0
  192. package/docs/documentation-coverage.md +5 -5
  193. package/docs/documentation-guide.md +1 -1
  194. package/docs/environment-variables.md +3 -0
  195. package/docs/eval-runner.md +262 -11
  196. package/docs/evals-internal.md +72 -2
  197. package/docs/evidence-and-memory.md +12 -11
  198. package/docs/evolution.md +1 -1
  199. package/docs/exit-codes-and-output.md +1 -1
  200. package/docs/extensions-and-sharing.md +27 -1
  201. package/docs/fleet-dispatch.md +25 -4
  202. package/docs/installation-and-lifecycle.md +15 -2
  203. package/docs/middleware-and-components.md +1 -1
  204. package/docs/model-catalog.md +10 -1
  205. package/docs/observability.md +55 -4
  206. package/docs/proactive-memory.md +127 -14
  207. package/docs/prompt-envelope-and-tools.md +19 -1
  208. package/docs/provider-adapter-cookbook.md +1 -1
  209. package/docs/release-cut-checklist.md +19 -3
  210. package/docs/safety-model.md +2 -2
  211. package/docs/scientific-validation.md +3 -3
  212. package/docs/session-lifecycle.md +1 -1
  213. package/docs/skills-marketplace.md +1 -1
  214. package/docs/tool-usage.md +18 -8
  215. package/docs/trace-store.md +1 -1
  216. package/docs/troubleshooting.md +88 -1
  217. package/docs/tui-design.md +1 -1
  218. package/docs/worker-dispatch-mechanics.md +1 -1
  219. package/package.json +5 -2
  220. package/src/cli/acp.ts +6 -2
  221. package/src/cli/agents.ts +1 -1
  222. package/src/cli/argv.ts +25 -0
  223. package/src/cli/config-inspect.ts +33 -6
  224. package/src/cli/config.ts +1 -1
  225. package/src/cli/configure.ts +23 -21
  226. package/src/cli/doctor-panes.ts +124 -0
  227. package/src/cli/doctor-state-size.ts +82 -0
  228. package/src/cli/doctor-toolchain.ts +57 -0
  229. package/src/cli/doctor.ts +22 -1
  230. package/src/cli/eval-inventory.ts +436 -0
  231. package/src/cli/eval.ts +93 -16
  232. package/src/cli/evidence-detail.ts +88 -0
  233. package/src/cli/evidence-inventory.ts +183 -0
  234. package/src/cli/evidence.ts +30 -5
  235. package/src/cli/extensions.ts +5 -1
  236. package/src/cli/fleet-decisions.ts +69 -0
  237. package/src/cli/fleet-inspect.ts +334 -0
  238. package/src/cli/fleet-verify.ts +133 -0
  239. package/src/cli/fleet-view.ts +810 -0
  240. package/src/cli/fleet.ts +179 -39
  241. package/src/cli/index.ts +14 -2
  242. package/src/cli/interop-inspect.ts +128 -0
  243. package/src/cli/interop.ts +34 -0
  244. package/src/cli/panes.ts +35 -0
  245. package/src/cli/reset.ts +5 -2
  246. package/src/cli/run.ts +58 -0
  247. package/src/cli/skills-inventory.ts +185 -0
  248. package/src/cli/skills.ts +16 -13
  249. package/src/cli/targets.ts +44 -13
  250. package/src/cli/tools.ts +321 -0
  251. package/src/cli/trace-inspect.ts +252 -0
  252. package/src/cli/trace.ts +85 -5
  253. package/src/cli/usage.ts +63 -14
  254. package/src/cli/verifiers-inspect.ts +347 -0
  255. package/src/cli/verifiers.ts +9 -0
  256. package/src/core/bus-events.ts +33 -1
  257. package/src/core/cache-telemetry.ts +42 -0
  258. package/src/core/config.ts +67 -0
  259. package/src/core/defaults.ts +124 -8
  260. package/src/core/endpoint-key.ts +27 -0
  261. package/src/core/residency-target-key.ts +25 -0
  262. package/src/core/response-schema.ts +80 -6
  263. package/src/core/theme-token-hex.ts +43 -0
  264. package/src/core/tool-names.ts +2 -1
  265. package/src/core/xdg.ts +1 -1
  266. package/src/domains/agents/fleets/build-review.md +0 -3
  267. package/src/domains/agents/fleets/build-test.md +0 -3
  268. package/src/domains/agents/result-contract-filesystem.ts +32 -0
  269. package/src/domains/agents/result-contract.ts +164 -35
  270. package/src/domains/config/classify.ts +6 -0
  271. package/src/domains/context/codewiki/coordinator.ts +12 -4
  272. package/src/domains/dispatch/admission-error.ts +9 -0
  273. package/src/domains/dispatch/admission.ts +52 -14
  274. package/src/domains/dispatch/capacity-lease.ts +118 -9
  275. package/src/domains/dispatch/contract.ts +11 -0
  276. package/src/domains/dispatch/council-topology.ts +398 -0
  277. package/src/domains/dispatch/execution-plan.ts +44 -4
  278. package/src/domains/dispatch/extension.ts +378 -82
  279. package/src/domains/dispatch/fleet-node-prompt.ts +62 -0
  280. package/src/domains/dispatch/fleet-plan.ts +7 -2
  281. package/src/domains/dispatch/fleet-run.ts +87 -4
  282. package/src/domains/dispatch/gate-decisions.ts +11 -1
  283. package/src/domains/dispatch/gate-role-prompts.ts +9 -0
  284. package/src/domains/dispatch/gate-topology.ts +289 -0
  285. package/src/domains/dispatch/heartbeat.ts +32 -8
  286. package/src/domains/dispatch/index.ts +22 -0
  287. package/src/domains/dispatch/intent-compatibility.ts +330 -0
  288. package/src/domains/dispatch/intent.ts +85 -1
  289. package/src/domains/dispatch/orphan-recovery.ts +5 -0
  290. package/src/domains/dispatch/reservation-store.ts +139 -11
  291. package/src/domains/dispatch/run-event-journal-bridge.ts +149 -0
  292. package/src/domains/dispatch/run-event-journal.ts +598 -0
  293. package/src/domains/dispatch/state.ts +49 -1
  294. package/src/domains/dispatch/types.ts +13 -0
  295. package/src/domains/dispatch/validation.ts +33 -8
  296. package/src/domains/dispatch/worker-spawn.ts +25 -11
  297. package/src/domains/dispatch/write-boundary-enforcer.ts +20 -3
  298. package/src/domains/dispatch/write-boundary.ts +62 -1
  299. package/src/domains/eval/artifacts/store.ts +62 -0
  300. package/src/domains/eval/compare/behavioral.ts +224 -0
  301. package/src/domains/eval/compare/compare.ts +342 -2
  302. package/src/domains/eval/compare/envelope.ts +128 -0
  303. package/src/domains/eval/compare/gates.ts +24 -6
  304. package/src/domains/eval/compare/thresholds.ts +30 -3
  305. package/src/domains/eval/execution-provenance.ts +240 -0
  306. package/src/domains/eval/inventory.ts +113 -0
  307. package/src/domains/eval/metrics/aggregate.ts +136 -0
  308. package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
  309. package/src/domains/eval/metrics/tracked.ts +413 -0
  310. package/src/domains/eval/provenance.ts +117 -0
  311. package/src/domains/eval/reports/comparison.ts +128 -0
  312. package/src/domains/eval/reports/junit.ts +17 -3
  313. package/src/domains/eval/reports/markdown.ts +3 -3
  314. package/src/domains/eval/reports/text.ts +14 -0
  315. package/src/domains/eval/run-compare.ts +20 -0
  316. package/src/domains/eval/runners/clio-run.ts +127 -0
  317. package/src/domains/eval/runners/external-command.ts +28 -3
  318. package/src/domains/eval/schema/adapter.ts +111 -0
  319. package/src/domains/eval/schema/artifact.ts +20 -0
  320. package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
  321. package/src/domains/eval/schema/behavioral.ts +520 -0
  322. package/src/domains/eval/schema/execution-envelope.ts +194 -0
  323. package/src/domains/eval/schema/serving.ts +105 -0
  324. package/src/domains/eval/schema/suite.ts +38 -8
  325. package/src/domains/eval/schema/validate.ts +58 -3
  326. package/src/domains/eval/schema/verdict.ts +237 -0
  327. package/src/domains/eval/suites/resolve.ts +2 -0
  328. package/src/domains/eval/suites/run.ts +264 -33
  329. package/src/domains/eval/verifiers/command.ts +2 -1
  330. package/src/domains/eval/workspaces/temp-copy.ts +145 -13
  331. package/src/domains/evidence/build.ts +2 -13
  332. package/src/domains/evidence/eval.ts +2 -12
  333. package/src/domains/evidence/findings-markdown.ts +33 -0
  334. package/src/domains/evidence/run-trust.ts +7 -113
  335. package/src/domains/evidence/store.ts +6 -0
  336. package/src/domains/evidence/trust-projection.ts +2 -2
  337. package/src/domains/extensions/compatibility.ts +285 -0
  338. package/src/domains/extensions/discovery.ts +38 -3
  339. package/src/domains/extensions/resources.ts +1 -1
  340. package/src/domains/extensions/state.ts +12 -3
  341. package/src/domains/extensions/types.ts +2 -0
  342. package/src/domains/lifecycle/doctor.ts +69 -1
  343. package/src/domains/memory/index.ts +14 -1
  344. package/src/domains/memory/task-bank-promotion.ts +64 -0
  345. package/src/domains/memory/task-memory-policy.ts +82 -17
  346. package/src/domains/memory/task-memory-spend.ts +131 -0
  347. package/src/domains/memory/task-memory-status.ts +7 -0
  348. package/src/domains/memory/task-memory-telemetry.ts +3 -0
  349. package/src/domains/middleware/index.ts +1 -0
  350. package/src/domains/middleware/memory-intervention.ts +97 -21
  351. package/src/domains/middleware/memory-step-endpoint.ts +71 -0
  352. package/src/domains/mux/contract.ts +434 -0
  353. package/src/domains/mux/detect.ts +158 -0
  354. package/src/domains/mux/extension.ts +47 -0
  355. package/src/domains/mux/index.ts +96 -0
  356. package/src/domains/mux/manifest.ts +6 -0
  357. package/src/domains/mux/operations.ts +164 -0
  358. package/src/domains/mux/pane-registry.ts +90 -0
  359. package/src/domains/mux/protocol.ts +49 -0
  360. package/src/domains/mux/socket-client.ts +816 -0
  361. package/src/domains/mux/types.ts +222 -0
  362. package/src/domains/mux/viewer-command.ts +59 -0
  363. package/src/domains/mux/yazi/assets/init.lua +2 -0
  364. package/src/domains/mux/yazi/assets/plugins/git.yazi/LICENSE +21 -0
  365. package/src/domains/mux/yazi/assets/plugins/git.yazi/README.md +78 -0
  366. package/src/domains/mux/yazi/assets/plugins/git.yazi/main.lua +255 -0
  367. package/src/domains/mux/yazi/assets/plugins/git.yazi/types.lua +12 -0
  368. package/src/domains/mux/yazi/assets/yazi.toml +17 -0
  369. package/src/domains/mux/yazi/event-stream.ts +180 -0
  370. package/src/domains/mux/yazi/profile.ts +299 -0
  371. package/src/domains/mux/yazi/session.ts +228 -0
  372. package/src/domains/mux/yazi/theme.ts +30 -0
  373. package/src/domains/observability/background-memory-usage.ts +140 -0
  374. package/src/domains/observability/cost.ts +22 -1
  375. package/src/domains/observability/index.ts +9 -0
  376. package/src/domains/observability/out-of-turn-usage.ts +51 -2
  377. package/src/domains/observability/trace-store.ts +234 -2
  378. package/src/domains/prompts/compiler.ts +100 -13
  379. package/src/domains/providers/endpoint-capacity.ts +228 -0
  380. package/src/domains/providers/endpoint-slots-store.ts +189 -0
  381. package/src/domains/providers/extension.ts +20 -3
  382. package/src/domains/providers/index.ts +32 -0
  383. package/src/domains/providers/model-runtime-capabilities.ts +32 -0
  384. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
  385. package/src/domains/providers/runtime-resolution.ts +8 -1
  386. package/src/domains/providers/runtimes/boot-manifest.ts +1 -0
  387. package/src/domains/providers/runtimes/builtins.ts +2 -0
  388. package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
  389. package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
  390. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
  391. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
  392. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
  393. package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
  394. package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
  395. package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
  396. package/src/domains/providers/runtimes/protocol/litellm.ts +375 -0
  397. package/src/domains/providers/support.ts +1 -0
  398. package/src/domains/providers/target-model-cache.ts +124 -0
  399. package/src/domains/providers/types/capability-flags.ts +2 -0
  400. package/src/domains/providers/types/target-descriptor.ts +2 -0
  401. package/src/domains/resources/index.ts +3 -0
  402. package/src/domains/resources/prompts/loader.ts +95 -33
  403. package/src/domains/resources/skills/loader.ts +33 -0
  404. package/src/domains/safety/action-classifier.ts +6 -0
  405. package/src/domains/safety/call-target.ts +52 -0
  406. package/src/domains/safety/run-effects.ts +35 -4
  407. package/src/domains/session/context-accounting.ts +52 -1
  408. package/src/domains/session/context-ledger.ts +37 -13
  409. package/src/domains/session/index.ts +6 -0
  410. package/src/domains/session/prompt-cache.ts +140 -0
  411. package/src/domains/session/prompt-manifest.ts +42 -0
  412. package/src/domains/toolchain/archive.ts +175 -0
  413. package/src/domains/toolchain/contract.ts +28 -0
  414. package/src/domains/toolchain/extension.ts +47 -0
  415. package/src/domains/toolchain/index.ts +39 -0
  416. package/src/domains/toolchain/install.ts +327 -0
  417. package/src/domains/toolchain/manifest.ts +8 -0
  418. package/src/domains/toolchain/paths.ts +34 -0
  419. package/src/domains/toolchain/registry.ts +265 -0
  420. package/src/domains/toolchain/remove.ts +218 -0
  421. package/src/domains/toolchain/resolve.ts +182 -0
  422. package/src/domains/toolchain/types.ts +113 -0
  423. package/src/domains/toolchain/version.ts +88 -0
  424. package/src/engine/acp/adapter.ts +18 -3
  425. package/src/engine/acp/server.ts +413 -70
  426. package/src/engine/acp/types.ts +19 -1
  427. package/src/engine/ai.ts +35 -0
  428. package/src/engine/apis/llamacpp-residency.ts +55 -3
  429. package/src/engine/apis/lmstudio.ts +25 -5
  430. package/src/engine/apis/ollama-native.ts +2 -1
  431. package/src/engine/apis/openai-completions.ts +80 -17
  432. package/src/engine/apis/residency-lock.ts +3 -1
  433. package/src/engine/apis/residency.ts +34 -1
  434. package/src/engine/claude/sdk-module.ts +98 -0
  435. package/src/engine/claude/sdk-runtime.ts +19 -11
  436. package/src/engine/provider-payload.ts +29 -1
  437. package/src/engine/tui-primitives.ts +21 -0
  438. package/src/engine/tui.ts +1 -0
  439. package/src/engine/worker-runtime.ts +2 -13
  440. package/src/entry/boot-options.ts +2 -0
  441. package/src/entry/orchestrator.ts +279 -35
  442. package/src/entry/panes-activation.ts +31 -0
  443. package/src/entry/with-panes.ts +20 -0
  444. package/src/interactive/chat-loop-messages.ts +26 -7
  445. package/src/interactive/chat-loop.ts +318 -41
  446. package/src/interactive/chat-panel.ts +62 -8
  447. package/src/interactive/clio-editor.ts +45 -8
  448. package/src/interactive/context-activity.ts +5 -1
  449. package/src/interactive/context-meter.ts +1 -1
  450. package/src/interactive/context-overlay.ts +41 -10
  451. package/src/interactive/cost-overlay.ts +66 -6
  452. package/src/interactive/council-grid.ts +1 -3
  453. package/src/interactive/council.ts +11 -0
  454. package/src/interactive/dispatch-board.ts +102 -20
  455. package/src/interactive/fleet-run-preview.ts +41 -15
  456. package/src/interactive/handoff-round.ts +41 -2
  457. package/src/interactive/interactive-application.ts +177 -7
  458. package/src/interactive/interactive-input-runtime.ts +15 -0
  459. package/src/interactive/interactive-presentation.ts +4 -0
  460. package/src/interactive/interactive-shell.ts +20 -17
  461. package/src/interactive/interactive-slash-runtime.ts +63 -10
  462. package/src/interactive/memory-overlay.ts +9 -0
  463. package/src/interactive/modal-marker.ts +170 -0
  464. package/src/interactive/mutation-preview.ts +295 -0
  465. package/src/interactive/mux-bridge.ts +214 -0
  466. package/src/interactive/overlay-frame.ts +58 -2
  467. package/src/interactive/overlay-general-openers.ts +17 -0
  468. package/src/interactive/overlay-key-routing.ts +52 -3
  469. package/src/interactive/overlay-lifecycle.ts +56 -7
  470. package/src/interactive/overlay-model-selectors.ts +40 -3
  471. package/src/interactive/overlay-permission-lifecycle.ts +112 -24
  472. package/src/interactive/overlay-session-lifecycle.ts +73 -9
  473. package/src/interactive/overlay-transitions.ts +18 -4
  474. package/src/interactive/overlays/agents.ts +1 -0
  475. package/src/interactive/overlays/ask-user.ts +227 -49
  476. package/src/interactive/overlays/auth-dialog.ts +1 -0
  477. package/src/interactive/overlays/context-reset.ts +1 -0
  478. package/src/interactive/overlays/cwd-fallback.ts +1 -0
  479. package/src/interactive/overlays/decisions.ts +11 -11
  480. package/src/interactive/overlays/extensions.ts +1 -0
  481. package/src/interactive/overlays/fleet-run-approval.ts +1 -0
  482. package/src/interactive/overlays/handoff-review.ts +1 -0
  483. package/src/interactive/overlays/help-reference.ts +6 -0
  484. package/src/interactive/overlays/interop.ts +1 -0
  485. package/src/interactive/overlays/library-install-confirm.ts +1 -0
  486. package/src/interactive/overlays/library-tabs.ts +28 -0
  487. package/src/interactive/overlays/list-overlay.ts +10 -1
  488. package/src/interactive/overlays/message-picker.ts +1 -0
  489. package/src/interactive/overlays/model-scope.ts +86 -0
  490. package/src/interactive/overlays/model-selector.ts +1 -0
  491. package/src/interactive/overlays/prompts.ts +12 -1
  492. package/src/interactive/overlays/session-selector.ts +1 -0
  493. package/src/interactive/overlays/settings-sections.ts +30 -0
  494. package/src/interactive/overlays/settings.ts +614 -45
  495. package/src/interactive/overlays/side-question.ts +1 -0
  496. package/src/interactive/overlays/skills-hub.ts +3 -11
  497. package/src/interactive/overlays/tree-selector.ts +1 -0
  498. package/src/interactive/pane-policy.ts +46 -0
  499. package/src/interactive/panes-runtime.ts +292 -0
  500. package/src/interactive/permission-hint.ts +34 -2
  501. package/src/interactive/permission-overlay.ts +159 -9
  502. package/src/interactive/prewarm.ts +197 -0
  503. package/src/interactive/render-trace.ts +162 -15
  504. package/src/interactive/renderers/compaction-summary.ts +29 -0
  505. package/src/interactive/renderers/tool-execution.ts +4 -0
  506. package/src/interactive/renderers/worker-entry.ts +122 -14
  507. package/src/interactive/side-question.ts +58 -1
  508. package/src/interactive/slash-commands.ts +251 -15
  509. package/src/interactive/status/controller.ts +11 -0
  510. package/src/interactive/status/state-machine.ts +54 -2
  511. package/src/interactive/status/types.ts +7 -0
  512. package/src/interactive/tasks-overlay.ts +1 -0
  513. package/src/interactive/terminal-lease.ts +2 -0
  514. package/src/interactive/theme/tokens.ts +3 -14
  515. package/src/interactive/turn-context.ts +346 -33
  516. package/src/interactive/turn-persistence.ts +14 -4
  517. package/src/interactive/turn-prewarm.ts +364 -0
  518. package/src/interactive/turn-queues.ts +7 -4
  519. package/src/interactive/turn-runtime.ts +8 -1
  520. package/src/interactive/turn-state.ts +23 -0
  521. package/src/interactive/view/artifacts.ts +109 -1
  522. package/src/interactive/view/view-overlay.ts +29 -3
  523. package/src/interactive/watch-pane.ts +152 -0
  524. package/src/interactive/worker-progress.ts +7 -1
  525. package/src/interactive/worker-receipts.ts +19 -1
  526. package/src/interactive/worker-stream.ts +5 -0
  527. package/src/interactive/yazi-bridge.ts +444 -0
  528. package/src/tools/ask-user.ts +43 -2
  529. package/src/tools/bootstrap.ts +26 -2
  530. package/src/tools/builtin-tool-catalog.ts +15 -0
  531. package/src/tools/compete-worktrees.ts +83 -2
  532. package/src/tools/core-bootstrap.ts +2 -1
  533. package/src/tools/dispatch-admission.ts +14 -3
  534. package/src/tools/dispatch-arguments.ts +20 -20
  535. package/src/tools/dispatch-plan.ts +17 -9
  536. package/src/tools/dispatch-run-events.ts +134 -19
  537. package/src/tools/dispatch-runner.ts +29 -7
  538. package/src/tools/dispatch-scout.ts +1 -1
  539. package/src/tools/dispatch-types.ts +15 -3
  540. package/src/tools/dispatch.ts +1 -1
  541. package/src/tools/executables.ts +17 -14
  542. package/src/tools/observation.ts +54 -4
  543. package/src/tools/panes-surface.ts +38 -0
  544. package/src/tools/panes.ts +112 -0
  545. package/src/tools/policy.ts +10 -1
  546. package/src/tools/presentation.ts +1 -0
  547. package/src/tools/registry.ts +16 -0
  548. package/dist/chunk-AOCYTWAV.js +0 -449
  549. package/dist/chunk-HLE42MG7.js +0 -37
  550. package/dist/chunk-HWUFFB6L.js +0 -83
  551. package/dist/chunk-JOZYP4GM.js +0 -279
  552. package/dist/doctor-M7YEDGAE.js +0 -91
@@ -21,6 +21,7 @@ import {
21
21
  import { isExecutionRole } from "./execution-role.js";
22
22
  import { COMPETE_STANCES, type CompeteStance } from "./gate-role-prompts.js";
23
23
  import { type DispatchIntent, isDispatchIntent } from "./intent.js";
24
+ import { classifyDispatchIntentCompatibility, dispatchIntentRefusals } from "./intent-compatibility.js";
24
25
  import { isRoutingIntent, type RoutingIntent } from "./routing-intent.js";
25
26
  import type {
26
27
  DispatchRequestOrigin,
@@ -478,14 +479,21 @@ export function validateJobSpec(spec: unknown): Validated {
478
479
  errors.push("writeRoots must be a non-empty array of non-empty strings");
479
480
  }
480
481
  }
481
- if ("intent" in spec && spec.intent !== undefined && !isDispatchIntent(spec.intent)) {
482
- errors.push("intent must be a normalized dispatch intent");
483
- }
484
- if (isDispatchIntent(spec.intent) && spec.intent.writeRoots.length > 0 && Array.isArray(spec.writeRoots)) {
485
- const jobCwd = typeof spec.cwd === "string" && spec.cwd.length > 0 ? spec.cwd : process.cwd();
486
- const legacy = [...new Set(spec.writeRoots.map((root) => resolvePathBoundary(jobCwd, String(root))))].sort();
487
- const declared = [...new Set(spec.intent.writeRoots.map((root) => resolvePathBoundary(jobCwd, root)))].sort();
488
- if (JSON.stringify(legacy) !== JSON.stringify(declared)) errors.push("intent_write_roots_contradiction");
482
+ // One owner for the typed-intent migration rules (#163). Every dispatch
483
+ // producer reaches a worker through this validator, so classifying here is
484
+ // what makes the compatibility table true of the fleet, CLI, ACP, and
485
+ // extension paths rather than only of the model-facing tool.
486
+ if ("intent" in spec) {
487
+ for (const refusal of dispatchIntentRefusals(
488
+ classifyDispatchIntentCompatibility({
489
+ intent: spec.intent,
490
+ ...(spec.writeRoots !== undefined ? { writeRoots: spec.writeRoots } : {}),
491
+ ...(spec.autonomy !== undefined ? { autonomy: spec.autonomy } : {}),
492
+ ...(typeof spec.cwd === "string" && spec.cwd.length > 0 ? { cwd: spec.cwd } : {}),
493
+ }),
494
+ )) {
495
+ errors.push(refusal.message);
496
+ }
489
497
  }
490
498
 
491
499
  if ("requestOrigin" in spec && spec.requestOrigin !== undefined) {
@@ -696,6 +704,8 @@ function isValidProtectedArtifactRemap(value: unknown): value is ProtectedArtifa
696
704
 
697
705
  const VALID_GATE_ROLES = new Set(["builder", "reviewer", "candidate", "judge", "member", "synthesis"]);
698
706
  const VALID_GATE_VERDICTS = new Set(["pass", "fail", "revise"]);
707
+ const VALID_WORKTREE_BACKENDS = new Set(["herdr", "native"]);
708
+ const VALID_WORKTREE_FALLBACKS = new Set(["mux-unavailable", "mux-operation-failed"]);
699
709
  const VALID_PLAN_TOPOLOGIES = new Set([
700
710
  "parallel",
701
711
  "sequential",
@@ -724,6 +734,20 @@ function isValidGate(value: unknown): value is RunGateProvenance {
724
734
  if (value.verdict !== undefined && (typeof value.verdict !== "string" || !VALID_GATE_VERDICTS.has(value.verdict))) {
725
735
  return false;
726
736
  }
737
+ if (value.worktree !== undefined) {
738
+ if (!isPlainObject(value.worktree)) return false;
739
+ if (typeof value.worktree.backend !== "string" || !VALID_WORKTREE_BACKENDS.has(value.worktree.backend)) return false;
740
+ if (typeof value.worktree.path !== "string" || !path.isAbsolute(value.worktree.path)) return false;
741
+ if (typeof value.worktree.branch !== "string" || value.worktree.branch.length === 0) return false;
742
+ if (value.worktree.workspaceId !== undefined && typeof value.worktree.workspaceId !== "string") return false;
743
+ if (
744
+ value.worktree.fallback !== undefined &&
745
+ (typeof value.worktree.fallback !== "string" || !VALID_WORKTREE_FALLBACKS.has(value.worktree.fallback))
746
+ ) {
747
+ return false;
748
+ }
749
+ if (value.worktree.backend === "herdr" && typeof value.worktree.workspaceId !== "string") return false;
750
+ }
727
751
  return true;
728
752
  }
729
753
 
@@ -734,6 +758,7 @@ function cloneGate(gate: RunGateProvenance): RunGateProvenance {
734
758
  cycle: gate.cycle,
735
759
  ...(gate.subjects !== undefined ? { subjects: gate.subjects.map((subject) => ({ ...subject })) } : {}),
736
760
  ...(gate.verdict !== undefined ? { verdict: gate.verdict } : {}),
761
+ ...(gate.worktree !== undefined ? { worktree: { ...gate.worktree } } : {}),
737
762
  };
738
763
  }
739
764
 
@@ -26,10 +26,12 @@
26
26
 
27
27
  import { type ChildProcess, spawn } from "node:child_process";
28
28
  import { join } from "node:path";
29
+ import { performance } from "node:perf_hooks";
29
30
  import { withClioAgentEnvironment } from "../../core/agent-environment.js";
30
31
  import { enableClioCompileCache, workerCompileCacheEnvironment } from "../../core/compile-cache.js";
31
32
  import { resolvePackageRoot } from "../../core/package-root.js";
32
33
  import type { WorkerSpec } from "../../worker/spec-contract.js";
34
+ import type { HeartbeatStamp } from "./heartbeat.js";
33
35
  import {
34
36
  type AgentLedgerBody,
35
37
  type ApprovedWorkerIdentity,
@@ -71,7 +73,7 @@ export interface SpawnedWorker {
71
73
  promise: Promise<SpawnedWorkerResult>;
72
74
  events: AsyncIterableIterator<unknown>;
73
75
  abort(): void;
74
- heartbeatAt: { current: number };
76
+ heartbeatAt: HeartbeatStamp;
75
77
  /**
76
78
  * Write one JSON line to the worker's open stdin (the same line protocol
77
79
  * that carried the spec). Returns false when the worker has exited, its
@@ -134,15 +136,14 @@ export interface WorkerProcessOptions {
134
136
  /** How long bulk output may precede the announce before the peer is refused. */
135
137
  attestationGraceMs?: number;
136
138
  /**
137
- * Clock behind the heartbeat stamp, defaulting to `Date.now`. The stamp stays
138
- * a wall-clock instant in production because the ledger, the receipt, and the
139
- * evidence record all serialize it as ISO-8601 for an operator to read. That
140
- * leaves a test with no honest way to assert the stamp advanced: comparing
141
- * two wall-clock reads taken either side of an interval orders them backward
142
- * on any host that resyncs its clock in between. A test injects the steppable
143
- * clock from tests/harness/clock.ts here and asserts on that instead.
139
+ * Wall clock behind the heartbeat anchor, defaulting to `Date.now`. The
140
+ * anchor stays a wall-clock instant because the ledger and evidence record
141
+ * serialize the derived heartbeat as ISO-8601 for an operator to read. This
142
+ * clock is read once; later heartbeat instants add a monotonic span to it.
144
143
  */
145
144
  now?: () => number;
145
+ /** Monotonic clock used for heartbeat spans, defaulting to `performance.now`. */
146
+ monotonicNow?: () => number;
146
147
  }
147
148
 
148
149
  /**
@@ -192,6 +193,7 @@ export function spawnWorkerProcess(
192
193
  const shutdownGraceMs = opts?.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS;
193
194
  const attestationGraceMs = opts?.attestationGraceMs ?? DEFAULT_ATTESTATION_GRACE_MS;
194
195
  const now = opts?.now ?? Date.now;
196
+ const monotonicNow = opts?.monotonicNow ?? (() => performance.now());
195
197
  const approved = opts?.approvedIdentity ?? approvedIdentityForSpec(spec);
196
198
 
197
199
  const child: ChildProcess = spawn(command, [...args], {
@@ -204,7 +206,19 @@ export function spawnWorkerProcess(
204
206
  });
205
207
  const pid = child.pid ?? null;
206
208
 
207
- const heartbeatAt = { current: now() };
209
+ // One wall anchor plus a monotonic span keeps the persisted/display instant
210
+ // readable without ever asking a later wall-clock read how much time passed.
211
+ const heartbeatWallAnchor = now();
212
+ const heartbeatMonotonicAnchor = monotonicNow();
213
+ const heartbeatAt: HeartbeatStamp = {
214
+ current: heartbeatWallAnchor,
215
+ monotonic: heartbeatMonotonicAnchor,
216
+ };
217
+ const stampHeartbeat = (): void => {
218
+ const stamp = monotonicNow();
219
+ heartbeatAt.monotonic = stamp;
220
+ heartbeatAt.current = heartbeatWallAnchor + (stamp - heartbeatMonotonicAnchor);
221
+ };
208
222
 
209
223
  const queue = createBoundedEventQueue();
210
224
  const waiters: Array<(r: IteratorResult<unknown>) => void> = [];
@@ -233,7 +247,7 @@ export function spawnWorkerProcess(
233
247
  }
234
248
 
235
249
  function push(value: unknown): void {
236
- heartbeatAt.current = now();
250
+ stampHeartbeat();
237
251
  const w = waiters.shift();
238
252
  if (w) {
239
253
  w({ value, done: false });
@@ -378,7 +392,7 @@ export function spawnWorkerProcess(
378
392
  if (!announceAccepted) failAttestation(`Invalid worker attestation: ${frame.reason}`);
379
393
  return;
380
394
  }
381
- heartbeatAt.current = now();
395
+ stampHeartbeat();
382
396
  if (frame.value.kind === "announce") {
383
397
  if (announceAccepted || announceFailed) return;
384
398
  const verdict = verifyWorkerAttestation(frame.value.attestation, approved);
@@ -26,6 +26,7 @@ import {
26
26
  enforceWriteBoundary,
27
27
  type WorkspaceSnapshot,
28
28
  type WriteBoundaryAttribution,
29
+ type WriteBoundaryAttributionDowngrade,
29
30
  type WriteBoundaryVerdict,
30
31
  writeWriteBoundaryVerdict,
31
32
  } from "./write-boundary.js";
@@ -45,7 +46,7 @@ export interface WriteBoundaryEnforcerInput {
45
46
  * argument names. Absent entirely means the caller offers no record at all,
46
47
  * and every change outside the declaration is blamed on the window.
47
48
  */
48
- recordedWritesFor?(stepId: string): ReadonlyArray<string> | null;
49
+ recordedWritesFor?(stepId: string): ReadonlyArray<string> | WriteBoundaryAttribution | null;
49
50
  /** Called once per closed window with the sealed verdict and where it landed. */
50
51
  onVerdict?(verdict: WriteBoundaryVerdict, path: string): void;
51
52
  }
@@ -91,16 +92,32 @@ export function createWriteBoundaryEnforcer(input: WriteBoundaryEnforcerInput):
91
92
  const recordedWritesFor = input.recordedWritesFor;
92
93
  if (recordedWritesFor === undefined) return undefined;
93
94
  const recorded = new Set<string>();
95
+ const downgrades: WriteBoundaryAttributionDowngrade[] = [];
94
96
  let complete = true;
95
97
  for (const stepId of stepIds) {
96
98
  const observed = recordedWritesFor(stepId);
97
99
  if (observed === null) {
98
100
  complete = false;
101
+ downgrades.push({
102
+ reason: "write_record_unavailable",
103
+ tool: null,
104
+ toolCallId: null,
105
+ runId: null,
106
+ stepId,
107
+ });
99
108
  continue;
100
109
  }
101
- for (const target of observed) recorded.add(target);
110
+ if (!("recorded" in observed)) {
111
+ for (const target of observed) recorded.add(target);
112
+ continue;
113
+ }
114
+ for (const target of observed.recorded) recorded.add(target);
115
+ if (!observed.complete) complete = false;
116
+ for (const downgrade of observed.downgrades ?? []) {
117
+ downgrades.push({ ...downgrade, stepId: downgrade.stepId ?? stepId });
118
+ }
102
119
  }
103
- return { recorded: [...recorded].sort(), complete };
120
+ return { recorded: [...recorded].sort(), complete, downgrades };
104
121
  };
105
122
  return {
106
123
  begin(window, stepIds) {
@@ -283,6 +283,49 @@ export interface WriteBoundaryAttribution {
283
283
  * change outside the allowlist, and the verdict says it did.
284
284
  */
285
285
  complete: boolean;
286
+ /** Durable causes for an incomplete record. Empty only when complete is true. */
287
+ downgrades?: ReadonlyArray<WriteBoundaryAttributionDowngrade>;
288
+ }
289
+
290
+ export type WriteBoundaryAttributionDowngradeReason =
291
+ | "opaque_tool_succeeded"
292
+ | "incomplete_tool_telemetry"
293
+ | "code_step_untracked"
294
+ | "write_record_unavailable";
295
+
296
+ /** Why one step could not offer a closed write record. */
297
+ export interface WriteBoundaryAttributionDowngrade {
298
+ reason: WriteBoundaryAttributionDowngradeReason;
299
+ /** Tool whose successful call opened the record, or null for a non-tool cause. */
300
+ tool: string | null;
301
+ /** Exact worker call when telemetry supplied one, or null for a non-tool cause. */
302
+ toolCallId: string | null;
303
+ /** Run that observed the cause, when one reached dispatch. */
304
+ runId: string | null;
305
+ /** Fleet step whose record was downgraded. */
306
+ stepId: string | null;
307
+ }
308
+
309
+ /** One operator-facing explanation shared by live and durable surfaces. */
310
+ export function describeWriteBoundaryAttributionDowngrade(downgrade: WriteBoundaryAttributionDowngrade): string {
311
+ if (downgrade.reason === "opaque_tool_succeeded") {
312
+ const tool = downgrade.tool ?? "unknown";
313
+ const call = downgrade.toolCallId === null ? "" : ` (call ${downgrade.toolCallId})`;
314
+ return `a successful '${tool}' call${call} can write paths its arguments do not name`;
315
+ }
316
+ if (downgrade.reason === "incomplete_tool_telemetry") {
317
+ return downgrade.runId === null
318
+ ? "tool telemetry was incomplete"
319
+ : `run '${downgrade.runId}' reported incomplete tool telemetry`;
320
+ }
321
+ if (downgrade.reason === "code_step_untracked") {
322
+ return downgrade.stepId === null
323
+ ? "a code step ran through the untracked command runner"
324
+ : `code step '${downgrade.stepId}' ran through the untracked command runner`;
325
+ }
326
+ return downgrade.stepId === null
327
+ ? "no usable write record was available"
328
+ : `step '${downgrade.stepId}' supplied no usable write record`;
286
329
  }
287
330
 
288
331
  export interface WriteBoundaryVerdict {
@@ -307,6 +350,8 @@ export interface WriteBoundaryVerdict {
307
350
  unattributed: ReadonlyArray<string>;
308
351
  /** Whether every step in the window offered an enumerable write record. */
309
352
  attributionComplete: boolean;
353
+ /** Why attribution was incomplete, including the causal tool call when known. */
354
+ attributionDowngrades: ReadonlyArray<WriteBoundaryAttributionDowngrade>;
310
355
  rolledBack: ReadonlyArray<WriteBoundaryRollback>;
311
356
  unrecoverable: ReadonlyArray<WriteBoundaryUnrecoverable>;
312
357
  status: WriteBoundaryStatus;
@@ -417,6 +462,19 @@ export function enforceWriteBoundary(input: EnforceWriteBoundaryInput): WriteBou
417
462
  const changes = diffWorkspace(input.snapshot);
418
463
  const outside = changes.filter((change) => !writeBoundaryCovers(allow, change.path));
419
464
  const attributionComplete = input.attribution?.complete === true;
465
+ const attributionDowngrades: ReadonlyArray<WriteBoundaryAttributionDowngrade> = attributionComplete
466
+ ? []
467
+ : input.attribution?.downgrades !== undefined && input.attribution.downgrades.length > 0
468
+ ? input.attribution.downgrades.map((entry) => ({ ...entry }))
469
+ : [
470
+ {
471
+ reason: "write_record_unavailable",
472
+ tool: null,
473
+ toolCallId: null,
474
+ runId: null,
475
+ stepId: input.stepIds.length === 1 ? (input.stepIds[0] ?? null) : null,
476
+ },
477
+ ];
420
478
  const recorded = new Set<string>();
421
479
  for (const target of input.attribution?.recorded ?? []) {
422
480
  const relativeTarget = repoRelative(input.snapshot.root, target);
@@ -457,7 +515,9 @@ export function enforceWriteBoundary(input: EnforceWriteBoundaryInput): WriteBou
457
515
  .join("; ")}.`,
458
516
  attributionComplete
459
517
  ? "Each was blamed on this window because a run in it recorded writing that path."
460
- : "This window had no complete write record, so every change outside the declaration was blamed on it.",
518
+ : `Attribution was incomplete because ${attributionDowngrades
519
+ .map(describeWriteBoundaryAttributionDowngrade)
520
+ .join("; ")}, so every change outside the declaration was blamed on this window.`,
461
521
  "If the change was legitimate, widen the step's `writes:` declaration to cover it.",
462
522
  ];
463
523
  const concurrentDetail =
@@ -482,6 +542,7 @@ export function enforceWriteBoundary(input: EnforceWriteBoundaryInput): WriteBou
482
542
  violations: violations.map((change) => change.path),
483
543
  unattributed: unattributed.map((change) => change.path),
484
544
  attributionComplete,
545
+ attributionDowngrades,
485
546
  rolledBack,
486
547
  unrecoverable,
487
548
  status,
@@ -3,6 +3,11 @@ import { join } from "node:path";
3
3
  import { assertSafeId } from "../../../core/safe-id.js";
4
4
  import { safeResourceWrite } from "../../../core/safe-resource-write.js";
5
5
  import type { EvalArtifactV4, EvalTokenAccountingV4 } from "../schema/artifact.js";
6
+ import { assertEvalBehaviorReferencesVerdictV1, parseEvalBehaviorVerdictV1 } from "../schema/behavioral.js";
7
+ import { parseEvalBehaviorMetricsV1 } from "../schema/behavioral-metrics.js";
8
+ import { parseEvalExecutionEnvelopeV1, parseEvalExecutionMatrixDimensionsV1 } from "../schema/execution-envelope.js";
9
+ import { parseEvalServingConfigurationV1 } from "../schema/serving.js";
10
+ import { parseEvalVerdictEnvelopeV1 } from "../schema/verdict.js";
6
11
  import { evalRoot } from "../store.js";
7
12
  import { redactArtifactForStorage } from "./redact.js";
8
13
 
@@ -45,6 +50,14 @@ export function parseEvalArtifactV4(value: unknown, source: string): EvalArtifac
45
50
  const summary = asRecord(value.summary, `${source}.summary`);
46
51
  const matrix = asRecord(value.matrix, `${source}.matrix`);
47
52
  const suite = asRecord(value.suite, `${source}.suite`);
53
+ const servingConfiguration =
54
+ value.servingConfiguration === undefined
55
+ ? undefined
56
+ : parseEvalServingConfigurationV1(value.servingConfiguration, `${source}.servingConfiguration`);
57
+ const aggregates =
58
+ value.aggregates === undefined
59
+ ? undefined
60
+ : (readArray(value, source, "aggregates") as NonNullable<EvalArtifactV4["aggregates"]>);
48
61
  return {
49
62
  version: 4,
50
63
  evalId: readString(value, source, "evalId"),
@@ -62,7 +75,11 @@ export function parseEvalArtifactV4(value: unknown, source: string): EvalArtifac
62
75
  target: readString(matrix, `${source}.matrix`, "target"),
63
76
  model: readNullableString(matrix, `${source}.matrix`, "model"),
64
77
  thinking: readNullableString(matrix, `${source}.matrix`, "thinking"),
78
+ ...(matrix.dimensions === undefined
79
+ ? {}
80
+ : { dimensions: parseEvalExecutionMatrixDimensionsV1(matrix.dimensions, `${source}.matrix.dimensions`) }),
65
81
  },
82
+ ...(servingConfiguration === undefined ? {} : { servingConfiguration }),
66
83
  summary: {
67
84
  runs: readNumber(summary, `${source}.summary`, "runs"),
68
85
  passed: readNumber(summary, `${source}.summary`, "passed"),
@@ -71,6 +88,7 @@ export function parseEvalArtifactV4(value: unknown, source: string): EvalArtifac
71
88
  tokens: parseTokenAccounting(summary.tokens, `${source}.summary.tokens`),
72
89
  wallTimeMs: readNumber(summary, `${source}.summary`, "wallTimeMs"),
73
90
  },
91
+ ...(aggregates === undefined ? {} : { aggregates }),
74
92
  results: readArray(value, source, "results").map((entry, index) => parseResult(entry, `${source}.results[${index}]`)),
75
93
  };
76
94
  }
@@ -108,6 +126,46 @@ function parseTokenAccounting(value: unknown, source: string): EvalTokenAccounti
108
126
  function parseResult(value: unknown, source: string): EvalArtifactV4["results"][number] {
109
127
  const record = asRecord(value, source);
110
128
  const target = asRecord(record.target, `${source}.target`);
129
+ const verdict =
130
+ record.verdict === undefined ? undefined : parseEvalVerdictEnvelopeV1(record.verdict, `${source}.verdict`);
131
+ const behavioral =
132
+ record.behavioral === undefined ? undefined : parseEvalBehaviorVerdictV1(record.behavioral, `${source}.behavioral`);
133
+ const behavioralMetrics =
134
+ record.behavioralMetrics === undefined
135
+ ? undefined
136
+ : parseEvalBehaviorMetricsV1(record.behavioralMetrics, `${source}.behavioralMetrics`);
137
+ const executionEnvelope =
138
+ record.executionEnvelope === undefined
139
+ ? undefined
140
+ : parseEvalExecutionEnvelopeV1(record.executionEnvelope, `${source}.executionEnvelope`);
141
+ if (behavioral !== undefined && verdict === undefined) {
142
+ throw new Error(`${source}.behavioral: sibling document requires a verdict`);
143
+ }
144
+ if (behavioral !== undefined && verdict !== undefined) {
145
+ assertEvalBehaviorReferencesVerdictV1(behavioral, verdict, `${source}.behavioral`);
146
+ }
147
+ if (behavioralMetrics !== undefined) {
148
+ if (behavioral === undefined) throw new Error(`${source}.behavioralMetrics: requires a behavioral verdict`);
149
+ if (behavioralMetrics.scenarioId !== readString(record, source, "taskId")) {
150
+ throw new Error(`${source}.behavioralMetrics.scenarioId: conflicts with result taskId`);
151
+ }
152
+ if (
153
+ behavioralMetrics.target.id !== readString(target, `${source}.target`, "id") ||
154
+ behavioralMetrics.target.model !== readNullableString(target, `${source}.target`, "model")
155
+ ) {
156
+ throw new Error(`${source}.behavioralMetrics.target: conflicts with result target`);
157
+ }
158
+ }
159
+ if (executionEnvelope !== undefined) {
160
+ if (behavioral === undefined) throw new Error(`${source}.executionEnvelope: requires a behavioral verdict`);
161
+ if (
162
+ executionEnvelope.target !== readString(target, `${source}.target`, "id") ||
163
+ executionEnvelope.corpus.id !== behavioral.corpus.id ||
164
+ executionEnvelope.corpus.version !== behavioral.corpus.version
165
+ ) {
166
+ throw new Error(`${source}.executionEnvelope: conflicts with result target or behavioral corpus`);
167
+ }
168
+ }
111
169
  return {
112
170
  assignmentId: readNullableString(record, source, "assignmentId"),
113
171
  terminalReceiptDigest: readNullableDigest(record, source, "terminalReceiptDigest"),
@@ -122,6 +180,10 @@ function parseResult(value: unknown, source: string): EvalArtifactV4["results"][
122
180
  failureClass: readNullableString(record, source, "failureClass"),
123
181
  metrics: asRecord(record.metrics, `${source}.metrics`) as Record<string, number | string | boolean | null>,
124
182
  artifacts: asRecord(record.artifacts, `${source}.artifacts`) as Record<string, string | string[] | null>,
183
+ ...(verdict === undefined ? {} : { verdict }),
184
+ ...(behavioral === undefined ? {} : { behavioral }),
185
+ ...(behavioralMetrics === undefined ? {} : { behavioralMetrics }),
186
+ ...(executionEnvelope === undefined ? {} : { executionEnvelope }),
125
187
  };
126
188
  }
127
189
 
@@ -0,0 +1,224 @@
1
+ import type { EvalArtifactResultV4, EvalArtifactV4 } from "../schema/artifact.js";
2
+ import {
3
+ EVAL_BEHAVIOR_METRIC_DEFINITIONS_V1,
4
+ type EvalBehaviorMetricDefinitionV1,
5
+ type EvalBehaviorMetricFamilyV1,
6
+ type EvalBehaviorMetricNameV1,
7
+ type EvalBehaviorMetricSourceV1,
8
+ } from "../schema/behavioral-metrics.js";
9
+ import type { EvalExecutionMatrixDimensionV1 } from "../schema/execution-envelope.js";
10
+ import {
11
+ compareEvalExecutionEnvelopesV1,
12
+ type EvalEnvelopeComparabilityV1,
13
+ type EvalEnvelopeMismatchV1,
14
+ } from "./envelope.js";
15
+
16
+ export type EvalMetricChangeV1 = "improved" | "regressed" | "unchanged" | "incomparable";
17
+
18
+ export interface EvalBehaviorMetricDistributionV1 {
19
+ observations: number;
20
+ measured: number;
21
+ unmeasured: number;
22
+ mean: number | null;
23
+ min: number | null;
24
+ max: number | null;
25
+ p90: number | null;
26
+ variance: number | null;
27
+ standardDeviation: number | null;
28
+ source: EvalBehaviorMetricSourceV1;
29
+ }
30
+
31
+ export interface EvalBehaviorMetricComparisonV1 {
32
+ scenarioId: string;
33
+ role: string;
34
+ target: { id: string; model: string | null };
35
+ metric: EvalBehaviorMetricNameV1;
36
+ family: EvalBehaviorMetricFamilyV1;
37
+ direction: "higher" | "lower";
38
+ hardGate: boolean;
39
+ baseline: EvalBehaviorMetricDistributionV1;
40
+ candidate: EvalBehaviorMetricDistributionV1;
41
+ change: EvalMetricChangeV1;
42
+ meanDelta: number | null;
43
+ varianceChange: EvalMetricChangeV1;
44
+ varianceDelta: number | null;
45
+ comparability: EvalEnvelopeComparabilityV1;
46
+ }
47
+
48
+ export interface EvalBehaviorHardGateV1 {
49
+ pass: boolean;
50
+ envelopeFailures: EvalEnvelopeMismatchV1[];
51
+ failures: Array<{
52
+ scenarioId: string;
53
+ role: string;
54
+ target: { id: string; model: string | null };
55
+ metric: EvalBehaviorMetricNameV1;
56
+ change: "regressed" | "incomparable";
57
+ }>;
58
+ }
59
+
60
+ interface BehaviorGroup {
61
+ scenarioId: string;
62
+ role: string;
63
+ target: { id: string; model: string | null };
64
+ results: EvalArtifactResultV4[];
65
+ }
66
+
67
+ export function compareEvalBehaviorMetricsV1(
68
+ baseline: EvalArtifactV4,
69
+ candidate: EvalArtifactV4,
70
+ ): {
71
+ comparisons: EvalBehaviorMetricComparisonV1[];
72
+ hardGate: EvalBehaviorHardGateV1;
73
+ envelopeMismatches: EvalEnvelopeMismatchV1[];
74
+ } {
75
+ const baselineGroups = behaviorGroups(baseline);
76
+ const candidateGroups = behaviorGroups(candidate);
77
+ const keys = new Set([...baselineGroups.keys(), ...candidateGroups.keys()]);
78
+ const comparisons: EvalBehaviorMetricComparisonV1[] = [];
79
+ const envelopeMismatches: EvalEnvelopeMismatchV1[] = [];
80
+ const baselineDimensions = baseline.matrix.dimensions ?? ([] as EvalExecutionMatrixDimensionV1[]);
81
+ const candidateDimensions = candidate.matrix.dimensions ?? ([] as EvalExecutionMatrixDimensionV1[]);
82
+ for (const key of [...keys].sort((left, right) => left.localeCompare(right))) {
83
+ const baselineGroup = baselineGroups.get(key);
84
+ const candidateGroup = candidateGroups.get(key);
85
+ const identity = baselineGroup ?? candidateGroup;
86
+ if (identity === undefined) continue;
87
+ const envelopeMismatch = compareEvalExecutionEnvelopesV1(
88
+ identity,
89
+ baselineGroup?.results ?? [],
90
+ candidateGroup?.results ?? [],
91
+ baselineDimensions,
92
+ candidateDimensions,
93
+ );
94
+ if (envelopeMismatch !== null) envelopeMismatches.push(envelopeMismatch);
95
+ const comparability: EvalEnvelopeComparabilityV1 = {
96
+ comparable: envelopeMismatch === null,
97
+ mismatchedFields: envelopeMismatch?.fields ?? [],
98
+ };
99
+ for (const definition of EVAL_BEHAVIOR_METRIC_DEFINITIONS_V1) {
100
+ const baselineDistribution = behaviorDistribution(baselineGroup?.results ?? [], definition);
101
+ const candidateDistribution = behaviorDistribution(candidateGroup?.results ?? [], definition);
102
+ comparisons.push({
103
+ scenarioId: identity.scenarioId,
104
+ role: identity.role,
105
+ target: identity.target,
106
+ metric: definition.name,
107
+ family: definition.family,
108
+ direction: definition.direction,
109
+ hardGate: definition.hardGate,
110
+ baseline: baselineDistribution,
111
+ candidate: candidateDistribution,
112
+ change:
113
+ envelopeMismatch === null
114
+ ? classifyChange(baselineDistribution.mean, candidateDistribution.mean, definition.direction)
115
+ : "incomparable",
116
+ meanDelta: subtractNullable(candidateDistribution.mean, baselineDistribution.mean),
117
+ varianceChange:
118
+ envelopeMismatch === null
119
+ ? classifyChange(baselineDistribution.variance, candidateDistribution.variance, "lower")
120
+ : "incomparable",
121
+ varianceDelta: subtractNullable(candidateDistribution.variance, baselineDistribution.variance),
122
+ comparability,
123
+ });
124
+ }
125
+ }
126
+ const failures = comparisons.flatMap((comparison) =>
127
+ comparison.hardGate &&
128
+ (comparison.change === "regressed" ||
129
+ (comparison.change === "incomparable" && comparison.baseline.mean !== null && comparison.candidate.mean === null))
130
+ ? [
131
+ {
132
+ scenarioId: comparison.scenarioId,
133
+ role: comparison.role,
134
+ target: comparison.target,
135
+ metric: comparison.metric,
136
+ change: comparison.change,
137
+ },
138
+ ]
139
+ : [],
140
+ );
141
+ return {
142
+ comparisons,
143
+ hardGate: {
144
+ pass: failures.length === 0 && envelopeMismatches.length === 0,
145
+ failures,
146
+ envelopeFailures: envelopeMismatches,
147
+ },
148
+ envelopeMismatches,
149
+ };
150
+ }
151
+
152
+ export function classifyChange(
153
+ baseline: number | null,
154
+ candidate: number | null,
155
+ direction: "higher" | "lower",
156
+ ): EvalMetricChangeV1 {
157
+ if (baseline === null || candidate === null) return "incomparable";
158
+ if (baseline === candidate) return "unchanged";
159
+ if (direction === "higher") return candidate > baseline ? "improved" : "regressed";
160
+ return candidate < baseline ? "improved" : "regressed";
161
+ }
162
+
163
+ function behaviorGroups(artifact: EvalArtifactV4): Map<string, BehaviorGroup> {
164
+ const groups = new Map<string, BehaviorGroup>();
165
+ for (const result of artifact.results) {
166
+ const behavioral = result.behavioralMetrics;
167
+ if (behavioral === undefined) continue;
168
+ const key = groupKey(behavioral.scenarioId, behavioral.role, behavioral.target);
169
+ const group = groups.get(key) ?? {
170
+ scenarioId: behavioral.scenarioId,
171
+ role: behavioral.role,
172
+ target: behavioral.target,
173
+ results: [],
174
+ };
175
+ group.results.push(result);
176
+ groups.set(key, group);
177
+ }
178
+ return groups;
179
+ }
180
+
181
+ function behaviorDistribution(
182
+ results: ReadonlyArray<EvalArtifactResultV4>,
183
+ definition: EvalBehaviorMetricDefinitionV1,
184
+ ): EvalBehaviorMetricDistributionV1 {
185
+ const observations = results.map((result) => result.behavioralMetrics?.metrics[definition.name].value ?? null);
186
+ const values = observations.flatMap((value) => (value === null ? [] : [value]));
187
+ if (values.length === 0) {
188
+ return {
189
+ observations: observations.length,
190
+ measured: 0,
191
+ unmeasured: observations.length,
192
+ mean: null,
193
+ min: null,
194
+ max: null,
195
+ p90: null,
196
+ variance: null,
197
+ standardDeviation: null,
198
+ source: definition.source,
199
+ };
200
+ }
201
+ const ordered = [...values].sort((left, right) => left - right);
202
+ const mean = ordered.reduce((sum, value) => sum + value, 0) / ordered.length;
203
+ const variance = ordered.reduce((sum, value) => sum + (value - mean) ** 2, 0) / ordered.length;
204
+ return {
205
+ observations: observations.length,
206
+ measured: values.length,
207
+ unmeasured: observations.length - values.length,
208
+ mean,
209
+ min: ordered[0] ?? null,
210
+ max: ordered.at(-1) ?? null,
211
+ p90: ordered[Math.max(0, Math.ceil(ordered.length * 0.9) - 1)] ?? null,
212
+ variance,
213
+ standardDeviation: Math.sqrt(variance),
214
+ source: definition.source,
215
+ };
216
+ }
217
+
218
+ function groupKey(scenarioId: string, role: string, target: { id: string; model: string | null }): string {
219
+ return JSON.stringify([scenarioId, role, target.id, target.model]);
220
+ }
221
+
222
+ function subtractNullable(left: number | null, right: number | null): number | null {
223
+ return left === null || right === null ? null : left - right;
224
+ }