@telorun/kernel 0.81.0 → 0.82.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/controllers/logging/console-sink-controller.d.ts.map +1 -1
  2. package/dist/controllers/logging/console-sink-controller.js +19 -9
  3. package/dist/controllers/logging/console-sink-controller.js.map +1 -1
  4. package/dist/controllers/logging/file-sink-controller.d.ts.map +1 -1
  5. package/dist/controllers/logging/file-sink-controller.js +17 -6
  6. package/dist/controllers/logging/file-sink-controller.js.map +1 -1
  7. package/dist/controllers/module/import-controller.d.ts.map +1 -1
  8. package/dist/controllers/module/import-controller.js +36 -15
  9. package/dist/controllers/module/import-controller.js.map +1 -1
  10. package/dist/controllers/resource-definition/resource-template-controller.d.ts.map +1 -1
  11. package/dist/controllers/resource-definition/resource-template-controller.js +7 -5
  12. package/dist/controllers/resource-definition/resource-template-controller.js.map +1 -1
  13. package/dist/effect-scope.d.ts +132 -0
  14. package/dist/effect-scope.d.ts.map +1 -0
  15. package/dist/effect-scope.js +253 -0
  16. package/dist/effect-scope.js.map +1 -0
  17. package/dist/evaluation-context.d.ts +26 -1
  18. package/dist/evaluation-context.d.ts.map +1 -1
  19. package/dist/evaluation-context.js +156 -12
  20. package/dist/evaluation-context.js.map +1 -1
  21. package/dist/init-failure-diagnostics.d.ts +10 -0
  22. package/dist/init-failure-diagnostics.d.ts.map +1 -1
  23. package/dist/init-failure-diagnostics.js +7 -1
  24. package/dist/init-failure-diagnostics.js.map +1 -1
  25. package/dist/kernel.d.ts.map +1 -1
  26. package/dist/kernel.js +17 -12
  27. package/dist/kernel.js.map +1 -1
  28. package/dist/manifest-schemas.d.ts +4 -1
  29. package/dist/manifest-schemas.d.ts.map +1 -1
  30. package/dist/manifest-schemas.js +4 -10
  31. package/dist/manifest-schemas.js.map +1 -1
  32. package/dist/module-context.d.ts +10 -0
  33. package/dist/module-context.d.ts.map +1 -1
  34. package/dist/module-context.js +15 -0
  35. package/dist/module-context.js.map +1 -1
  36. package/dist/observed-state.d.ts.map +1 -1
  37. package/dist/observed-state.js +2 -4
  38. package/dist/observed-state.js.map +1 -1
  39. package/dist/resource-context.d.ts +18 -1
  40. package/dist/resource-context.d.ts.map +1 -1
  41. package/dist/resource-context.js +27 -0
  42. package/dist/resource-context.js.map +1 -1
  43. package/dist/schema-compiled-values.d.ts +2 -1
  44. package/dist/schema-compiled-values.d.ts.map +1 -1
  45. package/dist/schema-compiled-values.js +23 -20
  46. package/dist/schema-compiled-values.js.map +1 -1
  47. package/package.json +4 -4
  48. package/src/controllers/logging/console-sink-controller.ts +19 -9
  49. package/src/controllers/logging/file-sink-controller.ts +17 -6
  50. package/src/controllers/module/import-controller.ts +50 -27
  51. package/src/controllers/resource-definition/resource-template-controller.ts +14 -11
  52. package/src/effect-scope.ts +340 -0
  53. package/src/evaluation-context.ts +165 -10
  54. package/src/init-failure-diagnostics.ts +8 -1
  55. package/src/kernel.ts +20 -11
  56. package/src/manifest-schemas.ts +4 -9
  57. package/src/module-context.ts +16 -0
  58. package/src/observed-state.ts +2 -4
  59. package/src/resource-context.ts +32 -0
  60. package/src/schema-compiled-values.ts +35 -21
@@ -0,0 +1,253 @@
1
+ import { RuntimeError } from "@telorun/sdk";
2
+ /** One line for an inverse's refusal, quoted into a recovery aggregate. */
3
+ function errorText(err) {
4
+ return err instanceof Error ? err.message : String(err);
5
+ }
6
+ function isAsyncGenerator(value) {
7
+ return (typeof value === "object" &&
8
+ value !== null &&
9
+ typeof value.next === "function" &&
10
+ Symbol.asyncIterator in value);
11
+ }
12
+ /**
13
+ * A lazy chain of steps against one scope.
14
+ *
15
+ * Immutable and value-like, so `chain.effect(...)` in a branch or a loop builds
16
+ * a new description rather than mutating a shared one. Deliberately NOT a
17
+ * thenable: an `async init()` would unwrap it and hand the kernel its last
18
+ * result instead of the chain.
19
+ */
20
+ class Chain {
21
+ scope;
22
+ steps;
23
+ constructor(scope, steps) {
24
+ this.scope = scope;
25
+ this.steps = steps;
26
+ }
27
+ effect(reason, body) {
28
+ return new Chain(this.scope, [
29
+ ...this.steps,
30
+ { reason, body: body },
31
+ ]);
32
+ }
33
+ perform() {
34
+ return this.scope.execute(this.steps);
35
+ }
36
+ /** The steps, for the kernel executing a chain a controller returned. */
37
+ plan() {
38
+ return this.steps;
39
+ }
40
+ }
41
+ /** A chain produced by this kernel, as opposed to any other object a controller
42
+ * might return from `init()`. */
43
+ export function isEffectChain(value) {
44
+ return value instanceof Chain;
45
+ }
46
+ export class EffectScope {
47
+ label;
48
+ /** Innermost last. `create` is opened with the scope, so there is always a
49
+ * frame to register onto. */
50
+ frames = [];
51
+ /** Set while this scope is unwinding. A generator body checks it at each step
52
+ * boundary and stops rather than allocating into a scope that is going away —
53
+ * the mid-boot SIGINT case. */
54
+ unwinding = false;
55
+ /** Terminal: set by {@link unwindAll}. A closed scope has no frames and takes
56
+ * no new effects. */
57
+ closed = false;
58
+ constructor(label) {
59
+ this.label = label;
60
+ this.openFrame("create");
61
+ }
62
+ /** Refuse work against a scope that has already unwound. Raised BEFORE a
63
+ * forward body runs, so a late effect cannot allocate and then find it has
64
+ * nowhere to record the inverse. */
65
+ assertOpen(what) {
66
+ if (!this.closed)
67
+ return;
68
+ throw new RuntimeError("ERR_EFFECT_SCOPE_CLOSED", `${this.label}: '${what}' cannot run — this resource has been torn down. ` +
69
+ `An effect registered now would record an inverse nothing will ever run.`);
70
+ }
71
+ openFrame(label) {
72
+ this.frames.push({ label, entries: [] });
73
+ }
74
+ /** Start a chain. Nothing runs until it is executed. */
75
+ chain(reason, body) {
76
+ return new Chain(this, []).effect(reason, body);
77
+ }
78
+ /**
79
+ * Register an inverse for work performed elsewhere, without a forward body.
80
+ *
81
+ * The synchronous door onto the same accumulator, for a primitive that
82
+ * already returns its own inverse: `acquireHold` hands back a release
83
+ * closure, and its public signature is synchronous, so it cannot go through a
84
+ * chain.
85
+ */
86
+ register(reason, inverse) {
87
+ this.assertOpen(reason);
88
+ const entry = { reason, inverse, disposed: false };
89
+ this.current().entries.push(entry);
90
+ return () => this.disposeEntries([entry]);
91
+ }
92
+ /**
93
+ * Run a chain's steps in order against the frame open NOW, threading each
94
+ * step's result into the next.
95
+ *
96
+ * A step that throws leaves every inverse produced so far on the frame — this
97
+ * does not unwind, because whether a partial `init()` is recovered-and-retried
98
+ * or torn down is the caller's decision, not this function's.
99
+ */
100
+ async execute(steps) {
101
+ const registered = [];
102
+ const push = (reason, inverse) => {
103
+ const entry = { reason, inverse, disposed: false };
104
+ this.current().entries.push(entry);
105
+ registered.push(entry);
106
+ };
107
+ let value = undefined;
108
+ for (const step of steps) {
109
+ this.assertOpen(step.reason);
110
+ if (this.unwinding) {
111
+ throw new RuntimeError("ERR_EFFECT_SCOPE_CLOSING", `${this.label}: '${step.reason}' not started because the resource is being torn down`);
112
+ }
113
+ const produced = step.body(value);
114
+ if (isAsyncGenerator(produced)) {
115
+ const iterator = produced;
116
+ for (;;) {
117
+ if (this.unwinding) {
118
+ // Stop at the step boundary and let the generator run its own
119
+ // `finally`. What already yielded stays on the frame and is
120
+ // recovered by the unwind in progress.
121
+ await iterator.return?.(undefined);
122
+ throw new RuntimeError("ERR_EFFECT_SCOPE_CLOSING", `${this.label}: '${step.reason}' stopped because the resource is being torn down`);
123
+ }
124
+ const next = await iterator.next();
125
+ if (next.done) {
126
+ value = next.value;
127
+ break;
128
+ }
129
+ push(step.reason, next.value);
130
+ }
131
+ }
132
+ else {
133
+ const outcome = (await produced);
134
+ // No inverse means the step allocated nothing that outlives a failure —
135
+ // a chain is the sequencing structure for lifecycle work as well as the
136
+ // record of what to undo, so a step with nothing to undo registers
137
+ // nothing rather than a no-op that would read as an oversight.
138
+ if (outcome.inverse)
139
+ push(step.reason, outcome.inverse);
140
+ value = outcome.result;
141
+ }
142
+ }
143
+ return { result: value, dispose: () => this.disposeEntries(registered) };
144
+ }
145
+ /**
146
+ * Unwind the innermost frame and close it, returning what refused.
147
+ *
148
+ * Failures are returned rather than thrown: the caller decides what a refusal
149
+ * means. Pre-retry recovery withholds the resource (retrying from a state that
150
+ * could not be rolled back is worse than not retrying); teardown aggregates
151
+ * and keeps going, so one throwing resource cannot strand the log sinks pinned
152
+ * to outlive it.
153
+ */
154
+ async unwindFrame() {
155
+ const frame = this.frames.pop();
156
+ if (!frame)
157
+ return [];
158
+ return this.runInverses(frame.entries);
159
+ }
160
+ /**
161
+ * Unwind every open frame, innermost first, and CLOSE the scope.
162
+ *
163
+ * Terminal, because both callers are: teardown, and the discard of a resource
164
+ * whose `init()` failed (its replacement is built with a fresh context, so a
165
+ * fresh scope). A closed scope refuses new effects rather than accepting them
166
+ * onto a frame nothing will ever unwind — recording an inverse that will never
167
+ * run is the silent leak this whole mechanism exists to remove, and it is
168
+ * exactly the shape a detached task still settling after teardown produces.
169
+ */
170
+ async unwindAll() {
171
+ const failures = [];
172
+ while (this.frames.length > 0)
173
+ failures.push(...(await this.unwindFrame()));
174
+ this.closed = true;
175
+ return failures;
176
+ }
177
+ async runInverses(entries) {
178
+ const wasUnwinding = this.unwinding;
179
+ this.unwinding = true;
180
+ const failures = [];
181
+ for (const entry of [...entries].reverse()) {
182
+ if (entry.disposed)
183
+ continue;
184
+ entry.disposed = true;
185
+ try {
186
+ await entry.inverse();
187
+ }
188
+ catch (error) {
189
+ failures.push({ reason: entry.reason, error });
190
+ }
191
+ }
192
+ this.unwinding = wasUnwinding;
193
+ return failures;
194
+ }
195
+ async disposeEntries(entries) {
196
+ const failures = await this.runInverses(entries);
197
+ if (failures.length === 0)
198
+ return;
199
+ // An explicit dispose HAS a caller, unlike an unwind — so it throws rather
200
+ // than being collected into someone else's aggregate. Every refusal travels,
201
+ // matching the init and teardown paths: a dispose covers a chain, so
202
+ // reporting the first and dropping the rest would hide the others exactly
203
+ // where more than one thing failed to roll back.
204
+ throw new RuntimeError("ERR_EFFECT_RECOVERY_FAILED", `${this.label}: ${failures.length} inverse(s) refused: ` +
205
+ failures.map((f) => `'${f.reason}' (${errorText(f.error)})`).join(", "), failures.map((f) => ({
206
+ severity: "error",
207
+ message: `inverse '${f.reason}' refused: ${errorText(f.error)}`,
208
+ })));
209
+ }
210
+ current() {
211
+ this.assertOpen("effect");
212
+ const frame = this.frames[this.frames.length - 1];
213
+ if (!frame)
214
+ throw new RuntimeError("ERR_EFFECT_NO_FRAME", `${this.label}: no open effect frame`);
215
+ return frame;
216
+ }
217
+ }
218
+ /**
219
+ * Execute whatever a lifecycle method returned.
220
+ *
221
+ * A controller that allocates nothing returns nothing, so a non-chain return is
222
+ * not an error — but it is also not an inverse, which is why `init()` returning
223
+ * a chain it forgot to hand back fails loudly and immediately: nothing was
224
+ * allocated at all.
225
+ */
226
+ export async function executeReturnedChain(returned, scope) {
227
+ if (!isEffectChain(returned))
228
+ return;
229
+ if (!scope) {
230
+ throw new RuntimeError("ERR_EFFECT_NO_SCOPE", "a controller returned an effect chain from a resource with no effect scope");
231
+ }
232
+ await scope.execute(returned.plan());
233
+ }
234
+ /**
235
+ * An instance's effect owner, recorded at the kernel's single
236
+ * instance-production site — the anchor that already carries handle minting and
237
+ * contract binding, so an instance is never observable without one.
238
+ *
239
+ * A WeakMap rather than a field on the resource-instances map: teardown holds
240
+ * the instance and nothing else, and every reader of that map would otherwise
241
+ * have to learn about effects. First bind wins, mirroring handle minting: a
242
+ * `base:` child IS the parent instance returned verbatim, and re-binding would
243
+ * give one object two accumulators.
244
+ */
245
+ const owners = new WeakMap();
246
+ export function bindEffectOwner(instance, owner) {
247
+ if (!owners.has(instance))
248
+ owners.set(instance, owner);
249
+ }
250
+ export function effectOwnerOf(instance) {
251
+ return owners.get(instance);
252
+ }
253
+ //# sourceMappingURL=effect-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect-scope.js","sourceRoot":"","sources":["../src/effect-scope.ts"],"names":[],"mappings":"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"}
@@ -50,12 +50,27 @@ export declare class EvaluationContext implements IEvaluationContext {
50
50
  resource: ResourceManifest;
51
51
  instance: ResourceInstance;
52
52
  }>;
53
- /** Resources that have been created but not yet initialized (between phases). */
53
+ /** Resources that have been created but not yet initialized (between phases).
54
+ * `source` is the manifest as REGISTERED, kept so a discarded instance is
55
+ * rebuilt from what the author wrote rather than from the create-time
56
+ * expansion of it. */
54
57
  protected readonly createdInstances: Map<string, {
55
58
  resource: ResourceManifest;
56
59
  instance: ResourceInstance;
57
60
  ctx: any;
61
+ source: ResourceManifest;
58
62
  }>;
63
+ /**
64
+ * Resources whose failed `init()` could not be rolled back.
65
+ *
66
+ * Withheld rather than retried: re-running `init()` from a state an inverse
67
+ * refused to restore is worse than not retrying, so the loop skips them and
68
+ * they are reported with both the init error and the refusing inverse.
69
+ */
70
+ private readonly withheldResources;
71
+ /** Resources discarded after a failed `init()` and re-queued for creation.
72
+ * Their re-creation is not progress — see the create sub-phase. */
73
+ private readonly recreatedResources;
59
74
  /** Resources queued for initialization on this context node. */
60
75
  private pendingResources;
61
76
  /**
@@ -178,6 +193,16 @@ export declare class EvaluationContext implements IEvaluationContext {
178
193
  * Attach a child context to this node. The child's parent is set to this
179
194
  * context and the child is registered under the given name.
180
195
  */
196
+ /**
197
+ * Detach a child from this node — the inverse of {@link spawnChild}.
198
+ *
199
+ * A child is normally torn down in place, so this exists for the one case
200
+ * that discards it instead: an import whose instance is dropped and rebuilt
201
+ * must not leave its child in `children`, or this context's teardown cascades
202
+ * into a context whose replacement is already live. Here rather than in the
203
+ * controller, because how children are tracked is this class's own fact.
204
+ */
205
+ detachChild(child: IEvaluationContext): void;
181
206
  spawnChild<T extends IEvaluationContext>(child: T): T;
182
207
  /** Spawn a fresh child context attached to this node — the isolated scope a
183
208
  * templated definition registers its `resources:` into. Rooting it on the
@@ -1 +1 @@
1
- {"version":3,"file":"evaluation-context.d.ts","sourceRoot":"","sources":["../src/evaluation-context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"evaluation-context.d.ts","sourceRoot":"","sources":["../src/evaluation-context.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,8 @@ import { formatSpanCounter } from "./logging/span-id.js";
3
3
  import { deriveContext, getRefIdentity, isCompiledValue, isInvokeError, isCancellationError, isSuspension, resourceKey, UNCANCELLABLE_CONTEXT, } from "@telorun/sdk";
4
4
  import { RuntimeError } from "@telorun/sdk";
5
5
  import { evalPathCovers } from "@telorun/analyzer";
6
- import { classifyInitFailures, renderInitFailureText, summarizeInitFailures, } from "./init-failure-diagnostics.js";
6
+ import { effectOwnerOf, executeReturnedChain } from "./effect-scope.js";
7
+ import { classifyInitFailures, isDeferral, renderInitFailureText, summarizeInitFailures, } from "./init-failure-diagnostics.js";
7
8
  import { acceptReportedStatus, buildPublishedProps, diagnoseObservedStateAccess, } from "./observed-state.js";
8
9
  export { resourceKey };
9
10
  /** A pure resolved reference is a `{ kind, name, alias? }` object and nothing
@@ -398,8 +399,22 @@ export class EvaluationContext {
398
399
  state = "Pending";
399
400
  /** Resource instances owned by this context node, keyed by resourceKey(). */
400
401
  resourceInstances = new Map();
401
- /** Resources that have been created but not yet initialized (between phases). */
402
+ /** Resources that have been created but not yet initialized (between phases).
403
+ * `source` is the manifest as REGISTERED, kept so a discarded instance is
404
+ * rebuilt from what the author wrote rather than from the create-time
405
+ * expansion of it. */
402
406
  createdInstances = new Map();
407
+ /**
408
+ * Resources whose failed `init()` could not be rolled back.
409
+ *
410
+ * Withheld rather than retried: re-running `init()` from a state an inverse
411
+ * refused to restore is worse than not retrying, so the loop skips them and
412
+ * they are reported with both the init error and the refusing inverse.
413
+ */
414
+ withheldResources = new Set();
415
+ /** Resources discarded after a failed `init()` and re-queued for creation.
416
+ * Their re-creation is not progress — see the create sub-phase. */
417
+ recreatedResources = new Set();
403
418
  /** Resources queued for initialization on this context node. */
404
419
  pendingResources = [];
405
420
  /**
@@ -614,6 +629,20 @@ export class EvaluationContext {
614
629
  * Attach a child context to this node. The child's parent is set to this
615
630
  * context and the child is registered under the given name.
616
631
  */
632
+ /**
633
+ * Detach a child from this node — the inverse of {@link spawnChild}.
634
+ *
635
+ * A child is normally torn down in place, so this exists for the one case
636
+ * that discards it instead: an import whose instance is dropped and rebuilt
637
+ * must not leave its child in `children`, or this context's teardown cascades
638
+ * into a context whose replacement is already live. Here rather than in the
639
+ * controller, because how children are tracked is this class's own fact.
640
+ */
641
+ detachChild(child) {
642
+ const index = this.children.indexOf(child);
643
+ if (index >= 0)
644
+ this.children.splice(index, 1);
645
+ }
617
646
  spawnChild(child) {
618
647
  child.parent = this;
619
648
  this.children.push(child);
@@ -698,12 +727,20 @@ export class EvaluationContext {
698
727
  resource: created.resource,
699
728
  instance: created.instance,
700
729
  ctx: created.ctx,
730
+ source: resource,
701
731
  });
702
732
  const idx = this.pendingResources.findIndex((m) => m.metadata.name === name);
703
733
  if (idx >= 0)
704
734
  this.pendingResources.splice(idx, 1);
705
735
  errors.delete(name);
706
- progress = true;
736
+ // A FIRST creation is progress; RE-creating a resource whose init
737
+ // failed is not. Counting it would keep the loop alive on a
738
+ // permanently failing resource for every remaining pass, re-running
739
+ // `create()` each time — ten module loads for a broken import, ten
740
+ // pools for an unreachable database. Its retry still happens; what
741
+ // it no longer does is claim the environment moved.
742
+ if (!this.recreatedResources.has(name))
743
+ progress = true;
707
744
  const createdRes = created.resource;
708
745
  const refs = collectResourceRefs(createdRes);
709
746
  this.resourceDependencies.set(name, localDependencyNames(refs));
@@ -738,8 +775,8 @@ export class EvaluationContext {
738
775
  }
739
776
  }
740
777
  // Init sub-phase
741
- for (const [name, { resource, instance, ctx }] of [...this.createdInstances]) {
742
- if (this.resourceInstances.has(name))
778
+ for (const [name, { resource, instance, ctx, source }] of [...this.createdInstances]) {
779
+ if (this.resourceInstances.has(name) || this.withheldResources.has(name))
743
780
  continue;
744
781
  try {
745
782
  if (this.preInitHook) {
@@ -747,8 +784,67 @@ export class EvaluationContext {
747
784
  ? this.resolveImportedInstance(alias, n)
748
785
  : this.resourceInstances.get(n)?.instance, (n) => this.hasManifest(n) && !this.resourceInstances.has(n), this);
749
786
  }
750
- if (instance.init)
751
- await instance.init(ctx);
787
+ const scope = effectOwnerOf(instance)?.effects;
788
+ scope?.openFrame("init");
789
+ try {
790
+ // What `init()` RETURNS is what undoes it. A controller that
791
+ // allocates nothing returns nothing; there is no `teardown()`, so an
792
+ // allocation outside the chain is one nothing will reclaim.
793
+ if (instance.init)
794
+ await executeReturnedChain(await instance.init(ctx), scope);
795
+ }
796
+ catch (error) {
797
+ // Recover before the next pass: the loop retries a failed init, and
798
+ // an init that registered a listener before it failed to connect
799
+ // would otherwise register that listener again on every pass.
800
+ //
801
+ // HOW MUCH unwinds depends on whether the instance survives. A
802
+ // deferral keeps it, so only the init frame goes — unwinding the
803
+ // create frame there would destroy what construction built (a
804
+ // connection's pool) and then re-init the same instance against it.
805
+ // A real failure discards the instance, so everything `create()`
806
+ // allocated on its behalf goes with it or nothing reclaims it.
807
+ const deferred = isDeferral(error);
808
+ const refused = (await (deferred ? scope?.unwindFrame() : scope?.unwindAll())) ?? [];
809
+ if (refused.length > 0) {
810
+ // Retrying from a state that could not be rolled back is worse
811
+ // than not retrying, so the resource is withheld with a cause
812
+ // naming both the init error and the inverse that refused. The
813
+ // entry stays in `createdInstances` so it is still reported as a
814
+ // failure; the loop skips it from here on.
815
+ this.withheldResources.add(name);
816
+ throw new RuntimeError("ERR_EFFECT_RECOVERY_FAILED", `${resource.kind} '${name}' failed to initialize and could not be rolled back: ` +
817
+ `${refused.map((f) => `'${f.reason}' (${errorText(f.error)})`).join(", ")}`, [
818
+ { severity: "error", message: errorText(error), resource: name },
819
+ ...refused.map((f) => ({
820
+ severity: "error",
821
+ message: `inverse '${f.reason}' refused: ${errorText(f.error)}`,
822
+ resource: name,
823
+ })),
824
+ ]);
825
+ }
826
+ // A DEFERRAL is not a failure: it is the loop's own "your turn has
827
+ // not come" signal, raised when a ref names a resource that has not
828
+ // initialized yet. The instance stays — re-creating it would re-run
829
+ // `create()`, which for an import re-registers its alias and reloads
830
+ // its module, and for a template re-registers its children. Its init
831
+ // frame alone was unwound above, so the next pass re-inits a
832
+ // constructed resource rather than a dismantled one.
833
+ if (deferred)
834
+ throw error;
835
+ // The inverses restored what init() touched OUTSIDE the instance;
836
+ // the instance's own half-built fields are beyond their reach, so
837
+ // the object goes too and the next pass builds a fresh one. That is
838
+ // what makes "retry from a clean state" literal rather than a
839
+ // convention each controller has to honour.
840
+ this.createdInstances.delete(name);
841
+ this.recreatedResources.add(name);
842
+ // Re-queued as REGISTERED, not as created: the create-time manifest
843
+ // has already been through compile-field expansion, and expanding
844
+ // it a second time would evaluate an author's expression twice.
845
+ this.pendingResources.push(source);
846
+ throw error;
847
+ }
752
848
  // Publish BEFORE registering: publication can fail (a kind returning
753
849
  // the reserved `status` key without declaring it, a report that does
754
850
  // not match `status:`), and a resource that failed must not be left
@@ -956,9 +1052,26 @@ export class EvaluationContext {
956
1052
  reportedStatus.delete(instance);
957
1053
  startedInstances.delete(instance);
958
1054
  completedInstances.delete(instance);
1055
+ // Tearing a resource down IS unwinding its effects — every frame, newest
1056
+ // first, LIFO within each. There is no `teardown()` to call: what undoes a
1057
+ // resource is what its `init()` and `run()` returned.
1058
+ const owner = effectOwnerOf(instance);
1059
+ const refused = (await owner?.effects.unwindAll()) ?? [];
1060
+ if (refused.length > 0) {
1061
+ // Aggregate and continue: one refusing inverse must not strand the log
1062
+ // sinks pinned last to outlive everything that might log on the way
1063
+ // down.
1064
+ failures.push({
1065
+ resource: label,
1066
+ error: new RuntimeError("ERR_EFFECT_RECOVERY_FAILED", `${refused.length} inverse(s) refused: ` +
1067
+ refused.map((f) => `'${f.reason}' (${errorText(f.error)})`).join(", ")),
1068
+ });
1069
+ }
1070
+ // A drain waits for in-flight background work under a bound and then
1071
+ // abandons it — not an inverse, so it is not on a frame; it runs here,
1072
+ // once the resource's own effects are undone.
959
1073
  try {
960
- if (instance.teardown)
961
- await instance.teardown();
1074
+ await owner?.drainDetached();
962
1075
  }
963
1076
  catch (err) {
964
1077
  // Aggregate rather than abort. A single throwing resource used to
@@ -1357,12 +1470,26 @@ export class EvaluationContext {
1357
1470
  // the whole point — `ctx.setStatus()` is an error until the resource counts
1358
1471
  // as started, so this has to happen first.
1359
1472
  await this.markStarted(name, instance);
1473
+ // A frame of its own: what `run()` allocates (a listening socket, a kernel
1474
+ // hold) is undone when the run's frame unwinds, without disturbing what
1475
+ // `init()` built underneath it.
1476
+ const effects = effectOwnerOf(instance)?.effects;
1477
+ effects?.openFrame("run");
1360
1478
  try {
1361
1479
  // Runnable: run inside the ALS scope so nested invokes inherit the token and
1362
1480
  // trace id (skip the redundant `run` when the token is already ambient).
1363
1481
  // Service: call directly with the explicit context and NO ambient scope, so
1364
1482
  // its long-lived async work does not capture this scope.
1365
- const call = () => instance.run(invokeCtx);
1483
+ // What `run()` returns is what undoes it — the socket it opened, the hold
1484
+ // it took. The chain executes INSIDE the same ambient scope as the call
1485
+ // that produced it: its bodies are the work `run()` would otherwise have
1486
+ // done inline, so running them outside would silently strip the
1487
+ // cancellation token and trace parent from exactly the code that moved
1488
+ // into a chain.
1489
+ const call = async () => {
1490
+ const returned = await instance.run(invokeCtx);
1491
+ await executeReturnedChain(returned, effects);
1492
+ };
1366
1493
  await (isService || invokeCtx === ambient ? call() : cancellationStore.run(invokeCtx, call));
1367
1494
  // A one-shot Runnable that discovered something during run() publishes it
1368
1495
  // without an explicit call; a Service never reaches this until teardown.
@@ -1371,15 +1498,28 @@ export class EvaluationContext {
1371
1498
  await this.emit(`${name}.Run`, span("end", "ok", {}));
1372
1499
  }
1373
1500
  catch (err) {
1501
+ // A run that did not complete leaves nothing of its own behind: its frame
1502
+ // unwinds here, so a `listen()` that threw releases the hold it took a
1503
+ // line earlier instead of holding the process open for a server that
1504
+ // never came up.
1505
+ const refused = (await effects?.unwindFrame()) ?? [];
1506
+ const recovery = refused.length > 0
1507
+ ? {
1508
+ recoveryFailures: refused.map((f) => ({
1509
+ reason: f.reason,
1510
+ message: errorText(f.error),
1511
+ })),
1512
+ }
1513
+ : {};
1374
1514
  if (isCancellationError(err)) {
1375
1515
  const reason = err instanceof Error ? err.message : String(err);
1376
- await this.emit(`${name}.RunCancelled`, span("end", "cancelled", { reason }));
1516
+ await this.emit(`${name}.RunCancelled`, span("end", "cancelled", { reason, ...recovery }));
1377
1517
  throw err;
1378
1518
  }
1379
1519
  const detail = err instanceof Error
1380
1520
  ? { name: err.name, message: err.message }
1381
1521
  : { name: "UnknownError", message: String(err) };
1382
- await this.emit(`${name}.RunFailed`, span("end", "failed", detail));
1522
+ await this.emit(`${name}.RunFailed`, span("end", "failed", { ...detail, ...recovery }));
1383
1523
  throw err;
1384
1524
  }
1385
1525
  }
@@ -1714,6 +1854,10 @@ function locateFailedAccess(source, ctx, msg) {
1714
1854
  }
1715
1855
  return null;
1716
1856
  }
1857
+ /** One line for an inverse's refusal, quoted into a recovery aggregate. */
1858
+ function errorText(err) {
1859
+ return err instanceof Error ? err.message : String(err);
1860
+ }
1717
1861
  function describeMissingAccess(value, key) {
1718
1862
  if (value === null)
1719
1863
  return `cannot read '${key}' — value is null`;