@harperfast/harper 5.2.12 → 5.2.14

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 (269) hide show
  1. package/components/Application.ts +7 -7
  2. package/components/componentLoader.ts +70 -33
  3. package/dist/components/Application.js +1 -7
  4. package/dist/components/Application.js.map +1 -1
  5. package/dist/components/componentLoader.d.ts +6 -0
  6. package/dist/components/componentLoader.js +60 -31
  7. package/dist/components/componentLoader.js.map +1 -1
  8. package/dist/resources/RocksTransactionLogStore.d.ts +4 -2
  9. package/dist/resources/RocksTransactionLogStore.js +12 -6
  10. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  11. package/dist/resources/Table.d.ts +2 -2
  12. package/dist/resources/Table.js +222 -65
  13. package/dist/resources/Table.js.map +1 -1
  14. package/dist/resources/replayLogs.js +25 -21
  15. package/dist/resources/replayLogs.js.map +1 -1
  16. package/dist/resources/transactionBroadcast.js +4 -6
  17. package/dist/resources/transactionBroadcast.js.map +1 -1
  18. package/dist/security/certificateVerification/index.js +40 -11
  19. package/dist/security/certificateVerification/index.js.map +1 -1
  20. package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
  21. package/dist/security/certificateVerification/trustedIssuers.js +79 -0
  22. package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
  23. package/dist/security/certificateVerification/types.d.ts +1 -0
  24. package/dist/security/keys.js +4 -0
  25. package/dist/security/keys.js.map +1 -1
  26. package/dist/server/loadRootComponents.js +4 -2
  27. package/dist/server/loadRootComponents.js.map +1 -1
  28. package/dist/server/threads/manageThreads.d.ts +1 -0
  29. package/dist/server/threads/manageThreads.js +175 -24
  30. package/dist/server/threads/manageThreads.js.map +1 -1
  31. package/dist/utility/errors/hdbError.d.ts +8 -0
  32. package/dist/utility/errors/hdbError.js +29 -0
  33. package/dist/utility/errors/hdbError.js.map +1 -1
  34. package/npm-shrinkwrap.json +2 -2
  35. package/package.json +1 -1
  36. package/resources/DESIGN.md +5 -0
  37. package/resources/RocksTransactionLogStore.ts +12 -6
  38. package/resources/Table.ts +216 -66
  39. package/resources/replayLogs.ts +25 -22
  40. package/resources/transactionBroadcast.ts +4 -4
  41. package/security/certificateVerification/index.ts +54 -13
  42. package/security/certificateVerification/trustedIssuers.ts +76 -0
  43. package/security/certificateVerification/types.ts +1 -0
  44. package/security/keys.ts +3 -0
  45. package/server/DESIGN.md +14 -13
  46. package/server/loadRootComponents.js +9 -2
  47. package/server/threads/manageThreads.js +176 -23
  48. package/studio/web/404.html +79 -0
  49. package/studio/web/assets/{Chat-CokJOjyX.js → Chat-C97PMlIn.js} +274 -247
  50. package/studio/web/assets/FloatingChat-BUlsFGMV.js +23 -0
  51. package/studio/web/assets/_basePickBy-QJXUw_pr.js +1 -0
  52. package/studio/web/assets/_baseUniq-DR4vVjyC.js +1 -0
  53. package/studio/web/assets/{abnfDiagram-VCTEODGH-B0BebmD2.js → abnfDiagram-O67JEVCF-DpPZWMOf.js} +1 -1
  54. package/studio/web/assets/{alertDialog-CQyAJJhl.js → alertDialog-UrbiMgQt.js} +1 -1
  55. package/studio/web/assets/{apiToken-B_g12QrM.js → apiToken-CPhbc_zN.js} +1 -1
  56. package/studio/web/assets/applications-D3Z6BVDl.js +296 -0
  57. package/studio/web/assets/architecture-7GRP2DOG-11l4Ny1l.js +1 -0
  58. package/studio/web/assets/architectureDiagram-NJMV4G6O-OmgLraID.js +36 -0
  59. package/studio/web/assets/authStore-5jSS5pTW.js +3 -0
  60. package/studio/web/assets/{blockDiagram-I7D4REHJ-BqguiadH.js → blockDiagram-BEXU5L5S-Cv7CD3Ai.js} +15 -4
  61. package/studio/web/assets/{button-BIsUKRZq.js → button-MFP9n8Ck.js} +1 -1
  62. package/studio/web/assets/c4Diagram-YGBWAQC7-Ds1vKYnN.js +38 -0
  63. package/studio/web/assets/channel-BDtouPd6.js +1 -0
  64. package/studio/web/assets/chevron-up-CFe8x5Bp.js +1 -0
  65. package/studio/web/assets/chunk-3FUC2YCW-DpnBPH8B.js +2 -0
  66. package/studio/web/assets/{chunk-GVQU2GXP-sbwVIQ8i.js → chunk-5DYCD2WN-LLLCQSzg.js} +1 -1
  67. package/studio/web/assets/{chunk-PWAF6VOD-1z1THyS5.js → chunk-742MDFTN-BL8J_pIO.js} +1 -1
  68. package/studio/web/assets/chunk-7INBJB4K-C-hJHiiK.js +62 -0
  69. package/studio/web/assets/chunk-7M6MHVWA-C6ZqkFbA.js +213 -0
  70. package/studio/web/assets/chunk-7PRAP22T-CUXM8BG6.js +1 -0
  71. package/studio/web/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  72. package/studio/web/assets/{chunk-FOHPRMQF-DHwB1DNv.js → chunk-FOHPRMQF-DzxwRta7.js} +9 -9
  73. package/studio/web/assets/{chunk-SVP7TREG-jtdAHw0S.js → chunk-GTNCS2PH-DJ2BFvS1.js} +1 -1
  74. package/studio/web/assets/{chunk-F27PBJKO-BVA5EPhV.js → chunk-GWA4HPMP-C42bSqLL.js} +1 -1
  75. package/studio/web/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
  76. package/studio/web/assets/chunk-MBY4JIJT-C-5jz6Lo.js +72 -0
  77. package/studio/web/assets/chunk-NETBCI7D-D-bzlN4r.js +1 -0
  78. package/studio/web/assets/chunk-O7XYJQB3-ep14zA23.js +128 -0
  79. package/studio/web/assets/chunk-UA2S7LBM-BMRWRHED.js +1 -0
  80. package/studio/web/assets/{chunk-POPQ4Y6H-ClWhhkwW.js → chunk-WEXAMYUT-DdLYVdX1.js} +1 -1
  81. package/studio/web/assets/{chunk-XXDRQBXY-BoGZu3HH.js → chunk-XXDRQBXY-CbPh2l8S.js} +1 -1
  82. package/studio/web/assets/{chunk-OSK3NFVY-ByciRftO.js → chunk-Z7XXMR3K-BmKEDjcP.js} +3 -3
  83. package/studio/web/assets/{chunk-75Z2AOVW-QGQD6th2.js → chunk-ZIGJFQKS-DLZgdNxA.js} +2 -2
  84. package/studio/web/assets/classDiagram-v2-NBCMYWYE-DOI5vgKT.js +217 -0
  85. package/studio/web/assets/cose-bilkent-JH36ORCC-BSOszL11.js +1 -0
  86. package/studio/web/assets/createLucideIcon-bziYxJli.js +1 -0
  87. package/studio/web/assets/{css.worker-BD9HbRGW.js → css.worker-l1psR_wG.js} +13 -10
  88. package/studio/web/assets/{cssMode-DAjzPoBf.js → cssMode-DyLZPMjo.js} +1 -1
  89. package/studio/web/assets/cynefin-OW5HDTMX-BSW-DB7S.js +1 -0
  90. package/studio/web/assets/cynefinDiagram-VND7K2PF-CbAHEwxt.js +62 -0
  91. package/studio/web/assets/cytoscape.esm-BZkf-uXn.js +321 -0
  92. package/studio/web/assets/dagre-6A5THRUB-Csxewgs4.js +4 -0
  93. package/studio/web/assets/diagram-22UHCM2B-a0rt8tY4.js +200 -0
  94. package/studio/web/assets/{diagram-VX7I27RA-DccVJet6.js → diagram-3UASUU5V-lEj2SVNh.js} +2 -2
  95. package/studio/web/assets/{diagram-VSXAHHWV-fhEdmkwM.js → diagram-ATOU4E4O-HcrAeB-q.js} +3 -3
  96. package/studio/web/assets/{diagram-S7CK7UJ4-aoCVTtcy.js → diagram-CDSNMT55-CYxiuyEx.js} +3 -3
  97. package/studio/web/assets/{diagram-Z3DM3KII-D-RyJJb7.js → diagram-MLGK6HIB-ChbCJTgJ.js} +1 -1
  98. package/studio/web/assets/diagram-MPIPVDR6-DaX9Y-N3.js +41 -0
  99. package/studio/web/assets/dialog-DTepachC.js +1 -0
  100. package/studio/web/assets/{dist-kYfyPZP9.js → dist-BRuBKNNy.js} +1 -1
  101. package/studio/web/assets/{dist-DP8UjMB_.js → dist-Cz2Y6-6G.js} +2 -2
  102. package/studio/web/assets/download-CtostjYn.js +1 -0
  103. package/studio/web/assets/{ebnfDiagram-PWID7BFC-DJGpIpz_.js → ebnfDiagram-ZINNZB2B-CwAsENB1.js} +1 -1
  104. package/studio/web/assets/{editor-VUNQ-5Ye.js → editor-CFQ5kbiT.js} +1 -1
  105. package/studio/web/assets/editor.worker-CkkvdXvO.js +33 -0
  106. package/studio/web/assets/elk-276RUBZZ-Hexg9E4D.js +27 -0
  107. package/studio/web/assets/{erDiagram-RLTQ6QDP-CIfNlgkC.js → erDiagram-OPXOYQCR-CeC-wwO1.js} +15 -15
  108. package/studio/web/assets/eventmodeling-NTZA5JFV-CFb03e0o.js +1 -0
  109. package/studio/web/assets/flowDiagram-KWPJA3E3-Cidi00_Y.js +1 -0
  110. package/studio/web/assets/{ganttDiagram-EL5Y4UJY-BxToTzzD.js → ganttDiagram-FUAMR5RP-C2_Hhl1-.js} +2 -2
  111. package/studio/web/assets/{getAnalytics-GHK8ORfM.js → getAnalytics-CjthMQqF.js} +1 -1
  112. package/studio/web/assets/gitGraph-4MIJSDKK-xIk5Vuew.js +1 -0
  113. package/studio/web/assets/{gitGraphDiagram-WWUBYQGX-Dwntd4-x.js → gitGraphDiagram-X574FWY7-CMxJ34up.js} +4 -4
  114. package/studio/web/assets/graphlib-C9Tl7QrK.js +1 -0
  115. package/studio/web/assets/{html-CFqhtdfa.js → html-DdMBJEGu.js} +1 -1
  116. package/studio/web/assets/{html.worker-DsMmVtuG.js → html.worker-DdwM4IrA.js} +12 -9
  117. package/studio/web/assets/{htmlMode-CpgOTfjz.js → htmlMode-DNtBx3OV.js} +1 -1
  118. package/studio/web/assets/index-CPoDzQTe.css +1 -0
  119. package/studio/web/assets/index-zZCxAIEb.js +824 -0
  120. package/studio/web/assets/index.lazy-CKbsHbSN.js +2 -0
  121. package/studio/web/assets/info-A6RAGUB7-o49TEbj-.js +1 -0
  122. package/studio/web/assets/infoDiagram-VRGFBTTK-Cjhh7yzZ.js +2 -0
  123. package/studio/web/assets/{ishikawaDiagram-5VMMS53U-D9Xh2r6X.js → ishikawaDiagram-OU5B5YK6-C63aXRmh.js} +2 -2
  124. package/studio/web/assets/{javascript-9l5JXJwc.js → javascript-Dy6KbeHT.js} +1 -1
  125. package/studio/web/assets/{journeyDiagram-3NMN7TZE-CokIi6ll.js → journeyDiagram-ZHPQQLJL-D_o0P5Mu.js} +3 -3
  126. package/studio/web/assets/json.worker-CSi2hF-a.js +65 -0
  127. package/studio/web/assets/{jsonMode-BJBH-iR0.js → jsonMode-BiuDVrzl.js} +1 -1
  128. package/studio/web/assets/{kanban-definition-UXKFOSKX-CukSFJfX.js → kanban-definition-PNTS6WVX-CTI9SSxY.js} +7 -7
  129. package/studio/web/assets/{katex-CXMH3UgJ.js → katex-ZlcWpGUi.js} +2 -2
  130. package/studio/web/assets/languageServices-Dk8_6B25.js +1 -0
  131. package/studio/web/assets/linear-Ds1AeO4V.js +1 -0
  132. package/studio/web/assets/{lspLanguageFeatures-sNVDopx3.js → lspLanguageFeatures-DwJKXzQa.js} +2 -2
  133. package/studio/web/assets/{mermaid-parser.core-BlEsOWNO.js → mermaid-parser.core-C2lcH_jP.js} +3 -3
  134. package/studio/web/assets/mermaid.core-BG5bjfIG.js +44 -0
  135. package/studio/web/assets/{mindmap-definition-YA3MSWOX-IprMc_0j.js → mindmap-definition-NLK3R4M7-BK7ho6JD.js} +24 -24
  136. package/studio/web/assets/{notifications-BukjVuVe.js → notifications-DJqAtWxD.js} +1 -1
  137. package/studio/web/assets/{notifications-DvBJIIO0.js → notifications-DsDtb41c.js} +1 -1
  138. package/studio/web/assets/packet-AYTQ26CC-BHPy6KBV.js +1 -0
  139. package/studio/web/assets/{pegDiagram-XKGWAZYB-BNuPDLZY.js → pegDiagram-GJSIUBJH-DzjwG3jO.js} +1 -1
  140. package/studio/web/assets/pie-WAS4IAKB-D0cP-DTU.js +1 -0
  141. package/studio/web/assets/{pieDiagram-E7YTZNPT-DqNb6Ht2.js → pieDiagram-5QR66LMP-C_hOP9Lv.js} +2 -2
  142. package/studio/web/assets/profile-zTGUN51x.js +1 -0
  143. package/studio/web/assets/{quadrantDiagram-AXDQQJYC-BGH9E2YR.js → quadrantDiagram-O4NWA36T-CZW8PbV1.js} +3 -3
  144. package/studio/web/assets/radar-RG4KPBEZ-C8fk0U81.js +1 -0
  145. package/studio/web/assets/railroad-74A4TZTK-B60FWm17.js +1 -0
  146. package/studio/web/assets/railroad-abnf-HS5TGJTU-CLMVZvH0.js +1 -0
  147. package/studio/web/assets/railroad-ebnf-LZEXJU2U--tlwFoVB.js +1 -0
  148. package/studio/web/assets/railroad-peg-WCYAUIDC-Dpuslblm.js +1 -0
  149. package/studio/web/assets/{railroadDiagram-O6MQD6OU-DGTPh2KZ.js → railroadDiagram-XR7U4H2S-DyyNmgbU.js} +1 -1
  150. package/studio/web/assets/regions-SOtABPB9.js +1 -0
  151. package/studio/web/assets/{register-CU-gCfLP.js → register-7JezVakc.js} +39 -39
  152. package/studio/web/assets/{requirementDiagram-BXWQKSXE-BJnO6uLz.js → requirementDiagram-PLB6GJNP-C4H-mLKu.js} +11 -11
  153. package/studio/web/assets/{rolldown-runtime-hePW80VL.js → rolldown-runtime-Dd_uD5pT.js} +1 -1
  154. package/studio/web/assets/sankeyDiagram-IPEJSGJF-DHDyga4c.js +40 -0
  155. package/studio/web/assets/sequenceDiagram-PO4LG4MO-CdNcHXRI.js +169 -0
  156. package/studio/web/assets/setComponentFile-BavlhfY0.js +1 -0
  157. package/studio/web/assets/{setup-DG3Ws0Gg.js → setup-04yVzr2I.js} +2 -2
  158. package/studio/web/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  159. package/studio/web/assets/{src-CMc7m_Ut.js → src-D0vMJQRr.js} +1 -1
  160. package/studio/web/assets/stateDiagram-v2-GCMORJYK-CpKuXq15.js +291 -0
  161. package/studio/web/assets/status-g_mNYE7j.js +61 -0
  162. package/studio/web/assets/swimlanes-2SLR337P-NRHa2D8I.js +1 -0
  163. package/studio/web/assets/swimlanesDiagram-TC7HE7FX-BBk4t4hO.js +8 -0
  164. package/studio/web/assets/{tabs-BrHu7gJi.js → tabs-CnQ_zenf.js} +1 -1
  165. package/studio/web/assets/{time-Cclkb7kQ.js → time-Ci1v4fvT.js} +1 -1
  166. package/studio/web/assets/timeline-definition-EJHVYXUP-D39Y-xpq.js +120 -0
  167. package/studio/web/assets/toggleHighContrast-i2FjCrzO.js +49 -0
  168. package/studio/web/assets/treeView-Q6P3EWNA-CRWR9OqD.js +1 -0
  169. package/studio/web/assets/treemap-WGGIJYW6-ZfXyJgLO.js +1 -0
  170. package/studio/web/assets/{ts.worker-DpaaFKhy.js → ts.worker-DDMUrpqh.js} +118 -115
  171. package/studio/web/assets/{tsMode-D3T_5QzW.js → tsMode-DGeWfx0W.js} +5 -5
  172. package/studio/web/assets/{typescript-BegjUvtB.js → typescript-62_1FUbs.js} +1 -1
  173. package/studio/web/assets/{useEntityRestURL-BicFpx_N.js → useEntityRestURL-DSVwev9J.js} +1 -1
  174. package/studio/web/assets/useLocalStorage-BYoZF60G.js +1 -0
  175. package/studio/web/assets/usecaseDiagram-POWQR4AR-jf-zIDZ_.js +328 -0
  176. package/studio/web/assets/vendor-core-BdkdbUek.js +63 -0
  177. package/studio/web/assets/vendor-datadog-fCQhBHGk.js +6 -0
  178. package/studio/web/assets/vendor-react-izWchTRL.js +9 -0
  179. package/studio/web/assets/vendor-tanstack-CQ1aRjVa.js +2 -0
  180. package/studio/web/assets/vendor-ui-aE1tHcW7.js +41 -0
  181. package/studio/web/assets/vennDiagram-UO4OBE2U-BKYAZUh_.js +34 -0
  182. package/studio/web/assets/wardley-WFR3VGLG-BO7f36ci.js +1 -0
  183. package/studio/web/assets/{wardleyDiagram-VM6X3IG4-CjrkKWUR.js → wardleyDiagram-VNRHLVJA-Djb0JDQc.js} +3 -3
  184. package/studio/web/assets/{workers-Bmx4l_9B.js → workers-Cs52GyHv.js} +1 -1
  185. package/studio/web/assets/x-vYntCxm4.js +1 -0
  186. package/studio/web/assets/{xml-Bg40Rcra.js → xml-Co5ow_Ix.js} +1 -1
  187. package/studio/web/assets/{xychartDiagram-S5SC5T6Z-Biok4GYV.js → xychartDiagram-PMCCYNJV-BQh9ubvb.js} +3 -3
  188. package/studio/web/assets/{yaml-DolDvfXE.js → yaml-BcngpXye.js} +1 -1
  189. package/studio/web/assets/yaml.worker-DAK2o_H1.js +479 -0
  190. package/studio/web/auth/fabric-globe-dark.png +0 -0
  191. package/studio/web/auth/fabric-globe-light.png +0 -0
  192. package/studio/web/auth/fabric-platform-dark.png +0 -0
  193. package/studio/web/auth/fabric-platform-light.png +0 -0
  194. package/studio/web/index.html +15 -15
  195. package/utility/errors/hdbError.ts +26 -0
  196. package/studio/web/assets/FloatingChat-h9BJo6YG.js +0 -23
  197. package/studio/web/assets/applications-Dlq5kJ2O.js +0 -296
  198. package/studio/web/assets/architecture-7GRP2DOG-LB-MLAAb.js +0 -1
  199. package/studio/web/assets/architectureDiagram-5GKGNRK7-7SW3GD-K.js +0 -36
  200. package/studio/web/assets/authStore-C3Nfubqr.js +0 -3
  201. package/studio/web/assets/c4Diagram-7LVT6UL2-LBNf8t_X.js +0 -38
  202. package/studio/web/assets/channel-yictG-U-.js +0 -1
  203. package/studio/web/assets/chevron-up-DtKGqDn3.js +0 -1
  204. package/studio/web/assets/chunk-4HAMMTFA-DWtTut21.js +0 -62
  205. package/studio/web/assets/chunk-DU6HZSFF-Chq20Ba5.js +0 -127
  206. package/studio/web/assets/chunk-GMAD6QVW-BeS7S07A.js +0 -72
  207. package/studio/web/assets/chunk-IMKFNOWR-Bnh3tAVd.js +0 -231
  208. package/studio/web/assets/chunk-L3NEJ4N5-COfUyKII.js +0 -1
  209. package/studio/web/assets/chunk-P2QGCYS3-CP1VhG_c.js +0 -1
  210. package/studio/web/assets/chunk-SHT3W25Y-LpQkMsah.js +0 -168
  211. package/studio/web/assets/chunk-TICWLB2K-VOwzetX-.js +0 -206
  212. package/studio/web/assets/classDiagram-ZZMXUADV-VaEwSy_g.js +0 -1
  213. package/studio/web/assets/classDiagram-v2-VYDZK3BY-VaEwSy_g.js +0 -1
  214. package/studio/web/assets/cose-bilkent-JH36ORCC-Cuuf67Bh.js +0 -1
  215. package/studio/web/assets/createLucideIcon-CzW9508A.js +0 -1
  216. package/studio/web/assets/cynefin-OW5HDTMX-BbdbCvub.js +0 -1
  217. package/studio/web/assets/cynefinDiagram-5FMLGOSQ-TP-aIqbt.js +0 -62
  218. package/studio/web/assets/cytoscape.esm-C2x7inlt.js +0 -321
  219. package/studio/web/assets/dagre-GXQ25YYZ-DShnGpGo.js +0 -4
  220. package/studio/web/assets/dagre-PrKaheQc.js +0 -1
  221. package/studio/web/assets/diagram-UQ7AKVKN-DglXtQ6x.js +0 -41
  222. package/studio/web/assets/dialog-Cn2uWgD4.js +0 -1
  223. package/studio/web/assets/download-B5T5r7ss.js +0 -1
  224. package/studio/web/assets/editor.worker-DwcOiOeP.js +0 -30
  225. package/studio/web/assets/eventmodeling-NTZA5JFV-CLxnp2CR.js +0 -1
  226. package/studio/web/assets/flowDiagram-HODETNUW-BIbhmz9f.js +0 -1
  227. package/studio/web/assets/gitGraph-4MIJSDKK-D2s2w8lE.js +0 -1
  228. package/studio/web/assets/graphlib-DS17s2tU.js +0 -1
  229. package/studio/web/assets/index-7RMEgVG1.css +0 -1
  230. package/studio/web/assets/index-ZQ1upnh7.js +0 -824
  231. package/studio/web/assets/index.lazy-HDafSRxx.js +0 -2
  232. package/studio/web/assets/info-A6RAGUB7-DYjkvb0C.js +0 -1
  233. package/studio/web/assets/infoDiagram-27XIBGKW-Bnp1FJE5.js +0 -2
  234. package/studio/web/assets/json.worker-fO_hOLCP.js +0 -62
  235. package/studio/web/assets/languageServices-0ZvgO63b.js +0 -1
  236. package/studio/web/assets/linear-Cnt8506Q.js +0 -1
  237. package/studio/web/assets/mermaid.core-BlkGaMIH.js +0 -44
  238. package/studio/web/assets/packet-AYTQ26CC-Bi3V04Zi.js +0 -1
  239. package/studio/web/assets/pie-WAS4IAKB-_6DoDbng.js +0 -1
  240. package/studio/web/assets/profile-B-jyYdzV.js +0 -1
  241. package/studio/web/assets/radar-RG4KPBEZ-DDdVczcL.js +0 -1
  242. package/studio/web/assets/railroad-74A4TZTK-BJUP4Jds.js +0 -1
  243. package/studio/web/assets/railroad-abnf-HS5TGJTU-Bm3L1L0h.js +0 -1
  244. package/studio/web/assets/railroad-ebnf-LZEXJU2U-CKzLGlkw.js +0 -1
  245. package/studio/web/assets/railroad-peg-WCYAUIDC-S8xLjslx.js +0 -1
  246. package/studio/web/assets/regions-GN6Mu5aH.js +0 -1
  247. package/studio/web/assets/sankeyDiagram-P5KCCOFB-0vSOdymH.js +0 -40
  248. package/studio/web/assets/sequenceDiagram-WJ2MYXX4-hETizDWE.js +0 -162
  249. package/studio/web/assets/setComponentFile-D7Fww0lC.js +0 -1
  250. package/studio/web/assets/sizeCapture-INFHLROL-B0uUizjq.js +0 -1
  251. package/studio/web/assets/stateDiagram-D77RDMKH-CdYQ_KtC.js +0 -1
  252. package/studio/web/assets/stateDiagram-v2-MP3YSRHH-CdKuzQMT.js +0 -1
  253. package/studio/web/assets/status-koJd66XF.js +0 -61
  254. package/studio/web/assets/swimlanes-42K2YHIH-B8cHIpU4.js +0 -1
  255. package/studio/web/assets/swimlanesDiagram-VR7AAH4N-DmOSJwaH.js +0 -8
  256. package/studio/web/assets/timeline-definition-24CTP7MA-BJWYSXqF.js +0 -120
  257. package/studio/web/assets/toggleHighContrast-CGksgPgv.js +0 -49
  258. package/studio/web/assets/treeView-Q6P3EWNA-qxe_v6CQ.js +0 -1
  259. package/studio/web/assets/treemap-WGGIJYW6-dDo97XXF.js +0 -1
  260. package/studio/web/assets/useLocalStorage-BqMR3D8_.js +0 -1
  261. package/studio/web/assets/vendor-core-c2JRRJpV.js +0 -58
  262. package/studio/web/assets/vendor-datadog-CLUcJXOo.js +0 -6
  263. package/studio/web/assets/vendor-react-CJV_K1u4.js +0 -9
  264. package/studio/web/assets/vendor-tanstack-DxzraizX.js +0 -1
  265. package/studio/web/assets/vendor-ui-BUjK0h8a.js +0 -41
  266. package/studio/web/assets/vennDiagram-4TSXK5OY-A3i-lCdl.js +0 -34
  267. package/studio/web/assets/wardley-WFR3VGLG-B0ik-_6g.js +0 -1
  268. package/studio/web/assets/x-DIzaLEdK.js +0 -1
  269. package/studio/web/assets/yaml.worker-BDL8_iuk.js +0 -454
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Resolves a client certificate's issuer from Harper's own trusted certificate authorities.
3
+ *
4
+ * Node completes a peer chain from the trust store only through the listener's default TLS
5
+ * context; Harper's client CAs live on the SNI contexts built by createTLSSelector, so whenever the
6
+ * client-sent chain is unavailable (every resumed TLS session; Node 26.8.0/26.8.1 on every
7
+ * connection, nodejs/node#65579) getPeerCertificate(true) exposes the leaf alone. The CA set that
8
+ * authorized the connection is the same set published here, so the issuer is recoverable from it
9
+ * unless it is an intermediate the client sent but Harper does not trust directly.
10
+ */
11
+
12
+ import { X509Certificate, createHash } from 'node:crypto';
13
+
14
+ const PEM_CERTIFICATE_PATTERN =
15
+ /-----BEGIN (?:TRUSTED |X509 )?CERTIFICATE-----[\s\S]*?-----END (?:TRUSTED |X509 )?CERTIFICATE-----/g;
16
+ const MAX_RESOLVED_LEAVES = 10_000;
17
+
18
+ let trustedAuthorities: X509Certificate[] = [];
19
+ let publishedAuthorityPems = '';
20
+ let resolvedIssuers = new Map<string, Buffer | null>();
21
+
22
+ /**
23
+ * Publish the current trusted authority set (PEM strings, each possibly a bundle of several
24
+ * certificates). Replaces the previous generation atomically and drops its resolution cache,
25
+ * unless the set is unchanged. Unparseable certificates are skipped so one bad record cannot
26
+ * disable resolution for the rest; this never throws, because it runs inside the TLS selector's
27
+ * publication step.
28
+ */
29
+ export function publishTrustedAuthorities(authorityPems: Iterable<unknown>): void {
30
+ const pems = Array.from(authorityPems).filter((pem): pem is string => typeof pem === 'string');
31
+ const joined = pems.join('\n');
32
+ if (joined === publishedAuthorityPems) return;
33
+ publishedAuthorityPems = joined;
34
+ const parsed: X509Certificate[] = [];
35
+ for (const pem of pems) {
36
+ for (const block of pem.match(PEM_CERTIFICATE_PATTERN) ?? []) {
37
+ try {
38
+ parsed.push(new X509Certificate(block));
39
+ } catch {}
40
+ }
41
+ }
42
+ trustedAuthorities = parsed;
43
+ resolvedIssuers = new Map();
44
+ }
45
+
46
+ /**
47
+ * Find the trusted authority that issued `leafDer`: subject/AKI match plus a signature check, so a
48
+ * same-named authority with a different key never counts, and a certificate is never its own
49
+ * issuer. Returns the issuer's DER, or undefined.
50
+ */
51
+ export function resolveTrustedIssuer(leafDer: Buffer, fingerprint256?: string): Buffer | undefined {
52
+ if (trustedAuthorities.length === 0) return undefined;
53
+ const key = fingerprint256 ?? createHash('sha256').update(leafDer).digest('hex');
54
+ const cached = resolvedIssuers.get(key);
55
+ if (cached !== undefined) return cached ?? undefined;
56
+
57
+ let issuer: Buffer | null = null;
58
+ try {
59
+ const leaf = new X509Certificate(leafDer);
60
+ for (const authority of trustedAuthorities) {
61
+ try {
62
+ if (
63
+ authority.fingerprint256 !== leaf.fingerprint256 &&
64
+ leaf.checkIssued(authority) &&
65
+ leaf.verify(authority.publicKey)
66
+ ) {
67
+ issuer = authority.raw;
68
+ break;
69
+ }
70
+ } catch {}
71
+ }
72
+ } catch {}
73
+ if (resolvedIssuers.size >= MAX_RESOLVED_LEAVES) resolvedIssuers.clear();
74
+ resolvedIssuers.set(key, issuer);
75
+ return issuer ?? undefined;
76
+ }
@@ -17,6 +17,7 @@ export interface PeerCertificate {
17
17
  [key: string]: any;
18
18
  };
19
19
  raw?: Buffer;
20
+ fingerprint256?: string;
20
21
  issuerCertificate?: PeerCertificate;
21
22
  }
22
23
 
package/security/keys.ts CHANGED
@@ -34,6 +34,7 @@ import { readFileSync, statSync } from 'node:fs';
34
34
  import { getTicketKeys, onMessageFromWorkers } from '../server/threads/manageThreads.js';
35
35
  import { isMainThread } from 'worker_threads';
36
36
  import { TLSSocket } from 'node:tls';
37
+ import { publishTrustedAuthorities } from './certificateVerification/trustedIssuers.ts';
37
38
 
38
39
  const CERT_VALIDITY_DAYS = 3650;
39
40
  // Default interval (ms) for the periodic cert-file re-read safety net. The chokidar (inotify)
@@ -1212,6 +1213,8 @@ export function createTLSSelector(type, mtlsOptions?, liveReload = true): any {
1212
1213
  scheduleRebuild();
1213
1214
  return;
1214
1215
  }
1216
+ // only listener selectors publish: a one-shot client selector's pass may see no authority rows
1217
+ if (liveReload) publishTrustedAuthorities(caCerts.values());
1215
1218
  // A successful pass ends any warn latches so a later recurrence logs again.
1216
1219
  if (server) {
1217
1220
  server.tlsSelectorWaitedForSystemDb = false;
package/server/DESIGN.md CHANGED
@@ -172,19 +172,20 @@ The `@table(cacheControl:)` value is persisted on the primary-key attribute (lik
172
172
 
173
173
  ## "Where is X" cheat sheet
174
174
 
175
- | Question | Where |
176
- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
177
- | Where do I register a new HTTP handler? | `http.ts → httpServer()` (or `onRequest()` for the request-only form) |
178
- | Where do I register a WebSocket handler? | `http.ts → onWebSocket()` |
179
- | How does `before`/`after` middleware ordering work? | `middlewareChain.ts → topoSort` |
180
- | Where does PROXY protocol get parsed? | `serverHelpers/proxyProtocol.ts` (applied by `http.ts → enableProxyProtocol` / `createH2CProxyFront`) |
181
- | How does an app read forwarded TLS facts (JA3/JA4, ALPN, SNI, mTLS cert)? | `request.connectionInfo` (`ConnectionInfo` from `serverHelpers/proxyProtocol.ts`); only set from a trusted PROXY v2 header on the UDS mirror, never from a request header |
182
- | Where is the REST request → Resource dispatch? | `REST.ts → http()` |
183
- | Where is the operations API request handled? | `operationsServer.ts → handler` |
184
- | How are content types (de)serialized? | `serverHelpers/contentTypes.ts` |
185
- | Where do durable subscriptions live? | `DurableSubscriptionsSession.ts` |
186
- | How are sockets dispatched to worker threads? | `threads/socketRouter.ts` |
187
- | Where is the Operations API wired into Fastify? | `operationsServer.ts → buildServer` |
175
+ | Question | Where |
176
+ | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
177
+ | Where do I register a new HTTP handler? | `http.ts → httpServer()` (or `onRequest()` for the request-only form) |
178
+ | Where do I register a WebSocket handler? | `http.ts → onWebSocket()` |
179
+ | How does `before`/`after` middleware ordering work? | `middlewareChain.ts → topoSort` |
180
+ | Where does PROXY protocol get parsed? | `serverHelpers/proxyProtocol.ts` (applied by `http.ts → enableProxyProtocol` / `createH2CProxyFront`) |
181
+ | How does an app read forwarded TLS facts (JA3/JA4, ALPN, SNI, mTLS cert)? | `request.connectionInfo` (`ConnectionInfo` from `serverHelpers/proxyProtocol.ts`); only set from a trusted PROXY v2 header on the UDS mirror, never from a request header |
182
+ | Why does mTLS revocation checking look up the client cert's issuer itself? | `security/certificateVerification/trustedIssuers.ts` — Node only completes a peer chain from the _listener_ context's store, but Harper's client CAs live on SNI contexts (`security/keys.ts → createTLSSelector`), so a resumed TLS session (and Node 26.8.0/26.8.1, nodejs/node#65579) exposes the leaf alone; `verifyCertificate` recovers the issuer from the published CA set and otherwise applies `failureMode` |
183
+ | Where is the REST request → Resource dispatch? | `REST.ts → http()` |
184
+ | Where is the operations API request handled? | `operationsServer.ts → handler` |
185
+ | How are content types (de)serialized? | `serverHelpers/contentTypes.ts` |
186
+ | Where do durable subscriptions live? | `DurableSubscriptionsSession.ts` |
187
+ | How are sockets dispatched to worker threads? | `threads/socketRouter.ts` |
188
+ | Where is the Operations API wired into Fastify? | `operationsServer.ts → buildServer` |
188
189
 
189
190
  ---
190
191
 
@@ -1,6 +1,10 @@
1
1
  const { isMainThread } = require('worker_threads');
2
2
  const { getTables } = require('../resources/databases.ts');
3
- const { loadComponentDirectories, loadComponent } = require('../components/componentLoader.ts');
3
+ const {
4
+ loadComponentDirectories,
5
+ loadComponent,
6
+ startSecretCustodyOnMainThread,
7
+ } = require('../components/componentLoader.ts');
4
8
  const { resetResources } = require('../resources/Resources.ts');
5
9
  const configUtils = require('../config/configUtils.ts');
6
10
  const { dirname } = require('path');
@@ -15,7 +19,10 @@ let loadedComponents = new Map();
15
19
  */
16
20
  async function loadRootComponents(isWorkerThread = false) {
17
21
  try {
18
- if (isMainThread && !process.env.HARPER_SAFE_MODE) await installApplications();
22
+ if (isMainThread && !process.env.HARPER_SAFE_MODE) {
23
+ await startSecretCustodyOnMainThread();
24
+ await installApplications();
25
+ }
19
26
  } catch (error) {
20
27
  console.error(errorForLog(error));
21
28
  }
@@ -7,7 +7,7 @@ const { realExit } = require('./workerProcessGuard.ts');
7
7
 
8
8
  const { Worker, MessageChannel, parentPort, isMainThread, threadId, workerData } = require('worker_threads');
9
9
  const { spawn, spawnSync } = require('node:child_process');
10
- const { readFileSync } = require('node:fs');
10
+ const { readdirSync, readFileSync } = require('node:fs');
11
11
  const { setTimeout: delay } = require('node:timers/promises');
12
12
  const { join, isAbsolute, extname } = require('path');
13
13
  const { pathToFileURL } = require('url');
@@ -156,6 +156,7 @@ module.exports = {
156
156
  onThreadExit,
157
157
  registerProcessGroup,
158
158
  unregisterProcessGroup,
159
+ isProcessGroupAlive,
159
160
  isThreadRunning,
160
161
  waitUntilConfirmedGone,
161
162
  restartNumber: workerData?.restartNumber || 1,
@@ -923,17 +924,23 @@ if (parentPort && workerData?.addPorts) {
923
924
  }
924
925
  });
925
926
  let awaitTerminationRequestId = 0;
926
- awaitProcessGroupTermination = (ownerThreadId) =>
927
+ awaitProcessGroupTermination = (ownerThreadId, signal) =>
927
928
  new Promise((resolve) => {
928
929
  // Deliberately no timeout: a contender must not reclaim a dead owner's lock while its
929
930
  // process group might still be alive and mutating files, so this mirrors the unbounded
930
- // wait Application.ts's waitForConfirmedTermination uses for the same reason.
931
+ // wait Application.ts's waitForConfirmedTermination uses for the same reason. A caller
932
+ // that needs its own bound (isThreadRunning) passes `signal` so this listener still gets
933
+ // torn down when that caller gives up, instead of leaking for the life of the worker.
931
934
  const requestId = ++awaitTerminationRequestId;
932
935
  parentPort.on('message', receiveConfirmation);
936
+ signal?.addEventListener('abort', cleanup, { once: true });
933
937
  parentPort.postMessage({ type: AWAIT_PROCESS_GROUP_TERMINATION, ownerThreadId, requestId });
938
+ function cleanup() {
939
+ parentPort.off('message', receiveConfirmation);
940
+ }
934
941
  function receiveConfirmation(message) {
935
942
  if (message.type === PROCESS_GROUP_TERMINATION_CONFIRMED && message.requestId === requestId) {
936
- parentPort.off('message', receiveConfirmation);
943
+ cleanup();
937
944
  resolve();
938
945
  }
939
946
  }
@@ -966,33 +973,142 @@ const processGroupsByThread = new Map();
966
973
  // window component-preparation locking exists to close.
967
974
  const pendingProcessGroupTerminations = new Map();
968
975
  const PROCESS_GROUP_TERMINATION_POLL_MS = 25;
976
+ const ZOMBIE_GROUP_MEMBER_SCAN_INTERVAL_MS = 1000;
977
+ const PROCESS_GROUP_LIVENESS_WARNING_MS = 30000;
978
+ // Bounds isThreadRunning's own wait (below); Application.ts's waitForConfirmedTermination stays
979
+ // deliberately unbounded.
980
+ const THREAD_RUNNING_TERMINATION_BACKSTOP_MS = PROCESS_GROUP_LIVENESS_WARNING_MS;
981
+ const zombieGroupScanTimes = new Map();
982
+ const processGroupLivenessStates = new Map();
983
+
984
+ function processGroupExists(processGroupId) {
985
+ try {
986
+ process.kill(-processGroupId, 0);
987
+ return true;
988
+ } catch (error) {
989
+ return error.code === 'EPERM';
990
+ }
991
+ }
992
+
993
+ function processProbeError(error) {
994
+ return error?.code ?? error?.message ?? String(error ?? 'unknown error');
995
+ }
969
996
 
970
- // A process-group leader spawned by a worker thread's own event loop is normally reaped by that
971
- // same loop when it dies. But the only caller of this check (waitForProcessGroupExit, for a
972
- // *dead* owner thread) runs after that loop is already gone, so nothing will ever reap it here:
973
- // kill(pid, 0) keeps succeeding forever against the zombie's still-allocated PID slot. A zombie
974
- // can no longer touch the filesystem, so treat a confirmed zombie as dead rather than poll
975
- // forever for a reap that will never come. Reproduced by killing a worker mid-install: the
976
- // installer's process-group leader becomes a permanent `Z` in /proc without this check.
977
- function isZombieProcessGroupLeader(processGroupId) {
978
- if (process.platform !== 'linux') return false;
997
+ function processGroupLeaderState(processGroupId, platform, readStat) {
998
+ if (platform !== 'linux') {
999
+ return { state: 'unknown', reason: `zombie-process detection is unavailable on ${platform}` };
1000
+ }
979
1001
  let stat;
980
1002
  try {
981
- stat = readFileSync(`/proc/${processGroupId}/stat`, 'utf8');
982
- } catch {
983
- return false;
1003
+ stat = readStat(`/proc/${processGroupId}/stat`, 'utf8');
1004
+ } catch (error) {
1005
+ if (error.code === 'ENOENT' || error.code === 'ESRCH') return { state: 'missing' };
1006
+ return {
1007
+ state: 'unknown',
1008
+ reason: `reading /proc/${processGroupId}/stat failed (${processProbeError(error)})`,
1009
+ };
984
1010
  }
985
- // Format is "pid (comm) state ..."; comm can contain spaces/parens, so anchor on the last ')'.
986
- return stat[stat.lastIndexOf(')') + 2] === 'Z';
1011
+ const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
1012
+ if (Number(fields[2]) !== processGroupId) return { state: 'missing' };
1013
+ return { state: fields[0] === 'Z' ? 'zombie' : 'alive' };
987
1014
  }
988
1015
 
989
- function processGroupIsAlive(processGroupId) {
1016
+ function scanLinuxProcessGroup(processGroupId, readDirectory, readStat) {
1017
+ let processIds;
990
1018
  try {
991
- process.kill(-processGroupId, 0);
1019
+ processIds = readDirectory('/proc');
992
1020
  } catch (error) {
993
- return error.code === 'EPERM';
1021
+ return {
1022
+ isAlive: null,
1023
+ reason: `reading /proc failed (${processProbeError(error)})`,
1024
+ };
1025
+ }
1026
+ for (const processId of processIds) {
1027
+ if (!/^\d+$/.test(processId)) continue;
1028
+ let stat;
1029
+ try {
1030
+ stat = readStat(`/proc/${processId}/stat`, 'utf8');
1031
+ } catch (error) {
1032
+ if (error.code === 'ENOENT' || error.code === 'ESRCH') continue; // already gone
1033
+ // EACCES/EPERM (e.g. hidepid/ProtectProc) can only happen on a pid we don't own — we
1034
+ // spawned our own group's members as this same user, so their stat is always readable —
1035
+ // so it's not one of ours. Anything else is genuinely unknown, not "not ours".
1036
+ if (error.code === 'EACCES' || error.code === 'EPERM') continue;
1037
+ return {
1038
+ isAlive: null,
1039
+ reason: `reading /proc/${processId}/stat failed (${processProbeError(error)})`,
1040
+ };
1041
+ }
1042
+ const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
1043
+ const pgrp = Number(fields[2]);
1044
+ if (!Number.isFinite(pgrp)) {
1045
+ return { isAlive: null, reason: `/proc/${processId}/stat had an unparseable process-group field` };
1046
+ }
1047
+ if (pgrp !== processGroupId) continue;
1048
+ if (fields[0] !== 'Z') return { isAlive: true, reason: `a live member was observed at pid ${processId}` };
994
1049
  }
995
- return !isZombieProcessGroupLeader(processGroupId);
1050
+ return { isAlive: false };
1051
+ }
1052
+
1053
+ function keepProcessGroupAlive(processGroupId, reason, observationTime, warn) {
1054
+ let livenessState = processGroupLivenessStates.get(processGroupId);
1055
+ if (!livenessState) {
1056
+ livenessState = { observedAt: observationTime, reason, warned: false };
1057
+ processGroupLivenessStates.set(processGroupId, livenessState);
1058
+ } else {
1059
+ livenessState.reason = reason;
1060
+ }
1061
+ if (!livenessState.warned && observationTime - livenessState.observedAt >= PROCESS_GROUP_LIVENESS_WARNING_MS) {
1062
+ livenessState.warned = true;
1063
+ warn(
1064
+ `Process group ${processGroupId} termination remains unconfirmed after ${PROCESS_GROUP_LIVENESS_WARNING_MS}ms: ${livenessState.reason}`
1065
+ );
1066
+ }
1067
+ return true;
1068
+ }
1069
+
1070
+ function clearProcessGroupLivenessState(processGroupId) {
1071
+ zombieGroupScanTimes.delete(processGroupId);
1072
+ processGroupLivenessStates.delete(processGroupId);
1073
+ }
1074
+
1075
+ function isProcessGroupAlive(processGroupId, options) {
1076
+ const platform = options?.platform ?? process.platform;
1077
+ const groupExists = options?.processGroupExists ?? processGroupExists;
1078
+ const readDirectory = options?.readDirectory ?? readdirSync;
1079
+ const readStat = options?.readStat ?? readFileSync;
1080
+ const observationTime = options?.now?.() ?? performance.now();
1081
+ const warn = options?.warn ?? ((message) => harperLogger.warn(message));
1082
+ if (!groupExists(processGroupId)) {
1083
+ clearProcessGroupLivenessState(processGroupId);
1084
+ return false;
1085
+ }
1086
+ const leaderState = processGroupLeaderState(processGroupId, platform, readStat);
1087
+ if (leaderState.state === 'alive')
1088
+ return keepProcessGroupAlive(processGroupId, 'the process-group leader is still alive', observationTime, warn);
1089
+ if (leaderState.state === 'unknown')
1090
+ return keepProcessGroupAlive(processGroupId, leaderState.reason, observationTime, warn);
1091
+ const scanTime = observationTime;
1092
+ const lastScan = zombieGroupScanTimes.get(processGroupId);
1093
+ if (lastScan !== undefined && scanTime >= lastScan && scanTime - lastScan < ZOMBIE_GROUP_MEMBER_SCAN_INTERVAL_MS) {
1094
+ const reason = processGroupLivenessStates.get(processGroupId)?.reason ?? 'the previous Linux scan is still current';
1095
+ return keepProcessGroupAlive(processGroupId, reason, observationTime, warn);
1096
+ }
1097
+ zombieGroupScanTimes.set(processGroupId, scanTime);
1098
+ let scanResult = scanLinuxProcessGroup(processGroupId, readDirectory, readStat);
1099
+ if (scanResult.isAlive !== false)
1100
+ return keepProcessGroupAlive(processGroupId, scanResult.reason, observationTime, warn);
1101
+ // A second snapshot catches a member omitted after forking inside the first readdir/stat window.
1102
+ // It narrows rather than closes the race because the second synchronous pass has the same gap.
1103
+ scanResult = scanLinuxProcessGroup(processGroupId, readDirectory, readStat);
1104
+ if (scanResult.isAlive !== false)
1105
+ return keepProcessGroupAlive(processGroupId, scanResult.reason, observationTime, warn);
1106
+ clearProcessGroupLivenessState(processGroupId);
1107
+ return false;
1108
+ }
1109
+
1110
+ function processGroupIsAlive(processGroupId) {
1111
+ return isProcessGroupAlive(processGroupId);
996
1112
  }
997
1113
 
998
1114
  async function waitForProcessGroupExit(processGroupId) {
@@ -1074,6 +1190,7 @@ function addProcessGroup(ownerThreadId, processGroupId) {
1074
1190
  }
1075
1191
 
1076
1192
  function removeProcessGroup(ownerThreadId, processGroupId) {
1193
+ clearProcessGroupLivenessState(processGroupId);
1077
1194
  const processGroups = processGroupsByThread.get(ownerThreadId);
1078
1195
  if (!processGroups) return;
1079
1196
  processGroups.delete(processGroupId);
@@ -1125,12 +1242,48 @@ function unregisterProcessGroup(processGroupId) {
1125
1242
  else parentPort?.postMessage({ type: UNREGISTER_PROCESS_GROUP, processGroupId });
1126
1243
  }
1127
1244
 
1245
+ class ProcessGroupTerminationUnconfirmedError extends Error {
1246
+ code = 'ERR_PROCESS_GROUP_TERMINATION_UNCONFIRMED';
1247
+ constructor(ownerThreadId, timeoutMs) {
1248
+ super(`Thread ${ownerThreadId}'s process-group termination could not be confirmed after ${timeoutMs}ms`);
1249
+ this.name = 'ProcessGroupTerminationUnconfirmedError';
1250
+ }
1251
+ }
1252
+
1253
+ // Bounds awaitProcessGroupTermination for isThreadRunning below. Throws rather than resolving to
1254
+ // "not running": componentPreparationLock's ownerIsAlive/ownerLivenessConfirmed already treat a
1255
+ // throwing isOwnerAlive as "can't confirm — don't steal the claim, don't renew the deadline", so
1256
+ // the lock's own bounded wait is what eventually fails the waiter.
1257
+ async function awaitConfirmedProcessGroupTermination(ownerThreadId) {
1258
+ const abortController = new AbortController();
1259
+ let timedOut = false;
1260
+ const timer = setTimeout(() => {
1261
+ timedOut = true;
1262
+ abortController.abort();
1263
+ }, THREAD_RUNNING_TERMINATION_BACKSTOP_MS);
1264
+ timer.unref?.();
1265
+ try {
1266
+ await Promise.race([
1267
+ awaitProcessGroupTermination(ownerThreadId, abortController.signal),
1268
+ new Promise((resolve) => abortController.signal.addEventListener('abort', resolve, { once: true })),
1269
+ ]);
1270
+ } finally {
1271
+ clearTimeout(timer);
1272
+ }
1273
+ if (timedOut) {
1274
+ harperLogger.warn(
1275
+ `Thread ${ownerThreadId}'s process-group termination could not be confirmed after ${THREAD_RUNNING_TERMINATION_BACKSTOP_MS}ms`
1276
+ );
1277
+ throw new ProcessGroupTerminationUnconfirmedError(ownerThreadId, THREAD_RUNNING_TERMINATION_BACKSTOP_MS);
1278
+ }
1279
+ }
1280
+
1128
1281
  async function isThreadRunning(ownerThreadId, timeoutMs = THREAD_INFO_REQUEST_TIMEOUT_MS) {
1129
1282
  if (ownerThreadId === threadId || ownerThreadId === 0) return true;
1130
1283
  if ((await getThreadInfo(timeoutMs)).some((worker) => worker.threadId === ownerThreadId)) return true;
1131
1284
  // The thread itself is gone, but it may still own process groups whose forced termination is
1132
1285
  // in flight — wait for that to be confirmed before reporting the owner as reclaimable.
1133
- await awaitProcessGroupTermination(ownerThreadId);
1286
+ await awaitConfirmedProcessGroupTermination(ownerThreadId);
1134
1287
  return false;
1135
1288
  }
1136
1289
 
@@ -0,0 +1,79 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Page Not Found — Harper Fabric</title>
6
+ <link rel="icon" type="image/svg+xml" href="/HDBDogOnly.svg" />
7
+ <link rel="icon" type="dynamic-favicon" href="/favicon_purple.png" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
+ <meta name="robots" content="noindex" />
10
+ <!-- Self-contained on purpose: this page is served when a URL matched nothing, so it must not
11
+ depend on a hashed bundle, a stylesheet, or a font that might itself be the thing missing. -->
12
+ <style>
13
+ * {
14
+ margin: 0;
15
+ padding: 0;
16
+ }
17
+
18
+ body {
19
+ align-items: center;
20
+ background: linear-gradient(45deg, #312556, #403b8a, #7a3a87);
21
+ box-sizing: border-box;
22
+ color: #efefef;
23
+ display: flex;
24
+ font-family: helvetica, sans-serif;
25
+ justify-content: center;
26
+ line-height: 1.5;
27
+ min-height: 100vh;
28
+ padding: 2rem 1.5rem;
29
+ }
30
+
31
+ main {
32
+ max-width: 30rem;
33
+ text-align: center;
34
+ }
35
+
36
+ img {
37
+ height: 84px;
38
+ width: 84px;
39
+ }
40
+
41
+ h1 {
42
+ font-size: 1.75rem;
43
+ font-weight: 400;
44
+ margin: 1.5rem 0 0.75rem;
45
+ }
46
+
47
+ p {
48
+ color: hsl(0, 0%, 85%);
49
+ }
50
+
51
+ a {
52
+ background-color: hsla(0, 0%, 0%, 0.35);
53
+ border-radius: 0.375rem;
54
+ color: #fff;
55
+ display: inline-block;
56
+ margin-top: 1.75rem;
57
+ padding: 0.625rem 1.25rem;
58
+ text-decoration: none;
59
+ }
60
+
61
+ a:hover,
62
+ a:focus-visible {
63
+ background-color: hsla(0, 0%, 0%, 0.6);
64
+ }
65
+ </style>
66
+ </head>
67
+
68
+ <body>
69
+ <main>
70
+ <img src="/HDBDogOnly.svg" alt="" width="84" height="84" />
71
+ <h1>Page not found</h1>
72
+ <p>
73
+ That address isn't part of Harper Fabric. If you followed a link from
74
+ outside Studio, it may have pointed somewhere that has since moved.
75
+ </p>
76
+ <a href="/">Go to Harper Fabric</a>
77
+ </main>
78
+ </body>
79
+ </html>