@tequila99/githuman 0.11.0 → 0.12.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 (330) hide show
  1. package/README.md +62 -2
  2. package/dist/cli/commands/mcp.js +112 -0
  3. package/dist/cli/commands/pi-acp.js +21 -0
  4. package/dist/cli/config.js +7 -1
  5. package/dist/cli/index.js +19 -8
  6. package/dist/cli/server-runtime.js +1 -0
  7. package/dist/server/adapters/agents/artifact-permission.js +88 -0
  8. package/dist/server/adapters/pi-acp/agent.js +98 -0
  9. package/dist/server/adapters/pi-acp/compatibility.js +57 -0
  10. package/dist/server/adapters/pi-acp/control.js +76 -0
  11. package/dist/server/adapters/pi-acp/errors.js +17 -0
  12. package/dist/server/adapters/pi-acp/extension.js +255 -0
  13. package/dist/server/adapters/pi-acp/launch.js +16 -0
  14. package/dist/server/adapters/pi-acp/mcp.js +153 -0
  15. package/dist/server/adapters/pi-acp/process.js +199 -0
  16. package/dist/server/adapters/pi-acp/session.js +409 -0
  17. package/dist/server/adapters/pi-acp/vendor/LICENSE +21 -0
  18. package/dist/server/adapters/pi-acp/vendor/README.md +29 -0
  19. package/dist/server/adapters/pi-acp/vendor/prompt.js +56 -0
  20. package/dist/server/app.js +94 -5
  21. package/dist/server/config/agents.js +4 -6
  22. package/dist/server/db/artifact-graph-migrations.js +56 -0
  23. package/dist/server/db/artifact-migrations.js +70 -0
  24. package/dist/server/db/artifact-review-migrations.js +24 -0
  25. package/dist/server/db/artifact-search-migrations.js +31 -0
  26. package/dist/server/db/artifact-validation-migrations.js +70 -0
  27. package/dist/server/db/index.js +3 -0
  28. package/dist/server/db/migrations.js +10 -0
  29. package/dist/server/db/sqlite-error.js +15 -0
  30. package/dist/server/db/transaction.js +40 -0
  31. package/dist/server/errors/artifacts.js +28 -0
  32. package/dist/server/errors/database.js +2 -0
  33. package/dist/server/hooks/access/require-access.js +8 -0
  34. package/dist/server/repositories/artifact-catalog.repo.js +283 -0
  35. package/dist/server/repositories/artifact-files.repo.js +181 -0
  36. package/dist/server/repositories/artifact-graph.repo.js +50 -0
  37. package/dist/server/repositories/artifact-search.repo.js +192 -0
  38. package/dist/server/repositories/artifact-transition.repo.js +85 -0
  39. package/dist/server/repositories/artifact-validation.repo.js +146 -0
  40. package/dist/server/repositories/notes-catalog.repo.js +2 -10
  41. package/dist/server/repositories/task.repo.js +114 -0
  42. package/dist/server/routes/agent.js +8 -1
  43. package/dist/server/routes/artifact-actions.js +129 -0
  44. package/dist/server/routes/artifact-agent.js +183 -0
  45. package/dist/server/routes/artifact-definitions.js +163 -0
  46. package/dist/server/routes/artifact-transitions.js +80 -0
  47. package/dist/server/routes/artifacts.js +111 -0
  48. package/dist/server/routes/events.js +5 -1
  49. package/dist/server/routes/project-files.js +90 -0
  50. package/dist/server/services/agent/agent-session.js +180 -10
  51. package/dist/server/services/agent/context-builder.js +30 -2
  52. package/dist/server/services/agent/session-registry.js +106 -2
  53. package/dist/server/services/agent/turn-task-context.js +1 -0
  54. package/dist/server/services/artifact-authorization.service.js +174 -0
  55. package/dist/server/services/artifact-definition.service.js +96 -0
  56. package/dist/server/services/artifact-export.service.js +44 -0
  57. package/dist/server/services/artifact-graph-validation.js +497 -0
  58. package/dist/server/services/artifact-graph.service.js +81 -0
  59. package/dist/server/services/artifact-lifecycle.service.js +343 -0
  60. package/dist/server/services/artifact-maintenance.service.js +55 -0
  61. package/dist/server/services/artifact-search.service.js +331 -0
  62. package/dist/server/services/artifact-storage.service.js +646 -0
  63. package/dist/server/services/artifact-transition.service.js +607 -0
  64. package/dist/server/services/artifact-type.service.js +74 -0
  65. package/dist/server/services/artifact-validation-jobs.service.js +518 -0
  66. package/dist/server/services/device-connections.js +9 -1
  67. package/dist/server/services/project-files.service.js +144 -0
  68. package/dist/server/services/review.service.js +6 -5
  69. package/dist/server/services/task.service.js +225 -0
  70. package/dist/server/utils/artifact-actor.js +13 -0
  71. package/dist/server/utils/artifact-page.js +42 -0
  72. package/dist/server/utils/take-within-bytes.js +16 -0
  73. package/dist/server/utils/text.js +7 -0
  74. package/dist/shared/agents/schemas.js +27 -0
  75. package/dist/shared/app/storage-constants.js +2 -0
  76. package/dist/shared/artifact-tools/descriptions.js +11 -0
  77. package/dist/shared/artifact-tools/instructions.js +14 -0
  78. package/dist/shared/artifact-tools/schemas.js +119 -0
  79. package/dist/shared/artifact-transitions/constants.js +21 -0
  80. package/dist/shared/artifact-transitions/schemas.js +111 -0
  81. package/dist/shared/artifact-transitions/transitions.js +61 -0
  82. package/dist/shared/artifact-transitions/types.js +1 -0
  83. package/dist/shared/artifacts/constants.js +34 -0
  84. package/dist/shared/artifacts/mcp-constants.js +24 -0
  85. package/dist/shared/artifacts/normalize.js +9 -0
  86. package/dist/shared/artifacts/schemas.js +820 -0
  87. package/dist/shared/artifacts/search.js +101 -0
  88. package/dist/shared/artifacts/types.js +1 -0
  89. package/dist/shared/events/schemas.js +1 -1
  90. package/dist/shared/events/types.js +1 -0
  91. package/dist/shared/git/constants.js +2 -0
  92. package/dist/shared/project-files/constants.js +18 -0
  93. package/dist/shared/project-files/schemas.js +35 -0
  94. package/dist/shared/project-files/types.js +1 -0
  95. package/dist/shared/tasks/schemas.js +261 -0
  96. package/dist/shared/tasks/types.js +1 -0
  97. package/dist/shared/utils/artifact-body.js +10 -0
  98. package/dist/shared/utils/canonical-json.js +22 -0
  99. package/dist/shared/utils/is-safe-prefixed-filename.js +10 -0
  100. package/dist/shared/utils/is-uuid.js +4 -2
  101. package/dist/shared/utils/is-valid-artifact-type-id.js +9 -0
  102. package/dist/shared/utils/is-valid-filename-prefix.js +13 -0
  103. package/dist/shared/utils/markdown-path.js +4 -0
  104. package/dist/shared/utils/pdf-path.js +4 -0
  105. package/dist/web/assets/AccessPage-Dvj0aCkm.js +1 -0
  106. package/dist/web/assets/AddButton-CMC9qf51.js +4 -0
  107. package/dist/web/assets/AddToAgentButton-BitTdCEg.js +1 -0
  108. package/dist/web/assets/AppPage-Cozv2vqG.js +1 -0
  109. package/dist/web/assets/ArtifactDefinitionPicker-BQOoU60k.js +3 -0
  110. package/dist/web/assets/ArtifactDefinitionPicker-D3qdrxlC.css +1 -0
  111. package/dist/web/assets/ArtifactsPage-BJR-XYYs.js +2 -0
  112. package/dist/web/assets/ArtifactsPage-Cu1xdYNf.css +1 -0
  113. package/dist/web/assets/CancelButton-mkxRHwoZ.js +1 -0
  114. package/dist/web/assets/ClosePopup-BtDeDZsk.js +1 -0
  115. package/dist/web/assets/CreateButton-zCmb7snC.js +1 -0
  116. package/dist/web/assets/CreateReviewFabButton-BCg1TvFh.js +1 -0
  117. package/dist/web/assets/DialogHeader-CPL9OtDV.css +1 -0
  118. package/dist/web/assets/DialogHeader-CQvAZVx7.js +1 -0
  119. package/dist/web/assets/{ErrorNotFound-DtnO85PU.js → ErrorNotFound-fXS367lZ.js} +1 -1
  120. package/dist/web/assets/{IndexPage-D5PvyGrX.css → IndexPage-5V5GLtgi.css} +1 -1
  121. package/dist/web/assets/IndexPage-DMTZyWQA.js +1 -0
  122. package/dist/web/assets/MainLayout-DWJ_-EMV.js +7 -0
  123. package/dist/web/assets/MainLayout-Dy74m_q1.css +1 -0
  124. package/dist/web/assets/NoteVisualEditor-5rHBQ3j0.js +1054 -0
  125. package/dist/web/assets/{NoteVisualEditor-BYikrY7Q.css → NoteVisualEditor-DE4ACTRJ.css} +1 -1
  126. package/dist/web/assets/NotesPage-C_eoCUl7.css +1 -0
  127. package/dist/web/assets/NotesPage-CjaxjLBW.js +4 -0
  128. package/dist/web/assets/{PdfPreview-kL_nk9my.js → PdfPreview-2NQ5gkjh.js} +2 -2
  129. package/dist/web/assets/ProjectFilePage-B-WUUxnq.js +1 -0
  130. package/dist/web/assets/QBtn-V3VXtJXi.js +1 -0
  131. package/dist/web/assets/QFab-CxpedhPI.js +1 -0
  132. package/dist/web/assets/QForm-ClpSneeD.js +1 -0
  133. package/dist/web/assets/QInput-BnqLJGXl.js +1 -0
  134. package/dist/web/assets/QScrollArea-js5dkUz1.js +1 -0
  135. package/dist/web/assets/QSpace-C7iAnGlU.js +1 -0
  136. package/dist/web/assets/QTabs-DIy5gWMb.js +1 -0
  137. package/dist/web/assets/QVirtualScroll-DkD0TczV.js +1 -0
  138. package/dist/web/assets/ReviewDetailPage-BgxcsTqY.css +1 -0
  139. package/dist/web/assets/ReviewDetailPage-Bmc8DmSu.js +1 -0
  140. package/dist/web/assets/ReviewsPage-DR1IWCmu.js +1 -0
  141. package/dist/web/assets/SearchInput-BRyN93Z1.js +1 -0
  142. package/dist/web/assets/StatusSwitcher-CpkH8BuC.js +1 -0
  143. package/dist/web/assets/{StatusSwitcher-D6i8_OfR.css → StatusSwitcher-DaE1ufAA.css} +1 -1
  144. package/dist/web/assets/TagFilterField-Bl3FBkB5.js +1 -0
  145. package/dist/web/assets/TagFilterField-DjUH0m3_.css +1 -0
  146. package/dist/web/assets/{TerminalView-617DZuPt.css → TerminalView-BCeke4xd.css} +1 -1
  147. package/dist/web/assets/{TerminalView-BsO_SZut.js → TerminalView-tEyLM5qZ.js} +12 -12
  148. package/dist/web/assets/{abnfDiagram-O67JEVCF-qUSrlp_c.js → abnfDiagram-O67JEVCF-DN9xiV9p.js} +1 -1
  149. package/dist/web/assets/{access-store-Q8nkv4Nb.js → access-store-CwkOl71l.js} +1 -1
  150. package/dist/web/assets/{agent-store-D2jgeBgi.js → agent-store-Grbx4jkz.js} +5 -5
  151. package/dist/web/assets/architecture-7GRP2DOG-DmaYCP8T.js +1 -0
  152. package/dist/web/assets/{architectureDiagram-NJMV4G6O-D7RC6OS2.js → architectureDiagram-NJMV4G6O-BVAJOucG.js} +1 -1
  153. package/dist/web/assets/{blockDiagram-BEXU5L5S-CmNtTpfK.js → blockDiagram-BEXU5L5S-TJAfSX44.js} +1 -1
  154. package/dist/web/assets/{c4Diagram-YGBWAQC7-CKCcu1qx.js → c4Diagram-YGBWAQC7-5UHyT_tW.js} +1 -1
  155. package/dist/web/assets/channel-B4_5H-fb.js +1 -0
  156. package/dist/web/assets/{chunk-3FUC2YCW-Drz6oeKx.js → chunk-3FUC2YCW-CelgLyJi.js} +1 -1
  157. package/dist/web/assets/{chunk-5DYCD2WN-RItzEgV_.js → chunk-5DYCD2WN-bhEAVIbQ.js} +1 -1
  158. package/dist/web/assets/{chunk-742MDFTN-D6hKdOv7.js → chunk-742MDFTN-ByLz4EcD.js} +1 -1
  159. package/dist/web/assets/{chunk-7INBJB4K-Dk7-Eior.js → chunk-7INBJB4K-CK2i0csa.js} +1 -1
  160. package/dist/web/assets/{chunk-7M6MHVWA-Cw8E0Nkn.js → chunk-7M6MHVWA-CoCiujjC.js} +1 -1
  161. package/dist/web/assets/{chunk-7PRAP22T-t-mTeNXF.js → chunk-7PRAP22T-CY4NCrCY.js} +1 -1
  162. package/dist/web/assets/{chunk-GTNCS2PH-C7j2atV8.js → chunk-GTNCS2PH-DMB7bQGB.js} +1 -1
  163. package/dist/web/assets/{chunk-GWA4HPMP-CH9T9LZO.js → chunk-GWA4HPMP-F6qclPTR.js} +1 -1
  164. package/dist/web/assets/{chunk-MBY4JIJT-yXOO37xD.js → chunk-MBY4JIJT-CSiiBtlX.js} +1 -1
  165. package/dist/web/assets/{chunk-NETBCI7D-C-s2UtAR.js → chunk-NETBCI7D-CBh7GChL.js} +1 -1
  166. package/dist/web/assets/{chunk-O7XYJQB3-5Me_aV8o.js → chunk-O7XYJQB3-B2aqz0sN.js} +1 -1
  167. package/dist/web/assets/{chunk-UA2S7LBM-BQaibeff.js → chunk-UA2S7LBM-B_-P_eR0.js} +1 -1
  168. package/dist/web/assets/{chunk-WEXAMYUT-UBcOE4Lf.js → chunk-WEXAMYUT-19nMFxzK.js} +1 -1
  169. package/dist/web/assets/{chunk-Z7XXMR3K-BsA8o3Pt.js → chunk-Z7XXMR3K-Co7gzRY1.js} +1 -1
  170. package/dist/web/assets/{chunk-ZIGJFQKS--KwSI1U0.js → chunk-ZIGJFQKS-Eoi4K5bK.js} +1 -1
  171. package/dist/web/assets/{classDiagram-v2-NBCMYWYE-pMF8oCmq.js → classDiagram-v2-NBCMYWYE-CfFGzL-A.js} +1 -1
  172. package/dist/web/assets/copy-code-DaqEfdXL.js +1 -0
  173. package/dist/web/assets/create-DedXY-KP.js +1 -0
  174. package/dist/web/assets/{cynefin-OW5HDTMX-DFgtXolF.js → cynefin-OW5HDTMX-DdZXl3KU.js} +1 -1
  175. package/dist/web/assets/{cynefinDiagram-VND7K2PF-CPY0JRyq.js → cynefinDiagram-VND7K2PF-WU21nYH-.js} +1 -1
  176. package/dist/web/assets/{dagre-6A5THRUB-KdjJKAB7.js → dagre-6A5THRUB-48z3F8Pl.js} +1 -1
  177. package/dist/web/assets/date-DZIuZp-w.js +1 -0
  178. package/dist/web/assets/devices-store-Bfk2wdrQ.js +1 -0
  179. package/dist/web/assets/{diagram-22UHCM2B-BDcOcvF_.js → diagram-22UHCM2B-B0AZgJW9.js} +1 -1
  180. package/dist/web/assets/{diagram-3UASUU5V-6j2nlZL3.js → diagram-3UASUU5V-CzEzrxuO.js} +1 -1
  181. package/dist/web/assets/{diagram-ATOU4E4O-BN8cdxED.js → diagram-ATOU4E4O-w6h1bD3w.js} +1 -1
  182. package/dist/web/assets/{diagram-CDSNMT55-cWPiXHkr.js → diagram-CDSNMT55-BdTHQd_o.js} +1 -1
  183. package/dist/web/assets/{diagram-MLGK6HIB-DcgkH_Vl.js → diagram-MLGK6HIB-pB4cj6c2.js} +1 -1
  184. package/dist/web/assets/{diagram-MPIPVDR6-D_e86L99.js → diagram-MPIPVDR6-C7C06qyV.js} +1 -1
  185. package/dist/web/assets/diff-store-DBDL5Hw5.js +93 -0
  186. package/dist/web/assets/{dist-Bdsq-Ze1.js → dist-B7Cu0FBW.js} +1 -1
  187. package/dist/web/assets/{dist-BVVhgjWT.js → dist-B8i8y3GY.js} +1 -1
  188. package/dist/web/assets/{dist-CjkUzU4s.js → dist-BNgPhnKo.js} +1 -1
  189. package/dist/web/assets/{dist-Bz40Nd9I.js → dist-BWoPV5P3.js} +1 -1
  190. package/dist/web/assets/{dist-BP40mvwJ.js → dist-CJ7isxli.js} +1 -1
  191. package/dist/web/assets/{dist-C9enVshI.js → dist-CTJJYvYG.js} +1 -1
  192. package/dist/web/assets/{dist-BwpyANeW.js → dist-CZz0KXAA.js} +1 -1
  193. package/dist/web/assets/{dist-CU43ytsh.js → dist-DNpwgDba.js} +1 -1
  194. package/dist/web/assets/{dist-BMFtkY2y.js → dist-DWCdhk-O.js} +1 -1
  195. package/dist/web/assets/{dist-MUQaJRtC.js → dist-DqSUdjG9.js} +1 -1
  196. package/dist/web/assets/{dist-CLCP7f3x.js → dist-Dy9K9LOR.js} +1 -1
  197. package/dist/web/assets/{ebnfDiagram-ZINNZB2B-D8xHOFOi.js → ebnfDiagram-ZINNZB2B-75vt7539.js} +1 -1
  198. package/dist/web/assets/{elk-276RUBZZ-JiDwNyLy.js → elk-276RUBZZ-DXDjJVTy.js} +1 -1
  199. package/dist/web/assets/{erDiagram-OPXOYQCR-D3J6u81a.js → erDiagram-OPXOYQCR-tNNInTmk.js} +1 -1
  200. package/dist/web/assets/eventmodeling-NTZA5JFV-QMRehQtM.js +1 -0
  201. package/dist/web/assets/flowDiagram-KWPJA3E3-DJAgsbXC.js +1 -0
  202. package/dist/web/assets/{ganttDiagram-FUAMR5RP-nvAVhbgI.js → ganttDiagram-FUAMR5RP-h3E8lnCd.js} +1 -1
  203. package/dist/web/assets/{gitGraph-4MIJSDKK-Bv_wkB2T.js → gitGraph-4MIJSDKK-BUwY-iv2.js} +1 -1
  204. package/dist/web/assets/{gitGraphDiagram-X574FWY7-DxxwadhH.js → gitGraphDiagram-X574FWY7-C0AgQH5k.js} +1 -1
  205. package/dist/web/assets/i18n-CfTd46OX.js +1 -0
  206. package/dist/web/assets/icons-BoaZfGBj.js +1 -0
  207. package/dist/web/assets/index-BQU3KK1E.css +1 -0
  208. package/dist/web/assets/index-BTpokWxr.js +2 -0
  209. package/dist/web/assets/{info-A6RAGUB7-Dw18yeIE.js → info-A6RAGUB7-DVMZ4hdK.js} +1 -1
  210. package/dist/web/assets/{infoDiagram-VRGFBTTK-C3BrgjYg.js → infoDiagram-VRGFBTTK-kLaGXGQ2.js} +1 -1
  211. package/dist/web/assets/init-app-Bxc_l2yT.js +1 -0
  212. package/dist/web/assets/{is-D0waCELe.js → is-B1kBQ5Va.js} +1 -1
  213. package/dist/web/assets/{ishikawaDiagram-OU5B5YK6-DU2p9szX.js → ishikawaDiagram-OU5B5YK6-Oj7bZlyo.js} +1 -1
  214. package/dist/web/assets/{journeyDiagram-ZHPQQLJL-Bs2zQnew.js → journeyDiagram-ZHPQQLJL-BG7V2Srs.js} +1 -1
  215. package/dist/web/assets/{kanban-definition-PNTS6WVX-pqXd5uNi.js → kanban-definition-PNTS6WVX-CIJiDE6d.js} +1 -1
  216. package/dist/web/assets/{line-CN4Dxpei.js → line-D2vEKYRZ.js} +1 -1
  217. package/dist/web/assets/markdown-Bm2wfvok.js +16 -0
  218. package/dist/web/assets/mdi-Byw1NZrZ.js +1 -0
  219. package/dist/web/assets/{mermaid-parser.core-DZqDOGRM.js → mermaid-parser.core-BcBEWjW6.js} +3 -3
  220. package/dist/web/assets/{mermaid.core-CUndCvkz.js → mermaid.core-DobO0deo.js} +4 -4
  221. package/dist/web/assets/{mindmap-definition-NLK3R4M7-DyK7YqB7.js → mindmap-definition-NLK3R4M7-y7Qz5gBF.js} +1 -1
  222. package/dist/web/assets/{packet-AYTQ26CC-DvlW_0BP.js → packet-AYTQ26CC-oRQgKr9d.js} +1 -1
  223. package/dist/web/assets/{pegDiagram-GJSIUBJH-k2e_-FWQ.js → pegDiagram-GJSIUBJH-RIU8XOHq.js} +1 -1
  224. package/dist/web/assets/{pie-WAS4IAKB-B99zaYjO.js → pie-WAS4IAKB-qmOlsLkx.js} +1 -1
  225. package/dist/web/assets/{pieDiagram-5QR66LMP-BPbS05lK.js → pieDiagram-5QR66LMP-BM932TDA.js} +1 -1
  226. package/dist/web/assets/pinia-BfqEuvsi.js +1 -0
  227. package/dist/web/assets/pinia-Cmp1GI8k.js +1 -0
  228. package/dist/web/assets/preview-store-D7qF6SgX.js +2 -0
  229. package/dist/web/assets/project-files-store-OhGs_H7U.js +1 -0
  230. package/dist/web/assets/{quadrantDiagram-O4NWA36T-DqNNdsd2.js → quadrantDiagram-O4NWA36T-qQZYwTy7.js} +1 -1
  231. package/dist/web/assets/{radar-RG4KPBEZ-BIAAr9LO.js → radar-RG4KPBEZ-Kdglk6eY.js} +1 -1
  232. package/dist/web/assets/{railroad-74A4TZTK-DYxr-1-f.js → railroad-74A4TZTK-kVhMYx4I.js} +1 -1
  233. package/dist/web/assets/railroad-abnf-HS5TGJTU-MiKgjOXr.js +1 -0
  234. package/dist/web/assets/railroad-ebnf-LZEXJU2U-BFL9h7Zx.js +1 -0
  235. package/dist/web/assets/railroad-peg-WCYAUIDC-ChGGfIn7.js +1 -0
  236. package/dist/web/assets/{railroadDiagram-XR7U4H2S-TNg-LqRR.js → railroadDiagram-XR7U4H2S-K9SrOGcT.js} +1 -1
  237. package/dist/web/assets/render-Brao9Nj2.js +1 -0
  238. package/dist/web/assets/repository-store-DZgQ5H7J.js +1 -0
  239. package/dist/web/assets/{requirementDiagram-PLB6GJNP-hKAyJR6W.js → requirementDiagram-PLB6GJNP-CtA9ZKEx.js} +1 -1
  240. package/dist/web/assets/runtime-core.esm-bundler-DDnHUoIy.js +1 -0
  241. package/dist/web/assets/{safe-storage-B1Aq5CGi.js → safe-storage-CvVvrm4v.js} +1 -1
  242. package/dist/web/assets/{sankeyDiagram-IPEJSGJF-_MSL-tES.js → sankeyDiagram-IPEJSGJF-BH_t4exx.js} +1 -1
  243. package/dist/web/assets/{sequenceDiagram-PO4LG4MO-Byq-joBG.js → sequenceDiagram-PO4LG4MO-hPNPqpDa.js} +1 -1
  244. package/dist/web/assets/single-flight-CyDZB3rb.js +1 -0
  245. package/dist/web/assets/{stateDiagram-v2-GCMORJYK-BR2jAgaa.js → stateDiagram-v2-GCMORJYK-BVmjZDRD.js} +1 -1
  246. package/dist/web/assets/{swimlanes-2SLR337P-Nt8ojGKK.js → swimlanes-2SLR337P-Tgk8-6jj.js} +1 -1
  247. package/dist/web/assets/swimlanesDiagram-TC7HE7FX-W1zDrK61.js +8 -0
  248. package/dist/web/assets/{terminal-store-D0abibMa.js → terminal-store-CKgJjtC6.js} +1 -1
  249. package/dist/web/assets/theme-slSxLNv6.js +1 -0
  250. package/dist/web/assets/{timeline-definition-EJHVYXUP-BtihLvxk.js → timeline-definition-EJHVYXUP-lAIHEGe8.js} +1 -1
  251. package/dist/web/assets/{treeView-Q6P3EWNA-D5HzXeKd.js → treeView-Q6P3EWNA-CdvzKtWf.js} +1 -1
  252. package/dist/web/assets/{treemap-WGGIJYW6-Dovjuutg.js → treemap-WGGIJYW6-BxDSJfrJ.js} +1 -1
  253. package/dist/web/assets/uid-B1JNhznI.js +1 -0
  254. package/dist/web/assets/use-add-agent-context-BSHGL5iV.js +1 -0
  255. package/dist/web/assets/use-app-theme-fmVV2QM8.js +1 -0
  256. package/dist/web/assets/use-countdown-D6_qSOZJ.js +1 -0
  257. package/dist/web/assets/use-notify-error-Dx2D9IEP.js +1 -0
  258. package/dist/web/assets/use-panel-width-BYOPuHat.js +1 -0
  259. package/dist/web/assets/use-router-link-D7geCGQI.js +1 -0
  260. package/dist/web/assets/use-row-height-BK8kNihW.js +1 -0
  261. package/dist/web/assets/use-server-events-CvcbsbIf.js +1 -0
  262. package/dist/web/assets/use-virtual-scroll-DixIsJk4.js +1 -0
  263. package/dist/web/assets/use-workspace-navigation-AaRMWmjQ.js +1 -0
  264. package/dist/web/assets/{usecaseDiagram-POWQR4AR-DSwRE8zj.js → usecaseDiagram-POWQR4AR-BH3KvMpz.js} +1 -1
  265. package/dist/web/assets/{vennDiagram-UO4OBE2U-C-B8yP1K.js → vennDiagram-UO4OBE2U-Dfi-raKp.js} +1 -1
  266. package/dist/web/assets/viewport-store-NqQS7itT.js +1 -0
  267. package/dist/web/assets/{vue-i18n.runtime-CEtihHHL.js → vue-i18n.runtime-JOo8b7-t.js} +2 -2
  268. package/dist/web/assets/{wardley-WFR3VGLG-wWgaZuh6.js → wardley-WFR3VGLG-DmbtSM24.js} +1 -1
  269. package/dist/web/assets/{wardleyDiagram-VNRHLVJA-DbhenrHd.js → wardleyDiagram-VNRHLVJA-DIzhGvI7.js} +1 -1
  270. package/dist/web/assets/weighted-lru-BFtZKA_y.js +1 -0
  271. package/dist/web/assets/workspace-navigation-xfxULfM7.js +1 -0
  272. package/dist/web/assets/{xychartDiagram-PMCCYNJV-CxFDmAEt.js → xychartDiagram-PMCCYNJV-XgqSHH_N.js} +1 -1
  273. package/dist/web/index.html +10 -8
  274. package/package.json +5 -3
  275. package/dist/web/assets/AccessPage-_OKn9U0P.js +0 -1
  276. package/dist/web/assets/AppPage-oQ9Rz0be.js +0 -1
  277. package/dist/web/assets/CreateButton-D1EtoicG.js +0 -1
  278. package/dist/web/assets/CreateReviewFabButton-BxvT3i9e.js +0 -1
  279. package/dist/web/assets/IndexPage-gdD7BHxA.js +0 -1
  280. package/dist/web/assets/MainLayout-47PTLKZf.js +0 -6
  281. package/dist/web/assets/MainLayout-ikSrFqBh.css +0 -1
  282. package/dist/web/assets/NoteVisualEditor-BQ6mVfj0.js +0 -1054
  283. package/dist/web/assets/NotesPage-DNR9iW_G.js +0 -4
  284. package/dist/web/assets/NotesPage-_8naQciW.css +0 -1
  285. package/dist/web/assets/PanelResize-CAQFVc8b.js +0 -1
  286. package/dist/web/assets/QBtn-CB-yw_iP.js +0 -1
  287. package/dist/web/assets/QExpansionItem-Dr5oXO0m.js +0 -4
  288. package/dist/web/assets/QFab-DPz8RiP0.js +0 -1
  289. package/dist/web/assets/QForm-CASiKhGm.js +0 -1
  290. package/dist/web/assets/QInput-k34nCPWg.js +0 -1
  291. package/dist/web/assets/QSelect-CaKn9Tkg.js +0 -1
  292. package/dist/web/assets/QSpace-Di_bZQY6.js +0 -1
  293. package/dist/web/assets/QVirtualScroll-oGN_Tl5J.js +0 -1
  294. package/dist/web/assets/ReviewDetailPage-BKgIprY0.js +0 -1
  295. package/dist/web/assets/ReviewDetailPage-CkCilMNW.css +0 -1
  296. package/dist/web/assets/ReviewsPage-6PJ6wofN.js +0 -1
  297. package/dist/web/assets/StatusSwitcher-s9w6iKpR.js +0 -1
  298. package/dist/web/assets/architecture-7GRP2DOG-D6PShS0_.js +0 -1
  299. package/dist/web/assets/channel-D6lb5DbY.js +0 -1
  300. package/dist/web/assets/create-B9Tn5Xvi.js +0 -1
  301. package/dist/web/assets/devices-store-CrZ4MxyY.js +0 -1
  302. package/dist/web/assets/diff-store-ByxMCKoa.js +0 -93
  303. package/dist/web/assets/eventmodeling-NTZA5JFV-QCwZLvmm.js +0 -1
  304. package/dist/web/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-Dr0goTwe.woff +0 -0
  305. package/dist/web/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ-D-x-0Q06.woff2 +0 -0
  306. package/dist/web/assets/flowDiagram-KWPJA3E3-CgF90hDi.js +0 -1
  307. package/dist/web/assets/i18n-DDCqtTTW.js +0 -1
  308. package/dist/web/assets/index-BNHt6zmP.js +0 -2
  309. package/dist/web/assets/index-BwbM_RKi.css +0 -1
  310. package/dist/web/assets/init-app-BouXTk8p.js +0 -1
  311. package/dist/web/assets/markdown-jR0fhe4d.js +0 -15
  312. package/dist/web/assets/pinia-BETEFLDG.js +0 -1
  313. package/dist/web/assets/pinia-DO0AdKGg.js +0 -1
  314. package/dist/web/assets/railroad-abnf-HS5TGJTU-GESjhc3t.js +0 -1
  315. package/dist/web/assets/railroad-ebnf-LZEXJU2U-Cybu0A7J.js +0 -1
  316. package/dist/web/assets/railroad-peg-WCYAUIDC-DCHxrtbI.js +0 -1
  317. package/dist/web/assets/runtime-core.esm-bundler-CAvPiMpd.js +0 -1
  318. package/dist/web/assets/single-flight-DLCbzLI3.js +0 -1
  319. package/dist/web/assets/swimlanesDiagram-TC7HE7FX-Ds1hVYqh.js +0 -8
  320. package/dist/web/assets/theme-QEcOl0lf.js +0 -1
  321. package/dist/web/assets/use-add-agent-context-BaPUIO93.js +0 -1
  322. package/dist/web/assets/use-app-theme-B1cdbnHn.js +0 -1
  323. package/dist/web/assets/use-countdown-CTag4T13.js +0 -1
  324. package/dist/web/assets/use-panel-width-CB-0S3zX.js +0 -1
  325. package/dist/web/assets/use-router-link-Dr9wnnyZ.js +0 -1
  326. package/dist/web/assets/use-row-height-DQHVMtUe.js +0 -1
  327. package/dist/web/assets/use-server-events-BrBCbeHu.js +0 -1
  328. package/dist/web/assets/use-virtual-scroll-DG0UKjVk.js +0 -1
  329. package/dist/web/assets/weighted-lru-BSxRnMHR.js +0 -1
  330. /package/dist/web/assets/{PanelResize-XjrbOj2S.css → AddToAgentButton-XjrbOj2S.css} +0 -0
@@ -0,0 +1,607 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { Value } from '@sinclair/typebox/value';
3
+ import { errorMessage } from "../../shared/utils/error-message.js";
4
+ import { canonicalJson } from "../../shared/utils/canonical-json.js";
5
+ import { ArtifactChangeSetSchema } from "../../shared/artifacts/schemas.js";
6
+ import { HEARTBEAT_STALE_MS, TRANSITION_CONSENT_TTL_MS, TRANSITION_PREVIEW_TTL_MS } from "../../shared/artifact-transitions/constants.js";
7
+ import { isTerminalTransitionStage, transitionCommand, userTransitionTargets } from "../../shared/artifact-transitions/transitions.js";
8
+ import { sha256Hex } from "../utils/hash.js";
9
+ import { ArtifactConflictError, ArtifactInputError, artifactLeaseBusyError, isArtifactBusyError, isArtifactLeaseBusyError, ArtifactNotFoundError } from "../errors/artifacts.js";
10
+ // Reconcile cadence; other processes cannot call this process, so it polls.
11
+ const RECONCILE_INTERVAL_MS = 1000;
12
+ // Memory bound for unconfirmed previews.
13
+ const MAX_PREVIEWS = 200;
14
+ // Extra time before another process may call a silent owner interrupted.
15
+ const OWNER_GRACE_MS = 60_000;
16
+ // A start that did not reach the validator within this time fails.
17
+ const PREPARE_STALL_MS = 10_000;
18
+ // Heartbeat rows of dead owners are dropped after this time.
19
+ const HEARTBEAT_KEEP_MS = 60 * 60_000;
20
+ /**
21
+ * Runs one user-confirmed status change from preview to commit. The server
22
+ * owns the work, so closing the browser does not stop it (ADR 0107).
23
+ */
24
+ export class ArtifactTransitionService {
25
+ repository;
26
+ lifecycle;
27
+ now;
28
+ isActorActive = () => true;
29
+ owner;
30
+ previews = new Map();
31
+ onError = () => { };
32
+ timer;
33
+ stopped = false;
34
+ lastBeatError = 0;
35
+ unsubscribe;
36
+ constructor(lifecycle, repository, now) {
37
+ this.lifecycle = lifecycle;
38
+ this.repository = repository;
39
+ this.now = now;
40
+ this.owner = lifecycle.jobs.ownerId;
41
+ }
42
+ /** Sets the receiver of errors from background work, which has no caller. */
43
+ setErrorHandler(handler) {
44
+ this.onError = handler;
45
+ }
46
+ guard(run) {
47
+ if (this.stopped) {
48
+ return;
49
+ }
50
+ try {
51
+ run();
52
+ }
53
+ catch (error) {
54
+ this.onError(error);
55
+ }
56
+ }
57
+ /** Sets the check that the confirming actor still has access (device not revoked). */
58
+ setActorGuard(guard) {
59
+ this.isActorActive = guard;
60
+ }
61
+ /** Starts listening to validation results; the constructor starts nothing. */
62
+ start() {
63
+ this.stopped = false;
64
+ this.unsubscribe ??= this.lifecycle.jobs.onSettled(validationId => setImmediate(() => this.guard(() => this.settle(validationId))));
65
+ this.guard(() => this.reconcile());
66
+ this.timer ??= setInterval(() => this.guard(() => this.reconcile()), RECONCILE_INTERVAL_MS);
67
+ this.timer.unref();
68
+ }
69
+ /** Marks own unfinished operations interrupted; it never applies anything. */
70
+ stop() {
71
+ this.stopped = true;
72
+ clearInterval(this.timer);
73
+ this.timer = undefined;
74
+ this.unsubscribe?.();
75
+ this.unsubscribe = undefined;
76
+ for (const operation of this.repository.unfinished()) {
77
+ if (operation.owner === this.owner && operation.stage !== 'applying') {
78
+ this.finish(operation.id, 'interrupted', 'Server stopped.');
79
+ }
80
+ }
81
+ try {
82
+ this.lifecycle.repository.forget(this.owner);
83
+ }
84
+ catch {
85
+ // The row ages out by itself when the database stays busy.
86
+ }
87
+ }
88
+ /** Writes the heartbeat; a failure is reported at most once per stale period. */
89
+ beat() {
90
+ try {
91
+ this.lifecycle.repository.beat(this.owner, this.now(), HEARTBEAT_KEEP_MS);
92
+ }
93
+ catch (error) {
94
+ if (this.now() - this.lastBeatError > HEARTBEAT_STALE_MS) {
95
+ this.lastBeatError = this.now();
96
+ this.onError(error);
97
+ }
98
+ }
99
+ }
100
+ options(artifactId) {
101
+ const artifact = this.lifecycle.storage.get(artifactId);
102
+ const history = this.lifecycle.storage.catalog
103
+ .supersededArtifactIds()
104
+ .has(artifactId);
105
+ const active = this.repository
106
+ .forArtifact(artifactId)
107
+ .find(item => !isTerminalTransitionStage(item.stage));
108
+ return {
109
+ storeId: this.lifecycle.storage.storeId,
110
+ artifactId,
111
+ status: artifact.status,
112
+ targets: userTransitionTargets(artifact.status, history),
113
+ activeOperationId: active?.id ?? null
114
+ };
115
+ }
116
+ preview(artifactId, targetStatus, actor) {
117
+ const built = this.lifecycle.storage.catalog.readSnapshot(() => this.build(artifactId, targetStatus));
118
+ const now = this.now();
119
+ for (const [id, record] of this.previews) {
120
+ if (record.view.expiresAt <= now) {
121
+ this.previews.delete(id);
122
+ }
123
+ }
124
+ // One open preview per document, target and actor; a new one replaces it.
125
+ for (const [key, record] of this.previews) {
126
+ if (record.artifactId === artifactId &&
127
+ record.targetStatus === targetStatus &&
128
+ canonicalJson(record.actor) === canonicalJson(actor)) {
129
+ this.previews.delete(key);
130
+ }
131
+ }
132
+ if (this.previews.size >= MAX_PREVIEWS) {
133
+ throw new ArtifactConflictError('Too many open transition previews.');
134
+ }
135
+ const id = randomUUID();
136
+ const view = {
137
+ ...built.view,
138
+ previewId: id,
139
+ expiresAt: now + TRANSITION_PREVIEW_TTL_MS
140
+ };
141
+ this.previews.set(id, {
142
+ id,
143
+ artifactId,
144
+ targetStatus,
145
+ actor,
146
+ changeSet: built.changeSet,
147
+ changeDigest: built.changeDigest,
148
+ view
149
+ });
150
+ return view;
151
+ }
152
+ /** The single user confirmation: record consent and start the validator. */
153
+ begin(input, actor, deviceId) {
154
+ if (this.stopped) {
155
+ throw new ArtifactConflictError('The server is stopping.');
156
+ }
157
+ const requestDigest = sha256Hex(canonicalJson({
158
+ previewId: input.previewId,
159
+ presetId: input.presetId,
160
+ actor
161
+ }));
162
+ const replay = this.repository.request(input.requestId);
163
+ if (replay) {
164
+ if (replay.payloadHash !== requestDigest) {
165
+ throw new ArtifactConflictError('Transition request ID was reused.');
166
+ }
167
+ return this.view(this.repository.operation(replay.operationId));
168
+ }
169
+ const preview = this.previews.get(input.previewId);
170
+ if (!preview ||
171
+ preview.view.expiresAt <= this.now() ||
172
+ canonicalJson(preview.actor) !== canonicalJson(actor)) {
173
+ throw new ArtifactConflictError('The preview expired or is unknown. Refresh the consequences.');
174
+ }
175
+ if (!preview.view.canConfirm) {
176
+ throw new ArtifactConflictError('The transition is blocked: ' +
177
+ preview.view.blockers.map(item => item.message).join(' '));
178
+ }
179
+ const presetState = this.lifecycle.jobs.presetState(input.presetId);
180
+ if (presetState !== 'ok') {
181
+ throw new ArtifactConflictError(presetState === 'unknown'
182
+ ? 'The validator agent is unknown. Choose another one.'
183
+ : 'The validator agent is unavailable. Choose another one.');
184
+ }
185
+ this.lifecycle.jobs.recover();
186
+ if (this.lifecycle.repository.lease()) {
187
+ throw artifactLeaseBusyError('Another validation job is active. Try again when it finishes.');
188
+ }
189
+ const operation = this.lifecycle.storage.catalog.transaction(() => {
190
+ if (this.repository
191
+ .forArtifact(preview.artifactId)
192
+ .some(item => !isTerminalTransitionStage(item.stage))) {
193
+ throw new ArtifactConflictError('A transition of this artifact is already running.');
194
+ }
195
+ const current = this.build(preview.artifactId, preview.targetStatus);
196
+ if (!current.view.canConfirm ||
197
+ current.changeDigest !== preview.changeDigest) {
198
+ throw new ArtifactConflictError('The affected documents changed. Refresh the consequences.');
199
+ }
200
+ const operationId = randomUUID();
201
+ const proposal = this.lifecycle.proposeInTransaction({
202
+ requestId: `transition:${operationId}`,
203
+ changeSet: current.changeSet
204
+ }, actor);
205
+ const validation = this.lifecycle.prepareInTransaction(proposal.id, proposal.version);
206
+ const now = this.now();
207
+ this.repository.appendConsent({
208
+ operationId,
209
+ actor,
210
+ proposalId: proposal.id,
211
+ proposalVersion: proposal.version,
212
+ changeDigest: proposal.changeDigest,
213
+ manifestDigest: validation.manifestDigest,
214
+ validationId: validation.id,
215
+ expiresAt: now + TRANSITION_CONSENT_TTL_MS
216
+ });
217
+ this.repository.appendRequest(input.requestId, requestDigest, operationId);
218
+ const value = {
219
+ id: operationId,
220
+ artifactId: preview.artifactId,
221
+ title: current.view.title,
222
+ fromStatus: current.view.fromStatus,
223
+ targetStatus: preview.targetStatus,
224
+ presetId: input.presetId,
225
+ stage: 'preparing',
226
+ changes: current.view.changes,
227
+ cancelRequested: false,
228
+ reason: null,
229
+ proposalId: proposal.id,
230
+ proposalVersion: proposal.version,
231
+ validationId: validation.id,
232
+ verdict: null,
233
+ reportArtifactId: null,
234
+ createdAt: now,
235
+ updatedAt: now,
236
+ consentExpiresAt: now + TRANSITION_CONSENT_TTL_MS,
237
+ actor,
238
+ requestDigest,
239
+ owner: this.owner,
240
+ ownerPid: process.pid,
241
+ eventKind: 'started'
242
+ };
243
+ this.write(value);
244
+ return value;
245
+ });
246
+ this.previews.delete(input.previewId);
247
+ this.lifecycle.storage.pollChanges();
248
+ try {
249
+ this.lifecycle.jobs.start(operation.validationId, input.presetId, deviceId);
250
+ }
251
+ catch (error) {
252
+ this.finish(operation.id, 'failed', errorMessage(error));
253
+ // A lost race for the lease is the caller's conflict, not a result.
254
+ if (isArtifactLeaseBusyError(error)) {
255
+ throw error;
256
+ }
257
+ return this.view(this.repository.operation(operation.id));
258
+ }
259
+ this.update(operation.id, 'validating', current => current.stage === 'preparing', 'validating');
260
+ return this.view(this.repository.operation(operation.id));
261
+ }
262
+ operation(id) {
263
+ return this.view(this.repository.operation(id));
264
+ }
265
+ list(artifactId) {
266
+ this.lifecycle.storage.get(artifactId);
267
+ return {
268
+ storeId: this.lifecycle.storage.storeId,
269
+ items: this.repository
270
+ .forArtifact(artifactId)
271
+ .map(item => this.view(item))
272
+ };
273
+ }
274
+ /** Records a cancel request; the owner process stops the validator. */
275
+ cancel(id) {
276
+ const operation = this.lifecycle.storage.catalog.transaction(() => {
277
+ const current = this.repository.operation(id);
278
+ if (isTerminalTransitionStage(current.stage)) {
279
+ return current;
280
+ }
281
+ if (current.stage === 'applying') {
282
+ throw new ArtifactConflictError('Cancellation is no longer possible: the change is being applied.');
283
+ }
284
+ if (current.cancelRequested) {
285
+ return current;
286
+ }
287
+ const next = {
288
+ ...current,
289
+ cancelRequested: true,
290
+ updatedAt: this.now(),
291
+ eventKind: 'cancel_requested'
292
+ };
293
+ this.write(next);
294
+ return next;
295
+ });
296
+ this.lifecycle.storage.pollChanges();
297
+ if (operation.owner === this.owner &&
298
+ !isTerminalTransitionStage(operation.stage)) {
299
+ this.runCancel(operation);
300
+ }
301
+ return this.view(this.repository.operation(id));
302
+ }
303
+ /** Advances operations of this process and settles those of dead processes. */
304
+ reconcile() {
305
+ if (this.stopped) {
306
+ return;
307
+ }
308
+ // The own heartbeat comes first: a stalled loop must not kill its own work.
309
+ this.beat();
310
+ this.lifecycle.jobs.recover();
311
+ for (const operation of this.repository.unfinished()) {
312
+ if (operation.owner === this.owner) {
313
+ // One busy operation must not stop the others in this pass.
314
+ try {
315
+ if (operation.cancelRequested) {
316
+ this.runCancel(operation);
317
+ }
318
+ else {
319
+ this.advance(operation.id);
320
+ }
321
+ }
322
+ catch (error) {
323
+ if (!isArtifactBusyError(error)) {
324
+ throw error;
325
+ }
326
+ }
327
+ continue;
328
+ }
329
+ if (!this.lifecycle.ownerAlive(operation.owner, operation.ownerPid) ||
330
+ this.now() > operation.consentExpiresAt + OWNER_GRACE_MS) {
331
+ this.finish(operation.id, 'interrupted', 'The process that ran this operation stopped.');
332
+ }
333
+ }
334
+ }
335
+ build(artifactId, targetStatus) {
336
+ const storage = this.lifecycle.storage;
337
+ const artifact = storage.get(artifactId);
338
+ const history = storage.catalog.supersededArtifactIds().has(artifactId);
339
+ const option = userTransitionTargets(artifact.status, history).find(item => item.status === targetStatus);
340
+ const command = transitionCommand(artifact.status, targetStatus);
341
+ if (!option || !command) {
342
+ throw new ArtifactInputError(`Status ${targetStatus} cannot be chosen from ${artifact.status}.`);
343
+ }
344
+ const base = {
345
+ storeId: storage.storeId,
346
+ command,
347
+ subjectIds: [artifactId],
348
+ expectedVersions: [],
349
+ relationChanges: [],
350
+ targetStatuses: [{ artifactId, status: targetStatus }],
351
+ supplementDispositions: [],
352
+ reason: `User changed the status of artifact ${artifactId} from ${artifact.status} to ${targetStatus}.`
353
+ };
354
+ if (!Value.Check(ArtifactChangeSetSchema, base)) {
355
+ throw new ArtifactInputError('Invalid transition change set.');
356
+ }
357
+ // First pass discovers the versions; the second pass checks them strictly.
358
+ const discovery = this.lifecycle.preview(base);
359
+ const changeSet = {
360
+ ...base,
361
+ expectedVersions: discovery.expectedVersions
362
+ };
363
+ const strict = this.lifecycle.preview(changeSet);
364
+ const blockers = strict.errors.map(item => ({
365
+ code: item.code,
366
+ message: item.message,
367
+ artifactIds: item.artifactIds
368
+ }));
369
+ if (!option.available) {
370
+ blockers.unshift({
371
+ code: 'transition_unavailable',
372
+ message: option.reason ?? 'The transition is unavailable.',
373
+ artifactIds: [artifactId]
374
+ });
375
+ }
376
+ if (!Value.Check(ArtifactChangeSetSchema, strict.canonical)) {
377
+ throw new ArtifactInputError('Invalid canonical transition change set.');
378
+ }
379
+ const canonical = strict.canonical;
380
+ const changes = strict.postState.flatMap(node => {
381
+ const before = storage.get(node.id);
382
+ return before.status === node.status
383
+ ? []
384
+ : [
385
+ {
386
+ artifactId: node.id,
387
+ title: node.title,
388
+ fromStatus: before.status,
389
+ toStatus: node.status
390
+ }
391
+ ];
392
+ });
393
+ return {
394
+ changeSet: canonical,
395
+ changeDigest: strict.changeDigest,
396
+ view: {
397
+ storeId: storage.storeId,
398
+ previewId: '',
399
+ artifactId,
400
+ title: artifact.title,
401
+ fromStatus: artifact.status,
402
+ targetStatus,
403
+ changes,
404
+ blockers,
405
+ canConfirm: blockers.length === 0,
406
+ expiresAt: 0
407
+ }
408
+ };
409
+ }
410
+ view(value) {
411
+ return {
412
+ storeId: this.lifecycle.storage.storeId,
413
+ id: value.id,
414
+ artifactId: value.artifactId,
415
+ title: value.title,
416
+ fromStatus: value.fromStatus,
417
+ targetStatus: value.targetStatus,
418
+ presetId: value.presetId,
419
+ stage: value.stage,
420
+ changes: value.changes,
421
+ canCancel: !isTerminalTransitionStage(value.stage) && value.stage !== 'applying',
422
+ cancelRequested: value.cancelRequested,
423
+ reason: value.reason,
424
+ proposalId: value.proposalId,
425
+ proposalVersion: value.proposalVersion,
426
+ validationId: value.validationId,
427
+ verdict: value.verdict,
428
+ reportArtifactId: value.reportArtifactId,
429
+ createdAt: value.createdAt,
430
+ updatedAt: value.updatedAt,
431
+ consentExpiresAt: value.consentExpiresAt
432
+ };
433
+ }
434
+ /** Appends an event and bumps the store epoch so other processes notice. */
435
+ write(value) {
436
+ this.repository.append(value);
437
+ this.lifecycle.storage.catalog.recordChange('transition', value.id, randomUUID(), false);
438
+ }
439
+ update(id, stage, when, eventKind) {
440
+ const changed = this.lifecycle.storage.catalog.transaction(() => {
441
+ const current = this.repository.operation(id);
442
+ if (isTerminalTransitionStage(current.stage) || !when(current)) {
443
+ return false;
444
+ }
445
+ this.write({ ...current, stage, updatedAt: this.now(), eventKind });
446
+ return true;
447
+ });
448
+ this.lifecycle.storage.pollChanges();
449
+ return changed;
450
+ }
451
+ finish(id, stage, reason, extra = {}) {
452
+ this.lifecycle.storage.catalog.transaction(() => {
453
+ const current = this.repository.operation(id);
454
+ if (isTerminalTransitionStage(current.stage)) {
455
+ return;
456
+ }
457
+ this.write({
458
+ ...current,
459
+ ...extra,
460
+ stage,
461
+ reason,
462
+ updatedAt: this.now(),
463
+ eventKind: stage
464
+ });
465
+ });
466
+ this.lifecycle.storage.pollChanges();
467
+ }
468
+ settle(validationId) {
469
+ const operation = this.repository
470
+ .unfinished()
471
+ .find(item => item.validationId === validationId);
472
+ if (operation) {
473
+ this.advance(operation.id);
474
+ }
475
+ }
476
+ runCancel(operation) {
477
+ try {
478
+ this.lifecycle.jobs.cancel(operation.validationId);
479
+ }
480
+ catch (error) {
481
+ if (!(error instanceof ArtifactNotFoundError)) {
482
+ throw error;
483
+ }
484
+ }
485
+ this.advance(operation.id);
486
+ }
487
+ /** Reads the validation outcome and applies, fails or cancels the operation. */
488
+ advance(id) {
489
+ if (this.stopped) {
490
+ return;
491
+ }
492
+ const operation = this.repository.operation(id);
493
+ if (isTerminalTransitionStage(operation.stage) ||
494
+ operation.owner !== this.owner) {
495
+ return;
496
+ }
497
+ const validation = this.lifecycle.repository.validation(operation.validationId);
498
+ const outcome = {
499
+ verdict: validation.report?.verdict ?? validation.state,
500
+ reportArtifactId: validation.report?.reportArtifactId ?? null
501
+ };
502
+ if (validation.state === 'prepared' || validation.state === 'running') {
503
+ if (operation.cancelRequested) {
504
+ this.runCancel(operation);
505
+ }
506
+ else if (operation.stage === 'preparing' &&
507
+ validation.state === 'prepared' &&
508
+ this.now() - operation.updatedAt > PREPARE_STALL_MS) {
509
+ // The start failed and its failure record could not be written (busy).
510
+ this.finish(id, 'failed', 'The validator did not start.');
511
+ }
512
+ return;
513
+ }
514
+ if (operation.cancelRequested) {
515
+ this.finish(id, 'cancelled', 'Cancelled by user.', outcome);
516
+ return;
517
+ }
518
+ if (validation.state !== 'pass') {
519
+ const reason = validation.state === 'conflict'
520
+ ? 'The validator found conflicts; statuses are unchanged.'
521
+ : (validation.reason ??
522
+ 'Validation did not finish; statuses are unchanged.');
523
+ this.finish(id, 'failed', reason, outcome);
524
+ return;
525
+ }
526
+ this.apply(operation, outcome);
527
+ }
528
+ apply(operation, outcome) {
529
+ const consent = this.repository.consent(operation.id);
530
+ const validation = this.lifecycle.repository.validation(operation.validationId);
531
+ const reject = (reason) => this.finish(operation.id, 'failed', reason, outcome);
532
+ if (!consent || consent.validationId !== operation.validationId) {
533
+ reject('The prior consent is missing.');
534
+ return;
535
+ }
536
+ if (consent.manifestDigest !== validation.manifestDigest ||
537
+ consent.changeDigest !== validation.manifest.changeDigest) {
538
+ reject('The confirmed scope changed; statuses are unchanged.');
539
+ return;
540
+ }
541
+ const gate = () => {
542
+ if (this.now() >= consent.expiresAt) {
543
+ return 'The prior consent expired; confirm again.';
544
+ }
545
+ if (!this.isActorActive(consent.actor)) {
546
+ return 'Access of the confirming device ended; statuses are unchanged.';
547
+ }
548
+ return null;
549
+ };
550
+ const early = gate();
551
+ if (early) {
552
+ reject(early);
553
+ return;
554
+ }
555
+ // Cancel and apply meet in this transaction; the loser sees the winner.
556
+ const started = this.lifecycle.storage.catalog.transaction(() => {
557
+ const current = this.repository.operation(operation.id);
558
+ if (isTerminalTransitionStage(current.stage) || current.cancelRequested) {
559
+ return false;
560
+ }
561
+ // A retry after a busy database finds the stage already written.
562
+ if (current.stage !== 'applying') {
563
+ this.write({
564
+ ...current,
565
+ stage: 'applying',
566
+ ...outcome,
567
+ updatedAt: this.now(),
568
+ eventKind: 'applying'
569
+ });
570
+ }
571
+ return true;
572
+ });
573
+ if (!started) {
574
+ this.advance(operation.id);
575
+ return;
576
+ }
577
+ try {
578
+ this.lifecycle.commitConfirmed({
579
+ changeSetId: consent.proposalId,
580
+ version: consent.proposalVersion,
581
+ validationId: consent.validationId,
582
+ requestId: `transition:${operation.id}`
583
+ }, consent.actor, () => {
584
+ const current = this.repository.operation(operation.id);
585
+ const blocked = gate();
586
+ if (current.stage !== 'applying' || blocked) {
587
+ throw new ArtifactConflictError(blocked ?? 'Operation is not applying.');
588
+ }
589
+ this.write({
590
+ ...current,
591
+ stage: 'succeeded',
592
+ reason: null,
593
+ updatedAt: this.now(),
594
+ eventKind: 'succeeded'
595
+ });
596
+ });
597
+ }
598
+ catch (error) {
599
+ // The commit rolled back; the next reconcile retries it under the same
600
+ // request ID, and the consent gate still bounds the time.
601
+ if (isArtifactBusyError(error)) {
602
+ return;
603
+ }
604
+ this.finish(operation.id, 'failed', errorMessage(error), outcome);
605
+ }
606
+ }
607
+ }
@@ -0,0 +1,74 @@
1
+ import { isValidArtifactTypeId } from "../../shared/utils/is-valid-artifact-type-id.js";
2
+ import { sha256Hex } from "../utils/hash.js";
3
+ import { ArtifactTypeInputSchema } from "../../shared/tasks/schemas.js";
4
+ import { ArtifactConflictError, ArtifactInputError } from "../errors/artifacts.js";
5
+ import { ArtifactDefinitionService, assertDefinitionInput } from "./artifact-definition.service.js";
6
+ // Built-in identifiers stay usable by old documents and are seeded once per store.
7
+ const INITIAL_TYPES = [
8
+ 'generic',
9
+ 'plan',
10
+ 'review',
11
+ 'research',
12
+ 'prd',
13
+ 'adr',
14
+ 'proposal',
15
+ 'specification',
16
+ 'report'
17
+ ];
18
+ // New documents use this type unless the caller names another one.
19
+ const DEFAULT_TYPE_ID = 'generic';
20
+ export class ArtifactTypeService extends ArtifactDefinitionService {
21
+ constructor(options) {
22
+ super(options, 'artifact_type');
23
+ options.catalog.transaction(() => {
24
+ for (const id of INITIAL_TYPES) {
25
+ if (options.repository.get('artifact_type', id))
26
+ continue;
27
+ if (options.repository.history('artifact_type', id).length) {
28
+ options.repository.rebuild();
29
+ continue;
30
+ }
31
+ const snapshot = {
32
+ storeId: options.catalog.storeId,
33
+ id,
34
+ label: id,
35
+ active: true,
36
+ version: 1
37
+ };
38
+ options.repository.append('artifact_type', snapshot, `seed:type:${id}`, { kind: 'system', id: 'artifact-types' }, new Date((options.now ?? Date.now)()).toISOString());
39
+ options.repository.record(`seed:type:${id}`, sha256Hex(`seed:type:${id}`), snapshot);
40
+ }
41
+ });
42
+ }
43
+ create(requestId, id, input, actor) {
44
+ this.validate(id, input);
45
+ return this.mutate(requestId, { command: 'create', id, input }, actor, () => {
46
+ if (this.options.repository.get('artifact_type', id)) {
47
+ throw new ArtifactConflictError('Artifact type already exists.');
48
+ }
49
+ return {
50
+ storeId: this.options.catalog.storeId,
51
+ id,
52
+ ...input,
53
+ version: 1
54
+ };
55
+ });
56
+ }
57
+ update(id, requestId, expectedVersion, input, actor) {
58
+ this.validate(id, input);
59
+ if (id === DEFAULT_TYPE_ID && !input.active) {
60
+ throw new ArtifactInputError('The default artifact type cannot be deactivated.');
61
+ }
62
+ return this.mutate(requestId, { command: 'update', id, expectedVersion, input }, actor, () => {
63
+ const previous = this.get(id);
64
+ this.assertVersion(previous.version, expectedVersion);
65
+ return { ...previous, ...input, version: previous.version + 1 };
66
+ });
67
+ }
68
+ validate(id, input) {
69
+ assertDefinitionInput(ArtifactTypeInputSchema, input);
70
+ if (!isValidArtifactTypeId(id) || !input.label.trim()) {
71
+ throw new ArtifactInputError('Invalid artifact type.');
72
+ }
73
+ }
74
+ }