@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
package/server/DESIGN.md CHANGED
@@ -54,7 +54,7 @@ A request entering `http.ts` does **not** go through Fastify. The two `handleApp
54
54
  | `serverHelpers/JSONStream.ts` | Streaming JSON output for large responses. |
55
55
  | `nodeName.ts` | Resolves this node's name (config → hostname). |
56
56
  | `static.ts` | Static file serving for component-bundled assets. |
57
- | `throttle.ts` | Per-IP / per-user request throttling. |
57
+ | `throttle.ts` | Event-loop backpressure: runs a caller's calls one per `setImmediate` cycle and hands a call to the caller's limit handler (HTTP request queues, cache-source resolution) once queue depth × average cycle time passes the limit. |
58
58
  | `storageReclamation.ts` | Disk-pressure signals to downstream consumers; `getStorageSpaceStats()` is the shared quota-aware (falls back to `statfs`) source of available/free/size storage numbers — used by `Table.getStorageStats()` (#1976). NOT used for blob storage path weighting (`resources/blob.ts`): quota-status.json is a single instance-wide figure, so it can't distinguish between multiple `STORAGE_BLOBPATHS` disks — that still needs raw per-path `statfs`. |
59
59
  | `serverRegistry.ts` | Trivial registry export. |
60
60
  | `status/` | Server status reporting (cluster status, per-port info). |
@@ -219,6 +219,8 @@ The `error` value is the stable programmatic discriminator; `message` is diagnos
219
219
 
220
220
  Clean stream completion does not prove completeness; clients must inspect SSE for the `harper-error` event and NDJSON for the `$harperStreamError` control record.
221
221
 
222
+ The operations API keeps the same line for a request refused before its progress stream starts (authentication, the role allowlist, validation): the Fastify `preSerialization` hook in `contentTypes.ts` answers an error-status reply that would negotiate `text/event-stream` as JSON, unless the route itself set `text/event-stream`. A stream payload never reaches that hook, which is how the progress stream (`progressEmitter.ts`) keeps its in-band `event: error` at status 200. The hook is shared with the legacy custom-functions server (`fastifyRoutes.ts`), whose untyped errors follow the same rule. `harper deploy` unwraps the unnamed frame older servers still send in that case.
223
+
222
224
  ### Deferred credential rejection (#2418)
223
225
 
224
226
  `authentication` runs before route matching, so when it meets an `Authorization` header it cannot
@@ -573,6 +575,10 @@ port so the gap is visible instead of silent.
573
575
 
574
576
  `broadcastWithAcknowledgement` already times out (30 s) on a worker whose port stays open but never acks, and that shape is almost always a blocked event loop — a native lock, a runaway synchronous call — which nothing inside the worker can report (harper-pro#788: a restarted node's single http worker went byte-silent while main kept serving `cluster_status`, and the app log only said "not acknowledged by worker thread(s) 2"). So each worker posts its Linux thread id (`readlink /proc/thread-self`) to main once at startup, before anything else runs on it, and the timeout branch reads that thread's kernel state from `/proc/self/task/<tid>`: state, `wchan`, the syscall number (the first token only — the rest of that file is argument registers and stack/instruction pointers), CPU ticks, and context-switch counts, plus two cross-platform signals main already has, `worker.performance.eventLoopUtilization()` and the age of the last 1 s resource report. It samples again a second later and logs the deltas: no CPU ticks, no context switches and `event loop active +1000ms` is "parked on a lock"; ticks climbing with state `R` is "spinning". It is deliberately main-thread-only and best-effort: `workers` and the tid live on the main thread's `Worker` objects, every `/proc` field is reported individually (a hardened container may deny `wchan`/`syscall` while `stat` stays readable), a follow-up sample whose `starttime` differs from the first is discarded (the tid may have been recycled), one diagnostic runs per worker with a 30 s cooldown so concurrent timeouts on the same worker don't multiply reads, and nothing here runs when acks arrive on time. It does not name the lock owner; that still needs a native stack from the next occurrence.
575
577
 
578
+ ## A worker's `parentPort` close is not the main thread's exit (`server/threads/manageThreads.js`)
579
+
580
+ `REMOVE_PORT {threadId}` and the `onThreadExit` listeners (database-drop owner release, deploy lifecycle, registered operations, log rotation) act on a thread's death. A sibling port's `close` is taken as the far thread's exit. A worker's `parentPort` carries `threadId = 0` so lookups can route to main, but its `close` means this worker is leaving: main's exit ends the process. `addPort`'s close listener therefore passes no dead thread id for `parentPort`. Announcing one would make every sibling fail its pending acks to main, drop its own `parentPort`, and fire exit listeners for main. Regression cover: `unitTests/server/threads/stuckWorkerDiagnostics.test.js`, "does not report the main thread as exited to a sibling of a worker that closes its parentPort".
581
+
576
582
  ## `chooseOperation` authorizes the invoked operation against the authenticated principal (`server/serverHelpers/serverUtilities.ts`)
577
583
 
578
584
  `verifyPerms` takes a request-shaped object and reads _both_ halves of the permission question off it: the principal from `hdb_user`, and the tables from `schema`/`database`/`table`/`records`. `chooseOperation` used to hand it `json.search_operation` — a caller-supplied field — which made both halves body-controlled. Fixing one half and not the other is not a fix: with an empty `search_operation` the table map is empty, and `hasPermissions` iterating nothing authorizes everything. Regression cover: `integrationTests/security/choose-operation-authz.test.ts`.
@@ -636,3 +642,23 @@ the bare message until harper#2703, so the same error carried an error code on t
636
642
 
637
643
  REST settles a credential rejection _before_ its route lookup, so a rejected client gets the unauthorized
638
644
  close rather than `1011 No resource was found` — which would otherwise disclose whether the resource exists.
645
+
646
+ ## `serverErrorHandler` skips what `handlePostRequest` already logged (`server/serverHelpers/serverHandlers.js`)
647
+
648
+ `handlePostRequest` logs every error it throws, at error level or the error's own higher `fatal`/`notify`, and records it against its request; `serverErrorHandler`, which Fastify calls next with the same request, skips that error. Keep the log in `handlePostRequest` rather than moving it to the handler: `serverErrorHandler` logs an error with no `logLevel` of its own at info, below the default `warn`, so most operation errors would disappear from a default log; and a custom-function route that catches `hdbCore.request()`'s rejection never reaches the handler at all. The record is keyed by request, not by error, so an error object that surfaces again in another request is still logged there.
649
+
650
+ So a failure raised before the operation runs — a refusal from `chooseOperation`, a rejected request body — gets one Error line (a refusal also writes `chooseOperation`'s own `403 from operation …` and `User '…' is not permitted to …` lines; the Error line is the one that says why). An error the operation throws while it runs does not: `callOperationFunctionAsAwait` (`utility/OperationFunctionCaller.ts`) logs it, after an `Error calling operation: <handler>` line, and rethrows, and `handlePostRequest` logs it again. Its log is the only one for the callers that do not log — `server.operation()` from a component, the MCP operations tool, the SSE branch of `handlePostRequest` — so giving execution errors a single owner means deciding it for each of those, not dropping one log.
651
+
652
+ ## A request-queue shed is a 503 the server never logged (`server/throttle.ts`)
653
+
654
+ `throttle()` sheds a queued call once `queuedCalls.length × averageEventCycleTime` exceeds the limit (20s
655
+ by default). `server/http.ts` answers it directly with `Service unavailable, exceeded request queue limit`
656
+ and records only an analytics action; nothing is thrown, so the HTTP error path that logs every thrown
657
+ status (`info` for a 503, `warn` for a 500) never runs, and the `DatabaseTransaction` write-queue and
658
+ conflict 503s log at `error` (once per stuck commit, rate-limited) as well as `info` when thrown. A 503
659
+ with that exact body is therefore the throttle, which now writes a rate-limited `warn` per throttle instance naming
660
+ the queue; `resources/Table.ts`'s cache-resolution throttle throws its own 503 under the same warn. The
661
+ gate is per instance on purpose: a shared one let a cache-fill shed silence the HTTP shed that followed.
662
+ Reproduce with a large concurrent non-GET burst on one CPU-starved worker
663
+ (`integrationTests/resources/sourcedfrom-eav-cache-coherence.test.ts` P1 under Bun pinned to a contended
664
+ core).
package/server/Server.ts CHANGED
@@ -48,7 +48,8 @@ export interface Server {
48
48
  replicated: boolean;
49
49
  [key: string]: any;
50
50
  },
51
- options?: { onPeerResult?: (result: any) => void }
51
+ // `timeoutMs`: how long to wait for each peer's answer, in milliseconds; omitted waits for as long as the peer takes.
52
+ options?: { onPeerResult?: (result: any) => void; timeoutMs?: number }
52
53
  ): Promise<{ message: string; replicated?: unknown[] }>;
53
54
  monitorNodeCAs(listener: () => void): void;
54
55
  sendOperationToNode(node: string, operation: any, options: any): Promise<any>;
package/server/http.ts CHANGED
@@ -856,7 +856,8 @@ function getHTTPServer(port: number, secure: boolean, options: ServerOptions) {
856
856
  nodeResponse.end('Service unavailable, exceeded request queue limit');
857
857
  recordAction(true, 'service-unavailable', port);
858
858
  },
859
- env.get(serverPrefix + '_requestQueueLimit')
859
+ env.get(serverPrefix + '_requestQueueLimit'),
860
+ `HTTP request queue on port ${port}`
860
861
  );
861
862
  const server = (httpServers[port] = (
862
863
  secure ? (http2 ? createSecureServer : createSecureServerHttp1) : createServer
@@ -1127,7 +1128,8 @@ export function makeUwsHandler(port: number | string, isOperationsServer: boolea
1127
1128
  body: 'Service unavailable, exceeded request queue limit',
1128
1129
  };
1129
1130
  },
1130
- requestQueueLimit
1131
+ requestQueueLimit,
1132
+ `HTTP request queue on port ${port}`
1131
1133
  );
1132
1134
  return (request: any) => {
1133
1135
  const method = request.method;
@@ -11,6 +11,8 @@ const {
11
11
  databases,
12
12
  resetDatabases,
13
13
  closeDatabase,
14
+ prepareDatabaseDrop,
15
+ completeDatabaseDropPreparation,
14
16
  reloadBranchAt,
15
17
  markDropInProgress,
16
18
  } = require('../../resources/databases.ts');
@@ -46,11 +48,37 @@ async function schemaHandler(event) {
46
48
  }
47
49
 
48
50
  hdbLogger.trace(`ITC schemaHandler received schema event:`, event);
51
+ if (event.message?.operation === hdbTerms.OPERATIONS_ENUM.DROP_SCHEMA && event.message.schema) {
52
+ if (event.message.prepareDrop) {
53
+ if (!event.message.dropPreparationId) throw new Error('Drop-schema preparation is missing its id');
54
+ if (!Number.isInteger(event.message.dropPreparationOwnerThreadId))
55
+ throw new Error('Drop-schema preparation is missing its owner thread id');
56
+ await prepareDatabaseDrop(
57
+ event.message.schema,
58
+ event.message.dropPreparationId,
59
+ event.message.dropPreparationOwnerThreadId,
60
+ event.message.dropPreparationRootPaths
61
+ );
62
+ return;
63
+ }
64
+ if (event.message.dropPreparationId)
65
+ await completeDatabaseDropPreparation(
66
+ event.message.schema,
67
+ event.message.dropPreparationId,
68
+ event.message.dropPreparationRootPaths
69
+ );
70
+ }
49
71
  // restore_backup: this thread must release its store handles so the restore can purge and
50
72
  // rewrite the database directory. The rescan below (resetDatabases) skips reloading it while
51
73
  // the restoring marker is present, and reloads it on the completion signal (marker gone).
52
74
  if (event.message?.operation === hdbTerms.OPERATIONS_ENUM.RESTORE_BACKUP && event.message.schema) {
53
- closeDatabase(event.message.schema);
75
+ try {
76
+ await closeDatabase(event.message.schema);
77
+ } catch (error) {
78
+ // Let the originator's process-wide closure check fail the restore immediately instead of
79
+ // withholding this worker's acknowledgement until the broadcast timeout.
80
+ hdbLogger.error(`Could not release database '${event.message.schema}' for restore`, error);
81
+ }
54
82
  }
55
83
  await cleanLmdbMap(event.message);
56
84
  await syncSchemaMetadata(event.message);
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ import { getDatabases } from '../../resources/databases.ts';
4
+ import * as hdbTerms from '../../utility/hdbTerms.ts';
5
+ import log from '../../utility/logging/harper_logger.ts';
6
+ import * as manageThreads from '../threads/manageThreads.js';
7
+ import { updateJob } from './jobs.ts';
8
+
9
+ /**
10
+ * The thread fabric's process incarnation, not a pid: pids are reused, and a reused pid would make a
11
+ * dead job look alive. Every thread of one Harper process carries the same value, and any restart
12
+ * produces a new one. `undefined` on a thread started without one.
13
+ */
14
+ export const JOB_OWNER_INSTANCE_ID: string | undefined = manageThreads.processIncarnation;
15
+
16
+ /** Stripped from `get_job` responses. */
17
+ export const JOB_OWNER_ATTRIBUTES = ['owner_instance', 'owner_pid'] as const;
18
+
19
+ const UNFINISHED_JOB_STATUSES = [hdbTerms.JOB_STATUS_ENUM.CREATED, hdbTerms.JOB_STATUS_ENUM.IN_PROGRESS];
20
+
21
+ export function stampJobOwner(job: any): void {
22
+ if (job == null || typeof job !== 'object') return;
23
+ job.owner_instance = JOB_OWNER_INSTANCE_ID;
24
+ job.owner_pid = process.pid;
25
+ }
26
+
27
+ let reconciliation: Promise<number> | undefined;
28
+
29
+ /**
30
+ * Reconcile at most once per process. `loadRootComponents` re-runs on every root component reload, and
31
+ * no process can add a row that this one owns, so every pass after a *complete* one could only find
32
+ * nothing. A pass that left any row unsettled clears the memo instead, so the next reload retries the
33
+ * stragglers — the recovery this exists for must not be lost to one transient write failure.
34
+ */
35
+ export function reconcileInterruptedJobsOnce(): Promise<number> {
36
+ return (reconciliation ??= reconcileInterruptedJobs().catch((error) => {
37
+ reconciliation = undefined;
38
+ throw error;
39
+ }));
40
+ }
41
+
42
+ /**
43
+ * Settle every job row left unfinished by a process that is no longer running, and report how many were
44
+ * settled. Callers need not await it: the rows are chosen before the first write, so a job created while
45
+ * it runs is this process's and out of scope by construction.
46
+ *
47
+ * Settled rows are reported as ERROR rather than a new status, because every consumer of `get_job`
48
+ * already handles ERROR.
49
+ */
50
+ export async function reconcileInterruptedJobs(): Promise<number> {
51
+ // A thread with no incarnation cannot tell a dead owner from a live one, so it settles nothing rather
52
+ // than declaring a running job dead. The main thread always has one.
53
+ if (JOB_OWNER_INSTANCE_ID == null) return 0;
54
+
55
+ const jobTable = (getDatabases() as any).system?.[hdbTerms.SYSTEM_TABLE_NAMES.JOB_TABLE_NAME];
56
+ if (!jobTable) return 0;
57
+
58
+ // Collect before writing: the search walks the `status` attribute these updates change.
59
+ const interrupted: Array<{ id: any; owner_pid: any }> = [];
60
+ for (const status of UNFINISHED_JOB_STATUSES) {
61
+ for await (const job of jobTable.search([{ attribute: 'status', value: status }])) {
62
+ if (job.owner_instance !== JOB_OWNER_INSTANCE_ID) interrupted.push({ id: job.id, owner_pid: job.owner_pid });
63
+ }
64
+ }
65
+
66
+ let settled = 0;
67
+ let unsettled = 0;
68
+ for (const { id, owner_pid } of interrupted) {
69
+ const owner = owner_pid == null ? 'an earlier Harper process' : `Harper process ${owner_pid}`;
70
+ try {
71
+ // Deliberately not "rerun it": the job may have applied some of its effects before it died, and
72
+ // nothing here can tell how far it got.
73
+ await updateJob({
74
+ id,
75
+ status: hdbTerms.JOB_STATUS_ENUM.ERROR,
76
+ message: `Job was interrupted: ${owner} exited before it finished. Its outcome is unknown — check for partial effects before running the operation again.`,
77
+ });
78
+ settled++;
79
+ } catch (error) {
80
+ // Keep going: one unwritable row must not strand the rest.
81
+ unsettled++;
82
+ log.error(`Could not settle interrupted job ${id}`, error);
83
+ }
84
+ }
85
+ if (settled > 0) log.warn(`Settled ${settled} job(s) left unfinished by a Harper process that is no longer running`);
86
+ // Reported as a failure so the pass is not memoized as complete while rows are still stuck.
87
+ if (unsettled > 0) throw new Error(`Could not settle ${unsettled} of ${interrupted.length} interrupted job(s)`);
88
+ return settled;
89
+ }
@@ -19,6 +19,7 @@ import { cloneDeep } from 'lodash';
19
19
  import { pathToFileURL } from 'node:url';
20
20
  import { join } from 'node:path';
21
21
  import { parentPort } from 'node:worker_threads';
22
+ import { notifyJobCleanupComplete } from '../threads/manageThreads.js';
22
23
  import { getEnvBuiltInComponents } from './../../components/Application.ts';
23
24
  import { PACKAGE_ROOT } from '../../utility/packageUtils.js';
24
25
  const JOB_NAME = process.env[(hdbTerms as any).PROCESS_NAME_ENV_PROP] as string;
@@ -80,7 +81,9 @@ const JOB_ID = JOB_NAME.substring(4);
80
81
  exitCode = 1;
81
82
  harperLogger.error(err);
82
83
  jobObj.status = hdbTerms.JOB_STATUS_ENUM.ERROR;
83
- jobObj.message = err.message ? err.message : err;
84
+ // get_job answers a refused bulk load with its structured permission report as the message.
85
+ const report = err?.http_resp_msg;
86
+ jobObj.message = report !== null && typeof report === 'object' ? report : err?.message ? err.message : err;
84
87
  jobObj.end_datetime = moment().valueOf();
85
88
  } finally {
86
89
  // A rejected updateJob must not skip handle cleanup and exit scheduling below (that would
@@ -95,12 +98,15 @@ const JOB_ID = JOB_NAME.substring(4);
95
98
  // that exits without closing leaks its handles process-wide, which (among other costs)
96
99
  // blocks an online restore_backup from confirming the target database is closed. Best
97
100
  // effort — never let cleanup mask the job result.
101
+ let databaseHandlesReleased = false;
98
102
  try {
99
103
  const { closeLoadedDatabases } = await import('../../resources/databases.ts');
100
- closeLoadedDatabases();
104
+ await closeLoadedDatabases({ requireClosed: true });
105
+ databaseHandlesReleased = true;
101
106
  } catch (closeErr) {
102
107
  harperLogger.warn('Error releasing database handles on job worker exit:', closeErr);
103
108
  }
109
+ if (databaseHandlesReleased) notifyJobCleanupComplete();
104
110
  // On Bun 1.3.13, calling process.exit() in a worker thread with lmdb-js loaded
105
111
  // while sibling workers are running causes a NAPI fatal error crash. Unref
106
112
  // parentPort (which broadcastWithAcknowledgement may have ref'd during schema
@@ -26,6 +26,7 @@ import bulkDeleteValidator from '../../validation/bulkDeleteValidator.ts';
26
26
  import { deleteTransactionLogsBeforeValidator } from '../../validation/transactionLogValidator.ts';
27
27
  import { handleHDBError, ClientError } from '../../utility/errors/hdbError.ts';
28
28
  import { HTTP_STATUS_CODES } from '../../utility/errors/commonErrors.ts';
29
+ import { JOB_OWNER_ATTRIBUTES, stampJobOwner } from './jobOwnership.ts';
29
30
 
30
31
  //Promisified functions
31
32
  const pSearchByValue = search.searchByValue;
@@ -38,10 +39,16 @@ export async function handleGetJob(jsonBody: any) {
38
39
  if (jsonBody.id === undefined) throw new ClientError("'id' is required");
39
40
  let result = await getJobById(jsonBody.id);
40
41
  if (!hdbUtil.isEmptyOrZeroLength(result)) {
41
- result[0] = { ...result[0] };
42
- if (result[0].request !== undefined) delete result[0].request;
43
- delete result[0]['__createdtime__'];
44
- delete result[0]['__updatedtime__'];
42
+ // Every row, not just the first: a lookup by id should yield at most one, but the owner
43
+ // attributes are internal bookkeeping and a leak here exposes process identity.
44
+ const rows = result as any[];
45
+ for (let index = 0; index < rows.length; index++) {
46
+ const job = (rows[index] = { ...rows[index] });
47
+ if (job.request !== undefined) delete job.request;
48
+ delete job['__createdtime__'];
49
+ delete job['__updatedtime__'];
50
+ for (const attribute of JOB_OWNER_ATTRIBUTES) delete job[attribute];
51
+ }
45
52
  }
46
53
 
47
54
  return result;
@@ -63,6 +70,7 @@ export async function handleGetJobsByStartDate(jsonBody: any) {
63
70
  if (currRes.request !== undefined) delete currRes.request;
64
71
  delete currRes['__createdtime__'];
65
72
  delete currRes['__updatedtime__'];
73
+ for (const attribute of JOB_OWNER_ATTRIBUTES) delete currRes[attribute];
66
74
  }
67
75
  }
68
76
  return result;
@@ -198,6 +206,9 @@ export async function addJob(jsonBody: any) {
198
206
  // We save the request so that the job process can get it and run the operation.
199
207
  // Sending the request via IPC to the job process was causing some messages to be lost under load.
200
208
  newJob.request = jsonBody;
209
+ // Stamped at creation, not at IN_PROGRESS: a row that never reaches its worker is stuck in exactly
210
+ // the same way as one whose worker died, and only an owner makes either recoverable at boot.
211
+ stampJobOwner(newJob);
201
212
 
202
213
  let insertObject = new (Insert_Object as any)(
203
214
  hdbTerms.SYSTEM_SCHEMA_NAME,
@@ -75,6 +75,17 @@ async function loadRootComponents(isWorkerThread = false) {
75
75
  getTables();
76
76
  resources.isWorker = isWorkerThread;
77
77
 
78
+ // Settle jobs whose owning process is gone. Main thread only, and deliberately neither awaited nor
79
+ // fatal: the sweep picks its rows before its first write, so nothing started below can fall into it,
80
+ // and an install carrying a long backlog of interrupted rows must not hold up boot while they settle.
81
+ // An unreconciled job row is misleading, but it is no reason to refuse to start. This function re-runs
82
+ // on every root component reload; the sweep itself holds the once-per-process guard.
83
+ if (isMainThread) {
84
+ require('./jobs/jobOwnership.ts')
85
+ .reconcileInterruptedJobsOnce()
86
+ .catch((error) => console.error(errorForLog(error)));
87
+ }
88
+
78
89
  await loadCertificates();
79
90
  // the Harper root component
80
91
  await loadComponent(dirname(configUtils.getConfigFilePath()), resources, 'hdb', {
@@ -314,7 +314,19 @@ const registerFastifySerializers = fp(
314
314
  fastify.addHook('preSerialization', async (request, reply) => {
315
315
  const contentType = reply.raw.getHeader('content-type');
316
316
  if (contentType) return;
317
- const { serializer, type } = findBestSerializer(request.raw);
317
+ let { serializer, type } = findBestSerializer(request.raw);
318
+ // An error that no route chose to stream is an ordinary response: written as an unnamed event,
319
+ // an SSE client reads it as a stream that ended without a result.
320
+ if (
321
+ type === 'text/event-stream' &&
322
+ reply.statusCode >= 400 &&
323
+ !String(reply.getHeader('content-type') ?? '')
324
+ .toLowerCase()
325
+ .startsWith('text/event-stream')
326
+ ) {
327
+ serializer = mediaTypes.get('application/json');
328
+ type = 'application/json';
329
+ }
318
330
  reply.type(type);
319
331
  reply.serializer(function (data: any) {
320
332
  let serialize: (data: any, context: any) => any;
@@ -70,8 +70,14 @@ function handleServerUncaughtException(err) {
70
70
  realExit(1);
71
71
  }
72
72
 
73
+ // The errors handlePostRequest logged, per request; see server/DESIGN.md.
74
+ const errorsLoggedByRequest = new WeakMap();
75
+ const LEVELS_ABOVE_ERROR = new Set([terms.LOG_LEVELS.FATAL, terms.LOG_LEVELS.NOTIFY]);
76
+
73
77
  function serverErrorHandler(error, req, resp) {
74
- harperLogger[error.logLevel || 'info'](error);
78
+ // Fastify passes a handler's reason through as-is, so a rejection with none arrives here as nullish.
79
+ error ??= handleHDBError(new Error('The request failed without an error'), undefined, 500);
80
+ if (!errorsLoggedByRequest.get(req)?.has(error)) harperLogger[error.logLevel || 'info'](error);
75
81
  if (error.statusCode) {
76
82
  if (typeof error.http_resp_msg !== 'object') {
77
83
  const body = { error: error.http_resp_msg || error.message };
@@ -220,7 +226,10 @@ async function handlePostRequest(req, res, _bypassAuth = false) {
220
226
  }
221
227
  return result;
222
228
  } catch (error) {
223
- harperLogger.error(error);
229
+ harperLogger[LEVELS_ABOVE_ERROR.has(error?.logLevel) ? error.logLevel : 'error'](error);
230
+ let logged = errorsLoggedByRequest.get(req);
231
+ if (!logged) errorsLoggedByRequest.set(req, (logged = new Set()));
232
+ logged.add(error);
224
233
  throw error;
225
234
  }
226
235
  }
@@ -4,37 +4,63 @@ const hdbUtils = require('../../utility/common_utils.ts');
4
4
  const hdbTerms = require('../../utility/hdbTerms.ts');
5
5
  const { ITC_ERRORS } = require('../../utility/errors/commonErrors.ts');
6
6
  const { threadId } = require('worker_threads');
7
- const { onMessageFromWorkers, broadcastWithAcknowledgement } = require('./manageThreads.js');
7
+ const {
8
+ onMessageFromWorkers,
9
+ broadcastWithAcknowledgement,
10
+ broadcastWithStrictAcknowledgement,
11
+ } = require('./manageThreads.js');
8
12
 
9
13
  module.exports = {
10
14
  sendItcEvent,
15
+ sendItcEventStrict,
11
16
  validateEvent,
12
17
  SchemaEventMsg,
13
18
  };
14
19
  let serverItcHandlers;
15
20
  onMessageFromWorkers(async (event, sender) => {
16
21
  serverItcHandlers = serverItcHandlers || require('../itc/serverHandlers.js');
17
- validateEvent(event);
18
- if (serverItcHandlers[event.type]) {
19
- await serverItcHandlers[event.type](event);
22
+ let error;
23
+ try {
24
+ validateEvent(event);
25
+ if (serverItcHandlers[event.type]) {
26
+ await serverItcHandlers[event.type](event);
27
+ }
28
+ } catch (caught) {
29
+ const hdbLogger = require('../../utility/logging/harper_logger.ts');
30
+ hdbLogger.error('ITC event handler failed', caught);
31
+ error = {
32
+ name: caught?.name,
33
+ message: caught?.message ?? String(caught),
34
+ code: caught?.code,
35
+ statusCode: caught?.statusCode,
36
+ retryable: caught?.retryable,
37
+ };
20
38
  }
21
39
  if (event.requestId && sender)
22
40
  sender.postMessage({
23
41
  type: 'ack',
24
42
  id: event.requestId,
43
+ error,
25
44
  });
26
45
  });
27
46
 
28
47
  /**
29
48
  * Emits an ITC event to the ITC server.
30
49
  * @param event
50
+ * @param {boolean|'active'} includeJobWorkers
31
51
  */
32
- function sendItcEvent(event) {
52
+ function sendItcEvent(event, includeJobWorkers = false) {
33
53
  // Always stamp originator so handlers can send direct responses back.
34
54
  // The main thread's threadId is 0 (worker_threads convention); parentPort.threadId
35
55
  // is set to 0 in workers, so sendToThread(0, ...) routes back to main.
36
56
  if (event.message) event.message.originator = threadId;
37
- return broadcastWithAcknowledgement(event);
57
+ return broadcastWithAcknowledgement(event, undefined, false, includeJobWorkers);
58
+ }
59
+
60
+ /** @param {boolean|'active'} includeJobWorkers */
61
+ function sendItcEventStrict(event, timeout, includeJobWorkers = false) {
62
+ if (event.message) event.message.originator = threadId;
63
+ return broadcastWithStrictAcknowledgement(event, timeout, includeJobWorkers);
38
64
  }
39
65
 
40
66
  /**