@telorun/kernel 0.85.0 → 0.86.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.
- package/dist/evaluation-context.d.ts +158 -25
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +234 -44
- package/dist/evaluation-context.js.map +1 -1
- package/dist/kernel.d.ts +60 -0
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +251 -2
- package/dist/kernel.js.map +1 -1
- package/dist/module-context.d.ts +31 -1
- package/dist/module-context.d.ts.map +1 -1
- package/dist/module-context.js +41 -1
- package/dist/module-context.js.map +1 -1
- package/dist/reconcile.d.ts +42 -0
- package/dist/reconcile.d.ts.map +1 -0
- package/dist/reconcile.js +58 -0
- package/dist/reconcile.js.map +1 -0
- package/dist/resource-edges.d.ts +58 -0
- package/dist/resource-edges.d.ts.map +1 -0
- package/dist/resource-edges.js +110 -0
- package/dist/resource-edges.js.map +1 -0
- package/package.json +3 -3
- package/src/evaluation-context.ts +257 -53
- package/src/kernel.ts +310 -1
- package/src/module-context.ts +41 -1
- package/src/reconcile.ts +83 -0
- package/src/resource-edges.ts +114 -0
|
@@ -122,9 +122,27 @@ export declare class EvaluationContext implements IEvaluationContext {
|
|
|
122
122
|
protected readonly declaredManifests: Map<string, ResourceManifest>;
|
|
123
123
|
/** Per-resource dependency names, captured at create() time — BEFORE Phase-5
|
|
124
124
|
* injection swaps refs for live instances, so the walk sees plain objects and
|
|
125
|
-
* cannot wander into a controller's (possibly cyclic) object graph.
|
|
126
|
-
*
|
|
125
|
+
* cannot wander into a controller's (possibly cyclic) object graph.
|
|
126
|
+
*
|
|
127
|
+
* Read twice, and RETAINED for the context's lifetime because of the second
|
|
128
|
+
* reader: to attribute an init failure to its cause, and to order teardown
|
|
129
|
+
* (`teardownOrder`) so a consumer's inverses run while the resources it holds
|
|
130
|
+
* are still alive. */
|
|
127
131
|
private readonly resourceDependencies;
|
|
132
|
+
/**
|
|
133
|
+
* Names resolved by NAME during initialization, rather than through a
|
|
134
|
+
* declared reference slot — so a resource somebody may be holding, with no
|
|
135
|
+
* edge recording who.
|
|
136
|
+
*
|
|
137
|
+
* The set is of TARGETS, not of pairs: the door that records
|
|
138
|
+
* (`ModuleContext.getInstance`) is reached as `ctx.moduleContext`, which every
|
|
139
|
+
* resource of the module shares, so there is no caller to attribute the read
|
|
140
|
+
* to. That is enough for the one decision that depends on it — see
|
|
141
|
+
* {@link impactedBy}.
|
|
142
|
+
*/
|
|
143
|
+
protected readonly opaquelyRead: Set<string>;
|
|
144
|
+
/** Record a by-name resolution. Called by the recording door only. */
|
|
145
|
+
protected recordOpaqueRead(name: string): void;
|
|
128
146
|
/**
|
|
129
147
|
* Optional hook called between create() and init() for each resource.
|
|
130
148
|
* Set by the kernel to inject live instances into reference fields.
|
|
@@ -231,6 +249,22 @@ export declare class EvaluationContext implements IEvaluationContext {
|
|
|
231
249
|
*/
|
|
232
250
|
hasManifest(name: string): boolean;
|
|
233
251
|
registerManifest(resource: ResourceManifest): void;
|
|
252
|
+
/**
|
|
253
|
+
* Forget a declaration entirely — the inverse of {@link registerManifest}.
|
|
254
|
+
*
|
|
255
|
+
* Reconciliation's other half: {@link unwindResources} disposes the INSTANCE,
|
|
256
|
+
* and this clears everything keyed by the name so the same name can be
|
|
257
|
+
* declared again. Without it `registerManifest` refuses with
|
|
258
|
+
* `ERR_DUPLICATE_RESOURCE`, which is the right answer for a manifest
|
|
259
|
+
* declaring one name twice and the wrong one for a second load of the same
|
|
260
|
+
* manifest.
|
|
261
|
+
*
|
|
262
|
+
* Every per-name record goes, not just the declaration: a resource left in
|
|
263
|
+
* `withheldResources` would be skipped by the init loop for the life of the
|
|
264
|
+
* kernel, and a stale `createdInstances` entry would have the loop initialize
|
|
265
|
+
* the object built from the PREVIOUS declaration.
|
|
266
|
+
*/
|
|
267
|
+
deregisterManifest(name: string): void;
|
|
234
268
|
/**
|
|
235
269
|
* The manifest a name was DECLARED with, resolved scope-local first and then
|
|
236
270
|
* up the enclosing chain — the order `getInstance` and the CEL `resources`
|
|
@@ -320,39 +354,138 @@ export declare class EvaluationContext implements IEvaluationContext {
|
|
|
320
354
|
*/
|
|
321
355
|
createScopeHandle(manifests: ResourceManifest[]): ScopeHandle;
|
|
322
356
|
/**
|
|
323
|
-
* Cascade teardown
|
|
324
|
-
* 1. Tear down
|
|
325
|
-
*
|
|
326
|
-
*
|
|
357
|
+
* Cascade teardown through the tree:
|
|
358
|
+
* 1. Tear down own resource instances in {@link teardownOrder}, emitting a
|
|
359
|
+
* Teardown event for each via the injected emit callback.
|
|
360
|
+
* 2. Sweep any child context still standing, in {@link childTeardownOrder}.
|
|
361
|
+
*
|
|
362
|
+
* **Own resources go FIRST, and the child sweep is a backstop.** A child
|
|
363
|
+
* context that belongs to a resource is torn down by that resource's own
|
|
364
|
+
* inverse — an import's `init()` returns `child.teardownResources()`, and so
|
|
365
|
+
* does a template's — so it already unwinds at its owner's position in step 1,
|
|
366
|
+
* which is the position the edges put it at. Running a child cascade ahead of
|
|
367
|
+
* step 1 tore every imported library down before any resource of THIS context,
|
|
368
|
+
* so an app resource holding `!ref Alias.name` unwound after its provider was
|
|
369
|
+
* already gone, and the owner's inverse then found nothing left to do.
|
|
370
|
+
*
|
|
371
|
+
* Sweeping afterwards rather than not at all is what still reclaims a context
|
|
372
|
+
* no inverse claims: a `lifecycle: shared` library, which is spawned under the
|
|
373
|
+
* root and deliberately gives no importer a claim on it, and an import whose
|
|
374
|
+
* `init()` never ran to register one. `teardownResources` is idempotent, so a
|
|
375
|
+
* context already taken down in step 1 costs the sweep nothing.
|
|
327
376
|
*/
|
|
328
377
|
teardownResources(): Promise<void>;
|
|
329
378
|
/**
|
|
330
|
-
*
|
|
331
|
-
*
|
|
379
|
+
* Unwind SOME of this context's resources and leave the rest running.
|
|
380
|
+
*
|
|
381
|
+
* The reconciliation half of teardown: a host that has decided which
|
|
382
|
+
* declarations moved unwinds exactly {@link impactedBy}'s answer, then
|
|
383
|
+
* re-registers and re-initializes. Ordering is {@link teardownOrder}
|
|
384
|
+
* restricted to the selection, so a consumer still unwinds before what it
|
|
385
|
+
* holds — and the selection being closed under holders is what makes that
|
|
386
|
+
* true of the resources left standing as well, since none of them holds
|
|
387
|
+
* anything in it.
|
|
388
|
+
*
|
|
389
|
+
* The context keeps its state: it is neither draining nor torn down, and no
|
|
390
|
+
* child context is swept, because a child belonging to an unwound import goes
|
|
391
|
+
* down with that import's own inverse exactly as it does at teardown.
|
|
392
|
+
*
|
|
393
|
+
* A name with no live instance is skipped rather than reported — a resource
|
|
394
|
+
* that failed to initialize has nothing to unwind, and a host asking for it is
|
|
395
|
+
* asking about a declaration, not about an instance.
|
|
396
|
+
*/
|
|
397
|
+
unwindResources(names: ReadonlySet<string>): Promise<void>;
|
|
398
|
+
private raiseTeardownFailures;
|
|
399
|
+
/** Unwind the given entries in the order supplied, aggregating what refused.
|
|
400
|
+
* Failures are returned rather than thrown so one refusing inverse cannot
|
|
401
|
+
* strand the resources after it — the log sinks above all. */
|
|
402
|
+
private unwindEach;
|
|
403
|
+
/**
|
|
404
|
+
* Child contexts for the backstop sweep: ascending `teardownPriority`
|
|
405
|
+
* (default 0), reverse registration within a tier.
|
|
406
|
+
*
|
|
407
|
+
* What reaches the sweep is a context no resource's inverse claimed, which in
|
|
408
|
+
* practice is the `lifecycle: shared` libraries. Those are spawned under the
|
|
409
|
+
* ROOT and give no importer a claim, so nothing but this orders them — and
|
|
410
|
+
* reverse registration alone does not, since a singleton is registered when
|
|
411
|
+
* the FIRST import reaches it, which for an import declared inside another
|
|
412
|
+
* library is after that library's own context. `TEARDOWN_LAST` on the context
|
|
413
|
+
* is what keeps a singleton alive until the libraries borrowing it have gone.
|
|
332
414
|
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
* depend on when it happened to be created. A `lifecycle: shared` library is
|
|
337
|
-
* registered when the FIRST import reaches it — which, for an import declared
|
|
338
|
-
* inside another library, is after that library's own context — so reverse
|
|
339
|
-
* registration would tear the singleton down while a borrower still holds it.
|
|
415
|
+
* Its protection now stops at library-against-library. Every resource of the
|
|
416
|
+
* context that owns the sweep has already unwound by the time it runs, which
|
|
417
|
+
* is the ordering the sweep used to invert.
|
|
340
418
|
*/
|
|
341
419
|
private childTeardownOrder;
|
|
342
420
|
/**
|
|
343
|
-
* Resource instances in teardown order: ascending `teardownPriority
|
|
344
|
-
*
|
|
421
|
+
* Resource instances in teardown order: ascending `teardownPriority` as a hard
|
|
422
|
+
* tier, and within a tier a consumer before every resource it holds
|
|
423
|
+
* ({@link reverseTopologicalOrder} over the create-time edges), with reverse
|
|
424
|
+
* insertion as the tiebreak.
|
|
345
425
|
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
* path orders by a declared number rather than sniffing any one subsystem's
|
|
353
|
-
* instance shape.
|
|
426
|
+
* `teardownPriority` is a TIER rather than another edge because it is the
|
|
427
|
+
* author's statement about an edge nothing captured: a log sink is reached
|
|
428
|
+
* through `ctx.log` rather than through a ref slot, so no walk of the manifest
|
|
429
|
+
* can find the resources that will log on the way down. Letting topology
|
|
430
|
+
* reorder across tiers would let one discovered edge override a declaration
|
|
431
|
+
* made precisely because the edges are not all discoverable.
|
|
354
432
|
*/
|
|
355
433
|
private teardownOrder;
|
|
434
|
+
/**
|
|
435
|
+
* Every resource of this context that becomes invalid when `names` do — the
|
|
436
|
+
* named resources plus everything that transitively holds one
|
|
437
|
+
* ({@link impactClosure} over the same create-time edges teardown reads).
|
|
438
|
+
*
|
|
439
|
+
* What a reconciliation unwinds. A cross-module reference projects onto the
|
|
440
|
+
* local `Telo.Import` (`localDependencyNames`), so a change inside an
|
|
441
|
+
* imported library reaches this context as its import being impacted, and the
|
|
442
|
+
* library goes down with that import's own inverse — which is why this
|
|
443
|
+
* answers for one context rather than walking the tree.
|
|
444
|
+
*
|
|
445
|
+
* **`opaque` is what the answer cannot cover.** A name resolved by NAME during
|
|
446
|
+
* initialization ({@link opaquelyRead}) may be held by a resource no edge
|
|
447
|
+
* names, so a closure reaching one is not an answer at all. Those names are
|
|
448
|
+
* reported rather than absorbed: expanding the set to "every resource here"
|
|
449
|
+
* would sweep in the module document, which is not a resource a caller can
|
|
450
|
+
* unwind and re-register, and would present a whole-context rebuild as a
|
|
451
|
+
* narrowing. The caller escalates, and can say which resource forced it.
|
|
452
|
+
*/
|
|
453
|
+
impactedBy(names: Iterable<string>): {
|
|
454
|
+
impacted: Set<string>;
|
|
455
|
+
opaque: string[];
|
|
456
|
+
};
|
|
457
|
+
/** Whether this resource's `run()` has been dispatched. A rebuilt resource
|
|
458
|
+
* that had been started is one nothing will start again — boot targets run
|
|
459
|
+
* once — so a caller reconciling has to escalate rather than leave it
|
|
460
|
+
* constructed and idle. */
|
|
461
|
+
wasStarted(name: string): boolean;
|
|
462
|
+
/** Drop a resource's published reading. A context with no `resources` scope of
|
|
463
|
+
* its own has nothing to drop; `ModuleContext` overrides. */
|
|
464
|
+
clearPublishedReading(name: string): void;
|
|
465
|
+
/** The declaration registered under `name` in THIS context, without the
|
|
466
|
+
* walk up the enclosing chain a name lookup does. */
|
|
467
|
+
declaredManifestFor(name: string): ResourceManifest | undefined;
|
|
468
|
+
/** Replace a declaration in place, without queueing the resource for
|
|
469
|
+
* creation. For a survivor of a reconciliation: its declaration is
|
|
470
|
+
* content-identical by construction, but the object carries fresh loader
|
|
471
|
+
* stamps (`metadata.sourceLine` above all), and a diagnostic anchored on the
|
|
472
|
+
* stale one points at a pre-edit line. */
|
|
473
|
+
refreshManifest(name: string, resource: ResourceManifest): void;
|
|
474
|
+
/** Re-open an initialized context for another initialization pass.
|
|
475
|
+
*
|
|
476
|
+
* The transition is the context's own, not a field a caller assigns: while it
|
|
477
|
+
* is open, a reference to a resource that has not been rebuilt yet must
|
|
478
|
+
* produce the deferral the init loop retries on rather than a hard
|
|
479
|
+
* not-found, and leaving it open after a failed pass turns every later
|
|
480
|
+
* lookup into that deferral with no pass coming. */
|
|
481
|
+
reopenForInitialization(): void;
|
|
482
|
+
/** Close a pass opened by {@link reopenForInitialization} that did not reach
|
|
483
|
+
* the end of `initializeResources`. */
|
|
484
|
+
closeInitialization(): void;
|
|
485
|
+
/** Names a resource resolved by NAME while this context was initializing, so
|
|
486
|
+
* no edge records who is holding them. Read by {@link impactedBy}; exposed so
|
|
487
|
+
* a host can report why a reconciliation could not be narrowed. */
|
|
488
|
+
opaqueReads(): ReadonlySet<string>;
|
|
356
489
|
transientChild(context: Record<string, any>): EvaluationContext;
|
|
357
490
|
/**
|
|
358
491
|
* Invoke a resource by kind and name within this context's resourceInstances.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluation-context.d.ts","sourceRoot":"","sources":["../src/evaluation-context.ts"],"names":[],"mappings":"AAEA,OAAO,EAOL,WAAW,EAEX,KAAK,iBAAiB,IAAI,kBAAkB,EAC5C,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAGlB,KAAK,WAAW,EAChB,KAAK,MAAM,EACZ,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"evaluation-context.d.ts","sourceRoot":"","sources":["../src/evaluation-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,8 +2,9 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
2
2
|
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
|
-
import { evalPathCovers } from "@telorun/analyzer";
|
|
5
|
+
import { celResourceReads, evalPathCovers } from "@telorun/analyzer";
|
|
6
6
|
import { effectOwnerOf, executeReturnedChain } from "./effect-scope.js";
|
|
7
|
+
import { impactClosure, reverseTopologicalOrder } from "./resource-edges.js";
|
|
7
8
|
import { REDACTED, redactSensitive, sensitivePathsOfInstance, } from "./instance-sensitive-paths.js";
|
|
8
9
|
import { classifyInitFailures, isDeferral, renderInitFailureText, summarizeInitFailures, } from "./init-failure-diagnostics.js";
|
|
9
10
|
import { acceptReportedStatus, buildPublishedProps, diagnoseObservedStateAccess, } from "./observed-state.js";
|
|
@@ -521,9 +522,29 @@ export class EvaluationContext {
|
|
|
521
522
|
declaredManifests = new Map();
|
|
522
523
|
/** Per-resource dependency names, captured at create() time — BEFORE Phase-5
|
|
523
524
|
* injection swaps refs for live instances, so the walk sees plain objects and
|
|
524
|
-
* cannot wander into a controller's (possibly cyclic) object graph.
|
|
525
|
-
*
|
|
525
|
+
* cannot wander into a controller's (possibly cyclic) object graph.
|
|
526
|
+
*
|
|
527
|
+
* Read twice, and RETAINED for the context's lifetime because of the second
|
|
528
|
+
* reader: to attribute an init failure to its cause, and to order teardown
|
|
529
|
+
* (`teardownOrder`) so a consumer's inverses run while the resources it holds
|
|
530
|
+
* are still alive. */
|
|
526
531
|
resourceDependencies = new Map();
|
|
532
|
+
/**
|
|
533
|
+
* Names resolved by NAME during initialization, rather than through a
|
|
534
|
+
* declared reference slot — so a resource somebody may be holding, with no
|
|
535
|
+
* edge recording who.
|
|
536
|
+
*
|
|
537
|
+
* The set is of TARGETS, not of pairs: the door that records
|
|
538
|
+
* (`ModuleContext.getInstance`) is reached as `ctx.moduleContext`, which every
|
|
539
|
+
* resource of the module shares, so there is no caller to attribute the read
|
|
540
|
+
* to. That is enough for the one decision that depends on it — see
|
|
541
|
+
* {@link impactedBy}.
|
|
542
|
+
*/
|
|
543
|
+
opaquelyRead = new Set();
|
|
544
|
+
/** Record a by-name resolution. Called by the recording door only. */
|
|
545
|
+
recordOpaqueRead(name) {
|
|
546
|
+
this.opaquelyRead.add(name);
|
|
547
|
+
}
|
|
527
548
|
/**
|
|
528
549
|
* Optional hook called between create() and init() for each resource.
|
|
529
550
|
* Set by the kernel to inject live instances into reference fields.
|
|
@@ -730,6 +751,32 @@ export class EvaluationContext {
|
|
|
730
751
|
this.pendingResources.push(resource);
|
|
731
752
|
this.declaredManifests.set(name, resource);
|
|
732
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Forget a declaration entirely — the inverse of {@link registerManifest}.
|
|
756
|
+
*
|
|
757
|
+
* Reconciliation's other half: {@link unwindResources} disposes the INSTANCE,
|
|
758
|
+
* and this clears everything keyed by the name so the same name can be
|
|
759
|
+
* declared again. Without it `registerManifest` refuses with
|
|
760
|
+
* `ERR_DUPLICATE_RESOURCE`, which is the right answer for a manifest
|
|
761
|
+
* declaring one name twice and the wrong one for a second load of the same
|
|
762
|
+
* manifest.
|
|
763
|
+
*
|
|
764
|
+
* Every per-name record goes, not just the declaration: a resource left in
|
|
765
|
+
* `withheldResources` would be skipped by the init loop for the life of the
|
|
766
|
+
* kernel, and a stale `createdInstances` entry would have the loop initialize
|
|
767
|
+
* the object built from the PREVIOUS declaration.
|
|
768
|
+
*/
|
|
769
|
+
deregisterManifest(name) {
|
|
770
|
+
this.declaredManifests.delete(name);
|
|
771
|
+
this.resourceDependencies.delete(name);
|
|
772
|
+
this.createdInstances.delete(name);
|
|
773
|
+
this.withheldResources.delete(name);
|
|
774
|
+
this.recreatedResources.delete(name);
|
|
775
|
+
this.opaquelyRead.delete(name);
|
|
776
|
+
const pending = this.pendingResources.findIndex((r) => r.metadata?.name === name);
|
|
777
|
+
if (pending >= 0)
|
|
778
|
+
this.pendingResources.splice(pending, 1);
|
|
779
|
+
}
|
|
733
780
|
/**
|
|
734
781
|
* The manifest a name was DECLARED with, resolved scope-local first and then
|
|
735
782
|
* up the enclosing chain — the order `getInstance` and the CEL `resources`
|
|
@@ -890,7 +937,12 @@ export class EvaluationContext {
|
|
|
890
937
|
progress = true;
|
|
891
938
|
const createdRes = created.resource;
|
|
892
939
|
const refs = collectResourceRefs(createdRes);
|
|
893
|
-
|
|
940
|
+
// `resource` rather than `createdRes`: the registered declaration
|
|
941
|
+
// still holds its expressions, while the created copy holds the
|
|
942
|
+
// values they were expanded to.
|
|
943
|
+
this.resourceDependencies.set(name, [
|
|
944
|
+
...new Set([...localDependencyNames(refs), ...celResourceReads(resource)]),
|
|
945
|
+
]);
|
|
894
946
|
const payload = {
|
|
895
947
|
resource: {
|
|
896
948
|
kind: createdRes.kind,
|
|
@@ -1001,9 +1053,6 @@ export class EvaluationContext {
|
|
|
1001
1053
|
await this.publishSnapshot(name);
|
|
1002
1054
|
this.resourceInstances.set(name, { resource, instance });
|
|
1003
1055
|
this.createdInstances.delete(name);
|
|
1004
|
-
// Read only on failure, and this one succeeded — drop it rather than
|
|
1005
|
-
// holding a dep-name array per resource for the context's lifetime.
|
|
1006
|
-
this.resourceDependencies.delete(name);
|
|
1007
1056
|
errors.delete(name);
|
|
1008
1057
|
progress = true;
|
|
1009
1058
|
await this.emit(`${resource.kind}.${resource.metadata.name}.Initialized`, {
|
|
@@ -1173,15 +1222,32 @@ export class EvaluationContext {
|
|
|
1173
1222
|
};
|
|
1174
1223
|
}
|
|
1175
1224
|
/**
|
|
1176
|
-
* Cascade teardown
|
|
1177
|
-
* 1. Tear down
|
|
1178
|
-
*
|
|
1179
|
-
*
|
|
1225
|
+
* Cascade teardown through the tree:
|
|
1226
|
+
* 1. Tear down own resource instances in {@link teardownOrder}, emitting a
|
|
1227
|
+
* Teardown event for each via the injected emit callback.
|
|
1228
|
+
* 2. Sweep any child context still standing, in {@link childTeardownOrder}.
|
|
1229
|
+
*
|
|
1230
|
+
* **Own resources go FIRST, and the child sweep is a backstop.** A child
|
|
1231
|
+
* context that belongs to a resource is torn down by that resource's own
|
|
1232
|
+
* inverse — an import's `init()` returns `child.teardownResources()`, and so
|
|
1233
|
+
* does a template's — so it already unwinds at its owner's position in step 1,
|
|
1234
|
+
* which is the position the edges put it at. Running a child cascade ahead of
|
|
1235
|
+
* step 1 tore every imported library down before any resource of THIS context,
|
|
1236
|
+
* so an app resource holding `!ref Alias.name` unwound after its provider was
|
|
1237
|
+
* already gone, and the owner's inverse then found nothing left to do.
|
|
1238
|
+
*
|
|
1239
|
+
* Sweeping afterwards rather than not at all is what still reclaims a context
|
|
1240
|
+
* no inverse claims: a `lifecycle: shared` library, which is spawned under the
|
|
1241
|
+
* root and deliberately gives no importer a claim on it, and an import whose
|
|
1242
|
+
* `init()` never ran to register one. `teardownResources` is idempotent, so a
|
|
1243
|
+
* context already taken down in step 1 costs the sweep nothing.
|
|
1180
1244
|
*/
|
|
1181
1245
|
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
1182
1246
|
async teardownResources() {
|
|
1183
1247
|
this.state = "Draining";
|
|
1184
|
-
const failures =
|
|
1248
|
+
const failures = await this.unwindEach(this.teardownOrder());
|
|
1249
|
+
// The backstop: whatever no resource's inverse claimed. Everything an import
|
|
1250
|
+
// or a template owns is already down, so this is a no-op for it.
|
|
1185
1251
|
for (const child of this.childTeardownOrder()) {
|
|
1186
1252
|
try {
|
|
1187
1253
|
await child.teardownResources();
|
|
@@ -1190,7 +1256,48 @@ export class EvaluationContext {
|
|
|
1190
1256
|
failures.push({ resource: "(child context)", error: err });
|
|
1191
1257
|
}
|
|
1192
1258
|
}
|
|
1193
|
-
|
|
1259
|
+
this.state = "Teardown";
|
|
1260
|
+
this.raiseTeardownFailures(failures);
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Unwind SOME of this context's resources and leave the rest running.
|
|
1264
|
+
*
|
|
1265
|
+
* The reconciliation half of teardown: a host that has decided which
|
|
1266
|
+
* declarations moved unwinds exactly {@link impactedBy}'s answer, then
|
|
1267
|
+
* re-registers and re-initializes. Ordering is {@link teardownOrder}
|
|
1268
|
+
* restricted to the selection, so a consumer still unwinds before what it
|
|
1269
|
+
* holds — and the selection being closed under holders is what makes that
|
|
1270
|
+
* true of the resources left standing as well, since none of them holds
|
|
1271
|
+
* anything in it.
|
|
1272
|
+
*
|
|
1273
|
+
* The context keeps its state: it is neither draining nor torn down, and no
|
|
1274
|
+
* child context is swept, because a child belonging to an unwound import goes
|
|
1275
|
+
* down with that import's own inverse exactly as it does at teardown.
|
|
1276
|
+
*
|
|
1277
|
+
* A name with no live instance is skipped rather than reported — a resource
|
|
1278
|
+
* that failed to initialize has nothing to unwind, and a host asking for it is
|
|
1279
|
+
* asking about a declaration, not about an instance.
|
|
1280
|
+
*/
|
|
1281
|
+
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
1282
|
+
async unwindResources(names) {
|
|
1283
|
+
const selected = this.teardownOrder().filter(([, entry]) => names.has(entry.resource.metadata.name));
|
|
1284
|
+
this.raiseTeardownFailures(await this.unwindEach(selected));
|
|
1285
|
+
}
|
|
1286
|
+
raiseTeardownFailures(failures) {
|
|
1287
|
+
if (failures.length === 0)
|
|
1288
|
+
return;
|
|
1289
|
+
throw new RuntimeError("ERR_TEARDOWN_FAILED", `${failures.length} resource(s) failed during teardown`, failures.map(({ resource, error }) => ({
|
|
1290
|
+
severity: "error",
|
|
1291
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1292
|
+
resource,
|
|
1293
|
+
})));
|
|
1294
|
+
}
|
|
1295
|
+
/** Unwind the given entries in the order supplied, aggregating what refused.
|
|
1296
|
+
* Failures are returned rather than thrown so one refusing inverse cannot
|
|
1297
|
+
* strand the resources after it — the log sinks above all. */
|
|
1298
|
+
async unwindEach(entries) {
|
|
1299
|
+
const failures = [];
|
|
1300
|
+
for (const [key, { resource, instance }] of entries) {
|
|
1194
1301
|
const label = `${resource.kind}.${resource.metadata.name}`;
|
|
1195
1302
|
// A reading belongs to the run that produced it. The WeakMap would drop it
|
|
1196
1303
|
// with the instance anyway; clearing here also covers an instance something
|
|
@@ -1242,27 +1349,27 @@ export class EvaluationContext {
|
|
|
1242
1349
|
failures.push({ resource: `${label} (Teardown event)`, error: err });
|
|
1243
1350
|
}
|
|
1244
1351
|
this.resourceInstances.delete(key);
|
|
1352
|
+
// A torn-down resource must stop being readable: a CEL expansion that still
|
|
1353
|
+
// found its reading would bake a value nothing is serving any more.
|
|
1354
|
+
this.clearPublishedReading(resource.metadata.name);
|
|
1245
1355
|
}
|
|
1246
|
-
|
|
1247
|
-
if (failures.length > 0) {
|
|
1248
|
-
throw new RuntimeError("ERR_TEARDOWN_FAILED", `${failures.length} resource(s) failed during teardown`, failures.map(({ resource, error }) => ({
|
|
1249
|
-
severity: "error",
|
|
1250
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1251
|
-
resource,
|
|
1252
|
-
})));
|
|
1253
|
-
}
|
|
1356
|
+
return failures;
|
|
1254
1357
|
}
|
|
1255
1358
|
/**
|
|
1256
|
-
* Child contexts
|
|
1257
|
-
*
|
|
1359
|
+
* Child contexts for the backstop sweep: ascending `teardownPriority`
|
|
1360
|
+
* (default 0), reverse registration within a tier.
|
|
1361
|
+
*
|
|
1362
|
+
* What reaches the sweep is a context no resource's inverse claimed, which in
|
|
1363
|
+
* practice is the `lifecycle: shared` libraries. Those are spawned under the
|
|
1364
|
+
* ROOT and give no importer a claim, so nothing but this orders them — and
|
|
1365
|
+
* reverse registration alone does not, since a singleton is registered when
|
|
1366
|
+
* the FIRST import reaches it, which for an import declared inside another
|
|
1367
|
+
* library is after that library's own context. `TEARDOWN_LAST` on the context
|
|
1368
|
+
* is what keeps a singleton alive until the libraries borrowing it have gone.
|
|
1258
1369
|
*
|
|
1259
|
-
*
|
|
1260
|
-
*
|
|
1261
|
-
*
|
|
1262
|
-
* depend on when it happened to be created. A `lifecycle: shared` library is
|
|
1263
|
-
* registered when the FIRST import reaches it — which, for an import declared
|
|
1264
|
-
* inside another library, is after that library's own context — so reverse
|
|
1265
|
-
* registration would tear the singleton down while a borrower still holds it.
|
|
1370
|
+
* Its protection now stops at library-against-library. Every resource of the
|
|
1371
|
+
* context that owns the sweep has already unwound by the time it runs, which
|
|
1372
|
+
* is the ordering the sweep used to invert.
|
|
1266
1373
|
*/
|
|
1267
1374
|
childTeardownOrder() {
|
|
1268
1375
|
return [...this.children]
|
|
@@ -1271,17 +1378,17 @@ export class EvaluationContext {
|
|
|
1271
1378
|
(b.teardownPriority ?? 0));
|
|
1272
1379
|
}
|
|
1273
1380
|
/**
|
|
1274
|
-
* Resource instances in teardown order: ascending `teardownPriority
|
|
1275
|
-
*
|
|
1381
|
+
* Resource instances in teardown order: ascending `teardownPriority` as a hard
|
|
1382
|
+
* tier, and within a tier a consumer before every resource it holds
|
|
1383
|
+
* ({@link reverseTopologicalOrder} over the create-time edges), with reverse
|
|
1384
|
+
* insertion as the tiebreak.
|
|
1276
1385
|
*
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1279
|
-
*
|
|
1280
|
-
*
|
|
1281
|
-
*
|
|
1282
|
-
*
|
|
1283
|
-
* path orders by a declared number rather than sniffing any one subsystem's
|
|
1284
|
-
* instance shape.
|
|
1386
|
+
* `teardownPriority` is a TIER rather than another edge because it is the
|
|
1387
|
+
* author's statement about an edge nothing captured: a log sink is reached
|
|
1388
|
+
* through `ctx.log` rather than through a ref slot, so no walk of the manifest
|
|
1389
|
+
* can find the resources that will log on the way down. Letting topology
|
|
1390
|
+
* reorder across tiers would let one discovered edge override a declaration
|
|
1391
|
+
* made precisely because the edges are not all discoverable.
|
|
1285
1392
|
*/
|
|
1286
1393
|
teardownOrder() {
|
|
1287
1394
|
// A borrowed instance is torn down by the scope that declared it, never
|
|
@@ -1289,10 +1396,93 @@ export class EvaluationContext {
|
|
|
1289
1396
|
const entries = [...this.resourceInstances.entries()]
|
|
1290
1397
|
.filter(([name]) => !this.borrowedResources.has(name))
|
|
1291
1398
|
.reverse();
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1399
|
+
const tiers = new Map();
|
|
1400
|
+
for (const entry of entries) {
|
|
1401
|
+
const priority = entry[1].instance?.teardownPriority ?? 0;
|
|
1402
|
+
const tier = tiers.get(priority);
|
|
1403
|
+
if (tier)
|
|
1404
|
+
tier.push(entry);
|
|
1405
|
+
else
|
|
1406
|
+
tiers.set(priority, [entry]);
|
|
1407
|
+
}
|
|
1408
|
+
return [...tiers.keys()]
|
|
1409
|
+
.sort((a, b) => a - b)
|
|
1410
|
+
.flatMap((priority) => reverseTopologicalOrder(tiers.get(priority), (value) => value.resource.metadata.name, (name) => this.resourceDependencies.get(name)));
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Every resource of this context that becomes invalid when `names` do — the
|
|
1414
|
+
* named resources plus everything that transitively holds one
|
|
1415
|
+
* ({@link impactClosure} over the same create-time edges teardown reads).
|
|
1416
|
+
*
|
|
1417
|
+
* What a reconciliation unwinds. A cross-module reference projects onto the
|
|
1418
|
+
* local `Telo.Import` (`localDependencyNames`), so a change inside an
|
|
1419
|
+
* imported library reaches this context as its import being impacted, and the
|
|
1420
|
+
* library goes down with that import's own inverse — which is why this
|
|
1421
|
+
* answers for one context rather than walking the tree.
|
|
1422
|
+
*
|
|
1423
|
+
* **`opaque` is what the answer cannot cover.** A name resolved by NAME during
|
|
1424
|
+
* initialization ({@link opaquelyRead}) may be held by a resource no edge
|
|
1425
|
+
* names, so a closure reaching one is not an answer at all. Those names are
|
|
1426
|
+
* reported rather than absorbed: expanding the set to "every resource here"
|
|
1427
|
+
* would sweep in the module document, which is not a resource a caller can
|
|
1428
|
+
* unwind and re-register, and would present a whole-context rebuild as a
|
|
1429
|
+
* narrowing. The caller escalates, and can say which resource forced it.
|
|
1430
|
+
*/
|
|
1431
|
+
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
1432
|
+
impactedBy(names) {
|
|
1433
|
+
const impacted = impactClosure(names, this.resourceDependencies);
|
|
1434
|
+
const opaque = [...impacted].filter((name) => this.opaquelyRead.has(name));
|
|
1435
|
+
return { impacted, opaque };
|
|
1436
|
+
}
|
|
1437
|
+
/** Whether this resource's `run()` has been dispatched. A rebuilt resource
|
|
1438
|
+
* that had been started is one nothing will start again — boot targets run
|
|
1439
|
+
* once — so a caller reconciling has to escalate rather than leave it
|
|
1440
|
+
* constructed and idle. */
|
|
1441
|
+
wasStarted(name) {
|
|
1442
|
+
const instance = this.resourceInstances.get(name)?.instance;
|
|
1443
|
+
return instance !== undefined && startedInstances.has(instance);
|
|
1444
|
+
}
|
|
1445
|
+
/** Drop a resource's published reading. A context with no `resources` scope of
|
|
1446
|
+
* its own has nothing to drop; `ModuleContext` overrides. */
|
|
1447
|
+
clearPublishedReading(name) {
|
|
1448
|
+
void name;
|
|
1449
|
+
}
|
|
1450
|
+
/** The declaration registered under `name` in THIS context, without the
|
|
1451
|
+
* walk up the enclosing chain a name lookup does. */
|
|
1452
|
+
declaredManifestFor(name) {
|
|
1453
|
+
return this.declaredManifests.get(name);
|
|
1454
|
+
}
|
|
1455
|
+
/** Replace a declaration in place, without queueing the resource for
|
|
1456
|
+
* creation. For a survivor of a reconciliation: its declaration is
|
|
1457
|
+
* content-identical by construction, but the object carries fresh loader
|
|
1458
|
+
* stamps (`metadata.sourceLine` above all), and a diagnostic anchored on the
|
|
1459
|
+
* stale one points at a pre-edit line. */
|
|
1460
|
+
refreshManifest(name, resource) {
|
|
1461
|
+
if (this.declaredManifests.has(name))
|
|
1462
|
+
this.declaredManifests.set(name, resource);
|
|
1463
|
+
}
|
|
1464
|
+
/** Re-open an initialized context for another initialization pass.
|
|
1465
|
+
*
|
|
1466
|
+
* The transition is the context's own, not a field a caller assigns: while it
|
|
1467
|
+
* is open, a reference to a resource that has not been rebuilt yet must
|
|
1468
|
+
* produce the deferral the init loop retries on rather than a hard
|
|
1469
|
+
* not-found, and leaving it open after a failed pass turns every later
|
|
1470
|
+
* lookup into that deferral with no pass coming. */
|
|
1471
|
+
reopenForInitialization() {
|
|
1472
|
+
if (this.state === "Initialized")
|
|
1473
|
+
this.state = "Validated";
|
|
1474
|
+
}
|
|
1475
|
+
/** Close a pass opened by {@link reopenForInitialization} that did not reach
|
|
1476
|
+
* the end of `initializeResources`. */
|
|
1477
|
+
closeInitialization() {
|
|
1478
|
+
if (this.state === "Validated")
|
|
1479
|
+
this.state = "Initialized";
|
|
1480
|
+
}
|
|
1481
|
+
/** Names a resource resolved by NAME while this context was initializing, so
|
|
1482
|
+
* no edge records who is holding them. Read by {@link impactedBy}; exposed so
|
|
1483
|
+
* a host can report why a reconciliation could not be narrowed. */
|
|
1484
|
+
opaqueReads() {
|
|
1485
|
+
return this.opaquelyRead;
|
|
1296
1486
|
}
|
|
1297
1487
|
transientChild(context) {
|
|
1298
1488
|
return new EvaluationContext(this.source, { ...this.context, ...context }, this._createInstance, this._secretValues, this.emit);
|