@spine-event-engine/server 2.0.0-snapshot.23 → 2.0.0-snapshot.25

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 (300) hide show
  1. package/README.md +38 -11
  2. package/REFERENCE.md +278 -141
  3. package/dist/agent/agent-acceptance-receipts.d.ts +69 -0
  4. package/dist/agent/agent-acceptance-receipts.d.ts.map +1 -0
  5. package/dist/agent/agent-acceptance-receipts.js +198 -0
  6. package/dist/agent/agent-acceptance-receipts.js.map +1 -0
  7. package/dist/agent/agent-execution-capacity.d.ts +37 -9
  8. package/dist/agent/agent-execution-capacity.d.ts.map +1 -1
  9. package/dist/agent/agent-execution-capacity.js +73 -15
  10. package/dist/agent/agent-execution-capacity.js.map +1 -1
  11. package/dist/agent/agent-history.d.ts +15 -10
  12. package/dist/agent/agent-history.d.ts.map +1 -1
  13. package/dist/agent/agent-history.js +26 -14
  14. package/dist/agent/agent-history.js.map +1 -1
  15. package/dist/agent/agent-live-ai-audit.d.ts +95 -0
  16. package/dist/agent/agent-live-ai-audit.d.ts.map +1 -0
  17. package/dist/agent/agent-live-ai-audit.js +290 -0
  18. package/dist/agent/agent-live-ai-audit.js.map +1 -0
  19. package/dist/agent/agent-live-ai-runtime.d.ts +300 -0
  20. package/dist/agent/agent-live-ai-runtime.d.ts.map +1 -0
  21. package/dist/agent/agent-live-ai-runtime.js +611 -0
  22. package/dist/agent/agent-live-ai-runtime.js.map +1 -0
  23. package/dist/agent/agent-live-audit.d.ts +67 -0
  24. package/dist/agent/agent-live-audit.d.ts.map +1 -0
  25. package/dist/agent/agent-live-audit.js +121 -0
  26. package/dist/agent/agent-live-audit.js.map +1 -0
  27. package/dist/agent/agent-live-budget.d.ts +84 -0
  28. package/dist/agent/agent-live-budget.d.ts.map +1 -0
  29. package/dist/agent/agent-live-budget.js +161 -0
  30. package/dist/agent/agent-live-budget.js.map +1 -0
  31. package/dist/agent/agent-live-fault.d.ts +18 -0
  32. package/dist/agent/agent-live-fault.d.ts.map +1 -0
  33. package/dist/agent/{agent-execution-fault.js → agent-live-fault.js} +7 -7
  34. package/dist/agent/agent-live-fault.js.map +1 -0
  35. package/dist/agent/agent-live-inputs.d.ts +74 -0
  36. package/dist/agent/agent-live-inputs.d.ts.map +1 -0
  37. package/dist/agent/agent-live-inputs.js +15 -0
  38. package/dist/agent/agent-live-inputs.js.map +1 -0
  39. package/dist/agent/agent-live-interaction-events.d.ts +46 -0
  40. package/dist/agent/agent-live-interaction-events.d.ts.map +1 -0
  41. package/dist/agent/agent-live-interaction-events.js +104 -0
  42. package/dist/agent/agent-live-interaction-events.js.map +1 -0
  43. package/dist/agent/agent-live-mcp-controls.d.ts +129 -0
  44. package/dist/agent/agent-live-mcp-controls.d.ts.map +1 -0
  45. package/dist/agent/agent-live-mcp-controls.js +228 -0
  46. package/dist/agent/agent-live-mcp-controls.js.map +1 -0
  47. package/dist/agent/agent-live-mcp-host.d.ts +88 -0
  48. package/dist/agent/agent-live-mcp-host.d.ts.map +1 -0
  49. package/dist/agent/{agent-mcp-host.js → agent-live-mcp-host.js} +1 -1
  50. package/dist/agent/agent-live-mcp-host.js.map +1 -0
  51. package/dist/agent/{agent-mcp-runtime.d.ts → agent-live-mcp-runtime.d.ts} +6 -6
  52. package/dist/agent/agent-live-mcp-runtime.d.ts.map +1 -0
  53. package/dist/agent/{agent-mcp-runtime.js → agent-live-mcp-runtime.js} +13 -42
  54. package/dist/agent/agent-live-mcp-runtime.js.map +1 -0
  55. package/dist/agent/agent-live-read-runtime.d.ts +61 -0
  56. package/dist/agent/agent-live-read-runtime.d.ts.map +1 -0
  57. package/dist/agent/agent-live-read-runtime.js +121 -0
  58. package/dist/agent/agent-live-read-runtime.js.map +1 -0
  59. package/dist/agent/agent-model-selection.d.ts +21 -34
  60. package/dist/agent/agent-model-selection.d.ts.map +1 -1
  61. package/dist/agent/agent-model-selection.js +61 -78
  62. package/dist/agent/agent-model-selection.js.map +1 -1
  63. package/dist/bus/command-bus.d.ts +14 -18
  64. package/dist/bus/command-bus.d.ts.map +1 -1
  65. package/dist/bus/command-bus.js +31 -48
  66. package/dist/bus/command-bus.js.map +1 -1
  67. package/dist/bus/event-bus.d.ts +82 -104
  68. package/dist/bus/event-bus.d.ts.map +1 -1
  69. package/dist/bus/event-bus.js +87 -127
  70. package/dist/bus/event-bus.js.map +1 -1
  71. package/dist/context/bounded-context.d.ts +143 -91
  72. package/dist/context/bounded-context.d.ts.map +1 -1
  73. package/dist/context/bounded-context.js +753 -359
  74. package/dist/context/bounded-context.js.map +1 -1
  75. package/dist/context/entity-inbox.d.ts +7 -2
  76. package/dist/context/entity-inbox.d.ts.map +1 -1
  77. package/dist/context/entity-inbox.js +43 -20
  78. package/dist/context/entity-inbox.js.map +1 -1
  79. package/dist/context/local-inbox-handoff.d.ts +35 -34
  80. package/dist/context/local-inbox-handoff.d.ts.map +1 -1
  81. package/dist/context/local-inbox-handoff.js +123 -135
  82. package/dist/context/local-inbox-handoff.js.map +1 -1
  83. package/dist/context/projection-handoff.d.ts +2 -2
  84. package/dist/context/projection-handoff.d.ts.map +1 -1
  85. package/dist/context/projection-handoff.js +12 -8
  86. package/dist/context/projection-handoff.js.map +1 -1
  87. package/dist/context/tenant-index.d.ts +36 -31
  88. package/dist/context/tenant-index.d.ts.map +1 -1
  89. package/dist/context/tenant-index.js +123 -125
  90. package/dist/context/tenant-index.js.map +1 -1
  91. package/dist/context/tenant-unknown-error.d.ts +33 -0
  92. package/dist/context/tenant-unknown-error.d.ts.map +1 -0
  93. package/dist/context/tenant-unknown-error.js +75 -0
  94. package/dist/context/tenant-unknown-error.js.map +1 -0
  95. package/dist/delivery/agent-delivery-scope.d.ts +66 -0
  96. package/dist/delivery/agent-delivery-scope.d.ts.map +1 -0
  97. package/dist/delivery/agent-delivery-scope.js +44 -0
  98. package/dist/delivery/agent-delivery-scope.js.map +1 -0
  99. package/dist/delivery/delivery-builder.d.ts +25 -1
  100. package/dist/delivery/delivery-builder.d.ts.map +1 -1
  101. package/dist/delivery/delivery-builder.js +89 -0
  102. package/dist/delivery/delivery-builder.js.map +1 -1
  103. package/dist/delivery/delivery-monitor.d.ts +12 -9
  104. package/dist/delivery/delivery-monitor.d.ts.map +1 -1
  105. package/dist/delivery/delivery-monitor.js +15 -9
  106. package/dist/delivery/delivery-monitor.js.map +1 -1
  107. package/dist/delivery/delivery-ports.d.ts +6 -13
  108. package/dist/delivery/delivery-ports.d.ts.map +1 -1
  109. package/dist/delivery/delivery-run-control.d.ts +21 -0
  110. package/dist/delivery/delivery-run-control.d.ts.map +1 -1
  111. package/dist/delivery/delivery-run-control.js +10 -0
  112. package/dist/delivery/delivery-run-control.js.map +1 -1
  113. package/dist/delivery/delivery-supervisor.d.ts +33 -5
  114. package/dist/delivery/delivery-supervisor.d.ts.map +1 -1
  115. package/dist/delivery/delivery-supervisor.js +178 -26
  116. package/dist/delivery/delivery-supervisor.js.map +1 -1
  117. package/dist/delivery/delivery-tenant.d.ts +17 -0
  118. package/dist/delivery/delivery-tenant.d.ts.map +1 -0
  119. package/dist/delivery/delivery-tenant.js +50 -0
  120. package/dist/delivery/delivery-tenant.js.map +1 -0
  121. package/dist/delivery/delivery.d.ts +96 -2
  122. package/dist/delivery/delivery.d.ts.map +1 -1
  123. package/dist/delivery/delivery.js +803 -89
  124. package/dist/delivery/delivery.js.map +1 -1
  125. package/dist/delivery/inbox-records.d.ts.map +1 -1
  126. package/dist/delivery/inbox-records.js +9 -16
  127. package/dist/delivery/inbox-records.js.map +1 -1
  128. package/dist/delivery/inbox-storage.d.ts +14 -11
  129. package/dist/delivery/inbox-storage.d.ts.map +1 -1
  130. package/dist/delivery/inbox-storage.js +72 -78
  131. package/dist/delivery/inbox-storage.js.map +1 -1
  132. package/dist/delivery/inbox.d.ts +9 -12
  133. package/dist/delivery/inbox.d.ts.map +1 -1
  134. package/dist/delivery/inbox.js +13 -15
  135. package/dist/delivery/inbox.js.map +1 -1
  136. package/dist/delivery/reception-observation.d.ts +64 -0
  137. package/dist/delivery/reception-observation.d.ts.map +1 -0
  138. package/dist/delivery/reception-observation.js +78 -0
  139. package/dist/delivery/reception-observation.js.map +1 -0
  140. package/dist/delivery/sharded-work-registry.d.ts +6 -0
  141. package/dist/delivery/sharded-work-registry.d.ts.map +1 -1
  142. package/dist/delivery/sharded-work-registry.js +41 -4
  143. package/dist/delivery/sharded-work-registry.js.map +1 -1
  144. package/dist/entity/entity-storage-descriptor.d.ts.map +1 -1
  145. package/dist/entity/entity-storage-descriptor.js +7 -4
  146. package/dist/entity/entity-storage-descriptor.js.map +1 -1
  147. package/dist/index.d.ts +4 -2
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +2 -1
  150. package/dist/index.js.map +1 -1
  151. package/dist/integration/external-messages.d.ts.map +1 -1
  152. package/dist/integration/external-messages.js +9 -15
  153. package/dist/integration/external-messages.js.map +1 -1
  154. package/dist/integration/integration-broker.d.ts.map +1 -1
  155. package/dist/integration/integration-broker.js +112 -6
  156. package/dist/integration/integration-broker.js.map +1 -1
  157. package/dist/integration/third-party-context.d.ts.map +1 -1
  158. package/dist/integration/third-party-context.js +7 -10
  159. package/dist/integration/third-party-context.js.map +1 -1
  160. package/dist/repository/primitive-id.js +26 -11
  161. package/dist/repository/primitive-id.js.map +1 -1
  162. package/dist/repository/repository.d.ts +33 -62
  163. package/dist/repository/repository.d.ts.map +1 -1
  164. package/dist/repository/repository.js +1475 -1827
  165. package/dist/repository/repository.js.map +1 -1
  166. package/dist/runtime/runtime.d.ts +24 -0
  167. package/dist/runtime/runtime.d.ts.map +1 -1
  168. package/dist/runtime/runtime.js +31 -0
  169. package/dist/runtime/runtime.js.map +1 -1
  170. package/dist/runtime/signal-metadata.d.ts.map +1 -1
  171. package/dist/runtime/signal-metadata.js +8 -15
  172. package/dist/runtime/signal-metadata.js.map +1 -1
  173. package/dist/runtime/signal-publisher.d.ts +8 -36
  174. package/dist/runtime/signal-publisher.d.ts.map +1 -1
  175. package/dist/runtime/signal-publisher.js +68 -61
  176. package/dist/runtime/signal-publisher.js.map +1 -1
  177. package/dist/runtime/standalone-handler-runtime.d.ts +0 -7
  178. package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -1
  179. package/dist/runtime/standalone-handler-runtime.js +14 -130
  180. package/dist/runtime/standalone-handler-runtime.js.map +1 -1
  181. package/dist/server/managed-delivery-gate.d.ts +34 -0
  182. package/dist/server/managed-delivery-gate.d.ts.map +1 -0
  183. package/dist/server/managed-delivery-gate.js +70 -0
  184. package/dist/server/managed-delivery-gate.js.map +1 -0
  185. package/dist/server/managed-server-application.d.ts +2 -2
  186. package/dist/server/managed-server-application.d.ts.map +1 -1
  187. package/dist/server/managed-server-application.js +471 -178
  188. package/dist/server/managed-server-application.js.map +1 -1
  189. package/dist/server/node-coordinator.d.ts +14 -0
  190. package/dist/server/node-coordinator.d.ts.map +1 -1
  191. package/dist/server/node-coordinator.js +266 -45
  192. package/dist/server/node-coordinator.js.map +1 -1
  193. package/dist/server/server-environment.d.ts +203 -25
  194. package/dist/server/server-environment.d.ts.map +1 -1
  195. package/dist/server/server-environment.js +661 -133
  196. package/dist/server/server-environment.js.map +1 -1
  197. package/dist/server/server-log.d.ts +7 -0
  198. package/dist/server/server-log.d.ts.map +1 -1
  199. package/dist/server/server-log.js +18 -1
  200. package/dist/server/server-log.js.map +1 -1
  201. package/dist/server/server.d.ts +18 -32
  202. package/dist/server/server.d.ts.map +1 -1
  203. package/dist/server/server.js +62 -203
  204. package/dist/server/server.js.map +1 -1
  205. package/dist/services/spine-services.d.ts +1 -1
  206. package/dist/services/spine-services.d.ts.map +1 -1
  207. package/dist/services/spine-services.js +399 -370
  208. package/dist/services/spine-services.js.map +1 -1
  209. package/dist/stand/event-origin-filter.d.ts +16 -0
  210. package/dist/stand/event-origin-filter.d.ts.map +1 -0
  211. package/dist/stand/event-origin-filter.js +124 -0
  212. package/dist/stand/event-origin-filter.js.map +1 -0
  213. package/dist/stand/stand.d.ts +14 -0
  214. package/dist/stand/stand.d.ts.map +1 -1
  215. package/dist/stand/stand.js +53 -9
  216. package/dist/stand/stand.js.map +1 -1
  217. package/dist/stand/subscription-observer.d.ts.map +1 -1
  218. package/dist/stand/subscription-observer.js +61 -40
  219. package/dist/stand/subscription-observer.js.map +1 -1
  220. package/dist/stand/subscription-registry.d.ts +6 -2
  221. package/dist/stand/subscription-registry.d.ts.map +1 -1
  222. package/dist/stand/subscription-registry.js +15 -11
  223. package/dist/stand/subscription-registry.js.map +1 -1
  224. package/dist/stand/subscription-runtime.d.ts +23 -4
  225. package/dist/stand/subscription-runtime.d.ts.map +1 -1
  226. package/dist/stand/subscription-runtime.js +84 -13
  227. package/dist/stand/subscription-runtime.js.map +1 -1
  228. package/dist/testing/index.d.ts.map +1 -1
  229. package/dist/testing/index.js +3 -2
  230. package/dist/testing/index.js.map +1 -1
  231. package/dist/tsconfig.tsbuildinfo +1 -1
  232. package/package.json +15 -15
  233. package/dist/agent/agent-admission.d.ts +0 -80
  234. package/dist/agent/agent-admission.d.ts.map +0 -1
  235. package/dist/agent/agent-admission.js +0 -127
  236. package/dist/agent/agent-admission.js.map +0 -1
  237. package/dist/agent/agent-ai-runtime.d.ts +0 -76
  238. package/dist/agent/agent-ai-runtime.d.ts.map +0 -1
  239. package/dist/agent/agent-ai-runtime.js +0 -1269
  240. package/dist/agent/agent-ai-runtime.js.map +0 -1
  241. package/dist/agent/agent-execution-fault.d.ts +0 -18
  242. package/dist/agent/agent-execution-fault.d.ts.map +0 -1
  243. package/dist/agent/agent-execution-fault.js.map +0 -1
  244. package/dist/agent/agent-execution-session.d.ts +0 -103
  245. package/dist/agent/agent-execution-session.d.ts.map +0 -1
  246. package/dist/agent/agent-execution-session.js +0 -238
  247. package/dist/agent/agent-execution-session.js.map +0 -1
  248. package/dist/agent/agent-interaction-audit.d.ts +0 -58
  249. package/dist/agent/agent-interaction-audit.d.ts.map +0 -1
  250. package/dist/agent/agent-interaction-audit.js +0 -114
  251. package/dist/agent/agent-interaction-audit.js.map +0 -1
  252. package/dist/agent/agent-interaction-events.d.ts +0 -40
  253. package/dist/agent/agent-interaction-events.d.ts.map +0 -1
  254. package/dist/agent/agent-interaction-events.js +0 -129
  255. package/dist/agent/agent-interaction-events.js.map +0 -1
  256. package/dist/agent/agent-mcp-budget.d.ts +0 -125
  257. package/dist/agent/agent-mcp-budget.d.ts.map +0 -1
  258. package/dist/agent/agent-mcp-budget.js +0 -566
  259. package/dist/agent/agent-mcp-budget.js.map +0 -1
  260. package/dist/agent/agent-mcp-host.d.ts +0 -125
  261. package/dist/agent/agent-mcp-host.d.ts.map +0 -1
  262. package/dist/agent/agent-mcp-host.js.map +0 -1
  263. package/dist/agent/agent-mcp-runtime.d.ts.map +0 -1
  264. package/dist/agent/agent-mcp-runtime.js.map +0 -1
  265. package/dist/agent/agent-read-runtime.d.ts +0 -70
  266. package/dist/agent/agent-read-runtime.d.ts.map +0 -1
  267. package/dist/agent/agent-read-runtime.js +0 -320
  268. package/dist/agent/agent-read-runtime.js.map +0 -1
  269. package/dist/agent/agent-revisions.d.ts +0 -35
  270. package/dist/agent/agent-revisions.d.ts.map +0 -1
  271. package/dist/agent/agent-revisions.js +0 -133
  272. package/dist/agent/agent-revisions.js.map +0 -1
  273. package/dist/agent/agent-scheduler.d.ts +0 -99
  274. package/dist/agent/agent-scheduler.d.ts.map +0 -1
  275. package/dist/agent/agent-scheduler.js +0 -398
  276. package/dist/agent/agent-scheduler.js.map +0 -1
  277. package/dist/bus/saved-dispatcher-binding.d.ts +0 -96
  278. package/dist/bus/saved-dispatcher-binding.d.ts.map +0 -1
  279. package/dist/bus/saved-dispatcher-binding.js +0 -35
  280. package/dist/bus/saved-dispatcher-binding.js.map +0 -1
  281. package/dist/delivery/delivery-run-coordinator.d.ts +0 -218
  282. package/dist/delivery/delivery-run-coordinator.d.ts.map +0 -1
  283. package/dist/delivery/delivery-run-coordinator.js +0 -609
  284. package/dist/delivery/delivery-run-coordinator.js.map +0 -1
  285. package/dist/delivery/parked-delivery-obligations.d.ts +0 -182
  286. package/dist/delivery/parked-delivery-obligations.d.ts.map +0 -1
  287. package/dist/delivery/parked-delivery-obligations.js +0 -390
  288. package/dist/delivery/parked-delivery-obligations.js.map +0 -1
  289. package/dist/server/environment-attachment.d.ts +0 -356
  290. package/dist/server/environment-attachment.d.ts.map +0 -1
  291. package/dist/server/environment-attachment.js +0 -2062
  292. package/dist/server/environment-attachment.js.map +0 -1
  293. package/dist/server/environment-delivery-records.d.ts +0 -77
  294. package/dist/server/environment-delivery-records.d.ts.map +0 -1
  295. package/dist/server/environment-delivery-records.js +0 -282
  296. package/dist/server/environment-delivery-records.js.map +0 -1
  297. package/dist/server/environment-delivery-worker.d.ts +0 -161
  298. package/dist/server/environment-delivery-worker.d.ts.map +0 -1
  299. package/dist/server/environment-delivery-worker.js +0 -673
  300. package/dist/server/environment-delivery-worker.js.map +0 -1
@@ -19,7 +19,7 @@ import { LogLayer, StructuredTransport } from "loglayer";
19
19
  import { runningServerAccess } from "./server.js";
20
20
  import { emitServerWarning } from "./server-log.js";
21
21
  import { NodeCoordinator } from "./node-coordinator.js";
22
- import { environmentDeliveryWorkerAccess } from "./environment-delivery-worker.js";
22
+ import { managedDeliveryGate } from "./managed-delivery-gate.js";
23
23
  import { managedChildSubscriptionAccess } from "./managed-child-subscription.js";
24
24
  import { InMemorySubscriptionRegistry } from "../stand/subscription-registry.js";
25
25
  const childMarker = "SPINE_MANAGED_SERVER_CHILD";
@@ -106,115 +106,7 @@ const ManagedServerValues = Object.freeze({
106
106
  }
107
107
  throw error;
108
108
  }
109
- // Node may emit an IPC EPIPE after a parent has already disappeared. The
110
- // disconnect handler below handles shutdown; this listener prevents that
111
- // transport detail from becoming an unhandled child-process exception.
112
- const containIpcError = () => undefined;
113
- process.on("error", containIpcError);
114
- managedChildSubscriptionAccess.install((subscriptionId) => {
115
- const notification = ManagedServerValues.send({
116
- type: "subscription-installed",
117
- slot: process.env[slotMarker],
118
- incarnation: process.env[incarnationMarker],
119
- subscriptionId,
120
- });
121
- // spine-log-boundary: server.managed_replica_subscription_ipc
122
- void notification.catch(() => undefined);
123
- });
124
- let activated = false;
125
- let releaseActivation;
126
- const activation = new Promise((resolve) => {
127
- releaseActivation = resolve;
128
- });
129
- let closing;
130
- const close = () => {
131
- if (closing !== undefined)
132
- return closing;
133
- const draining = ManagedServerValues.send({
134
- type: "draining",
135
- slot: process.env[slotMarker],
136
- incarnation: process.env[incarnationMarker],
137
- });
138
- // spine-log-boundary: server.managed_replica_drain_ipc
139
- const attempt = draining
140
- .catch(() => undefined)
141
- .then(() => {
142
- environmentDeliveryWorkerAccess.cancelManagedChild();
143
- managedChildSubscriptionAccess.clear();
144
- releaseActivation();
145
- return runningServerAccess.drainDelivery(server);
146
- })
147
- .then(() => {
148
- return server.close();
149
- })
150
- .then(() => {
151
- const closed = ManagedServerValues.send({
152
- type: "closed",
153
- slot: process.env[slotMarker],
154
- incarnation: process.env[incarnationMarker],
155
- });
156
- // spine-log-boundary: server.managed_replica_closed_ipc
157
- return closed.catch(() => undefined);
158
- }, (error) => {
159
- const failed = ManagedServerValues.send({
160
- type: "close-failed",
161
- slot: process.env[slotMarker],
162
- incarnation: process.env[incarnationMarker],
163
- });
164
- // spine-log-boundary: server.managed_replica_close_failed_ipc
165
- void failed.catch(() => undefined);
166
- if (closing === attempt)
167
- closing = undefined;
168
- throw error;
169
- })
170
- .then(() => {
171
- process.off("disconnect", onDisconnect);
172
- if (process.connected && typeof process.disconnect === "function")
173
- process.disconnect();
174
- process.off("error", containIpcError);
175
- process.off("message", onMessage);
176
- });
177
- closing = attempt;
178
- return attempt;
179
- };
180
- const onMessage = (message) => {
181
- if (message.type === "activate" && !activated) {
182
- activated = true;
183
- environmentDeliveryWorkerAccess.activateManagedChild();
184
- releaseActivation();
185
- }
186
- else if (message.type === "close") {
187
- ManagedServerValues.closeFromEvent(close);
188
- }
189
- };
190
- const onDisconnect = () => {
191
- ManagedServerValues.closeFromEvent(close);
192
- };
193
- process.on("message", onMessage);
194
- process.once("disconnect", onDisconnect);
195
- try {
196
- await ManagedServerValues.send({
197
- type: "synchronizing",
198
- slot: process.env[slotMarker],
199
- incarnation: process.env[incarnationMarker],
200
- endpoint: server.baseUrl,
201
- });
202
- }
203
- catch (error) {
204
- await close();
205
- throw error;
206
- }
207
- await activation;
208
- await ManagedServerValues.send({
209
- type: "ready",
210
- slot: process.env[slotMarker],
211
- incarnation: process.env[incarnationMarker],
212
- endpoint: server.baseUrl,
213
- });
214
- return {
215
- ready: true,
216
- close,
217
- };
109
+ return new ManagedChildLifecycle(server).start();
218
110
  },
219
111
  /**
220
112
  * Starts a coordinator for the configured complete-replica cohort.
@@ -277,6 +169,177 @@ const ManagedServerValues = Object.freeze({
277
169
  });
278
170
  },
279
171
  });
172
+ /**
173
+ * Runs the private child IPC lifecycle while keeping its listener open for relay cleanup.
174
+ */
175
+ class ManagedChildLifecycle {
176
+ #activation = ManagedServerCoordinatorValues.deferred();
177
+ #serverCloseRelease = ManagedServerCoordinatorValues.deferred();
178
+ #server;
179
+ #activated = false;
180
+ #waitForParentClose = false;
181
+ #closing;
182
+ /**
183
+ * Creates the lifecycle around an assembled child server.
184
+ *
185
+ * @param server The already assembled and synchronized child server.
186
+ */
187
+ constructor(server) {
188
+ this.#server = server;
189
+ }
190
+ /**
191
+ *
192
+ * Starts the child and waits for Coordinator activation.
193
+ *
194
+ * @returns A child handle after the private READY notification.
195
+ */
196
+ async start() {
197
+ process.on("error", this.#containIpcError);
198
+ managedChildSubscriptionAccess.install((subscriptionId) => {
199
+ const notification = ManagedServerValues.send({
200
+ type: "subscription-installed",
201
+ slot: process.env[slotMarker],
202
+ incarnation: process.env[incarnationMarker],
203
+ subscriptionId,
204
+ });
205
+ // spine-log-boundary: server.managed_replica_subscription_ipc
206
+ void notification.catch(() => undefined);
207
+ });
208
+ process.on("message", this.#onMessage);
209
+ process.once("disconnect", this.#onDisconnect);
210
+ try {
211
+ await this.#notify("synchronizing", this.#server.baseUrl);
212
+ }
213
+ catch (error) {
214
+ this.#serverCloseRelease.resolve(undefined);
215
+ await this.close();
216
+ throw error;
217
+ }
218
+ await this.#activation.promise;
219
+ await this.#notify("ready", this.#server.baseUrl);
220
+ return { ready: true, close: () => this.close() };
221
+ }
222
+ /**
223
+ *
224
+ * Closes Delivery before closing the child listener and its server.
225
+ *
226
+ * @returns The shared close attempt; a failed attempt may be retried.
227
+ */
228
+ close() {
229
+ this.#closing ??= this.#closeOnce().catch((error) => {
230
+ this.#closing = undefined;
231
+ throw error;
232
+ });
233
+ return this.#closing;
234
+ }
235
+ /**
236
+ *
237
+ * Performs child shutdown and observes a failed completion notification.
238
+ *
239
+ * @returns Completion after the child server closes.
240
+ */
241
+ async #closeOnce() {
242
+ try {
243
+ await this.#drainDelivery();
244
+ await this.#closeServerAfterRelayCleanup();
245
+ // spine-log-boundary: server.managed_replica_closed_ipc
246
+ await this.#notify("closed").catch(() => undefined);
247
+ this.#disconnect();
248
+ }
249
+ catch (error) {
250
+ // spine-log-boundary: server.managed_replica_close_failed_ipc
251
+ await this.#notify("close-failed").catch(() => undefined);
252
+ throw error;
253
+ }
254
+ }
255
+ /**
256
+ *
257
+ * Stops new child Delivery work and awaits admitted effects.
258
+ *
259
+ * @returns Completion when admitted work drains.
260
+ */
261
+ async #drainDelivery() {
262
+ managedDeliveryGate.cancel();
263
+ managedChildSubscriptionAccess.clear();
264
+ this.#activation.resolve(undefined);
265
+ // spine-log-boundary: server.managed_replica_drain_ipc
266
+ void this.#notify("draining").catch(() => undefined);
267
+ await runningServerAccess.drainDelivery(this.#server);
268
+ }
269
+ /**
270
+ *
271
+ * Waits for parent relay cancellation before closing the child listener.
272
+ *
273
+ * @returns Completion after final server close.
274
+ */
275
+ async #closeServerAfterRelayCleanup() {
276
+ // spine-log-boundary: server.managed_replica_delivery_drained_ipc
277
+ void this.#notify("delivery-drained").catch(() => undefined);
278
+ if (this.#waitForParentClose)
279
+ await this.#serverCloseRelease.promise;
280
+ await this.#server.close();
281
+ }
282
+ /**
283
+ *
284
+ * Sends one child lifecycle notification on the private IPC channel.
285
+ *
286
+ * @param type The lifecycle phase to report.
287
+ * @param endpoint The listening endpoint for readiness messages.
288
+ * @returns Completion after the parent accepts the frame.
289
+ */
290
+ #notify(type, endpoint) {
291
+ return ManagedServerValues.send({
292
+ type,
293
+ slot: process.env[slotMarker],
294
+ incarnation: process.env[incarnationMarker],
295
+ ...(endpoint === undefined ? {} : { endpoint }),
296
+ });
297
+ }
298
+ /**
299
+ *
300
+ * Releases child startup and close gates for matching parent messages.
301
+ *
302
+ * @param message The private parent frame.
303
+ */
304
+ #onMessage = (message) => {
305
+ if (message.type === "activate" && !this.#activated) {
306
+ this.#activated = true;
307
+ void managedDeliveryGate.activate().then(() => {
308
+ this.#activation.resolve(undefined);
309
+ }, (error) => {
310
+ this.#activation.reject(error);
311
+ });
312
+ }
313
+ else if (message.type === "close") {
314
+ this.#waitForParentClose = true;
315
+ ManagedServerValues.closeFromEvent(() => this.close());
316
+ }
317
+ else if (message.type === "finish-close") {
318
+ this.#serverCloseRelease.resolve(undefined);
319
+ }
320
+ };
321
+ /**
322
+ * Releases the final close phase if the parent disappears.
323
+ */
324
+ #onDisconnect = () => {
325
+ this.#serverCloseRelease.resolve(undefined);
326
+ ManagedServerValues.closeFromEvent(() => this.close());
327
+ };
328
+ /**
329
+ * Contains IPC EPIPE after an already disappeared parent.
330
+ */
331
+ #containIpcError = () => undefined;
332
+ /**
333
+ * Removes private listeners once the child server has closed.
334
+ */
335
+ #disconnect() {
336
+ process.off("disconnect", this.#onDisconnect);
337
+ if (process.connected && typeof process.disconnect === "function")
338
+ process.disconnect();
339
+ process.off("error", this.#containIpcError);
340
+ process.off("message", this.#onMessage);
341
+ }
342
+ }
280
343
  /**
281
344
  *
282
345
  * Coordinates the parent-only lifecycle of managed application replicas.
@@ -288,6 +351,8 @@ export class ManagedServerCoordinator {
288
351
  #restart;
289
352
  #options;
290
353
  #dependencies;
354
+ #terminalChildFailure;
355
+ #drainStart;
291
356
  #closing = false;
292
357
  #starts = 0;
293
358
  #ready;
@@ -394,6 +459,16 @@ export class ManagedServerCoordinator {
394
459
  this.#drainStarts();
395
460
  return;
396
461
  }
462
+ this.#installReplica(slot, incarnation, child);
463
+ }
464
+ /**
465
+ * Records a spawned child and observes its private lifecycle messages.
466
+ *
467
+ * @param slot The configured replica slot.
468
+ * @param incarnation The current child incarnation identifier.
469
+ * @param child The spawned process handle.
470
+ */
471
+ #installReplica(slot, incarnation, child) {
397
472
  const replica = {
398
473
  slot: slot.slot,
399
474
  incarnation,
@@ -404,6 +479,9 @@ export class ManagedServerCoordinator {
404
479
  draining: false,
405
480
  subscriptionWaiters: new Map(),
406
481
  closeAttempt: undefined,
482
+ closePromise: undefined,
483
+ closeFailure: undefined,
484
+ forcedFailure: undefined,
407
485
  expectedExit: false,
408
486
  terminal: false,
409
487
  };
@@ -428,43 +506,86 @@ export class ManagedServerCoordinator {
428
506
  * @param message The untrusted child IPC notification.
429
507
  */
430
508
  #onMessage(slot, replica, message) {
509
+ if (this.#onCloseMessage(slot, replica, message))
510
+ return;
511
+ if (ManagedServerCoordinatorValues.subscriptionInstalledMessage(message, slot.slot, replica.incarnation)) {
512
+ if (slot.replica === replica)
513
+ this.#settleSubscriptionWaiter(replica, message.subscriptionId);
514
+ return;
515
+ }
516
+ if (this.#onTransitionMessage(slot, replica, message))
517
+ return;
518
+ this.#onReadyMessage(slot, replica, message);
519
+ }
520
+ /**
521
+ * Settles the private child shutdown notifications.
522
+ *
523
+ * @param slot The reported replica slot.
524
+ * @param replica The matching child incarnation.
525
+ * @param message The candidate notification.
526
+ * @returns Whether a close-phase notification was consumed.
527
+ */
528
+ #onCloseMessage(slot, replica, message) {
431
529
  const closeResult = ManagedServerCoordinatorValues.closeResultMessage(message, slot.slot, replica.incarnation);
432
530
  if (closeResult !== undefined) {
433
531
  if (slot.replica !== replica || !replica.draining || replica.closeAttempt === undefined)
434
- return;
532
+ return true;
435
533
  if (closeResult === "closed")
436
534
  replica.closeAttempt.outcome.resolve(undefined);
437
- else
535
+ else {
536
+ replica.closeAttempt.drained.reject(new Error("Managed child Delivery drain failed."));
438
537
  replica.closeAttempt.outcome.reject(new Error("Managed child server close failed."));
439
- return;
538
+ }
539
+ return true;
440
540
  }
441
- if (ManagedServerCoordinatorValues.subscriptionInstalledMessage(message, slot.slot, replica.incarnation)) {
442
- if (slot.replica !== replica)
443
- return;
444
- this.#settleSubscriptionWaiter(replica, message.subscriptionId);
445
- return;
541
+ if (ManagedServerCoordinatorValues.deliveryDrainedMessage(message, slot.slot, replica.incarnation)) {
542
+ if (replica.closeAttempt !== undefined)
543
+ replica.closeAttempt.drainedAcknowledged = true;
544
+ replica.closeAttempt?.drained.resolve(undefined);
545
+ return true;
446
546
  }
547
+ return false;
548
+ }
549
+ /**
550
+ * Advances a synchronizing or draining child without completing public readiness.
551
+ *
552
+ * @param slot The reported replica slot.
553
+ * @param replica The matching child incarnation.
554
+ * @param message The candidate notification.
555
+ * @returns Whether a transition notification was consumed.
556
+ */
557
+ #onTransitionMessage(slot, replica, message) {
447
558
  if (ManagedServerCoordinatorValues.synchronizingMessage(message, slot.slot, replica.incarnation)) {
448
559
  if (slot.replica !== replica || replica.endpoint !== undefined)
449
- return;
560
+ return true;
450
561
  replica.endpoint = message.endpoint;
451
562
  replica.synchronizing = true;
452
563
  this.#notifyReadyMembers();
453
564
  if (this.#nodeCoordinator === undefined)
454
565
  replica.child.send({ type: "activate" }, () => undefined);
455
- return;
566
+ return true;
456
567
  }
457
568
  if (ManagedServerCoordinatorValues.drainingMessage(message, slot.slot, replica.incarnation)) {
458
569
  if (slot.replica !== replica)
459
- return;
570
+ return true;
460
571
  replica.closeAttempt?.acknowledged.resolve(undefined);
461
572
  if (replica.draining)
462
- return;
573
+ return true;
463
574
  replica.draining = true;
464
575
  replica.expectedExit = true;
465
576
  this.#notifyReadyMembers();
466
- return;
577
+ return true;
467
578
  }
579
+ return false;
580
+ }
581
+ /**
582
+ * Publishes one fully ready child after its synchronization phase.
583
+ *
584
+ * @param slot The reported replica slot.
585
+ * @param replica The matching child incarnation.
586
+ * @param message The candidate notification.
587
+ */
588
+ #onReadyMessage(slot, replica, message) {
468
589
  if (!ManagedServerCoordinatorValues.readyMessage(message, slot.slot, replica.incarnation))
469
590
  return;
470
591
  if (slot.replica !== replica || replica.readyAt !== undefined || this.#closing)
@@ -490,6 +611,9 @@ export class ManagedServerCoordinator {
490
611
  if (slot.replica !== replica || replica.terminal)
491
612
  return;
492
613
  replica.terminal = true;
614
+ if (this.#closing && replica.closeFailure !== undefined)
615
+ this.#terminalChildFailure ??= replica.closeFailure;
616
+ this.#failIncompleteDrain(replica.closeAttempt);
493
617
  this.#clearSubscriptionWaiters(replica, new Error("Managed child exited before subscription installed."));
494
618
  slot.replica = undefined;
495
619
  this.#notifyReadyMembers();
@@ -499,10 +623,10 @@ export class ManagedServerCoordinator {
499
623
  }
500
624
  if (this.#closing || replica.expectedExit)
501
625
  return;
502
- if (replica.readyAt !== undefined &&
503
- this.#dependencies.clock.now() - replica.readyAt >= this.#restart.healthyReadyMs) {
626
+ const healthy = replica.readyAt !== undefined &&
627
+ this.#dependencies.clock.now() - replica.readyAt >= this.#restart.healthyReadyMs;
628
+ if (healthy)
504
629
  slot.failures = 0;
505
- }
506
630
  // spine-log-boundary: server.managed_replica_exit
507
631
  emitServerWarning(lifecycleLogger, "Managed replica exited unexpectedly.", {
508
632
  operation: "managed_replica.exit",
@@ -514,6 +638,20 @@ export class ManagedServerCoordinator {
514
638
  this.#scheduleReplacement(slot);
515
639
  this.#drainStarts();
516
640
  }
641
+ /**
642
+ * Retains an exited child's missing drain acknowledgement across close attempts.
643
+ *
644
+ * @param closeAttempt The pending close attempt, if any.
645
+ */
646
+ #failIncompleteDrain(closeAttempt) {
647
+ if (closeAttempt === undefined || closeAttempt.drainedAcknowledged)
648
+ return;
649
+ const failure = new Error("Managed child exited before Delivery drained.");
650
+ closeAttempt.exitFailure = failure;
651
+ if (this.#closing)
652
+ this.#terminalChildFailure ??= failure;
653
+ closeAttempt.drained.reject(failure);
654
+ }
517
655
  /**
518
656
  * Retires a child after a process error and starts bounded termination.
519
657
  *
@@ -571,37 +709,7 @@ export class ManagedServerCoordinator {
571
709
  const closing = this.#close;
572
710
  if (closing !== undefined)
573
711
  return closing;
574
- const close = (async () => {
575
- this.#closing = true;
576
- this.#removeSignalHandlers();
577
- for (const slot of this.#slots) {
578
- if (slot.replacementTimer !== undefined)
579
- this.#dependencies.clock.clearTimeout(slot.replacementTimer);
580
- slot.replacementTimer = undefined;
581
- }
582
- // `beginDrain()` makes the unary snapshot empty synchronously. Start every
583
- // child quiescence attempt without waiting for its reconciliation promise:
584
- // child close IPC must be issued in this turn, while the Coordinator keeps
585
- // subscription relays alive until every child has settled.
586
- const draining = this.#nodeCoordinator?.beginDrain();
587
- for (const slot of this.#slots)
588
- if (slot.replica !== undefined)
589
- this.#clearSubscriptionWaiters(slot.replica, new Error("Managed application is closing before subscription installed."));
590
- const childCloses = [
591
- ...this.#slots.map((slot) => this.#closeReplica(slot.replica)),
592
- ...[...this.#retired].map((replica) => this.#closeRetired(replica)),
593
- ];
594
- const results = await Promise.allSettled([draining, ...childCloses]);
595
- const failures = [];
596
- for (const result of results)
597
- if (result.status === "rejected")
598
- failures.push(result.reason);
599
- if (failures.length === 1)
600
- throw failures[0];
601
- if (failures.length > 1)
602
- throw new AggregateError(failures, "Managed child quiescence failed during drain.");
603
- await this.#nodeCoordinator?.close();
604
- })();
712
+ const close = this.#closeGracefully();
605
713
  this.#close = close;
606
714
  void close.then(undefined, () => {
607
715
  if (this.#close === close)
@@ -609,6 +717,91 @@ export class ManagedServerCoordinator {
609
717
  });
610
718
  return close;
611
719
  }
720
+ /**
721
+ * Stops replacement starts and awaits all cleanup phases.
722
+ */
723
+ async #closeGracefully() {
724
+ this.#closing = true;
725
+ this.#removeSignalHandlers();
726
+ for (const slot of this.#slots) {
727
+ if (slot.replacementTimer !== undefined)
728
+ this.#dependencies.clock.clearTimeout(slot.replacementTimer);
729
+ slot.replacementTimer = undefined;
730
+ }
731
+ await this.#drainAndCloseReplicas();
732
+ }
733
+ /**
734
+ * Drains admitted child work, removes relays while listeners live, then closes children.
735
+ *
736
+ * @returns Completion after Coordinator and child cleanup attempts settle.
737
+ */
738
+ async #drainAndCloseReplicas() {
739
+ this.#drainStart ??= this.#nodeCoordinator?.beginDrain() ?? Promise.resolve();
740
+ const closing = new Error("Managed application is closing before subscription installed.");
741
+ for (const slot of this.#slots)
742
+ if (slot.replica !== undefined)
743
+ this.#clearSubscriptionWaiters(slot.replica, closing);
744
+ const replicas = this.#slots.flatMap((slot) => slot.replica === undefined ? [] : [slot.replica]);
745
+ const childCloses = [
746
+ ...replicas.map((replica) => this.#closeReplica(replica)),
747
+ ...[...this.#retired].map((replica) => this.#closeRetired(replica)),
748
+ ];
749
+ const children = Promise.allSettled(childCloses);
750
+ const failures = await this.#awaitDeliveryDrains(replicas);
751
+ await this.#finishClose(replicas, children, failures);
752
+ }
753
+ /**
754
+ * Cancels relays, releases children, and closes the Coordinator after a drain attempt.
755
+ * @param replicas The active children being closed.
756
+ * @param children The already-running bounded child close attempts.
757
+ * @param failures Errors collected from the drain phase.
758
+ */
759
+ async #finishClose(replicas, children, failures) {
760
+ try {
761
+ await this.#nodeCoordinator?.closeSubscriptions();
762
+ }
763
+ catch (error) {
764
+ failures.push(error);
765
+ }
766
+ for (const replica of replicas) {
767
+ try {
768
+ if (replica.child.connected)
769
+ replica.child.send({ type: "finish-close" }, () => undefined);
770
+ }
771
+ catch (error) {
772
+ failures.push(error);
773
+ }
774
+ }
775
+ const childResults = await children;
776
+ const coordinator = Promise.resolve().then(() => this.#nodeCoordinator?.close());
777
+ const [coordinatorResult] = await Promise.allSettled([coordinator]);
778
+ for (const result of childResults)
779
+ if (result.status === "rejected" && !failures.includes(result.reason))
780
+ failures.push(result.reason);
781
+ if (coordinatorResult.status === "rejected")
782
+ failures.push(coordinatorResult.reason);
783
+ if (this.#terminalChildFailure !== undefined && !failures.includes(this.#terminalChildFailure))
784
+ failures.push(this.#terminalChildFailure);
785
+ if (failures.length === 1)
786
+ throw failures[0];
787
+ if (failures.length > 1)
788
+ throw new AggregateError(failures, "Managed child quiescence failed during drain.");
789
+ }
790
+ /**
791
+ * Waits for actual child Delivery drains without awaiting final child exits.
792
+ *
793
+ * @param replicas The active child incarnations.
794
+ * @returns Drain failures to report after child and Coordinator cleanup.
795
+ */
796
+ async #awaitDeliveryDrains(replicas) {
797
+ const deliveryDrains = replicas.map((replica) => replica.closeAttempt?.drained.promise ?? Promise.resolve());
798
+ const results = await Promise.allSettled([this.#drainStart, ...deliveryDrains]);
799
+ const failures = [];
800
+ for (const result of results)
801
+ if (result.status === "rejected")
802
+ failures.push(result.reason);
803
+ return failures;
804
+ }
612
805
  /**
613
806
  * Closes the current replica through the bounded child shutdown procedure.
614
807
  *
@@ -616,7 +809,28 @@ export class ManagedServerCoordinator {
616
809
  * @returns Completion after child shutdown.
617
810
  */
618
811
  #closeReplica(replica) {
619
- return ManagedServerCoordinatorValues.close(replica, this.#dependencies.clock);
812
+ if (replica === undefined)
813
+ return Promise.resolve();
814
+ if (replica.closePromise !== undefined)
815
+ return replica.closePromise;
816
+ replica.closeFailure = undefined;
817
+ const close = ManagedServerCoordinatorValues.close(replica, this.#dependencies.clock);
818
+ replica.closePromise = close;
819
+ void close.then(() => {
820
+ replica.closeFailure = undefined;
821
+ }, (error) => {
822
+ replica.closeFailure = error;
823
+ if (replica.forcedFailure !== undefined || replica.terminal)
824
+ this.#terminalChildFailure ??= replica.forcedFailure ?? error;
825
+ });
826
+ // spine-log-boundary: server.managed_replica_close_finally_observer
827
+ void close
828
+ .finally(() => {
829
+ if (replica.closePromise === close)
830
+ replica.closePromise = undefined;
831
+ })
832
+ .catch(() => undefined);
833
+ return close;
620
834
  }
621
835
  /**
622
836
  * Terminates a retired replica and removes it from the pending set.
@@ -928,6 +1142,8 @@ const ManagedServerCoordinatorValues = Object.freeze({
928
1142
  resolve = next;
929
1143
  reject = fail;
930
1144
  });
1145
+ // spine-log-boundary: server.managed_deferred_rejection_observer
1146
+ void promise.catch(() => undefined);
931
1147
  return { promise, resolve, reject };
932
1148
  },
933
1149
  /**
@@ -943,7 +1159,7 @@ const ManagedServerCoordinatorValues = Object.freeze({
943
1159
  },
944
1160
  dependencies: {
945
1161
  clock: {
946
- now: () => Time.currentTimeMillis(),
1162
+ now: () => Time.monotonicTime(),
947
1163
  setTimeout: (onTimeout, delay) => setTimeout(onTimeout, delay),
948
1164
  clearTimeout: (timer) => {
949
1165
  clearTimeout(timer);
@@ -1053,6 +1269,22 @@ const ManagedServerCoordinatorValues = Object.freeze({
1053
1269
  candidate.slot === String(slot) &&
1054
1270
  candidate.incarnation === incarnation);
1055
1271
  },
1272
+ /**
1273
+ * Matches the child's completed Delivery drain before its listener closes.
1274
+ *
1275
+ * @param message The candidate child IPC notification.
1276
+ * @param slot The expected replica slot.
1277
+ * @param incarnation The expected child incarnation token.
1278
+ * @returns Whether the notification belongs to that child.
1279
+ */
1280
+ deliveryDrainedMessage(message, slot, incarnation) {
1281
+ if (typeof message !== "object" || message === null)
1282
+ return false;
1283
+ const candidate = message;
1284
+ return (candidate.type === "delivery-drained" &&
1285
+ candidate.slot === String(slot) &&
1286
+ candidate.incarnation === incarnation);
1287
+ },
1056
1288
  /**
1057
1289
  * Matches a CLOSED or CLOSE-FAILED notification from the expected child.
1058
1290
  *
@@ -1090,35 +1322,93 @@ const ManagedServerCoordinatorValues = Object.freeze({
1090
1322
  }));
1091
1323
  const closeAttempt = {
1092
1324
  acknowledged: ManagedServerCoordinatorValues.deferred(),
1325
+ drained: ManagedServerCoordinatorValues.deferred(),
1093
1326
  outcome: ManagedServerCoordinatorValues.deferred(),
1094
1327
  };
1095
1328
  replica.closeAttempt = closeAttempt;
1096
1329
  try {
1097
- if (child.connected)
1098
- child.send({ type: "close" }, () => undefined);
1099
- const observation = Promise.race([
1100
- closeAttempt.acknowledged.promise.then(() => "acknowledged"),
1101
- exited.then(() => "exited"),
1102
- ]);
1103
- if (await ManagedServerCoordinatorValues.within(observation, closeGraceMs, clock)) {
1104
- if ((await observation) === "exited")
1105
- return;
1106
- await Promise.race([
1107
- closeAttempt.outcome.promise,
1108
- exited.then(() => {
1109
- throw new Error("Managed child exited before graceful close completed.");
1110
- }),
1111
- ]);
1112
- await ManagedServerCoordinatorValues.terminate(child, clock, exited);
1113
- return;
1114
- }
1115
- await ManagedServerCoordinatorValues.terminate(child, clock, exited, false);
1330
+ await ManagedServerCoordinatorValues.awaitChildClose(child, closeAttempt, exited, clock);
1331
+ }
1332
+ catch (error) {
1333
+ replica.forcedFailure = closeAttempt.forcedFailure;
1334
+ throw error;
1116
1335
  }
1117
1336
  finally {
1118
1337
  if (replica.closeAttempt === closeAttempt)
1119
1338
  replica.closeAttempt = undefined;
1120
1339
  }
1121
1340
  },
1341
+ /**
1342
+ * Waits for child drain and final close, escalating only after bounded acknowledgement.
1343
+ *
1344
+ * @param child The child process to close.
1345
+ * @param closeAttempt Tracks the child's IPC phase acknowledgements.
1346
+ * @param exited Resolves when the child exits.
1347
+ * @param clock Supplies bounded shutdown timers.
1348
+ * @returns Completion after the child has exited or close failed.
1349
+ */
1350
+ async awaitChildClose(child, closeAttempt, exited, clock) {
1351
+ if (child.connected)
1352
+ child.send({ type: "close" }, () => undefined);
1353
+ const observation = Promise.race([
1354
+ closeAttempt.acknowledged.promise.then(() => "acknowledged"),
1355
+ exited.then(() => "exited"),
1356
+ ]);
1357
+ if (!(await ManagedServerCoordinatorValues.within(observation, closeGraceMs, clock))) {
1358
+ await this.missingDrainAck(child, closeAttempt, exited, clock);
1359
+ }
1360
+ if ((await observation) === "exited") {
1361
+ this.verifyExitedDrain(closeAttempt);
1362
+ return;
1363
+ }
1364
+ await ManagedServerCoordinatorValues.awaitChildOutcome(closeAttempt, exited);
1365
+ await ManagedServerCoordinatorValues.terminate(child, clock, exited);
1366
+ },
1367
+ /**
1368
+ * Rejects an unacknowledged Delivery drain after bounded child termination.
1369
+ *
1370
+ * @param child The unresponsive child process.
1371
+ * @param closeAttempt The current close attempt.
1372
+ * @param exited Resolves when the child exits.
1373
+ * @param clock Supplies bounded shutdown timers.
1374
+ * @returns No value; the missing acknowledgement always rejects.
1375
+ */
1376
+ async missingDrainAck(child, closeAttempt, exited, clock) {
1377
+ const failure = new Error("Managed child did not acknowledge Delivery drain.");
1378
+ closeAttempt.forcedFailure = failure;
1379
+ closeAttempt.drained.reject(failure);
1380
+ try {
1381
+ await this.terminate(child, clock, exited, false);
1382
+ }
1383
+ catch (termination) {
1384
+ throw new AggregateError([failure, termination], "Managed child shutdown failed.");
1385
+ }
1386
+ throw failure;
1387
+ },
1388
+ /**
1389
+ * Rejects a child exit that preceded its Delivery drain acknowledgement.
1390
+ *
1391
+ * @param closeAttempt The observed close attempt.
1392
+ */
1393
+ verifyExitedDrain(closeAttempt) {
1394
+ if (closeAttempt.exitFailure !== undefined)
1395
+ throw closeAttempt.exitFailure;
1396
+ },
1397
+ /**
1398
+ * Waits for the real child close outcome before process reaping.
1399
+ *
1400
+ * @param closeAttempt Tracks the child's close notifications.
1401
+ * @param exited Resolves when the child process exits.
1402
+ * @returns Completion after the child's final close fact.
1403
+ */
1404
+ async awaitChildOutcome(closeAttempt, exited) {
1405
+ await Promise.race([
1406
+ closeAttempt.outcome.promise,
1407
+ exited.then(() => {
1408
+ throw new Error("Managed child exited before graceful close completed.");
1409
+ }),
1410
+ ]);
1411
+ },
1122
1412
  /**
1123
1413
  * Waits for child exit, then escalates through SIGTERM and SIGKILL within bounded intervals.
1124
1414
  *
@@ -1161,6 +1451,9 @@ const ManagedServerCoordinatorValues = Object.freeze({
1161
1451
  void promise.then(() => {
1162
1452
  clock.clearTimeout(timer);
1163
1453
  resolve(true);
1454
+ }, () => {
1455
+ clock.clearTimeout(timer);
1456
+ resolve(true);
1164
1457
  });
1165
1458
  });
1166
1459
  },