@harperfast/harper 5.3.0-beta.3 → 5.3.0-beta.4

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 (311) hide show
  1. package/bin/cliOperations.ts +25 -3
  2. package/bin/run.ts +18 -0
  3. package/components/Application.ts +52 -78
  4. package/components/DESIGN.md +76 -3
  5. package/components/Scope.ts +10 -4
  6. package/components/anthropic/index.ts +10 -1
  7. package/components/bedrock/index.ts +10 -1
  8. package/components/componentLoader.ts +23 -14
  9. package/components/mcp/tools/operations.ts +3 -1
  10. package/components/ollama/index.ts +10 -1
  11. package/components/openai/index.ts +265 -18
  12. package/components/operations.js +40 -1
  13. package/config/configUtils.ts +3 -19
  14. package/dataLayer/DESIGN.md +29 -5
  15. package/dataLayer/harperBridge/ResourceBridge.ts +39 -3
  16. package/dataLayer/restoreMarker.ts +250 -14
  17. package/dataLayer/schema.ts +2 -7
  18. package/dataLayer/schemaDescribe.ts +10 -0
  19. package/dist/bin/cliOperations.js +28 -3
  20. package/dist/bin/cliOperations.js.map +1 -1
  21. package/dist/bin/run.js +19 -0
  22. package/dist/bin/run.js.map +1 -1
  23. package/dist/components/Application.d.ts +8 -2
  24. package/dist/components/Application.js +43 -69
  25. package/dist/components/Application.js.map +1 -1
  26. package/dist/components/Scope.d.ts +1 -1
  27. package/dist/components/Scope.js +9 -4
  28. package/dist/components/Scope.js.map +1 -1
  29. package/dist/components/anthropic/index.d.ts +0 -10
  30. package/dist/components/anthropic/index.js +9 -1
  31. package/dist/components/anthropic/index.js.map +1 -1
  32. package/dist/components/bedrock/index.d.ts +0 -12
  33. package/dist/components/bedrock/index.js +9 -1
  34. package/dist/components/bedrock/index.js.map +1 -1
  35. package/dist/components/componentLoader.js +23 -14
  36. package/dist/components/componentLoader.js.map +1 -1
  37. package/dist/components/mcp/tools/operations.js +1 -1
  38. package/dist/components/mcp/tools/operations.js.map +1 -1
  39. package/dist/components/ollama/index.d.ts +0 -13
  40. package/dist/components/ollama/index.js +9 -1
  41. package/dist/components/ollama/index.js.map +1 -1
  42. package/dist/components/openai/index.d.ts +11 -15
  43. package/dist/components/openai/index.js +224 -12
  44. package/dist/components/openai/index.js.map +1 -1
  45. package/dist/components/operations.d.ts +9 -0
  46. package/dist/components/operations.js +32 -2
  47. package/dist/components/operations.js.map +1 -1
  48. package/dist/config/configUtils.d.ts +1 -2
  49. package/dist/config/configUtils.js +7 -22
  50. package/dist/config/configUtils.js.map +1 -1
  51. package/dist/dataLayer/harperBridge/ResourceBridge.js +29 -2
  52. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  53. package/dist/dataLayer/restoreMarker.d.ts +25 -3
  54. package/dist/dataLayer/restoreMarker.js +213 -13
  55. package/dist/dataLayer/restoreMarker.js.map +1 -1
  56. package/dist/dataLayer/schema.js +1 -4
  57. package/dist/dataLayer/schema.js.map +1 -1
  58. package/dist/dataLayer/schemaDescribe.js +10 -0
  59. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.js.map +1 -1
  62. package/dist/json/systemSchema.json +28 -0
  63. package/dist/resources/DatabaseTransaction.d.ts +21 -1
  64. package/dist/resources/DatabaseTransaction.js +147 -8
  65. package/dist/resources/DatabaseTransaction.js.map +1 -1
  66. package/dist/resources/PrimaryRocksDatabase.js +1 -1
  67. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  68. package/dist/resources/Table.d.ts +31 -3
  69. package/dist/resources/Table.js +397 -79
  70. package/dist/resources/Table.js.map +1 -1
  71. package/dist/resources/auditStore.js +16 -10
  72. package/dist/resources/auditStore.js.map +1 -1
  73. package/dist/resources/blob.d.ts +1 -0
  74. package/dist/resources/blob.js +14 -5
  75. package/dist/resources/blob.js.map +1 -1
  76. package/dist/resources/branchDatabase.js +3 -3
  77. package/dist/resources/branchDatabase.js.map +1 -1
  78. package/dist/resources/databaseDropPreparation.d.ts +20 -0
  79. package/dist/resources/databaseDropPreparation.js +106 -0
  80. package/dist/resources/databaseDropPreparation.js.map +1 -0
  81. package/dist/resources/databases.d.ts +26 -21
  82. package/dist/resources/databases.js +1040 -204
  83. package/dist/resources/databases.js.map +1 -1
  84. package/dist/resources/derivedIndexRegistry.d.ts +23 -0
  85. package/dist/resources/derivedIndexRegistry.js +92 -0
  86. package/dist/resources/derivedIndexRegistry.js.map +1 -1
  87. package/dist/resources/derivedIndexRuntime.d.ts +10 -0
  88. package/dist/resources/derivedIndexRuntime.js +136 -25
  89. package/dist/resources/derivedIndexRuntime.js.map +1 -1
  90. package/dist/resources/derivedIndexes.d.ts +39 -0
  91. package/dist/resources/derivedIndexes.js +733 -0
  92. package/dist/resources/derivedIndexes.js.map +1 -0
  93. package/dist/resources/fullTextSchema.d.ts +8 -0
  94. package/dist/resources/fullTextSchema.js +42 -0
  95. package/dist/resources/fullTextSchema.js.map +1 -1
  96. package/dist/resources/fullTextSchemaLifecycle.d.ts +0 -5
  97. package/dist/resources/fullTextSchemaLifecycle.js +0 -9
  98. package/dist/resources/fullTextSchemaLifecycle.js.map +1 -1
  99. package/dist/resources/graphql.js +101 -9
  100. package/dist/resources/graphql.js.map +1 -1
  101. package/dist/resources/indexes/hnswDerivedIndex.d.ts +0 -10
  102. package/dist/resources/indexes/hnswDerivedIndex.js +0 -230
  103. package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -1
  104. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +3 -0
  105. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +57 -8
  106. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -1
  107. package/dist/resources/models/Models.d.ts +26 -3
  108. package/dist/resources/models/Models.js +256 -13
  109. package/dist/resources/models/Models.js.map +1 -1
  110. package/dist/resources/models/TestBackend.d.ts +3 -1
  111. package/dist/resources/models/TestBackend.js +35 -1
  112. package/dist/resources/models/TestBackend.js.map +1 -1
  113. package/dist/resources/models/analyticsTable.d.ts +2 -2
  114. package/dist/resources/models/analyticsTable.js +4 -2
  115. package/dist/resources/models/analyticsTable.js.map +1 -1
  116. package/dist/resources/models/backendHelpers.d.ts +24 -0
  117. package/dist/resources/models/backendHelpers.js +18 -1
  118. package/dist/resources/models/backendHelpers.js.map +1 -1
  119. package/dist/resources/models/backendRegistry.d.ts +10 -21
  120. package/dist/resources/models/backendRegistry.js +74 -18
  121. package/dist/resources/models/backendRegistry.js.map +1 -1
  122. package/dist/resources/models/bootstrap.d.ts +7 -0
  123. package/dist/resources/models/bootstrap.js +36 -4
  124. package/dist/resources/models/bootstrap.js.map +1 -1
  125. package/dist/resources/models/decideHook.d.ts +33 -0
  126. package/dist/resources/models/decideHook.js +105 -0
  127. package/dist/resources/models/decideHook.js.map +1 -0
  128. package/dist/resources/models/decision.d.ts +64 -0
  129. package/dist/resources/models/decision.js +507 -0
  130. package/dist/resources/models/decision.js.map +1 -0
  131. package/dist/resources/models/decisionStore.d.ts +134 -0
  132. package/dist/resources/models/decisionStore.js +350 -0
  133. package/dist/resources/models/decisionStore.js.map +1 -0
  134. package/dist/resources/models/embedHook.d.ts +66 -8
  135. package/dist/resources/models/embedHook.js +228 -61
  136. package/dist/resources/models/embedHook.js.map +1 -1
  137. package/dist/resources/models/generativeDecision.d.ts +58 -0
  138. package/dist/resources/models/generativeDecision.js +315 -0
  139. package/dist/resources/models/generativeDecision.js.map +1 -0
  140. package/dist/resources/models/types.d.ts +232 -4
  141. package/dist/security/authn/oidc/tokenExchange.js +3 -33
  142. package/dist/security/authn/oidc/tokenExchange.js.map +1 -1
  143. package/dist/security/authn/oidc/tokenUseTable.d.ts +9 -0
  144. package/dist/security/authn/oidc/tokenUseTable.js +28 -0
  145. package/dist/security/authn/oidc/tokenUseTable.js.map +1 -0
  146. package/dist/security/certificateVerification/certificateVerificationSource.d.ts +0 -1
  147. package/dist/security/certificateVerification/certificateVerificationSource.js +3 -3
  148. package/dist/security/certificateVerification/certificateVerificationSource.js.map +1 -1
  149. package/dist/security/certificateVerification/crlVerification.js +77 -165
  150. package/dist/security/certificateVerification/crlVerification.js.map +1 -1
  151. package/dist/security/certificateVerification/types.d.ts +0 -3
  152. package/dist/security/certificateVerification/verificationTables.d.ts +19 -0
  153. package/dist/security/certificateVerification/verificationTables.js +125 -0
  154. package/dist/security/certificateVerification/verificationTables.js.map +1 -0
  155. package/dist/security/certificateVerification/verificationUtils.d.ts +1 -1
  156. package/dist/security/certificateVerification/verificationUtils.js +6 -31
  157. package/dist/security/certificateVerification/verificationUtils.js.map +1 -1
  158. package/dist/security/jsLoader.d.ts +5 -0
  159. package/dist/security/jsLoader.js +8 -0
  160. package/dist/security/jsLoader.js.map +1 -1
  161. package/dist/server/Server.d.ts +1 -0
  162. package/dist/server/Server.js.map +1 -1
  163. package/dist/server/http.js +2 -2
  164. package/dist/server/http.js.map +1 -1
  165. package/dist/server/itc/serverHandlers.js +21 -2
  166. package/dist/server/itc/serverHandlers.js.map +1 -1
  167. package/dist/server/jobs/jobOwnership.d.ts +25 -0
  168. package/dist/server/jobs/jobOwnership.js +127 -0
  169. package/dist/server/jobs/jobOwnership.js.map +1 -0
  170. package/dist/server/jobs/jobProcess.js +9 -2
  171. package/dist/server/jobs/jobProcess.js.map +1 -1
  172. package/dist/server/jobs/jobs.js +18 -5
  173. package/dist/server/jobs/jobs.js.map +1 -1
  174. package/dist/server/loadRootComponents.js +10 -0
  175. package/dist/server/loadRootComponents.js.map +1 -1
  176. package/dist/server/serverHelpers/contentTypes.js +11 -1
  177. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  178. package/dist/server/serverHelpers/serverHandlers.js +12 -2
  179. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  180. package/dist/server/threads/itc.d.ts +4 -1
  181. package/dist/server/threads/itc.js +29 -6
  182. package/dist/server/threads/itc.js.map +1 -1
  183. package/dist/server/threads/manageThreads.d.ts +12 -4
  184. package/dist/server/threads/manageThreads.js +241 -119
  185. package/dist/server/threads/manageThreads.js.map +1 -1
  186. package/dist/server/threads/threadServer.d.ts +1 -0
  187. package/dist/server/threads/threadServer.js +15 -1
  188. package/dist/server/threads/threadServer.js.map +1 -1
  189. package/dist/server/threads/windowsProcessTree.d.ts +91 -0
  190. package/dist/server/threads/windowsProcessTree.js +361 -0
  191. package/dist/server/threads/windowsProcessTree.js.map +1 -0
  192. package/dist/server/throttle.d.ts +1 -1
  193. package/dist/server/throttle.js +8 -1
  194. package/dist/server/throttle.js.map +1 -1
  195. package/dist/upgrade/directives/5-3-0.js +53 -46
  196. package/dist/upgrade/directives/5-3-0.js.map +1 -1
  197. package/dist/utility/errors/hdbError.d.ts +10 -1
  198. package/dist/utility/errors/hdbError.js +55 -11
  199. package/dist/utility/errors/hdbError.js.map +1 -1
  200. package/dist/utility/fsync.d.ts +2 -0
  201. package/dist/utility/fsync.js +20 -0
  202. package/dist/utility/fsync.js.map +1 -0
  203. package/dist/utility/globalSchema.d.ts +18 -0
  204. package/dist/utility/hdbTerms.d.ts +3 -0
  205. package/dist/utility/hdbTerms.js +3 -0
  206. package/dist/utility/hdbTerms.js.map +1 -1
  207. package/dist/utility/signalling.d.ts +7 -1
  208. package/dist/utility/signalling.js +27 -3
  209. package/dist/utility/signalling.js.map +1 -1
  210. package/dist/utility/when.d.ts +1 -0
  211. package/dist/utility/when.js +21 -0
  212. package/dist/utility/when.js.map +1 -1
  213. package/dist/validation/configValidator.js +30 -3
  214. package/dist/validation/configValidator.js.map +1 -1
  215. package/index.ts +13 -0
  216. package/json/systemSchema.json +28 -0
  217. package/npm-shrinkwrap.json +151 -55
  218. package/package.json +14 -13
  219. package/resources/DESIGN.md +80 -100
  220. package/resources/DatabaseTransaction.ts +157 -10
  221. package/resources/PrimaryRocksDatabase.ts +2 -2
  222. package/resources/Table.ts +494 -101
  223. package/resources/auditStore.ts +18 -10
  224. package/resources/blob.ts +13 -6
  225. package/resources/branchDatabase.ts +3 -3
  226. package/resources/databaseDropPreparation.ts +120 -0
  227. package/resources/databases.ts +1155 -205
  228. package/resources/derivedIndexRegistry.ts +122 -0
  229. package/resources/derivedIndexRuntime.ts +157 -30
  230. package/resources/derivedIndexes.ts +856 -0
  231. package/resources/fullTextSchema.ts +55 -0
  232. package/resources/fullTextSchemaLifecycle.ts +0 -15
  233. package/resources/graphql.ts +107 -12
  234. package/resources/indexes/DESIGN.md +7 -0
  235. package/resources/indexes/hnswDerivedIndex.ts +0 -260
  236. package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +72 -9
  237. package/resources/models/DESIGN.md +39 -0
  238. package/resources/models/Models.ts +361 -18
  239. package/resources/models/TestBackend.ts +56 -1
  240. package/resources/models/analyticsTable.ts +6 -4
  241. package/resources/models/backendHelpers.ts +17 -0
  242. package/resources/models/backendRegistry.ts +76 -21
  243. package/resources/models/bootstrap.ts +57 -7
  244. package/resources/models/decideHook.ts +135 -0
  245. package/resources/models/decision.ts +505 -0
  246. package/resources/models/decisionStore.ts +419 -0
  247. package/resources/models/embedHook.ts +272 -55
  248. package/resources/models/generativeDecision.ts +459 -0
  249. package/resources/models/types.ts +231 -4
  250. package/security/DESIGN.md +22 -2
  251. package/security/authn/oidc/tokenExchange.ts +6 -34
  252. package/security/authn/oidc/tokenUseTable.ts +27 -0
  253. package/security/certificateVerification/certificateVerificationSource.ts +2 -3
  254. package/security/certificateVerification/crlVerification.ts +96 -174
  255. package/security/certificateVerification/types.ts +0 -3
  256. package/security/certificateVerification/verificationTables.ts +124 -0
  257. package/security/certificateVerification/verificationUtils.ts +8 -32
  258. package/security/jsLoader.ts +9 -1
  259. package/server/DESIGN.md +27 -1
  260. package/server/Server.ts +2 -1
  261. package/server/http.ts +4 -2
  262. package/server/itc/serverHandlers.js +29 -1
  263. package/server/jobs/jobOwnership.ts +89 -0
  264. package/server/jobs/jobProcess.ts +8 -2
  265. package/server/jobs/jobs.ts +15 -4
  266. package/server/loadRootComponents.js +11 -0
  267. package/server/serverHelpers/contentTypes.ts +13 -1
  268. package/server/serverHelpers/serverHandlers.js +11 -2
  269. package/server/threads/itc.js +32 -6
  270. package/server/threads/manageThreads.js +268 -122
  271. package/server/threads/threadServer.js +13 -1
  272. package/server/threads/windowsProcessTree.ts +409 -0
  273. package/server/throttle.ts +11 -1
  274. package/studio/web/assets/{Chat-CveGbxe6.js → Chat-Bq60AO-g.js} +1 -1
  275. package/studio/web/assets/{FloatingChat-IpCFgYWL.js → FloatingChat-D4560T0p.js} +3 -3
  276. package/studio/web/assets/{apiToken-CS8fFTgL.js → apiToken-BEvbFPMc.js} +1 -1
  277. package/studio/web/assets/{applications-D7F4Jopl.js → applications-CSUVTH4E.js} +1 -1
  278. package/studio/web/assets/{cssMode-Daw_oLPq.js → cssMode-DJyc9mHj.js} +1 -1
  279. package/studio/web/assets/{editor-B3Bs1zWX.js → editor-DDIsQfj6.js} +1 -1
  280. package/studio/web/assets/{html-CDaQwWiE.js → html-g1BJHxLy.js} +1 -1
  281. package/studio/web/assets/{htmlMode-Cb6emO4U.js → htmlMode-Vh5IglCQ.js} +1 -1
  282. package/studio/web/assets/{index-B4g2Fu_2.js → index-DGRAzvO4.js} +5 -5
  283. package/studio/web/assets/{index.lazy-NwhvF1q1.js → index.lazy-vgSic7et.js} +1 -1
  284. package/studio/web/assets/{javascript-2_86gB9Q.js → javascript-5VIolqf7.js} +1 -1
  285. package/studio/web/assets/{jsonMode-DX-i86uQ.js → jsonMode-DG4zQraI.js} +1 -1
  286. package/studio/web/assets/{languageServices-B85WN-Ej.js → languageServices-t5FBpX-i.js} +1 -1
  287. package/studio/web/assets/{lspLanguageFeatures-JCWHIsKg.js → lspLanguageFeatures-DgVnNPh-.js} +1 -1
  288. package/studio/web/assets/{notifications-D9ejzt52.js → notifications-BeWcX04m.js} +1 -1
  289. package/studio/web/assets/{notifications-B7hjKhai.js → notifications-DUGWrWoO.js} +1 -1
  290. package/studio/web/assets/{profile-D8B6No3F.js → profile-BvUEDBzW.js} +1 -1
  291. package/studio/web/assets/{regions-DFazIfTK.js → regions-CB-_2m7M.js} +1 -1
  292. package/studio/web/assets/{register-CwFLviLF.js → register-1w1Zr3b2.js} +2 -2
  293. package/studio/web/assets/{setComponentFile-VnJ80SSJ.js → setComponentFile-z4-qJCbA.js} +1 -1
  294. package/studio/web/assets/{setup-BYQxFy-C.js → setup-CS50uJ4x.js} +2 -2
  295. package/studio/web/assets/{status-Cn5XjmsL.js → status-CI0wOdOE.js} +1 -1
  296. package/studio/web/assets/{toggleHighContrast-g9xIh6TX.js → toggleHighContrast-BsAE4jPe.js} +1 -1
  297. package/studio/web/assets/{tsMode-EC-n0Wqc.js → tsMode-D83Gg3cq.js} +1 -1
  298. package/studio/web/assets/{typescript-DHmM9JCO.js → typescript-Dfp-GrNq.js} +1 -1
  299. package/studio/web/assets/{useEntityRestURL-BUdnVSVv.js → useEntityRestURL-BKetlINf.js} +1 -1
  300. package/studio/web/assets/{workers-BVl9bBB-.js → workers-CCvmDLH3.js} +1 -1
  301. package/studio/web/assets/{xml-C4_sqOkX.js → xml-06pZp00U.js} +1 -1
  302. package/studio/web/assets/{yaml-CVkbDqtW.js → yaml-iSnWMVlV.js} +1 -1
  303. package/studio/web/index.html +1 -1
  304. package/upgrade/directives/5-3-0.ts +61 -54
  305. package/utility/DESIGN.md +5 -1
  306. package/utility/errors/hdbError.ts +56 -8
  307. package/utility/fsync.ts +16 -0
  308. package/utility/hdbTerms.ts +3 -0
  309. package/utility/signalling.ts +33 -4
  310. package/utility/when.ts +22 -0
  311. package/validation/configValidator.ts +36 -7
@@ -4,8 +4,8 @@
4
4
 
5
5
  import * as pkijs from 'pkijs';
6
6
  import { loggerWithTag } from '../../utility/logging/logger.ts';
7
- import { table } from '../../resources/databases.ts';
8
7
  import { Resource } from '../../resources/Resource.ts';
8
+ import { transaction } from '../../resources/transaction.ts';
9
9
  import type { SourceContext } from '../../resources/ResourceInterface.ts';
10
10
  import {
11
11
  extractCRLDistributionPoints,
@@ -17,6 +17,7 @@ import {
17
17
  getCertificateCacheTable as getSharedCertificateCacheTable,
18
18
  pemToBuffer,
19
19
  } from './verificationUtils.ts';
20
+ import { declareCRLCacheTable, declareRevokedCertificatesTable } from './verificationTables.ts';
20
21
  import { ERROR_CACHE_TTL, CRL_DEFAULT_VALIDITY_PERIOD, CRL_USER_AGENT } from './verificationConfig.ts';
21
22
  import type {
22
23
  CertificateVerificationResult,
@@ -68,17 +69,12 @@ class CertificateRevocationListSource extends Resource {
68
69
  const { distributionPoint, issuerPem: issuerPemStr, config } = requestContext;
69
70
 
70
71
  try {
71
- const result = await downloadAndParseCRL(distributionPoint, issuerPemStr, config.timeout);
72
+ const { entry } = await downloadAndParseCRLOnce(distributionPoint, issuerPemStr, config);
72
73
 
73
74
  // Set expiration - use the CRL's nextUpdate time or configured TTL, whichever is sooner
74
- const crlExpiry = result.next_update;
75
- const configExpiry = Date.now() + config.cacheTtl;
76
- const expiresAt = Math.min(crlExpiry, configExpiry);
75
+ context.expiresAt = Math.min(entry.next_update, Date.now() + config.cacheTtl);
77
76
 
78
- return {
79
- ...result,
80
- expiresAt,
81
- };
77
+ return entry;
82
78
  } catch (error) {
83
79
  logger.error?.(`CRL fetch error for: ${distributionPoint} - ${error}`);
84
80
 
@@ -89,7 +85,7 @@ class CertificateRevocationListSource extends Resource {
89
85
  // Check failure mode
90
86
  if (config.failureMode === 'fail-closed') {
91
87
  // Cache the error for faster recovery
92
- const expiresAt = Date.now() + ERROR_CACHE_TTL;
88
+ context.expiresAt = Date.now() + ERROR_CACHE_TTL;
93
89
 
94
90
  return {
95
91
  crl_id: id,
@@ -97,9 +93,8 @@ class CertificateRevocationListSource extends Resource {
97
93
  issuer_dn: 'unknown',
98
94
  crl_blob: Buffer.alloc(0),
99
95
  this_update: Date.now(),
100
- next_update: expiresAt,
96
+ next_update: context.expiresAt,
101
97
  signature_valid: false,
102
- expiresAt,
103
98
  };
104
99
  }
105
100
 
@@ -111,42 +106,12 @@ class CertificateRevocationListSource extends Resource {
111
106
  }
112
107
 
113
108
  // Lazy-load Harper tables
114
- let crlCacheTable: ReturnType<typeof table>;
115
- let revokedCertificateTable: ReturnType<typeof table>;
109
+ let crlCacheTable: ReturnType<typeof declareCRLCacheTable>;
110
+ let revokedCertificateTable: ReturnType<typeof declareRevokedCertificatesTable>;
116
111
 
117
112
  function getCRLCacheTable() {
118
113
  if (!crlCacheTable) {
119
- crlCacheTable = table({
120
- table: 'hdb_crl_cache',
121
- database: 'system',
122
- attributes: [
123
- {
124
- name: 'distribution_point',
125
- isPrimaryKey: true,
126
- },
127
- {
128
- name: 'issuer_dn',
129
- },
130
- {
131
- name: 'crl_blob',
132
- },
133
- {
134
- name: 'this_update',
135
- },
136
- {
137
- name: 'next_update',
138
- },
139
- {
140
- name: 'signature_valid',
141
- },
142
- {
143
- name: 'expiresAt',
144
- expiresAt: true,
145
- indexed: true,
146
- },
147
- ],
148
- });
149
-
114
+ crlCacheTable = declareCRLCacheTable();
150
115
  // Configure the caching source
151
116
  (crlCacheTable as any).sourcedFrom(CertificateRevocationListSource);
152
117
  }
@@ -154,44 +119,7 @@ function getCRLCacheTable() {
154
119
  }
155
120
 
156
121
  function getRevokedCertificateTable() {
157
- if (!revokedCertificateTable) {
158
- revokedCertificateTable = table({
159
- table: 'hdb_revoked_certificates',
160
- database: 'system',
161
- attributes: [
162
- {
163
- name: 'composite_id',
164
- isPrimaryKey: true,
165
- },
166
- {
167
- name: 'serial_number',
168
- indexed: true,
169
- },
170
- {
171
- name: 'issuer_key_id',
172
- indexed: true,
173
- },
174
- {
175
- name: 'revocation_date',
176
- },
177
- {
178
- name: 'revocation_reason',
179
- },
180
- {
181
- name: 'crl_source',
182
- indexed: true, // Links to CRL cache
183
- },
184
- {
185
- name: 'crl_next_update',
186
- },
187
- {
188
- name: 'expiresAt',
189
- expiresAt: true,
190
- indexed: true,
191
- },
192
- ],
193
- });
194
- }
122
+ revokedCertificateTable ??= declareRevokedCertificatesTable();
195
123
  return revokedCertificateTable;
196
124
  }
197
125
 
@@ -345,9 +273,15 @@ export async function performCRLCheck(
345
273
  const crlStatus = await checkCRLFreshness(distributionPoints, issuerPem, config);
346
274
 
347
275
  if (crlStatus.upToDate) {
348
- // CRL was just fetched/refreshed — re-check the revoked table since it may have
349
- // been populated by processRevokedCertificates during the download above
350
- const revokedEntryFresh = await (revokedTable as any).get(compositeId);
276
+ // A CRL this check downloaded decides it: by now another thread may have replaced the stored set with
277
+ // a different generation of that CRL
278
+ if (crlStatus.revokedIds) {
279
+ return crlStatus.revokedIds.has(compositeId)
280
+ ? { status: 'revoked', reason: 'unspecified', source: crlStatus.source }
281
+ : { status: 'good', source: crlStatus.source };
282
+ }
283
+ // Outside the verdict fill's transaction: on LMDB its read snapshot predates rows committed since
284
+ const revokedEntryFresh = await (revokedTable as any).get(compositeId, {});
351
285
  if (revokedEntryFresh) {
352
286
  const entry = revokedEntryFresh as any;
353
287
  const now = Date.now();
@@ -392,7 +326,7 @@ async function checkCRLFreshness(
392
326
  distributionPoints: string[],
393
327
  issuerPem: string,
394
328
  config: CRLConfig
395
- ): Promise<{ upToDate: boolean; reason?: string; source?: string }> {
329
+ ): Promise<{ upToDate: boolean; reason?: string; source?: string; revokedIds?: Set<string> }> {
396
330
  const now = Date.now();
397
331
 
398
332
  // Check each distribution point
@@ -416,8 +350,9 @@ async function checkCRLFreshness(
416
350
  }
417
351
 
418
352
  // If no valid cached CRL, download and parse fresh
353
+ let revokedIds: Set<string> | undefined;
419
354
  if (!crlData) {
420
- crlData = await downloadAndParseCRL(distributionPoint, issuerPem, config.timeout);
355
+ ({ entry: crlData, revokedIds } = await downloadAndParseCRLOnce(distributionPoint, issuerPem, config));
421
356
  }
422
357
 
423
358
  // Check if CRL is current
@@ -426,15 +361,15 @@ async function checkCRLFreshness(
426
361
  // Store in cache for future use (only if we downloaded it fresh)
427
362
  if (!cachedCRL) {
428
363
  try {
429
- await (crlTable as any).put(distributionPoint, crlData);
364
+ await (crlTable as any).put(distributionPoint, crlData, { expiresAt: crlExpiry });
430
365
  } catch {
431
366
  // Failed to cache, but continue anyway
432
367
  }
433
368
  }
434
369
 
435
- return { upToDate: true, source: distributionPoint };
370
+ return { upToDate: true, source: distributionPoint, revokedIds };
436
371
  } else if (crlExpiry + config.gracePeriod > now) {
437
- return { upToDate: true, source: distributionPoint };
372
+ return { upToDate: true, source: distributionPoint, revokedIds };
438
373
  } else {
439
374
  return { upToDate: false, reason: 'crl-expired' };
440
375
  }
@@ -451,23 +386,43 @@ async function checkCRLFreshness(
451
386
  return { upToDate: false, reason: 'no-current-crl-data' };
452
387
  }
453
388
 
389
+ // Concurrent checks of certificates from one CA share one download and replacement per distribution point on
390
+ // this thread; each replacing the same rows would make their commits conflict, and past the retry limit that
391
+ // fails the check. The key holds every input the download uses, so no check inherits another's deadline.
392
+ const crlDownloads = new Map<string, Promise<DownloadedCRL>>();
393
+
394
+ function downloadAndParseCRLOnce(distributionPoint: string, issuerPemStr: string, config: CRLConfig) {
395
+ const key = `${distributionPoint}\n${config.gracePeriod}\n${config.timeout}\n${issuerPemStr}`;
396
+ let download = crlDownloads.get(key);
397
+ if (!download) {
398
+ download = downloadAndParseCRL(distributionPoint, issuerPemStr, config).finally(() => crlDownloads.delete(key));
399
+ crlDownloads.set(key, download);
400
+ }
401
+ return download;
402
+ }
403
+
404
+ interface DownloadedCRL {
405
+ entry: CRLCacheEntry;
406
+ revokedIds: Set<string>;
407
+ }
408
+
454
409
  /**
455
410
  * Download and parse a CRL from a distribution point
456
411
  * @param distributionPoint - CRL URL
457
412
  * @param issuerPemStr - Issuer certificate for signature verification
458
- * @param timeout - Download timeout in milliseconds
459
- * @returns Parsed CRL entry for caching
413
+ * @param config - CRL configuration (download timeout, grace period)
414
+ * @returns Parsed CRL entry for caching, and the certificates it revokes
460
415
  */
461
416
  async function downloadAndParseCRL(
462
417
  distributionPoint: string,
463
418
  issuerPemStr: string,
464
- timeout: number
465
- ): Promise<CRLCacheEntry> {
419
+ config: CRLConfig
420
+ ): Promise<DownloadedCRL> {
466
421
  // Download the CRL
467
422
  // Note: Using fetch here since CRL downloads are cached and infrequent
468
423
  // (typically one per CA), so this is not a hot path
469
424
  const controller = new AbortController();
470
- const timeoutId = setTimeout(() => controller.abort(), timeout);
425
+ const timeoutId = setTimeout(() => controller.abort(), config.timeout);
471
426
 
472
427
  try {
473
428
  const response = await fetch(distributionPoint, {
@@ -477,8 +432,6 @@ async function downloadAndParseCRL(
477
432
  },
478
433
  });
479
434
 
480
- clearTimeout(timeoutId);
481
-
482
435
  if (!response.ok) {
483
436
  throw new Error(`CRL download failed: ${response.status}`);
484
437
  }
@@ -523,93 +476,80 @@ async function downloadAndParseCRL(
523
476
  this_update: thisUpdate,
524
477
  next_update: nextUpdate,
525
478
  signature_valid: signatureValid,
526
- expiresAt: nextUpdate,
527
479
  };
528
480
 
529
481
  // Process revoked certificates before returning so the revoked table is populated
530
482
  // before any subsequent lookup in performCRLCheck
531
- await processRevokedCertificates(crl, issuerPemStr, distributionPoint, nextUpdate);
532
-
533
- return cacheEntry;
483
+ const revokedIds = await processRevokedCertificates(
484
+ crl,
485
+ issuerPemStr,
486
+ distributionPoint,
487
+ nextUpdate,
488
+ config.gracePeriod ?? 0
489
+ );
490
+
491
+ return { entry: cacheEntry, revokedIds };
534
492
  } finally {
535
493
  clearTimeout(timeoutId);
536
494
  }
537
495
  }
538
496
 
539
497
  /**
540
- * Process revoked certificates from CRL and store them in the revoked certificates table
498
+ * Replace this CRL's rows in the revoked certificates table with its current revocations
541
499
  * @param crl - Parsed CRL object
542
500
  * @param issuerPemStr - Issuer certificate PEM
543
501
  * @param distributionPoint - CRL distribution point URL
544
502
  * @param nextUpdate - When this CRL expires
503
+ * @param gracePeriod - How long past nextUpdate performCRLCheck still honors a revocation
504
+ * @returns The composite ids of the revoked certificates
545
505
  */
546
506
  async function processRevokedCertificates(
547
507
  crl: pkijs.CertificateRevocationList,
548
508
  issuerPemStr: string,
549
509
  distributionPoint: string,
550
- nextUpdate: number
551
- ): Promise<void> {
510
+ nextUpdate: number,
511
+ gracePeriod: number
512
+ ): Promise<Set<string>> {
552
513
  const revokedTable = getRevokedCertificateTable();
553
514
  const issuerKeyId = extractIssuerKeyId(issuerPemStr);
554
515
  const cacheKey = distributionPoint;
516
+ const revokedIds = new Set<string>();
555
517
 
556
- // Clear existing entries for this CRL to ensure data consistency
557
- // This prevents stale revocation data when certificates are removed from updated CRLs
558
- try {
518
+ // All or nothing, so a failure leaves the previous revocations rather than a partial set that would read
519
+ // as good. Its own transaction, not the verdict fill's, so the rows are committed before performCRLCheck
520
+ // reads them back; the context's expiresAt is what each row stores as its expiry.
521
+ await transaction({ expiresAt: nextUpdate + gracePeriod }, async () => {
559
522
  await clearExistingCRLEntries(revokedTable, cacheKey);
560
- } catch (error) {
561
- logger.warn?.(`Failed to clear existing CRL entries: ${error}`);
562
- // Continue with processing - partial cleanup is better than no update
563
- }
564
523
 
565
- // Add new revoked certificates
566
- if (crl.revokedCertificates) {
567
- for (const revokedCert of crl.revokedCertificates) {
568
- try {
569
- // Extract serial number using PKI.js - same method as extractSerialNumber() function
570
- // This gives us the clean serial number without ASN.1 encoding
571
- const serialHex = revokedCert.userCertificate.valueBlock.valueHexView;
572
-
573
- if (!serialHex) {
574
- logger.warn?.('Could not extract serial number from revoked certificate');
575
- continue;
576
- }
524
+ for (const revokedCert of crl.revokedCertificates ?? []) {
525
+ // Extract serial number using PKI.js - same method as extractSerialNumber() function
526
+ // This gives us the clean serial number without ASN.1 encoding
527
+ const serialHex = revokedCert.userCertificate.valueBlock.valueHexView;
528
+ if (!serialHex)
529
+ throw new Error(`A revoked certificate in the CRL from ${distributionPoint} has no serial number`);
577
530
 
578
- const serialNumber = Array.from(serialHex)
579
- .map((b) => b.toString(16).padStart(2, '0'))
580
- .join('');
581
-
582
- const compositeId = createRevokedCertificateId(issuerKeyId, serialNumber);
583
-
584
- // Extract revocation date
585
- const revocationDate = revokedCert.revocationDate.value.getTime();
531
+ const serialNumber = Buffer.from(serialHex).toString('hex');
586
532
 
533
+ const entry: RevokedCertificateEntry = {
534
+ composite_id: createRevokedCertificateId(issuerKeyId, serialNumber),
535
+ serial_number: serialNumber,
536
+ issuer_key_id: issuerKeyId,
537
+ revocation_date: revokedCert.revocationDate.value.getTime(),
587
538
  // For now, skip complex extension parsing and just use default reason
588
- const revocationReason = 'unspecified';
589
-
590
- const entry: RevokedCertificateEntry = {
591
- composite_id: compositeId,
592
- serial_number: serialNumber,
593
- issuer_key_id: issuerKeyId,
594
- revocation_date: revocationDate,
595
- revocation_reason: revocationReason,
596
- crl_source: cacheKey,
597
- crl_next_update: nextUpdate,
598
- expiresAt: nextUpdate,
599
- };
539
+ revocation_reason: 'unspecified',
540
+ crl_source: cacheKey,
541
+ crl_next_update: nextUpdate,
542
+ };
600
543
 
601
- await (revokedTable as any).put(entry.composite_id, entry);
602
- } catch (error) {
603
- logger.warn?.(`Failed to process revoked certificate: ${error}`);
604
- // Continue with other certificates
605
- }
544
+ revokedIds.add(entry.composite_id);
545
+ await (revokedTable as any).put(entry.composite_id, entry);
606
546
  }
607
- }
547
+ });
548
+ return revokedIds;
608
549
  }
609
550
 
610
551
  /**
611
- * Clear existing revoked certificate entries for a specific CRL source
612
- * This ensures data consistency when CRLs are updated and certificates are removed
552
+ * Delete the revoked certificate entries stored for a specific CRL source
613
553
  * @param revokedTable - Harper table for revoked certificates
614
554
  * @param crlSource - CRL cache key to identify entries to remove
615
555
  */
@@ -617,27 +557,9 @@ async function clearExistingCRLEntries(
617
557
  revokedTable: ReturnType<typeof getRevokedCertificateTable>,
618
558
  crlSource: string
619
559
  ): Promise<void> {
620
- // We need to find all entries with the matching crl_source and delete them individually
621
- try {
622
- // Use Harper's search capabilities to find entries by crl_source
623
- // Note: This assumes the crl_source field is indexed (which it is)
624
- const existingEntries = (revokedTable as any).search([
625
- {
626
- attribute: 'crl_source',
627
- value: crlSource,
628
- },
629
- ]);
630
-
631
- for await (const entry of existingEntries) {
632
- try {
633
- await (revokedTable as any).delete((entry as any).composite_id);
634
- } catch (deleteError) {
635
- logger.warn?.(`Failed to delete revoked certificate entry: ${deleteError}`);
636
- // Continue with other entries
637
- }
638
- }
639
- } catch (searchError) {
640
- logger.error?.(`Failed to search for existing CRL entries: ${searchError}`);
641
- throw searchError;
560
+ const compositeIds: string[] = [];
561
+ for await (const entry of (revokedTable as any).search([{ attribute: 'crl_source', value: crlSource }])) {
562
+ compositeIds.push((entry as any).composite_id);
642
563
  }
564
+ for (const compositeId of compositeIds) await (revokedTable as any).delete(compositeId);
643
565
  }
@@ -34,7 +34,6 @@ export interface CertificateCacheEntry {
34
34
  status: CertificateStatus;
35
35
  reason?: string;
36
36
  checked_at: number;
37
- expiresAt: number;
38
37
  method: VerificationMethod;
39
38
  }
40
39
 
@@ -45,7 +44,6 @@ export interface CRLCacheEntry {
45
44
  this_update: number;
46
45
  next_update: number;
47
46
  signature_valid: boolean;
48
- expiresAt: number;
49
47
  }
50
48
 
51
49
  export interface RevokedCertificateEntry {
@@ -56,7 +54,6 @@ export interface RevokedCertificateEntry {
56
54
  revocation_reason?: string;
57
55
  crl_source: string; // Links to CRLCacheEntry.crl_id
58
56
  crl_next_update: number;
59
- expiresAt: number;
60
57
  }
61
58
 
62
59
  export interface CertificateChainEntry {
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The tables certificate verification caches into. Every node declares them in this shape at every writable
3
+ * start, whether or not it ever verifies a certificate: they replicate, and only an `expiration` stored in the
4
+ * node's own catalog arms the cleanup scan that removes the rows replicated to it. See dataLayer/DESIGN.md,
5
+ * "System table bootstrap".
6
+ */
7
+
8
+ import { databases, table, type Table } from '../../resources/databases.ts';
9
+ import { ServerError } from '../../utility/errors/hdbError.ts';
10
+ import { SYSTEM_SCHEMA_NAME } from '../../utility/hdbTerms.ts';
11
+ import { CRL_DEFAULTS, OCSP_DEFAULTS } from './configValidation.ts';
12
+ import { CRL_DEFAULT_VALIDITY_PERIOD } from './verificationConfig.ts';
13
+
14
+ export const CERTIFICATE_CACHE_TABLE = 'hdb_certificate_cache';
15
+ export const CRL_CACHE_TABLE = 'hdb_crl_cache';
16
+ export const REVOKED_CERTIFICATES_TABLE = 'hdb_revoked_certificates';
17
+
18
+ // A table's expiration bounds only a row written without an expiry of its own; every writer here sets one.
19
+ // For a verdict, the shorter of the two methods' default lifetimes.
20
+ const CERTIFICATE_CACHE_EXPIRATION_SECONDS = Math.min(OCSP_DEFAULTS.cacheTtl, CRL_DEFAULTS.cacheTtl) / 1000;
21
+ const CRL_CACHE_EXPIRATION_SECONDS = CRL_DEFAULTS.cacheTtl / 1000;
22
+ // Never shorter than a revocation's own window, which would admit a revoked certificate early.
23
+ const REVOKED_CERTIFICATES_EXPIRATION_SECONDS = (CRL_DEFAULT_VALIDITY_PERIOD + CRL_DEFAULTS.gracePeriod) / 1000;
24
+
25
+ const MAX_OUTSTANDING_EVICTIONS = 100;
26
+
27
+ export function declareCertificateCacheTable(): Table {
28
+ return table<Table>({
29
+ table: CERTIFICATE_CACHE_TABLE,
30
+ database: SYSTEM_SCHEMA_NAME,
31
+ schemaDefined: true,
32
+ expiration: CERTIFICATE_CACHE_EXPIRATION_SECONDS,
33
+ attributes: [
34
+ { name: 'certificate_id', isPrimaryKey: true },
35
+ { name: 'status' },
36
+ { name: 'reason' },
37
+ { name: 'checked_at' },
38
+ { name: 'method' },
39
+ ],
40
+ });
41
+ }
42
+
43
+ export function declareCRLCacheTable(): Table {
44
+ return table<Table>({
45
+ table: CRL_CACHE_TABLE,
46
+ database: SYSTEM_SCHEMA_NAME,
47
+ schemaDefined: true,
48
+ expiration: CRL_CACHE_EXPIRATION_SECONDS,
49
+ attributes: [
50
+ { name: 'distribution_point', isPrimaryKey: true },
51
+ { name: 'issuer_dn' },
52
+ { name: 'crl_blob' },
53
+ { name: 'this_update' },
54
+ { name: 'next_update' },
55
+ { name: 'signature_valid' },
56
+ ],
57
+ });
58
+ }
59
+
60
+ export function declareRevokedCertificatesTable(): Table {
61
+ return table<Table>({
62
+ table: REVOKED_CERTIFICATES_TABLE,
63
+ database: SYSTEM_SCHEMA_NAME,
64
+ schemaDefined: true,
65
+ expiration: REVOKED_CERTIFICATES_EXPIRATION_SECONDS,
66
+ attributes: [
67
+ { name: 'composite_id', isPrimaryKey: true },
68
+ { name: 'serial_number', indexed: true },
69
+ { name: 'issuer_key_id', indexed: true },
70
+ { name: 'revocation_date' },
71
+ { name: 'revocation_reason' },
72
+ { name: 'crl_source', indexed: true },
73
+ { name: 'crl_next_update' },
74
+ ],
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Declares the three tables and waits for any index backfill that starts, reading its outcome back from the
80
+ * catalog because a failed backfill still settles. After the first declaration on a node, it reclaims the
81
+ * verdicts cached before a verdict carried its own expiry.
82
+ */
83
+ export async function ensureCertificateVerificationTables(): Promise<void> {
84
+ const reclaimLegacyVerdicts = databases.system?.[CERTIFICATE_CACHE_TABLE]?.expirationMS === undefined;
85
+ const declared = [declareCertificateCacheTable(), declareCRLCacheTable(), declareRevokedCertificatesTable()];
86
+ if (reclaimLegacyVerdicts) await evictVerdictsWithoutExpiry(declared[0]);
87
+ const incomplete: string[] = [];
88
+ for (const Table of declared) {
89
+ await Table.indexingOperation;
90
+ for (const { name, indexed } of Table.attributes) {
91
+ if (!indexed) continue;
92
+ const descriptor = Table.dbisDB.getSync(`${Table.tableName}/${name}`);
93
+ if (descriptor?.indexingFailed || descriptor?.indexingPID) incomplete.push(`${Table.tableName}.${name}`);
94
+ }
95
+ }
96
+ if (incomplete.length > 0) {
97
+ throw new ServerError(
98
+ `The index backfill of system.${incomplete.join(', system.')} did not complete; the next start retries it`
99
+ );
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Those verdicts were stored with no expiry, so they would never be removed. Their keys are never read again
105
+ * (createCacheKey hashes a key version), so this only reclaims their space, with local evictions.
106
+ */
107
+ async function evictVerdictsWithoutExpiry(CertificateCache: any): Promise<void> {
108
+ const outstanding: unknown[] = new Array(MAX_OUTSTANDING_EVICTIONS);
109
+ let evictions = 0;
110
+ for (const { key, value, version, expiresAt } of CertificateCache.primaryStore.getRange({
111
+ start: false,
112
+ versions: true,
113
+ snapshot: false,
114
+ lazy: true,
115
+ })) {
116
+ if (value == null || expiresAt != undefined) continue;
117
+ const slot = evictions++ % MAX_OUTSTANDING_EVICTIONS;
118
+ await outstanding[slot];
119
+ outstanding[slot] = CertificateCache.evict(key, value, version);
120
+ // yields between evictions, as the table's own cleanup scan does, so LMDB can renew its read transaction
121
+ await new Promise(setImmediate);
122
+ }
123
+ await Promise.all(outstanding);
124
+ }
@@ -6,7 +6,7 @@ import { createHash } from 'node:crypto';
6
6
  import * as pkijs from 'pkijs';
7
7
  import * as asn1js from 'asn1js';
8
8
  import { loggerWithTag } from '../../utility/logging/logger.ts';
9
- import { table } from '../../resources/databases.ts';
9
+ import { declareCertificateCacheTable } from './verificationTables.ts';
10
10
  import type { PeerCertificate, CertificateChainEntry } from './types.ts';
11
11
 
12
12
  const logger = loggerWithTag('cert-verification-utils');
@@ -297,6 +297,10 @@ export function pemToBuffer(pem: string): ArrayBuffer {
297
297
  return buffer;
298
298
  }
299
299
 
300
+ // A verdict cached before verdicts carried their own expiry has none and would read as fresh forever, so the
301
+ // keys it was stored under are never read again.
302
+ const VERDICT_KEY_VERSION = 2;
303
+
300
304
  /**
301
305
  * Create a cache key for certificate verification
302
306
  * @param certPem - Certificate in PEM format
@@ -316,6 +320,7 @@ export function createCacheKey(
316
320
  issuerPem,
317
321
  method,
318
322
  ...additionalData,
323
+ keyVersion: VERDICT_KEY_VERSION,
319
324
  };
320
325
  const cacheKeyHash = createHash('sha256').update(JSON.stringify(cacheData)).digest('hex');
321
326
  return `${method}:${cacheKeyHash}`;
@@ -410,38 +415,9 @@ export function extractIssuerKeyId(certPem: string): string {
410
415
  * Get shared certificate verification cache table
411
416
  * @returns Harper table instance for certificate verification cache
412
417
  */
413
- // Cache the certificate cache table instance to avoid recreating it
414
- let certificateCacheTable: ReturnType<typeof table> | null = null;
418
+ let certificateCacheTable: ReturnType<typeof declareCertificateCacheTable> | null = null;
415
419
 
416
420
  export function getCertificateCacheTable() {
417
- if (!certificateCacheTable) {
418
- certificateCacheTable = table({
419
- table: 'hdb_certificate_cache',
420
- database: 'system',
421
- attributes: [
422
- {
423
- name: 'certificate_id',
424
- isPrimaryKey: true,
425
- },
426
- {
427
- name: 'status', // 'good', 'revoked', 'unknown'
428
- },
429
- {
430
- name: 'reason',
431
- },
432
- {
433
- name: 'checked_at',
434
- },
435
- {
436
- name: 'expiresAt',
437
- expiresAt: true,
438
- indexed: true,
439
- },
440
- {
441
- name: 'method', // 'ocsp' or 'crl'
442
- },
443
- ],
444
- });
445
- }
421
+ certificateCacheTable ??= declareCertificateCacheTable();
446
422
  return certificateCacheTable;
447
423
  }
@@ -32,7 +32,7 @@ import {
32
32
  realpathSync,
33
33
  } from 'node:fs';
34
34
  import { EventEmitter } from 'node:events';
35
- import { whenComponentsLoaded } from '../server/threads/threadServer.js';
35
+ import { whenComponentsLoaded, bootLoadsComponents } from '../server/threads/threadServer.js';
36
36
  import { thisThreadOwnsApplication } from '../server/threads/isolatedApplications.ts';
37
37
 
38
38
  type Lockdown = 'none' | 'freeze' | 'ses' | 'freeze-after-load';
@@ -88,6 +88,14 @@ export async function scopedImport(filePath: string | URL, scope?: ApplicationSc
88
88
  return runWithComponentBinding(scope?.name, () => importScoped(moduleUrl, scope));
89
89
  }
90
90
 
91
+ /**
92
+ * True where `freeze-after-load` freezes this thread's intrinsics once its boot load finishes, so no later
93
+ * load here matches a boot load.
94
+ */
95
+ export function laterLoadsMeetFrozenIntrinsics(): boolean {
96
+ return APPLICATIONS_LOCKDOWN === 'freeze-after-load' && bootLoadsComponents();
97
+ }
98
+
91
99
  async function importScoped(moduleUrl: string, scope?: ApplicationScope) {
92
100
  try {
93
101
  const loaderMode = scope?.mode;