@harperfast/harper 5.2.13 → 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.
- package/components/Application.ts +7 -7
- package/components/componentLoader.ts +70 -33
- package/dist/components/Application.js +1 -7
- package/dist/components/Application.js.map +1 -1
- package/dist/components/componentLoader.d.ts +6 -0
- package/dist/components/componentLoader.js +60 -31
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.d.ts +4 -2
- package/dist/resources/RocksTransactionLogStore.js +12 -6
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.js +40 -4
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/replayLogs.js +25 -21
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/transactionBroadcast.js +4 -6
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/certificateVerification/index.js +40 -11
- package/dist/security/certificateVerification/index.js.map +1 -1
- package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
- package/dist/security/certificateVerification/trustedIssuers.js +79 -0
- package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
- package/dist/security/certificateVerification/types.d.ts +1 -0
- package/dist/security/keys.js +4 -0
- package/dist/security/keys.js.map +1 -1
- package/dist/server/loadRootComponents.js +4 -2
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +1 -0
- package/dist/server/threads/manageThreads.js +175 -24
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/resources/DESIGN.md +5 -0
- package/resources/RocksTransactionLogStore.ts +12 -6
- package/resources/Table.ts +39 -4
- package/resources/replayLogs.ts +25 -22
- package/resources/transactionBroadcast.ts +4 -4
- package/security/certificateVerification/index.ts +54 -13
- package/security/certificateVerification/trustedIssuers.ts +76 -0
- package/security/certificateVerification/types.ts +1 -0
- package/security/keys.ts +3 -0
- package/server/DESIGN.md +14 -13
- package/server/loadRootComponents.js +9 -2
- package/server/threads/manageThreads.js +176 -23
- package/studio/web/404.html +79 -0
- package/studio/web/assets/{Chat-4RrB5134.js → Chat-C97PMlIn.js} +274 -247
- package/studio/web/assets/FloatingChat-BUlsFGMV.js +23 -0
- package/studio/web/assets/_basePickBy-QJXUw_pr.js +1 -0
- package/studio/web/assets/_baseUniq-DR4vVjyC.js +1 -0
- package/studio/web/assets/{abnfDiagram-VCTEODGH-B0BebmD2.js → abnfDiagram-O67JEVCF-DpPZWMOf.js} +1 -1
- package/studio/web/assets/{alertDialog-CQyAJJhl.js → alertDialog-UrbiMgQt.js} +1 -1
- package/studio/web/assets/{apiToken-Bke3wvfZ.js → apiToken-CPhbc_zN.js} +1 -1
- package/studio/web/assets/applications-D3Z6BVDl.js +296 -0
- package/studio/web/assets/architecture-7GRP2DOG-11l4Ny1l.js +1 -0
- package/studio/web/assets/architectureDiagram-NJMV4G6O-OmgLraID.js +36 -0
- package/studio/web/assets/authStore-5jSS5pTW.js +3 -0
- package/studio/web/assets/{blockDiagram-I7D4REHJ-BqguiadH.js → blockDiagram-BEXU5L5S-Cv7CD3Ai.js} +15 -4
- package/studio/web/assets/{button-BIsUKRZq.js → button-MFP9n8Ck.js} +1 -1
- package/studio/web/assets/c4Diagram-YGBWAQC7-Ds1vKYnN.js +38 -0
- package/studio/web/assets/channel-BDtouPd6.js +1 -0
- package/studio/web/assets/chevron-up-CFe8x5Bp.js +1 -0
- package/studio/web/assets/chunk-3FUC2YCW-DpnBPH8B.js +2 -0
- package/studio/web/assets/{chunk-GVQU2GXP-sbwVIQ8i.js → chunk-5DYCD2WN-LLLCQSzg.js} +1 -1
- package/studio/web/assets/{chunk-PWAF6VOD-1z1THyS5.js → chunk-742MDFTN-BL8J_pIO.js} +1 -1
- package/studio/web/assets/chunk-7INBJB4K-C-hJHiiK.js +62 -0
- package/studio/web/assets/chunk-7M6MHVWA-C6ZqkFbA.js +213 -0
- package/studio/web/assets/chunk-7PRAP22T-CUXM8BG6.js +1 -0
- package/studio/web/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
- package/studio/web/assets/{chunk-FOHPRMQF-DHwB1DNv.js → chunk-FOHPRMQF-DzxwRta7.js} +9 -9
- package/studio/web/assets/{chunk-SVP7TREG-jtdAHw0S.js → chunk-GTNCS2PH-DJ2BFvS1.js} +1 -1
- package/studio/web/assets/{chunk-F27PBJKO-BVA5EPhV.js → chunk-GWA4HPMP-C42bSqLL.js} +1 -1
- package/studio/web/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
- package/studio/web/assets/chunk-MBY4JIJT-C-5jz6Lo.js +72 -0
- package/studio/web/assets/chunk-NETBCI7D-D-bzlN4r.js +1 -0
- package/studio/web/assets/chunk-O7XYJQB3-ep14zA23.js +128 -0
- package/studio/web/assets/chunk-UA2S7LBM-BMRWRHED.js +1 -0
- package/studio/web/assets/{chunk-POPQ4Y6H-ClWhhkwW.js → chunk-WEXAMYUT-DdLYVdX1.js} +1 -1
- package/studio/web/assets/{chunk-XXDRQBXY-BoGZu3HH.js → chunk-XXDRQBXY-CbPh2l8S.js} +1 -1
- package/studio/web/assets/{chunk-OSK3NFVY-ByciRftO.js → chunk-Z7XXMR3K-BmKEDjcP.js} +3 -3
- package/studio/web/assets/{chunk-75Z2AOVW-QGQD6th2.js → chunk-ZIGJFQKS-DLZgdNxA.js} +2 -2
- package/studio/web/assets/classDiagram-v2-NBCMYWYE-DOI5vgKT.js +217 -0
- package/studio/web/assets/cose-bilkent-JH36ORCC-BSOszL11.js +1 -0
- package/studio/web/assets/createLucideIcon-bziYxJli.js +1 -0
- package/studio/web/assets/{css.worker-BD9HbRGW.js → css.worker-l1psR_wG.js} +13 -10
- package/studio/web/assets/{cssMode-C1vRa7zh.js → cssMode-DyLZPMjo.js} +1 -1
- package/studio/web/assets/cynefin-OW5HDTMX-BSW-DB7S.js +1 -0
- package/studio/web/assets/cynefinDiagram-VND7K2PF-CbAHEwxt.js +62 -0
- package/studio/web/assets/cytoscape.esm-BZkf-uXn.js +321 -0
- package/studio/web/assets/dagre-6A5THRUB-Csxewgs4.js +4 -0
- package/studio/web/assets/diagram-22UHCM2B-a0rt8tY4.js +200 -0
- package/studio/web/assets/{diagram-VX7I27RA-DccVJet6.js → diagram-3UASUU5V-lEj2SVNh.js} +2 -2
- package/studio/web/assets/{diagram-VSXAHHWV-fhEdmkwM.js → diagram-ATOU4E4O-HcrAeB-q.js} +3 -3
- package/studio/web/assets/{diagram-S7CK7UJ4-aoCVTtcy.js → diagram-CDSNMT55-CYxiuyEx.js} +3 -3
- package/studio/web/assets/{diagram-Z3DM3KII-D-RyJJb7.js → diagram-MLGK6HIB-ChbCJTgJ.js} +1 -1
- package/studio/web/assets/diagram-MPIPVDR6-DaX9Y-N3.js +41 -0
- package/studio/web/assets/dialog-DTepachC.js +1 -0
- package/studio/web/assets/{dist-kYfyPZP9.js → dist-BRuBKNNy.js} +1 -1
- package/studio/web/assets/{dist-DP8UjMB_.js → dist-Cz2Y6-6G.js} +2 -2
- package/studio/web/assets/download-CtostjYn.js +1 -0
- package/studio/web/assets/{ebnfDiagram-PWID7BFC-DJGpIpz_.js → ebnfDiagram-ZINNZB2B-CwAsENB1.js} +1 -1
- package/studio/web/assets/{editor-3XRWEDWX.js → editor-CFQ5kbiT.js} +1 -1
- package/studio/web/assets/editor.worker-CkkvdXvO.js +33 -0
- package/studio/web/assets/elk-276RUBZZ-Hexg9E4D.js +27 -0
- package/studio/web/assets/{erDiagram-RLTQ6QDP-CIfNlgkC.js → erDiagram-OPXOYQCR-CeC-wwO1.js} +15 -15
- package/studio/web/assets/eventmodeling-NTZA5JFV-CFb03e0o.js +1 -0
- package/studio/web/assets/flowDiagram-KWPJA3E3-Cidi00_Y.js +1 -0
- package/studio/web/assets/{ganttDiagram-EL5Y4UJY-BxToTzzD.js → ganttDiagram-FUAMR5RP-C2_Hhl1-.js} +2 -2
- package/studio/web/assets/{getAnalytics-GHK8ORfM.js → getAnalytics-CjthMQqF.js} +1 -1
- package/studio/web/assets/gitGraph-4MIJSDKK-xIk5Vuew.js +1 -0
- package/studio/web/assets/{gitGraphDiagram-WWUBYQGX-Dwntd4-x.js → gitGraphDiagram-X574FWY7-CMxJ34up.js} +4 -4
- package/studio/web/assets/graphlib-C9Tl7QrK.js +1 -0
- package/studio/web/assets/{html-CTY5tdMr.js → html-DdMBJEGu.js} +1 -1
- package/studio/web/assets/{html.worker-DsMmVtuG.js → html.worker-DdwM4IrA.js} +12 -9
- package/studio/web/assets/{htmlMode-CG1vSD9t.js → htmlMode-DNtBx3OV.js} +1 -1
- package/studio/web/assets/index-CPoDzQTe.css +1 -0
- package/studio/web/assets/index-zZCxAIEb.js +824 -0
- package/studio/web/assets/index.lazy-CKbsHbSN.js +2 -0
- package/studio/web/assets/info-A6RAGUB7-o49TEbj-.js +1 -0
- package/studio/web/assets/infoDiagram-VRGFBTTK-Cjhh7yzZ.js +2 -0
- package/studio/web/assets/{ishikawaDiagram-5VMMS53U-D9Xh2r6X.js → ishikawaDiagram-OU5B5YK6-C63aXRmh.js} +2 -2
- package/studio/web/assets/{javascript-CJeJzGnI.js → javascript-Dy6KbeHT.js} +1 -1
- package/studio/web/assets/{journeyDiagram-3NMN7TZE-CokIi6ll.js → journeyDiagram-ZHPQQLJL-D_o0P5Mu.js} +3 -3
- package/studio/web/assets/json.worker-CSi2hF-a.js +65 -0
- package/studio/web/assets/{jsonMode-CYPBwM82.js → jsonMode-BiuDVrzl.js} +1 -1
- package/studio/web/assets/{kanban-definition-UXKFOSKX-CukSFJfX.js → kanban-definition-PNTS6WVX-CTI9SSxY.js} +7 -7
- package/studio/web/assets/{katex-CXMH3UgJ.js → katex-ZlcWpGUi.js} +2 -2
- package/studio/web/assets/languageServices-Dk8_6B25.js +1 -0
- package/studio/web/assets/linear-Ds1AeO4V.js +1 -0
- package/studio/web/assets/{lspLanguageFeatures-BC8_gxKG.js → lspLanguageFeatures-DwJKXzQa.js} +2 -2
- package/studio/web/assets/{mermaid-parser.core-BlEsOWNO.js → mermaid-parser.core-C2lcH_jP.js} +3 -3
- package/studio/web/assets/mermaid.core-BG5bjfIG.js +44 -0
- package/studio/web/assets/{mindmap-definition-YA3MSWOX-IprMc_0j.js → mindmap-definition-NLK3R4M7-BK7ho6JD.js} +24 -24
- package/studio/web/assets/{notifications-DFbArTfC.js → notifications-DJqAtWxD.js} +1 -1
- package/studio/web/assets/{notifications-BfKBpYcq.js → notifications-DsDtb41c.js} +1 -1
- package/studio/web/assets/packet-AYTQ26CC-BHPy6KBV.js +1 -0
- package/studio/web/assets/{pegDiagram-XKGWAZYB-BNuPDLZY.js → pegDiagram-GJSIUBJH-DzjwG3jO.js} +1 -1
- package/studio/web/assets/pie-WAS4IAKB-D0cP-DTU.js +1 -0
- package/studio/web/assets/{pieDiagram-E7YTZNPT-DqNb6Ht2.js → pieDiagram-5QR66LMP-C_hOP9Lv.js} +2 -2
- package/studio/web/assets/profile-zTGUN51x.js +1 -0
- package/studio/web/assets/{quadrantDiagram-AXDQQJYC-BGH9E2YR.js → quadrantDiagram-O4NWA36T-CZW8PbV1.js} +3 -3
- package/studio/web/assets/radar-RG4KPBEZ-C8fk0U81.js +1 -0
- package/studio/web/assets/railroad-74A4TZTK-B60FWm17.js +1 -0
- package/studio/web/assets/railroad-abnf-HS5TGJTU-CLMVZvH0.js +1 -0
- package/studio/web/assets/railroad-ebnf-LZEXJU2U--tlwFoVB.js +1 -0
- package/studio/web/assets/railroad-peg-WCYAUIDC-Dpuslblm.js +1 -0
- package/studio/web/assets/{railroadDiagram-O6MQD6OU-DGTPh2KZ.js → railroadDiagram-XR7U4H2S-DyyNmgbU.js} +1 -1
- package/studio/web/assets/regions-SOtABPB9.js +1 -0
- package/studio/web/assets/{register-1ZZuMsiA.js → register-7JezVakc.js} +39 -39
- package/studio/web/assets/{requirementDiagram-BXWQKSXE-BJnO6uLz.js → requirementDiagram-PLB6GJNP-C4H-mLKu.js} +11 -11
- package/studio/web/assets/{rolldown-runtime-hePW80VL.js → rolldown-runtime-Dd_uD5pT.js} +1 -1
- package/studio/web/assets/sankeyDiagram-IPEJSGJF-DHDyga4c.js +40 -0
- package/studio/web/assets/sequenceDiagram-PO4LG4MO-CdNcHXRI.js +169 -0
- package/studio/web/assets/setComponentFile-BavlhfY0.js +1 -0
- package/studio/web/assets/{setup-DWprJyJy.js → setup-04yVzr2I.js} +2 -2
- package/studio/web/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
- package/studio/web/assets/{src-CMc7m_Ut.js → src-D0vMJQRr.js} +1 -1
- package/studio/web/assets/stateDiagram-v2-GCMORJYK-CpKuXq15.js +291 -0
- package/studio/web/assets/status-g_mNYE7j.js +61 -0
- package/studio/web/assets/swimlanes-2SLR337P-NRHa2D8I.js +1 -0
- package/studio/web/assets/swimlanesDiagram-TC7HE7FX-BBk4t4hO.js +8 -0
- package/studio/web/assets/{tabs-BrHu7gJi.js → tabs-CnQ_zenf.js} +1 -1
- package/studio/web/assets/{time-Cclkb7kQ.js → time-Ci1v4fvT.js} +1 -1
- package/studio/web/assets/timeline-definition-EJHVYXUP-D39Y-xpq.js +120 -0
- package/studio/web/assets/toggleHighContrast-i2FjCrzO.js +49 -0
- package/studio/web/assets/treeView-Q6P3EWNA-CRWR9OqD.js +1 -0
- package/studio/web/assets/treemap-WGGIJYW6-ZfXyJgLO.js +1 -0
- package/studio/web/assets/{ts.worker-DpaaFKhy.js → ts.worker-DDMUrpqh.js} +118 -115
- package/studio/web/assets/{tsMode-pjgytARx.js → tsMode-DGeWfx0W.js} +5 -5
- package/studio/web/assets/{typescript-Cdg0mqUh.js → typescript-62_1FUbs.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-RZhaY8Rn.js → useEntityRestURL-DSVwev9J.js} +1 -1
- package/studio/web/assets/useLocalStorage-BYoZF60G.js +1 -0
- package/studio/web/assets/usecaseDiagram-POWQR4AR-jf-zIDZ_.js +328 -0
- package/studio/web/assets/vendor-core-BdkdbUek.js +63 -0
- package/studio/web/assets/vendor-datadog-fCQhBHGk.js +6 -0
- package/studio/web/assets/vendor-react-izWchTRL.js +9 -0
- package/studio/web/assets/vendor-tanstack-CQ1aRjVa.js +2 -0
- package/studio/web/assets/vendor-ui-aE1tHcW7.js +41 -0
- package/studio/web/assets/vennDiagram-UO4OBE2U-BKYAZUh_.js +34 -0
- package/studio/web/assets/wardley-WFR3VGLG-BO7f36ci.js +1 -0
- package/studio/web/assets/{wardleyDiagram-VM6X3IG4-CjrkKWUR.js → wardleyDiagram-VNRHLVJA-Djb0JDQc.js} +3 -3
- package/studio/web/assets/{workers-BgoXIqQe.js → workers-Cs52GyHv.js} +1 -1
- package/studio/web/assets/x-vYntCxm4.js +1 -0
- package/studio/web/assets/{xml-BwSeDMiP.js → xml-Co5ow_Ix.js} +1 -1
- package/studio/web/assets/{xychartDiagram-S5SC5T6Z-Biok4GYV.js → xychartDiagram-PMCCYNJV-BQh9ubvb.js} +3 -3
- package/studio/web/assets/{yaml-DotCUG5l.js → yaml-BcngpXye.js} +1 -1
- package/studio/web/assets/yaml.worker-DAK2o_H1.js +479 -0
- package/studio/web/auth/fabric-globe-dark.png +0 -0
- package/studio/web/auth/fabric-globe-light.png +0 -0
- package/studio/web/auth/fabric-platform-dark.png +0 -0
- package/studio/web/auth/fabric-platform-light.png +0 -0
- package/studio/web/index.html +15 -15
- package/studio/web/assets/FloatingChat-omlNMDcJ.js +0 -23
- package/studio/web/assets/applications-DvFDYJgK.js +0 -296
- package/studio/web/assets/architecture-7GRP2DOG-LB-MLAAb.js +0 -1
- package/studio/web/assets/architectureDiagram-5GKGNRK7-7SW3GD-K.js +0 -36
- package/studio/web/assets/authStore-C3Nfubqr.js +0 -3
- package/studio/web/assets/c4Diagram-7LVT6UL2-LBNf8t_X.js +0 -38
- package/studio/web/assets/channel-yictG-U-.js +0 -1
- package/studio/web/assets/chevron-up-DtKGqDn3.js +0 -1
- package/studio/web/assets/chunk-4HAMMTFA-DWtTut21.js +0 -62
- package/studio/web/assets/chunk-DU6HZSFF-Chq20Ba5.js +0 -127
- package/studio/web/assets/chunk-GMAD6QVW-BeS7S07A.js +0 -72
- package/studio/web/assets/chunk-IMKFNOWR-Bnh3tAVd.js +0 -231
- package/studio/web/assets/chunk-L3NEJ4N5-COfUyKII.js +0 -1
- package/studio/web/assets/chunk-P2QGCYS3-CP1VhG_c.js +0 -1
- package/studio/web/assets/chunk-SHT3W25Y-LpQkMsah.js +0 -168
- package/studio/web/assets/chunk-TICWLB2K-VOwzetX-.js +0 -206
- package/studio/web/assets/classDiagram-ZZMXUADV-VaEwSy_g.js +0 -1
- package/studio/web/assets/classDiagram-v2-VYDZK3BY-VaEwSy_g.js +0 -1
- package/studio/web/assets/cose-bilkent-JH36ORCC-Cuuf67Bh.js +0 -1
- package/studio/web/assets/createLucideIcon-CzW9508A.js +0 -1
- package/studio/web/assets/cynefin-OW5HDTMX-BbdbCvub.js +0 -1
- package/studio/web/assets/cynefinDiagram-5FMLGOSQ-TP-aIqbt.js +0 -62
- package/studio/web/assets/cytoscape.esm-C2x7inlt.js +0 -321
- package/studio/web/assets/dagre-GXQ25YYZ-DShnGpGo.js +0 -4
- package/studio/web/assets/dagre-PrKaheQc.js +0 -1
- package/studio/web/assets/diagram-UQ7AKVKN-DglXtQ6x.js +0 -41
- package/studio/web/assets/dialog-Cn2uWgD4.js +0 -1
- package/studio/web/assets/download-B5T5r7ss.js +0 -1
- package/studio/web/assets/editor.worker-DwcOiOeP.js +0 -30
- package/studio/web/assets/eventmodeling-NTZA5JFV-CLxnp2CR.js +0 -1
- package/studio/web/assets/flowDiagram-HODETNUW-BIbhmz9f.js +0 -1
- package/studio/web/assets/gitGraph-4MIJSDKK-D2s2w8lE.js +0 -1
- package/studio/web/assets/graphlib-DS17s2tU.js +0 -1
- package/studio/web/assets/index-7RMEgVG1.css +0 -1
- package/studio/web/assets/index-Dfpeofdu.js +0 -824
- package/studio/web/assets/index.lazy-B1VOIv-t.js +0 -2
- package/studio/web/assets/info-A6RAGUB7-DYjkvb0C.js +0 -1
- package/studio/web/assets/infoDiagram-27XIBGKW-Bnp1FJE5.js +0 -2
- package/studio/web/assets/json.worker-fO_hOLCP.js +0 -62
- package/studio/web/assets/languageServices-IKH4GUHl.js +0 -1
- package/studio/web/assets/linear-Cnt8506Q.js +0 -1
- package/studio/web/assets/mermaid.core-BlkGaMIH.js +0 -44
- package/studio/web/assets/packet-AYTQ26CC-Bi3V04Zi.js +0 -1
- package/studio/web/assets/pie-WAS4IAKB-_6DoDbng.js +0 -1
- package/studio/web/assets/profile-Q4-T6c-S.js +0 -1
- package/studio/web/assets/radar-RG4KPBEZ-DDdVczcL.js +0 -1
- package/studio/web/assets/railroad-74A4TZTK-BJUP4Jds.js +0 -1
- package/studio/web/assets/railroad-abnf-HS5TGJTU-Bm3L1L0h.js +0 -1
- package/studio/web/assets/railroad-ebnf-LZEXJU2U-CKzLGlkw.js +0 -1
- package/studio/web/assets/railroad-peg-WCYAUIDC-S8xLjslx.js +0 -1
- package/studio/web/assets/regions-CtkV0xje.js +0 -1
- package/studio/web/assets/sankeyDiagram-P5KCCOFB-0vSOdymH.js +0 -40
- package/studio/web/assets/sequenceDiagram-WJ2MYXX4-hETizDWE.js +0 -162
- package/studio/web/assets/setComponentFile-ZWLcWv5X.js +0 -1
- package/studio/web/assets/sizeCapture-INFHLROL-B0uUizjq.js +0 -1
- package/studio/web/assets/stateDiagram-D77RDMKH-CdYQ_KtC.js +0 -1
- package/studio/web/assets/stateDiagram-v2-MP3YSRHH-CdKuzQMT.js +0 -1
- package/studio/web/assets/status--aNm8isn.js +0 -61
- package/studio/web/assets/swimlanes-42K2YHIH-B8cHIpU4.js +0 -1
- package/studio/web/assets/swimlanesDiagram-VR7AAH4N-DmOSJwaH.js +0 -8
- package/studio/web/assets/timeline-definition-24CTP7MA-BJWYSXqF.js +0 -120
- package/studio/web/assets/toggleHighContrast-Cq_lt3XD.js +0 -49
- package/studio/web/assets/treeView-Q6P3EWNA-qxe_v6CQ.js +0 -1
- package/studio/web/assets/treemap-WGGIJYW6-dDo97XXF.js +0 -1
- package/studio/web/assets/useLocalStorage-BqMR3D8_.js +0 -1
- package/studio/web/assets/vendor-core-c2JRRJpV.js +0 -58
- package/studio/web/assets/vendor-datadog-CLUcJXOo.js +0 -6
- package/studio/web/assets/vendor-react-CJV_K1u4.js +0 -9
- package/studio/web/assets/vendor-tanstack-DxzraizX.js +0 -1
- package/studio/web/assets/vendor-ui-BUjK0h8a.js +0 -41
- package/studio/web/assets/vennDiagram-4TSXK5OY-A3i-lCdl.js +0 -34
- package/studio/web/assets/wardley-WFR3VGLG-B0ik-_6g.js +0 -1
- package/studio/web/assets/x-DIzaLEdK.js +0 -1
- 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
|
+
}
|
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
|
|
176
|
-
|
|
|
177
|
-
| Where do I register a new HTTP handler?
|
|
178
|
-
| Where do I register a WebSocket handler?
|
|
179
|
-
| How does `before`/`after` middleware ordering work?
|
|
180
|
-
| Where does PROXY protocol get parsed?
|
|
181
|
-
| How does an app read forwarded TLS facts (JA3/JA4, ALPN, SNI, mTLS cert)?
|
|
182
|
-
|
|
|
183
|
-
| Where is the
|
|
184
|
-
|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
|
|
|
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 {
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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 =
|
|
982
|
-
} catch {
|
|
983
|
-
return
|
|
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
|
-
|
|
986
|
-
|
|
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
|
|
1016
|
+
function scanLinuxProcessGroup(processGroupId, readDirectory, readStat) {
|
|
1017
|
+
let processIds;
|
|
990
1018
|
try {
|
|
991
|
-
|
|
1019
|
+
processIds = readDirectory('/proc');
|
|
992
1020
|
} catch (error) {
|
|
993
|
-
return
|
|
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
|
|
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
|
|
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>
|