@iowarp/clio-coder 0.3.6 → 0.3.8

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 (323) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +22 -6
  3. package/dist/{acp-2BEHC4DL.js → acp-U67UHUK2.js} +14 -14
  4. package/dist/{agents-LNNFTM53.js → agents-YU6SGALZ.js} +42 -35
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-KXXFI2VS.js → auth-5ZPJOIVG.js} +32 -24
  7. package/dist/builtins-C6JMZVV6.js +17 -0
  8. package/dist/{chunk-E25LMLRW.js → chunk-26LEYJZH.js} +2 -2
  9. package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
  10. package/dist/{chunk-PBTHKCPN.js → chunk-2HFZQUHL.js} +7 -7
  11. package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
  12. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  13. package/dist/{chunk-CKXWIANG.js → chunk-4SPRNWDE.js} +18 -16
  14. package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
  15. package/dist/{chunk-XF5N4U5A.js → chunk-5DHKRSMQ.js} +9 -8
  16. package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
  17. package/dist/{chunk-EKY57CSP.js → chunk-5H3GB5BO.js} +68 -772
  18. package/dist/{chunk-6XXKFVSN.js → chunk-5Q2VVUKB.js} +4 -4
  19. package/dist/{chunk-DJVECN66.js → chunk-7RFXX52T.js} +295 -3705
  20. package/dist/{chunk-ZXF4XRKW.js → chunk-7RGZWPB6.js} +158 -8
  21. package/dist/{chunk-FYYLNIL5.js → chunk-A2NJGIB3.js} +2 -2
  22. package/dist/{chunk-XXQNGV4M.js → chunk-A3WNZD3P.js} +548 -72
  23. package/dist/{chunk-VEZEGCGW.js → chunk-B5CSFE7B.js} +23 -21
  24. package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
  25. package/dist/chunk-DR52UMZW.js +21 -0
  26. package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
  27. package/dist/{chunk-QKMUKYO7.js → chunk-E77JEWSD.js} +270 -125
  28. package/dist/{chunk-LYF7OHWH.js → chunk-EMYUUSFG.js} +12 -467
  29. package/dist/{chunk-4VP4KH3K.js → chunk-EQ63NRB7.js} +8 -8
  30. package/dist/chunk-FBVTI2TJ.js +518 -0
  31. package/dist/{chunk-R46L2BIR.js → chunk-FHJEP5SW.js} +19 -25
  32. package/dist/{chunk-WR67VIZY.js → chunk-GN57SG4G.js} +66 -8
  33. package/dist/{chunk-RD5U66HV.js → chunk-GPIEI3LY.js} +9 -9
  34. package/dist/{chunk-PCZJO5TI.js → chunk-GU2UIAFZ.js} +13 -178
  35. package/dist/chunk-GWS3VEIW.js +195 -0
  36. package/dist/chunk-H7IXIC72.js +103 -0
  37. package/dist/{chunk-3BPUFZDL.js → chunk-HLE42MG7.js} +3 -3
  38. package/dist/{chunk-24I7BN55.js → chunk-IFBNV6H6.js} +3 -3
  39. package/dist/{chunk-QNQHSOLF.js → chunk-IGWKHNIQ.js} +114 -55
  40. package/dist/chunk-IIZWH4XA.js +172 -0
  41. package/dist/{chunk-AD2SYQYC.js → chunk-IJ7RPIYJ.js} +124 -6
  42. package/dist/chunk-J3YUBZWY.js +382 -0
  43. package/dist/{chunk-5JGRAMKL.js → chunk-JOZYP4GM.js} +8 -6
  44. package/dist/{chunk-OH3TOQTB.js → chunk-K4XHGFR5.js} +751 -15
  45. package/dist/{chunk-EYPA3EGJ.js → chunk-KTYTFRMB.js} +178 -14
  46. package/dist/chunk-LU7P4LHA.js +33 -0
  47. package/dist/chunk-ME6CCNFO.js +108 -0
  48. package/dist/chunk-MXKJU4JB.js +1100 -0
  49. package/dist/{chunk-RY3LY4J5.js → chunk-N22QMJKY.js} +21 -18
  50. package/dist/chunk-NMPKI6XL.js +3006 -0
  51. package/dist/chunk-NUGM5KR6.js +165 -0
  52. package/dist/{chunk-22NAGB7X.js → chunk-P43ETTHK.js} +5 -94
  53. package/dist/{chunk-NILBFAPG.js → chunk-PMDBGQSJ.js} +2 -2
  54. package/dist/chunk-PT7HYKEM.js +165 -0
  55. package/dist/chunk-RVG5JXAL.js +41 -0
  56. package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
  57. package/dist/{chunk-4BPJXDWC.js → chunk-TANS5ZJS.js} +35 -19
  58. package/dist/{verifiers-NCBTHHN2.js → chunk-TB5666IT.js} +67 -324
  59. package/dist/{chunk-QM3F2GKX.js → chunk-TLQJPP24.js} +7927 -8088
  60. package/dist/{chunk-G7MUEIGA.js → chunk-TT36MB5S.js} +2 -1
  61. package/dist/chunk-TTHACPOM.js +961 -0
  62. package/dist/{chunk-6US73PDB.js → chunk-TYPGUK6W.js} +7 -7
  63. package/dist/{chunk-MFFY33HR.js → chunk-U6MBIEMB.js} +554 -209
  64. package/dist/chunk-VAWNZU7Z.js +242 -0
  65. package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
  66. package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
  67. package/dist/{chunk-XYDYPRZI.js → chunk-VWZOAB7K.js} +10 -10
  68. package/dist/{chunk-ZRGEBJ4T.js → chunk-WLFILSD5.js} +48 -48
  69. package/dist/{chunk-K7T3E2SR.js → chunk-WNIJTQQK.js} +12 -11
  70. package/dist/{chunk-CYQKWTG3.js → chunk-WSB3FPX7.js} +68 -20
  71. package/dist/{chunk-KHSFENX2.js → chunk-WWCZ5F23.js} +116 -14
  72. package/dist/{chunk-XE2VEJHX.js → chunk-WXY7KU3G.js} +2 -2
  73. package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
  74. package/dist/{chunk-KOHPCX4K.js → chunk-XWSF374K.js} +5 -5
  75. package/dist/{chunk-CJUB2JJ2.js → chunk-YS5VLNH5.js} +10 -10
  76. package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
  77. package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
  78. package/dist/cli/index.js +33 -31
  79. package/dist/{clio-M2KGYUFZ.js → clio-QVTYJ57A.js} +10 -10
  80. package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +5 -5
  81. package/dist/codewiki/build-worker.js +4 -4
  82. package/dist/{components-5TTYYX6G.js → components-ZFA3SAER.js} +9 -9
  83. package/dist/{config-XUUYQIWO.js → config-LW5IJFQN.js} +65 -55
  84. package/dist/{configure-IHJ7YOMV.js → configure-7XIZCOU4.js} +29 -24
  85. package/dist/{context-75MIWW3U.js → context-L3WL3X7K.js} +54 -43
  86. package/dist/{context-ZQ7SIFJV.js → context-N52ZA626.js} +30 -14
  87. package/dist/{context-74JLXAWD.js → context-Y6Y7QPR6.js} +12 -12
  88. package/dist/{context-clear-GYKWNUML.js → context-clear-MBQRLSDQ.js} +54 -43
  89. package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
  90. package/dist/{context-working-set-UX5KEP4J.js → context-working-set-GS6DSO7F.js} +18 -18
  91. package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-22ZCNOM3.js} +367 -79
  92. package/dist/{docs-6FZSCG5B.js → docs-7LQ23DLM.js} +9 -9
  93. package/dist/{doctor-SVJ5BZCW.js → doctor-M7YEDGAE.js} +27 -23
  94. package/dist/{eval-CG6LLBLD.js → eval-BEC2WHDA.js} +73 -20
  95. package/dist/{evidence-ZYFIEN42.js → evidence-REJUMSKM.js} +70 -59
  96. package/dist/{evolve-QGEXEMDW.js → evolve-PY5ZBA5K.js} +50 -39
  97. package/dist/{extensions-ADGNCJJD.js → extensions-HVKU65YU.js} +7 -7
  98. package/dist/{fleet-S5R4ZOQY.js → fleet-7WZEWRFA.js} +236 -377
  99. package/dist/fleet-commands-UVHWM76J.js +70 -0
  100. package/dist/fleet-graph-6ULH7PES.js +125 -0
  101. package/dist/fleet-new-RDVJLHHH.js +48 -0
  102. package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
  103. package/dist/fleet-validate-72PC4SLA.js +79 -0
  104. package/dist/{init-5DRU55YR.js → init-OG3TPGQG.js} +69 -57
  105. package/dist/library-CNTMPLRF.js +217 -0
  106. package/dist/{memory-7YKKR6UC.js → memory-6IS7F275.js} +52 -41
  107. package/dist/{models-ZPOLRU2C.js → models-ENRJDA5W.js} +37 -31
  108. package/dist/{monitor-US5F5YGZ.js → monitor-XLDVO7TN.js} +79 -42
  109. package/dist/{orchestrator-E2AL4T5N.js → orchestrator-6KSPYRHA.js} +4126 -762
  110. package/dist/{paths-E7KYAQWE.js → paths-DBXMZMDU.js} +6 -6
  111. package/dist/registry-LG64LTF4.js +11 -0
  112. package/dist/{reset-KZ652EK6.js → reset-RZ4ER727.js} +12 -12
  113. package/dist/{run-SRNBKDWD.js → run-Y2CNK5RU.js} +89 -76
  114. package/dist/{share-CGZE33UP.js → share-A55GYP6Z.js} +37 -13
  115. package/dist/{skills-S2X4DLY5.js → skills-ALC5J6AT.js} +29 -13
  116. package/dist/{skills-eval-W2GGIC4R.js → skills-eval-JPBEBYQU.js} +60 -48
  117. package/dist/support-MIETYA5E.js +38 -0
  118. package/dist/{targets-54SWINWB.js → targets-VGNXIR3S.js} +46 -36
  119. package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WOBR64YA.js} +4 -4
  120. package/dist/{uninstall-BVLWXKBT.js → uninstall-ZJF5H5ZN.js} +9 -9
  121. package/dist/{upgrade-JKAR27XC.js → upgrade-FUSUAGHR.js} +31 -28
  122. package/dist/{usage-MSAWCLX4.js → usage-N4MKVHKD.js} +140 -68
  123. package/dist/verifiers-YAWOJ3H2.js +336 -0
  124. package/dist/{verify-X5HDROLA.js → verify-LTDHYBGY.js} +10 -9
  125. package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-6M7GHTBJ.js} +75 -62
  126. package/dist/worker/entry.js +67 -57
  127. package/docs/README.md +3 -2
  128. package/docs/acp.md +1 -1
  129. package/docs/alcf-provider.md +1 -1
  130. package/docs/architecture.md +2 -2
  131. package/docs/artifact-versions.md +11 -6
  132. package/docs/built-in-agents.md +26 -2
  133. package/docs/capacity-and-scheduling.md +1 -1
  134. package/docs/commands-and-modes.md +83 -3
  135. package/docs/configuration-and-targets.md +86 -4
  136. package/docs/context-engine.md +1 -1
  137. package/docs/development-pipeline.md +1 -1
  138. package/docs/dispatch-architecture-rationale.md +1 -1
  139. package/docs/documentation-coverage.md +3 -3
  140. package/docs/documentation-guide.md +3 -3
  141. package/docs/eval-runner.md +1 -1
  142. package/docs/evals-internal.md +1 -1
  143. package/docs/evidence-and-memory.md +74 -10
  144. package/docs/evolution.md +1 -1
  145. package/docs/exit-codes-and-output.md +4 -1
  146. package/docs/extensions-and-sharing.md +6 -2
  147. package/docs/fleet-demo-runbook.md +2 -2
  148. package/docs/fleet-dispatch.md +228 -16
  149. package/docs/git-commit-provenance.md +2 -2
  150. package/docs/glossary.md +22 -2
  151. package/docs/installation-and-lifecycle.md +2 -2
  152. package/docs/middleware-and-components.md +2 -1
  153. package/docs/model-catalog.md +1 -1
  154. package/docs/observability.md +56 -9
  155. package/docs/proactive-memory.md +1 -1
  156. package/docs/prompt-envelope-and-tools.md +4 -2
  157. package/docs/provider-adapter-cookbook.md +1 -1
  158. package/docs/release-cut-checklist.md +83 -65
  159. package/docs/resource-library.md +59 -0
  160. package/docs/safety-model.md +2 -2
  161. package/docs/scientific-validation.md +3 -3
  162. package/docs/session-lifecycle.md +37 -1
  163. package/docs/skills-marketplace.md +16 -3
  164. package/docs/tool-usage.md +14 -7
  165. package/docs/trace-store.md +1 -1
  166. package/docs/troubleshooting.md +1 -1
  167. package/docs/tui-design.md +1 -1
  168. package/docs/worker-dispatch-mechanics.md +3 -3
  169. package/package.json +1 -2
  170. package/src/cli/argv.ts +5 -0
  171. package/src/cli/configure.ts +107 -23
  172. package/src/cli/doctor.ts +5 -1
  173. package/src/cli/evidence.ts +30 -25
  174. package/src/cli/fleet-commands.ts +37 -0
  175. package/src/cli/fleet-graph.ts +102 -0
  176. package/src/cli/fleet-new.ts +36 -0
  177. package/src/cli/fleet-preflight.ts +111 -0
  178. package/src/cli/fleet-validate.ts +30 -0
  179. package/src/cli/fleet.ts +173 -335
  180. package/src/cli/index.ts +3 -1
  181. package/src/cli/library.ts +190 -0
  182. package/src/cli/share.ts +13 -1
  183. package/src/cli/shared.ts +1 -0
  184. package/src/cli/targets.ts +4 -1
  185. package/src/cli/usage.ts +111 -19
  186. package/src/cli/validate-model.ts +60 -5
  187. package/src/core/bus-events.ts +29 -0
  188. package/src/core/commit-attribution.ts +4 -4
  189. package/src/core/config.ts +130 -0
  190. package/src/core/defaults.ts +81 -0
  191. package/src/core/path-boundary.ts +100 -0
  192. package/src/domains/agents/builtins/architect.md +1 -0
  193. package/src/domains/agents/builtins/oracle.md +33 -0
  194. package/src/domains/agents/catalog.ts +13 -1
  195. package/src/domains/agents/extension.ts +2 -11
  196. package/src/domains/agents/fleet-contract.ts +304 -24
  197. package/src/domains/agents/index.ts +14 -0
  198. package/src/domains/agents/recipe.ts +7 -1
  199. package/src/domains/agents/registry.ts +73 -5
  200. package/src/domains/agents/result-contract.ts +360 -15
  201. package/src/domains/agents/write-boundary.ts +15 -50
  202. package/src/domains/config/classify.ts +4 -0
  203. package/src/domains/context/project-rules.ts +51 -1
  204. package/src/domains/dispatch/active-route-planner.ts +14 -0
  205. package/src/domains/dispatch/assignment-reconcile.ts +22 -5
  206. package/src/domains/dispatch/assignment-store.ts +151 -14
  207. package/src/domains/dispatch/backoff.ts +2 -1
  208. package/src/domains/dispatch/capability-match.ts +1 -0
  209. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  210. package/src/domains/dispatch/contract.ts +48 -0
  211. package/src/domains/dispatch/delegation-plan.ts +164 -0
  212. package/src/domains/dispatch/execution-plan.ts +76 -5
  213. package/src/domains/dispatch/execution-role.ts +12 -2
  214. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  215. package/src/domains/dispatch/extension.ts +412 -74
  216. package/src/domains/dispatch/fleet-gate.ts +14 -0
  217. package/src/domains/dispatch/fleet-plan.ts +63 -3
  218. package/src/domains/dispatch/fleet-run.ts +791 -0
  219. package/src/domains/dispatch/gate-role-prompts.ts +47 -0
  220. package/src/domains/dispatch/host-verification.ts +178 -0
  221. package/src/domains/dispatch/index.ts +41 -1
  222. package/src/domains/dispatch/intent-requirements.ts +40 -0
  223. package/src/domains/dispatch/intent.ts +235 -0
  224. package/src/domains/dispatch/path-scope.ts +370 -0
  225. package/src/domains/dispatch/receipt-integrity.ts +9 -4
  226. package/src/domains/dispatch/state.ts +36 -3
  227. package/src/domains/dispatch/types.ts +64 -12
  228. package/src/domains/dispatch/validation.ts +69 -6
  229. package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
  230. package/src/domains/dispatch/write-boundary.ts +201 -22
  231. package/src/domains/eval/metrics/evidence.ts +79 -2
  232. package/src/domains/eval/runners/clio-run.ts +12 -2
  233. package/src/domains/evidence/build.ts +69 -11
  234. package/src/domains/evidence/index.ts +21 -0
  235. package/src/domains/evidence/provenance.ts +46 -11
  236. package/src/domains/evidence/trust-projection.ts +274 -0
  237. package/src/domains/evidence/trust-status.ts +155 -18
  238. package/src/domains/evidence/types.ts +4 -0
  239. package/src/domains/extensions/discovery.ts +88 -1
  240. package/src/domains/extensions/resources.ts +20 -8
  241. package/src/domains/extensions/state.ts +6 -2
  242. package/src/domains/extensions/types.ts +4 -1
  243. package/src/domains/lifecycle/doctor.ts +140 -1
  244. package/src/domains/middleware/index.ts +15 -0
  245. package/src/domains/middleware/watchdog.ts +281 -0
  246. package/src/domains/observability/contract.ts +3 -1
  247. package/src/domains/observability/cost.ts +12 -1
  248. package/src/domains/observability/extension.ts +2 -2
  249. package/src/domains/observability/index.ts +10 -0
  250. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  251. package/src/domains/prompts/contract.ts +3 -5
  252. package/src/domains/providers/extension.ts +30 -2
  253. package/src/domains/resources/common-loader.ts +3 -0
  254. package/src/domains/resources/index.ts +20 -0
  255. package/src/domains/resources/library.ts +326 -0
  256. package/src/domains/resources/prompts/loader.ts +119 -14
  257. package/src/domains/resources/skills/marketplace.ts +37 -12
  258. package/src/domains/safety/policy-engine.ts +5 -5
  259. package/src/domains/safety/run-effects.ts +64 -1
  260. package/src/domains/safety/scope.ts +7 -12
  261. package/src/domains/session/handoff.ts +629 -0
  262. package/src/domains/share/archive.ts +67 -2
  263. package/src/engine/acp/server.ts +4 -1
  264. package/src/engine/prompt-templates.ts +18 -1
  265. package/src/engine/worker-runtime.ts +6 -3
  266. package/src/entry/orchestrator.ts +37 -0
  267. package/src/interactive/bus-notices.ts +26 -0
  268. package/src/interactive/chat-loop.ts +235 -1
  269. package/src/interactive/chat-renderer.ts +22 -0
  270. package/src/interactive/cost-overlay.ts +31 -3
  271. package/src/interactive/council-dispatch.ts +30 -0
  272. package/src/interactive/council-grid.ts +213 -0
  273. package/src/interactive/council.ts +99 -0
  274. package/src/interactive/dispatch-board.ts +311 -17
  275. package/src/interactive/fleet-run-preview.ts +307 -0
  276. package/src/interactive/footer/notifications.ts +219 -0
  277. package/src/interactive/handoff-round.ts +56 -0
  278. package/src/interactive/interactive-application.ts +43 -1
  279. package/src/interactive/interactive-event-projection.ts +23 -1
  280. package/src/interactive/interactive-slash-runtime.ts +52 -2
  281. package/src/interactive/interactive-subscriptions.ts +14 -2
  282. package/src/interactive/oracle.ts +179 -0
  283. package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
  284. package/src/interactive/overlay-general-openers.ts +190 -1
  285. package/src/interactive/overlay-key-routing.ts +17 -1
  286. package/src/interactive/overlay-lifecycle.ts +41 -1
  287. package/src/interactive/overlay-permission-lifecycle.ts +10 -0
  288. package/src/interactive/overlay-resource-openers.ts +11 -3
  289. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  290. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  291. package/src/interactive/overlays/handoff-review.ts +185 -0
  292. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  293. package/src/interactive/overlays/list-overlay.ts +168 -2
  294. package/src/interactive/overlays/settings.ts +221 -30
  295. package/src/interactive/overlays/side-question.ts +139 -0
  296. package/src/interactive/overlays/skills-hub.ts +401 -15
  297. package/src/interactive/side-question.ts +171 -0
  298. package/src/interactive/slash-commands.ts +439 -7
  299. package/src/interactive/slash-spec.ts +19 -6
  300. package/src/interactive/theme/tokens.ts +30 -0
  301. package/src/interactive/turn-middleware.ts +15 -1
  302. package/src/interactive/view/artifacts.ts +42 -9
  303. package/src/interactive/view/view-overlay.ts +15 -3
  304. package/src/interactive/watchdog-run.ts +75 -0
  305. package/src/interactive/worker-receipts.ts +14 -2
  306. package/src/interactive/worker-share.ts +56 -1
  307. package/src/interactive/worker-stream.ts +15 -0
  308. package/src/tools/bootstrap.ts +3 -0
  309. package/src/tools/compete-worktrees.ts +13 -79
  310. package/src/tools/dispatch-admission.ts +251 -18
  311. package/src/tools/dispatch-arguments.ts +84 -1
  312. package/src/tools/dispatch-plan.ts +165 -6
  313. package/src/tools/dispatch-runner.ts +364 -23
  314. package/src/tools/dispatch-types.ts +20 -1
  315. package/src/tools/dispatch.ts +72 -2
  316. package/src/tools/monitor.ts +29 -0
  317. package/src/tools/profiles.ts +18 -4
  318. package/src/tools/task-worktree.ts +238 -0
  319. package/src/tools/verify/authoring.ts +61 -1
  320. package/src/tools/verify/scripts.ts +62 -0
  321. package/src/tools/worker-evidence.ts +21 -14
  322. package/src/worker/spec-contract.ts +3 -1
  323. package/dist/chunk-HC4CLZ2Y.js +0 -68
@@ -0,0 +1,274 @@
1
+ import { RUN_RECEIPT_INTEGRITY_VERSION } from "../dispatch/receipt-integrity.js";
2
+ import {
3
+ type CanonicalTrustStatus,
4
+ retiredIntegrityVersionOf,
5
+ TRUST_STATUS_AXES,
6
+ type TRUST_STATUS_STATES,
7
+ type TrustArtifactReference,
8
+ type TrustStatusAxis,
9
+ } from "./trust-status.js";
10
+
11
+ /**
12
+ * The one projection of the canonical trust status every operator surface
13
+ * renders. The six-axis model stays the source of truth and is never
14
+ * collapsed into a score here; this module only fixes the words each state is
15
+ * called and the order the axes are read in, so the dispatch line, the
16
+ * monitor block, `evidence inspect`, `findings.md`, the Alt+W board, the
17
+ * receipt view, the eval bridge, and the ACP wire cannot each spell the same
18
+ * fact differently.
19
+ *
20
+ * Two shapes leave this module. `formatTrustSummary` is the compact human
21
+ * line: it answers who claims the result, what was observed, what was
22
+ * independently checked, and what is still unknown, without receipt
23
+ * internals. `summarizeTrustStatus` is the bounded, versioned machine record
24
+ * behind that line, carrying the axis states and typed references back to
25
+ * the artifacts the detailed model cites.
26
+ */
27
+
28
+ export const TRUST_SUMMARY_VERSION = 1 as const;
29
+
30
+ /** Reference cap for the machine projection; the canonical model keeps up to 16 per axis. */
31
+ export const TRUST_SUMMARY_MAX_REFS = 8;
32
+
33
+ /**
34
+ * How much the run's result can be leaned on, read off the axes in a fixed
35
+ * order. It is a presentation tier for styling and sorting, never a score:
36
+ * `reviewed` is the only tier that may be styled as independently verified.
37
+ */
38
+ export type TrustVerdict = "reviewed" | "grounded" | "unverified" | "compromised" | "unknown";
39
+
40
+ export const TRUST_VERDICTS: ReadonlyArray<TrustVerdict> = [
41
+ "reviewed",
42
+ "grounded",
43
+ "unverified",
44
+ "compromised",
45
+ "unknown",
46
+ ];
47
+
48
+ /** Bounded, versioned machine projection with references to the detailed artifacts. */
49
+ export interface TrustSummaryProjection {
50
+ version: typeof TRUST_SUMMARY_VERSION;
51
+ verdict: TrustVerdict;
52
+ /** The compact human line, byte-identical to what the text surfaces print. */
53
+ text: string;
54
+ /** Every axis state, in canonical vocabulary, for machine consumers. */
55
+ axes: Record<TrustStatusAxis, string>;
56
+ /** Who asserts the validation fact: `<authority kind>:<id>`, or `worker` when only the run's own prose exists. */
57
+ claimant: string;
58
+ /** Axes that answer nothing, so a consumer knows what remains unknown. */
59
+ unknown: TrustStatusAxis[];
60
+ /** `<artifact kind>:<id>` pointers to the detailed records, sorted, unique, capped. */
61
+ refs: string[];
62
+ }
63
+
64
+ type AxisStates = { [Axis in TrustStatusAxis]: (typeof TRUST_STATUS_STATES)[Axis][number] };
65
+
66
+ /**
67
+ * The standardized word for every state. A word is what an operator reads;
68
+ * the state id is what a machine reads. `mediated` names the `enforced`
69
+ * autonomy state because that is the receipt grade and the documented
70
+ * meaning: Clio's own safety gate mediated the run. `inferred` names an
71
+ * ungrounded validation claim: the worker said it validated, nothing was
72
+ * observed to have run.
73
+ */
74
+ export const TRUST_STATE_WORDS: { [Axis in TrustStatusAxis]: Record<AxisStates[Axis], string> } = {
75
+ artifactIntegrity: {
76
+ verified: "sealed",
77
+ failed: "seal broken",
78
+ absent: "no receipt",
79
+ unknown: "seal unchecked",
80
+ not_applicable: "seal not applicable",
81
+ },
82
+ validationGrounding: {
83
+ validated: "grounded",
84
+ failed: "validation failed",
85
+ ungrounded: "inferred: validation claimed, none observed",
86
+ absent: "no validation observed",
87
+ unknown: "validation unknown",
88
+ not_applicable: "validation not applicable",
89
+ },
90
+ independentReview: {
91
+ passed: "independently reviewed: pass",
92
+ failed: "independently reviewed: fail",
93
+ inconclusive: "independent review inconclusive",
94
+ not_independent: "review not independent",
95
+ absent: "not independently reviewed",
96
+ unknown: "independent review unknown",
97
+ not_applicable: "independent review not applicable",
98
+ },
99
+ contextProvenance: {
100
+ recorded: "context recorded",
101
+ invalid: "context record invalid",
102
+ absent: "context not recorded",
103
+ unknown: "context unknown",
104
+ not_applicable: "context not applicable",
105
+ },
106
+ autonomyEnforcement: {
107
+ enforced: "mediated",
108
+ approximated: "approximated",
109
+ bypassed: "bypassed",
110
+ absent: "autonomy not recorded",
111
+ unknown: "autonomy unknown",
112
+ not_applicable: "autonomy not applicable",
113
+ },
114
+ completionEvidence: {
115
+ evidenced: "completion evidenced",
116
+ incomplete: "completion unevidenced",
117
+ limited: "completion limited",
118
+ absent: "completion not recorded",
119
+ unknown: "completion unknown",
120
+ not_applicable: "completion not applicable",
121
+ },
122
+ };
123
+
124
+ /** The word for one axis state; unknown input never throws, it reads as unknown. */
125
+ export function trustStateWord<Axis extends TrustStatusAxis>(axis: Axis, state: string): string {
126
+ const words = TRUST_STATE_WORDS[axis] as Record<string, string>;
127
+ return words[state] ?? words.unknown ?? "unknown";
128
+ }
129
+
130
+ function authorityOf(status: CanonicalTrustStatus, axis: TrustStatusAxis): string | null {
131
+ const entry = status[axis];
132
+ return entry.state === "absent" ? null : `${entry.authority.kind}:${entry.authority.id}`;
133
+ }
134
+
135
+ /**
136
+ * A seal this build does not verify because its version is retired names
137
+ * that version and the one in force, so an intact receipt from the previous
138
+ * release never reads as "seal broken" or as merely unchecked. The state
139
+ * behind the clause is `unknown`, which is what it is: nothing was checked.
140
+ */
141
+ function integrityClause(status: CanonicalTrustStatus): string {
142
+ const retired = retiredIntegrityVersionOf(status.artifactIntegrity);
143
+ if (retired !== null) return `seal v${retired} retired (this build verifies v${RUN_RECEIPT_INTEGRITY_VERSION})`;
144
+ return trustStateWord("artifactIntegrity", status.artifactIntegrity.state);
145
+ }
146
+
147
+ /**
148
+ * The validation clause names its claimant, because "grounded" alone hides
149
+ * the difference between a host-run check and a self-reported one.
150
+ */
151
+ function validationClause(status: CanonicalTrustStatus): string {
152
+ const entry = status.validationGrounding;
153
+ const word = trustStateWord("validationGrounding", entry.state);
154
+ if (entry.state === "absent") return word;
155
+ if (entry.state === "validated" || entry.state === "failed") return `${word} by ${entry.authority.id}`;
156
+ // A named external system that could not observe the run is worth naming;
157
+ // the historical-format placeholder is not an authority anyone can ask.
158
+ if (entry.state === "unknown" && entry.authority.kind !== "unknown") return `${word} (${entry.authority.id})`;
159
+ return word;
160
+ }
161
+
162
+ /** External runtimes name themselves on the autonomy clause so approximation is never anonymous. */
163
+ function autonomyClause(status: CanonicalTrustStatus): string {
164
+ const entry = status.autonomyEnforcement;
165
+ const word = trustStateWord("autonomyEnforcement", entry.state);
166
+ if (entry.state !== "approximated" && entry.state !== "bypassed") return word;
167
+ return `${word} (${entry.authority.id})`;
168
+ }
169
+
170
+ /**
171
+ * The compact human body. Clause order is fixed: integrity, validation with
172
+ * its claimant, independent review, autonomy, context, completion. Every
173
+ * surface prints this body verbatim, so two surfaces can only disagree by
174
+ * reading different canonical input.
175
+ */
176
+ export function formatTrustSummary(status: CanonicalTrustStatus): string {
177
+ return [
178
+ integrityClause(status),
179
+ validationClause(status),
180
+ trustStateWord("independentReview", status.independentReview.state),
181
+ autonomyClause(status),
182
+ trustStateWord("contextProvenance", status.contextProvenance.state),
183
+ trustStateWord("completionEvidence", status.completionEvidence.state),
184
+ ].join("; ");
185
+ }
186
+
187
+ /** The body under its versioned label, for surfaces that print one line per run. */
188
+ export function formatTrustSummaryLine(status: CanonicalTrustStatus): string {
189
+ return `trust v${TRUST_SUMMARY_VERSION}: ${formatTrustSummary(status)}`;
190
+ }
191
+
192
+ /**
193
+ * The drill-down token line: every axis in canonical state ids. This is the
194
+ * one machine-readable text form; `evidence inspect` and the dispatch and
195
+ * monitor labels all print it from here.
196
+ */
197
+ export function formatTrustAxes(status: CanonicalTrustStatus): string {
198
+ return [`trust_status=v${status.version}`, ...TRUST_STATUS_AXES.map((axis) => `${axis}:${status[axis].state}`)].join(
199
+ " ",
200
+ );
201
+ }
202
+
203
+ function isUnanswered(status: CanonicalTrustStatus, axis: TrustStatusAxis): boolean {
204
+ const state = status[axis].state;
205
+ return state === "absent" || state === "unknown";
206
+ }
207
+
208
+ /**
209
+ * Read the tier off the axes in priority order. A broken seal, a bypassed
210
+ * gate, a failed or inferred validation, a failed or correlated review, or a
211
+ * contradictory context record compromises the result whatever else holds.
212
+ * An unchecked or missing seal leaves it unknown. Only an authenticated
213
+ * independent pass is `reviewed`; observed validation without one is
214
+ * `grounded`; a sealed receipt with nothing observed is `unverified`.
215
+ */
216
+ export function trustVerdict(status: CanonicalTrustStatus): TrustVerdict {
217
+ const integrity = status.artifactIntegrity.state;
218
+ const validation = status.validationGrounding.state;
219
+ const review = status.independentReview.state;
220
+ const autonomy = status.autonomyEnforcement.state;
221
+ if (
222
+ integrity === "failed" ||
223
+ autonomy === "bypassed" ||
224
+ validation === "failed" ||
225
+ validation === "ungrounded" ||
226
+ review === "failed" ||
227
+ review === "not_independent" ||
228
+ status.contextProvenance.state === "invalid"
229
+ ) {
230
+ return "compromised";
231
+ }
232
+ if (integrity !== "verified") return "unknown";
233
+ if (review === "passed") return "reviewed";
234
+ if (validation === "validated") return "grounded";
235
+ return "unverified";
236
+ }
237
+
238
+ function claimantOf(status: CanonicalTrustStatus): string {
239
+ const state = status.validationGrounding.state;
240
+ if (state === "absent" || state === "ungrounded") return "worker";
241
+ return authorityOf(status, "validationGrounding") ?? "worker";
242
+ }
243
+
244
+ function referenceKey(reference: TrustArtifactReference): string {
245
+ return `${reference.kind}:${reference.id}`;
246
+ }
247
+
248
+ /** Every artifact the axes cite, as bounded pointers a consumer can resolve. */
249
+ export function trustSummaryReferences(status: CanonicalTrustStatus): string[] {
250
+ const keys = new Set<string>();
251
+ for (const axis of TRUST_STATUS_AXES) {
252
+ const entry = status[axis];
253
+ if (entry.state === "absent") continue;
254
+ for (const reference of entry.artifacts) keys.add(referenceKey(reference));
255
+ }
256
+ return [...keys].sort().slice(0, TRUST_SUMMARY_MAX_REFS);
257
+ }
258
+
259
+ /** The bounded machine projection. Flat by design so a depth-capped wire keeps it whole. */
260
+ export function summarizeTrustStatus(status: CanonicalTrustStatus): TrustSummaryProjection {
261
+ const axes = Object.fromEntries(TRUST_STATUS_AXES.map((axis) => [axis, status[axis].state])) as Record<
262
+ TrustStatusAxis,
263
+ string
264
+ >;
265
+ return {
266
+ version: TRUST_SUMMARY_VERSION,
267
+ verdict: trustVerdict(status),
268
+ text: formatTrustSummary(status),
269
+ axes,
270
+ claimant: claimantOf(status),
271
+ unknown: TRUST_STATUS_AXES.filter((axis) => isUnanswered(status, axis)),
272
+ refs: trustSummaryReferences(status),
273
+ };
274
+ }
@@ -1,5 +1,5 @@
1
1
  import type { GateDecisionArtifact, GateDecisionVerification } from "../dispatch/gate-decisions.js";
2
- import { type ReceiptIntegrityResult, verifyReceiptIntegrity } from "../dispatch/receipt-integrity.js";
2
+ import { RUN_RECEIPT_INTEGRITY_VERSION, verifyReceiptIntegrity } from "../dispatch/receipt-integrity.js";
3
3
  import type {
4
4
  RunBriefingProvenance,
5
5
  RunEnvelope,
@@ -418,6 +418,7 @@ export type PersistedRunReceiptTrustFacts = Pick<RunReceipt, "runId"> &
418
418
  RunReceipt,
419
419
  | "integrity"
420
420
  | "verification"
421
+ | "hostVerification"
421
422
  | "quality"
422
423
  | "validationGrounding"
423
424
  | "briefing"
@@ -458,13 +459,89 @@ function compatibilitySource(receipt: PersistedRunReceiptTrustFacts, field: stri
458
459
  const DISPATCH_AUTHORITY: TrustStatusAuthority = { kind: "clio", id: "dispatch" };
459
460
  const COMPATIBILITY_AUTHORITY: TrustStatusAuthority = { kind: "unknown", id: "historical-persisted-format" };
460
461
 
462
+ /**
463
+ * A receipt sealed under an integrity version this build no longer verifies.
464
+ * It is not migrated and not read as evidence, on the same terms as every
465
+ * prior integrity bump, but it is not a tampered receipt either: nothing was
466
+ * checked, so nothing failed. The outcome names both versions so an operator
467
+ * with a store full of last release's runs can tell a retired seal from a
468
+ * broken one.
469
+ */
470
+ export interface RetiredReceiptIntegrity {
471
+ receiptVersion: number;
472
+ supportedVersion: number;
473
+ }
474
+
475
+ /**
476
+ * The dispatch verifier's result, widened with the retired-format diagnosis
477
+ * this module puts in front of it. A failure without `retired` is the
478
+ * verifier's own verdict: a malformed seal, a ledger mismatch, or a digest
479
+ * that does not match the receipt's contents.
480
+ */
481
+ export type ReceiptIntegrityOutcome = { ok: true } | { ok: false; reason: string; retired?: RetiredReceiptIntegrity };
482
+
483
+ /** The retired-version diagnosis for a receipt's integrity block, or null when the block is current or unreadable. */
484
+ export function retiredReceiptIntegrity(integrity: unknown): RetiredReceiptIntegrity | null {
485
+ if (!isRecord(integrity)) return null;
486
+ const version = integrity.version;
487
+ if (typeof version !== "number" || !Number.isInteger(version) || version < 1) return null;
488
+ if (version >= RUN_RECEIPT_INTEGRITY_VERSION) return null;
489
+ return { receiptVersion: version, supportedVersion: RUN_RECEIPT_INTEGRITY_VERSION };
490
+ }
491
+
492
+ /** The one sentence every surface prints for a retired seal. It never calls the receipt invalid, broken, or corrupt. */
493
+ export function retiredReceiptIntegrityReason(retired: RetiredReceiptIntegrity): string {
494
+ return `receipt integrity v${retired.receiptVersion} is retired; this build verifies v${retired.supportedVersion}; the receipt is not read as evidence`;
495
+ }
496
+
497
+ /**
498
+ * Verify a receipt against its ledger row, diagnosing a retired seal before
499
+ * the verifier can report it as a malformed one. Every trust-bearing
500
+ * verification goes through here so the CLI, the bundle, and the TUI receipt
501
+ * view agree on what a retired receipt is.
502
+ */
503
+ export function verifyReceiptIntegrityOutcome(receipt: RunReceipt, envelope: RunEnvelope): ReceiptIntegrityOutcome {
504
+ const retired = retiredReceiptIntegrity(receipt.integrity);
505
+ if (retired !== null) return { ok: false, reason: retiredReceiptIntegrityReason(retired), retired };
506
+ return verifyReceiptIntegrity(receipt, envelope);
507
+ }
508
+
509
+ const RETIRED_INTEGRITY_SOURCE = /:integrity-v(\d+)-retired$/u;
510
+
511
+ function retiredIntegritySourceId(runId: string, receiptVersion: number): string {
512
+ return `run_receipt:${runId}:integrity-v${receiptVersion}-retired`;
513
+ }
514
+
515
+ /**
516
+ * The retired integrity version a projected artifact-integrity axis names, or
517
+ * null when the axis says something else. The compatibility source id is the
518
+ * only place the persisted projection carries the version, so the human clause
519
+ * reads it back from there rather than from the receipt.
520
+ */
521
+ export function retiredIntegrityVersionOf(status: ArtifactIntegrityStatus): number | null {
522
+ if (status.state !== "unknown" || status.source.kind !== "compatibility") return null;
523
+ const match = RETIRED_INTEGRITY_SOURCE.exec(status.source.id);
524
+ return match === null ? null : Number(match[1]);
525
+ }
526
+
461
527
  /** Adapt receipt integrity verification without reading any receipt claim as self-authenticating. */
462
528
  export function adaptReceiptIntegrityStatus(
463
529
  receipt: PersistedRunReceiptTrustFacts | null | undefined,
464
- verification?: ReceiptIntegrityResult,
530
+ verification?: ReceiptIntegrityOutcome,
465
531
  ): ArtifactIntegrityStatus {
466
532
  if (receipt === null || receipt === undefined) return absentTrustStatus("artifact_missing");
467
533
  const artifacts = [receiptReference(receipt)];
534
+ // A retired seal was never checked, so the axis is unknown rather than
535
+ // failed, and the compatibility source names the version it was sealed
536
+ // under. `failed` stays reserved for a seal this build verified and rejected.
537
+ if (verification !== undefined && !verification.ok && verification.retired !== undefined) {
538
+ return attributed(
539
+ "unknown",
540
+ { kind: "compatibility", id: retiredIntegritySourceId(receipt.runId, verification.retired.receiptVersion) },
541
+ COMPATIBILITY_AUTHORITY,
542
+ artifacts,
543
+ );
544
+ }
468
545
  if (verification === undefined || (verification.ok && receipt.integrity === undefined)) {
469
546
  return attributed(
470
547
  "unknown",
@@ -536,6 +613,12 @@ export function adaptRunReceiptValidationStatus(
536
613
  ): ValidationGroundingStatus {
537
614
  if (receipt === null || receipt === undefined) return absentTrustStatus("artifact_missing");
538
615
  const artifacts = validationArtifacts(receipt);
616
+ if (receipt.hostVerification?.status === "rejected") {
617
+ return attributed("failed", receiptSource(receipt), { kind: "validator", id: "host-verification" }, artifacts);
618
+ }
619
+ if (receipt.hostVerification?.status === "verified") {
620
+ return attributed("validated", receiptSource(receipt), { kind: "validator", id: "host-verification" }, artifacts);
621
+ }
539
622
  if (qualityHasFailure(receipt.quality)) {
540
623
  return attributed("failed", receiptSource(receipt), { kind: "validator", id: "receipt-quality" }, artifacts);
541
624
  }
@@ -549,7 +632,9 @@ export function adaptRunReceiptValidationStatus(
549
632
  if (qualityHasValidation(receipt.quality)) {
550
633
  return attributed("validated", receiptSource(receipt), { kind: "validator", id: "receipt-quality" }, artifacts);
551
634
  }
552
- if (receipt.verification !== undefined) return verificationStatus(receipt.verification, receipt, artifacts);
635
+ if (receipt.verification !== undefined) {
636
+ return verificationStatus(receipt.verification, receipt, artifacts);
637
+ }
553
638
  return attributed("unknown", compatibilitySource(receipt, "verification"), COMPATIBILITY_AUTHORITY, artifacts);
554
639
  }
555
640
 
@@ -567,15 +652,46 @@ function validBoundedProjectContext(context: RunProjectContextProvenance): boole
567
652
  );
568
653
  }
569
654
 
655
+ /**
656
+ * The one structured message a `none`-tier run can still receive. The tier is
657
+ * the CLIO-CODER.md handbook policy; the workspace-root message is sent
658
+ * regardless of it, and the producer records the characters and hash of
659
+ * every structured message it sent (`projectContextProvenanceFor` in the
660
+ * dispatch extension). A none-tier block naming exactly this section, with
661
+ * a well-formed count and hash, is therefore a consistent record of a real
662
+ * message, not a contradiction. Anything else under a none tier cannot be
663
+ * explained by the policy: a handbook section the policy forbids, a hash
664
+ * with no section to hash, or a malformed count.
665
+ */
666
+ const NONE_TIER_SECTIONS: ReadonlyArray<string> = ["workspace-root"];
667
+
668
+ function validNoneTierProjectContext(context: RunProjectContextProvenance): boolean {
669
+ return (
670
+ context.tier === "none" &&
671
+ context.sections !== undefined &&
672
+ context.sections.length === NONE_TIER_SECTIONS.length &&
673
+ context.sections.every((section) => NONE_TIER_SECTIONS.includes(section)) &&
674
+ Number.isSafeInteger(context.chars) &&
675
+ (context.chars ?? -1) >= 0 &&
676
+ typeof context.contentHash === "string" &&
677
+ /^[a-f0-9]{64}$/u.test(context.contentHash)
678
+ );
679
+ }
680
+
681
+ function recordedProjectContextHash(context: RunProjectContextProvenance | undefined): string | undefined {
682
+ if (context === undefined) return undefined;
683
+ if (context.tier === "bounded") return validBoundedProjectContext(context) ? context.contentHash : undefined;
684
+ return validNoneTierProjectContext(context) ? context.contentHash : undefined;
685
+ }
686
+
570
687
  function contextArtifacts(receipt: PersistedRunReceiptTrustFacts): TrustArtifactReference[] {
571
688
  const artifacts = [receiptReference(receipt)];
572
689
  if (receipt.briefing !== undefined && validBriefing(receipt.briefing)) {
573
690
  artifacts.push(sha256Reference("briefing", `${receipt.runId}:briefing`, receipt.briefing.contentHash));
574
691
  }
575
- if (receipt.projectContext?.tier === "bounded" && validBoundedProjectContext(receipt.projectContext)) {
576
- artifacts.push(
577
- sha256Reference("project_context", `${receipt.runId}:project-context`, receipt.projectContext.contentHash),
578
- );
692
+ const recordedHash = recordedProjectContextHash(receipt.projectContext);
693
+ if (recordedHash !== undefined) {
694
+ artifacts.push(sha256Reference("project_context", `${receipt.runId}:project-context`, recordedHash));
579
695
  }
580
696
  return uniqueBoundedReferences(artifacts);
581
697
  }
@@ -595,12 +711,23 @@ export function adaptRunReceiptContextStatus(
595
711
  return attributed("unknown", compatibilitySource(receipt, "projectContext"), COMPATIBILITY_AUTHORITY, artifacts);
596
712
  }
597
713
  if (context.tier === "none") {
598
- if (context.chars !== undefined || context.contentHash !== undefined || context.sections !== undefined) {
599
- return attributed("invalid", source, DISPATCH_AUTHORITY, artifacts);
714
+ const bare = context.chars === undefined && context.contentHash === undefined && context.sections === undefined;
715
+ if (bare) {
716
+ // Nothing structured was sent: the axis does not apply unless a
717
+ // briefing was recorded, which is context of its own.
718
+ return receipt.briefing === undefined
719
+ ? attributed("not_applicable", source, DISPATCH_AUTHORITY, artifacts)
720
+ : attributed("recorded", source, DISPATCH_AUTHORITY, artifacts);
600
721
  }
601
- return receipt.briefing === undefined
602
- ? attributed("not_applicable", source, DISPATCH_AUTHORITY, artifacts)
603
- : attributed("recorded", source, DISPATCH_AUTHORITY, artifacts);
722
+ // The workspace-root message is the one thing a none-tier run receives,
723
+ // and its record is valid; every other none-tier content shape is a
724
+ // record the policy cannot account for.
725
+ return attributed(
726
+ validNoneTierProjectContext(context) ? "recorded" : "invalid",
727
+ source,
728
+ DISPATCH_AUTHORITY,
729
+ artifacts,
730
+ );
604
731
  }
605
732
  return attributed(validBoundedProjectContext(context) ? "recorded" : "invalid", source, DISPATCH_AUTHORITY, artifacts);
606
733
  }
@@ -630,18 +757,19 @@ export function adaptRunReceiptAutonomyStatus(
630
757
  }
631
758
 
632
759
  export interface AdaptRunReceiptTrustOptions {
633
- integrity?: ReceiptIntegrityResult;
760
+ integrity?: ReceiptIntegrityOutcome;
634
761
  }
635
762
 
636
763
  function receiptClaimsAreAuthenticated(
637
764
  receipt: PersistedRunReceiptTrustFacts | null | undefined,
638
- integrity: ReceiptIntegrityResult | undefined,
765
+ integrity: ReceiptIntegrityOutcome | undefined,
639
766
  ): boolean {
640
767
  return receipt?.integrity !== undefined && integrity?.ok === true;
641
768
  }
642
769
 
643
770
  function unauthenticatedReceiptProjection(
644
771
  receipt: PersistedRunReceiptTrustFacts | null | undefined,
772
+ integrity: ReceiptIntegrityOutcome | undefined,
645
773
  ): TrustStatusProjection {
646
774
  if (receipt === null || receipt === undefined) {
647
775
  return {
@@ -673,6 +801,15 @@ function unauthenticatedReceiptProjection(
673
801
  ),
674
802
  };
675
803
  }
804
+ // A retired seal leaves the receipt unread rather than rejected: its axes
805
+ // are absent because the format is historical, not because a check failed.
806
+ if (integrity !== undefined && !integrity.ok && integrity.retired !== undefined) {
807
+ return {
808
+ validationGrounding: absentTrustStatus("historical_format"),
809
+ contextProvenance: absentTrustStatus("historical_format"),
810
+ autonomyEnforcement: absentTrustStatus("historical_format"),
811
+ };
812
+ }
676
813
  // The artifact-integrity axis retains the failure diagnostic. No other
677
814
  // axis reads a field from a receipt that failed authentication.
678
815
  return {
@@ -694,7 +831,7 @@ export function adaptRunReceiptTrustStatus(
694
831
  contextProvenance: adaptRunReceiptContextStatus(receipt),
695
832
  autonomyEnforcement: adaptRunReceiptAutonomyStatus(receipt),
696
833
  }
697
- : unauthenticatedReceiptProjection(receipt);
834
+ : unauthenticatedReceiptProjection(receipt, options.integrity);
698
835
  return composeTrustStatus({
699
836
  artifactIntegrity: adaptReceiptIntegrityStatus(receipt, options.integrity),
700
837
  ...receiptProjection,
@@ -706,7 +843,7 @@ export function adaptRunReceiptTrustStatus(
706
843
  }
707
844
 
708
845
  export interface RunReceiptTrustInspection {
709
- integrity: ReceiptIntegrityResult;
846
+ integrity: ReceiptIntegrityOutcome;
710
847
  status: CanonicalTrustStatus;
711
848
  }
712
849
 
@@ -719,14 +856,14 @@ export function inspectRunReceiptTrustStatus(
719
856
  receipt: RunReceipt | null | undefined,
720
857
  envelope: RunEnvelope | null | undefined,
721
858
  ): RunReceiptTrustInspection {
722
- let integrity: ReceiptIntegrityResult;
859
+ let integrity: ReceiptIntegrityOutcome;
723
860
  if (receipt === null || receipt === undefined) {
724
861
  integrity = { ok: false, reason: "receipt unavailable" };
725
862
  } else if (envelope === null || envelope === undefined) {
726
863
  integrity = { ok: false, reason: "run ledger envelope unavailable" };
727
864
  } else {
728
865
  try {
729
- integrity = verifyReceiptIntegrity(receipt, envelope);
866
+ integrity = verifyReceiptIntegrityOutcome(receipt, envelope);
730
867
  } catch (error) {
731
868
  integrity = {
732
869
  ok: false,
@@ -23,7 +23,11 @@ export const EVIDENCE_TAGS = [
23
23
  "escalation",
24
24
  "external-bypass",
25
25
  "external-approximation",
26
+ "independent-review",
27
+ "context-provenance",
28
+ "completion-evidence",
26
29
  "receipt-integrity",
30
+ "receipt-retired",
27
31
  "protected-artifact",
28
32
  "tool-loop",
29
33
  "test-failure",
@@ -1,4 +1,4 @@
1
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
1
+ import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
4
4
  import type {
@@ -39,13 +39,99 @@ function normalizeResources(value: unknown): ExtensionManifestResources {
39
39
  const out: ExtensionManifestResources = {};
40
40
  const skills = trimString(value.skills);
41
41
  const prompts = trimString(value.prompts);
42
+ const agents = trimString(value.agents);
43
+ const fleets = trimString(value.fleets);
42
44
  const themes = trimString(value.themes);
43
45
  if (skills) out.skills = skills;
44
46
  if (prompts) out.prompts = prompts;
47
+ if (agents) out.agents = agents;
48
+ if (fleets) out.fleets = fleets;
45
49
  if (themes) out.themes = themes;
46
50
  return out;
47
51
  }
48
52
 
53
+ function contained(root: string, candidate: string): boolean {
54
+ const relative = path.relative(root, candidate);
55
+ return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
56
+ }
57
+
58
+ function validateResourceTree(
59
+ root: string,
60
+ resourcePath: string,
61
+ kind: string,
62
+ diagnostics: ExtensionDiagnostic[],
63
+ ): void {
64
+ if (path.isAbsolute(resourcePath)) {
65
+ diagnostics.push({ type: "error", message: `resources.${kind} must be relative`, path: resourcePath });
66
+ return;
67
+ }
68
+ const resolvedRoot = path.resolve(root);
69
+ const resolved = path.resolve(resolvedRoot, resourcePath);
70
+ if (!contained(resolvedRoot, resolved) || resolved === resolvedRoot) {
71
+ diagnostics.push({ type: "error", message: `resources.${kind} escapes the extension root`, path: resourcePath });
72
+ return;
73
+ }
74
+ let canonicalRoot: string;
75
+ let canonicalResource: string;
76
+ try {
77
+ canonicalRoot = realpathSync(resolvedRoot);
78
+ canonicalResource = realpathSync(resolved);
79
+ if (!lstatSync(resolved).isDirectory()) {
80
+ diagnostics.push({ type: "error", message: `resources.${kind} is not a directory`, path: resolved });
81
+ return;
82
+ }
83
+ } catch (error) {
84
+ diagnostics.push({
85
+ type: "error",
86
+ message: `resources.${kind} cannot be resolved: ${error instanceof Error ? error.message : String(error)}`,
87
+ path: resolved,
88
+ });
89
+ return;
90
+ }
91
+ if (!contained(canonicalRoot, canonicalResource)) {
92
+ diagnostics.push({ type: "error", message: `resources.${kind} escapes through a symbolic link`, path: resolved });
93
+ return;
94
+ }
95
+
96
+ const pending = [resolved];
97
+ while (pending.length > 0) {
98
+ const directory = pending.pop();
99
+ if (!directory) continue;
100
+ for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
101
+ const candidate = path.join(directory, entry.name);
102
+ if (entry.isSymbolicLink()) {
103
+ try {
104
+ if (!contained(canonicalRoot, realpathSync(candidate))) {
105
+ diagnostics.push({
106
+ type: "error",
107
+ message: `resources.${kind} contains an escaping symbolic link`,
108
+ path: candidate,
109
+ });
110
+ }
111
+ } catch (error) {
112
+ diagnostics.push({
113
+ type: "error",
114
+ message: `resources.${kind} contains an unresolved symbolic link: ${error instanceof Error ? error.message : String(error)}`,
115
+ path: candidate,
116
+ });
117
+ }
118
+ } else if (entry.isDirectory()) {
119
+ pending.push(candidate);
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ function validateResourceRoots(
126
+ root: string,
127
+ resources: ExtensionManifestResources,
128
+ diagnostics: ExtensionDiagnostic[],
129
+ ): void {
130
+ for (const [kind, resourcePath] of Object.entries(resources)) {
131
+ if (resourcePath) validateResourceTree(root, resourcePath, kind, diagnostics);
132
+ }
133
+ }
134
+
49
135
  function stringArray(value: unknown): string[] | undefined {
50
136
  if (!Array.isArray(value)) return undefined;
51
137
  const out = value.map((entry) => trimString(entry)).filter((entry): entry is string => entry !== undefined);
@@ -123,6 +209,7 @@ export function loadManifestFromRoot(root: string): ExtensionCandidate {
123
209
  }
124
210
  try {
125
211
  const parsed = parseExtensionManifest(readJsonOrYaml(manifestPath), manifestPath);
212
+ if (parsed.manifest) validateResourceRoots(root, parsed.manifest.resources, parsed.diagnostics);
126
213
  return {
127
214
  path: root,
128
215
  manifestPath,