@harperfast/harper 5.2.13 → 5.3.0-beta.1

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 (655) hide show
  1. package/agent/mcpTools.ts +1 -1
  2. package/agent/session.ts +25 -14
  3. package/bin/cliOperations.ts +58 -9
  4. package/bin/copyDb.ts +282 -59
  5. package/bin/deploySetup.ts +16 -5
  6. package/bin/harper.ts +1 -1
  7. package/bin/help.ts +4 -1
  8. package/bin/lite.ts +4 -1
  9. package/bin/restart.ts +120 -4
  10. package/bin/run.ts +6 -11
  11. package/bin/upgrade.js +7 -3
  12. package/bin/workloadIdentity.ts +119 -0
  13. package/components/Application.ts +3436 -242
  14. package/components/ApplicationScope.ts +8 -0
  15. package/components/EntryHandler.ts +59 -39
  16. package/components/OptionsWatcher.ts +440 -98
  17. package/components/RuntimeModuleTracker.ts +38 -7
  18. package/components/Scope.ts +56 -15
  19. package/components/awaitRestart.ts +84 -0
  20. package/components/componentLoader.ts +381 -32
  21. package/components/componentPreparationLock.ts +16 -5
  22. package/components/deploymentOperations.ts +4 -1
  23. package/components/deploymentRecorder.ts +9 -2
  24. package/components/mcp/adapters/harperHttp.ts +4 -0
  25. package/components/mcp/listChanged.ts +4 -0
  26. package/components/mcp/toolRegistry.ts +2 -0
  27. package/components/mcp/tools/operations.ts +9 -0
  28. package/components/mcp/tools/schemas/operationDescriptions.ts +2 -2
  29. package/components/operations.js +537 -113
  30. package/components/operationsValidation.js +98 -3
  31. package/components/packageComponent.ts +25 -1
  32. package/components/requestRestart.ts +11 -0
  33. package/components/status/ComponentStatusRegistry.ts +59 -0
  34. package/config/RootConfigWatcher.ts +240 -40
  35. package/config/configReadRetry.ts +62 -0
  36. package/config/configUtils.ts +357 -48
  37. package/config/harperConfigEnvVars.ts +170 -27
  38. package/config/parseConfigFile.ts +34 -0
  39. package/config/readConfigFileSync.ts +44 -0
  40. package/config/watcherArming.ts +59 -0
  41. package/config-root.schema.json +33 -0
  42. package/dataLayer/blobBackup.ts +160 -50
  43. package/dataLayer/delete.ts +6 -1
  44. package/dataLayer/harperBridge/ResourceBridge.ts +80 -10
  45. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
  46. package/dataLayer/hdbInfoController.ts +34 -1
  47. package/dataLayer/insert.ts +44 -1
  48. package/dataLayer/rocksdbBackup.ts +53 -10
  49. package/dataLayer/schema.ts +11 -1
  50. package/dataLayer/schemaDescribe.ts +8 -1
  51. package/dist/agent/mcpTools.js +1 -1
  52. package/dist/agent/mcpTools.js.map +1 -1
  53. package/dist/agent/session.d.ts +22 -0
  54. package/dist/agent/session.js +26 -15
  55. package/dist/agent/session.js.map +1 -1
  56. package/dist/bin/cliOperations.js +61 -9
  57. package/dist/bin/cliOperations.js.map +1 -1
  58. package/dist/bin/copyDb.d.ts +12 -1
  59. package/dist/bin/copyDb.js +248 -60
  60. package/dist/bin/copyDb.js.map +1 -1
  61. package/dist/bin/deploySetup.d.ts +2 -0
  62. package/dist/bin/deploySetup.js +11 -3
  63. package/dist/bin/deploySetup.js.map +1 -1
  64. package/dist/bin/harper.js +1 -1
  65. package/dist/bin/harper.js.map +1 -1
  66. package/dist/bin/help.js +4 -1
  67. package/dist/bin/help.js.map +1 -1
  68. package/dist/bin/lite.js +4 -1
  69. package/dist/bin/lite.js.map +1 -1
  70. package/dist/bin/restart.js +91 -6
  71. package/dist/bin/restart.js.map +1 -1
  72. package/dist/bin/run.js +4 -10
  73. package/dist/bin/run.js.map +1 -1
  74. package/dist/bin/upgrade.js +4 -3
  75. package/dist/bin/upgrade.js.map +1 -1
  76. package/dist/bin/workloadIdentity.d.ts +18 -0
  77. package/dist/bin/workloadIdentity.js +100 -0
  78. package/dist/bin/workloadIdentity.js.map +1 -0
  79. package/dist/components/Application.d.ts +235 -17
  80. package/dist/components/Application.js +3038 -238
  81. package/dist/components/Application.js.map +1 -1
  82. package/dist/components/ApplicationScope.d.ts +8 -0
  83. package/dist/components/ApplicationScope.js +7 -0
  84. package/dist/components/ApplicationScope.js.map +1 -1
  85. package/dist/components/EntryHandler.js +26 -10
  86. package/dist/components/EntryHandler.js.map +1 -1
  87. package/dist/components/OptionsWatcher.d.ts +4 -0
  88. package/dist/components/OptionsWatcher.js +440 -99
  89. package/dist/components/OptionsWatcher.js.map +1 -1
  90. package/dist/components/RuntimeModuleTracker.js +40 -6
  91. package/dist/components/RuntimeModuleTracker.js.map +1 -1
  92. package/dist/components/Scope.js +52 -13
  93. package/dist/components/Scope.js.map +1 -1
  94. package/dist/components/awaitRestart.d.ts +33 -0
  95. package/dist/components/awaitRestart.js +61 -0
  96. package/dist/components/awaitRestart.js.map +1 -0
  97. package/dist/components/componentLoader.d.ts +38 -1
  98. package/dist/components/componentLoader.js +313 -24
  99. package/dist/components/componentLoader.js.map +1 -1
  100. package/dist/components/componentPreparationLock.d.ts +5 -0
  101. package/dist/components/componentPreparationLock.js +14 -6
  102. package/dist/components/componentPreparationLock.js.map +1 -1
  103. package/dist/components/deploymentOperations.js +4 -1
  104. package/dist/components/deploymentOperations.js.map +1 -1
  105. package/dist/components/deploymentRecorder.d.ts +4 -2
  106. package/dist/components/deploymentRecorder.js +1 -0
  107. package/dist/components/deploymentRecorder.js.map +1 -1
  108. package/dist/components/mcp/adapters/harperHttp.js +4 -0
  109. package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
  110. package/dist/components/mcp/listChanged.js +5 -0
  111. package/dist/components/mcp/listChanged.js.map +1 -1
  112. package/dist/components/mcp/toolRegistry.d.ts +1 -0
  113. package/dist/components/mcp/toolRegistry.js.map +1 -1
  114. package/dist/components/mcp/tools/operations.d.ts +5 -0
  115. package/dist/components/mcp/tools/operations.js +9 -0
  116. package/dist/components/mcp/tools/operations.js.map +1 -1
  117. package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -2
  118. package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
  119. package/dist/components/operations.d.ts +28 -0
  120. package/dist/components/operations.js +476 -113
  121. package/dist/components/operations.js.map +1 -1
  122. package/dist/components/operationsValidation.js +97 -3
  123. package/dist/components/operationsValidation.js.map +1 -1
  124. package/dist/components/packageComponent.js +24 -0
  125. package/dist/components/packageComponent.js.map +1 -1
  126. package/dist/components/requestRestart.d.ts +1 -0
  127. package/dist/components/requestRestart.js +7 -0
  128. package/dist/components/requestRestart.js.map +1 -1
  129. package/dist/components/status/ComponentStatusRegistry.d.ts +0 -4
  130. package/dist/components/status/ComponentStatusRegistry.js +63 -0
  131. package/dist/components/status/ComponentStatusRegistry.js.map +1 -1
  132. package/dist/config/RootConfigWatcher.d.ts +9 -1
  133. package/dist/config/RootConfigWatcher.js +228 -37
  134. package/dist/config/RootConfigWatcher.js.map +1 -1
  135. package/dist/config/configReadRetry.d.ts +8 -0
  136. package/dist/config/configReadRetry.js +62 -0
  137. package/dist/config/configReadRetry.js.map +1 -0
  138. package/dist/config/configUtils.d.ts +11 -2
  139. package/dist/config/configUtils.js +304 -47
  140. package/dist/config/configUtils.js.map +1 -1
  141. package/dist/config/harperConfigEnvVars.d.ts +16 -0
  142. package/dist/config/harperConfigEnvVars.js +162 -25
  143. package/dist/config/harperConfigEnvVars.js.map +1 -1
  144. package/dist/config/parseConfigFile.d.ts +4 -0
  145. package/dist/config/parseConfigFile.js +35 -0
  146. package/dist/config/parseConfigFile.js.map +1 -0
  147. package/dist/config/readConfigFileSync.d.ts +1 -0
  148. package/dist/config/readConfigFileSync.js +47 -0
  149. package/dist/config/readConfigFileSync.js.map +1 -0
  150. package/dist/config/watcherArming.d.ts +15 -0
  151. package/dist/config/watcherArming.js +59 -0
  152. package/dist/config/watcherArming.js.map +1 -0
  153. package/dist/dataLayer/blobBackup.d.ts +49 -20
  154. package/dist/dataLayer/blobBackup.js +139 -50
  155. package/dist/dataLayer/blobBackup.js.map +1 -1
  156. package/dist/dataLayer/delete.js +1 -1
  157. package/dist/dataLayer/delete.js.map +1 -1
  158. package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +14 -1
  159. package/dist/dataLayer/harperBridge/ResourceBridge.js +72 -12
  160. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  161. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.d.ts +3 -1
  162. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
  163. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js.map +1 -1
  164. package/dist/dataLayer/hdbInfoController.d.ts +10 -0
  165. package/dist/dataLayer/hdbInfoController.js +30 -1
  166. package/dist/dataLayer/hdbInfoController.js.map +1 -1
  167. package/dist/dataLayer/insert.d.ts +9 -1
  168. package/dist/dataLayer/insert.js +30 -0
  169. package/dist/dataLayer/insert.js.map +1 -1
  170. package/dist/dataLayer/rocksdbBackup.d.ts +2 -2
  171. package/dist/dataLayer/rocksdbBackup.js +45 -8
  172. package/dist/dataLayer/rocksdbBackup.js.map +1 -1
  173. package/dist/dataLayer/schema.js +8 -0
  174. package/dist/dataLayer/schema.js.map +1 -1
  175. package/dist/dataLayer/schemaDescribe.js +8 -1
  176. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  177. package/dist/index.d.ts +2 -1
  178. package/dist/index.js +4 -1
  179. package/dist/index.js.map +1 -1
  180. package/dist/json/systemSchema.json +55 -0
  181. package/dist/resources/DatabaseTransaction.d.ts +77 -0
  182. package/dist/resources/DatabaseTransaction.js +586 -54
  183. package/dist/resources/DatabaseTransaction.js.map +1 -1
  184. package/dist/resources/LMDBTransaction.d.ts +2 -1
  185. package/dist/resources/LMDBTransaction.js +43 -6
  186. package/dist/resources/LMDBTransaction.js.map +1 -1
  187. package/dist/resources/PrimaryRocksDatabase.d.ts +1 -0
  188. package/dist/resources/PrimaryRocksDatabase.js +49 -5
  189. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  190. package/dist/resources/RecordEncoder.d.ts +20 -0
  191. package/dist/resources/RecordEncoder.js +110 -9
  192. package/dist/resources/RecordEncoder.js.map +1 -1
  193. package/dist/resources/RequestTarget.d.ts +2 -0
  194. package/dist/resources/RequestTarget.js.map +1 -1
  195. package/dist/resources/Resource.js +117 -24
  196. package/dist/resources/Resource.js.map +1 -1
  197. package/dist/resources/ResourceInterface.d.ts +28 -1
  198. package/dist/resources/ResourceInterface.js.map +1 -1
  199. package/dist/resources/RocksIndexStore.d.ts +6 -1
  200. package/dist/resources/RocksIndexStore.js +24 -9
  201. package/dist/resources/RocksIndexStore.js.map +1 -1
  202. package/dist/resources/RocksTransactionLogStore.d.ts +24 -1
  203. package/dist/resources/RocksTransactionLogStore.js +160 -49
  204. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  205. package/dist/resources/Table.d.ts +165 -10
  206. package/dist/resources/Table.js +1805 -303
  207. package/dist/resources/Table.js.map +1 -1
  208. package/dist/resources/analytics/write.js +10 -3
  209. package/dist/resources/analytics/write.js.map +1 -1
  210. package/dist/resources/auditStore.d.ts +181 -2
  211. package/dist/resources/auditStore.js +640 -29
  212. package/dist/resources/auditStore.js.map +1 -1
  213. package/dist/resources/blob.d.ts +129 -9
  214. package/dist/resources/blob.js +992 -114
  215. package/dist/resources/blob.js.map +1 -1
  216. package/dist/resources/branchDatabase.d.ts +48 -0
  217. package/dist/resources/branchDatabase.js +892 -0
  218. package/dist/resources/branchDatabase.js.map +1 -0
  219. package/dist/resources/crdt.js +50 -12
  220. package/dist/resources/crdt.js.map +1 -1
  221. package/dist/resources/dataLoader.js +3 -4
  222. package/dist/resources/dataLoader.js.map +1 -1
  223. package/dist/resources/databases.d.ts +157 -14
  224. package/dist/resources/databases.js +1462 -325
  225. package/dist/resources/databases.js.map +1 -1
  226. package/dist/resources/defineTable.d.ts +10 -2
  227. package/dist/resources/defineTable.js +9 -1
  228. package/dist/resources/defineTable.js.map +1 -1
  229. package/dist/resources/derivedIndexRegistry.d.ts +5 -0
  230. package/dist/resources/derivedIndexRegistry.js +68 -0
  231. package/dist/resources/derivedIndexRegistry.js.map +1 -0
  232. package/dist/resources/derivedIndexRuntime.d.ts +215 -0
  233. package/dist/resources/derivedIndexRuntime.js +2027 -0
  234. package/dist/resources/derivedIndexRuntime.js.map +1 -0
  235. package/dist/resources/graphql.d.ts +1 -1
  236. package/dist/resources/graphql.js +52 -16
  237. package/dist/resources/graphql.js.map +1 -1
  238. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +103 -9
  239. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +854 -41
  240. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  241. package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
  242. package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
  243. package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
  244. package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
  245. package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
  246. package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
  247. package/dist/resources/longLivedTransactions.d.ts +71 -0
  248. package/dist/resources/longLivedTransactions.js +358 -0
  249. package/dist/resources/longLivedTransactions.js.map +1 -0
  250. package/dist/resources/models/backendRegistry.d.ts +26 -0
  251. package/dist/resources/models/backendRegistry.js +60 -2
  252. package/dist/resources/models/backendRegistry.js.map +1 -1
  253. package/dist/resources/models/bootstrap.d.ts +33 -1
  254. package/dist/resources/models/bootstrap.js +416 -31
  255. package/dist/resources/models/bootstrap.js.map +1 -1
  256. package/dist/resources/nodeIdMapping.d.ts +5 -0
  257. package/dist/resources/nodeIdMapping.js +49 -0
  258. package/dist/resources/nodeIdMapping.js.map +1 -1
  259. package/dist/resources/recordLock.d.ts +123 -0
  260. package/dist/resources/recordLock.js +315 -0
  261. package/dist/resources/recordLock.js.map +1 -0
  262. package/dist/resources/recordLockCoordinator.d.ts +557 -0
  263. package/dist/resources/recordLockCoordinator.js +2565 -0
  264. package/dist/resources/recordLockCoordinator.js.map +1 -0
  265. package/dist/resources/replayLogs.d.ts +14 -1
  266. package/dist/resources/replayLogs.js +157 -24
  267. package/dist/resources/replayLogs.js.map +1 -1
  268. package/dist/resources/replayLogsGuards.d.ts +94 -7
  269. package/dist/resources/replayLogsGuards.js +111 -7
  270. package/dist/resources/replayLogsGuards.js.map +1 -1
  271. package/dist/resources/replicatedApplyFailure.d.ts +16 -0
  272. package/dist/resources/replicatedApplyFailure.js +63 -0
  273. package/dist/resources/replicatedApplyFailure.js.map +1 -0
  274. package/dist/resources/scheduler/scheduler.js +3 -3
  275. package/dist/resources/scheduler/scheduler.js.map +1 -1
  276. package/dist/resources/search.d.ts +10 -4
  277. package/dist/resources/search.js +283 -37
  278. package/dist/resources/search.js.map +1 -1
  279. package/dist/resources/tracked.d.ts +5 -1
  280. package/dist/resources/tracked.js +74 -23
  281. package/dist/resources/tracked.js.map +1 -1
  282. package/dist/resources/transactionBroadcast.d.ts +1 -1
  283. package/dist/resources/transactionBroadcast.js +2 -2
  284. package/dist/resources/transactionBroadcast.js.map +1 -1
  285. package/dist/security/auth.js +61 -30
  286. package/dist/security/auth.js.map +1 -1
  287. package/dist/security/authn/oidc/claims.d.ts +22 -0
  288. package/dist/security/authn/oidc/claims.js +71 -0
  289. package/dist/security/authn/oidc/claims.js.map +1 -0
  290. package/dist/security/authn/oidc/identityToken.d.ts +27 -0
  291. package/dist/security/authn/oidc/identityToken.js +111 -0
  292. package/dist/security/authn/oidc/identityToken.js.map +1 -0
  293. package/dist/security/authn/oidc/jwks.d.ts +25 -0
  294. package/dist/security/authn/oidc/jwks.js +261 -0
  295. package/dist/security/authn/oidc/jwks.js.map +1 -0
  296. package/dist/security/authn/oidc/providers/generic.d.ts +13 -0
  297. package/dist/security/authn/oidc/providers/generic.js +34 -0
  298. package/dist/security/authn/oidc/providers/generic.js.map +1 -0
  299. package/dist/security/authn/oidc/providers/githubActions.d.ts +11 -0
  300. package/dist/security/authn/oidc/providers/githubActions.js +129 -0
  301. package/dist/security/authn/oidc/providers/githubActions.js.map +1 -0
  302. package/dist/security/authn/oidc/providers/index.d.ts +37 -0
  303. package/dist/security/authn/oidc/providers/index.js +24 -0
  304. package/dist/security/authn/oidc/providers/index.js.map +1 -0
  305. package/dist/security/authn/oidc/tokenExchange.d.ts +12 -0
  306. package/dist/security/authn/oidc/tokenExchange.js +306 -0
  307. package/dist/security/authn/oidc/tokenExchange.js.map +1 -0
  308. package/dist/security/authn/oidc/trustPolicyOperations.d.ts +49 -0
  309. package/dist/security/authn/oidc/trustPolicyOperations.js +358 -0
  310. package/dist/security/authn/oidc/trustPolicyOperations.js.map +1 -0
  311. package/dist/security/authn/oidc/types.d.ts +38 -0
  312. package/dist/security/authn/oidc/types.js +6 -0
  313. package/dist/security/authn/oidc/types.js.map +1 -0
  314. package/dist/security/certificateVerification/index.js +40 -11
  315. package/dist/security/certificateVerification/index.js.map +1 -1
  316. package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
  317. package/dist/security/certificateVerification/trustedIssuers.js +79 -0
  318. package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
  319. package/dist/security/certificateVerification/types.d.ts +1 -0
  320. package/dist/security/credentialProvenance.d.ts +35 -0
  321. package/dist/security/credentialProvenance.js +51 -0
  322. package/dist/security/credentialProvenance.js.map +1 -0
  323. package/dist/security/credentialRejection.d.ts +4 -0
  324. package/dist/security/credentialRejection.js +24 -0
  325. package/dist/security/credentialRejection.js.map +1 -0
  326. package/dist/security/deferredAuthentication.d.ts +36 -0
  327. package/dist/security/deferredAuthentication.js +70 -0
  328. package/dist/security/deferredAuthentication.js.map +1 -0
  329. package/dist/security/impersonation.d.ts +21 -0
  330. package/dist/security/impersonation.js +108 -9
  331. package/dist/security/impersonation.js.map +1 -1
  332. package/dist/security/jsLoader.d.ts +6 -0
  333. package/dist/security/jsLoader.js +77 -15
  334. package/dist/security/jsLoader.js.map +1 -1
  335. package/dist/security/keys.js +301 -71
  336. package/dist/security/keys.js.map +1 -1
  337. package/dist/security/operationScope.d.ts +21 -0
  338. package/dist/security/operationScope.js +36 -0
  339. package/dist/security/operationScope.js.map +1 -0
  340. package/dist/security/permissionsTranslator.js +21 -0
  341. package/dist/security/permissionsTranslator.js.map +1 -1
  342. package/dist/security/tokenAuthentication.d.ts +19 -1
  343. package/dist/security/tokenAuthentication.js +191 -10
  344. package/dist/security/tokenAuthentication.js.map +1 -1
  345. package/dist/security/user.js +4 -3
  346. package/dist/security/user.js.map +1 -1
  347. package/dist/server/DurableSubscriptionsSession.d.ts +2 -2
  348. package/dist/server/DurableSubscriptionsSession.js +67 -10
  349. package/dist/server/DurableSubscriptionsSession.js.map +1 -1
  350. package/dist/server/REST.js +106 -2
  351. package/dist/server/REST.js.map +1 -1
  352. package/dist/server/graphqlQuerying.js +4 -0
  353. package/dist/server/graphqlQuerying.js.map +1 -1
  354. package/dist/server/http.d.ts +16 -1
  355. package/dist/server/http.js +122 -17
  356. package/dist/server/http.js.map +1 -1
  357. package/dist/server/itc/serverHandlers.js +8 -1
  358. package/dist/server/itc/serverHandlers.js.map +1 -1
  359. package/dist/server/jobs/jobProcess.js +6 -2
  360. package/dist/server/jobs/jobProcess.js.map +1 -1
  361. package/dist/server/jobs/jobs.js +4 -1
  362. package/dist/server/jobs/jobs.js.map +1 -1
  363. package/dist/server/liveSubscriptionAuth.d.ts +26 -4
  364. package/dist/server/liveSubscriptionAuth.js +105 -39
  365. package/dist/server/liveSubscriptionAuth.js.map +1 -1
  366. package/dist/server/loadRootComponents.js +49 -10
  367. package/dist/server/loadRootComponents.js.map +1 -1
  368. package/dist/server/mqtt.d.ts +2 -0
  369. package/dist/server/mqtt.js +165 -30
  370. package/dist/server/mqtt.js.map +1 -1
  371. package/dist/server/nodeName.d.ts +2 -0
  372. package/dist/server/nodeName.js +107 -23
  373. package/dist/server/nodeName.js.map +1 -1
  374. package/dist/server/serverHelpers/Headers.d.ts +27 -0
  375. package/dist/server/serverHelpers/Headers.js +145 -1
  376. package/dist/server/serverHelpers/Headers.js.map +1 -1
  377. package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
  378. package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
  379. package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
  380. package/dist/server/serverHelpers/Request.d.ts +5 -10
  381. package/dist/server/serverHelpers/Request.js +38 -136
  382. package/dist/server/serverHelpers/Request.js.map +1 -1
  383. package/dist/server/serverHelpers/contentTypes.d.ts +11 -0
  384. package/dist/server/serverHelpers/contentTypes.js +221 -40
  385. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  386. package/dist/server/serverHelpers/deployValidationState.d.ts +3 -0
  387. package/dist/server/serverHelpers/deployValidationState.js +9 -19
  388. package/dist/server/serverHelpers/deployValidationState.js.map +1 -1
  389. package/dist/server/serverHelpers/operationAuthorizationState.d.ts +12 -0
  390. package/dist/server/serverHelpers/operationAuthorizationState.js +24 -2
  391. package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -1
  392. package/dist/server/serverHelpers/registeredOperations.d.ts +5 -4
  393. package/dist/server/serverHelpers/registeredOperations.js +74 -21
  394. package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
  395. package/dist/server/serverHelpers/requestSanitization.d.ts +11 -0
  396. package/dist/server/serverHelpers/requestSanitization.js +20 -0
  397. package/dist/server/serverHelpers/requestSanitization.js.map +1 -0
  398. package/dist/server/serverHelpers/serverHandlers.js +6 -3
  399. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  400. package/dist/server/serverHelpers/serverUtilities.d.ts +18 -0
  401. package/dist/server/serverHelpers/serverUtilities.js +178 -29
  402. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  403. package/dist/server/serverHelpers/sharedMessageEncoding.d.ts +67 -0
  404. package/dist/server/serverHelpers/sharedMessageEncoding.js +280 -0
  405. package/dist/server/serverHelpers/sharedMessageEncoding.js.map +1 -0
  406. package/dist/server/serverHelpers/uwsServer.js +19 -1
  407. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  408. package/dist/server/static.js +24 -28
  409. package/dist/server/static.js.map +1 -1
  410. package/dist/server/storageReclamation.d.ts +5 -0
  411. package/dist/server/storageReclamation.js +17 -1
  412. package/dist/server/storageReclamation.js.map +1 -1
  413. package/dist/server/threads/isolatedApplications.d.ts +47 -0
  414. package/dist/server/threads/isolatedApplications.js +171 -0
  415. package/dist/server/threads/isolatedApplications.js.map +1 -0
  416. package/dist/server/threads/itc.d.ts +7 -2
  417. package/dist/server/threads/itc.js +5 -1
  418. package/dist/server/threads/itc.js.map +1 -1
  419. package/dist/server/threads/logRotationTransport.d.ts +1 -0
  420. package/dist/server/threads/logRotationTransport.js +33 -0
  421. package/dist/server/threads/logRotationTransport.js.map +1 -0
  422. package/dist/server/threads/manageThreads.d.ts +83 -3
  423. package/dist/server/threads/manageThreads.js +769 -75
  424. package/dist/server/threads/manageThreads.js.map +1 -1
  425. package/dist/server/threads/socketRouter.d.ts +1 -0
  426. package/dist/server/threads/socketRouter.js +277 -31
  427. package/dist/server/threads/socketRouter.js.map +1 -1
  428. package/dist/server/threads/threadHeapMemory.d.ts +2 -0
  429. package/dist/server/threads/threadHeapMemory.js +31 -0
  430. package/dist/server/threads/threadHeapMemory.js.map +1 -0
  431. package/dist/server/threads/threadServer.js +76 -22
  432. package/dist/server/threads/threadServer.js.map +1 -1
  433. package/dist/sqlEngine/config.d.ts +1 -3
  434. package/dist/sqlEngine/config.js +19 -16
  435. package/dist/sqlEngine/config.js.map +1 -1
  436. package/dist/sqlTranslator/index.d.ts +1 -1
  437. package/dist/sqlTranslator/index.js +30 -7
  438. package/dist/sqlTranslator/index.js.map +1 -1
  439. package/dist/upgrade/directives/5-3-0.d.ts +7 -0
  440. package/dist/upgrade/directives/5-3-0.js +148 -0
  441. package/dist/upgrade/directives/5-3-0.js.map +1 -0
  442. package/dist/upgrade/directives/directivesController.js +2 -1
  443. package/dist/upgrade/directives/directivesController.js.map +1 -1
  444. package/dist/utility/OperationFunctionCaller.js +2 -1
  445. package/dist/utility/OperationFunctionCaller.js.map +1 -1
  446. package/dist/utility/common_utils.d.ts +16 -0
  447. package/dist/utility/common_utils.js +32 -6
  448. package/dist/utility/common_utils.js.map +1 -1
  449. package/dist/utility/componentNames.d.ts +8 -0
  450. package/dist/utility/componentNames.js +12 -1
  451. package/dist/utility/componentNames.js.map +1 -1
  452. package/dist/utility/environment/environmentManager.js +3 -6
  453. package/dist/utility/environment/environmentManager.js.map +1 -1
  454. package/dist/utility/environment/systemInformation.d.ts +1 -0
  455. package/dist/utility/environment/systemInformation.js +1 -0
  456. package/dist/utility/environment/systemInformation.js.map +1 -1
  457. package/dist/utility/errors/commonErrors.d.ts +2 -0
  458. package/dist/utility/errors/commonErrors.js +2 -0
  459. package/dist/utility/errors/commonErrors.js.map +1 -1
  460. package/dist/utility/errors/hdbError.d.ts +33 -0
  461. package/dist/utility/errors/hdbError.js +58 -1
  462. package/dist/utility/errors/hdbError.js.map +1 -1
  463. package/dist/utility/globalSchema.d.ts +18 -0
  464. package/dist/utility/hdbTerms.d.ts +18 -0
  465. package/dist/utility/hdbTerms.js +20 -2
  466. package/dist/utility/hdbTerms.js.map +1 -1
  467. package/dist/utility/logging/harper_logger.d.ts +2 -0
  468. package/dist/utility/logging/harper_logger.js +286 -30
  469. package/dist/utility/logging/harper_logger.js.map +1 -1
  470. package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
  471. package/dist/utility/logging/logGenerationCoordinator.js +184 -0
  472. package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
  473. package/dist/utility/logging/logRotation.d.ts +46 -0
  474. package/dist/utility/logging/logRotation.js +365 -0
  475. package/dist/utility/logging/logRotation.js.map +1 -0
  476. package/dist/utility/logging/logRotator.d.ts +1 -1
  477. package/dist/utility/logging/logRotator.js +192 -85
  478. package/dist/utility/logging/logRotator.js.map +1 -1
  479. package/dist/utility/nodeIdentity.d.ts +9 -0
  480. package/dist/utility/nodeIdentity.js +58 -0
  481. package/dist/utility/nodeIdentity.js.map +1 -0
  482. package/dist/utility/npmUtilities.js +11 -7
  483. package/dist/utility/npmUtilities.js.map +1 -1
  484. package/dist/utility/operationPermissions.d.ts +3 -1
  485. package/dist/utility/operationPermissions.js +16 -1
  486. package/dist/utility/operationPermissions.js.map +1 -1
  487. package/dist/utility/operation_authorization.d.ts +10 -7
  488. package/dist/utility/operation_authorization.js +212 -41
  489. package/dist/utility/operation_authorization.js.map +1 -1
  490. package/dist/utility/watchPath.d.ts +29 -0
  491. package/dist/utility/watchPath.js +68 -0
  492. package/dist/utility/watchPath.js.map +1 -0
  493. package/dist/utility/watcherFallback.d.ts +41 -0
  494. package/dist/utility/watcherFallback.js +153 -0
  495. package/dist/utility/watcherFallback.js.map +1 -1
  496. package/dist/validation/configValidator.d.ts +12 -0
  497. package/dist/validation/configValidator.js +205 -75
  498. package/dist/validation/configValidator.js.map +1 -1
  499. package/dist/validation/installValidator.js +12 -0
  500. package/dist/validation/installValidator.js.map +1 -1
  501. package/dist/validation/validationWrapper.d.ts +11 -0
  502. package/dist/validation/validationWrapper.js +16 -3
  503. package/dist/validation/validationWrapper.js.map +1 -1
  504. package/index.ts +8 -0
  505. package/json/systemSchema.json +55 -0
  506. package/npm-shrinkwrap.json +286 -194
  507. package/package.json +15 -7
  508. package/resources/DESIGN.md +219 -52
  509. package/resources/DatabaseTransaction.ts +657 -52
  510. package/resources/LMDBTransaction.ts +46 -6
  511. package/resources/PrimaryRocksDatabase.ts +46 -7
  512. package/resources/RecordEncoder.ts +124 -9
  513. package/resources/RequestTarget.ts +2 -0
  514. package/resources/Resource.ts +114 -22
  515. package/resources/ResourceInterface.ts +31 -0
  516. package/resources/RocksIndexStore.ts +30 -9
  517. package/resources/RocksTransactionLogStore.ts +188 -51
  518. package/resources/Table.ts +1960 -301
  519. package/resources/analytics/write.ts +10 -3
  520. package/resources/auditStore.ts +647 -32
  521. package/resources/blob.ts +1029 -110
  522. package/resources/branchDatabase.ts +941 -0
  523. package/resources/crdt.ts +76 -12
  524. package/resources/dataLoader.ts +3 -4
  525. package/resources/databases.ts +1584 -320
  526. package/resources/defineTable.ts +18 -2
  527. package/resources/derivedIndexRegistry.ts +56 -0
  528. package/resources/derivedIndexRuntime.ts +2292 -0
  529. package/resources/graphql.ts +72 -17
  530. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +911 -48
  531. package/resources/indexes/hnswDerivedIndex.ts +531 -0
  532. package/resources/indexes/hnswPlaneBinding.ts +174 -0
  533. package/resources/longLivedTransactions.ts +360 -0
  534. package/resources/models/backendRegistry.ts +84 -2
  535. package/resources/models/bootstrap.ts +473 -28
  536. package/resources/nodeIdMapping.ts +50 -0
  537. package/resources/recordLock.ts +419 -0
  538. package/resources/recordLockCoordinator.ts +3043 -0
  539. package/resources/replayLogs.ts +152 -26
  540. package/resources/replayLogsGuards.ts +171 -8
  541. package/resources/replicatedApplyFailure.ts +77 -0
  542. package/resources/scheduler/scheduler.ts +4 -4
  543. package/resources/search.ts +288 -46
  544. package/resources/tracked.ts +73 -22
  545. package/resources/transactionBroadcast.ts +3 -3
  546. package/security/auth.ts +68 -29
  547. package/security/authn/oidc/claims.ts +72 -0
  548. package/security/authn/oidc/identityToken.ts +129 -0
  549. package/security/authn/oidc/jwks.ts +260 -0
  550. package/security/authn/oidc/providers/generic.ts +40 -0
  551. package/security/authn/oidc/providers/githubActions.ts +137 -0
  552. package/security/authn/oidc/providers/index.ts +52 -0
  553. package/security/authn/oidc/tokenExchange.ts +300 -0
  554. package/security/authn/oidc/trustPolicyOperations.ts +343 -0
  555. package/security/authn/oidc/types.ts +41 -0
  556. package/security/certificateVerification/index.ts +54 -13
  557. package/security/certificateVerification/trustedIssuers.ts +76 -0
  558. package/security/certificateVerification/types.ts +1 -0
  559. package/security/credentialProvenance.ts +47 -0
  560. package/security/credentialRejection.ts +22 -0
  561. package/security/deferredAuthentication.ts +71 -0
  562. package/security/impersonation.ts +117 -12
  563. package/security/jsLoader.ts +83 -18
  564. package/security/keys.ts +298 -72
  565. package/security/operationScope.ts +33 -0
  566. package/security/permissionsTranslator.js +23 -0
  567. package/security/tokenAuthentication.ts +233 -12
  568. package/security/user.ts +4 -3
  569. package/server/DESIGN.md +194 -16
  570. package/server/DurableSubscriptionsSession.ts +71 -11
  571. package/server/REST.ts +115 -4
  572. package/server/graphqlQuerying.ts +4 -0
  573. package/server/http.ts +133 -20
  574. package/server/itc/serverHandlers.js +8 -1
  575. package/server/jobs/jobProcess.ts +8 -2
  576. package/server/jobs/jobs.ts +4 -1
  577. package/server/liveSubscriptionAuth.ts +129 -46
  578. package/server/loadRootComponents.js +50 -8
  579. package/server/mqtt.ts +179 -38
  580. package/server/nodeName.ts +103 -21
  581. package/server/serverHelpers/Headers.ts +135 -0
  582. package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
  583. package/server/serverHelpers/Request.ts +33 -131
  584. package/server/serverHelpers/contentTypes.ts +217 -36
  585. package/server/serverHelpers/deployValidationState.ts +24 -13
  586. package/server/serverHelpers/operationAuthorizationState.ts +34 -3
  587. package/server/serverHelpers/registeredOperations.ts +79 -22
  588. package/server/serverHelpers/requestSanitization.ts +15 -0
  589. package/server/serverHelpers/serverHandlers.js +6 -3
  590. package/server/serverHelpers/serverUtilities.ts +232 -40
  591. package/server/serverHelpers/sharedMessageEncoding.ts +307 -0
  592. package/server/serverHelpers/uwsServer.ts +17 -2
  593. package/server/static.ts +23 -29
  594. package/server/storageReclamation.ts +15 -2
  595. package/server/threads/isolatedApplications.ts +157 -0
  596. package/server/threads/itc.js +11 -1
  597. package/server/threads/logRotationTransport.ts +40 -0
  598. package/server/threads/manageThreads.js +771 -66
  599. package/server/threads/socketRouter.ts +291 -30
  600. package/server/threads/threadHeapMemory.ts +26 -0
  601. package/server/threads/threadServer.js +73 -22
  602. package/sqlTranslator/index.ts +31 -8
  603. package/studio/web/assets/{Chat-4RrB5134.js → Chat-D3j-1yY1.js} +1 -1
  604. package/studio/web/assets/{FloatingChat-omlNMDcJ.js → FloatingChat-BxJGYcfB.js} +3 -3
  605. package/studio/web/assets/{apiToken-Bke3wvfZ.js → apiToken-CT55oWOe.js} +1 -1
  606. package/studio/web/assets/{applications-DvFDYJgK.js → applications-D9Ct9_vm.js} +1 -1
  607. package/studio/web/assets/{cssMode-C1vRa7zh.js → cssMode-DV8H7VwA.js} +1 -1
  608. package/studio/web/assets/{editor-3XRWEDWX.js → editor-uatc0unt.js} +1 -1
  609. package/studio/web/assets/{html-CTY5tdMr.js → html-Bm6D6paN.js} +1 -1
  610. package/studio/web/assets/{htmlMode-CG1vSD9t.js → htmlMode-CEn7tpLG.js} +1 -1
  611. package/studio/web/assets/{index-Dfpeofdu.js → index-BIXW6Pu4.js} +5 -5
  612. package/studio/web/assets/{index.lazy-B1VOIv-t.js → index.lazy-UI7L-Vrk.js} +1 -1
  613. package/studio/web/assets/{javascript-CJeJzGnI.js → javascript-CJ0G3AFZ.js} +1 -1
  614. package/studio/web/assets/{jsonMode-CYPBwM82.js → jsonMode-DQADAYEa.js} +1 -1
  615. package/studio/web/assets/{languageServices-IKH4GUHl.js → languageServices-CAQJXWcI.js} +1 -1
  616. package/studio/web/assets/{lspLanguageFeatures-BC8_gxKG.js → lspLanguageFeatures-CCQ8P5sY.js} +1 -1
  617. package/studio/web/assets/{notifications-BfKBpYcq.js → notifications-BbxTU6Aw.js} +1 -1
  618. package/studio/web/assets/{notifications-DFbArTfC.js → notifications-Cvb3P1lB.js} +1 -1
  619. package/studio/web/assets/{profile-Q4-T6c-S.js → profile-Yyb7gsvL.js} +1 -1
  620. package/studio/web/assets/{regions-CtkV0xje.js → regions-OgjGHlU5.js} +1 -1
  621. package/studio/web/assets/{register-1ZZuMsiA.js → register-6qwNEOY3.js} +2 -2
  622. package/studio/web/assets/{setComponentFile-ZWLcWv5X.js → setComponentFile-BilDMtgB.js} +1 -1
  623. package/studio/web/assets/{setup-DWprJyJy.js → setup-J6qJ7OIU.js} +2 -2
  624. package/studio/web/assets/{status--aNm8isn.js → status-0RWGcfyD.js} +1 -1
  625. package/studio/web/assets/{toggleHighContrast-Cq_lt3XD.js → toggleHighContrast-BIn-vErT.js} +1 -1
  626. package/studio/web/assets/{tsMode-pjgytARx.js → tsMode-DgUXku4d.js} +1 -1
  627. package/studio/web/assets/{typescript-Cdg0mqUh.js → typescript-C9orXcsM.js} +1 -1
  628. package/studio/web/assets/{useEntityRestURL-RZhaY8Rn.js → useEntityRestURL-BEoXXbUB.js} +1 -1
  629. package/studio/web/assets/{workers-BgoXIqQe.js → workers-JVzSDmgx.js} +1 -1
  630. package/studio/web/assets/{xml-BwSeDMiP.js → xml-Cq-S8S4X.js} +1 -1
  631. package/studio/web/assets/{yaml-DotCUG5l.js → yaml-sfoRdh1M.js} +1 -1
  632. package/studio/web/index.html +1 -1
  633. package/upgrade/directives/5-3-0.ts +132 -0
  634. package/upgrade/directives/directivesController.ts +2 -1
  635. package/utility/OperationFunctionCaller.ts +2 -1
  636. package/utility/common_utils.ts +30 -5
  637. package/utility/componentNames.ts +12 -0
  638. package/utility/environment/environmentManager.ts +3 -7
  639. package/utility/environment/systemInformation.ts +7 -0
  640. package/utility/errors/commonErrors.ts +4 -0
  641. package/utility/errors/hdbError.ts +57 -0
  642. package/utility/hdbTerms.ts +19 -0
  643. package/utility/logging/harper_logger.ts +278 -26
  644. package/utility/logging/logGenerationCoordinator.ts +196 -0
  645. package/utility/logging/logRotation.ts +367 -0
  646. package/utility/logging/logRotator.ts +213 -81
  647. package/utility/nodeIdentity.ts +45 -0
  648. package/utility/npmUtilities.ts +12 -8
  649. package/utility/operationPermissions.ts +18 -1
  650. package/utility/operation_authorization.ts +231 -42
  651. package/utility/watchPath.ts +63 -0
  652. package/utility/watcherFallback.ts +148 -0
  653. package/validation/configValidator.ts +215 -75
  654. package/validation/installValidator.ts +15 -0
  655. package/validation/validationWrapper.ts +18 -4
package/security/keys.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  import * as path from 'path';
4
- import { watch } from 'chokidar';
5
4
  import * as fs from 'fs-extra';
6
5
  import * as forge from 'node-forge';
7
6
  import * as net from 'net';
@@ -33,7 +32,16 @@ export const getPrivateKeys = () => privateKeys;
33
32
  import { readFileSync, statSync } from 'node:fs';
34
33
  import { getTicketKeys, onMessageFromWorkers } from '../server/threads/manageThreads.js';
35
34
  import { isMainThread } from 'worker_threads';
35
+ import {
36
+ POLLING_FALLBACK_OPTIONS,
37
+ claimLostNativeWatchError,
38
+ guardedWatch,
39
+ isWatcherExhaustionError,
40
+ warnWatcherFallback,
41
+ } from '../utility/watcherFallback.ts';
42
+ import { resolveWatchTarget } from '../utility/watchPath.ts';
36
43
  import { TLSSocket } from 'node:tls';
44
+ import { publishTrustedAuthorities } from './certificateVerification/trustedIssuers.ts';
37
45
 
38
46
  const CERT_VALIDITY_DAYS = 3650;
39
47
  // Default interval (ms) for the periodic cert-file re-read safety net. The chokidar (inotify)
@@ -149,6 +157,12 @@ const privateKeys = new Map();
149
157
  // subscription and the private-key hot-reload trigger so both coalesce on the same cadence.
150
158
  const TLS_REBUILD_DEBOUNCE_MS = 1500;
151
159
 
160
+ // Self-retry backoff cap: a permanently bad record must not cost every selector on every
161
+ // thread a table scan + X509 parse per debounce interval, forever.
162
+ const TLS_FAILURE_RETRY_MAX_DELAY_MS = 300_000;
163
+ // While a failure signature is unchanged, repeat occurrences log a summary at most this often.
164
+ const TLS_FAILURE_SUMMARY_INTERVAL_MS = 3_600_000;
165
+
152
166
  // Debounced rebuild triggers, one per live server TLS selector (registered in createTLSSelector's
153
167
  // initialize). When a private key is hot-reloaded on this thread, every live selector re-runs
154
168
  // updateTLS so a secure context built with a stale key — or built before this key arrived — is
@@ -269,7 +283,9 @@ export function loadCertificates() {
269
283
  return;
270
284
  }
271
285
 
272
- promise = certificateTable.put({
286
+ // Returned so loadAndWatch can roll back its mtime latch if the write fails —
287
+ // assigned as well to preserve loadCertificates()'s awaited-return contract.
288
+ return (promise = certificateTable.put({
273
289
  name: certCn,
274
290
  uses: config.uses ?? (configKey.includes('operations') ? ['operations-api'] : []),
275
291
  ciphers: config.ciphers,
@@ -286,7 +302,7 @@ export function loadCertificates() {
286
302
  valid_from: x509Cert.validFrom,
287
303
  valid_to: x509Cert.validTo,
288
304
  },
289
- });
305
+ }));
290
306
  },
291
307
  ca ? 'certificate authority' : 'certificate'
292
308
  );
@@ -338,24 +354,65 @@ const certificateWatchPollers = new Map<string, () => void>();
338
354
  */
339
355
  function loadAndWatch(path, loadCert, type) {
340
356
  let lastModified;
341
- const loadFile = (path, stats) => {
357
+ const loadFile = (path, stats?) => {
358
+ // The latch dedupes chokidar/poll but must mean "last successfully APPLIED", or a failed
359
+ // apply is deduplicated forever (#2382). Rollbacks are equality-guarded so an old failure
360
+ // can't unlatch a newer successful reload.
361
+ const previousModified = lastModified;
362
+ let modified;
342
363
  try {
343
364
  // chokidar's 'change' event omits stats unless alwaysStat is set (default off in v4), so
344
365
  // stat the file here when it's missing — otherwise the inotify fast path would throw and
345
366
  // silently never reload, leaving only the periodic poll to catch the change.
346
- let modified = (stats ?? statSync(path)).mtimeMs;
367
+ modified = (stats ?? statSync(path)).mtimeMs;
347
368
  if (modified && modified !== lastModified) {
348
369
  if (lastModified && isMainThread) logger.warn?.(`Reloading ${type}:`, path);
349
370
  lastModified = modified;
350
- loadCert(readPEM(path));
371
+ const applied = loadCert(readPEM(path));
372
+ if (typeof (applied as any)?.then === 'function') {
373
+ (applied as Promise<unknown>).catch((error) => {
374
+ logger.error?.(`Error applying ${type}:`, path, error);
375
+ if (lastModified === modified) lastModified = previousModified;
376
+ });
377
+ }
351
378
  }
352
379
  } catch (error) {
353
380
  logger.error?.(`Error loading ${type}:`, path, error);
381
+ if (modified !== undefined && lastModified === modified) lastModified = previousModified;
354
382
  }
355
383
  };
356
384
  if (fs.existsSync(path)) loadFile(path, statSync(path));
357
385
  else logger.error?.(`${type} file not found:`, path);
358
- watch(path, { persistent: false }).on('change', loadFile);
386
+ const watchTarget = resolveWatchTarget(path);
387
+ let usingPolling = watchTarget.mustPoll;
388
+ let liveWatcher;
389
+ const openWatcher = () => {
390
+ const opened = (liveWatcher = guardedWatch(watchTarget.path, {
391
+ persistent: false,
392
+ ...(usingPolling ? POLLING_FALLBACK_OPTIONS : {}),
393
+ }));
394
+ opened
395
+ // The event carries the watched spelling, which is not the configured one once canonicalized;
396
+ // reload through the configured path so a retargeted link is followed.
397
+ .on('change', () => loadFile(path))
398
+ // chokidar emits 'error' unguarded for anything but ENOENT/ENOTDIR.
399
+ .on('error', (error) => {
400
+ if (claimLostNativeWatchError(error)) return;
401
+ if (isWatcherExhaustionError(error)) {
402
+ if (usingPolling || liveWatcher !== opened) return;
403
+ warnWatcherFallback(path);
404
+ usingPolling = true;
405
+ Promise.resolve()
406
+ .then(() => opened.close())
407
+ .catch(() => {})
408
+ .then(openWatcher)
409
+ .catch((error) => logger.error?.(`Could not reopen the ${type} watch on polling:`, path, error));
410
+ return;
411
+ }
412
+ logger.error?.(`Error watching ${type}:`, path, error);
413
+ });
414
+ };
415
+ openWatcher();
359
416
 
360
417
  // Periodic re-read safety net. For certificates, this runs on the main thread only — workers
361
418
  // receive cert updates via the hdb_certificate table subscription, so polling the cert file on
@@ -392,13 +449,15 @@ function loadAndWatch(path, loadCert, type) {
392
449
  }
393
450
 
394
451
  function getHost() {
395
- let url = getThisNodeUrl();
396
- if (url == null) {
452
+ // urlToNodeName returns undefined for a missing or malformed node/replication URL, so a bad
453
+ // replication.url falls back to the default host here rather than throwing during cert review.
454
+ const name = urlToNodeName(getThisNodeUrl());
455
+ if (name == null) {
397
456
  const host = CERT_DOMAINS[0];
398
457
  logger.info?.('node url is missing from harperdb-config.yaml, using default host' + host);
399
458
  return host;
400
459
  }
401
- return urlToNodeName(url);
460
+ return name;
402
461
  }
403
462
 
404
463
  export function getCommonName() {
@@ -990,10 +1049,79 @@ export function createTLSSelector(type, mtlsOptions?, liveReload = true): any {
990
1049
  let subscribedTable = null;
991
1050
  let activeSubscription: Promise<any> | null = null;
992
1051
  return ((SNICallback as any).ready = new Promise<void>((resolve, reject) => {
1052
+ // Pass-level failure before `.ready` settles still rejects (boot surfaces it); after,
1053
+ // the only safe response is to keep serving live state and retry.
1054
+ let readySettled = false;
1055
+ const settle = (value?) => {
1056
+ readySettled = true;
1057
+ resolve(value);
1058
+ };
1059
+ // Self-retry state for passes with per-record failures: the signature identifies the
1060
+ // failure set, the delay backs off while it is unchanged, and summaries are rate-limited.
1061
+ let failureSignature = '';
1062
+ let failureRetryDelay = TLS_REBUILD_DEBOUNCE_MS;
1063
+ let failureRetryTimer;
1064
+ let failureRetryCount = 0;
1065
+ let failureLastSummaryAt = 0;
1066
+ const scheduleFailureRetry = () => {
1067
+ // An armed external rebuild (or an armed retry) will run a pass that re-arms if needed.
1068
+ if (failureRetryTimer || rebuildTimer) return;
1069
+ failureRetryTimer = setTimeout(() => {
1070
+ failureRetryTimer = undefined;
1071
+ updateTLS();
1072
+ }, failureRetryDelay).unref();
1073
+ failureRetryDelay = Math.min(failureRetryDelay * 2, TLS_FAILURE_RETRY_MAX_DELAY_MS);
1074
+ };
1075
+ // Log each failure on a new/changed failure set; while unchanged, stay silent apart from a
1076
+ // rate-limited summary — an un-throttled 1.5s retry would emit ~57k lines/day per selector.
1077
+ const reportFailures = (failures: { cert: any; error: any }[]) => {
1078
+ const signature = failures
1079
+ .map(({ cert, error }) => `${cert.name}: ${error?.message}`)
1080
+ .sort()
1081
+ .join('; ');
1082
+ if (signature !== failureSignature) {
1083
+ failureSignature = signature;
1084
+ failureRetryDelay = TLS_REBUILD_DEBOUNCE_MS;
1085
+ failureRetryCount = 0;
1086
+ failureLastSummaryAt = Date.now();
1087
+ // A timer armed for the previous signature would run the next pass on its stale
1088
+ // (possibly maxed-out) delay; re-arm fresh for the new failure set.
1089
+ if (failureRetryTimer) {
1090
+ clearTimeout(failureRetryTimer);
1091
+ failureRetryTimer = undefined;
1092
+ }
1093
+ for (const { cert, error } of failures)
1094
+ logger.error?.('Error applying TLS for', cert.name, `on the '${type}' listener`, error);
1095
+ } else {
1096
+ failureRetryCount++;
1097
+ // Stays at error: a stuck rotation must keep an alertable signal — the retained cert
1098
+ // is aging toward its notAfter the whole time.
1099
+ if (Date.now() - failureLastSummaryAt >= TLS_FAILURE_SUMMARY_INTERVAL_MS) {
1100
+ failureLastSummaryAt = Date.now();
1101
+ logger.error?.(
1102
+ `TLS rebuild for the '${type}' listener is still failing for [${failures
1103
+ .map(({ cert }) => cert.name)
1104
+ .join(', ')}] after ${failureRetryCount} retries; serving the last good state`
1105
+ );
1106
+ }
1107
+ }
1108
+ };
1109
+ const clearFailureState = () => {
1110
+ if (failureSignature) {
1111
+ logger.warn?.(`TLS rebuild for the '${type}' listener recovered; all certificates applied`);
1112
+ }
1113
+ failureSignature = '';
1114
+ failureRetryDelay = TLS_REBUILD_DEBOUNCE_MS;
1115
+ failureRetryCount = 0;
1116
+ if (failureRetryTimer) {
1117
+ clearTimeout(failureRetryTimer);
1118
+ failureRetryTimer = undefined;
1119
+ }
1120
+ };
993
1121
  function updateTLS() {
994
1122
  try {
995
1123
  if (databases === undefined) {
996
- resolve();
1124
+ settle();
997
1125
  return;
998
1126
  }
999
1127
  if (databases.system?.hdb_certificate === undefined) {
@@ -1023,19 +1151,19 @@ export function createTLSSelector(type, mtlsOptions?, liveReload = true): any {
1023
1151
  scheduleRebuild();
1024
1152
  return;
1025
1153
  }
1026
- // Guards above have returned before touching secureContexts/caCerts, so the retry
1027
- // path never wipes a previously-published (non-empty) state before failing to
1028
- // replace it.
1029
- secureContexts.clear();
1030
- caCerts.clear();
1154
+ // Transactional publication (#2382): build the whole replacement state off to the
1155
+ // side and reconcile the live maps only after the pass completes, so a failure of
1156
+ // any shape leaves the currently-served state intact.
1157
+ const candidateContexts = new Map();
1158
+ const candidateCAs = new Map();
1159
+ let candidateHasWildcards = false;
1031
1160
  let bestQuality = 0;
1032
- // Whether THIS pass produced a default context. The `defaultContext` closure variable
1033
- // is deliberately never reset (a transient zero-cert pass must keep serving the prior
1034
- // default while the retry below waits for certs to come back), so it can't be used to
1035
- // ask "did this pass find anything?" — after the first successful pass it is truthy
1036
- // forever, which would let a later transient zero-cert rebuild skip the retry and
1037
- // publish an empty certificates list (the #1998 symptom) on the post-boot path.
1161
+ let candidateDefault;
1162
+ // Whether THIS pass produced a default. The `defaultContext` closure variable is never
1163
+ // reset (a transient zero-cert pass keeps serving the prior default), so keying the
1164
+ // zero-cert retry off it would republish empty post-boot — the #1998 symptom.
1038
1165
  let defaultContextSetThisPass = false;
1166
+ const failedThisPass: { cert: any; error: any }[] = [];
1039
1167
  // Track the actual table instance, not just whether we've ever subscribed: resetDatabases()
1040
1168
  // (copy_db, ITC restart handling) replaces databases.system.hdb_certificate with a new
1041
1169
  // table object, and a boolean flag would never re-subscribe to it, permanently losing
@@ -1065,24 +1193,28 @@ export function createTLSSelector(type, mtlsOptions?, liveReload = true): any {
1065
1193
  previousSubscription.then((subscription: any) => subscription?.end?.()).catch(() => {});
1066
1194
  }
1067
1195
  }
1068
- for (const cert of databases.system.hdb_certificate.search([])) {
1196
+ // One snapshot drives both loops: separate search() calls own separate read
1197
+ // snapshots, and a write committing between them could publish a renewed leaf
1198
+ // against the previous CA set.
1199
+ const certRecords = Array.from(databases.system.hdb_certificate.search([])) as any[];
1200
+ for (const cert of certRecords) {
1069
1201
  const certificate = cert.certificate;
1070
1202
  let certParsed;
1071
1203
  try {
1072
1204
  certParsed = new X509Certificate(certificate);
1073
1205
  } catch (error) {
1074
- // One unparseable record shouldn't abort the whole pass before any context is
1075
- // built — the second loop's per-cert try/catch doesn't cover this loop.
1076
- logger.error?.('Skipping unparseable certificate record', cert.name, error);
1206
+ // A pass failure like any other (throttle + retry). Its caCerts entry can't be
1207
+ // retained — the subject is unrecoverable from a PEM that won't parse.
1208
+ failedThisPass.push({ cert, error });
1077
1209
  continue;
1078
1210
  }
1079
1211
  if (cert.is_authority) {
1080
1212
  (certParsed as any).asString = certificate;
1081
- caCerts.set(certParsed.subject, certificate);
1213
+ candidateCAs.set(certParsed.subject, certificate);
1082
1214
  }
1083
1215
  }
1084
1216
 
1085
- for (const cert of databases.system.hdb_certificate.search([])) {
1217
+ for (const cert of certRecords) {
1086
1218
  try {
1087
1219
  if (cert.is_authority) {
1088
1220
  continue;
@@ -1100,8 +1232,8 @@ export function createTLSSelector(type, mtlsOptions?, liveReload = true): any {
1100
1232
 
1101
1233
  let certificate = cert.certificate;
1102
1234
  const certParsed = new X509Certificate(certificate);
1103
- if (caCerts.has(certParsed.issuer)) {
1104
- certificate += '\n' + caCerts.get(certParsed.issuer);
1235
+ if (candidateCAs.has(certParsed.issuer)) {
1236
+ certificate += '\n' + candidateCAs.get(certParsed.issuer);
1105
1237
  }
1106
1238
  if (!private_key || !certificate) {
1107
1239
  throw new Error('Missing private key or certificate for secure server');
@@ -1109,8 +1241,10 @@ export function createTLSSelector(type, mtlsOptions?, liveReload = true): any {
1109
1241
  const secureOptions = {
1110
1242
  ciphers: cert.ciphers,
1111
1243
  ticketKeys: getTicketKeys(),
1112
- availableCAs: caCerts, // preserve the record of caCerts even if not used for mTLS here
1113
- ca: mtlsOptions && Array.from(caCerts.values()),
1244
+ // the live map, not the candidate: its identity survives publication, so contexts
1245
+ // keep seeing the current CA set after later rebuilds
1246
+ availableCAs: caCerts,
1247
+ ca: mtlsOptions && Array.from(candidateCAs.values()),
1114
1248
  cert: certificate,
1115
1249
  key: private_key,
1116
1250
  key_file: cert.private_key_name,
@@ -1126,7 +1260,7 @@ export function createTLSSelector(type, mtlsOptions?, liveReload = true): any {
1126
1260
  (secureContext as any).name = cert.name;
1127
1261
  (secureContext as any).options = secureOptions;
1128
1262
  (secureContext as any).quality = quality;
1129
- (secureContext as any).certificateAuthorities = Array.from(caCerts);
1263
+ (secureContext as any).certificateAuthorities = Array.from(candidateCAs);
1130
1264
  // we store the first 100 bytes of the certificate just for debug logging
1131
1265
  (secureContext as any).certStart = certificate.toString().slice(0, 100);
1132
1266
  // we want to configure SNI handling to pick the right certificate based on all the registered SANs
@@ -1134,14 +1268,14 @@ export function createTLSSelector(type, mtlsOptions?, liveReload = true): any {
1134
1268
  for (let hostname of hostnames) {
1135
1269
  if (hostname) {
1136
1270
  if (hostname[0] === '*') {
1137
- hasWildcards = true;
1271
+ candidateHasWildcards = true;
1138
1272
  hostname = hostname.slice(1);
1139
1273
  }
1140
1274
  // we use this certificate if it has a higher quality than the existing one for this hostname
1141
- let existingCertQuality = secureContexts.get(hostname)?.quality ?? 0;
1275
+ let existingCertQuality = candidateContexts.get(hostname)?.quality ?? 0;
1142
1276
  logger.trace?.('Assigning TLS for hostname', hostname, 'if', quality, '>', existingCertQuality);
1143
1277
  if (quality > existingCertQuality) {
1144
- secureContexts.set(hostname, secureContext);
1278
+ candidateContexts.set(hostname, secureContext);
1145
1279
  }
1146
1280
  } else {
1147
1281
  logger.error?.('No hostname found for certificate at', (tls as any).certificate);
@@ -1163,55 +1297,141 @@ export function createTLSSelector(type, mtlsOptions?, liveReload = true): any {
1163
1297
  );
1164
1298
  if (quality > bestQuality /* && hasIpAddress*/) {
1165
1299
  // we use this certificate as the default if it has a higher quality than the existing one
1166
- (SNICallback as any).defaultContext = defaultContext = secureContext;
1300
+ candidateDefault = secureContext;
1167
1301
  defaultContextSetThisPass = true;
1168
1302
  bestQuality = quality;
1169
- if (server) {
1170
- server.defaultContext = secureContext;
1171
- // note that we can not set the secure context on the server here, because this creates an
1172
- // indeterminate situation of whether openssl will use this certificate or the one from the SNI
1173
- // callback
1174
- //server.setSecureContext?.(server, secureOptions);
1175
- }
1176
1303
  }
1177
1304
  } catch (error) {
1178
- logger.error?.('Error applying TLS for', cert.name, error);
1305
+ failedThisPass.push({ cert, error });
1179
1306
  }
1180
1307
  }
1181
- if (liveReload && secureContexts.size === 0 && !defaultContextSetThisPass) {
1182
- // The not-loaded-yet guard above only covers the table object being absent, not the
1183
- // table being present but every row failing to apply (e.g. a private key not yet
1184
- // available on this thread, caught above per-cert). Resolving here would still write
1185
- // an empty `certificates:` list — the exact customer-visible symptom this PR exists to
1186
- // fix. Retry on the same debounce instead of publishing that state; a rebuild trigger
1187
- // (cert-table change or private-key reload, both already wired to scheduleRebuild) is
1188
- // almost certainly still coming on a normal boot, and this just avoids a window where
1189
- // we'd otherwise publish empty in the meantime.
1190
- //
1191
- // `!defaultContextSetThisPass` because an empty hostname map is not the same as "no TLS
1192
- // available": a cert whose hostnames resolve to [] (no usable SANs and no CN) builds no
1193
- // per-hostname entry but still sets a serviceable default context — that listener must
1194
- // resolve and serve via the default rather than retry forever for hostname entries that
1195
- // can't exist. It must be the per-pass flag, not the persistent `defaultContext` (see its
1196
- // declaration note): keying off the closure variable would disarm this retry for every
1197
- // pass after the first success, reopening the publish-empty window on live rebuilds.
1198
- //
1199
- // Gated on liveReload: transient, single-use selectors (getReplicationCert) legitimately
1200
- // resolve empty — e.g. the bootstrap check `if (!(await getReplicationCert())) { create
1201
- // one }` in generateCertAuthority's caller depends on an empty resolution meaning "no
1202
- // cert yet," and must not hang waiting for a cert that this exact call is about to create.
1203
- //
1204
- // Latched like the system-db wait above: this retries indefinitely on the debounce, and
1205
- // an unlatched warn would emit ~57k lines/day from a listener stuck this way.
1308
+
1309
+ // Retain-last-good: a record still in the table whose build failed keeps its live
1310
+ // hostname entries and its default candidacy; deletion remains the way to drop them.
1311
+ // On an mTLS listener whose CA set changed, the retained pair is rebuilt against the
1312
+ // current trust material — new handshakes never see stale trust (established sessions
1313
+ // and outstanding tickets are unaffected, as on any build); if the rebuild fails the entries drop
1314
+ // unless nothing else is servable (the zero-cert guard below then keeps the old
1315
+ // state). Full contract in DESIGN.md "TLS hot-reload".
1316
+ const caSetUnchanged = (previous) => {
1317
+ const builtWith = (previous as any).certificateAuthorities;
1318
+ if (!Array.isArray(builtWith)) return candidateCAs.size === 0;
1319
+ return (
1320
+ builtWith.length === candidateCAs.size &&
1321
+ builtWith.every(([subject, pem]) => candidateCAs.get(subject) === pem)
1322
+ );
1323
+ };
1324
+ const rebuiltRetentions = new Map();
1325
+ const retentionFailures: { cert: any; error: any }[] = [];
1326
+ const retainable = (previous) => {
1327
+ // Without mTLS nothing consulted depends on the CA set — skip the rebuild, but
1328
+ // refresh the CA bookkeeping mirrored into socket metadata for fronting proxies.
1329
+ if (!mtlsOptions || caSetUnchanged(previous)) {
1330
+ (previous as any).certificateAuthorities = Array.from(candidateCAs);
1331
+ return previous;
1332
+ }
1333
+ if (rebuiltRetentions.has(previous)) return rebuiltRetentions.get(previous);
1334
+ try {
1335
+ const secureOptions = {
1336
+ ...(previous as any).options,
1337
+ ticketKeys: getTicketKeys(),
1338
+ availableCAs: caCerts,
1339
+ ca: mtlsOptions && Array.from(candidateCAs.values()),
1340
+ };
1341
+ const rebuilt = tls.createSecureContext(secureOptions);
1342
+ (rebuilt as any).name = (previous as any).name;
1343
+ (rebuilt as any).options = secureOptions;
1344
+ (rebuilt as any).quality = (previous as any).quality;
1345
+ (rebuilt as any).certificateAuthorities = Array.from(candidateCAs);
1346
+ (rebuilt as any).certStart = (previous as any).certStart;
1347
+ rebuiltRetentions.set(previous, rebuilt);
1348
+ return rebuilt;
1349
+ } catch (error) {
1350
+ retentionFailures.push({
1351
+ cert: { name: `${(previous as any).name} (retained-context rebuild)` },
1352
+ error,
1353
+ });
1354
+ rebuiltRetentions.set(previous, undefined);
1355
+ return undefined;
1356
+ }
1357
+ };
1358
+ for (const { cert } of failedThisPass) {
1359
+ // Ties go to the incumbent: a hostname the retained context already owned, and the
1360
+ // live default when it is the retained record, must not flip to an equal-quality
1361
+ // sibling because of a transient failure. A context retained only for a hostname
1362
+ // still needs strict > to become the default.
1363
+ const retain = (previous, hostname?) => {
1364
+ const retained = retainable(previous);
1365
+ if (!retained) return;
1366
+ const previousQuality = (retained as any).quality ?? 0;
1367
+ if (hostname !== undefined) {
1368
+ if (previousQuality >= ((candidateContexts.get(hostname) as any)?.quality ?? 0)) {
1369
+ candidateContexts.set(hostname, retained);
1370
+ }
1371
+ if (hostname[0] === '.') candidateHasWildcards = true;
1372
+ }
1373
+ const winsDefault =
1374
+ previous === defaultContext ? previousQuality >= bestQuality : previousQuality > bestQuality;
1375
+ if (winsDefault) {
1376
+ candidateDefault = retained;
1377
+ defaultContextSetThisPass = true;
1378
+ bestQuality = previousQuality;
1379
+ }
1380
+ };
1381
+ for (const [hostname, previous] of secureContexts) {
1382
+ if ((previous as any).name === cert.name) retain(previous, hostname);
1383
+ }
1384
+ if ((defaultContext as any)?.name === cert.name) retain(defaultContext);
1385
+ }
1386
+ if (liveReload && candidateContexts.size === 0 && !defaultContextSetThisPass) {
1387
+ // Every row failed to apply (e.g. keys not yet on this thread). Publishing or
1388
+ // resolving would write an empty `certificates:` list (the #1998 symptom), so keep
1389
+ // live state and retry. `!defaultContextSetThisPass`, not the persistent
1390
+ // `defaultContext`: a cert with no usable hostnames still sets a serviceable
1391
+ // default (must resolve, not retry forever), while the closure variable is truthy
1392
+ // forever after the first success. Gated on liveReload: transient selectors
1393
+ // (getReplicationCert) legitimately resolve empty — bootstrap depends on that
1394
+ // meaning "no cert yet". Warn is latched: unlatched would emit ~57k lines/day.
1206
1395
  if (server && !server.tlsSelectorWarnedZeroCerts) {
1207
1396
  server.tlsSelectorWarnedZeroCerts = true;
1208
1397
  logger.warn?.(
1209
1398
  `TLS selector for the '${type}' listener resolved zero certificates; retrying every ${TLS_REBUILD_DEBOUNCE_MS}ms`
1210
1399
  );
1211
1400
  }
1212
- scheduleRebuild();
1401
+ if (failedThisPass.length > 0) {
1402
+ // Surface WHY the pass came up empty and put the retry on the backoff.
1403
+ reportFailures(failedThisPass.concat(retentionFailures));
1404
+ scheduleFailureRetry();
1405
+ } else {
1406
+ scheduleRebuild();
1407
+ }
1213
1408
  return;
1214
1409
  }
1410
+ // Publish: reconcile the live maps in place (their identity is aliased by
1411
+ // server.secureContexts and every context's availableCAs) and advance all default
1412
+ // references together so SNI and non-SNI traffic can't diverge.
1413
+ secureContexts.clear();
1414
+ for (const [hostname, context] of candidateContexts) secureContexts.set(hostname, context);
1415
+ caCerts.clear();
1416
+ for (const [subject, certificate] of candidateCAs) caCerts.set(subject, certificate);
1417
+ // only listener selectors publish: a one-shot client selector's pass may see no authority rows
1418
+ if (liveReload) publishTrustedAuthorities(caCerts.values());
1419
+ hasWildcards = candidateHasWildcards;
1420
+ if (candidateDefault) {
1421
+ (SNICallback as any).defaultContext = defaultContext = candidateDefault;
1422
+ // note that we can not set the secure context on the server here, because this creates an
1423
+ // indeterminate situation of whether openssl will use this certificate or the one from the SNI
1424
+ // callback
1425
+ if (server) server.defaultContext = candidateDefault;
1426
+ }
1427
+
1428
+ if (failedThisPass.length > 0) {
1429
+ reportFailures(failedThisPass.concat(retentionFailures));
1430
+ scheduleFailureRetry();
1431
+ } else {
1432
+ clearFailureState();
1433
+ }
1434
+
1215
1435
  // A successful pass ends any warn latches so a later recurrence logs again.
1216
1436
  if (server) {
1217
1437
  server.tlsSelectorWaitedForSystemDb = false;
@@ -1237,8 +1457,14 @@ export function createTLSSelector(type, mtlsOptions?, liveReload = true): any {
1237
1457
  }
1238
1458
  }
1239
1459
  server?.secureContextsListeners.forEach((listener) => listener());
1240
- resolve(defaultContext);
1460
+ settle(defaultContext);
1241
1461
  } catch (error) {
1462
+ if (readySettled) {
1463
+ // Live state is untouched (candidates are pass-local): keep serving it and retry.
1464
+ reportFailures([{ cert: { name: `(${type} rebuild pass)` }, error }]);
1465
+ scheduleFailureRetry();
1466
+ return;
1467
+ }
1242
1468
  reject(error);
1243
1469
  }
1244
1470
  }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * A token operation scope (#2174) narrows a credential to a subset of its user's operations.
3
+ *
4
+ * It lives under two property names depending on the carrier: the claim `operations` on a JWT
5
+ * payload, and `tokenOperations` on an in-memory user principal (validateToken lifts the claim onto
6
+ * the user; verifyPerms reads it there). Every path that PRODUCES a credential or principal must
7
+ * carry the scope forward or the result is unscoped — the five bypasses this feature had to close
8
+ * were each a produce-a-credential path that forgot to. So the guard lives here, called at each such
9
+ * site, rather than re-inlined as `Array.isArray(...)` in six places that could drift apart.
10
+ *
11
+ * A present scope is an array — INCLUDING an empty (deny-all) array, which must be preserved rather
12
+ * than treated as "no scope". Anything else (absent/null) is skipped, so unscoped credentials behave
13
+ * exactly as they did before this existed.
14
+ */
15
+
16
+ export type OperationScope = string[];
17
+
18
+ /** True for a present scope: an array, including the empty deny-all array. */
19
+ export function hasOperationScope(scope: unknown): scope is OperationScope {
20
+ return Array.isArray(scope);
21
+ }
22
+
23
+ /** Copies a present scope onto a JWT payload (claim name `operations`). Returns the payload. */
24
+ export function attachScopeToToken<T extends object>(payload: T, scope: unknown): T {
25
+ if (hasOperationScope(scope)) (payload as any).operations = scope;
26
+ return payload;
27
+ }
28
+
29
+ /** Copies a present scope onto a user principal (property `tokenOperations`). Returns the user. */
30
+ export function attachScopeToUser<T extends object>(user: T, scope: unknown): T {
31
+ if (hasOperationScope(scope)) (user as any).tokenOperations = scope;
32
+ return user;
33
+ }
@@ -13,6 +13,13 @@ module.exports = {
13
13
  const rolePermsMap = Object.create(null);
14
14
  const permsTemplateObj = (permsKey) => ({ key: permsKey, perms: {} });
15
15
 
16
+ // Synthetic roles (impersonation/scoped tokens, names prefixed '_' by syntheticRoleName) have
17
+ // content-derived names, so their population is unbounded — they get their own LRU-bounded map
18
+ // instead of the permanent rolePermsMap, or minting many distinct permission sets would grow a
19
+ // per-worker memo that is never freed.
20
+ const MAX_SYNTHETIC_ROLE_ENTRIES = 256;
21
+ const syntheticRolePermsMap = new Map();
22
+
16
23
  const schemaPermsTemplate = (describePerm = false) => ({
17
24
  describe: describePerm,
18
25
  tables: {},
@@ -81,6 +88,22 @@ function getRolePermissions(role) {
81
88
  // translation to get an updated permissions set
82
89
  const permsKey = JSON.stringify([role['__updatedtime__'], nonSysSchema]);
83
90
 
91
+ if (roleName.startsWith('_')) {
92
+ const cached = syntheticRolePermsMap.get(roleName);
93
+ if (cached && cached.key === permsKey) {
94
+ syntheticRolePermsMap.delete(roleName);
95
+ syntheticRolePermsMap.set(roleName, cached);
96
+ return cached.perms;
97
+ }
98
+ const newRolePerms = translateRolePermissions(role, nonSysSchema);
99
+ syntheticRolePermsMap.delete(roleName);
100
+ syntheticRolePermsMap.set(roleName, { key: permsKey, perms: newRolePerms });
101
+ if (syntheticRolePermsMap.size > MAX_SYNTHETIC_ROLE_ENTRIES) {
102
+ syntheticRolePermsMap.delete(syntheticRolePermsMap.keys().next().value);
103
+ }
104
+ return newRolePerms;
105
+ }
106
+
84
107
  //If key exists already, we can return the cached value
85
108
  if (rolePermsMap[roleName] && rolePermsMap[roleName].key === permsKey) {
86
109
  return rolePermsMap[roleName].perms;