@telorun/kernel 0.31.0 → 0.33.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.
@@ -6,5 +6,5 @@ import { ResourceInstance, ResourceManifest } from "@telorun/sdk";
6
6
  * returned live ResourceInstance. Values where getInstance returns undefined are
7
7
  * left unchanged.
8
8
  */
9
- export declare function injectAtPath(resource: ResourceManifest, fieldPath: string, getInstance: (name: string, alias?: string) => ResourceInstance | undefined): void;
9
+ export declare function injectAtPath(resource: ResourceManifest, fieldPath: string, getInstance: (name: string, alias?: string) => ResourceInstance | undefined, isPending?: (name: string) => boolean): void;
10
10
  //# sourceMappingURL=dependency-injection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-injection.d.ts","sourceRoot":"","sources":["../src/dependency-injection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAgB,MAAM,cAAc,CAAC;AAEhF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,gBAAgB,EAC1B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,gBAAgB,GAAG,SAAS,GAC1E,IAAI,CAkFN"}
1
+ {"version":3,"file":"dependency-injection.d.ts","sourceRoot":"","sources":["../src/dependency-injection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAkC,MAAM,cAAc,CAAC;AAElG;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,gBAAgB,EAC1B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,gBAAgB,GAAG,SAAS,EAC3E,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GACpC,IAAI,CAmGN"}
@@ -1,4 +1,4 @@
1
- import { RuntimeError } from "@telorun/sdk";
1
+ import { RuntimeError, stampRefIdentity } from "@telorun/sdk";
2
2
  /**
3
3
  * Walks `resource` following `fieldPath` (dot notation, `[]` = array traversal,
4
4
  * `{}` = map traversal). For each leaf value that looks like a {kind, name}
@@ -6,20 +6,34 @@ import { RuntimeError } from "@telorun/sdk";
6
6
  * returned live ResourceInstance. Values where getInstance returns undefined are
7
7
  * left unchanged.
8
8
  */
9
- export function injectAtPath(resource, fieldPath, getInstance) {
9
+ export function injectAtPath(resource, fieldPath, getInstance, isPending) {
10
10
  const parts = fieldPath.split(".");
11
11
  // Resolve a {kind, name, alias?} reference to its live instance. A non-`Self` alias is a
12
12
  // cross-module reference into an import's published exports; if that import hasn't
13
13
  // finished init() yet the instance is absent, so we throw to defer this resource to a
14
14
  // later pass of the multi-pass init loop (which catches and retries) rather than leaving
15
- // the ref unresolved. Local refs (no alias) that miss are left for topo ordering / later
16
- // diagnostics, matching prior behaviour.
15
+ // the ref unresolved. A LOCAL ref (no alias) that names a resource registered in this
16
+ // context but not yet initialized is deferred the same way — create-success order does
17
+ // not always match init order (e.g. a globally-registered controller lets a dependent
18
+ // create before its dependency's controller has loaded), so injection can run before the
19
+ // dependency inits. Without this defer the slot would be left unresolved and surface as a
20
+ // runtime ERR_RESOURCE_NOT_INVOKABLE. A local ref that names nothing pending is left as-is
21
+ // (topo ordering / later diagnostics), matching prior behaviour.
17
22
  function resolveInto(ref) {
18
23
  const alias = typeof ref.alias === "string" ? ref.alias : undefined;
19
24
  const instance = getInstance(ref.name, alias);
20
25
  if (!instance && alias && alias !== "Self") {
21
26
  throw new RuntimeError("ERR_CROSS_MODULE_REF_PENDING", `Cross-module reference '${alias}.${String(ref.name)}' is not available yet (import not initialized)`);
22
27
  }
28
+ if (!instance && (!alias || alias === "Self") && isPending?.(ref.name)) {
29
+ throw new RuntimeError("ERR_LOCAL_REF_PENDING", `Local reference '${String(ref.name)}' is registered but not initialized yet (deferring to a later init pass)`);
30
+ }
31
+ // Tag the instance with the kind+name it resolved from, so a consumer that
32
+ // holds only the bare instance (an invoke-step target) can dispatch it
33
+ // through the traced chokepoint rather than calling `.invoke()` directly.
34
+ if (instance && typeof ref.kind === "string" && typeof ref.name === "string") {
35
+ stampRefIdentity(instance, ref.kind, ref.name);
36
+ }
23
37
  return instance;
24
38
  }
25
39
  function traverse(obj, partsLeft) {
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-injection.js","sourceRoot":"","sources":["../src/dependency-injection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dependency-injection.js","sourceRoot":"","sources":["../src/dependency-injection.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { resourceKey, type EvaluationContext as IEvaluationContext, type EmitEvent, type InstanceFactory, type InvokeContext, type LifecycleState, type PreInitHook, type ResourceDefinition, type ResourceInstance, type ResourceManifest, type ScopeHandle, type Tracer } from "@telorun/sdk";
1
+ import { resourceKey, type EvaluationContext as IEvaluationContext, type EmitEvent, type InstanceFactory, type InvokeContext, type LifecycleState, type OpenSpan, type OpenSpanOptions, type PreInitHook, type ResourceDefinition, type ResourceInstance, type ResourceManifest, type ScopeHandle, type Tracer } from "@telorun/sdk";
2
2
  export { resourceKey };
3
3
  /**
4
4
  * Base class for all evaluation contexts. Owns template
@@ -135,9 +135,66 @@ export declare class EvaluationContext implements IEvaluationContext {
135
135
  * regardless of which path reached the instance.
136
136
  */
137
137
  invokeResolved<TInputs>(kind: string, name: string, instance: ResourceInstance, inputs: TInputs, ctx?: InvokeContext): Promise<any>;
138
+ /**
139
+ * Build the structured trace payload every capability dispatch emits. The
140
+ * event *name* stays human-meaningful (`<name>.Invoked`) for bus subscribers;
141
+ * everything a debug consumer needs to rebuild the call tree rides here, so the
142
+ * consumer never parses the dotted name. `spanId`/`parentSpanId` are the
143
+ * tracer's `invocationId`/`parentInvocationId` (present only while tracing);
144
+ * `ref` carries the kind+name the name no longer encodes; `detail` is the
145
+ * per-capability data (inputs/outputs, error fields, cancellation reason).
146
+ */
147
+ /**
148
+ * A redacted snapshot of the CEL root scope a debug consumer should see for a
149
+ * trace — `variables`, masked `secrets`, resource `snapshots`, `ports`. Attached
150
+ * to a trace's *root* span so the consumer can inspect what data the execution
151
+ * could reference (beyond its own inputs/outputs). Only a `ModuleContext` owns a
152
+ * root scope; child scopes return undefined. Host `env` is deliberately omitted
153
+ * (it is the raw process environment — too broad/sensitive to dump).
154
+ */
155
+ protected traceRootScope(): Record<string, unknown> | undefined;
156
+ protected tracePayload(kind: string, name: string, spanId: number | undefined, parentSpanId: number | undefined, traceId: string | undefined, capability: "invoke" | "run" | "provide" | "request", phase: "start" | "end", outcome: "ok" | "failed" | "rejected" | "cancelled" | undefined, detail: Record<string, unknown>): Record<string, unknown>;
138
157
  private runInvoke;
158
+ /**
159
+ * The declared capability of a kind, or undefined if unknown. Definitions are
160
+ * keyed by their canonical `<module>.<Kind>`, but a resource carries the alias
161
+ * kind it was written with (`Http.Server`), so resolve the alias first.
162
+ * Best-effort: `resolveKind` exists only on a `ModuleContext` and throws for
163
+ * unqualified / ungated kinds, so guard and fall back to the raw kind.
164
+ */
165
+ /** Resolve an alias kind (`Http.Server`) to its canonical `<module>.<Kind>`.
166
+ * Only a `ModuleContext` carries the import-alias table; the base returns the
167
+ * kind unchanged. A typed seam (overridden in `ModuleContext`), matching the
168
+ * `traceRootScope()` pattern, rather than reaching across the boundary. */
169
+ protected resolveKindSafe(kind: string): string;
170
+ private capabilityOf;
139
171
  private getDeclaredThrowCodes;
140
172
  run(name: string, ctx?: InvokeContext): Promise<void>;
173
+ /**
174
+ * Like run(), but the caller has already resolved the instance (e.g. a
175
+ * Phase-5-injected `!ref` boot target). Shares the single span-emitting path so
176
+ * a pre-resolved runnable is instrumented exactly like a by-name dispatch
177
+ * instead of escaping the chokepoint with a direct `instance.run()` call.
178
+ */
179
+ runResolved(kind: string, name: string, instance: ResourceInstance, ctx?: InvokeContext): Promise<void>;
180
+ /**
181
+ * Open a trace span for an inbound boundary (an HTTP request). Mints a span
182
+ * that roots a fresh trace (or continues `opts.inbound`), emits its `start`,
183
+ * and returns a child context to thread into `invokeResolved` so the handler
184
+ * nests under it. A no-op pass-through when tracing is off.
185
+ */
186
+ openSpan(base: InvokeContext | undefined, opts: OpenSpanOptions): Promise<OpenSpan>;
187
+ private runInstance;
188
+ /**
189
+ * Run `fn` detached from the caller's cancellation/trace scope. The current
190
+ * ambient `InvokeContext` (a request's token + span) is replaced with the
191
+ * uncancellable root, so request-scope teardown cannot abort the work and it
192
+ * does not nest under the request's trace. This is the bare scope primitive —
193
+ * tracking/draining a detached task is the owning resource's concern (the
194
+ * per-resource `ResourceContext` records the task and drains it in the
195
+ * resource's teardown).
196
+ */
197
+ runDetached<T>(fn: () => Promise<T>): Promise<T>;
141
198
  /**
142
199
  * Expand a value that may contain precompiled ${{ }} templates.
143
200
  *
@@ -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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"}
@@ -279,7 +279,7 @@ export class EvaluationContext {
279
279
  if (this.preInitHook) {
280
280
  this.preInitHook(resource, (n, alias) => alias && alias !== "Self"
281
281
  ? this.resolveImportedInstance(alias, n)
282
- : this.resourceInstances.get(n)?.instance);
282
+ : this.resourceInstances.get(n)?.instance, (n) => this.hasManifest(n) && !this.resourceInstances.has(n));
283
283
  }
284
284
  if (instance.init)
285
285
  await instance.init(ctx);
@@ -371,10 +371,14 @@ export class EvaluationContext {
371
371
  // Propagate injection hook: extend getInstance to also resolve parent singleton instances.
372
372
  if (parent.preInitHook) {
373
373
  const parentHook = parent.preInitHook;
374
- child.preInitHook = (resource, childGetInstance) => {
374
+ child.preInitHook = (resource, childGetInstance, childIsPending) => {
375
375
  parentHook(resource, (name, alias) => alias && alias !== "Self"
376
376
  ? parent.resolveImportedInstance(alias, name)
377
- : childGetInstance(name) ?? parent.resourceInstances.get(name)?.instance);
377
+ : childGetInstance(name) ?? parent.resourceInstances.get(name)?.instance,
378
+ // A scoped ref resolves against the scope's own resources or an already-inited
379
+ // outer one; only a scope-local dependency can still be pending. The outer is
380
+ // live by the time a scope opens, so the child's own predicate suffices.
381
+ childIsPending);
378
382
  };
379
383
  }
380
384
  try {
@@ -471,6 +475,38 @@ export class EvaluationContext {
471
475
  }
472
476
  return this.runInvoke(kind, name, instance, inputs, ctx);
473
477
  }
478
+ /**
479
+ * Build the structured trace payload every capability dispatch emits. The
480
+ * event *name* stays human-meaningful (`<name>.Invoked`) for bus subscribers;
481
+ * everything a debug consumer needs to rebuild the call tree rides here, so the
482
+ * consumer never parses the dotted name. `spanId`/`parentSpanId` are the
483
+ * tracer's `invocationId`/`parentInvocationId` (present only while tracing);
484
+ * `ref` carries the kind+name the name no longer encodes; `detail` is the
485
+ * per-capability data (inputs/outputs, error fields, cancellation reason).
486
+ */
487
+ /**
488
+ * A redacted snapshot of the CEL root scope a debug consumer should see for a
489
+ * trace — `variables`, masked `secrets`, resource `snapshots`, `ports`. Attached
490
+ * to a trace's *root* span so the consumer can inspect what data the execution
491
+ * could reference (beyond its own inputs/outputs). Only a `ModuleContext` owns a
492
+ * root scope; child scopes return undefined. Host `env` is deliberately omitted
493
+ * (it is the raw process environment — too broad/sensitive to dump).
494
+ */
495
+ traceRootScope() {
496
+ return undefined;
497
+ }
498
+ tracePayload(kind, name, spanId, parentSpanId, traceId, capability, phase, outcome, detail) {
499
+ return {
500
+ traceId,
501
+ spanId,
502
+ parentSpanId,
503
+ capability,
504
+ phase,
505
+ ...(outcome !== undefined ? { outcome } : {}),
506
+ ref: { kind, name },
507
+ ...detail,
508
+ };
509
+ }
474
510
  async runInvoke(kind, name, instance, inputs, ctx) {
475
511
  // Explicit seed (trigger / embedder) wins; otherwise inherit the ambient
476
512
  // tree token; otherwise open a fresh, never-cancellable scope. The token
@@ -479,28 +515,40 @@ export class EvaluationContext {
479
515
  const baseCtx = ctx ?? ambient ?? UNCANCELLABLE_CONTEXT;
480
516
  const token = baseCtx.cancellation;
481
517
  // Tracing gate (a debug consumer is attached): mint a monotonic id for this
482
- // invocation, parent it to the ambient one, and ride both on every event's
483
- // `metadata` so the consumer can rebuild the call tree. Off by default —
484
- // `tracedCtx` stays `baseCtx` and the fast `=== ambient` skip is preserved.
518
+ // invocation, parent it to the ambient one, and ride both in every event's
519
+ // payload so the consumer can rebuild the call tree. Off by default —
520
+ // `invokeCtx` stays `baseCtx` and the fast `=== ambient` skip is preserved.
485
521
  const tracing = this.tracer?.enabled === true;
486
522
  const invocationId = tracing ? this.tracer.next() : undefined;
487
523
  // Parent precedence mirrors the token: an explicit seed `ctx` wins, then the
488
524
  // ambient (ALS) invocation. A caller threading its own `ctx.invocationId` thus
489
525
  // has it honored as the parent, not silently dropped for the ALS value.
490
526
  const parentInvocationId = ctx?.invocationId ?? ambient?.invocationId;
491
- const meta = tracing ? { invocationId, parentInvocationId } : undefined;
527
+ // Inherit the trace from the parent (explicit ctx wins, then ambient); mint a
528
+ // fresh one only at a root. Carried on every span so an OTel exporter groups
529
+ // the trace without walking the parent chain.
530
+ const traceId = tracing
531
+ ? (ctx?.traceId ?? ambient?.traceId ?? this.tracer.newTraceId())
532
+ : undefined;
533
+ // Capture the root CEL scope once, on the trace's root span's terminal event.
534
+ const rootScope = tracing && parentInvocationId === undefined ? this.traceRootScope() : undefined;
535
+ const span = (phase, outcome, detail) => this.tracePayload(kind, name, invocationId, parentInvocationId, traceId, "invoke", phase, outcome, phase === "end" && rootScope ? { ...detail, context: rootScope } : detail);
492
536
  // When tracing, a fresh context carries the new id down the tree so nested
493
537
  // invokes read it as their parent; it is never `=== ambient`, so the call
494
538
  // always (re)establishes the ALS scope.
495
539
  const invokeCtx = tracing
496
- ? { cancellation: token, invocationId, parentInvocationId }
540
+ ? { cancellation: token, invocationId, parentInvocationId, traceId }
497
541
  : baseCtx;
498
542
  // Pre-dispatch gate: a sub-invoke reached after the tree was cancelled is
499
543
  // refused without ever touching the controller.
500
544
  if (token.isCancelled) {
501
- await this.emit(`${kind}.${name}.InvokeCancelled`, { inputs, reason: token.reason }, meta);
545
+ await this.emit(`${name}.InvokeCancelled`, span("end", "cancelled", { inputs, reason: token.reason }));
502
546
  throw new RuntimeError("ERR_INVOKE_CANCELLED", `Invoke ${kind}.${name} was cancelled${token.reason ? `: ${token.reason}` : ""}`);
503
547
  }
548
+ // Start span — only under tracing, so non-traced behaviour stays exactly
549
+ // one terminal event per call (subscribers to `<name>.Invoked` are unaffected).
550
+ if (tracing)
551
+ await this.emit(`${name}.Invoking`, span("start", undefined, { inputs }));
504
552
  try {
505
553
  // Only (re)establish the ALS scope when the token differs from the ambient
506
554
  // one — nested invokes that inherited it skip the redundant `run`.
@@ -515,7 +563,7 @@ export class EvaluationContext {
515
563
  const outputs = await (invokeCtx === ambient
516
564
  ? call()
517
565
  : cancellationStore.run(invokeCtx, call));
518
- await this.emit(`${kind}.${name}.Invoked`, { inputs, outputs }, meta);
566
+ await this.emit(`${name}.Invoked`, span("end", "ok", { inputs, outputs }));
519
567
  return outputs;
520
568
  }
521
569
  catch (err) {
@@ -523,23 +571,23 @@ export class EvaluationContext {
523
571
  // observable event family rather than masquerading as a rejection/failure.
524
572
  if (isCancellationError(err)) {
525
573
  const reason = err instanceof Error ? err.message : String(err);
526
- await this.emit(`${kind}.${name}.InvokeCancelled`, { inputs, reason }, meta);
574
+ await this.emit(`${name}.InvokeCancelled`, span("end", "cancelled", { inputs, reason }));
527
575
  throw err;
528
576
  }
529
577
  if (isInvokeError(err)) {
530
- const payload = { inputs, code: err.code, message: err.message, data: err.data };
531
- await this.emit(`${kind}.${name}.InvokeRejected`, payload, meta);
578
+ const detail = { inputs, code: err.code, message: err.message, data: err.data };
579
+ await this.emit(`${name}.InvokeRejected`, span("end", "rejected", detail));
532
580
  const declaredCodes = this.getDeclaredThrowCodes(kind);
533
581
  if (declaredCodes && !declaredCodes.has(err.code)) {
534
- await this.emit(`${kind}.${name}.InvokeRejected.Undeclared`, payload, meta);
582
+ await this.emit(`${name}.InvokeRejected.Undeclared`, span("end", "rejected", detail));
535
583
  }
536
584
  throw err;
537
585
  }
538
586
  if (err instanceof Error) {
539
- await this.emit(`${kind}.${name}.InvokeFailed`, { inputs, name: err.name, message: err.message }, meta);
587
+ await this.emit(`${name}.InvokeFailed`, span("end", "failed", { inputs, name: err.name, message: err.message }));
540
588
  }
541
589
  else {
542
- await this.emit(`${kind}.${name}.InvokeFailed`, { inputs, name: "UnknownError", message: String(err) }, meta);
590
+ await this.emit(`${name}.InvokeFailed`, span("end", "failed", { inputs, name: "UnknownError", message: String(err) }));
543
591
  }
544
592
  // Already enriched at an inner invoke: keep the innermost (most
545
593
  // specific) resource as the failure location.
@@ -560,6 +608,24 @@ export class EvaluationContext {
560
608
  throw wrapped;
561
609
  }
562
610
  }
611
+ /**
612
+ * The declared capability of a kind, or undefined if unknown. Definitions are
613
+ * keyed by their canonical `<module>.<Kind>`, but a resource carries the alias
614
+ * kind it was written with (`Http.Server`), so resolve the alias first.
615
+ * Best-effort: `resolveKind` exists only on a `ModuleContext` and throws for
616
+ * unqualified / ungated kinds, so guard and fall back to the raw kind.
617
+ */
618
+ /** Resolve an alias kind (`Http.Server`) to its canonical `<module>.<Kind>`.
619
+ * Only a `ModuleContext` carries the import-alias table; the base returns the
620
+ * kind unchanged. A typed seam (overridden in `ModuleContext`), matching the
621
+ * `traceRootScope()` pattern, rather than reaching across the boundary. */
622
+ resolveKindSafe(kind) {
623
+ return kind;
624
+ }
625
+ capabilityOf(kind) {
626
+ const resolved = this.resolveKindSafe(kind);
627
+ return this.getDefinition?.(resolved)?.capability ?? this.getDefinition?.(kind)?.capability;
628
+ }
563
629
  getDeclaredThrowCodes(kind) {
564
630
  if (!this.getDefinition)
565
631
  return null;
@@ -582,22 +648,131 @@ export class EvaluationContext {
582
648
  }
583
649
  async run(name, ctx) {
584
650
  const entry = this.resourceInstances.get(name);
585
- if (entry && typeof entry.instance.run === "function") {
586
- const ambient = cancellationStore.getStore();
587
- const invokeCtx = ctx ?? ambient ?? UNCANCELLABLE_CONTEXT;
588
- const token = invokeCtx.cancellation;
589
- // Refuse a target reached after the boot run was cancelled.
590
- if (token.isCancelled) {
591
- await this.emit(`${entry.resource.kind}.${name}.RunCancelled`, { reason: token.reason });
592
- throw new RuntimeError("ERR_INVOKE_CANCELLED", `Run ${entry.resource.kind}.${name} was cancelled${token.reason ? `: ${token.reason}` : ""}`);
651
+ if (!(entry && typeof entry.instance.run === "function")) {
652
+ throw new RuntimeError("ERR_RESOURCE_NOT_RUNNABLE", `Resource ${name} is not runnable or not found. Available resources: ${[...this.resourceInstances.keys()].join(", ")}`);
653
+ }
654
+ return this.runInstance(entry.resource.kind, name, entry.instance, ctx);
655
+ }
656
+ /**
657
+ * Like run(), but the caller has already resolved the instance (e.g. a
658
+ * Phase-5-injected `!ref` boot target). Shares the single span-emitting path so
659
+ * a pre-resolved runnable is instrumented exactly like a by-name dispatch
660
+ * instead of escaping the chokepoint with a direct `instance.run()` call.
661
+ */
662
+ async runResolved(kind, name, instance, ctx) {
663
+ if (typeof instance.run !== "function") {
664
+ throw new RuntimeError("ERR_RESOURCE_NOT_RUNNABLE", `Resource ${kind}.${name} does not have a run method`);
665
+ }
666
+ return this.runInstance(kind, name, instance, ctx);
667
+ }
668
+ /**
669
+ * Open a trace span for an inbound boundary (an HTTP request). Mints a span
670
+ * that roots a fresh trace (or continues `opts.inbound`), emits its `start`,
671
+ * and returns a child context to thread into `invokeResolved` so the handler
672
+ * nests under it. A no-op pass-through when tracing is off.
673
+ */
674
+ async openSpan(base, opts) {
675
+ const ctx = base ?? UNCANCELLABLE_CONTEXT;
676
+ if (this.tracer?.enabled !== true) {
677
+ return { context: ctx, settle: async () => { } };
678
+ }
679
+ const spanId = this.tracer.next();
680
+ const traceId = opts.inbound?.traceId ?? this.tracer.newTraceId();
681
+ const parentSpanId = opts.inbound?.parentSpanId;
682
+ // A root request span (not continuing an upstream trace) carries the root scope.
683
+ const rootScope = parentSpanId === undefined ? this.traceRootScope() : undefined;
684
+ const detail = {
685
+ ...(opts.label !== undefined ? { label: opts.label } : {}),
686
+ ...(opts.attributes !== undefined ? { attributes: opts.attributes } : {}),
687
+ };
688
+ const payload = (phase, outcome, extra = {}) => this.tracePayload(opts.ref.kind, opts.ref.name, spanId, parentSpanId, traceId, "request", phase, outcome, { ...detail, ...extra });
689
+ await this.emit(`${opts.ref.name}.Requesting`, payload("start", undefined));
690
+ const context = {
691
+ cancellation: ctx.cancellation,
692
+ invocationId: spanId,
693
+ parentInvocationId: parentSpanId,
694
+ traceId,
695
+ };
696
+ let settled = false;
697
+ return {
698
+ context,
699
+ settle: async (outcome, extra) => {
700
+ if (settled)
701
+ return;
702
+ settled = true;
703
+ await this.emit(`${opts.ref.name}.Request`, payload("end", outcome, rootScope ? { ...extra, context: rootScope } : extra));
704
+ },
705
+ };
706
+ }
707
+ async runInstance(kind, name, instance, ctx) {
708
+ const ambient = cancellationStore.getStore();
709
+ const baseCtx = ctx ?? ambient ?? UNCANCELLABLE_CONTEXT;
710
+ const token = baseCtx.cancellation;
711
+ // A long-lived Service's `run()` is not a one-shot dispatch: it stays pending
712
+ // for the process lifetime, so wrapping it in the cancellation/trace ALS scope
713
+ // would leak that scope onto every async resource the service creates (e.g. an
714
+ // HTTP server's listening socket → every inbound request callback). Such a
715
+ // service must NOT establish an ambient: its token reaches it via the explicit
716
+ // `run(invokeCtx)` argument (how it observes shutdown), and its externally
717
+ // triggered work then starts with a clean ambient — separate traces, no
718
+ // inherited cancellation. Runnables (one-shot, e.g. `Run.Sequence`) keep the
719
+ // ALS scope so their steps nest and inherit cancellation.
720
+ const isService = this.capabilityOf(kind) === "Telo.Service";
721
+ // Span instrumentation mirrors `runInvoke` — minting an id here makes
722
+ // Runnables (a `Run.Sequence` boot target) appear in the trace and re-parents
723
+ // their nested invokes. A long-lived Service emits only the `start` span (its
724
+ // `run()` resolves at teardown), the "running" signal a debug consumer wants.
725
+ const tracing = this.tracer?.enabled === true;
726
+ const invocationId = tracing ? this.tracer.next() : undefined;
727
+ const parentInvocationId = ctx?.invocationId ?? ambient?.invocationId;
728
+ const traceId = tracing
729
+ ? (ctx?.traceId ?? ambient?.traceId ?? this.tracer.newTraceId())
730
+ : undefined;
731
+ const rootScope = tracing && parentInvocationId === undefined ? this.traceRootScope() : undefined;
732
+ const span = (phase, outcome, detail) => this.tracePayload(kind, name, invocationId, parentInvocationId, traceId, "run", phase, outcome, phase === "end" && rootScope ? { ...detail, context: rootScope } : detail);
733
+ const invokeCtx = tracing
734
+ ? { cancellation: token, invocationId, parentInvocationId, traceId }
735
+ : baseCtx;
736
+ // Refuse a target reached after the boot run was cancelled.
737
+ if (token.isCancelled) {
738
+ await this.emit(`${name}.RunCancelled`, span("end", "cancelled", { reason: token.reason }));
739
+ throw new RuntimeError("ERR_INVOKE_CANCELLED", `Run ${kind}.${name} was cancelled${token.reason ? `: ${token.reason}` : ""}`);
740
+ }
741
+ if (tracing)
742
+ await this.emit(`${name}.Running`, span("start", undefined, {}));
743
+ try {
744
+ // Runnable: run inside the ALS scope so nested invokes inherit the token and
745
+ // trace id (skip the redundant `run` when the token is already ambient).
746
+ // Service: call directly with the explicit context and NO ambient scope, so
747
+ // its long-lived async work does not capture this scope.
748
+ const call = () => instance.run(invokeCtx);
749
+ await (isService || invokeCtx === ambient ? call() : cancellationStore.run(invokeCtx, call));
750
+ await this.emit(`${name}.Run`, span("end", "ok", {}));
751
+ }
752
+ catch (err) {
753
+ if (isCancellationError(err)) {
754
+ const reason = err instanceof Error ? err.message : String(err);
755
+ await this.emit(`${name}.RunCancelled`, span("end", "cancelled", { reason }));
756
+ throw err;
593
757
  }
594
- // Run inside the scope so the runnable's nested invokes inherit the token,
595
- // and pass it explicitly so long-lived targets can observe cancellation.
596
- // Skip the redundant `run` when the token is already the ambient one.
597
- const call = () => entry.instance.run(invokeCtx);
598
- return invokeCtx === ambient ? call() : cancellationStore.run(invokeCtx, call);
758
+ const detail = err instanceof Error
759
+ ? { name: err.name, message: err.message }
760
+ : { name: "UnknownError", message: String(err) };
761
+ await this.emit(`${name}.RunFailed`, span("end", "failed", detail));
762
+ throw err;
599
763
  }
600
- throw new RuntimeError("ERR_RESOURCE_NOT_RUNNABLE", `Resource ${name} is not runnable or not found. Available resources: ${[...this.resourceInstances.keys()].join(", ")}`);
764
+ }
765
+ /**
766
+ * Run `fn` detached from the caller's cancellation/trace scope. The current
767
+ * ambient `InvokeContext` (a request's token + span) is replaced with the
768
+ * uncancellable root, so request-scope teardown cannot abort the work and it
769
+ * does not nest under the request's trace. This is the bare scope primitive —
770
+ * tracking/draining a detached task is the owning resource's concern (the
771
+ * per-resource `ResourceContext` records the task and drains it in the
772
+ * resource's teardown).
773
+ */
774
+ runDetached(fn) {
775
+ return cancellationStore.run(UNCANCELLABLE_CONTEXT, fn);
601
776
  }
602
777
  /**
603
778
  * Expand a value that may contain precompiled ${{ }} templates.