@harperfast/harper 5.2.14 → 5.2.15
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/dist/resources/analytics/write.d.ts +3 -0
- package/dist/resources/analytics/write.js +39 -13
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/security/keys.js +258 -66
- package/dist/security/keys.js.map +1 -1
- package/dist/server/threads/threadServer.d.ts +3 -0
- package/dist/server/threads/threadServer.js +24 -6
- package/dist/server/threads/threadServer.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/resources/analytics/write.ts +40 -14
- package/security/keys.ts +251 -66
- package/server/threads/threadServer.js +24 -6
- package/studio/web/assets/{Chat-C97PMlIn.js → Chat-CeEDA28S.js} +13 -13
- package/studio/web/assets/FloatingChat-J_P5KaR4.js +23 -0
- package/studio/web/assets/SearchDialog-BWMGseDd.js +1 -0
- package/studio/web/assets/{abnfDiagram-O67JEVCF-DpPZWMOf.js → abnfDiagram-O67JEVCF-raXE027J.js} +1 -1
- package/studio/web/assets/alertDialog-BbWD9Xxa.js +1 -0
- package/studio/web/assets/apiToken-BKot-xR5.js +1 -0
- package/studio/web/assets/applications-BlJChLsS.js +296 -0
- package/studio/web/assets/architecture-7GRP2DOG-DDmz3hpI.js +1 -0
- package/studio/web/assets/{architectureDiagram-NJMV4G6O-OmgLraID.js → architectureDiagram-NJMV4G6O-C8_Eet7w.js} +1 -1
- package/studio/web/assets/arrow-up-D2g_57vT.js +1 -0
- package/studio/web/assets/authStore-CJCaADw9.js +3 -0
- package/studio/web/assets/{blockDiagram-BEXU5L5S-Cv7CD3Ai.js → blockDiagram-BEXU5L5S-DR6S5-1D.js} +1 -1
- package/studio/web/assets/{button-MFP9n8Ck.js → button-DWwfgGWI.js} +2 -2
- package/studio/web/assets/{c4Diagram-YGBWAQC7-Ds1vKYnN.js → c4Diagram-YGBWAQC7-BAfZ1G01.js} +1 -1
- package/studio/web/assets/channel-D0AkPjJ8.js +1 -0
- package/studio/web/assets/{chevron-up-CFe8x5Bp.js → chevron-up-CFxbMGv2.js} +1 -1
- package/studio/web/assets/{chunk-3FUC2YCW-DpnBPH8B.js → chunk-3FUC2YCW-DuRp-j19.js} +1 -1
- package/studio/web/assets/{chunk-5DYCD2WN-LLLCQSzg.js → chunk-5DYCD2WN-BceBfIHT.js} +1 -1
- package/studio/web/assets/{chunk-742MDFTN-BL8J_pIO.js → chunk-742MDFTN-CIkt8ijt.js} +1 -1
- package/studio/web/assets/{chunk-7INBJB4K-C-hJHiiK.js → chunk-7INBJB4K-BpeAXyOi.js} +1 -1
- package/studio/web/assets/{chunk-7M6MHVWA-C6ZqkFbA.js → chunk-7M6MHVWA-h6WlaTzO.js} +1 -1
- package/studio/web/assets/{chunk-7PRAP22T-CUXM8BG6.js → chunk-7PRAP22T-KyqX6x3f.js} +1 -1
- package/studio/web/assets/{chunk-GTNCS2PH-DJ2BFvS1.js → chunk-GTNCS2PH-DGHLXyow.js} +1 -1
- package/studio/web/assets/{chunk-GWA4HPMP-C42bSqLL.js → chunk-GWA4HPMP-DsejB6gA.js} +1 -1
- package/studio/web/assets/{chunk-MBY4JIJT-C-5jz6Lo.js → chunk-MBY4JIJT-BwSGweQP.js} +1 -1
- package/studio/web/assets/{chunk-NETBCI7D-D-bzlN4r.js → chunk-NETBCI7D-C8UnOw_f.js} +1 -1
- package/studio/web/assets/{chunk-O7XYJQB3-ep14zA23.js → chunk-O7XYJQB3-CBS3nZYV.js} +1 -1
- package/studio/web/assets/{chunk-UA2S7LBM-BMRWRHED.js → chunk-UA2S7LBM-BHpEdH8D.js} +1 -1
- package/studio/web/assets/{chunk-WEXAMYUT-DdLYVdX1.js → chunk-WEXAMYUT-BAIhTwyh.js} +1 -1
- package/studio/web/assets/{chunk-Z7XXMR3K-BmKEDjcP.js → chunk-Z7XXMR3K-DSHbImdf.js} +1 -1
- package/studio/web/assets/{chunk-ZIGJFQKS-DLZgdNxA.js → chunk-ZIGJFQKS-Cs8fUn7y.js} +1 -1
- package/studio/web/assets/{classDiagram-v2-NBCMYWYE-DOI5vgKT.js → classDiagram-v2-NBCMYWYE-DmDPkt2u.js} +1 -1
- package/studio/web/assets/cn-6T8VranJ.js +1 -0
- package/studio/web/assets/{codicon-Brq4_Ui5.ttf → codicon-DF1abBS2.ttf} +0 -0
- package/studio/web/assets/{createLucideIcon-bziYxJli.js → createLucideIcon-fO5dL7D5.js} +1 -1
- package/studio/web/assets/{css.worker-l1psR_wG.js → css.worker-DoT-OOzx.js} +36 -36
- package/studio/web/assets/{cssMode-DyLZPMjo.js → cssMode-DjeA9Hx_.js} +1 -1
- package/studio/web/assets/{cynefin-OW5HDTMX-BSW-DB7S.js → cynefin-OW5HDTMX-DIvbXLSj.js} +1 -1
- package/studio/web/assets/{cynefinDiagram-VND7K2PF-CbAHEwxt.js → cynefinDiagram-VND7K2PF-BdtaXiCE.js} +1 -1
- package/studio/web/assets/{dagre-6A5THRUB-Csxewgs4.js → dagre-6A5THRUB-C7RUWHyz.js} +1 -1
- package/studio/web/assets/{diagram-22UHCM2B-a0rt8tY4.js → diagram-22UHCM2B-DJCvcaWP.js} +1 -1
- package/studio/web/assets/{diagram-3UASUU5V-lEj2SVNh.js → diagram-3UASUU5V-Du6VKWln.js} +1 -1
- package/studio/web/assets/{diagram-ATOU4E4O-HcrAeB-q.js → diagram-ATOU4E4O-dxRfeG4B.js} +1 -1
- package/studio/web/assets/{diagram-CDSNMT55-CYxiuyEx.js → diagram-CDSNMT55-l5Ga0u-W.js} +1 -1
- package/studio/web/assets/{diagram-MLGK6HIB-ChbCJTgJ.js → diagram-MLGK6HIB-CNQY47LA.js} +1 -1
- package/studio/web/assets/{diagram-MPIPVDR6-DaX9Y-N3.js → diagram-MPIPVDR6-7prveD1B.js} +1 -1
- package/studio/web/assets/{dist-Cz2Y6-6G.js → dist-BrRuMUzt.js} +1 -1
- package/studio/web/assets/{download-CtostjYn.js → download-Cn6tPG4L.js} +1 -1
- package/studio/web/assets/{ebnfDiagram-ZINNZB2B-CwAsENB1.js → ebnfDiagram-ZINNZB2B-5OnkTpfi.js} +1 -1
- package/studio/web/assets/{editor-CFQ5kbiT.js → editor-DKwRNgor.js} +1 -1
- package/studio/web/assets/editor.worker-Yd1NR0QT.js +33 -0
- package/studio/web/assets/{elk-276RUBZZ-Hexg9E4D.js → elk-276RUBZZ-DxDQPjQT.js} +1 -1
- package/studio/web/assets/{erDiagram-OPXOYQCR-CeC-wwO1.js → erDiagram-OPXOYQCR-CQlCX84D.js} +1 -1
- package/studio/web/assets/eventmodeling-NTZA5JFV-DONQf5aG.js +1 -0
- package/studio/web/assets/flowDiagram-KWPJA3E3-BoZ8_QR1.js +1 -0
- package/studio/web/assets/{ganttDiagram-FUAMR5RP-C2_Hhl1-.js → ganttDiagram-FUAMR5RP-bloX3Voy.js} +1 -1
- package/studio/web/assets/{getAnalytics-CjthMQqF.js → getAnalytics-C0FHjtdQ.js} +1 -1
- package/studio/web/assets/{gitGraph-4MIJSDKK-xIk5Vuew.js → gitGraph-4MIJSDKK-BrXWmJt2.js} +1 -1
- package/studio/web/assets/{gitGraphDiagram-X574FWY7-CMxJ34up.js → gitGraphDiagram-X574FWY7-C2KnnZeY.js} +1 -1
- package/studio/web/assets/{html-DdMBJEGu.js → html-KzEcNmGL.js} +1 -1
- package/studio/web/assets/{html.worker-DdwM4IrA.js → html.worker-B1dwY0Ay.js} +27 -27
- package/studio/web/assets/{htmlMode-DNtBx3OV.js → htmlMode-Bipij2Sy.js} +1 -1
- package/studio/web/assets/index-DMUQ34Jl.js +828 -0
- package/studio/web/assets/index-QLZw6xrU.css +1 -0
- package/studio/web/assets/index.lazy-D5_uqR4Q.js +2 -0
- package/studio/web/assets/{info-A6RAGUB7-o49TEbj-.js → info-A6RAGUB7-BhzBHe4U.js} +1 -1
- package/studio/web/assets/{infoDiagram-VRGFBTTK-Cjhh7yzZ.js → infoDiagram-VRGFBTTK-B0J1132p.js} +1 -1
- package/studio/web/assets/{ishikawaDiagram-OU5B5YK6-C63aXRmh.js → ishikawaDiagram-OU5B5YK6-BNk0tuQx.js} +1 -1
- package/studio/web/assets/{javascript-Dy6KbeHT.js → javascript-QytBwXUJ.js} +1 -1
- package/studio/web/assets/{journeyDiagram-ZHPQQLJL-D_o0P5Mu.js → journeyDiagram-ZHPQQLJL-Be79RMQM.js} +1 -1
- package/studio/web/assets/json.worker-BQqXGI3X.js +65 -0
- package/studio/web/assets/{jsonMode-BiuDVrzl.js → jsonMode-CIoh4zwS.js} +1 -1
- package/studio/web/assets/{kanban-definition-PNTS6WVX-CTI9SSxY.js → kanban-definition-PNTS6WVX-4uuoQP9W.js} +1 -1
- package/studio/web/assets/{languageServices-Dk8_6B25.js → languageServices-BvFCul4Y.js} +1 -1
- package/studio/web/assets/localStorageKeys-etzFY_xV.js +1 -0
- package/studio/web/assets/{lspLanguageFeatures-DwJKXzQa.js → lspLanguageFeatures-BvDuJHGH.js} +1 -1
- package/studio/web/assets/{mermaid-parser.core-C2lcH_jP.js → mermaid-parser.core-V1UYMeXV.js} +3 -3
- package/studio/web/assets/{mermaid.core-BG5bjfIG.js → mermaid.core-1PX6R49U.js} +4 -4
- package/studio/web/assets/{mindmap-definition-NLK3R4M7-BK7ho6JD.js → mindmap-definition-NLK3R4M7-C70Ctm0J.js} +1 -1
- package/studio/web/assets/notifications-C5EZskVd.js +1 -0
- package/studio/web/assets/notifications-jQfsyiVJ.js +1 -0
- package/studio/web/assets/{packet-AYTQ26CC-BHPy6KBV.js → packet-AYTQ26CC-DWbXHx5g.js} +1 -1
- package/studio/web/assets/{pegDiagram-GJSIUBJH-DzjwG3jO.js → pegDiagram-GJSIUBJH-d6z1cc7W.js} +1 -1
- package/studio/web/assets/{pie-WAS4IAKB-D0cP-DTU.js → pie-WAS4IAKB-DjoTm3yr.js} +1 -1
- package/studio/web/assets/{pieDiagram-5QR66LMP-C_hOP9Lv.js → pieDiagram-5QR66LMP-Dz3RHpDA.js} +1 -1
- package/studio/web/assets/profile-CkLzjQm2.js +1 -0
- package/studio/web/assets/{quadrantDiagram-O4NWA36T-CZW8PbV1.js → quadrantDiagram-O4NWA36T-CIF5Qkou.js} +1 -1
- package/studio/web/assets/{radar-RG4KPBEZ-C8fk0U81.js → radar-RG4KPBEZ-CVBgjCOj.js} +1 -1
- package/studio/web/assets/{railroad-74A4TZTK-B60FWm17.js → railroad-74A4TZTK-BdhXw10Q.js} +1 -1
- package/studio/web/assets/railroad-abnf-HS5TGJTU-BcoQPN39.js +1 -0
- package/studio/web/assets/railroad-ebnf-LZEXJU2U-B1Lke2JN.js +1 -0
- package/studio/web/assets/railroad-peg-WCYAUIDC-D-oJFkiV.js +1 -0
- package/studio/web/assets/{railroadDiagram-XR7U4H2S-DyyNmgbU.js → railroadDiagram-XR7U4H2S-y3Jv2U6N.js} +1 -1
- package/studio/web/assets/regions-b7HODz0P.js +1 -0
- package/studio/web/assets/register-DGmUOJc1.js +909 -0
- package/studio/web/assets/register-DjpsE8to.css +1 -0
- package/studio/web/assets/{requirementDiagram-PLB6GJNP-C4H-mLKu.js → requirementDiagram-PLB6GJNP-ChINZEmu.js} +1 -1
- package/studio/web/assets/restartGate-nHAFLDh-.js +1 -0
- package/studio/web/assets/{sankeyDiagram-IPEJSGJF-DHDyga4c.js → sankeyDiagram-IPEJSGJF-D9AyLce2.js} +1 -1
- package/studio/web/assets/{sequenceDiagram-PO4LG4MO-CdNcHXRI.js → sequenceDiagram-PO4LG4MO-hfk4aIEm.js} +1 -1
- package/studio/web/assets/{setComponentFile-BavlhfY0.js → setComponentFile-DlUcJ4Y0.js} +1 -1
- package/studio/web/assets/{setup-04yVzr2I.js → setup-D1jywSGk.js} +2 -2
- package/studio/web/assets/{stateDiagram-v2-GCMORJYK-CpKuXq15.js → stateDiagram-v2-GCMORJYK-Bs8RIb9K.js} +1 -1
- package/studio/web/assets/status-CjVoPieo.js +61 -0
- package/studio/web/assets/{swimlanes-2SLR337P-NRHa2D8I.js → swimlanes-2SLR337P-B52xrf6t.js} +1 -1
- package/studio/web/assets/swimlanesDiagram-TC7HE7FX-DfN7BAP5.js +8 -0
- package/studio/web/assets/tabs-BCTQzoQK.js +1 -0
- package/studio/web/assets/{timeline-definition-EJHVYXUP-D39Y-xpq.js → timeline-definition-EJHVYXUP-DJSITpQX.js} +1 -1
- package/studio/web/assets/toggleHighContrast-C9CKwOn9.js +51 -0
- package/studio/web/assets/toggleHighContrast-n7clI0si.css +1 -0
- package/studio/web/assets/{treeView-Q6P3EWNA-CRWR9OqD.js → treeView-Q6P3EWNA-C-epdxRo.js} +1 -1
- package/studio/web/assets/{treemap-WGGIJYW6-ZfXyJgLO.js → treemap-WGGIJYW6-DOVyp_zw.js} +1 -1
- package/studio/web/assets/{ts.worker-DDMUrpqh.js → ts.worker-Clm12uY6.js} +131 -131
- package/studio/web/assets/{tsMode-DGeWfx0W.js → tsMode-CRibSU_y.js} +1 -1
- package/studio/web/assets/{typescript-62_1FUbs.js → typescript-BRCpg8By.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-DSVwev9J.js → useEntityRestURL-pPVmpjIi.js} +1 -1
- package/studio/web/assets/{usecaseDiagram-POWQR4AR-jf-zIDZ_.js → usecaseDiagram-POWQR4AR-B6252cbq.js} +1 -1
- package/studio/web/assets/{vendor-core-BdkdbUek.js → vendor-core-6LhfJlRC.js} +1 -1
- package/studio/web/assets/vendor-datadog-ozKf-pts.js +6 -0
- package/studio/web/assets/{vendor-react-izWchTRL.js → vendor-react-BOliS0BL.js} +1 -1
- package/studio/web/assets/vendor-tanstack-Bntfka-M.js +2 -0
- package/studio/web/assets/vendor-ui-9EYYDgoN.js +41 -0
- package/studio/web/assets/{vennDiagram-UO4OBE2U-BKYAZUh_.js → vennDiagram-UO4OBE2U-DZTsDA4k.js} +1 -1
- package/studio/web/assets/{wardley-WFR3VGLG-BO7f36ci.js → wardley-WFR3VGLG-Cfqoe6z4.js} +1 -1
- package/studio/web/assets/{wardleyDiagram-VNRHLVJA-Djb0JDQc.js → wardleyDiagram-VNRHLVJA-qu_f7Tvl.js} +1 -1
- package/studio/web/assets/{workers-Cs52GyHv.js → workers-BBDZZ4qw.js} +3 -3
- package/studio/web/assets/{xml-Co5ow_Ix.js → xml-tWNEwt7n.js} +1 -1
- package/studio/web/assets/{xychartDiagram-PMCCYNJV-BQh9ubvb.js → xychartDiagram-PMCCYNJV-UrOKRsmR.js} +1 -1
- package/studio/web/assets/{yaml-BcngpXye.js → yaml-BZgmBIf5.js} +1 -1
- package/studio/web/assets/{yaml.worker-DAK2o_H1.js → yaml.worker-D9JekGdp.js} +183 -183
- package/studio/web/index.html +15 -15
- package/studio/web/assets/FloatingChat-BUlsFGMV.js +0 -23
- package/studio/web/assets/alertDialog-UrbiMgQt.js +0 -1
- package/studio/web/assets/apiToken-CPhbc_zN.js +0 -1
- package/studio/web/assets/applications-D3Z6BVDl.js +0 -296
- package/studio/web/assets/architecture-7GRP2DOG-11l4Ny1l.js +0 -1
- package/studio/web/assets/authStore-5jSS5pTW.js +0 -3
- package/studio/web/assets/channel-BDtouPd6.js +0 -1
- package/studio/web/assets/dialog-DTepachC.js +0 -1
- package/studio/web/assets/editor.worker-CkkvdXvO.js +0 -33
- package/studio/web/assets/eventmodeling-NTZA5JFV-CFb03e0o.js +0 -1
- package/studio/web/assets/flowDiagram-KWPJA3E3-Cidi00_Y.js +0 -1
- package/studio/web/assets/index-CPoDzQTe.css +0 -1
- package/studio/web/assets/index-zZCxAIEb.js +0 -824
- package/studio/web/assets/index.lazy-CKbsHbSN.js +0 -2
- package/studio/web/assets/json.worker-CSi2hF-a.js +0 -65
- package/studio/web/assets/notifications-DJqAtWxD.js +0 -1
- package/studio/web/assets/notifications-DsDtb41c.js +0 -1
- package/studio/web/assets/profile-zTGUN51x.js +0 -1
- package/studio/web/assets/railroad-abnf-HS5TGJTU-CLMVZvH0.js +0 -1
- package/studio/web/assets/railroad-ebnf-LZEXJU2U--tlwFoVB.js +0 -1
- package/studio/web/assets/railroad-peg-WCYAUIDC-Dpuslblm.js +0 -1
- package/studio/web/assets/regions-SOtABPB9.js +0 -1
- package/studio/web/assets/register-7JezVakc.js +0 -848
- package/studio/web/assets/register-jjEx9u7D.css +0 -1
- package/studio/web/assets/setLocalStorage-D_kflv4U.js +0 -1
- package/studio/web/assets/status-g_mNYE7j.js +0 -61
- package/studio/web/assets/swimlanesDiagram-TC7HE7FX-BBk4t4hO.js +0 -8
- package/studio/web/assets/tabs-CnQ_zenf.js +0 -1
- package/studio/web/assets/toggleHighContrast--ixms01u.css +0 -1
- package/studio/web/assets/toggleHighContrast-i2FjCrzO.js +0 -49
- package/studio/web/assets/useLocalStorage-BYoZF60G.js +0 -1
- package/studio/web/assets/vendor-datadog-fCQhBHGk.js +0 -6
- package/studio/web/assets/vendor-tanstack-CQ1aRjVa.js +0 -2
- package/studio/web/assets/vendor-ui-aE1tHcW7.js +0 -41
- package/studio/web/assets/x-vYntCxm4.js +0 -1
package/dist/security/keys.js
CHANGED
|
@@ -193,6 +193,11 @@ const privateKeys = new Map();
|
|
|
193
193
|
// Debounce window (ms) for rebuilding TLS secure contexts. Shared by the hdb_certificate
|
|
194
194
|
// subscription and the private-key hot-reload trigger so both coalesce on the same cadence.
|
|
195
195
|
const TLS_REBUILD_DEBOUNCE_MS = 1500;
|
|
196
|
+
// Self-retry backoff cap: a permanently bad record must not cost every selector on every
|
|
197
|
+
// thread a table scan + X509 parse per debounce interval, forever.
|
|
198
|
+
const TLS_FAILURE_RETRY_MAX_DELAY_MS = 300_000;
|
|
199
|
+
// While a failure signature is unchanged, repeat occurrences log a summary at most this often.
|
|
200
|
+
const TLS_FAILURE_SUMMARY_INTERVAL_MS = 3_600_000;
|
|
196
201
|
// Debounced rebuild triggers, one per live server TLS selector (registered in createTLSSelector's
|
|
197
202
|
// initialize). When a private key is hot-reloaded on this thread, every live selector re-runs
|
|
198
203
|
// updateTLS so a secure context built with a stale key — or built before this key arrived — is
|
|
@@ -297,7 +302,9 @@ function loadCertificates() {
|
|
|
297
302
|
logger.info?.(`Certificate ${certCn} at ${path} is older (${new Date(fileTimestamp)}) than the certificate in the database (${recordTimestamp > 1 ? new Date(recordTimestamp) : 'only self signed certificate available'})`);
|
|
298
303
|
return;
|
|
299
304
|
}
|
|
300
|
-
|
|
305
|
+
// Returned so loadAndWatch can roll back its mtime latch if the write fails —
|
|
306
|
+
// assigned as well to preserve loadCertificates()'s awaited-return contract.
|
|
307
|
+
return (promise = certificateTable.put({
|
|
301
308
|
name: certCn,
|
|
302
309
|
uses: config.uses ?? (configKey.includes('operations') ? ['operations-api'] : []),
|
|
303
310
|
ciphers: config.ciphers,
|
|
@@ -314,7 +321,7 @@ function loadCertificates() {
|
|
|
314
321
|
valid_from: x509Cert.validFrom,
|
|
315
322
|
valid_to: x509Cert.validTo,
|
|
316
323
|
},
|
|
317
|
-
});
|
|
324
|
+
}));
|
|
318
325
|
}, ca ? 'certificate authority' : 'certificate');
|
|
319
326
|
}
|
|
320
327
|
}
|
|
@@ -365,20 +372,34 @@ const certificateWatchPollers = new Map();
|
|
|
365
372
|
function loadAndWatch(path, loadCert, type) {
|
|
366
373
|
let lastModified;
|
|
367
374
|
const loadFile = (path, stats) => {
|
|
375
|
+
// The latch dedupes chokidar/poll but must mean "last successfully APPLIED", or a failed
|
|
376
|
+
// apply is deduplicated forever (#2382). Rollbacks are equality-guarded so an old failure
|
|
377
|
+
// can't unlatch a newer successful reload.
|
|
378
|
+
const previousModified = lastModified;
|
|
379
|
+
let modified;
|
|
368
380
|
try {
|
|
369
381
|
// chokidar's 'change' event omits stats unless alwaysStat is set (default off in v4), so
|
|
370
382
|
// stat the file here when it's missing — otherwise the inotify fast path would throw and
|
|
371
383
|
// silently never reload, leaving only the periodic poll to catch the change.
|
|
372
|
-
|
|
384
|
+
modified = (stats ?? (0, node_fs_1.statSync)(path)).mtimeMs;
|
|
373
385
|
if (modified && modified !== lastModified) {
|
|
374
386
|
if (lastModified && worker_threads_1.isMainThread)
|
|
375
387
|
logger.warn?.(`Reloading ${type}:`, path);
|
|
376
388
|
lastModified = modified;
|
|
377
|
-
loadCert(readPEM(path));
|
|
389
|
+
const applied = loadCert(readPEM(path));
|
|
390
|
+
if (typeof applied?.then === 'function') {
|
|
391
|
+
applied.catch((error) => {
|
|
392
|
+
logger.error?.(`Error applying ${type}:`, path, error);
|
|
393
|
+
if (lastModified === modified)
|
|
394
|
+
lastModified = previousModified;
|
|
395
|
+
});
|
|
396
|
+
}
|
|
378
397
|
}
|
|
379
398
|
}
|
|
380
399
|
catch (error) {
|
|
381
400
|
logger.error?.(`Error loading ${type}:`, path, error);
|
|
401
|
+
if (modified !== undefined && lastModified === modified)
|
|
402
|
+
lastModified = previousModified;
|
|
382
403
|
}
|
|
383
404
|
};
|
|
384
405
|
if (fs.existsSync(path))
|
|
@@ -962,10 +983,79 @@ function createTLSSelector(type, mtlsOptions, liveReload = true) {
|
|
|
962
983
|
let subscribedTable = null;
|
|
963
984
|
let activeSubscription = null;
|
|
964
985
|
return (SNICallback.ready = new Promise((resolve, reject) => {
|
|
986
|
+
// Pass-level failure before `.ready` settles still rejects (boot surfaces it); after,
|
|
987
|
+
// the only safe response is to keep serving live state and retry.
|
|
988
|
+
let readySettled = false;
|
|
989
|
+
const settle = (value) => {
|
|
990
|
+
readySettled = true;
|
|
991
|
+
resolve(value);
|
|
992
|
+
};
|
|
993
|
+
// Self-retry state for passes with per-record failures: the signature identifies the
|
|
994
|
+
// failure set, the delay backs off while it is unchanged, and summaries are rate-limited.
|
|
995
|
+
let failureSignature = '';
|
|
996
|
+
let failureRetryDelay = TLS_REBUILD_DEBOUNCE_MS;
|
|
997
|
+
let failureRetryTimer;
|
|
998
|
+
let failureRetryCount = 0;
|
|
999
|
+
let failureLastSummaryAt = 0;
|
|
1000
|
+
const scheduleFailureRetry = () => {
|
|
1001
|
+
// An armed external rebuild (or an armed retry) will run a pass that re-arms if needed.
|
|
1002
|
+
if (failureRetryTimer || rebuildTimer)
|
|
1003
|
+
return;
|
|
1004
|
+
failureRetryTimer = setTimeout(() => {
|
|
1005
|
+
failureRetryTimer = undefined;
|
|
1006
|
+
updateTLS();
|
|
1007
|
+
}, failureRetryDelay).unref();
|
|
1008
|
+
failureRetryDelay = Math.min(failureRetryDelay * 2, TLS_FAILURE_RETRY_MAX_DELAY_MS);
|
|
1009
|
+
};
|
|
1010
|
+
// Log each failure on a new/changed failure set; while unchanged, stay silent apart from a
|
|
1011
|
+
// rate-limited summary — an un-throttled 1.5s retry would emit ~57k lines/day per selector.
|
|
1012
|
+
const reportFailures = (failures) => {
|
|
1013
|
+
const signature = failures
|
|
1014
|
+
.map(({ cert, error }) => `${cert.name}: ${error?.message}`)
|
|
1015
|
+
.sort()
|
|
1016
|
+
.join('; ');
|
|
1017
|
+
if (signature !== failureSignature) {
|
|
1018
|
+
failureSignature = signature;
|
|
1019
|
+
failureRetryDelay = TLS_REBUILD_DEBOUNCE_MS;
|
|
1020
|
+
failureRetryCount = 0;
|
|
1021
|
+
failureLastSummaryAt = Date.now();
|
|
1022
|
+
// A timer armed for the previous signature would run the next pass on its stale
|
|
1023
|
+
// (possibly maxed-out) delay; re-arm fresh for the new failure set.
|
|
1024
|
+
if (failureRetryTimer) {
|
|
1025
|
+
clearTimeout(failureRetryTimer);
|
|
1026
|
+
failureRetryTimer = undefined;
|
|
1027
|
+
}
|
|
1028
|
+
for (const { cert, error } of failures)
|
|
1029
|
+
logger.error?.('Error applying TLS for', cert.name, `on the '${type}' listener`, error);
|
|
1030
|
+
}
|
|
1031
|
+
else {
|
|
1032
|
+
failureRetryCount++;
|
|
1033
|
+
// Stays at error: a stuck rotation must keep an alertable signal — the retained cert
|
|
1034
|
+
// is aging toward its notAfter the whole time.
|
|
1035
|
+
if (Date.now() - failureLastSummaryAt >= TLS_FAILURE_SUMMARY_INTERVAL_MS) {
|
|
1036
|
+
failureLastSummaryAt = Date.now();
|
|
1037
|
+
logger.error?.(`TLS rebuild for the '${type}' listener is still failing for [${failures
|
|
1038
|
+
.map(({ cert }) => cert.name)
|
|
1039
|
+
.join(', ')}] after ${failureRetryCount} retries; serving the last good state`);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
const clearFailureState = () => {
|
|
1044
|
+
if (failureSignature) {
|
|
1045
|
+
logger.warn?.(`TLS rebuild for the '${type}' listener recovered; all certificates applied`);
|
|
1046
|
+
}
|
|
1047
|
+
failureSignature = '';
|
|
1048
|
+
failureRetryDelay = TLS_REBUILD_DEBOUNCE_MS;
|
|
1049
|
+
failureRetryCount = 0;
|
|
1050
|
+
if (failureRetryTimer) {
|
|
1051
|
+
clearTimeout(failureRetryTimer);
|
|
1052
|
+
failureRetryTimer = undefined;
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
965
1055
|
function updateTLS() {
|
|
966
1056
|
try {
|
|
967
1057
|
if (databases_ts_1.databases === undefined) {
|
|
968
|
-
|
|
1058
|
+
settle();
|
|
969
1059
|
return;
|
|
970
1060
|
}
|
|
971
1061
|
if (databases_ts_1.databases.system?.hdb_certificate === undefined) {
|
|
@@ -993,19 +1083,19 @@ function createTLSSelector(type, mtlsOptions, liveReload = true) {
|
|
|
993
1083
|
scheduleRebuild();
|
|
994
1084
|
return;
|
|
995
1085
|
}
|
|
996
|
-
//
|
|
997
|
-
//
|
|
998
|
-
//
|
|
999
|
-
|
|
1000
|
-
|
|
1086
|
+
// Transactional publication (#2382): build the whole replacement state off to the
|
|
1087
|
+
// side and reconcile the live maps only after the pass completes, so a failure of
|
|
1088
|
+
// any shape leaves the currently-served state intact.
|
|
1089
|
+
const candidateContexts = new Map();
|
|
1090
|
+
const candidateCAs = new Map();
|
|
1091
|
+
let candidateHasWildcards = false;
|
|
1001
1092
|
let bestQuality = 0;
|
|
1002
|
-
|
|
1003
|
-
//
|
|
1004
|
-
//
|
|
1005
|
-
//
|
|
1006
|
-
// forever, which would let a later transient zero-cert rebuild skip the retry and
|
|
1007
|
-
// publish an empty certificates list (the #1998 symptom) on the post-boot path.
|
|
1093
|
+
let candidateDefault;
|
|
1094
|
+
// Whether THIS pass produced a default. The `defaultContext` closure variable is never
|
|
1095
|
+
// reset (a transient zero-cert pass keeps serving the prior default), so keying the
|
|
1096
|
+
// zero-cert retry off it would republish empty post-boot — the #1998 symptom.
|
|
1008
1097
|
let defaultContextSetThisPass = false;
|
|
1098
|
+
const failedThisPass = [];
|
|
1009
1099
|
// Track the actual table instance, not just whether we've ever subscribed: resetDatabases()
|
|
1010
1100
|
// (copy_db, ITC restart handling) replaces databases.system.hdb_certificate with a new
|
|
1011
1101
|
// table object, and a boolean flag would never re-subscribe to it, permanently losing
|
|
@@ -1036,24 +1126,28 @@ function createTLSSelector(type, mtlsOptions, liveReload = true) {
|
|
|
1036
1126
|
previousSubscription.then((subscription) => subscription?.end?.()).catch(() => { });
|
|
1037
1127
|
}
|
|
1038
1128
|
}
|
|
1039
|
-
|
|
1129
|
+
// One snapshot drives both loops: separate search() calls own separate read
|
|
1130
|
+
// snapshots, and a write committing between them could publish a renewed leaf
|
|
1131
|
+
// against the previous CA set.
|
|
1132
|
+
const certRecords = Array.from(databases_ts_1.databases.system.hdb_certificate.search([]));
|
|
1133
|
+
for (const cert of certRecords) {
|
|
1040
1134
|
const certificate = cert.certificate;
|
|
1041
1135
|
let certParsed;
|
|
1042
1136
|
try {
|
|
1043
1137
|
certParsed = new node_crypto_1.X509Certificate(certificate);
|
|
1044
1138
|
}
|
|
1045
1139
|
catch (error) {
|
|
1046
|
-
//
|
|
1047
|
-
//
|
|
1048
|
-
|
|
1140
|
+
// A pass failure like any other (throttle + retry). Its caCerts entry can't be
|
|
1141
|
+
// retained — the subject is unrecoverable from a PEM that won't parse.
|
|
1142
|
+
failedThisPass.push({ cert, error });
|
|
1049
1143
|
continue;
|
|
1050
1144
|
}
|
|
1051
1145
|
if (cert.is_authority) {
|
|
1052
1146
|
certParsed.asString = certificate;
|
|
1053
|
-
|
|
1147
|
+
candidateCAs.set(certParsed.subject, certificate);
|
|
1054
1148
|
}
|
|
1055
1149
|
}
|
|
1056
|
-
for (const cert of
|
|
1150
|
+
for (const cert of certRecords) {
|
|
1057
1151
|
try {
|
|
1058
1152
|
if (cert.is_authority) {
|
|
1059
1153
|
continue;
|
|
@@ -1071,8 +1165,8 @@ function createTLSSelector(type, mtlsOptions, liveReload = true) {
|
|
|
1071
1165
|
const private_key = getPrivateKeyByName(cert.private_key_name);
|
|
1072
1166
|
let certificate = cert.certificate;
|
|
1073
1167
|
const certParsed = new node_crypto_1.X509Certificate(certificate);
|
|
1074
|
-
if (
|
|
1075
|
-
certificate += '\n' +
|
|
1168
|
+
if (candidateCAs.has(certParsed.issuer)) {
|
|
1169
|
+
certificate += '\n' + candidateCAs.get(certParsed.issuer);
|
|
1076
1170
|
}
|
|
1077
1171
|
if (!private_key || !certificate) {
|
|
1078
1172
|
throw new Error('Missing private key or certificate for secure server');
|
|
@@ -1080,8 +1174,10 @@ function createTLSSelector(type, mtlsOptions, liveReload = true) {
|
|
|
1080
1174
|
const secureOptions = {
|
|
1081
1175
|
ciphers: cert.ciphers,
|
|
1082
1176
|
ticketKeys: (0, manageThreads_js_1.getTicketKeys)(),
|
|
1083
|
-
|
|
1084
|
-
|
|
1177
|
+
// the live map, not the candidate: its identity survives publication, so contexts
|
|
1178
|
+
// keep seeing the current CA set after later rebuilds
|
|
1179
|
+
availableCAs: caCerts,
|
|
1180
|
+
ca: mtlsOptions && Array.from(candidateCAs.values()),
|
|
1085
1181
|
cert: certificate,
|
|
1086
1182
|
key: private_key,
|
|
1087
1183
|
key_file: cert.private_key_name,
|
|
@@ -1100,7 +1196,7 @@ function createTLSSelector(type, mtlsOptions, liveReload = true) {
|
|
|
1100
1196
|
secureContext.name = cert.name;
|
|
1101
1197
|
secureContext.options = secureOptions;
|
|
1102
1198
|
secureContext.quality = quality;
|
|
1103
|
-
secureContext.certificateAuthorities = Array.from(
|
|
1199
|
+
secureContext.certificateAuthorities = Array.from(candidateCAs);
|
|
1104
1200
|
// we store the first 100 bytes of the certificate just for debug logging
|
|
1105
1201
|
secureContext.certStart = certificate.toString().slice(0, 100);
|
|
1106
1202
|
// we want to configure SNI handling to pick the right certificate based on all the registered SANs
|
|
@@ -1108,14 +1204,14 @@ function createTLSSelector(type, mtlsOptions, liveReload = true) {
|
|
|
1108
1204
|
for (let hostname of hostnames) {
|
|
1109
1205
|
if (hostname) {
|
|
1110
1206
|
if (hostname[0] === '*') {
|
|
1111
|
-
|
|
1207
|
+
candidateHasWildcards = true;
|
|
1112
1208
|
hostname = hostname.slice(1);
|
|
1113
1209
|
}
|
|
1114
1210
|
// we use this certificate if it has a higher quality than the existing one for this hostname
|
|
1115
|
-
let existingCertQuality =
|
|
1211
|
+
let existingCertQuality = candidateContexts.get(hostname)?.quality ?? 0;
|
|
1116
1212
|
logger.trace?.('Assigning TLS for hostname', hostname, 'if', quality, '>', existingCertQuality);
|
|
1117
1213
|
if (quality > existingCertQuality) {
|
|
1118
|
-
|
|
1214
|
+
candidateContexts.set(hostname, secureContext);
|
|
1119
1215
|
}
|
|
1120
1216
|
}
|
|
1121
1217
|
else {
|
|
@@ -1125,57 +1221,147 @@ function createTLSSelector(type, mtlsOptions, liveReload = true) {
|
|
|
1125
1221
|
logger.trace?.('Adding TLS', secureContext.name, 'for', server?.ports || 'client', 'cert named', cert.name, 'hostnames', hostnames, 'quality', quality, 'best quality', bestQuality);
|
|
1126
1222
|
if (quality > bestQuality /* && hasIpAddress*/) {
|
|
1127
1223
|
// we use this certificate as the default if it has a higher quality than the existing one
|
|
1128
|
-
|
|
1224
|
+
candidateDefault = secureContext;
|
|
1129
1225
|
defaultContextSetThisPass = true;
|
|
1130
1226
|
bestQuality = quality;
|
|
1131
|
-
if (server) {
|
|
1132
|
-
server.defaultContext = secureContext;
|
|
1133
|
-
// note that we can not set the secure context on the server here, because this creates an
|
|
1134
|
-
// indeterminate situation of whether openssl will use this certificate or the one from the SNI
|
|
1135
|
-
// callback
|
|
1136
|
-
//server.setSecureContext?.(server, secureOptions);
|
|
1137
|
-
}
|
|
1138
1227
|
}
|
|
1139
1228
|
}
|
|
1140
1229
|
catch (error) {
|
|
1141
|
-
|
|
1230
|
+
failedThisPass.push({ cert, error });
|
|
1142
1231
|
}
|
|
1143
1232
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
//
|
|
1162
|
-
//
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1233
|
+
// Retain-last-good: a record still in the table whose build failed keeps its live
|
|
1234
|
+
// hostname entries and its default candidacy; deletion remains the way to drop them.
|
|
1235
|
+
// On an mTLS listener whose CA set changed, the retained pair is rebuilt against the
|
|
1236
|
+
// current trust material — new handshakes never see stale trust (established sessions
|
|
1237
|
+
// and outstanding tickets are unaffected, as on any build); if the rebuild fails the entries drop
|
|
1238
|
+
// unless nothing else is servable (the zero-cert guard below then keeps the old
|
|
1239
|
+
// state). Full contract in DESIGN.md "TLS hot-reload".
|
|
1240
|
+
const caSetUnchanged = (previous) => {
|
|
1241
|
+
const builtWith = previous.certificateAuthorities;
|
|
1242
|
+
if (!Array.isArray(builtWith))
|
|
1243
|
+
return candidateCAs.size === 0;
|
|
1244
|
+
return (builtWith.length === candidateCAs.size &&
|
|
1245
|
+
builtWith.every(([subject, pem]) => candidateCAs.get(subject) === pem));
|
|
1246
|
+
};
|
|
1247
|
+
const rebuiltRetentions = new Map();
|
|
1248
|
+
const retentionFailures = [];
|
|
1249
|
+
const retainable = (previous) => {
|
|
1250
|
+
// Without mTLS nothing consulted depends on the CA set — skip the rebuild, but
|
|
1251
|
+
// refresh the CA bookkeeping mirrored into socket metadata for fronting proxies.
|
|
1252
|
+
if (!mtlsOptions || caSetUnchanged(previous)) {
|
|
1253
|
+
previous.certificateAuthorities = Array.from(candidateCAs);
|
|
1254
|
+
return previous;
|
|
1255
|
+
}
|
|
1256
|
+
if (rebuiltRetentions.has(previous))
|
|
1257
|
+
return rebuiltRetentions.get(previous);
|
|
1258
|
+
try {
|
|
1259
|
+
const secureOptions = {
|
|
1260
|
+
...previous.options,
|
|
1261
|
+
ticketKeys: (0, manageThreads_js_1.getTicketKeys)(),
|
|
1262
|
+
availableCAs: caCerts,
|
|
1263
|
+
ca: mtlsOptions && Array.from(candidateCAs.values()),
|
|
1264
|
+
};
|
|
1265
|
+
const rebuilt = tls.createSecureContext(secureOptions);
|
|
1266
|
+
rebuilt.name = previous.name;
|
|
1267
|
+
rebuilt.options = secureOptions;
|
|
1268
|
+
rebuilt.quality = previous.quality;
|
|
1269
|
+
rebuilt.certificateAuthorities = Array.from(candidateCAs);
|
|
1270
|
+
rebuilt.certStart = previous.certStart;
|
|
1271
|
+
rebuiltRetentions.set(previous, rebuilt);
|
|
1272
|
+
return rebuilt;
|
|
1273
|
+
}
|
|
1274
|
+
catch (error) {
|
|
1275
|
+
retentionFailures.push({
|
|
1276
|
+
cert: { name: `${previous.name} (retained-context rebuild)` },
|
|
1277
|
+
error,
|
|
1278
|
+
});
|
|
1279
|
+
rebuiltRetentions.set(previous, undefined);
|
|
1280
|
+
return undefined;
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
for (const { cert } of failedThisPass) {
|
|
1284
|
+
// Ties go to the incumbent: a hostname the retained context already owned, and the
|
|
1285
|
+
// live default when it is the retained record, must not flip to an equal-quality
|
|
1286
|
+
// sibling because of a transient failure. A context retained only for a hostname
|
|
1287
|
+
// still needs strict > to become the default.
|
|
1288
|
+
const retain = (previous, hostname) => {
|
|
1289
|
+
const retained = retainable(previous);
|
|
1290
|
+
if (!retained)
|
|
1291
|
+
return;
|
|
1292
|
+
const previousQuality = retained.quality ?? 0;
|
|
1293
|
+
if (hostname !== undefined) {
|
|
1294
|
+
if (previousQuality >= (candidateContexts.get(hostname)?.quality ?? 0)) {
|
|
1295
|
+
candidateContexts.set(hostname, retained);
|
|
1296
|
+
}
|
|
1297
|
+
if (hostname[0] === '.')
|
|
1298
|
+
candidateHasWildcards = true;
|
|
1299
|
+
}
|
|
1300
|
+
const winsDefault = previous === defaultContext ? previousQuality >= bestQuality : previousQuality > bestQuality;
|
|
1301
|
+
if (winsDefault) {
|
|
1302
|
+
candidateDefault = retained;
|
|
1303
|
+
defaultContextSetThisPass = true;
|
|
1304
|
+
bestQuality = previousQuality;
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
for (const [hostname, previous] of secureContexts) {
|
|
1308
|
+
if (previous.name === cert.name)
|
|
1309
|
+
retain(previous, hostname);
|
|
1310
|
+
}
|
|
1311
|
+
if (defaultContext?.name === cert.name)
|
|
1312
|
+
retain(defaultContext);
|
|
1313
|
+
}
|
|
1314
|
+
if (liveReload && candidateContexts.size === 0 && !defaultContextSetThisPass) {
|
|
1315
|
+
// Every row failed to apply (e.g. keys not yet on this thread). Publishing or
|
|
1316
|
+
// resolving would write an empty `certificates:` list (the #1998 symptom), so keep
|
|
1317
|
+
// live state and retry. `!defaultContextSetThisPass`, not the persistent
|
|
1318
|
+
// `defaultContext`: a cert with no usable hostnames still sets a serviceable
|
|
1319
|
+
// default (must resolve, not retry forever), while the closure variable is truthy
|
|
1320
|
+
// forever after the first success. Gated on liveReload: transient selectors
|
|
1321
|
+
// (getReplicationCert) legitimately resolve empty — bootstrap depends on that
|
|
1322
|
+
// meaning "no cert yet". Warn is latched: unlatched would emit ~57k lines/day.
|
|
1169
1323
|
if (server && !server.tlsSelectorWarnedZeroCerts) {
|
|
1170
1324
|
server.tlsSelectorWarnedZeroCerts = true;
|
|
1171
1325
|
logger.warn?.(`TLS selector for the '${type}' listener resolved zero certificates; retrying every ${TLS_REBUILD_DEBOUNCE_MS}ms`);
|
|
1172
1326
|
}
|
|
1173
|
-
|
|
1327
|
+
if (failedThisPass.length > 0) {
|
|
1328
|
+
// Surface WHY the pass came up empty and put the retry on the backoff.
|
|
1329
|
+
reportFailures(failedThisPass.concat(retentionFailures));
|
|
1330
|
+
scheduleFailureRetry();
|
|
1331
|
+
}
|
|
1332
|
+
else {
|
|
1333
|
+
scheduleRebuild();
|
|
1334
|
+
}
|
|
1174
1335
|
return;
|
|
1175
1336
|
}
|
|
1337
|
+
// Publish: reconcile the live maps in place (their identity is aliased by
|
|
1338
|
+
// server.secureContexts and every context's availableCAs) and advance all default
|
|
1339
|
+
// references together so SNI and non-SNI traffic can't diverge.
|
|
1340
|
+
secureContexts.clear();
|
|
1341
|
+
for (const [hostname, context] of candidateContexts)
|
|
1342
|
+
secureContexts.set(hostname, context);
|
|
1343
|
+
caCerts.clear();
|
|
1344
|
+
for (const [subject, certificate] of candidateCAs)
|
|
1345
|
+
caCerts.set(subject, certificate);
|
|
1176
1346
|
// only listener selectors publish: a one-shot client selector's pass may see no authority rows
|
|
1177
1347
|
if (liveReload)
|
|
1178
1348
|
(0, trustedIssuers_ts_1.publishTrustedAuthorities)(caCerts.values());
|
|
1349
|
+
hasWildcards = candidateHasWildcards;
|
|
1350
|
+
if (candidateDefault) {
|
|
1351
|
+
SNICallback.defaultContext = defaultContext = candidateDefault;
|
|
1352
|
+
// note that we can not set the secure context on the server here, because this creates an
|
|
1353
|
+
// indeterminate situation of whether openssl will use this certificate or the one from the SNI
|
|
1354
|
+
// callback
|
|
1355
|
+
if (server)
|
|
1356
|
+
server.defaultContext = candidateDefault;
|
|
1357
|
+
}
|
|
1358
|
+
if (failedThisPass.length > 0) {
|
|
1359
|
+
reportFailures(failedThisPass.concat(retentionFailures));
|
|
1360
|
+
scheduleFailureRetry();
|
|
1361
|
+
}
|
|
1362
|
+
else {
|
|
1363
|
+
clearFailureState();
|
|
1364
|
+
}
|
|
1179
1365
|
// A successful pass ends any warn latches so a later recurrence logs again.
|
|
1180
1366
|
if (server) {
|
|
1181
1367
|
server.tlsSelectorWaitedForSystemDb = false;
|
|
@@ -1198,9 +1384,15 @@ function createTLSSelector(type, mtlsOptions, liveReload = true) {
|
|
|
1198
1384
|
}
|
|
1199
1385
|
}
|
|
1200
1386
|
server?.secureContextsListeners.forEach((listener) => listener());
|
|
1201
|
-
|
|
1387
|
+
settle(defaultContext);
|
|
1202
1388
|
}
|
|
1203
1389
|
catch (error) {
|
|
1390
|
+
if (readySettled) {
|
|
1391
|
+
// Live state is untouched (candidates are pass-local): keep serving it and retry.
|
|
1392
|
+
reportFailures([{ cert: { name: `(${type} rebuild pass)` }, error }]);
|
|
1393
|
+
scheduleFailureRetry();
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1204
1396
|
reject(error);
|
|
1205
1397
|
}
|
|
1206
1398
|
}
|