@dougongjs/core 0.3.0 → 0.5.0

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 (63) hide show
  1. package/README.md +2 -2
  2. package/dist/change-set.d.ts +15 -6
  3. package/dist/change-set.d.ts.map +1 -1
  4. package/dist/configuration.d.ts +12 -1
  5. package/dist/configuration.d.ts.map +1 -1
  6. package/dist/contract-registry.d.ts +2 -2
  7. package/dist/contract-registry.d.ts.map +1 -1
  8. package/dist/contracts.d.ts +27 -0
  9. package/dist/contracts.d.ts.map +1 -1
  10. package/dist/contribution-store.d.ts +21 -2
  11. package/dist/contribution-store.d.ts.map +1 -1
  12. package/dist/diagnostics.d.ts +21 -3
  13. package/dist/diagnostics.d.ts.map +1 -1
  14. package/dist/engine.d.ts +30 -9
  15. package/dist/engine.d.ts.map +1 -1
  16. package/dist/errors.d.ts +49 -3
  17. package/dist/errors.d.ts.map +1 -1
  18. package/dist/event-hub.d.ts +12 -1
  19. package/dist/event-hub.d.ts.map +1 -1
  20. package/dist/group-configuration.d.ts +20 -0
  21. package/dist/group-configuration.d.ts.map +1 -0
  22. package/dist/group-coordinator.d.ts +34 -15
  23. package/dist/group-coordinator.d.ts.map +1 -1
  24. package/dist/group-lifecycle.d.ts +13 -0
  25. package/dist/group-lifecycle.d.ts.map +1 -1
  26. package/dist/group.d.ts +5 -11
  27. package/dist/group.d.ts.map +1 -1
  28. package/dist/host-api.d.ts +35 -1
  29. package/dist/host-api.d.ts.map +1 -1
  30. package/dist/host.d.ts.map +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1053 -647
  34. package/dist/installation-graph.d.ts +8 -1
  35. package/dist/installation-graph.d.ts.map +1 -1
  36. package/dist/installation-registry.d.ts +7 -2
  37. package/dist/installation-registry.d.ts.map +1 -1
  38. package/dist/installation.d.ts +19 -1
  39. package/dist/installation.d.ts.map +1 -1
  40. package/dist/instance-coordinator.d.ts +64 -0
  41. package/dist/instance-coordinator.d.ts.map +1 -0
  42. package/dist/lifecycle-status.d.ts.map +1 -1
  43. package/dist/lifetime-diagnostics.d.ts +7 -1
  44. package/dist/lifetime-diagnostics.d.ts.map +1 -1
  45. package/dist/lifetime.d.ts +44 -8
  46. package/dist/lifetime.d.ts.map +1 -1
  47. package/dist/plugin.d.ts +28 -2
  48. package/dist/plugin.d.ts.map +1 -1
  49. package/dist/readonly-map.d.ts +8 -1
  50. package/dist/readonly-map.d.ts.map +1 -1
  51. package/dist/record.d.ts +16 -1
  52. package/dist/record.d.ts.map +1 -1
  53. package/dist/resource.d.ts +6 -0
  54. package/dist/resource.d.ts.map +1 -1
  55. package/dist/serial-queue.d.ts +7 -1
  56. package/dist/serial-queue.d.ts.map +1 -1
  57. package/dist/snapshot-view.d.ts +2 -0
  58. package/dist/snapshot-view.d.ts.map +1 -1
  59. package/dist/sync-result.d.ts +3 -0
  60. package/dist/sync-result.d.ts.map +1 -0
  61. package/package.json +1 -1
  62. package/dist/runtime.d.ts +0 -36
  63. package/dist/runtime.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,65 +1,136 @@
1
- function rt(r, t) {
1
+ function Ot(r, t) {
2
2
  if (typeof t != "string" || !t.trim())
3
3
  throw new TypeError(`${r} id must be a non-empty string`);
4
4
  if (t !== t.trim())
5
5
  throw new TypeError(`${r} id cannot start or end with whitespace`);
6
6
  }
7
- function V(r, t) {
8
- return rt("Contract", t), Object.freeze({ id: t, kind: r });
7
+ function Q(r, t) {
8
+ return Ot("Contract", t), Object.freeze({ id: t, kind: r });
9
9
  }
10
- function v(r, t) {
10
+ function E(r, t) {
11
11
  if (!r || typeof r != "object") return !1;
12
12
  const e = r;
13
13
  return typeof e.id == "string" && e.id.length > 0 && e.id === e.id.trim() && (e.kind === "service" || e.kind === "extensionPoint" || e.kind === "event") && (t === void 0 || e.kind === t);
14
14
  }
15
- function b(r, t) {
16
- if (!v(r, t))
17
- throw new TypeError(t ? `Expected ${nt(t)}` : "Invalid contract");
15
+ function A(r, t) {
16
+ if (!E(r, t))
17
+ throw new TypeError(t ? `Expected ${At(t)}` : "Invalid contract");
18
18
  }
19
- function nt(r) {
19
+ function At(r) {
20
20
  return r === "extensionPoint" ? "an ExtensionPoint" : r === "event" ? "an Event" : "a Service";
21
21
  }
22
- function de(r) {
23
- return V("service", r);
22
+ function It(r) {
23
+ return Q("service", r);
24
24
  }
25
- function le(r) {
26
- return V("event", r);
25
+ function Le(r) {
26
+ return Q("event", r);
27
27
  }
28
- function ue(r) {
29
- return V("extensionPoint", r);
28
+ function ze(r) {
29
+ return Q("extensionPoint", r);
30
30
  }
31
- function fe(r) {
32
- if (!v(r, "service"))
31
+ function Re(r) {
32
+ if (!E(r, "service"))
33
33
  throw new TypeError("optional() expects a Service");
34
- return Object.freeze({ kind: "optional", service: r });
34
+ return Object.freeze({ kind: "optional", service: It(r.id) });
35
35
  }
36
- function ot(r) {
36
+ function jt(r) {
37
37
  if (!r || typeof r != "object") return !1;
38
38
  const t = r;
39
- return t.kind === "optional" && v(t.service, "service");
39
+ return t.kind === "optional" && E(t.service, "service");
40
40
  }
41
41
  class u extends Error {
42
+ name = "DougongError";
43
+ code;
42
44
  constructor(t, e, i) {
45
+ if (typeof t != "string" || t.trim() !== t || t.length === 0)
46
+ throw new TypeError("DougongError code must be a non-empty trimmed string");
47
+ if (typeof e != "string") throw new TypeError("DougongError message must be a string");
43
48
  super(e, i), this.code = t;
44
49
  }
50
+ }
51
+ class J extends Error {
52
+ name = "RecordedFailure";
45
53
  code;
46
- name = "DougongError";
54
+ snapshot;
55
+ constructor(t) {
56
+ if (!(t instanceof Error)) throw new TypeError("RecordedFailure expects an Error");
57
+ const e = t instanceof J ? t.snapshot : kt(t);
58
+ super(e.message), this.code = e.code, this.snapshot = e, e.stack !== void 0 && (this.stack = e.stack), Object.freeze(this);
59
+ }
60
+ }
61
+ function kt(r) {
62
+ const t = /* @__PURE__ */ new Set();
63
+ let e = 32;
64
+ const i = (s, n) => {
65
+ if (n > 4 || e-- <= 0 || t.has(s))
66
+ return Object.freeze({
67
+ name: "TruncatedError",
68
+ message: "Error chain truncated",
69
+ truncated: !0
70
+ });
71
+ if (!(s instanceof Error)) {
72
+ const p = s === null || ["string", "number", "boolean", "undefined", "bigint", "symbol"].includes(typeof s) ? String(s).slice(0, 4096) : "Non-Error object omitted";
73
+ return Object.freeze({ name: "NonError", message: p });
74
+ }
75
+ t.add(s);
76
+ let o = !1;
77
+ const a = (p, w) => (p.length > w && (o = !0), p.slice(0, w)), h = (p, w) => {
78
+ const b = m(s, p);
79
+ return typeof b == "string" ? a(b, w) : void 0;
80
+ }, c = (p) => {
81
+ const w = m(s, p);
82
+ if (Array.isArray(w))
83
+ return w.length > 8 && (o = !0), Array.from(
84
+ { length: Math.min(w.length, 8) },
85
+ (b, j) => m(w, j)
86
+ );
87
+ }, d = h("name", 256) ?? "Error", l = h("message", 4096) ?? "", f = h("stack", 16384), g = h("code", 256), I = m(s, "cause"), et = c("errors"), it = h("manifestName", 256), st = c("denied")?.filter((p) => typeof p == "string").map((p) => a(p, 256)), rt = c("issues")?.map((p) => {
88
+ const w = m(p, "message"), b = m(p, "path"), j = Array.isArray(b) ? Array.from({ length: Math.min(b.length, 16) }, (Pe, St) => {
89
+ const k = m(b, St), S = k && typeof k == "object" ? m(k, "key") : k;
90
+ return typeof S == "number" ? S : typeof S == "string" || typeof S == "symbol" ? a(String(S), 256) : "[omitted]";
91
+ }) : void 0;
92
+ return Array.isArray(b) && b.length > 16 && (o = !0), Object.freeze({
93
+ message: typeof w == "string" ? a(w, 4096) : "",
94
+ ...j ? { path: Object.freeze(j) } : {}
95
+ });
96
+ });
97
+ return Object.freeze({
98
+ name: d,
99
+ message: l,
100
+ ...g === void 0 ? {} : { code: g },
101
+ ...f === void 0 ? {} : { stack: f },
102
+ ...I === void 0 ? {} : { cause: i(I, n + 1) },
103
+ ...et === void 0 ? {} : { errors: Object.freeze(et.map((p) => i(p, n + 1))) },
104
+ ...it === void 0 ? {} : { manifestName: it },
105
+ ...st === void 0 ? {} : { denied: Object.freeze(st) },
106
+ ...rt === void 0 ? {} : { issues: Object.freeze(rt) },
107
+ ...o ? { truncated: !0 } : {}
108
+ });
109
+ };
110
+ return i(r, 0);
47
111
  }
48
- class P extends u {
112
+ function m(r, t) {
113
+ try {
114
+ return r && typeof r == "object" ? r[t] : void 0;
115
+ } catch {
116
+ return;
117
+ }
49
118
  }
50
- function $(r, t, e) {
51
- return r instanceof P ? r.code === t ? r : new P(t, e, { cause: r.cause }) : r instanceof Error ? r : new P(t, e, { cause: r });
119
+ class G extends u {
52
120
  }
53
- function J(r, t) {
121
+ function V(r, t, e) {
122
+ return r instanceof G ? r.code === t ? r : new G(t, e, { cause: r.cause }) : r instanceof Error ? r : new G(t, e, { cause: r });
123
+ }
124
+ function wt(r, t) {
54
125
  if (!r || typeof r != "object" || typeof r.aborted != "boolean" || typeof r.addEventListener != "function" || typeof r.removeEventListener != "function" || typeof r.throwIfAborted != "function")
55
126
  throw new TypeError("Cancellation classifier expects an AbortSignal");
56
127
  return r.aborted ? Object.is(t, r.reason) ? !0 : t instanceof Error && t.name === "AbortError" : !1;
57
128
  }
58
- class at extends u {
129
+ class $t extends u {
59
130
  name = "ConfigValidationError";
60
131
  issues;
61
132
  constructor(t) {
62
- const e = ct(t);
133
+ const e = Tt(t);
63
134
  super(
64
135
  "CONFIG_INVALID",
65
136
  `Invalid Plugin config:
@@ -68,7 +139,7 @@ ${e.map((i) => ` - ${i.message}`).join(`
68
139
  ), this.issues = e;
69
140
  }
70
141
  }
71
- function ct(r) {
142
+ function Tt(r) {
72
143
  if (!Array.isArray(r))
73
144
  throw new TypeError("Config validation issues must be an array");
74
145
  return Object.freeze(
@@ -80,23 +151,23 @@ function ct(r) {
80
151
  throw new TypeError(`Config validation issue at index ${e} message must be a string`);
81
152
  return Object.freeze({
82
153
  message: i.message,
83
- ...i.path === void 0 ? {} : { path: ht(i.path, e) }
154
+ ...i.path === void 0 ? {} : { path: Pt(i.path, e) }
84
155
  });
85
156
  })
86
157
  );
87
158
  }
88
- function ht(r, t) {
159
+ function Pt(r, t) {
89
160
  if (!Array.isArray(r))
90
161
  throw new TypeError(`Config validation issue at index ${t} path must be an array`);
91
162
  return Object.freeze(
92
163
  Array.from(r, (e, i) => {
93
- if (G(e)) return e;
164
+ if (nt(e)) return e;
94
165
  if (!e || typeof e != "object")
95
166
  throw new TypeError(
96
167
  `Config validation issue at index ${t} path segment ${i} must be a property key`
97
168
  );
98
169
  const s = e.key;
99
- if (!G(s))
170
+ if (!nt(s))
100
171
  throw new TypeError(
101
172
  `Config validation issue at index ${t} path segment ${i} must contain a property key`
102
173
  );
@@ -104,10 +175,10 @@ function ht(r, t) {
104
175
  })
105
176
  );
106
177
  }
107
- function G(r) {
178
+ function nt(r) {
108
179
  return typeof r == "string" || typeof r == "number" || typeof r == "symbol";
109
180
  }
110
- async function X(r, t, e) {
181
+ async function Lt(r, t, e) {
111
182
  if (!r) return t;
112
183
  const i = await r["~standard"].validate(t);
113
184
  if (!i || typeof i != "object")
@@ -116,7 +187,7 @@ async function X(r, t, e) {
116
187
  );
117
188
  const s = Object.hasOwn(i, "issues") ? i.issues : void 0;
118
189
  if (s !== void 0)
119
- throw Array.isArray(s) ? new at(s) : new TypeError(
190
+ throw Array.isArray(s) ? new $t(s) : new TypeError(
120
191
  `Installation '${e}' config validator returned non-array issues`
121
192
  );
122
193
  if (!Object.hasOwn(i, "value"))
@@ -125,29 +196,29 @@ async function X(r, t, e) {
125
196
  );
126
197
  return i.value;
127
198
  }
128
- class dt {
199
+ class zt {
129
200
  #t = /* @__PURE__ */ new Map();
130
201
  get kinds() {
131
202
  return this.#t;
132
203
  }
133
204
  assertCompatible(t) {
134
- Z(this.#t, t);
205
+ bt(this.#t, t);
135
206
  }
136
207
  remember(t) {
137
- T(this.#t, t);
208
+ q(this.#t, t);
138
209
  }
139
- draft(t) {
140
- return new lt(this, t);
210
+ writer(t) {
211
+ return new Rt(this, t);
141
212
  }
142
213
  commit(t) {
143
214
  for (const [e, i] of t) this.assertCompatible({ id: e, kind: i });
144
215
  for (const [e, i] of t) this.#t.set(e, i);
145
216
  }
146
217
  }
147
- class lt {
218
+ class Rt {
148
219
  #t;
149
220
  constructor(t, e) {
150
- this.#t = { phase: "open", registry: t, pending: /* @__PURE__ */ new Map() };
221
+ this.#t = { phase: "staged", registry: t, pending: /* @__PURE__ */ new Map() };
151
222
  for (const [i, s] of e) this.remember({ id: i, kind: s });
152
223
  }
153
224
  remember(t) {
@@ -157,21 +228,21 @@ class lt {
157
228
  return;
158
229
  }
159
230
  if (e.phase === "discarded")
160
- throw new Error("Contract registry draft has been discarded");
161
- e.registry.assertCompatible(t), !e.registry.kinds.has(t.id) && T(e.pending, t);
231
+ throw new Error("Contract registry writer has been discarded");
232
+ e.registry.assertCompatible(t), !e.registry.kinds.has(t.id) && q(e.pending, t);
162
233
  }
163
234
  commit() {
164
235
  const t = this.#t;
165
- t.phase === "open" && (t.registry.commit(t.pending), this.#t = { phase: "committed", registry: t.registry });
236
+ t.phase === "staged" && (t.registry.commit(t.pending), this.#t = { phase: "committed", registry: t.registry });
166
237
  }
167
238
  discard() {
168
- this.#t.phase === "open" && (this.#t = { phase: "discarded" });
239
+ this.#t.phase === "staged" && (this.#t = { phase: "discarded" });
169
240
  }
170
241
  }
171
- function T(r, t) {
172
- Z(r, t), r.set(t.id, t.kind);
242
+ function q(r, t) {
243
+ bt(r, t), r.set(t.id, t.kind);
173
244
  }
174
- function Z(r, t) {
245
+ function bt(r, t) {
175
246
  const e = r.get(t.id);
176
247
  if (e && e !== t.kind)
177
248
  throw new u(
@@ -179,9 +250,9 @@ function Z(r, t) {
179
250
  `Contract '${t.id}' is used as both '${e}' and '${t.kind}'`
180
251
  );
181
252
  }
182
- class M {
183
- constructor(t, e, i, s, n, o) {
184
- this.order = t, this.layers = e, this.providers = i, this.dependents = s, this.contractKinds = n, this.#t = o;
253
+ class X {
254
+ constructor(t, e, i, s, n, o, a) {
255
+ this.order = e, this.layers = i, this.providers = s, this.dependents = n, this.contractKinds = o, this.#t = t, this.#e = a;
185
256
  }
186
257
  order;
187
258
  layers;
@@ -189,13 +260,15 @@ class M {
189
260
  dependents;
190
261
  contractKinds;
191
262
  #t;
263
+ #e;
192
264
  static build(t, e) {
193
- const i = [...t].sort((c, d) => c.index - d.index), s = new Map(e), n = ut(i, s), o = ft(i, n, s), { order: a, layers: h } = pt(
265
+ const i = [...t].sort((c, d) => c.index - d.index), s = new Map(e), n = Nt(i, s), o = xt(i, n, s), { order: a, layers: h } = Mt(
194
266
  i,
195
267
  o.dependents,
196
268
  o.indegree
197
269
  );
198
- return new M(
270
+ return new X(
271
+ new Map(i.map((c) => [c, c.declaration])),
199
272
  Object.freeze(a),
200
273
  Object.freeze(h.map((c) => Object.freeze(c))),
201
274
  n,
@@ -204,17 +277,28 @@ class M {
204
277
  o.resolvedProviders
205
278
  );
206
279
  }
280
+ declarationFor(t) {
281
+ const e = this.#t.get(t);
282
+ if (!e) throw new Error(`Installation '${t.id}' is not in this plan`);
283
+ return e;
284
+ }
207
285
  providerFor(t, e) {
208
- return this.#t.get(t)?.get(e);
286
+ return this.#e.get(t)?.get(e);
209
287
  }
210
288
  provider(t) {
211
289
  return this.providers.get(t);
212
290
  }
291
+ /**
292
+ * Transitive dependents in *both* plans. A Service being replaced has old
293
+ * consumers that must stop and new ones that must start, and only the union
294
+ * covers both — expanding in the current plan alone would leave an Instance
295
+ * running against a dependency that no longer exists.
296
+ */
213
297
  affectedByTransitionTo(t, e) {
214
298
  const i = /* @__PURE__ */ new Set();
215
- return this.#e(e, i), t.#e(e, i), i;
299
+ return this.#i(e, i), t.#i(e, i), i;
216
300
  }
217
- #e(t, e) {
301
+ #i(t, e) {
218
302
  const i = [...t], s = /* @__PURE__ */ new Set();
219
303
  for (let n = 0; n < i.length; n++) {
220
304
  const o = i[n];
@@ -225,11 +309,11 @@ class M {
225
309
  }
226
310
  }
227
311
  }
228
- function ut(r, t) {
312
+ function Nt(r, t) {
229
313
  const e = /* @__PURE__ */ new Map();
230
314
  for (const i of r)
231
- for (const s of Object.values(i.declaration.plugin.provides ?? {})) {
232
- T(t, s);
315
+ for (const s of Object.values(i.declaration.plugin.provides)) {
316
+ q(t, s);
233
317
  const n = e.get(s.id);
234
318
  if (n)
235
319
  throw new u(
@@ -240,12 +324,12 @@ function ut(r, t) {
240
324
  }
241
325
  return e;
242
326
  }
243
- function ft(r, t, e) {
327
+ function xt(r, t, e) {
244
328
  const i = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map(), n = new Map(r.map((o) => [o, 0]));
245
329
  for (const o of r)
246
- for (const a of Object.values(o.declaration.plugin.requires ?? {})) {
330
+ for (const a of Object.values(o.declaration.plugin.requires)) {
247
331
  const h = a.kind === "optional" ? a.service : a;
248
- if (T(e, h), h.kind === "extensionPoint") continue;
332
+ if (q(e, h), h.kind === "extensionPoint") continue;
249
333
  const c = t.get(h.id);
250
334
  if (!c) {
251
335
  if (a.kind === "optional") continue;
@@ -266,7 +350,7 @@ function ft(r, t, e) {
266
350
  }
267
351
  return { resolvedProviders: i, dependents: s, indegree: n };
268
352
  }
269
- function pt(r, t, e) {
353
+ function Mt(r, t, e) {
270
354
  let i = r.filter((o) => e.get(o) === 0);
271
355
  const s = [], n = [];
272
356
  for (; i.length; ) {
@@ -280,7 +364,7 @@ function pt(r, t, e) {
280
364
  }
281
365
  }
282
366
  if (s.length !== r.length) {
283
- const o = gt(r, t);
367
+ const o = Dt(r, t);
284
368
  throw new u(
285
369
  "SERVICE_CYCLE",
286
370
  `Installation dependency cycle: ${o.map((a) => a.id).join(" -> ")}`
@@ -288,7 +372,7 @@ function pt(r, t, e) {
288
372
  }
289
373
  return { order: s, layers: n };
290
374
  }
291
- function gt(r, t) {
375
+ function Dt(r, t) {
292
376
  const e = /* @__PURE__ */ new Set(), i = /* @__PURE__ */ new Set(), s = [], n = (o) => {
293
377
  i.add(o), s.push(o);
294
378
  for (const a of t.get(o) ?? []) {
@@ -309,7 +393,7 @@ function gt(r, t) {
309
393
  }
310
394
  throw new Error("Dependency graph is cyclic but no cycle path was found");
311
395
  }
312
- class S {
396
+ class z {
313
397
  #t;
314
398
  constructor(t = []) {
315
399
  if (!t || typeof t != "object" && typeof t != "function" || typeof t[Symbol.iterator] != "function")
@@ -341,22 +425,65 @@ class S {
341
425
  return this.entries();
342
426
  }
343
427
  }
344
- function wt(r) {
428
+ function Vt(r) {
345
429
  if (typeof r != "function")
346
430
  throw new Error("Unsupported JavaScript runtime: Promise.withResolvers is required");
347
431
  }
348
- function tt(r, t) {
432
+ function mt(r, t) {
349
433
  return r ?? Symbol.for(t);
350
434
  }
351
- wt(Promise.withResolvers);
352
- const g = tt(
435
+ Vt(Promise.withResolvers);
436
+ const v = mt(
353
437
  Symbol.dispose,
354
438
  "Symbol.dispose"
355
- ), j = tt(
439
+ ), F = mt(
356
440
  Symbol.asyncDispose,
357
441
  "Symbol.asyncDispose"
358
- ), m = /* @__PURE__ */ new WeakMap();
359
- class q {
442
+ );
443
+ function R(r, t) {
444
+ if (qt(r))
445
+ throw Promise.resolve(r).catch(() => {
446
+ }), new TypeError(t);
447
+ }
448
+ function qt(r) {
449
+ return r === null || typeof r != "object" && typeof r != "function" ? !1 : typeof r.then == "function";
450
+ }
451
+ const N = /* @__PURE__ */ new WeakMap(), C = /* @__PURE__ */ new Set();
452
+ let T = 0, U = !1;
453
+ function Ft(r) {
454
+ T++;
455
+ const t = [];
456
+ try {
457
+ r();
458
+ } catch (e) {
459
+ t.push(e);
460
+ } finally {
461
+ if (T--, !T)
462
+ try {
463
+ vt();
464
+ } catch (e) {
465
+ t.push(e);
466
+ }
467
+ }
468
+ if (t.length === 1) throw t[0];
469
+ if (t.length > 1) throw new AggregateError(t, "Snapshot batch failed");
470
+ }
471
+ function vt() {
472
+ if (U) return;
473
+ U = !0;
474
+ const r = [];
475
+ try {
476
+ for (; C.size; ) {
477
+ const t = C.values().next().value;
478
+ C.delete(t), Gt(t, r);
479
+ }
480
+ } finally {
481
+ U = !1, C.clear();
482
+ }
483
+ if (r.length)
484
+ throw new AggregateError(r, "Snapshot error reporting failed");
485
+ }
486
+ class Z {
360
487
  #t = /* @__PURE__ */ new Set();
361
488
  #e;
362
489
  #i;
@@ -369,24 +496,12 @@ class q {
369
496
  }
370
497
  view = Object.freeze({
371
498
  get: () => this.#n(),
372
- subscribe: (t) => this.#o(t)
499
+ subscribe: (t) => this.#a(t)
373
500
  });
374
501
  invalidate() {
375
- const { report: t } = this.#a();
376
- this.#s = !0;
377
- const e = [];
378
- for (const i of [...this.#t])
379
- try {
380
- bt(i);
381
- } catch (s) {
382
- try {
383
- t(s);
384
- } catch (n) {
385
- e.push(s, n);
386
- }
387
- }
388
- if (e.length)
389
- throw new AggregateError(e, "Snapshot error reporting failed");
502
+ this.#o(), this.#s = !0;
503
+ for (const t of this.#t) C.add(t);
504
+ T || vt();
390
505
  }
391
506
  dispose() {
392
507
  const t = this.#e;
@@ -398,53 +513,65 @@ class q {
398
513
  this.#s = !1;
399
514
  const e = [...this.#t];
400
515
  this.#t.clear();
401
- for (const i of e) mt(i);
516
+ for (const i of e) yt(i);
402
517
  }
403
518
  }
404
519
  }
405
- [g]() {
520
+ [v]() {
406
521
  this.dispose();
407
522
  }
408
523
  #n() {
409
524
  const t = this.#e;
410
525
  return t.phase === "active" && this.#r(t.read), this.#i;
411
526
  }
412
- #o(t) {
527
+ #a(t) {
413
528
  if (typeof t != "function") throw new TypeError("Subscriber must be a function");
414
- this.#a();
415
- const e = new vt(t, (i) => {
416
- this.#t.delete(i);
529
+ const { report: e } = this.#o(), i = new Bt(t, e, (s) => {
530
+ this.#t.delete(s);
417
531
  });
418
- return this.#t.add(e), e;
532
+ return this.#t.add(i), i;
419
533
  }
420
534
  #r(t) {
421
535
  this.#s && (this.#i = t(), this.#s = !1);
422
536
  }
423
- #a() {
537
+ #o() {
424
538
  const t = this.#e;
425
539
  if (t.phase === "disposed") throw new TypeError("Snapshot publisher is disposed");
426
540
  return t;
427
541
  }
428
542
  }
429
- class vt {
430
- constructor(t, e) {
431
- m.set(this, { listener: t, detach: e }), Object.freeze(this);
543
+ class Bt {
544
+ constructor(t, e, i) {
545
+ N.set(this, { listener: t, report: e, detach: i }), Object.freeze(this);
432
546
  }
433
547
  dispose() {
434
- const t = m.get(this);
435
- t && (m.delete(this), t.detach(this));
548
+ const t = N.get(this);
549
+ t && (yt(this), t.detach(this));
436
550
  }
437
- [g]() {
551
+ [v]() {
438
552
  this.dispose();
439
553
  }
440
554
  }
441
- function bt(r) {
442
- m.get(r)?.listener();
555
+ function Gt(r, t) {
556
+ const e = N.get(r);
557
+ if (e)
558
+ try {
559
+ R(e.listener(), "Snapshot subscribers must be synchronous");
560
+ } catch (i) {
561
+ try {
562
+ R(
563
+ e.report(i),
564
+ "Snapshot error reporters must be synchronous"
565
+ );
566
+ } catch (s) {
567
+ t.push(i, s);
568
+ }
569
+ }
443
570
  }
444
- function mt(r) {
445
- m.delete(r);
571
+ function yt(r) {
572
+ C.delete(r), N.delete(r);
446
573
  }
447
- class yt {
574
+ class Ut {
448
575
  #t;
449
576
  get;
450
577
  subscribe;
@@ -466,7 +593,7 @@ class yt {
466
593
  return t;
467
594
  }
468
595
  }
469
- class Et {
596
+ class _t {
470
597
  #t;
471
598
  constructor(t) {
472
599
  this.#t = t, Object.freeze(this);
@@ -477,16 +604,16 @@ class Et {
477
604
  dispose() {
478
605
  this.#t.dispose();
479
606
  }
480
- [g]() {
607
+ [v]() {
481
608
  this.dispose();
482
609
  }
483
610
  }
484
- class Ct {
611
+ class Ht {
485
612
  #t;
486
613
  #e;
487
614
  handle;
488
615
  constructor(t, e, i, s) {
489
- this.#e = e, this.#t = { phase: "staged", store: t, value: i, detachFromOwner: s }, this.handle = new Et(this);
616
+ this.#e = e, this.#t = { phase: "staged", store: t, value: i, detachFromOwner: s }, this.handle = new _t(this);
490
617
  }
491
618
  publish() {
492
619
  const t = this.#t;
@@ -515,55 +642,62 @@ class Ct {
515
642
  }
516
643
  }
517
644
  }
518
- [g]() {
645
+ [v]() {
519
646
  this.dispose();
520
647
  }
521
648
  }
522
- class Ot {
649
+ class Kt {
523
650
  #t;
524
651
  #e;
525
652
  #i = /* @__PURE__ */ new Map();
526
653
  #s = /* @__PURE__ */ new Map();
527
654
  #n;
528
- #o = new S();
655
+ #a = new z();
529
656
  #r = 0;
530
- #a = 0;
531
- #d = !1;
532
- #f = !1;
657
+ #o = 0;
658
+ #h = !1;
659
+ #p = !1;
533
660
  constructor(t, e, i) {
534
- this.#t = t, this.#e = i, this.#n = new q(() => this.#o, e);
661
+ this.#t = t, this.#e = i, this.#n = new Z(() => this.#a, e);
535
662
  }
536
663
  stage(t, e, i, s) {
537
- $t(e);
538
- const n = Tt(t, e);
664
+ Jt(e);
665
+ const n = Xt(t, e);
539
666
  if (this.#i.has(n))
540
667
  throw new TypeError(`Duplicate contribution '${n}'`);
541
- const o = new Ct(this, n, i, s);
668
+ const o = new Ht(this, n, i, s);
542
669
  return this.#i.set(n, o), o;
543
670
  }
544
671
  view(t) {
545
672
  this.#r++;
546
673
  const e = { binding: { store: this, own: t } };
547
674
  let i;
548
- const s = new St(e, () => {
675
+ const s = new Wt(e, () => {
549
676
  const n = i;
550
677
  i = void 0;
551
678
  try {
552
679
  n?.();
553
680
  } finally {
554
- this.#r--, this.#h();
681
+ this.#r--, this.#c();
555
682
  }
556
683
  });
557
684
  try {
558
685
  i = t(s, "view");
559
686
  } catch (n) {
560
- throw e.binding = void 0, this.#r--, this.#h(), n;
687
+ throw e.binding = void 0, this.#r--, this.#c(), n;
561
688
  }
562
- return new yt(e);
563
- }
564
- /** A stable view explicitly owned by the Host for graph-external consumers. */
689
+ return new Ut(e);
690
+ }
691
+ /**
692
+ * A stable view explicitly owned by the Host for graph-external consumers.
693
+ *
694
+ * Application code has no Lifetime to lease a view from, so this one is
695
+ * retained by the Host instead — which also pins the store open for as long as
696
+ * the Host lives. That is the trade: `host.contributions()` gives a handle that
697
+ * never goes stale, at the cost of a store that is no longer self-releasing.
698
+ */
565
699
  hostView() {
566
- return this.#d = !0, this.#n.view;
700
+ return this.#h = !0, this.#n.view;
567
701
  }
568
702
  snapshot() {
569
703
  return this.#n.view.get();
@@ -572,15 +706,15 @@ class Ot {
572
706
  if (typeof t != "function")
573
707
  throw new TypeError("Contribution subscriber must be a function");
574
708
  const i = this.#n.view.subscribe(t);
575
- this.#a++;
709
+ this.#o++;
576
710
  let s;
577
- const n = new It(i, () => {
711
+ const n = new Yt(i, () => {
578
712
  const o = s;
579
713
  s = void 0;
580
714
  try {
581
715
  o?.();
582
716
  } finally {
583
- this.#a--, this.#h();
717
+ this.#o--, this.#c();
584
718
  }
585
719
  });
586
720
  try {
@@ -591,39 +725,50 @@ class Ot {
591
725
  return n;
592
726
  }
593
727
  insert(t, e, i) {
594
- if (this.#l(t, e), this.#s.has(t))
728
+ if (this.#u(t, e), this.#s.has(t))
595
729
  throw new Error(`Contribution '${t}' is already published`);
596
730
  e.commitPublication(), this.#s.set(t, { contribution: e, value: i }), this.#t(this);
597
731
  }
598
732
  update(t, e, i) {
599
- const s = this.#u(t, e);
733
+ const s = this.#f(t, e);
600
734
  s.value = i, this.#t(this);
601
735
  }
602
736
  removeContribution(t, e, i) {
603
- this.#l(t, e), i === "published" && this.#u(t, e);
737
+ this.#u(t, e), i === "published" && this.#f(t, e);
604
738
  try {
605
739
  this.#i.delete(t), i === "published" && (this.#s.delete(t), this.#t(this));
606
740
  } finally {
607
- this.#h();
741
+ this.#c();
608
742
  }
609
743
  }
744
+ /**
745
+ * Rebuilds the published snapshot, and does nothing when the result would be
746
+ * equal by identity per key. A change that adds and removes the same entry —
747
+ * an Instance restarting with an identical contribution — therefore notifies
748
+ * nobody, instead of waking every observer to hand them what they already had.
749
+ */
610
750
  publishSnapshot() {
611
751
  const t = [...this.#s].map(([i, s]) => [i, s.value]);
612
- t.length === this.#o.size && t.every(([i, s]) => Object.is(this.#o.get(i), s)) || (this.#o = new S(t), this.#n.invalidate());
752
+ t.length === this.#a.size && t.every(
753
+ ([i, s]) => this.#a.has(i) && Object.is(this.#a.get(i), s)
754
+ ) || (this.#a = new z(t), this.#n.invalidate());
613
755
  }
614
- #l(t, e) {
756
+ #u(t, e) {
615
757
  if (this.#i.get(t) !== e)
616
758
  throw new Error(`Contribution '${t}' is not the current claim`);
617
759
  }
618
- #u(t, e) {
760
+ #f(t, e) {
619
761
  const i = this.#s.get(t);
620
762
  if (i?.contribution !== e)
621
763
  throw new Error(`Contribution '${t}' is not the published entry`);
622
764
  return i;
623
765
  }
624
- #h() {
625
- if (!(this.#f || this.#d || this.#i.size || this.#s.size || this.#a || this.#r)) {
626
- this.#f = !0;
766
+ // The self-release check. Every counter has to be at zero and the Host must
767
+ // not be holding a view; `#released` makes it run once, since disposing the
768
+ // publisher can re-enter through a subscription's own teardown.
769
+ #c() {
770
+ if (!(this.#p || this.#h || this.#i.size || this.#s.size || this.#o || this.#r)) {
771
+ this.#p = !0;
627
772
  try {
628
773
  this.#n.dispose();
629
774
  } finally {
@@ -632,7 +777,7 @@ class Ot {
632
777
  }
633
778
  }
634
779
  }
635
- class St {
780
+ class Wt {
636
781
  #t;
637
782
  constructor(t, e) {
638
783
  this.#t = { state: t, release: e }, Object.freeze(this);
@@ -641,11 +786,11 @@ class St {
641
786
  const t = this.#t;
642
787
  t && (this.#t = void 0, t.state.binding = void 0, t.release());
643
788
  }
644
- [g]() {
789
+ [v]() {
645
790
  this.dispose();
646
791
  }
647
792
  }
648
- class It {
793
+ class Yt {
649
794
  #t;
650
795
  constructor(t, e) {
651
796
  this.#t = { subscription: t, release: e }, Object.freeze(this);
@@ -661,11 +806,11 @@ class It {
661
806
  }
662
807
  }
663
808
  }
664
- [g]() {
809
+ [v]() {
665
810
  this.dispose();
666
811
  }
667
812
  }
668
- class At {
813
+ class Qt {
669
814
  #t = /* @__PURE__ */ new Map();
670
815
  #e = /* @__PURE__ */ new Set();
671
816
  #i;
@@ -676,7 +821,7 @@ class At {
676
821
  get(t) {
677
822
  const e = this.#t.get(t.id);
678
823
  if (e) return e;
679
- const i = new Ot(
824
+ const i = new Kt(
680
825
  (s) => this.#n(s),
681
826
  this.#i,
682
827
  (s) => {
@@ -691,39 +836,44 @@ class At {
691
836
  beginBatch() {
692
837
  this.#s++;
693
838
  }
839
+ // Depth-counted, so nested Host operations collapse into the outermost batch
840
+ // and publication happens exactly once. Every store's snapshot is attempted
841
+ // even if one of them throws, then the failures are aggregated — a single bad
842
+ // subscriber must not leave the remaining stores unpublished.
694
843
  endBatch() {
695
844
  if (!this.#s) throw new Error("Contribution batch is not active");
696
845
  if (this.#s--, this.#s) return;
697
846
  const t = [...this.#e];
698
- this.#e.clear();
699
- const e = [];
700
- for (const i of t)
701
- try {
702
- i.publishSnapshot();
703
- } catch (s) {
704
- e.push(s);
705
- }
706
- if (e.length === 1) throw e[0];
707
- if (e.length > 1)
708
- throw new AggregateError(e, "Contribution batch publication failed");
847
+ this.#e.clear(), Ft(() => {
848
+ const e = [];
849
+ for (const i of t)
850
+ try {
851
+ i.publishSnapshot();
852
+ } catch (s) {
853
+ e.push(s);
854
+ }
855
+ if (e.length === 1) throw e[0];
856
+ if (e.length > 1)
857
+ throw new AggregateError(e, "Contribution batch publication failed");
858
+ });
709
859
  }
710
860
  #n(t) {
711
861
  this.#e.add(t), !this.#s && (this.#e.delete(t), t.publishSnapshot());
712
862
  }
713
863
  }
714
- function $t(r) {
864
+ function Jt(r) {
715
865
  if (typeof r != "string" || !r.trim())
716
866
  throw new TypeError("Contribution key must be a non-empty string");
717
867
  if (r !== r.trim())
718
868
  throw new TypeError("Contribution key cannot start or end with whitespace");
719
869
  }
720
- function Tt(r, t) {
721
- return `${B(r)}/${B(t)}`;
870
+ function Xt(r, t) {
871
+ return `${ot(r)}/${ot(t)}`;
722
872
  }
723
- function B(r) {
873
+ function ot(r) {
724
874
  return r.replaceAll("%", "%25").replaceAll("/", "%2F");
725
875
  }
726
- class jt {
876
+ class Zt {
727
877
  #t;
728
878
  constructor(t) {
729
879
  this.#t = t, Object.freeze(this);
@@ -732,16 +882,16 @@ class jt {
732
882
  const t = this.#t;
733
883
  this.#t = void 0, t?.dispose();
734
884
  }
735
- [g]() {
885
+ [v]() {
736
886
  this.dispose();
737
887
  }
738
888
  }
739
- class kt {
889
+ class te {
740
890
  #t;
741
891
  #e;
742
892
  handle;
743
893
  constructor(t, e, i, s) {
744
- this.#e = e, this.#t = { phase: "staged", hub: t, slot: { listener: i }, release: s }, this.handle = new jt(this);
894
+ this.#e = e, this.#t = { phase: "staged", hub: t, slot: { listener: i }, release: s }, this.handle = new Zt(this);
745
895
  }
746
896
  publish() {
747
897
  const t = this.#t;
@@ -750,7 +900,7 @@ class kt {
750
900
  dispose() {
751
901
  const t = this.#t;
752
902
  if (t.phase !== "removed") {
753
- this.#t = { phase: "removed" };
903
+ this.#t = { phase: "removed" }, t.slot.listener = void 0;
754
904
  try {
755
905
  t.phase === "published" && t.hub.delete(this.#e, t.slot);
756
906
  } finally {
@@ -758,15 +908,15 @@ class kt {
758
908
  }
759
909
  }
760
910
  }
761
- [g]() {
911
+ [v]() {
762
912
  this.dispose();
763
913
  }
764
914
  }
765
- class Pt {
915
+ class ee {
766
916
  #t = /* @__PURE__ */ new Map();
767
917
  stage(t, e, i) {
768
918
  if (typeof e != "function") throw new TypeError("Event listener must be a function");
769
- return new kt(this, t, e, i);
919
+ return new te(this, t, e, i);
770
920
  }
771
921
  add(t, e) {
772
922
  const i = this.#t.get(t) ?? /* @__PURE__ */ new Set();
@@ -776,25 +926,34 @@ class Pt {
776
926
  const i = this.#t.get(t);
777
927
  i && (i.delete(e), i.size || this.#t.delete(t));
778
928
  }
929
+ /**
930
+ * Every still-subscribed listener runs, then failures are aggregated. Three details, all
931
+ * deliberate:
932
+ *
933
+ * - the listener set is copied first; a new subscription does not join this
934
+ * emission. A disposed slot is skipped if its callback has not started;
935
+ * - each call is wrapped in a resolved promise, so a listener that throws
936
+ * synchronously is collected like one that rejects;
937
+ * - `allSettled`, so one broken listener cannot stop the others from being
938
+ * told. The emitter still learns about it — every error comes back.
939
+ */
779
940
  async emit(t, e) {
780
- const i = [...this.#t.get(t) ?? []].map(
781
- (o) => o.listener
782
- ), n = (await Promise.allSettled(
783
- i.map((o) => Promise.resolve().then(() => o(e)))
941
+ const i = [...this.#t.get(t) ?? []], n = (await Promise.allSettled(
942
+ i.map((o) => Promise.resolve().then(() => o.listener?.(e)))
784
943
  )).filter((o) => o.status === "rejected").map((o) => o.reason);
785
944
  if (n.length)
786
945
  throw new AggregateError(n, `Event '${t}' listeners failed`);
787
946
  }
788
947
  }
789
- class Lt {
948
+ class ie {
790
949
  #t;
791
950
  root;
792
951
  view;
793
952
  constructor(t, e) {
794
- this.root = F(t), this.#t = new q(() => et(this.root), e), this.view = this.#t.view;
953
+ this.root = at(t), this.#t = new Z(() => Et(this.root), e), this.view = this.#t.view;
795
954
  }
796
955
  createNode(t) {
797
- return F(t);
956
+ return at(t);
798
957
  }
799
958
  attach(t, e) {
800
959
  if (t.children.has(e)) throw new Error("Lifetime diagnostic node is attached");
@@ -815,7 +974,7 @@ class Lt {
815
974
  this.root.phase !== "disposed" && (this.root.phase = "disposed", this.#t.invalidate(), this.#t.dispose());
816
975
  }
817
976
  }
818
- function F(r) {
977
+ function at(r) {
819
978
  return {
820
979
  label: r,
821
980
  phase: "active",
@@ -830,8 +989,8 @@ function F(r) {
830
989
  children: /* @__PURE__ */ new Set()
831
990
  };
832
991
  }
833
- function et(r) {
834
- const t = Object.freeze([...r.children].map(et));
992
+ function Et(r) {
993
+ const t = Object.freeze([...r.children].map(Et));
835
994
  return Object.freeze({
836
995
  label: r.label,
837
996
  phase: r.phase,
@@ -839,15 +998,19 @@ function et(r) {
839
998
  children: t
840
999
  });
841
1000
  }
842
- function zt(r) {
843
- if (!r || typeof r != "object") return !1;
844
- const t = r;
845
- return [t.debug, t.info, t.warn, t.error].every(
846
- (e) => typeof e == "function"
847
- );
1001
+ function se(r) {
1002
+ if (!r || typeof r != "object" && typeof r != "function") return !1;
1003
+ try {
1004
+ const t = r;
1005
+ return [t.debug, t.info, t.warn, t.error].every(
1006
+ (e) => typeof e == "function"
1007
+ );
1008
+ } catch {
1009
+ return !1;
1010
+ }
848
1011
  }
849
- const R = Object.freeze(new DOMException("Resource disposed", "AbortError"));
850
- class w {
1012
+ const W = Object.freeze(new DOMException("Resource disposed", "AbortError"));
1013
+ class y {
851
1014
  #t = /* @__PURE__ */ new Set();
852
1015
  #e;
853
1016
  constructor(t) {
@@ -867,6 +1030,12 @@ class w {
867
1030
  const e = this.#e;
868
1031
  return e && e.diagnostics.change(e.node, e.kind, -1), !0;
869
1032
  };
1033
+ /**
1034
+ * Disposes in reverse creation order, collecting failures rather than
1035
+ * throwing. Later resources may depend on earlier ones, and a resource that
1036
+ * refuses to shut down cleanly must not strand its siblings. The caller
1037
+ * aggregates the collected errors.
1038
+ */
870
1039
  async dispose(t) {
871
1040
  try {
872
1041
  for (const e of [...this.#t].reverse())
@@ -881,11 +1050,26 @@ class w {
881
1050
  this.#e = void 0;
882
1051
  }
883
1052
  }
1053
+ /** Capability withdrawal must finish without yielding to callbacks or tasks. */
1054
+ disposeSynchronously(t) {
1055
+ try {
1056
+ for (const e of [...this.#t].reverse())
1057
+ try {
1058
+ e.dispose();
1059
+ } catch (i) {
1060
+ t.push(i);
1061
+ } finally {
1062
+ this.release(e);
1063
+ }
1064
+ } finally {
1065
+ this.#e = void 0;
1066
+ }
1067
+ }
884
1068
  [Symbol.iterator]() {
885
1069
  return this.#t[Symbol.iterator]();
886
1070
  }
887
1071
  }
888
- class Rt {
1072
+ class re {
889
1073
  #t;
890
1074
  constructor(t, e) {
891
1075
  this.#t = { phase: "active", cleanup: t, detachFromParent: e }, Object.freeze(this);
@@ -905,11 +1089,11 @@ class Rt {
905
1089
  });
906
1090
  return this.#t = { phase: "disposing", completion: e }, e;
907
1091
  }
908
- [j]() {
1092
+ [F]() {
909
1093
  return this.dispose();
910
1094
  }
911
1095
  }
912
- class Nt {
1096
+ class ne {
913
1097
  #t;
914
1098
  #e;
915
1099
  #i;
@@ -922,7 +1106,7 @@ class Nt {
922
1106
  () => this.#s(),
923
1107
  (a) => {
924
1108
  try {
925
- J(n.signal, a) || i(a);
1109
+ wt(n.signal, a) || i(a);
926
1110
  } finally {
927
1111
  this.#s();
928
1112
  }
@@ -940,9 +1124,9 @@ class Nt {
940
1124
  () => {
941
1125
  }
942
1126
  );
943
- return this.#i = { phase: "disposing", completion: e }, t.controller.abort(R), this.#n(), e;
1127
+ return this.#i = { phase: "disposing", completion: e }, t.controller.abort(W), this.#n(), e;
944
1128
  }
945
- [j]() {
1129
+ [F]() {
946
1130
  return this.dispose();
947
1131
  }
948
1132
  #s() {
@@ -956,54 +1140,54 @@ class Nt {
956
1140
  this.#t = void 0, t?.();
957
1141
  }
958
1142
  }
959
- class D {
1143
+ class tt {
960
1144
  #t;
961
1145
  #e;
962
1146
  #i;
963
1147
  #s;
964
1148
  #n;
965
- #o;
966
- #r;
967
1149
  #a;
968
- #d;
969
- #f;
970
- #l;
1150
+ #r;
1151
+ #o;
1152
+ #h;
1153
+ #p;
971
1154
  #u;
1155
+ #f;
972
1156
  handle;
973
- #h;
1157
+ #c;
974
1158
  constructor(t, e, i = {}) {
975
- this.#e = e, this.handle = new xt(this);
1159
+ this.#e = e, this.handle = new oe(this);
976
1160
  const s = new AbortController();
977
- this.#h = {
1161
+ this.#c = {
978
1162
  phase: "active",
979
1163
  controller: s,
980
1164
  declarations: i.declarations ?? "staged"
981
1165
  };
982
1166
  const n = i.parent;
983
- this.#u = n?.detach, this.#f = n ? "child" : "root";
984
- const o = n?.diagnostics ?? new Lt(e, (l) => t.report(l)), a = n?.diagnosticNode ?? o.root;
1167
+ this.#f = n?.detach, this.#p = n ? "child" : "root";
1168
+ const o = n?.diagnostics ?? new ie(e, (l) => t.report(l)), a = n?.diagnosticNode ?? o.root;
985
1169
  this.#t = { port: t, diagnostics: o, diagnosticNode: a };
986
1170
  const h = (l) => ({
987
1171
  diagnostics: o,
988
1172
  node: a,
989
1173
  kind: l
990
1174
  });
991
- this.#i = new w(h("listeners")), this.#s = new w(h("contributions")), this.#n = new w(h("contributionViews")), this.#o = new w(h("subscriptions")), this.#r = new w(h("tasks")), this.#a = new w(), this.#d = new w(h("cleanups"));
1175
+ this.#i = new y(h("listeners")), this.#s = new y(h("contributions")), this.#n = new y(h("contributionViews")), this.#a = new y(h("subscriptions")), this.#r = new y(h("tasks")), this.#o = new y(), this.#h = new y(h("cleanups"));
992
1176
  const c = i.parentSignal;
993
1177
  if (!c) return;
994
1178
  const d = () => s.abort(c.reason);
995
- c.addEventListener("abort", d, { once: !0 }), this.#l = () => c.removeEventListener("abort", d), c.aborted && d();
1179
+ c.addEventListener("abort", d, { once: !0 }), this.#u = () => c.removeEventListener("abort", d), c.aborted && d();
996
1180
  }
997
1181
  get signal() {
998
- const t = this.#h;
1182
+ const t = this.#c;
999
1183
  return t.phase === "disposed" ? t.signal : t.controller.signal;
1000
1184
  }
1001
1185
  get diagnostics() {
1002
- return this.#c().diagnostics.view;
1186
+ return this.#g().diagnostics.view;
1003
1187
  }
1004
- /** A facade whose only Runtime edge disappears when this Lifetime terminates. */
1188
+ /** A facade whose only InstanceCoordinator edge disappears when this Lifetime terminates. */
1005
1189
  contextLogger(t) {
1006
- const e = (i) => (s, ...n) => this.#p().port.writeLog(i, s, t, n);
1190
+ const e = (i) => (s, ...n) => this.#g().port.writeLog(i, s, t, n);
1007
1191
  return Object.freeze({
1008
1192
  debug: e("debug"),
1009
1193
  info: e("info"),
@@ -1012,30 +1196,39 @@ class D {
1012
1196
  });
1013
1197
  }
1014
1198
  cleanup(t) {
1015
- if (this.#c(), typeof t != "function") throw new TypeError("Cleanup must be a function");
1016
- const e = new Rt(t, this.#d.release);
1017
- return this.#d.add(e), e;
1018
- }
1199
+ if (this.#d(), typeof t != "function") throw new TypeError("Cleanup must be a function");
1200
+ const e = new re(t, this.#h.release);
1201
+ return this.#h.add(e), e;
1202
+ }
1203
+ /**
1204
+ * A child scope. It inherits this Lifetime's signal, so cancellation flows
1205
+ * down, and its declaration phase, so a child created during `setup()` also
1206
+ * stages rather than publishing immediately.
1207
+ *
1208
+ * Disposing a child detaches it from both the resource set and the diagnostics
1209
+ * tree. A Plugin can therefore use child lifetimes as a unit of work — one per
1210
+ * open document, per connection — without the parent growing.
1211
+ */
1019
1212
  lifetime(t) {
1020
- const { port: e, diagnostics: i, diagnosticNode: s } = this.#c();
1021
- Vt(t);
1022
- const n = i.createNode(t), o = new D(e, this.#e, {
1213
+ const { port: e, diagnostics: i, diagnosticNode: s } = this.#d();
1214
+ ae(t);
1215
+ const n = i.createNode(t), o = new tt(e, this.#e, {
1023
1216
  parentSignal: this.signal,
1024
- declarations: this.#g(),
1217
+ declarations: this.#w(),
1025
1218
  parent: {
1026
1219
  diagnostics: i,
1027
1220
  diagnosticNode: n,
1028
1221
  detach: (a) => {
1029
- this.#a.release(a) && i.detach(s, n);
1222
+ this.#o.release(a) && i.detach(s, n);
1030
1223
  }
1031
1224
  }
1032
1225
  });
1033
- return this.#a.add(o), i.attach(s, n), o.handle;
1226
+ return this.#o.add(o), i.attach(s, n), o.handle;
1034
1227
  }
1035
1228
  spawn(t) {
1036
- const { port: e } = this.#c();
1229
+ const { port: e } = this.#d();
1037
1230
  if (typeof t != "function") throw new TypeError("Background task must be a function");
1038
- const i = new Nt(
1231
+ const i = new ne(
1039
1232
  this.signal,
1040
1233
  t,
1041
1234
  (s) => e.report(s),
@@ -1044,84 +1237,137 @@ class D {
1044
1237
  return this.#r.add(i), i;
1045
1238
  }
1046
1239
  on(t, e) {
1047
- const { port: i } = this.#c(), s = i.stageOn(this.#e, t, e, this.#i.release);
1048
- return this.#i.add(s), this.#g() === "published" && s.publish(), s.handle;
1240
+ const { port: i } = this.#d(), s = i.stageOn(
1241
+ this.#e,
1242
+ t,
1243
+ e,
1244
+ this.#i.release
1245
+ );
1246
+ return this.#i.add(s), this.#w() === "published" && s.publish(), s.handle;
1049
1247
  }
1050
1248
  async emit(t, ...e) {
1051
- const { port: i } = this.#c();
1249
+ const { port: i } = this.#d();
1052
1250
  await i.emit(this.#e, t, e[0]);
1053
1251
  }
1054
1252
  contribute(t, e, i) {
1055
- const { port: s } = this.#c(), n = s.stageContribution(
1253
+ const { port: s } = this.#d(), n = s.stageContribution(
1056
1254
  this.#e,
1057
1255
  t,
1058
1256
  e,
1059
1257
  i,
1060
1258
  this.#s.release
1061
1259
  );
1062
- return this.#s.add(n), this.#g() === "published" && n.publish(), n.handle;
1260
+ return this.#s.add(n), this.#w() === "published" && n.publish(), n.handle;
1063
1261
  }
1064
1262
  /** Owns an internal live capability without exposing a second Context API. */
1065
1263
  ownLease(t, e) {
1066
- return this.#c(), (e === "view" ? this.#n : this.#o).own(t);
1264
+ return this.#d(), (e === "view" ? this.#n : this.#a).own(t);
1067
1265
  }
1068
1266
  /** Releases a temporary startup cancellation edge after its layer commits. */
1069
1267
  detachStartupSignal() {
1070
- const t = this.#l;
1071
- this.#l = void 0, t?.();
1072
- }
1073
- /** Atomically makes all declarations staged during setup visible. */
1268
+ const t = this.#u;
1269
+ this.#u = void 0, t?.();
1270
+ }
1271
+ /**
1272
+ * Atomically makes all declarations staged during setup visible.
1273
+ *
1274
+ * Children publish too, recursively, so a Lifetime created inside `setup()`
1275
+ * becomes visible with its parent rather than being stranded staged. Called by
1276
+ * the InstanceCoordinator once the whole activation layer has succeeded —
1277
+ * which is what makes "no half-built Instance is observable" true.
1278
+ */
1074
1279
  publish() {
1075
- const t = this.#h;
1076
- if (t.phase !== "active") throw y();
1077
- if (this.#c(), t.declarations !== "published") {
1280
+ const t = this.#c;
1281
+ if (t.phase !== "active") throw $();
1282
+ if (this.#d(), t.declarations !== "published") {
1078
1283
  for (const e of this.#i) e.publish();
1079
1284
  for (const e of this.#s) e.publish();
1080
- for (const e of this.#a) e.publish();
1081
- this.#h = { ...t, declarations: "published" };
1285
+ for (const e of this.#o) e.publish();
1286
+ this.#c = { ...t, declarations: "published" };
1082
1287
  }
1083
1288
  }
1289
+ /**
1290
+ * Disposal is idempotent and joinable: concurrent callers share one completion
1291
+ * promise, so a Lifetime disposed by both its owner and a `using` block runs
1292
+ * its teardown once.
1293
+ *
1294
+ * The three-stage order is the module header's; see it for why. Errors from
1295
+ * every stage accumulate and are aggregated at the end, so one failing cleanup
1296
+ * neither hides the others nor prevents them from running.
1297
+ */
1084
1298
  dispose() {
1085
- const t = this.#h;
1299
+ const t = this.#c;
1086
1300
  if (t.phase === "disposing") return t.completion;
1087
1301
  if (t.phase === "disposed") return Promise.resolve();
1088
- const e = this.#p(), i = Promise.resolve().then(async () => {
1089
- const s = [];
1090
- if (await this.#i.dispose(s), await this.#s.dispose(s), await this.#o.dispose(s), await this.#n.dispose(s), t.controller.abort(R), this.detachStartupSignal(), await this.#r.dispose(s), await this.#a.dispose(s), await this.#d.dispose(s), s.length === 1) throw s[0];
1091
- if (s.length > 1) throw new AggregateError(s, "Lifetime cleanup failed");
1092
- }).finally(() => {
1093
- this.#h = { phase: "disposed", signal: AbortSignal.abort(R) };
1094
- const s = this.#u;
1095
- this.#u = void 0;
1096
- try {
1097
- s?.(this), this.#f === "root" && e.diagnostics.finishRoot();
1098
- } finally {
1099
- this.#t = void 0;
1100
- }
1101
- });
1102
- return this.#h = { phase: "disposing", controller: t.controller, completion: i }, e.diagnostics.beginDisposing(e.diagnosticNode), i;
1302
+ const e = [], i = this.#l(e);
1303
+ for (const { lifetime: s, errors: n } of e)
1304
+ s.#i.disposeSynchronously(n), s.#a.disposeSynchronously(n), s.#n.disposeSynchronously(n);
1305
+ for (const { lifetime: s, errors: n } of e)
1306
+ s.#s.disposeSynchronously(n);
1307
+ for (const { lifetime: s, binding: n, controller: o } of e)
1308
+ n.diagnostics.beginDisposing(n.diagnosticNode), o.abort(W), s.detachStartupSignal();
1309
+ for (const s of e) s.finish();
1310
+ return i;
1103
1311
  }
1104
- [j]() {
1312
+ /** Stages joinable completions without running any externally observable work. */
1313
+ #l(t) {
1314
+ const e = this.#c;
1315
+ if (e.phase === "disposing") return e.completion;
1316
+ if (e.phase === "disposed") return Promise.resolve();
1317
+ const i = this.#g(), s = Promise.withResolvers(), n = [];
1318
+ this.#c = {
1319
+ phase: "disposing",
1320
+ controller: e.controller,
1321
+ completion: s.promise
1322
+ };
1323
+ const o = Promise.allSettled(
1324
+ [...this.#o].reverse().map((h) => h.#l(t))
1325
+ ), a = () => {
1326
+ (async () => {
1327
+ try {
1328
+ await this.#r.dispose(n);
1329
+ for (const h of await o)
1330
+ h.status === "rejected" && n.push(h.reason);
1331
+ if (await this.#h.dispose(n), n.length === 1) throw n[0];
1332
+ if (n.length > 1) throw new AggregateError(n, "Lifetime cleanup failed");
1333
+ } finally {
1334
+ this.#c = { phase: "disposed", signal: AbortSignal.abort(W) };
1335
+ const h = this.#f;
1336
+ this.#f = void 0;
1337
+ try {
1338
+ h?.(this), this.#p === "root" && i.diagnostics.finishRoot();
1339
+ } finally {
1340
+ this.#t = void 0;
1341
+ }
1342
+ }
1343
+ })().then(s.resolve, s.reject);
1344
+ };
1345
+ return t.push({ lifetime: this, errors: n, binding: i, controller: e.controller, finish: a }), s.promise;
1346
+ }
1347
+ [F]() {
1105
1348
  return this.dispose();
1106
1349
  }
1107
- #c() {
1350
+ // The gate on every operation that creates a resource. `signal.aborted` is
1351
+ // checked as well as the phase, because a parent's disposal aborts this signal
1352
+ // during startup cancellation as well as disposal. Both boundaries close work.
1353
+ #d() {
1108
1354
  const t = this.#t;
1109
- if (this.#h.phase !== "active" || this.signal.aborted || !t)
1110
- throw y();
1355
+ if (this.#c.phase !== "active" || this.signal.aborted || !t)
1356
+ throw $();
1111
1357
  return t;
1112
1358
  }
1113
- #p() {
1359
+ #g() {
1114
1360
  const t = this.#t;
1115
- if (!t) throw y();
1361
+ if (!t) throw $();
1116
1362
  return t;
1117
1363
  }
1118
- #g() {
1119
- const t = this.#h;
1120
- if (t.phase !== "active") throw y();
1364
+ #w() {
1365
+ const t = this.#c;
1366
+ if (t.phase !== "active") throw $();
1121
1367
  return t.declarations;
1122
1368
  }
1123
1369
  }
1124
- class xt {
1370
+ class oe {
1125
1371
  #t;
1126
1372
  constructor(t) {
1127
1373
  this.#t = t, Object.freeze(this);
@@ -1150,32 +1396,32 @@ class xt {
1150
1396
  dispose() {
1151
1397
  return this.#t.dispose();
1152
1398
  }
1153
- [j]() {
1399
+ [F]() {
1154
1400
  return this.dispose();
1155
1401
  }
1156
1402
  }
1157
- function Vt(r) {
1403
+ function ae(r) {
1158
1404
  if (typeof r != "string" || !r.trim())
1159
1405
  throw new TypeError("Lifetime label must be a non-empty string");
1160
1406
  if (r !== r.trim())
1161
1407
  throw new TypeError("Lifetime label cannot start or end with whitespace");
1162
1408
  }
1163
- function y() {
1409
+ function $() {
1164
1410
  return new u("LIFETIME_DISPOSED", "Lifetime is disposing or has been disposed");
1165
1411
  }
1166
- class N extends AggregateError {
1412
+ class Y extends AggregateError {
1167
1413
  }
1168
- class Mt {
1414
+ class ce {
1169
1415
  #t;
1170
1416
  #e;
1171
1417
  #i;
1172
1418
  #s;
1173
1419
  #n = /* @__PURE__ */ new Map();
1174
- #o = new Pt();
1420
+ #a = new ee();
1175
1421
  #r;
1176
- #a = [];
1422
+ #o = [];
1177
1423
  constructor(t) {
1178
- this.#t = t.hostName, this.#e = t.logger, this.#i = t.isInstalled, this.#s = t.report, this.#r = new At(t.report);
1424
+ this.#t = t.hostName, this.#e = t.logger, this.#i = t.isInstalled, this.#s = t.report, this.#r = new Qt(t.report);
1179
1425
  }
1180
1426
  readService(t, e) {
1181
1427
  const i = this.#n.get(t);
@@ -1193,16 +1439,16 @@ class Mt {
1193
1439
  return e;
1194
1440
  }
1195
1441
  resetActivationState() {
1196
- this.#n.clear(), this.#a = [];
1442
+ this.#n.clear(), this.#o = [];
1197
1443
  }
1198
1444
  commitActivationOrder(t) {
1199
- this.#a = t.slice();
1445
+ this.#o = t.slice();
1200
1446
  }
1201
1447
  deactivateAll() {
1202
- return this.deactivate(new Set(this.#a));
1448
+ return this.deactivate(new Set(this.#o));
1203
1449
  }
1204
1450
  async activate(t, e, i, s) {
1205
- const n = this.#c(s);
1451
+ const n = this.#l(s);
1206
1452
  for (const o of t.layers) {
1207
1453
  const a = o.filter(
1208
1454
  (f) => e.has(f) && !f.instance
@@ -1211,38 +1457,42 @@ class Mt {
1211
1457
  const h = new AbortController(), c = await Promise.allSettled(
1212
1458
  a.map(async (f) => {
1213
1459
  try {
1214
- const p = i.has(f) ? i.get(f) : await X(
1215
- f.declaration.plugin.config,
1216
- f.declaration.config,
1217
- f.id
1218
- );
1219
- return await this.#d(
1460
+ if (!i.has(f))
1461
+ throw new Error(`Installation '${f.id}' has no prepared config`);
1462
+ const g = i.get(f);
1463
+ return await this.#h(
1220
1464
  t,
1221
1465
  f,
1222
- p,
1466
+ g,
1223
1467
  h.signal,
1224
1468
  n
1225
1469
  );
1226
- } catch (p) {
1227
- throw h.abort(p), p;
1470
+ } catch (g) {
1471
+ throw h.abort(g), g;
1228
1472
  }
1229
1473
  })
1230
- ), d = qt(c, h.signal), l = c.filter(
1474
+ ), d = he(c, h.signal), l = c.filter(
1231
1475
  (f) => f.status === "fulfilled"
1232
1476
  ).map((f) => f.value);
1233
1477
  if (d.length) {
1234
- const f = await this.#l(l), p = d.length === 1 ? d[0] : new AggregateError(d, "Installation startup layer failed");
1235
- throw f.length ? new N(
1236
- [p, ...f],
1478
+ const f = await this.#u(l), g = d.length === 1 ? d[0] : new AggregateError(d, "Installation startup layer failed");
1479
+ throw f.length ? new Y(
1480
+ [g, ...f],
1237
1481
  "Installation startup layer failed and could not be cleanly disposed"
1238
- ) : p;
1482
+ ) : g;
1239
1483
  }
1240
- this.#f(l);
1484
+ this.#p(l);
1241
1485
  }
1242
1486
  }
1487
+ /**
1488
+ * Stops in reverse activation order, sequentially, and collects every failure
1489
+ * instead of stopping at the first. A dependent must be gone before the
1490
+ * Service it consumed, and one Instance that refuses to shut down cleanly must
1491
+ * not leave the rest running — its error is returned, not thrown.
1492
+ */
1243
1493
  async deactivate(t) {
1244
- const e = [], i = this.#a.filter((s) => t.has(s)).reverse();
1245
- this.#a = this.#a.filter(
1494
+ const e = [], i = this.#o.filter((s) => t.has(s)).reverse();
1495
+ this.#o = this.#o.filter(
1246
1496
  (s) => !t.has(s)
1247
1497
  );
1248
1498
  for (const s of i) {
@@ -1260,6 +1510,17 @@ class Mt {
1260
1510
  }
1261
1511
  return e;
1262
1512
  }
1513
+ /**
1514
+ * Runs one operation with contribution publication deferred to its end, so a
1515
+ * change that withdraws and adds contributions is observed once rather than as
1516
+ * an intermediate state.
1517
+ *
1518
+ * Publication happens even when the operation failed, because contributions
1519
+ * withdrawn by Instances that did stop must not stay visible. If both the
1520
+ * operation and the publication fail, both errors are reported — the
1521
+ * publication failure must not hide the reason the change failed, and the
1522
+ * operation failure must not hide a store left inconsistent.
1523
+ */
1263
1524
  async withContributionBatch(t) {
1264
1525
  this.#r.beginBatch();
1265
1526
  let e;
@@ -1283,23 +1544,23 @@ class Mt {
1283
1544
  if (!i.ok) throw i.error;
1284
1545
  return e.value;
1285
1546
  }
1286
- async #d(t, e, i, s, n) {
1547
+ async #h(t, e, i, s, n) {
1287
1548
  e.deactivate();
1288
- const o = e.declaration.plugin, a = new D(n, e.id, { parentSignal: s });
1549
+ const o = t.declarationFor(e).plugin, a = new tt(n, e.id, { parentSignal: s });
1289
1550
  try {
1290
- const h = this.#u(t, e, o, a), c = Object.freeze({
1551
+ const h = this.#f(t, e, o, a), c = Object.freeze({
1291
1552
  hostName: this.#t,
1292
1553
  pluginName: o.name,
1293
1554
  installationId: e.id,
1294
1555
  groupId: e.groupId
1295
- }), d = this.#h(a, c, h), l = await o.setup(d, i), f = /* @__PURE__ */ new Map();
1296
- for (const [p, st] of Object.entries(o.provides ?? {})) {
1297
- if (typeof l != "object" || l === null || !Object.hasOwn(l, p))
1556
+ }), d = this.#c(a, c, h), l = await o.setup(d, i), f = /* @__PURE__ */ new Map();
1557
+ for (const [g, I] of Object.entries(o.provides)) {
1558
+ if (typeof l != "object" || l === null || !Object.hasOwn(l, g))
1298
1559
  throw new u(
1299
1560
  "SERVICE_NOT_RETURNED",
1300
- `Installation '${e.id}' did not return provided Service '${p}'`
1561
+ `Installation '${e.id}' did not return provided Service '${g}'`
1301
1562
  );
1302
- f.set(st.id, l[p]);
1563
+ f.set(I.id, l[g]);
1303
1564
  }
1304
1565
  return Object.freeze({
1305
1566
  installation: e,
@@ -1311,7 +1572,7 @@ class Mt {
1311
1572
  try {
1312
1573
  await a.dispose();
1313
1574
  } catch (d) {
1314
- throw new N(
1575
+ throw new Y(
1315
1576
  [c, d],
1316
1577
  `Installation '${e.id}' failed to start and could not be cleanly disposed`
1317
1578
  );
@@ -1319,13 +1580,13 @@ class Mt {
1319
1580
  throw c;
1320
1581
  }
1321
1582
  }
1322
- #f(t) {
1583
+ #p(t) {
1323
1584
  for (const { installation: e, instance: i, services: s } of t)
1324
- this.#n.set(e, s), e.activate(i), this.#a.push(e);
1585
+ this.#n.set(e, s), e.activate(i), this.#o.push(e);
1325
1586
  for (const { instance: e } of t)
1326
1587
  e.lifetime.publish(), e.lifetime.detachStartupSignal();
1327
1588
  }
1328
- async #l(t) {
1589
+ async #u(t) {
1329
1590
  const e = [];
1330
1591
  for (const i of [...t].reverse())
1331
1592
  try {
@@ -1335,9 +1596,9 @@ class Mt {
1335
1596
  }
1336
1597
  return e;
1337
1598
  }
1338
- #u(t, e, i, s) {
1599
+ #f(t, e, i, s) {
1339
1600
  const n = /* @__PURE__ */ Object.create(null);
1340
- for (const [o, a] of Object.entries(i.requires ?? {}))
1601
+ for (const [o, a] of Object.entries(i.requires))
1341
1602
  if (a.kind === "optional") {
1342
1603
  const h = t.providerFor(e, a.service.id);
1343
1604
  if (!h) {
@@ -1360,10 +1621,10 @@ class Mt {
1360
1621
  );
1361
1622
  n[o] = c.get(a.id);
1362
1623
  } else
1363
- n[o] = this.#b(a, s);
1624
+ n[o] = this.#m(a, s);
1364
1625
  return n;
1365
1626
  }
1366
- #h(t, e, i) {
1627
+ #c(t, e, i) {
1367
1628
  return Object.freeze({
1368
1629
  ...i,
1369
1630
  get signal() {
@@ -1379,53 +1640,58 @@ class Mt {
1379
1640
  contribute: t.contribute.bind(t)
1380
1641
  });
1381
1642
  }
1382
- #c(t) {
1643
+ #l(t) {
1383
1644
  return {
1384
- stageOn: (e, i, s, n) => this.#p(e, i, s, n, t),
1645
+ stageOn: (e, i, s, n) => this.#d(e, i, s, n, t),
1385
1646
  emit: (e, i, s) => this.#g(e, i, s, t),
1386
- stageContribution: (e, i, s, n, o) => this.#v(e, i, s, n, o, t),
1387
- writeLog: (e, i, s, n) => this.#e[e](i, s, ...n),
1647
+ stageContribution: (e, i, s, n, o) => this.#w(e, i, s, n, o, t),
1648
+ writeLog: (e, i, s, n) => {
1649
+ R(
1650
+ this.#e[e](i, s, ...n),
1651
+ "Logger methods must be synchronous"
1652
+ );
1653
+ },
1388
1654
  report: this.#s
1389
1655
  };
1390
1656
  }
1391
- #p(t, e, i, s, n) {
1392
- return this.#w(t), b(e, "event"), n.remember(e), this.#o.stage(e.id, i, s);
1657
+ #d(t, e, i, s, n) {
1658
+ return this.#b(t), A(e, "event"), n.remember(e), this.#a.stage(e.id, i, s);
1393
1659
  }
1394
1660
  #g(t, e, i, s) {
1395
- return this.#w(t), b(e, "event"), s.remember(e), this.#o.emit(e.id, i);
1661
+ return this.#b(t), A(e, "event"), s.remember(e), this.#a.emit(e.id, i);
1396
1662
  }
1397
- #v(t, e, i, s, n, o) {
1398
- return this.#w(t), b(e, "extensionPoint"), o.remember(e), this.#r.get(e).stage(t, i, s, n);
1663
+ #w(t, e, i, s, n, o) {
1664
+ return this.#b(t), A(e, "extensionPoint"), o.remember(e), this.#r.get(e).stage(t, i, s, n);
1399
1665
  }
1400
- #b(t, e) {
1666
+ #m(t, e) {
1401
1667
  return this.#r.get(t).view((i, s) => e.ownLease(i, s));
1402
1668
  }
1403
- #w(t) {
1669
+ #b(t) {
1404
1670
  if (!this.#i(t))
1405
1671
  throw new Error(`Installation '${t}' is not installed`);
1406
1672
  }
1407
1673
  }
1408
- function qt(r, t) {
1674
+ function he(r, t) {
1409
1675
  const e = [];
1410
1676
  let i = !1;
1411
1677
  for (const s of r)
1412
- s.status !== "fulfilled" && (Object.is(s.reason, t.reason) ? (i || e.push(s.reason), i = !0) : J(t, s.reason) || e.push(s.reason));
1678
+ s.status !== "fulfilled" && (Object.is(s.reason, t.reason) ? (i || e.push(s.reason), i = !0) : wt(t, s.reason) || e.push(s.reason));
1413
1679
  return e;
1414
1680
  }
1415
- class Dt {
1416
- #t = new dt();
1681
+ class de {
1682
+ #t = new zt();
1417
1683
  #e;
1418
1684
  #i;
1419
1685
  constructor(t) {
1420
- this.#e = new Mt(t);
1686
+ this.#e = new ce(t);
1421
1687
  }
1422
1688
  get hasCommittedPlan() {
1423
1689
  return this.#i !== void 0;
1424
1690
  }
1425
1691
  get(t, e) {
1426
- const i = ot(t), s = i ? t.service : t;
1427
- if (b(s, "service"), this.#t.assertCompatible(s), e === "unavailable") throw U();
1428
- const n = this.#a().provider(s.id);
1692
+ const i = jt(t), s = i ? t.service : t;
1693
+ if (A(s, "service"), this.#t.assertCompatible(s), e === "unavailable") throw ct();
1694
+ const n = this.#o().provider(s.id);
1429
1695
  if (!n) {
1430
1696
  if (i) return;
1431
1697
  throw new u("SERVICE_UNAVAILABLE", `Service '${s.id}' is not active`);
@@ -1438,13 +1704,13 @@ class Dt {
1438
1704
  return o.value;
1439
1705
  }
1440
1706
  contributions(t) {
1441
- return b(t, "extensionPoint"), this.#t.remember(t), this.#e.contributions(t);
1707
+ return A(t, "extensionPoint"), this.#t.remember(t), this.#e.contributions(t);
1442
1708
  }
1443
1709
  buildPlan(t) {
1444
- return M.build(t, this.#t.kinds);
1710
+ return X.build(t, this.#t.kinds);
1445
1711
  }
1446
1712
  async start(t) {
1447
- const e = this.#t.draft(t.contractKinds);
1713
+ const e = this.#t.writer(t.contractKinds);
1448
1714
  try {
1449
1715
  await this.#e.withContributionBatch(() => this.#s(t, e)), this.#i = t;
1450
1716
  } catch (i) {
@@ -1452,17 +1718,34 @@ class Dt {
1452
1718
  }
1453
1719
  }
1454
1720
  async stop() {
1455
- const t = await this.#e.withContributionBatch(() => this.#e.deactivateAll());
1721
+ const t = await this.#e.withContributionBatch(
1722
+ () => this.#e.deactivateAll()
1723
+ );
1456
1724
  return this.#i = void 0, t;
1457
1725
  }
1726
+ /**
1727
+ * The whole transaction, in the order the steps have to happen.
1728
+ *
1729
+ * Configs resolve and Contract identities draft *before* anything stops,
1730
+ * because those are the failures that can still be taken back for free — a
1731
+ * schema rejection at this point costs nothing. Once the affected Instances
1732
+ * are down, every remaining failure has a price, and the only question left is
1733
+ * whether the previous state can be restored.
1734
+ *
1735
+ * The whole body runs inside one contribution batch, so contributions
1736
+ * withdrawn by stopping Instances and added by starting ones become visible in
1737
+ * a single step. Without it, an ExtensionPoint observer would see the set
1738
+ * briefly empty in the middle of a successful change.
1739
+ */
1458
1740
  async transition(t, e, i) {
1459
1741
  return this.#e.withContributionBatch(async () => {
1460
- const s = this.#a(), n = s.affectedByTransitionTo(t, e);
1742
+ const s = this.#o(), n = s.affectedByTransitionTo(t, e);
1461
1743
  let o, a;
1462
1744
  try {
1463
1745
  o = await this.#r(
1746
+ t,
1464
1747
  t.order.filter((d) => n.has(d))
1465
- ), a = this.#t.draft(t.contractKinds);
1748
+ ), a = this.#t.writer(t.contractKinds);
1466
1749
  } catch (d) {
1467
1750
  throw i(), d;
1468
1751
  }
@@ -1477,11 +1760,11 @@ class Dt {
1477
1760
  return await this.#e.activate(t, n, o, a), a.commit(), this.#e.commitActivationOrder(t.order), this.#i = t, Object.freeze({ kind: "committed", affected: n });
1478
1761
  } catch (d) {
1479
1762
  const l = await this.#e.deactivate(n);
1480
- return a.discard(), d instanceof N || l.length ? this.#n(
1763
+ return a.discard(), d instanceof Y || l.length ? this.#n(
1481
1764
  i,
1482
1765
  [d, ...l],
1483
1766
  "Installation change failed and its partial activation could not be cleanly disposed"
1484
- ) : this.#o(i, s, n, h, [
1767
+ ) : this.#a(i, s, n, h, [
1485
1768
  d,
1486
1769
  ...l
1487
1770
  ]);
@@ -1489,7 +1772,7 @@ class Dt {
1489
1772
  });
1490
1773
  }
1491
1774
  async #s(t, e) {
1492
- const i = new Set(t.order), s = await this.#r(t.order);
1775
+ const i = new Set(t.order), s = await this.#r(t, t.order);
1493
1776
  this.#e.resetActivationState();
1494
1777
  try {
1495
1778
  await this.#e.activate(t, i, s, e), e.commit(), this.#e.commitActivationOrder(t.order);
@@ -1498,14 +1781,30 @@ class Dt {
1498
1781
  throw o.length ? new AggregateError([n, ...o], "Host startup failed") : n;
1499
1782
  }
1500
1783
  }
1784
+ /**
1785
+ * The last level: no consistent graph could be reached, so none is presented.
1786
+ *
1787
+ * Everything stops and `#plan` is cleared, which drops the Host to `idle` and
1788
+ * closes reads. The AggregateError carries every cause — the original failure
1789
+ * first, then whatever shutdown itself hit. Nothing is swallowed here; the
1790
+ * point of failing closed is that the report is complete.
1791
+ */
1501
1792
  async #n(t, e, i) {
1502
1793
  t();
1503
1794
  const s = await this.#e.deactivateAll();
1504
1795
  throw this.#i = void 0, new AggregateError([...e, ...s], i);
1505
1796
  }
1506
- async #o(t, e, i, s, n) {
1797
+ /**
1798
+ * Restores the previous plan using the configs captured before the change, not
1799
+ * mutable records. Declaration versions belong to the plan; re-validating
1800
+ * configs could fail for a second,
1801
+ * unrelated reason while trying to recover from the first.
1802
+ *
1803
+ * A rollback that itself fails escalates to fail-closed by throwing.
1804
+ */
1805
+ async #a(t, e, i, s, n) {
1507
1806
  t();
1508
- const o = this.#t.draft(e.contractKinds);
1807
+ const o = this.#t.writer(e.contractKinds);
1509
1808
  try {
1510
1809
  await this.#e.activate(e, i, s, o), o.commit(), this.#e.commitActivationOrder(e.order), this.#i = e;
1511
1810
  } catch (h) {
@@ -1518,48 +1817,47 @@ class Dt {
1518
1817
  const a = n.length === 1 ? n[0] : new AggregateError(n, "Installation change failed");
1519
1818
  return Object.freeze({ kind: "rolled-back", affected: i, error: a });
1520
1819
  }
1521
- async #r(t) {
1522
- const e = /* @__PURE__ */ new Map();
1523
- for (const i of t)
1820
+ async #r(t, e) {
1821
+ const i = /* @__PURE__ */ new Map();
1822
+ for (const s of e)
1524
1823
  try {
1525
- e.set(
1526
- i,
1527
- await X(
1528
- i.declaration.plugin.config,
1529
- i.declaration.config,
1530
- i.id
1531
- )
1532
- );
1533
- } catch (s) {
1534
- throw $(
1824
+ const n = t.declarationFor(s);
1825
+ i.set(
1535
1826
  s,
1827
+ await Lt(n.plugin.config, n.config, s.id)
1828
+ );
1829
+ } catch (n) {
1830
+ throw V(
1831
+ n,
1536
1832
  "INSTALLATION_UNAVAILABLE",
1537
- `Installation '${i.id}' failed with a non-Error value`
1833
+ `Installation '${s.id}' failed with a non-Error value`
1538
1834
  );
1539
1835
  }
1540
- return e;
1836
+ return i;
1541
1837
  }
1542
- #a() {
1543
- if (!this.#i) throw U();
1838
+ #o() {
1839
+ if (!this.#i) throw ct();
1544
1840
  return this.#i;
1545
1841
  }
1546
1842
  }
1547
- function U() {
1843
+ function ct() {
1548
1844
  return new u("SERVICE_UNAVAILABLE", "Host services are not active");
1549
1845
  }
1550
- class Gt {
1846
+ class le {
1551
1847
  #t;
1552
1848
  #e;
1553
- #i;
1554
- #s = 0;
1849
+ #i = 0;
1555
1850
  view;
1556
1851
  constructor(t, e, i) {
1557
- this.#t = t, this.#i = this.#n("idle", [], e), this.#e = new q(() => this.#i, i), this.view = this.#e.view;
1852
+ this.#t = t, this.#e = new Z(() => {
1853
+ const { status: s, installations: n, groups: o } = e();
1854
+ return this.#s(s, n, o);
1855
+ }, i), this.view = this.#e.view;
1558
1856
  }
1559
- publish(t, e, i) {
1560
- this.#s++, this.#i = this.#n(t, e, i), this.#e.invalidate();
1857
+ publish() {
1858
+ this.#i++, this.#e.invalidate();
1561
1859
  }
1562
- #n(t, e, i) {
1860
+ #s(t, e, i) {
1563
1861
  const s = /* @__PURE__ */ new Map();
1564
1862
  for (const o of e) {
1565
1863
  const a = {
@@ -1568,10 +1866,10 @@ class Gt {
1568
1866
  groupId: o.groupId,
1569
1867
  status: o.status,
1570
1868
  requires: Object.freeze(
1571
- Object.values(o.declaration.plugin.requires ?? {}).map((c) => c.kind === "optional" ? c.service.id : c.id)
1869
+ Object.values(o.declaration.plugin.requires).map((c) => c.kind === "optional" ? c.service.id : c.id)
1572
1870
  ),
1573
1871
  provides: Object.freeze(
1574
- Object.values(o.declaration.plugin.provides ?? {}).map((c) => c.id)
1872
+ Object.values(o.declaration.plugin.provides).map((c) => c.id)
1575
1873
  ),
1576
1874
  ...o.instance ? { lifetime: o.instance.lifetime.diagnostics } : {}
1577
1875
  }, h = o.error;
@@ -1591,29 +1889,32 @@ class Gt {
1591
1889
  return Object.freeze({
1592
1890
  name: this.#t,
1593
1891
  status: t,
1594
- revision: this.#s,
1595
- installations: new S(s),
1596
- groups: new S(n)
1892
+ revision: this.#i,
1893
+ installations: new z(s),
1894
+ groups: new z(n)
1597
1895
  });
1598
1896
  }
1599
1897
  }
1600
- function k(r, t, e = {}) {
1898
+ function B(r, t, e = {}) {
1601
1899
  if (!r || typeof r != "object" || Array.isArray(r))
1602
- throw E(e, `${t} must be a plain record`);
1900
+ throw O(e, `${t} must be a plain record`);
1603
1901
  const i = Object.getPrototypeOf(r);
1604
1902
  if (i !== Object.prototype && i !== null)
1605
- throw E(e, `${t} must be a plain record`);
1903
+ throw O(e, `${t} must be a plain record`);
1606
1904
  for (const s of Reflect.ownKeys(r)) {
1607
- if (typeof s != "string" || !Object.prototype.propertyIsEnumerable.call(r, s))
1608
- throw E(e, `${t} keys must be enumerable strings`);
1905
+ const n = Reflect.getOwnPropertyDescriptor(r, s);
1906
+ if (typeof s != "string" || !n?.enumerable)
1907
+ throw O(e, `${t} keys must be enumerable strings`);
1908
+ if (!("value" in n))
1909
+ throw O(e, `${t} field '${s}' must be a data property`);
1609
1910
  if (e.fields && !e.fields.has(s))
1610
- throw E(e, `${t}: unknown field '${s}'`);
1911
+ throw O(e, `${t}: unknown field '${s}'`);
1611
1912
  }
1612
1913
  }
1613
- function E(r, t) {
1914
+ function O(r, t) {
1614
1915
  return r.createError?.(t) ?? new TypeError(t);
1615
1916
  }
1616
- const Bt = /* @__PURE__ */ new Set([
1917
+ const ue = /* @__PURE__ */ new Set([
1617
1918
  "signal",
1618
1919
  "meta",
1619
1920
  "log",
@@ -1623,12 +1924,12 @@ const Bt = /* @__PURE__ */ new Set([
1623
1924
  "on",
1624
1925
  "emit",
1625
1926
  "contribute"
1626
- ]), Ft = /* @__PURE__ */ new Set(["name", "config", "requires", "provides", "setup"]);
1627
- function pe(r) {
1628
- return it(r);
1927
+ ]), fe = /* @__PURE__ */ new Set(["name", "config", "requires", "provides", "setup"]);
1928
+ function Ne(r) {
1929
+ return Ct(r);
1629
1930
  }
1630
- function it(r) {
1631
- Ut(r);
1931
+ function Ct(r) {
1932
+ pe(r);
1632
1933
  const t = Object.hasOwn(r, "name") ? r.name : void 0, e = Object.hasOwn(r, "config") ? r.config : void 0, i = Object.hasOwn(r, "requires") ? r.requires : void 0, s = Object.hasOwn(r, "provides") ? r.provides : void 0, n = Object.hasOwn(r, "setup") ? r.setup : void 0;
1633
1934
  if (typeof t != "string" || !t.trim())
1634
1935
  throw new TypeError("Plugin name must be a non-empty string");
@@ -1636,24 +1937,27 @@ function it(r) {
1636
1937
  throw new TypeError("Plugin name cannot start or end with whitespace");
1637
1938
  if (typeof n != "function")
1638
1939
  throw new TypeError(`Plugin '${t}' must define setup()`);
1639
- if (e !== void 0 && !_t(e))
1940
+ if (e !== void 0 && !ge(e))
1640
1941
  throw new TypeError(`Plugin '${t}' config must implement Standard Schema V1`);
1641
- _(i, t, "requires"), _(s, t, "provides");
1642
- const o = i ? Object.freeze({ ...i }) : void 0, a = s ? Object.freeze({ ...s }) : void 0, h = /* @__PURE__ */ new Map();
1643
- for (const [c, d] of Object.entries(o ?? {})) {
1942
+ ht(i, t, "requires"), ht(s, t, "provides");
1943
+ const o = /* @__PURE__ */ Object.create(null), a = /* @__PURE__ */ Object.create(null), h = /* @__PURE__ */ new Map();
1944
+ for (const [c, d] of Object.entries(i ?? {})) {
1644
1945
  if (!c.trim()) throw new TypeError("Plugin requirement alias cannot be empty");
1645
1946
  if (c !== c.trim())
1646
1947
  throw new TypeError("Plugin requirement alias cannot start or end with whitespace");
1647
- if (Bt.has(c))
1948
+ if (ue.has(c))
1648
1949
  throw new TypeError(`Plugin requirement '${c}' conflicts with the context API`);
1649
1950
  if (!d || typeof d != "object")
1650
1951
  throw new TypeError(`Plugin requirement '${c}' is not a contract`);
1651
1952
  if (d.kind === "optional") {
1652
- if (!v(d.service, "service"))
1953
+ if (!E(d.service, "service"))
1653
1954
  throw new TypeError(`Optional requirement '${c}' must wrap a Service`);
1654
- } else if (!v(d, "service") && !v(d, "extensionPoint"))
1955
+ } else if (!E(d, "service") && !E(d, "extensionPoint"))
1655
1956
  throw new TypeError(`Plugin requirement '${c}' must be a Service or ExtensionPoint`);
1656
- H(
1957
+ o[c] = d.kind === "optional" ? Object.freeze({
1958
+ kind: "optional",
1959
+ service: _(d.service)
1960
+ }) : _(d), dt(
1657
1961
  t,
1658
1962
  h,
1659
1963
  "requirement",
@@ -1661,36 +1965,41 @@ function it(r) {
1661
1965
  d.kind === "optional" ? d.service : d
1662
1966
  );
1663
1967
  }
1664
- for (const [c, d] of Object.entries(a ?? {})) {
1968
+ for (const [c, d] of Object.entries(s ?? {})) {
1665
1969
  if (!c.trim()) throw new TypeError("Plugin provision alias cannot be empty");
1666
1970
  if (c !== c.trim())
1667
1971
  throw new TypeError("Plugin provision alias cannot start or end with whitespace");
1668
- if (!v(d, "service"))
1972
+ if (c === "then")
1973
+ throw new TypeError("Plugin provision alias 'then' conflicts with the Promise protocol");
1974
+ if (!E(d, "service"))
1669
1975
  throw new TypeError(`Plugin provision '${c}' must be a Service`);
1670
- H(t, h, "provision", c, d);
1976
+ a[c] = _(d), dt(t, h, "provision", c, a[c]);
1671
1977
  }
1672
1978
  return Object.freeze({
1673
1979
  name: t,
1674
1980
  ...e === void 0 ? {} : { config: e },
1675
- ...o ? { requires: o } : {},
1676
- ...a ? { provides: a } : {},
1981
+ requires: Object.freeze(o),
1982
+ provides: Object.freeze(a),
1677
1983
  setup: n
1678
1984
  });
1679
1985
  }
1680
- function Ut(r) {
1681
- k(r, "Plugin declaration", { fields: Ft });
1986
+ function _(r) {
1987
+ return Object.freeze({ id: r.id, kind: r.kind });
1988
+ }
1989
+ function pe(r) {
1990
+ B(r, "Plugin declaration", { fields: fe });
1682
1991
  }
1683
- function _t(r) {
1992
+ function ge(r) {
1684
1993
  if (!r || typeof r != "object" && typeof r != "function") return !1;
1685
1994
  const t = r["~standard"];
1686
1995
  if (!t || typeof t != "object") return !1;
1687
1996
  const e = t;
1688
1997
  return e.version === 1 && typeof e.vendor == "string" && typeof e.validate == "function";
1689
1998
  }
1690
- function _(r, t, e) {
1691
- r !== void 0 && k(r, `Plugin '${t}' ${e}`);
1999
+ function ht(r, t, e) {
2000
+ r !== void 0 && B(r, `Plugin '${t}' ${e}`);
1692
2001
  }
1693
- function H(r, t, e, i, s) {
2002
+ function dt(r, t, e, i, s) {
1694
2003
  const n = t.get(s.id);
1695
2004
  if (!n) {
1696
2005
  t.set(s.id, { alias: i, kind: s.kind, role: e });
@@ -1698,38 +2007,49 @@ function H(r, t, e, i, s) {
1698
2007
  }
1699
2008
  if (n.kind !== s.kind)
1700
2009
  throw new TypeError(
1701
- `Plugin '${r}' uses Contract '${s.id}' as both ${L(n.kind)} and ${L(s.kind)}`
2010
+ `Plugin '${r}' uses Contract '${s.id}' as both ${H(n.kind)} and ${H(s.kind)}`
1702
2011
  );
1703
- const o = L(s.kind);
2012
+ const o = H(s.kind);
1704
2013
  throw n.role !== e ? new TypeError(
1705
2014
  `Plugin '${r}' cannot both require and provide ${o} '${s.id}'`
1706
2015
  ) : new TypeError(
1707
2016
  `Plugin '${r}' ${e} aliases '${n.alias}' and '${i}' reference the same ${o} '${s.id}'`
1708
2017
  );
1709
2018
  }
1710
- function L(r) {
2019
+ function H(r) {
1711
2020
  return r === "extensionPoint" ? "ExtensionPoint" : r === "event" ? "Event" : "Service";
1712
2021
  }
1713
- function K(r) {
1714
- return it(r);
2022
+ function lt(r) {
2023
+ return Ct(r);
1715
2024
  }
1716
- const Ht = /* @__PURE__ */ new Set(["plugin", "config"]), x = /* @__PURE__ */ new WeakMap();
1717
- function Kt(r, t) {
1718
- x.get(r)?.(t);
2025
+ const we = /* @__PURE__ */ new Set(["plugin", "config"]), x = /* @__PURE__ */ new WeakMap();
2026
+ function be(r, t) {
2027
+ x.get(r)?.discard(t);
1719
2028
  }
1720
- class Wt {
2029
+ function me(r, t, e, i) {
2030
+ const s = x.get(r);
2031
+ if (!s) throw new Error("ChangeSet draft control has been released");
2032
+ return s.install(t, e, i);
2033
+ }
2034
+ class ve {
1721
2035
  #t = /* @__PURE__ */ new Map();
1722
2036
  #e;
1723
2037
  constructor(t) {
1724
- this.#e = { phase: "open", port: t }, x.set(this, (e) => this.#i(e)), Object.freeze(this);
2038
+ this.#e = { phase: "open", port: t }, x.set(this, {
2039
+ discard: (e) => this.#s(e),
2040
+ install: (e, i, s) => this.#i(e, i, s)
2041
+ }), Object.freeze(this);
1725
2042
  }
1726
2043
  install(t, ...e) {
1727
- const i = this.#n(), s = K(t), n = i.create(s, e[0]);
1728
- return this.#s({ kind: "install", installation: n.record }), n.publicInstallation;
2044
+ return this.#i(t, e[0]);
2045
+ }
2046
+ #i(t, e, i) {
2047
+ const s = this.#a(), n = lt(t), o = (i ?? s.create)(n, e);
2048
+ return this.#n({ kind: "install", installation: o.record }), o.facade;
1729
2049
  }
1730
2050
  update(t, e) {
1731
- const i = this.#n();
1732
- k(e, "Installation update", { fields: Ht });
2051
+ const i = this.#a();
2052
+ B(e, "Installation update", { fields: we });
1733
2053
  const s = Object.hasOwn(e, "plugin"), n = Object.hasOwn(e, "config");
1734
2054
  if (!s && !n)
1735
2055
  throw new TypeError("Installation update must include 'plugin' or 'config'");
@@ -1738,17 +2058,23 @@ class Wt {
1738
2058
  if (s) {
1739
2059
  const d = e.plugin;
1740
2060
  if (!d) throw new TypeError("Installation update 'plugin' must be a Plugin");
1741
- a = K(d);
2061
+ a = lt(d);
1742
2062
  }
1743
2063
  let h;
1744
2064
  a && n ? h = { kind: "plugin-and-config", plugin: a, config: e.config } : a ? h = { kind: "plugin", plugin: a } : h = { kind: "config", config: e.config };
1745
2065
  const c = { kind: "update", installation: o, declaration: h };
1746
- this.#s(c);
2066
+ this.#n(c);
1747
2067
  }
1748
2068
  remove(t) {
1749
- const e = this.#n();
1750
- this.#s({ kind: "remove", installation: e.resolve(t) });
1751
- }
2069
+ const e = this.#a();
2070
+ this.#n({ kind: "remove", installation: e.resolve(t) });
2071
+ }
2072
+ /**
2073
+ * Commit is idempotent by returning the same promise, so two callers awaiting
2074
+ * one draft get one transaction rather than two. `committing` exists to catch
2075
+ * the reentrant case — an `attach` hook that calls `commit()` again — which
2076
+ * would otherwise submit the operation list twice.
2077
+ */
1752
2078
  commit() {
1753
2079
  const t = this.#e;
1754
2080
  if (t.phase === "submitted") return t.promise;
@@ -1764,7 +2090,7 @@ class Wt {
1764
2090
  } catch (n) {
1765
2091
  for (const o of e)
1766
2092
  o.kind === "install" && i.discard(o.installation, n);
1767
- return this.#o(Promise.reject(n));
2093
+ return this.#r(Promise.reject(n));
1768
2094
  }
1769
2095
  let s;
1770
2096
  try {
@@ -1772,83 +2098,42 @@ class Wt {
1772
2098
  } catch (n) {
1773
2099
  s = Promise.reject(n);
1774
2100
  }
1775
- return this.#o(s);
2101
+ return this.#r(s);
1776
2102
  }
1777
- #i(t) {
2103
+ #s(t) {
1778
2104
  const e = this.#e;
1779
2105
  if (e.phase !== "open") return;
1780
2106
  this.#e = { phase: "discarded" };
1781
2107
  const i = [...this.#t.values()];
1782
- this.#r();
2108
+ this.#o();
1783
2109
  for (const s of i)
1784
2110
  s.kind === "install" && e.port.discard(s.installation, t);
1785
2111
  }
1786
- #s(t) {
2112
+ // One operation per Installation per ChangeSet. Allowing two would make the
2113
+ // outcome depend on staging order — `update` then `remove` versus the reverse —
2114
+ // and there is no reading of "both" that is obviously right. The caller has to
2115
+ // say which one it meant.
2116
+ #n(t) {
1787
2117
  if (this.#t.has(t.installation))
1788
2118
  throw new TypeError(
1789
2119
  `Installation '${t.installation.id}' can only appear once in the same ChangeSet`
1790
2120
  );
1791
2121
  this.#t.set(t.installation, Object.freeze(t));
1792
2122
  }
1793
- #n() {
2123
+ #a() {
1794
2124
  const t = this.#e;
1795
2125
  if (t.phase !== "open")
1796
2126
  throw new TypeError(`Cannot modify a ${t.phase} ChangeSet`);
1797
2127
  return t.port;
1798
2128
  }
1799
- #o(t) {
1800
- return this.#e = { phase: "submitted", promise: t }, this.#r(), t;
2129
+ #r(t) {
2130
+ return this.#e = { phase: "submitted", promise: t }, this.#o(), t;
1801
2131
  }
1802
- #r() {
2132
+ #o() {
1803
2133
  this.#t.clear(), x.delete(this);
1804
2134
  }
1805
2135
  }
1806
- class Yt {
1807
- #t;
1808
- constructor(t, e, i) {
1809
- this.#t = { phase: "open", draft: t, discard: e, normalize: i };
1810
- }
1811
- get failure() {
1812
- return this.#t.phase === "failed" ? this.#t.error : void 0;
1813
- }
1814
- requireDraft() {
1815
- const t = this.#t;
1816
- if (t.phase === "failed") throw t.error;
1817
- if (t.phase === "sealed") throw z();
1818
- return t.draft;
1819
- }
1820
- assertOpen() {
1821
- this.requireDraft();
1822
- }
1823
- fail(t) {
1824
- const e = this.#t;
1825
- if (e.phase === "failed") return e.error;
1826
- if (e.phase === "sealed") throw z();
1827
- const i = e.normalize(t);
1828
- return this.#t = {
1829
- phase: "failed",
1830
- draft: e.draft,
1831
- discard: e.discard,
1832
- error: i
1833
- }, i;
1834
- }
1835
- seal() {
1836
- const t = this.#t;
1837
- if (t.phase === "failed") throw t.error;
1838
- if (t.phase === "sealed") throw z();
1839
- return this.#t = { phase: "sealed" }, t.draft;
1840
- }
1841
- discard(t) {
1842
- const e = this.#t;
1843
- if (e.phase === "sealed") return;
1844
- const i = e.phase === "failed" ? e.error : e.normalize(t);
1845
- this.#t = { phase: "sealed" }, e.discard(e.draft, i);
1846
- }
1847
- }
1848
- function z() {
1849
- return new Error("Group configuration has been sealed");
1850
- }
1851
- class I {
2136
+ class M {
1852
2137
  constructor(t, e, i) {
1853
2138
  this.id = t, this.name = e, this.#e = { phase: "attached", parent: i };
1854
2139
  }
@@ -1857,7 +2142,7 @@ class I {
1857
2142
  #t = /* @__PURE__ */ new Map();
1858
2143
  #e;
1859
2144
  static root(t) {
1860
- return new I("/", t, void 0);
2145
+ return new M("/", t, void 0);
1861
2146
  }
1862
2147
  get attached() {
1863
2148
  return this.#e.phase !== "detached";
@@ -1870,21 +2155,24 @@ class I {
1870
2155
  return t.phase === "detached" ? void 0 : t.parent;
1871
2156
  }
1872
2157
  create(t) {
1873
- if (this.assertAttached(), Qt(t), this.#t.has(t))
2158
+ if (this.assertAttached(), ye(t), this.#t.has(t))
1874
2159
  throw new TypeError(`Group '${this.#i(t)}' already exists`);
1875
- const e = new I(this.#i(t), t, this);
2160
+ const e = new M(this.#i(t), t, this);
1876
2161
  return this.#t.set(t, e), e;
1877
2162
  }
1878
2163
  contains(t) {
1879
- if (t === this) return !0;
1880
- for (const e of this.#t.values())
1881
- if (e.contains(t)) return !0;
2164
+ for (let e = t; e; e = e.parent)
2165
+ if (e === this) return !0;
1882
2166
  return !1;
1883
2167
  }
1884
2168
  assertAttached() {
1885
2169
  if (this.#e.phase === "detached")
1886
2170
  throw new TypeError(`Group '${this.id}' has been removed`);
1887
2171
  }
2172
+ // Depth-first, and children are detached before this node unlinks itself from
2173
+ // its parent. A node that unlinked first would leave its subtree unreachable
2174
+ // but still attached, so those groups would report themselves as live with no
2175
+ // path back to the root.
1888
2176
  detach() {
1889
2177
  const t = this.#e;
1890
2178
  if (t.phase !== "detached") {
@@ -1904,14 +2192,59 @@ class I {
1904
2192
  return this.id === "/" ? `/${t}` : `${this.id}/${t}`;
1905
2193
  }
1906
2194
  }
1907
- function Qt(r) {
2195
+ function ye(r) {
1908
2196
  if (typeof r != "string" || !r.trim())
1909
2197
  throw new TypeError("Group name must be a non-empty string");
1910
2198
  if (r !== r.trim())
1911
2199
  throw new TypeError("Group name cannot start or end with whitespace");
1912
2200
  if (r.includes("/")) throw new TypeError("Group name cannot contain '/'");
1913
2201
  }
1914
- class W {
2202
+ class Ee {
2203
+ #t;
2204
+ constructor(t, e, i) {
2205
+ this.#t = { phase: "open", draft: t, discard: e, normalize: i };
2206
+ }
2207
+ get failure() {
2208
+ return this.#t.phase === "failed" ? this.#t.error : void 0;
2209
+ }
2210
+ requireDraft() {
2211
+ const t = this.#t;
2212
+ if (t.phase === "failed") throw t.error;
2213
+ if (t.phase === "sealed") throw K();
2214
+ return t.draft;
2215
+ }
2216
+ assertOpen() {
2217
+ this.requireDraft();
2218
+ }
2219
+ fail(t) {
2220
+ const e = this.#t;
2221
+ if (e.phase === "failed") return e.error;
2222
+ if (e.phase === "sealed") throw K();
2223
+ const i = e.normalize(t);
2224
+ return this.#t = {
2225
+ phase: "failed",
2226
+ draft: e.draft,
2227
+ discard: e.discard,
2228
+ error: i
2229
+ }, i;
2230
+ }
2231
+ seal() {
2232
+ const t = this.#t;
2233
+ if (t.phase === "failed") throw t.error;
2234
+ if (t.phase === "sealed") throw K();
2235
+ return this.#t = { phase: "sealed" }, t.draft;
2236
+ }
2237
+ discard(t) {
2238
+ const e = this.#t;
2239
+ if (e.phase === "sealed") return;
2240
+ const i = e.phase === "failed" ? e.error : e.normalize(t);
2241
+ this.#t = { phase: "sealed" }, e.discard(e.draft, i);
2242
+ }
2243
+ }
2244
+ function K() {
2245
+ return new Error("Group configuration has been sealed");
2246
+ }
2247
+ class ut {
1915
2248
  constructor(t, e, i) {
1916
2249
  this.node = t, this.#t = { phase: e }, this.#e = i;
1917
2250
  }
@@ -1931,6 +2264,19 @@ class W {
1931
2264
  if (i.phase === "failed") throw i.error;
1932
2265
  await t();
1933
2266
  }
2267
+ /**
2268
+ * `preserveCommittedState` is the whole point of this class.
2269
+ *
2270
+ * A Group that has already committed something is established, and a later
2271
+ * failed change does not un-establish it — the transaction rolled back, so the
2272
+ * state the Group describes is still the one that was committed. Such a
2273
+ * failure leaves the phase at `established` and does not rethrow, because the
2274
+ * caller of the failed change already has the error.
2275
+ *
2276
+ * A Group whose *first* change fails never had a committed state, so it does
2277
+ * become `failed` and the failure propagates: there is nothing to report but
2278
+ * the failure.
2279
+ */
1934
2280
  track(t) {
1935
2281
  this.node.assertAttached();
1936
2282
  const e = this.#t, i = e.phase === "established" || e.phase === "pending" && e.baseline === "established", s = {}, n = t.then(
@@ -1938,7 +2284,7 @@ class W {
1938
2284
  this.#i(s) && (this.#t = { phase: "established" }, this.#e?.());
1939
2285
  },
1940
2286
  (o) => {
1941
- const a = $(
2287
+ const a = V(
1942
2288
  o,
1943
2289
  "GROUP_UNAVAILABLE",
1944
2290
  `Group '${this.node.id}' operation failed with a non-Error value`
@@ -1962,15 +2308,15 @@ class W {
1962
2308
  return e.phase === "pending" && e.attempt === t;
1963
2309
  }
1964
2310
  }
1965
- function A(r) {
2311
+ function D(r) {
1966
2312
  return new u("GROUP_REMOVED", `Group '${r.id}' has been removed`);
1967
2313
  }
1968
- const C = /* @__PURE__ */ new WeakMap();
1969
- class Jt {
2314
+ const P = /* @__PURE__ */ new WeakMap();
2315
+ class Ce {
1970
2316
  #t;
1971
2317
  #e;
1972
2318
  constructor(t, e, i) {
1973
- this.#t = e, this.#e = i ? { phase: "configuring", coordinator: t, configuration: i } : { phase: "attached", coordinator: t }, C.set(this, {
2319
+ this.#t = e, this.#e = i ? { phase: "configuring", coordinator: t, configuration: i } : { phase: "attached", coordinator: t }, P.set(this, {
1974
2320
  finishConfiguration: () => {
1975
2321
  const s = this.#e;
1976
2322
  s.phase === "configuring" && (this.#e = { phase: "attached", coordinator: s.coordinator });
@@ -1992,11 +2338,16 @@ class Jt {
1992
2338
  }
1993
2339
  ready() {
1994
2340
  const t = this.#e;
1995
- return t.phase === "revoked" ? Promise.reject(A(this.#t)) : t.coordinator.ready(this.#t);
2341
+ return t.phase === "revoked" ? Promise.reject(D(this.#t)) : t.coordinator.ready(this.#t);
1996
2342
  }
1997
2343
  install(t, ...e) {
1998
2344
  const i = this.#e;
1999
- return i.phase === "configuring" ? i.configuration.requireDraft().install(t, ...e) : this.#i().install(this.#t, t, ...e);
2345
+ return i.phase === "configuring" ? i.coordinator.stageConfigurationInstall(
2346
+ this.#t,
2347
+ i.configuration,
2348
+ t,
2349
+ ...e
2350
+ ) : this.#i().install(this.#t, t, ...e);
2000
2351
  }
2001
2352
  change() {
2002
2353
  if (this.#e.phase === "configuring")
@@ -2020,19 +2371,19 @@ class Jt {
2020
2371
  }
2021
2372
  #i() {
2022
2373
  const t = this.#e;
2023
- if (t.phase === "revoked") throw A(this.#t);
2374
+ if (t.phase === "revoked") throw D(this.#t);
2024
2375
  return t.coordinator;
2025
2376
  }
2026
2377
  }
2027
- class Xt {
2378
+ class Se {
2028
2379
  root;
2029
2380
  #t;
2030
2381
  #e = /* @__PURE__ */ new WeakMap();
2031
2382
  #i = /* @__PURE__ */ new WeakMap();
2032
2383
  constructor(t, e) {
2033
- this.root = I.root(t), this.#t = e, this.#i.set(
2384
+ this.root = M.root(t), this.#t = e, this.#i.set(
2034
2385
  this.root,
2035
- new W(this.root, "established", () => e.notifyChanged())
2386
+ new ut(this.root, "established", () => e.notifyChanged())
2036
2387
  );
2037
2388
  }
2038
2389
  nodes() {
@@ -2040,15 +2391,29 @@ class Xt {
2040
2391
  }
2041
2392
  install(t, e, ...i) {
2042
2393
  const s = this.change(t), n = s.install(e, ...i);
2043
- return Y(s.commit()), n;
2394
+ return ft(s.commit()), n;
2044
2395
  }
2396
+ stageConfigurationInstall(t, e, i, ...s) {
2397
+ return this.#r(t), me(
2398
+ e.requireDraft(),
2399
+ i,
2400
+ s[0],
2401
+ (n, o) => (this.#r(t), this.#t.createDraft(t, n, o))
2402
+ );
2403
+ }
2404
+ /**
2405
+ * `deferred` tracking exists for `create()`. A Group being configured must not
2406
+ * mark itself pending on each nested install, because the whole configuration
2407
+ * commits once; the coordinator attaches that single operation to every node in
2408
+ * the finished subtree instead.
2409
+ */
2045
2410
  change(t, e = "immediate") {
2046
- return this.#r(t), new Wt({
2411
+ return this.#r(t), new ve({
2047
2412
  create: (i, s) => (this.#r(t), this.#t.createDraft(t, i, s)),
2048
2413
  resolve: (i) => {
2049
2414
  this.#r(t);
2050
2415
  const s = this.#t.resolveInstallation(i);
2051
- if (!s.attached) throw s.unavailableError();
2416
+ if (!s.hasAuthority) throw s.unavailableError();
2052
2417
  if (!t.contains(s.group))
2053
2418
  throw new TypeError(`Installation '${s.id}' is outside Group '${t.id}'`);
2054
2419
  return s;
@@ -2057,7 +2422,7 @@ class Xt {
2057
2422
  this.#r(t);
2058
2423
  const s = this.#t.executeChanges(t, i);
2059
2424
  for (const n of i) n.installation.trackReadiness(s);
2060
- return i.length && e === "immediate" && this.#o(t, s), s;
2425
+ return i.length && e === "immediate" && this.#a(t, s), s;
2061
2426
  },
2062
2427
  attach: (i) => {
2063
2428
  this.#r(t), this.#t.attachInstallation(i);
@@ -2068,33 +2433,33 @@ class Xt {
2068
2433
  create(t, e, i, s) {
2069
2434
  if (typeof i != "function") throw new TypeError("Group configure must be a function");
2070
2435
  const n = t.create(e);
2071
- this.#i.set(n, new W(n, "new", () => this.#t.notifyChanged()));
2072
- const o = s === void 0, a = s ?? new Yt(
2436
+ this.#i.set(n, new ut(n, "new", () => this.#t.notifyChanged()));
2437
+ const o = s === void 0, a = s ?? new Ee(
2073
2438
  this.change(n, "deferred"),
2074
- Kt,
2075
- (c) => $(
2439
+ be,
2440
+ (c) => V(
2076
2441
  c,
2077
2442
  "GROUP_UNAVAILABLE",
2078
2443
  `Group '${n.id}' configuration failed with a non-Error value`
2079
2444
  )
2080
- ), h = new Jt(this, n, a);
2445
+ ), h = new Ce(this, n, a);
2081
2446
  this.#e.set(n, h);
2082
2447
  try {
2083
2448
  const c = i(h);
2084
- te(c) && Zt(c);
2449
+ R(c, "Group configure must be synchronous");
2085
2450
  const d = a.failure;
2086
2451
  if (d) throw d;
2087
2452
  } catch (c) {
2088
2453
  const d = a.fail(c), l = n.walk();
2089
- throw n.detach(), this.#a(l), o && a.discard(d), this.#t.notifyChanged(), d;
2454
+ throw n.detach(), this.#o(l), o && a.discard(d), this.#t.notifyChanged(), d;
2090
2455
  }
2091
2456
  if (o) {
2092
2457
  const c = a.seal().commit();
2093
2458
  for (const d of n.walk()) {
2094
2459
  const l = this.#e.get(d);
2095
- l && C.get(l)?.finishConfiguration(), this.#o(d, c);
2460
+ l && P.get(l)?.finishConfiguration(), this.#a(d, c);
2096
2461
  }
2097
- Y(c);
2462
+ ft(c);
2098
2463
  }
2099
2464
  return this.#t.notifyChanged(), h;
2100
2465
  }
@@ -2110,56 +2475,53 @@ class Xt {
2110
2475
  if (t === this.root) throw new TypeError("The root Group cannot be removed");
2111
2476
  return t.attached ? this.#t.runExclusive(async () => {
2112
2477
  if (!t.attached) {
2113
- this.#a([t]);
2478
+ this.#o([t]);
2114
2479
  return;
2115
2480
  }
2116
2481
  const e = t.walk(), i = this.#s(t).map((s) => ({
2117
2482
  kind: "remove",
2118
2483
  installation: s
2119
2484
  }));
2120
- await this.#t.removeInstallations(i), t.detach(), this.#a(e), this.#t.notifyChanged();
2121
- }) : (this.#a([t]), Promise.resolve());
2485
+ await this.#t.removeInstallations(i), t.detach(), this.#o(e), this.#t.notifyChanged();
2486
+ }) : (this.#o([t]), Promise.resolve());
2122
2487
  }
2123
2488
  #s(t) {
2124
2489
  return [...this.#t.installations()].filter(
2125
2490
  (e) => t.contains(e.group)
2126
2491
  );
2127
2492
  }
2493
+ // A Group has no status of its own — it reports what its contents say, with
2494
+ // the worst news winning. The last line is the one that needs stating: an
2495
+ // empty Group is `active`, because a subtree that owns nothing has nothing
2496
+ // left to wait for.
2128
2497
  #n(t) {
2129
2498
  const e = this.#s(t);
2130
2499
  return e.some((i) => i.status === "failed") ? "failed" : e.some((i) => i.status === "stopping") ? "stopping" : e.length && e.every((i) => i.status === "active") ? "active" : e.length ? "pending" : "active";
2131
2500
  }
2132
- #o(t, e) {
2501
+ #a(t, e) {
2133
2502
  this.#r(t).track(e);
2134
2503
  }
2135
2504
  #r(t) {
2136
2505
  const e = this.#i.get(t);
2137
- if (!t.attached || !e) throw A(t);
2506
+ if (!t.attached || !e) throw D(t);
2138
2507
  return e;
2139
2508
  }
2140
- #a(t) {
2509
+ #o(t) {
2141
2510
  for (const e of t) {
2142
2511
  this.#i.get(e)?.release(), this.#i.delete(e);
2143
2512
  const i = this.#e.get(e);
2144
- i && (C.get(i)?.revoke(), C.delete(i)), this.#e.delete(e);
2513
+ i && (P.get(i)?.revoke(), P.delete(i)), this.#e.delete(e);
2145
2514
  }
2146
2515
  }
2147
2516
  }
2148
- function Y(r) {
2517
+ function ft(r) {
2149
2518
  Promise.resolve(r).catch(() => {
2150
2519
  });
2151
2520
  }
2152
- function Zt(r) {
2153
- throw Promise.resolve(r).catch(() => {
2154
- }), new TypeError("Group configure must be synchronous");
2155
- }
2156
- function te(r) {
2157
- return r === null || typeof r != "object" && typeof r != "function" ? !1 : typeof r.then == "function";
2158
- }
2159
- function Q(r, t) {
2521
+ function pt(r, t) {
2160
2522
  return Object.freeze({ plugin: r, config: t });
2161
2523
  }
2162
- class ee {
2524
+ class Oe {
2163
2525
  constructor(t, e, i, s) {
2164
2526
  this.id = t, this.index = e, this.groupId = i.id, this.#i = { declaration: s, group: i, notifyChanged: void 0 };
2165
2527
  }
@@ -2171,7 +2533,7 @@ class ee {
2171
2533
  groupId;
2172
2534
  #s = /* @__PURE__ */ new Set();
2173
2535
  attach(t) {
2174
- const e = this.#d();
2536
+ const e = this.#h();
2175
2537
  if (e.notifyChanged) throw new Error(`Installation '${this.id}' is already attached`);
2176
2538
  e.notifyChanged = t;
2177
2539
  }
@@ -2179,7 +2541,7 @@ class ee {
2179
2541
  return this.#t.phase;
2180
2542
  }
2181
2543
  /** Whether the owning ChangeSet has granted this Installation Host authority. */
2182
- get attached() {
2544
+ get hasAuthority() {
2183
2545
  return this.#i?.notifyChanged !== void 0;
2184
2546
  }
2185
2547
  get instance() {
@@ -2189,18 +2551,18 @@ class ee {
2189
2551
  get error() {
2190
2552
  const t = this.#t;
2191
2553
  if (t.phase === "failed")
2192
- return t.failure.retention === "live" ? t.failure.error : se(t.failure.summary);
2554
+ return t.error;
2193
2555
  if (t.phase === "removed")
2194
2556
  return new u("INSTALLATION_REMOVED", `Installation '${this.id}' has been removed`);
2195
2557
  }
2196
2558
  get group() {
2197
- return this.#d().group;
2559
+ return this.#h().group;
2198
2560
  }
2199
2561
  get declaration() {
2200
- return this.#d().declaration;
2562
+ return this.#h().declaration;
2201
2563
  }
2202
2564
  replaceDeclaration(t) {
2203
- this.#d().declaration = t;
2565
+ this.#h().declaration = t;
2204
2566
  }
2205
2567
  ready() {
2206
2568
  const t = this.#e;
@@ -2212,18 +2574,34 @@ class ee {
2212
2574
  `Installation '${this.id}' has not been committed`
2213
2575
  );
2214
2576
  }
2577
+ /**
2578
+ * Binds `ready()` to the change currently in flight.
2579
+ *
2580
+ * The `attempt` token is the point: a second change can start before the
2581
+ * first one's promise settles, and only the latest attempt is allowed to clear
2582
+ * the barrier. Without it, a stale resolution would report readiness for an
2583
+ * attempt that has already been superseded.
2584
+ *
2585
+ * A rejection is rethrown only when this Installation did not end up active.
2586
+ * A change that failed elsewhere in the batch but left this one running is not
2587
+ * this Installation's failure to report.
2588
+ */
2215
2589
  trackReadiness(t) {
2216
2590
  const e = {}, i = t.then(
2217
2591
  () => {
2218
2592
  this.#e?.attempt === e && (this.#e = void 0);
2219
2593
  },
2220
2594
  (s) => {
2221
- if (this.#e?.attempt === e && (this.#e = void 0), this.#t.phase !== "active") throw s;
2595
+ if (this.#e?.attempt === e && (this.#e = void 0), this.#t.phase !== "active") throw this.error ?? s;
2222
2596
  }
2223
2597
  );
2224
2598
  this.#e = { attempt: e, barrier: i }, i.catch(() => {
2225
2599
  });
2226
2600
  }
2601
+ // A terminal state only answers `ready()` once its readiness has been settled.
2602
+ // Before that the outcome is still being decided by the change in flight, so
2603
+ // the caller joins the waiter set instead of being told a result that the
2604
+ // transaction might still roll back.
2227
2605
  #n() {
2228
2606
  const t = this.#t;
2229
2607
  if ((t.phase === "active" || t.phase === "failed" || t.phase === "removed") && t.readiness === "settled") {
@@ -2262,23 +2640,29 @@ class ee {
2262
2640
  this.#r({ phase: "pending" });
2263
2641
  }
2264
2642
  fail(t) {
2265
- return this.#o(t);
2266
- }
2643
+ return this.#a(t);
2644
+ }
2645
+ /**
2646
+ * For an Installation that never made it into the graph. Unlike `fail()`, this
2647
+ * is terminal: readiness settles immediately, the attachment is dropped, and
2648
+ * the reason is kept as a RecordedFailure rather than the live Error. The handle the
2649
+ * caller already holds stays usable and reports why it is dead.
2650
+ */
2267
2651
  discard(t) {
2268
- const e = this.#a(t);
2652
+ const e = new J(this.#o(t));
2269
2653
  this.#r({
2270
2654
  phase: "failed",
2271
- failure: { retention: "summary", summary: ie(e) },
2655
+ error: e,
2272
2656
  readiness: "settled"
2273
2657
  });
2274
2658
  for (const i of this.#s) i.reject(e);
2275
2659
  this.#s.clear(), this.#e = void 0, this.#i = void 0;
2276
2660
  }
2277
- #o(t) {
2278
- const e = this.#a(t);
2661
+ #a(t) {
2662
+ const e = this.#o(t);
2279
2663
  return this.#r({
2280
2664
  phase: "failed",
2281
- failure: { retention: "live", error: e },
2665
+ error: e,
2282
2666
  readiness: "unsettled"
2283
2667
  }), e;
2284
2668
  }
@@ -2288,36 +2672,25 @@ class ee {
2288
2672
  #r(t) {
2289
2673
  this.#t = t, this.#i?.notifyChanged?.();
2290
2674
  }
2291
- #a(t) {
2292
- return $(
2675
+ #o(t) {
2676
+ return V(
2293
2677
  t,
2294
2678
  "INSTALLATION_UNAVAILABLE",
2295
2679
  `Installation '${this.id}' failed with a non-Error value`
2296
2680
  );
2297
2681
  }
2298
- #d() {
2682
+ #h() {
2299
2683
  const t = this.#i;
2300
2684
  if (!t) throw new Error(`Installation '${this.id}' is no longer installed`);
2301
2685
  return t;
2302
2686
  }
2303
2687
  }
2304
- function ie(r) {
2305
- return r instanceof u ? { category: "dougong", name: r.name, message: r.message, code: r.code } : {
2306
- category: r instanceof TypeError ? "typeError" : "error",
2307
- name: r.name,
2308
- message: r.message
2309
- };
2310
- }
2311
- function se(r) {
2312
- const t = r.category === "dougong" ? new u(r.code, r.message) : r.category === "typeError" ? new TypeError(r.message) : new Error(r.message);
2313
- return t.name = r.name, t;
2314
- }
2315
- const O = /* @__PURE__ */ new WeakMap();
2316
- class re {
2688
+ const L = /* @__PURE__ */ new WeakMap();
2689
+ class Ae {
2317
2690
  #t;
2318
2691
  #e = { phase: "draft" };
2319
2692
  constructor(t) {
2320
- this.#t = t, O.set(this, {
2693
+ this.#t = t, L.set(this, {
2321
2694
  attach: (e, i) => {
2322
2695
  if (this.#e.phase !== "draft")
2323
2696
  throw new Error(`Installation '${this.#t.id}' control is already sealed`);
@@ -2362,7 +2735,7 @@ class re {
2362
2735
  );
2363
2736
  }
2364
2737
  }
2365
- class ne {
2738
+ class Ie {
2366
2739
  #t = /* @__PURE__ */ new Map();
2367
2740
  #e = /* @__PURE__ */ new WeakMap();
2368
2741
  #i = /* @__PURE__ */ new WeakMap();
@@ -2382,13 +2755,13 @@ class ne {
2382
2755
  }
2383
2756
  create(t, e, i) {
2384
2757
  t.assertAttached();
2385
- const s = ++this.#n, n = new ee(
2758
+ const s = ++this.#n, n = new Oe(
2386
2759
  `${e.name}:${s}`,
2387
2760
  s,
2388
2761
  t,
2389
- Q(e, i)
2390
- ), o = new re(n);
2391
- return this.#e.set(o, n), this.#i.set(n, o), { record: n, publicInstallation: o };
2762
+ pt(e, i)
2763
+ ), o = new Ae(n);
2764
+ return this.#e.set(o, n), this.#i.set(n, o), { record: n, facade: o };
2392
2765
  }
2393
2766
  resolve(t) {
2394
2767
  const e = this.#e.get(t);
@@ -2398,13 +2771,19 @@ class ne {
2398
2771
  attach(t) {
2399
2772
  const e = this.#i.get(t);
2400
2773
  if (!e) throw new Error(`Installation '${t.id}' has no public facade`);
2401
- const i = O.get(e);
2774
+ const i = L.get(e);
2402
2775
  if (!i) throw new Error(`Installation '${t.id}' has no draft control`);
2403
2776
  t.attach(this.#s.notifyChanged), i.attach(
2404
2777
  (s) => this.#s.update(t, e, s),
2405
2778
  () => this.#s.remove(t, e)
2406
2779
  );
2407
2780
  }
2781
+ /**
2782
+ * Validates the whole batch, then applies the whole batch. The two loops are
2783
+ * not a style choice: a rejected operation halfway through a single loop would
2784
+ * leave the earlier ones already written, and this method has no way to undo
2785
+ * them. Checking first means `apply` either changes everything or nothing.
2786
+ */
2408
2787
  apply(t) {
2409
2788
  for (const e of t) {
2410
2789
  if (e.kind === "install") {
@@ -2425,37 +2804,39 @@ class ne {
2425
2804
  this.#t.set(e.installation.id, e.installation);
2426
2805
  else if (e.kind === "update") {
2427
2806
  const i = e.installation.declaration, s = e.declaration.kind === "config" ? i.plugin : e.declaration.plugin, n = e.declaration.kind === "plugin" ? i.config : e.declaration.config;
2428
- e.installation.replaceDeclaration(Q(s, n));
2807
+ e.installation.replaceDeclaration(pt(s, n));
2429
2808
  } else this.contains(e.installation) && this.#t.delete(e.installation.id);
2430
2809
  }
2431
2810
  settleChanges(t, e) {
2432
2811
  for (const i of t)
2433
- i.kind === "remove" ? (i.installation.remove(), i.installation.settleReady(), this.#o(i.installation)) : e || i.installation.deactivate();
2812
+ i.kind === "remove" ? (i.installation.remove(), i.installation.settleReady(), this.#a(i.installation)) : e || i.installation.deactivate();
2434
2813
  }
2435
2814
  settleReadiness(t) {
2436
2815
  for (const e of t) e.settleReady();
2437
2816
  }
2438
2817
  discard(t, e) {
2439
- t.discard(e), this.#o(t);
2818
+ t.discard(e), this.#a(t);
2440
2819
  }
2820
+ // Captures declarations only, since that is all a rollback restores. Instances
2821
+ // belong to the InstanceCoordinator and are rebuilt from the previous plan
2822
+ // rather than snapshotted — a live Instance is not a value that can be copied.
2441
2823
  capture() {
2442
- return [...this.#t].map(([t, e]) => ({
2443
- id: t,
2444
- installation: e,
2445
- declaration: e.declaration
2824
+ return [...this.#t.values()].map((t) => ({
2825
+ installation: t,
2826
+ declaration: t.declaration
2446
2827
  }));
2447
2828
  }
2448
2829
  restore(t) {
2449
2830
  this.#t.clear();
2450
2831
  for (const e of t)
2451
- e.installation.replaceDeclaration(e.declaration), this.#t.set(e.id, e.installation);
2832
+ e.installation.replaceDeclaration(e.declaration), this.#t.set(e.installation.id, e.installation);
2452
2833
  }
2453
- #o(t) {
2834
+ #a(t) {
2454
2835
  const e = this.#i.get(t);
2455
- e && (O.get(e)?.revoke(), O.delete(e)), this.#i.delete(t);
2836
+ e && (L.get(e)?.revoke(), L.delete(e)), this.#i.delete(t);
2456
2837
  }
2457
2838
  }
2458
- class oe {
2839
+ class je {
2459
2840
  #t = Promise.resolve();
2460
2841
  get settled() {
2461
2842
  return this.#t;
@@ -2472,8 +2853,8 @@ class oe {
2472
2853
  ), e;
2473
2854
  }
2474
2855
  }
2475
- const ae = console, ce = /* @__PURE__ */ new Set(["name", "logger", "onError"]);
2476
- class he {
2856
+ const ke = console, $e = /* @__PURE__ */ new Set(["name", "logger", "onError"]);
2857
+ class Te {
2477
2858
  name;
2478
2859
  diagnostics;
2479
2860
  #t;
@@ -2481,11 +2862,11 @@ class he {
2481
2862
  #i;
2482
2863
  #s;
2483
2864
  #n;
2484
- #o;
2865
+ #a;
2485
2866
  #r = "idle";
2486
- #a = new oe();
2867
+ #o = new je();
2487
2868
  constructor(t = {}) {
2488
- k(t, "Host options", { fields: ce });
2869
+ B(t, "Host options", { fields: $e });
2489
2870
  const e = Object.hasOwn(t, "name") ? t.name : void 0, i = Object.hasOwn(t, "logger") ? t.logger : void 0, s = Object.hasOwn(t, "onError") ? t.onError : void 0, n = e ?? "host";
2490
2871
  if (typeof n != "string" || !n.trim())
2491
2872
  throw new TypeError("Host name must be a non-empty string");
@@ -2493,10 +2874,10 @@ class he {
2493
2874
  throw new TypeError("Host name cannot start or end with whitespace");
2494
2875
  if (s !== void 0 && typeof s != "function")
2495
2876
  throw new TypeError("Host onError must be a function");
2496
- if (i !== void 0 && !zt(i))
2877
+ if (i !== void 0 && !se(i))
2497
2878
  throw new TypeError("Host logger must implement debug/info/warn/error");
2498
- this.name = n, this.#i = i ?? ae, this.#n = s ?? ((o) => this.#i.error(o)), this.#t = new ne({
2499
- notifyChanged: () => this.#p(),
2879
+ this.name = n, this.#i = i ?? ke, this.#n = s ?? ((o) => this.#i.error(o)), this.#t = new Ie({
2880
+ notifyChanged: () => this.#d(),
2500
2881
  update: (o, a, h) => {
2501
2882
  const c = this.#s.change(o.group);
2502
2883
  return c.update(a, h), c.commit();
@@ -2505,25 +2886,29 @@ class he {
2505
2886
  const h = this.#s.change(o.group);
2506
2887
  return h.remove(a), h.commit();
2507
2888
  }
2508
- }), this.#o = new Dt({
2889
+ }), this.#a = new de({
2509
2890
  hostName: n,
2510
2891
  logger: this.#i,
2511
2892
  isInstalled: (o) => this.#t.has(o),
2512
- report: (o) => this.#h(o)
2513
- }), this.#s = new Xt(n, {
2893
+ report: (o) => this.#c(o)
2894
+ }), this.#s = new Se(n, {
2514
2895
  installations: () => this.#t.values(),
2515
2896
  createDraft: (o, a, h) => this.#t.create(o, a, h),
2516
2897
  resolveInstallation: (o) => this.#t.resolve(o),
2517
- executeChanges: (o, a) => this.#d(o, a),
2898
+ executeChanges: (o, a) => this.#h(o, a),
2518
2899
  attachInstallation: (o) => this.#t.attach(o),
2519
2900
  discardInstallation: (o, a) => this.#t.discard(o, a),
2520
- runExclusive: (o) => this.#a.run(o),
2521
- removeInstallations: (o) => this.#f(o),
2522
- notifyChanged: () => this.#p()
2523
- }), this.#e = new Gt(
2901
+ runExclusive: (o) => this.#o.run(o),
2902
+ removeInstallations: (o) => this.#p(o),
2903
+ notifyChanged: () => this.#d()
2904
+ }), this.#e = new le(
2524
2905
  n,
2525
- this.#s.nodes(),
2526
- (o) => this.#h(o)
2906
+ () => ({
2907
+ status: this.#r,
2908
+ installations: this.#t.values(),
2909
+ groups: this.#s.nodes()
2910
+ }),
2911
+ (o) => this.#c(o)
2527
2912
  ), this.diagnostics = this.#e.view, Object.freeze(this);
2528
2913
  }
2529
2914
  get status() {
@@ -2531,10 +2916,10 @@ class he {
2531
2916
  }
2532
2917
  get(t) {
2533
2918
  const e = this.#r === "active" ? "available" : "unavailable";
2534
- return this.#o.get(t, e);
2919
+ return this.#a.get(t, e);
2535
2920
  }
2536
2921
  contributions(t) {
2537
- return this.#o.contributions(t);
2922
+ return this.#a.contributions(t);
2538
2923
  }
2539
2924
  install(t, ...e) {
2540
2925
  return this.#s.install(this.#s.root, t, ...e);
@@ -2546,14 +2931,14 @@ class he {
2546
2931
  return this.#s.create(this.#s.root, t, e);
2547
2932
  }
2548
2933
  start() {
2549
- return this.#a.run(async () => {
2934
+ return this.#o.run(async () => {
2550
2935
  if (this.#r !== "active") {
2551
- this.#c("starting");
2936
+ this.#l("starting");
2552
2937
  try {
2553
- const t = this.#o.buildPlan(this.#t.values());
2554
- await this.#o.start(t), this.#c("active"), this.#t.settleReadiness(t.order);
2938
+ const t = this.#a.buildPlan(this.#t.values());
2939
+ await this.#a.start(t), this.#l("active"), this.#t.settleReadiness(t.order);
2555
2940
  } catch (t) {
2556
- this.#c("idle");
2941
+ this.#l("idle");
2557
2942
  for (const e of this.#t.values())
2558
2943
  e.status !== "active" && e.fail(t);
2559
2944
  throw this.#t.settleReadiness(this.#t.values()), t;
@@ -2562,21 +2947,21 @@ class he {
2562
2947
  });
2563
2948
  }
2564
2949
  stop() {
2565
- return this.#a.run(async () => {
2950
+ return this.#o.run(async () => {
2566
2951
  if (this.#r === "idle") return;
2567
- this.#c("stopping");
2568
- const t = await this.#o.stop();
2569
- if (this.#c("idle"), t.length === 1) throw t[0];
2952
+ this.#l("stopping");
2953
+ const t = await this.#a.stop();
2954
+ if (this.#l("idle"), t.length === 1) throw t[0];
2570
2955
  if (t.length > 1) throw new AggregateError(t, "Host shutdown failed");
2571
2956
  });
2572
2957
  }
2573
- #d(t, e) {
2958
+ #h(t, e) {
2574
2959
  const i = e.filter((s) => s.kind === "install").map((s) => s.installation);
2575
- return this.#a.run(async () => {
2960
+ return this.#o.run(async () => {
2576
2961
  try {
2577
- if (!t.attached) throw A(t);
2962
+ if (!t.attached) throw D(t);
2578
2963
  if (!e.length) return;
2579
- this.#r === "active" ? await this.#l(e) : (this.#t.apply(e), this.#t.settleChanges(e, !1)), this.#p();
2964
+ this.#r === "active" ? await this.#u(e) : (this.#t.apply(e), this.#t.settleChanges(e, !1)), this.#d();
2580
2965
  } catch (s) {
2581
2966
  for (const n of i)
2582
2967
  this.#t.contains(n) || this.#t.discard(n, s);
@@ -2584,71 +2969,92 @@ class he {
2584
2969
  }
2585
2970
  });
2586
2971
  }
2587
- async #f(t) {
2588
- t.length && this.#r === "active" ? await this.#l(t) : (this.#t.apply(t), this.#t.settleChanges(t, !1));
2972
+ async #p(t) {
2973
+ t.length && this.#r === "active" ? await this.#u(t) : (this.#t.apply(t), this.#t.settleChanges(t, !1));
2589
2974
  }
2590
- async #l(t) {
2591
- const e = await this.#u(t);
2975
+ async #u(t) {
2976
+ const e = await this.#f(t);
2592
2977
  if (this.#t.settleReadiness(e.affected), e.kind === "rolled-back") throw e.error;
2593
2978
  this.#t.settleChanges(t, !0);
2594
2979
  }
2595
- async #u(t) {
2980
+ // Three failure levels, in the order they are attempted:
2981
+ //
2982
+ // 1. the new plan does not even build restore declarations, stay active
2983
+ // 2. the plan builds but activation fails Engine rolls back to the previous
2984
+ // Instances and reports the cause
2985
+ // 3. rollback itself cannot complete fail closed: everything stops and
2986
+ // the Host reports `idle`
2987
+ //
2988
+ // Level 3 is the one worth stating out loud. A Host that cannot restore its
2989
+ // previous state is not healthy, so it refuses to present itself as active —
2990
+ // `hasCommittedPlan` is what distinguishes the two outcomes here.
2991
+ async #f(t) {
2596
2992
  const e = this.#t.capture(), i = new Set(t.map((n) => n.installation));
2597
- this.#c("changing");
2993
+ this.#l("changing");
2598
2994
  let s;
2599
2995
  try {
2600
- this.#t.apply(t), s = this.#o.buildPlan(this.#t.values());
2996
+ this.#t.apply(t), s = this.#a.buildPlan(this.#t.values());
2601
2997
  } catch (n) {
2602
- throw this.#t.restore(e), this.#c("active"), n;
2998
+ throw this.#t.restore(e), this.#l("active"), n;
2603
2999
  }
2604
3000
  try {
2605
- const n = await this.#o.transition(
3001
+ const n = await this.#a.transition(
2606
3002
  s,
2607
3003
  i,
2608
3004
  () => this.#t.restore(e)
2609
3005
  );
2610
- return this.#c("active"), n;
3006
+ return this.#l("active"), n;
2611
3007
  } catch (n) {
2612
- throw this.#c(this.#o.hasCommittedPlan ? "active" : "idle"), n;
3008
+ throw this.#l(this.#a.hasCommittedPlan ? "active" : "idle"), n;
2613
3009
  }
2614
3010
  }
2615
- #h(t) {
3011
+ #c(t) {
3012
+ const e = this.#i;
2616
3013
  try {
2617
- this.#n(t);
2618
- } catch (e) {
2619
- try {
2620
- this.#i.error(
2621
- new AggregateError([t, e], "Host error reporter failed")
2622
- );
2623
- } catch {
2624
- }
3014
+ const i = this.#n(t);
3015
+ Promise.resolve(i).catch((s) => {
3016
+ gt(e, t, s);
3017
+ });
3018
+ } catch (i) {
3019
+ gt(e, t, i);
2625
3020
  }
2626
3021
  }
2627
- #c(t) {
2628
- this.#r = t, this.#p();
3022
+ #l(t) {
3023
+ this.#r = t, this.#d();
2629
3024
  }
2630
- #p() {
2631
- this.#e.publish(this.#r, this.#t.values(), this.#s.nodes());
3025
+ #d() {
3026
+ this.#e.publish();
2632
3027
  }
2633
3028
  }
2634
- function ge(r) {
2635
- return new he(r);
3029
+ function xe(r) {
3030
+ return new Te(r);
3031
+ }
3032
+ function gt(r, t, e) {
3033
+ try {
3034
+ const i = r.error(
3035
+ new AggregateError([t, e], "Host error reporter failed")
3036
+ );
3037
+ Promise.resolve(i).catch(() => {
3038
+ });
3039
+ } catch {
3040
+ }
2636
3041
  }
2637
3042
  export {
2638
- at as ConfigValidationError,
3043
+ $t as ConfigValidationError,
2639
3044
  u as DougongError,
2640
- S as ReadonlyMapSnapshot,
2641
- oe as SerialQueue,
2642
- q as SnapshotPublisher,
2643
- k as assertPlainRecord,
2644
- j as asyncDisposeSymbol,
2645
- ge as createHost,
2646
- pe as definePlugin,
2647
- g as disposeSymbol,
2648
- le as event,
2649
- ue as extensionPoint,
2650
- J as isCancellationReason,
2651
- zt as isLogger,
2652
- fe as optional,
2653
- de as service
3045
+ z as ReadonlyMapSnapshot,
3046
+ J as RecordedFailure,
3047
+ je as SerialQueue,
3048
+ Z as SnapshotPublisher,
3049
+ B as assertPlainRecord,
3050
+ F as asyncDisposeSymbol,
3051
+ xe as createHost,
3052
+ Ne as definePlugin,
3053
+ v as disposeSymbol,
3054
+ Le as event,
3055
+ ze as extensionPoint,
3056
+ wt as isCancellationReason,
3057
+ se as isLogger,
3058
+ Re as optional,
3059
+ It as service
2654
3060
  };