@telorun/kernel 0.66.0 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/application-env.d.ts +25 -0
  2. package/dist/application-env.d.ts.map +1 -1
  3. package/dist/application-env.js +79 -3
  4. package/dist/application-env.js.map +1 -1
  5. package/dist/controller-loader.d.ts +7 -5
  6. package/dist/controller-loader.d.ts.map +1 -1
  7. package/dist/controller-loader.js +7 -5
  8. package/dist/controller-loader.js.map +1 -1
  9. package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
  10. package/dist/controllers/resource-definition/resource-definition-controller.js +29 -8
  11. package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
  12. package/dist/evaluation-context.d.ts +6 -0
  13. package/dist/evaluation-context.d.ts.map +1 -1
  14. package/dist/evaluation-context.js +20 -8
  15. package/dist/evaluation-context.js.map +1 -1
  16. package/dist/kernel.d.ts.map +1 -1
  17. package/dist/kernel.js +32 -3
  18. package/dist/kernel.js.map +1 -1
  19. package/dist/manifest-schemas.d.ts +1 -1
  20. package/dist/manifest-schemas.d.ts.map +1 -1
  21. package/dist/manifest-schemas.js +6 -0
  22. package/dist/manifest-schemas.js.map +1 -1
  23. package/dist/module-context.d.ts.map +1 -1
  24. package/dist/module-context.js +3 -4
  25. package/dist/module-context.js.map +1 -1
  26. package/dist/resource-context.d.ts +21 -20
  27. package/dist/resource-context.d.ts.map +1 -1
  28. package/dist/resource-context.js +71 -51
  29. package/dist/resource-context.js.map +1 -1
  30. package/dist/resource-handle.d.ts +11 -0
  31. package/dist/resource-handle.d.ts.map +1 -0
  32. package/dist/resource-handle.js +32 -0
  33. package/dist/resource-handle.js.map +1 -0
  34. package/dist/runtime-seam.d.ts.map +1 -1
  35. package/dist/runtime-seam.js +8 -2
  36. package/dist/runtime-seam.js.map +1 -1
  37. package/dist/schema-compiled-values.d.ts.map +1 -1
  38. package/dist/schema-compiled-values.js +39 -4
  39. package/dist/schema-compiled-values.js.map +1 -1
  40. package/dist/schema-validator.d.ts +21 -1
  41. package/dist/schema-validator.d.ts.map +1 -1
  42. package/dist/schema-validator.js +88 -6
  43. package/dist/schema-validator.js.map +1 -1
  44. package/dist/type-field-schema.d.ts +21 -0
  45. package/dist/type-field-schema.d.ts.map +1 -0
  46. package/dist/type-field-schema.js +54 -0
  47. package/dist/type-field-schema.js.map +1 -0
  48. package/dist/zone-context.d.ts +94 -0
  49. package/dist/zone-context.d.ts.map +1 -0
  50. package/dist/zone-context.js +272 -0
  51. package/dist/zone-context.js.map +1 -0
  52. package/package.json +4 -4
  53. package/src/application-env.ts +93 -4
  54. package/src/controller-loader.ts +7 -5
  55. package/src/controllers/resource-definition/resource-definition-controller.ts +31 -13
  56. package/src/evaluation-context.ts +21 -7
  57. package/src/kernel.ts +38 -1
  58. package/src/manifest-schemas.ts +6 -0
  59. package/src/module-context.ts +3 -4
  60. package/src/resource-context.ts +111 -50
  61. package/src/resource-handle.ts +35 -0
  62. package/src/runtime-seam.ts +9 -1
  63. package/src/schema-compiled-values.ts +36 -4
  64. package/src/schema-validator.ts +87 -5
  65. package/src/type-field-schema.ts +77 -0
  66. package/src/zone-context.ts +337 -0
@@ -0,0 +1,272 @@
1
+ /**
2
+ * The kernel half of execution zones — `kernel/specs/execution-zones.md`.
3
+ *
4
+ * Everything a `ResourceContext` needs to open a zone declared by one of its
5
+ * own slots, and to assert it is inside one: annotation resolution in the
6
+ * kind's *declaring* scope, correlation-pointer resolution over the resource's
7
+ * own (Phase-5-injected) manifest, `extends`-aware kind matching, and the
8
+ * ambient-stack search.
9
+ *
10
+ * It lives beside `resource-handle.ts` rather than inside `resource-context.ts`
11
+ * because it is a subsystem with its own state, not a few accessors: the
12
+ * context delegates and keeps its own file about the resource lifecycle.
13
+ *
14
+ * **Resolution is memoized per annotated field.** Everything a match needs —
15
+ * the resolved zone kind, the accepted-kind set, and the correlation handle —
16
+ * is a function of `(kind, field)` and the injected manifest, all fixed once
17
+ * `create()` has returned. A statement resolves them on *every* dispatch
18
+ * otherwise, and this sits on the SQL hot path.
19
+ */
20
+ import { InvokeError, RuntimeError, UNCANCELLABLE_CONTEXT, deriveContext, getRefIdentity, sameResource, } from "@telorun/sdk";
21
+ import { readProvidesZone, readRequiresZone } from "@telorun/analyzer";
22
+ import { isRefSentinel } from "@telorun/templating";
23
+ import { ambientInvokeContext, runWithAmbientContext } from "./evaluation-context.js";
24
+ import { handleOfInstance } from "./resource-handle.js";
25
+ export class ZoneContext {
26
+ #host;
27
+ /** Field → resolved requirement. See the file docstring. */
28
+ #requirements = new Map();
29
+ /** Slot → resolved correlation handle for a providing slot. */
30
+ #providers = new Map();
31
+ constructor(host) {
32
+ this.#host = host;
33
+ }
34
+ async withZone(slot, fn, base) {
35
+ const provider = this.resolveProvider(slot);
36
+ // Every field derived: kind = the declaring kind, provider = this resource,
37
+ // key = the annotation's pointer over this resource's own manifest — which
38
+ // is the runtime half of why a schema cannot claim another module's zone.
39
+ const entry = Object.freeze({
40
+ kind: this.#host.resolvedKind,
41
+ provider: this.#host.self,
42
+ ...(provider.key ? { key: provider.key } : {}),
43
+ });
44
+ const from = base ?? ambientInvokeContext() ?? UNCANCELLABLE_CONTEXT;
45
+ const derived = deriveContext(from, { zones: [...(from.zones ?? []), entry] });
46
+ return runWithAmbientContext(derived, () => fn(derived, entry));
47
+ }
48
+ requireZone(field, ctx) {
49
+ const resolved = this.resolveRequirement(field);
50
+ const entry = this.match(resolved, ctx);
51
+ if (entry)
52
+ return entry;
53
+ const where = resolved.keyHandle
54
+ ? ` on ${resolved.keyHandle.ref.kind} '${resolved.keyHandle.ref.name}'`
55
+ : "";
56
+ const reason = resolved.requirement.reason ? `: ${resolved.requirement.reason}` : "";
57
+ throw new InvokeError("ERR_ZONE_REQUIRED", `${this.#host.resolvedKind} '${this.#host.resourceName}': no ${resolved.requiredKind} zone open${where}${reason}`);
58
+ }
59
+ findZone(field, ctx) {
60
+ return this.match(this.resolveRequirement(field), ctx);
61
+ }
62
+ zonesFor(instance, ctx) {
63
+ const zones = (ctx ?? ambientInvokeContext())?.zones;
64
+ if (!zones || zones.length === 0)
65
+ return [];
66
+ const handle = handleOfInstance(instance);
67
+ if (!handle)
68
+ return [];
69
+ const out = [];
70
+ for (let i = zones.length - 1; i >= 0; i--) {
71
+ const entry = zones[i];
72
+ if (entry.key && sameResource(entry.key, handle))
73
+ out.push(entry);
74
+ }
75
+ return out;
76
+ }
77
+ // ── resolution ────────────────────────────────────────────────────────────
78
+ resolveProvider(slot) {
79
+ const cached = this.#providers.get(slot);
80
+ if (cached)
81
+ return cached;
82
+ const provides = readProvidesZone(this.slotSchema(slot));
83
+ if (!provides) {
84
+ throw new RuntimeError("ERR_ZONE_ANNOTATION_MISSING", `[${this.#host.resourceName}] withZone('${slot}'): schema.properties.${slot} of ` +
85
+ `${this.#host.resolvedKind} carries no x-telo-provides-zone — the controller and its ` +
86
+ `schema disagree`);
87
+ }
88
+ const key = provides.key ? this.resolveCorrelationHandle([provides.key]) : undefined;
89
+ const resolved = key ? { key } : {};
90
+ this.#providers.set(slot, resolved);
91
+ return resolved;
92
+ }
93
+ resolveRequirement(field) {
94
+ const cached = this.#requirements.get(field);
95
+ if (cached)
96
+ return cached;
97
+ const requirement = readRequiresZone(this.slotSchema(field));
98
+ if (!requirement) {
99
+ throw new RuntimeError("ERR_ZONE_ANNOTATION_MISSING", `[${this.#host.resourceName}] requireZone('${field}'): schema.properties.${field} of ` +
100
+ `${this.#host.resolvedKind} carries no x-telo-requires-zone — the controller and its ` +
101
+ `schema disagree`);
102
+ }
103
+ const requiredKind = this.resolveZoneKind(requirement.zone);
104
+ const resolved = {
105
+ requirement,
106
+ requiredKind,
107
+ accepts: this.acceptancePredicate(requiredKind),
108
+ // When no pointer resolves, the requirement discharges uncorrelated — any
109
+ // zone of the right type. Inventing a correlation the manifest does not
110
+ // state would manufacture failures from a guess.
111
+ keyHandle: requirement.key.length > 0 ? this.resolveCorrelationHandle(requirement.key) : undefined,
112
+ };
113
+ this.#requirements.set(field, resolved);
114
+ return resolved;
115
+ }
116
+ /** Innermost first — the order a nested zone must win in. */
117
+ match(resolved, ctx) {
118
+ const zones = (ctx ?? ambientInvokeContext())?.zones ?? [];
119
+ for (let i = zones.length - 1; i >= 0; i--) {
120
+ const entry = zones[i];
121
+ if (!resolved.accepts(entry.kind))
122
+ continue;
123
+ if (resolved.keyHandle && !(entry.key && sameResource(entry.key, resolved.keyHandle))) {
124
+ continue;
125
+ }
126
+ return entry;
127
+ }
128
+ return undefined;
129
+ }
130
+ /** The schema node for one of this kind's own top-level fields — the site the
131
+ * zone annotations live on. */
132
+ slotSchema(field) {
133
+ const def = this.ownDefinition();
134
+ return def.schema
135
+ ?.properties?.[field];
136
+ }
137
+ ownDefinition() {
138
+ const def = this.#host.resolveDefinition(this.#host.resolvedKind);
139
+ if (!def) {
140
+ throw new RuntimeError("ERR_ZONE_ANNOTATION_MISSING", `[${this.#host.resourceName}] no registered definition for kind '${this.#host.resolvedKind}'`);
141
+ }
142
+ return def;
143
+ }
144
+ /** Resolve the annotation's zone kind to canonical `<module>.<Kind>`.
145
+ * `resolveSchemaRefKinds` already rewrote it in the DECLARING module's scope
146
+ * during analysis; the scoped lookup is the fallback for an un-analyzed load. */
147
+ resolveZoneKind(zone) {
148
+ const def = this.#host.resolveDefinitionIn(zone, this.ownDefinition().metadata.module);
149
+ if (!def) {
150
+ throw new RuntimeError("ERR_ZONE_UNRESOLVED", `[${this.#host.resourceName}] x-telo-requires-zone names '${zone}', which resolves to no ` +
151
+ `registered kind`);
152
+ }
153
+ return `${def.metadata.module}.${def.metadata.name}`;
154
+ }
155
+ /**
156
+ * Acceptance for one requirement: the required kind, or anything that
157
+ * transitively `extends` it.
158
+ *
159
+ * A predicate rather than a precomputed set because the kernel's registry has
160
+ * no `extendedBy` index — only the upward `extends` link — so the set of
161
+ * acceptable kinds is not enumerable from the requirement alone. Each
162
+ * candidate walks UP instead, and only ever for a kind actually seen on the
163
+ * stack. The verdict is cached per entry kind, so a repeated dispatch through
164
+ * the same shape costs one map lookup.
165
+ */
166
+ acceptancePredicate(requiredKind) {
167
+ const verdicts = new Map([[requiredKind, true]]);
168
+ return (entryKind) => {
169
+ const known = verdicts.get(entryKind);
170
+ if (known !== undefined)
171
+ return known;
172
+ const verdict = this.extendsChainReaches(entryKind, requiredKind);
173
+ verdicts.set(entryKind, verdict);
174
+ return verdict;
175
+ };
176
+ }
177
+ extendsChainReaches(entryKind, requiredKind) {
178
+ let current = entryKind;
179
+ const seen = new Set();
180
+ while (current !== undefined && !seen.has(current)) {
181
+ if (current === requiredKind)
182
+ return true;
183
+ seen.add(current);
184
+ const def = this.#host.resolveDefinition(current);
185
+ if (typeof def?.extends !== "string")
186
+ return false;
187
+ const parent = this.#host.resolveDefinitionIn(def.extends, def.metadata.module);
188
+ current = parent ? `${parent.metadata.module}.${parent.metadata.name}` : undefined;
189
+ }
190
+ return false;
191
+ }
192
+ /** First pointer that resolves to a live instance wins — the manifest-level
193
+ * transcription of a controller's own `a ?? b` derivation. */
194
+ resolveCorrelationHandle(pointers) {
195
+ for (const pointer of pointers) {
196
+ const handle = this.resolveKeyPointer(pointer);
197
+ if (handle)
198
+ return handle;
199
+ }
200
+ return undefined;
201
+ }
202
+ resolveKeyPointer(pointer) {
203
+ const segments = pointer.split("/").filter((s) => s.length > 0);
204
+ if (segments.length === 0)
205
+ return undefined;
206
+ let manifest = this.#host.manifest;
207
+ let value;
208
+ for (let i = 0; i < segments.length; i++) {
209
+ if (i > 0) {
210
+ // A pointer may traverse a `!ref` into the referenced resource's own
211
+ // manifest: read field → resolve reference → read field. Purely
212
+ // mechanical — no kind is named here.
213
+ const name = this.referencedName(value);
214
+ manifest = name ? this.#host.resolveLocalManifest(name) : undefined;
215
+ if (!manifest)
216
+ return undefined;
217
+ }
218
+ value = manifest[segments[i]];
219
+ if (value === undefined || value === null)
220
+ return undefined;
221
+ }
222
+ return this.handleOfValue(value);
223
+ }
224
+ /**
225
+ * The LOCAL name a reference value points at, in any of its three shapes.
226
+ *
227
+ * A cross-module `!ref Alias.name` yields undefined: it names an instance in
228
+ * another module's scope, and this resolver is scope-local, so taking the
229
+ * bare name would bind to whatever local resource happened to share it.
230
+ * Leaving it uncorrelated is the under-approximating direction. The analyzer's
231
+ * `refName` holds the identical rule, so the two halves agree about what a
232
+ * traversing pointer means.
233
+ */
234
+ referencedName(value) {
235
+ if (value === null || typeof value !== "object")
236
+ return undefined;
237
+ const id = getRefIdentity(value) ?? handleOfInstance(value)?.ref;
238
+ if (id)
239
+ return id.name;
240
+ if (isRefSentinel(value)) {
241
+ const source = value.source;
242
+ const dot = source.indexOf(".");
243
+ if (dot <= 0)
244
+ return source;
245
+ return source.slice(0, dot) === "Self" ? source.slice(dot + 1) : undefined;
246
+ }
247
+ const v = value;
248
+ const pure = typeof v.kind === "string" &&
249
+ typeof v.name === "string" &&
250
+ Object.keys(v).every((k) => k === "kind" || k === "name" || k === "alias");
251
+ if (!pure)
252
+ return undefined;
253
+ if (typeof v.alias === "string" && v.alias !== "Self")
254
+ return undefined;
255
+ return v.name;
256
+ }
257
+ /** The handle a pointer's terminal value identifies: a live instance's own
258
+ * handle, or the handle of the instance a reference resolves to. */
259
+ handleOfValue(value) {
260
+ if (value === null || typeof value !== "object")
261
+ return undefined;
262
+ const direct = handleOfInstance(value);
263
+ if (direct)
264
+ return direct;
265
+ const name = this.referencedName(value);
266
+ if (!name)
267
+ return undefined;
268
+ const instance = this.#host.resolveLocalInstance(name);
269
+ return instance ? handleOfInstance(instance) : undefined;
270
+ }
271
+ }
272
+ //# sourceMappingURL=zone-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zone-context.js","sourceRoot":"","sources":["../src/zone-context.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/kernel",
3
- "version": "0.66.0",
3
+ "version": "0.68.0",
4
4
  "description": "Telo Runtime - A lightweight, polyglot execution host.",
5
5
  "keywords": [
6
6
  "telo",
@@ -61,9 +61,9 @@
61
61
  "dependencies": {
62
62
  "@marcbachmann/cel-js": "^7.6.1",
63
63
  "@sinclair/typebox": "^0.34.48",
64
- "@telorun/analyzer": "0.53.0",
64
+ "@telorun/analyzer": "0.55.0",
65
65
  "@telorun/glob": "0.2.0",
66
- "@telorun/templating": "0.11.1",
66
+ "@telorun/templating": "0.12.0",
67
67
  "ajv": "^8.17.1",
68
68
  "ajv-formats": "^3.0.1",
69
69
  "packageurl-js": "^2.0.1",
@@ -75,7 +75,7 @@
75
75
  "@types/tar-stream": "^3.1.3",
76
76
  "typescript": "^5.0.0",
77
77
  "vitest": "^2.1.8",
78
- "@telorun/sdk": "0.65.0"
78
+ "@telorun/sdk": "0.68.0"
79
79
  },
80
80
  "optionalDependencies": {
81
81
  "esbuild": "^0.25.12"
@@ -1,7 +1,19 @@
1
- import { type DefResolver, effectiveAuthorSchema, residualEntrySchema } from "@telorun/analyzer";
2
- import type { ResourceDefinition } from "@telorun/sdk";
1
+ import {
2
+ type DefResolver,
3
+ effectiveAuthorSchema,
4
+ residualEntrySchema,
5
+ withStreamPropertiesSkipped,
6
+ } from "@telorun/analyzer";
7
+ import type {
8
+ ResourceContext,
9
+ ResourceDefinition,
10
+ ResourceManifest,
11
+ TypeRule,
12
+ } from "@telorun/sdk";
3
13
  import { RuntimeError } from "@telorun/sdk";
14
+ import { create as createJsonSchemaType } from "./controllers/type/json-schema-controller.js";
4
15
  import { SchemaValidator } from "./schema-validator.js";
16
+ import { resolveTypeFieldSchema } from "./type-field-schema.js";
5
17
 
6
18
  type EntryType = "string" | "integer" | "number" | "boolean" | "object" | "array";
7
19
 
@@ -144,6 +156,63 @@ export function precompileApplicationEnvSchemas(
144
156
  }
145
157
  }
146
158
 
159
+ /**
160
+ * Register every `Telo.JsonSchema` resource's resolved schema into `validator`,
161
+ * ahead of the contract warm below.
162
+ *
163
+ * A contract declared as `{kind: Telo.JsonSchema, schema: {$ref: "telo:m/X"}}`
164
+ * — what `oauth-client` and `vector-store` write — is compiled at runtime from
165
+ * the schema registered under that id, reached by following the alias. With no
166
+ * types registered the warm follows it to the `$ref` wrapper itself, AJV refuses
167
+ * the unresolvable reference, and the entry is silently not baked while the
168
+ * runtime goes on compiling something else: a guaranteed miss for exactly the
169
+ * modules that declare their shapes once and reference them.
170
+ *
171
+ * Runs the REAL type controller rather than re-deriving registration here. The
172
+ * three names a type registers under, the canonical `telo:` id and the `extends`
173
+ * merge are its rules; a second implementation would drift into baking schemas
174
+ * under keys the runtime never asks for — the failure this whole pass exists to
175
+ * remove. The loop mirrors the kernel's multi-pass init (`create` returns null
176
+ * while a parent type is unregistered) and stops as soon as a pass registers
177
+ * nothing new, so an unresolvable parent ends it instead of spinning.
178
+ *
179
+ * The deprecated `Type.JsonSchema` is not warmed: it is a module kind with its
180
+ * own controller, and reaching for this one would be assuming the two stayed
181
+ * identical.
182
+ */
183
+ export async function precompileTypeSchemas(
184
+ manifests: Array<Record<string, any>>,
185
+ validator: SchemaValidator,
186
+ ): Promise<void> {
187
+ const ctx = {
188
+ lookupSchema: (name: string) => validator.getSchema(name),
189
+ registerSchema: (name: string, schema: object) => validator.addSchema(name, schema),
190
+ registerTypeRules: (name: string, rules: TypeRule[]) => validator.addTypeRules(name, rules),
191
+ } as unknown as ResourceContext;
192
+
193
+ let pending = manifests.filter(
194
+ (m) =>
195
+ m?.kind === "Telo.JsonSchema" &&
196
+ m.schema &&
197
+ typeof m.metadata?.name === "string" &&
198
+ typeof m.metadata?.module === "string",
199
+ );
200
+ while (pending.length > 0) {
201
+ const deferred: Array<Record<string, any>> = [];
202
+ for (const m of pending) {
203
+ try {
204
+ if ((await createJsonSchemaType(m as unknown as ResourceManifest, ctx)) === null) {
205
+ deferred.push(m);
206
+ }
207
+ } catch {
208
+ // A broken type surfaces through analysis / runtime, not the warm pass.
209
+ }
210
+ }
211
+ if (deferred.length === pending.length) break;
212
+ pending = deferred;
213
+ }
214
+ }
215
+
147
216
  /**
148
217
  * Build-time cache warm for resource-config validators. The runtime
149
218
  * `_createInstance` compiles the declaring `Telo.Definition`'s `schema` to
@@ -188,11 +257,31 @@ export function precompileDefinitionSchemas(
188
257
  // Broken schemas are reported by analysis / runtime, not the warm pass.
189
258
  }
190
259
  };
260
+ const lookup = (name: string) => validator.getSchema(name);
261
+ // A contract validator is compiled from the RESOLVED schema with its
262
+ // `x-telo-stream` properties stripped, never from the declaration — see
263
+ // `resolveBoundContract`. Baking the declaration instead bakes a validator for
264
+ // `{kind, schema}`, which no dispatch asks for. A declaration that needs the
265
+ // runtime type registry (a bare name, a `{kind, name}` ref) resolves to
266
+ // nothing here — named types register when their resources initialize, long
267
+ // after the warm — so it is skipped rather than baked wrong.
268
+ const compileContract = (declared: unknown): void => {
269
+ if (declared === undefined || declared === null) return;
270
+ try {
271
+ const schema = resolveTypeFieldSchema(declared, lookup);
272
+ if (!schema) return;
273
+ compile(withStreamPropertiesSkipped(schema, (ref) => lookup(ref) as any));
274
+ } catch {
275
+ // An unresolvable contract is a dispatch-time error, not a warm failure.
276
+ }
277
+ };
191
278
  for (const m of manifests) {
279
+ // A per-instance contract overrides the kind's, so a resource declaring its
280
+ // own narrowing is what the runtime compiles for that instance.
281
+ compileContract(m?.inputType);
282
+ compileContract(m?.outputType);
192
283
  if (m?.kind !== "Telo.Definition") continue;
193
284
  compile(m.schema);
194
- compile(m.inputType);
195
- compile(m.outputType);
196
285
  if (resolverFor && m.extends) {
197
286
  // Mirrors the runtime stamp in `resource-definition-controller`; sharing
198
287
  // `effectiveAuthorSchema` is what keeps the two keys identical.
@@ -183,14 +183,16 @@ export class ControllerLoader {
183
183
  * Resolve a controller without importing it: pick the first candidate this
184
184
  * environment can host (same ordering + env-missing fallback as {@link load}),
185
185
  * verify it's present, and return a {@link ResolvedController} whose
186
- * `importInstance` defers the actual import/eval. Used by lazy controller
187
- * loading so a `Telo.Definition` fails fast at boot when its controller can't
188
- * load at all, while the expensive import is paid only on first instantiation.
186
+ * `importInstance` defers the actual import/eval. Lazy controller loading
187
+ * calls this from the kind's first instantiation — never at boot so a
188
+ * definition whose candidate list nothing in this environment can host
189
+ * registers fine and errors only when a resource of it is declared (matching
190
+ * the Rust kernel's deferral).
189
191
  *
190
192
  * Silent by design — no lifecycle events here; the caller emits
191
193
  * ControllerLoading/Loaded around `importInstance` so the events fire when the
192
- * load actually happens. A total resolution failure throws (the boot-time
193
- * fail-fast), mirroring {@link load}'s aggregated error.
194
+ * load actually happens. A total resolution failure throws, mirroring
195
+ * {@link load}'s aggregated error.
194
196
  */
195
197
  async resolve(
196
198
  purlCandidates: string[],
@@ -161,32 +161,39 @@ class ResourceDefinition implements ResourceInstance {
161
161
  cacheRoot: host.getCacheRoot?.(),
162
162
  log: ctx.log,
163
163
  });
164
- // Eager resolve — verify the controller is hostable now (so a broken
165
- // `controllers:` candidate fails fast at boot), but defer the expensive
166
- // import/eval and the controller's `register()` to the kind's first
167
- // instantiation. Definitions whose kind is never instantiated never import.
168
164
  // The artifact of the module that DECLARED this kind — a bundled controller
169
165
  // ships in its own module's payload, not the consumer's. It owns the pinned
170
166
  // ref and the verified layer index, so the loader picks a candidate and asks
171
167
  // it for that selector's directory rather than fetching anything itself.
172
168
  const artifact = host.getModuleArtifact?.(this.resource.metadata.source);
173
- const resolved = await loader.resolve(
174
- this.resource.controllers,
175
- this.resource.metadata.source,
176
- ctx.getControllerPolicy(),
177
- artifact,
178
- );
179
169
  ctx.registerDefinition(this.resource);
180
170
 
181
171
  const moduleName = this.resource.metadata.module;
182
172
  const kindName = this.resource.metadata.name;
183
- // Emitted here (not in the loader) so ControllerLoading / ControllerLoaded /
184
- // ControllerLoadFailed and the import duration — surface when the load
185
- // actually happens (first instantiation), with the resolved PURL + source.
173
+ const controllers = this.resource.controllers;
174
+ const source = this.resource.metadata.source;
175
+ const policy = ctx.getControllerPolicy();
176
+ // Resolution AND import are deferred to the kind's first instantiation,
177
+ // matching the Rust kernel: a definition whose candidate list nothing in
178
+ // this environment can host registers fine and errors — naming the kind —
179
+ // only when a resource of it is declared. That is what lets both kernels
180
+ // load a partially-covered module (e.g. console's stream kinds with no Rust
181
+ // controller) instead of rejecting it over kinds nobody uses.
182
+ // Lifecycle events are emitted here (not in the loader) so ControllerLoading
183
+ // / ControllerLoaded / ControllerLoadFailed — and the import duration —
184
+ // surface when the load actually happens, with the resolved PURL + source.
186
185
  host.registerLazyController(
187
186
  moduleName,
188
187
  kindName,
189
188
  async () => {
189
+ const resolved = await loader
190
+ .resolve(controllers, source, policy, artifact)
191
+ .catch((err) => {
192
+ if (err instanceof RuntimeError) {
193
+ throw new RuntimeError(err.code, `kind '${moduleName}.${kindName}': ${err.message}`);
194
+ }
195
+ throw err;
196
+ });
190
197
  await ctx.emit("ControllerLoading", { purl: resolved.purl });
191
198
  const startedAt = Date.now();
192
199
  const instance = await resolved.importInstance().catch(async (err) => {
@@ -241,6 +248,17 @@ export function register(ctx: ControllerContext): void {
241
248
  }
242
249
 
243
250
  export async function create(resource: any, ctx: ResourceContext): Promise<ResourceDefinition> {
251
+ // Named preflight for the one capability the schema rejects by omission: the
252
+ // AJV oneOf failure it produces never says WHY, and this is the mistake an
253
+ // author migrating a multi-kind slot is most likely to make.
254
+ if (resource?.capability === "Telo.Executable") {
255
+ throw new Error(
256
+ `Invalid ResourceDefinition "${resource.metadata?.name}": capability 'Telo.Executable' ` +
257
+ `is an x-telo-ref slot constraint (the parent Telo.Invocable and Telo.Runnable ` +
258
+ `extend), not a declarable lifecycle role. Declare 'Telo.Invocable' (invoke) or ` +
259
+ `'Telo.Runnable' (run) instead.`,
260
+ );
261
+ }
244
262
  // Validate incoming resource definition against schema
245
263
  if (!validateResourceDefinition(resource)) {
246
264
  throw new Error(
@@ -1,6 +1,7 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { formatSpanCounter } from "./logging/span-id.js";
3
3
  import {
4
+ deriveContext,
4
5
  getRefIdentity,
5
6
  isCompiledValue,
6
7
  isInvokeError,
@@ -263,6 +264,15 @@ export function ambientInvokeContext(): InvokeContext | undefined {
263
264
  return cancellationStore.getStore();
264
265
  }
265
266
 
267
+ /**
268
+ * Establish `ctx` as the ambient invocation context for the duration of `fn`.
269
+ * The seam `withZone` uses to make an opened zone ambient without owning the
270
+ * store — the store itself stays kernel-internal, never on the SDK surface.
271
+ */
272
+ export function runWithAmbientContext<T>(ctx: InvokeContext, fn: () => T): T {
273
+ return cancellationStore.run(ctx, fn);
274
+ }
275
+
266
276
  /** Marks a scope built by {@link EvaluationContext.bindScope}, whose properties
267
277
  * are getters. `expandWith` merges such a scope by descriptor rather than by
268
278
  * value — reading the value here is what a lazy binding must not do. */
@@ -1189,11 +1199,13 @@ export class EvaluationContext implements IEvaluationContext {
1189
1199
  outcome,
1190
1200
  phase === "end" && rootScope ? { ...detail, context: rootScope } : detail,
1191
1201
  );
1192
- // When tracing, a fresh context carries the new id down the tree so nested
1202
+ // When tracing, a derived context carries the new id down the tree so nested
1193
1203
  // invokes read it as their parent; it is never `=== ambient`, so the call
1194
- // always (re)establishes the ALS scope.
1204
+ // always (re)establishes the ALS scope. Derived, never a fresh literal: a
1205
+ // literal drops every field it does not restate (`zones`), making
1206
+ // propagation differ between tracing on and off.
1195
1207
  const invokeCtx: InvokeContext = tracing
1196
- ? { cancellation: token, invocationId, parentInvocationId, traceId }
1208
+ ? deriveContext(baseCtx, { invocationId, parentInvocationId, traceId })
1197
1209
  : baseCtx;
1198
1210
 
1199
1211
  // Pre-dispatch gate: a sub-invoke reached after the tree was cancelled is
@@ -1391,12 +1403,14 @@ export class EvaluationContext implements IEvaluationContext {
1391
1403
  );
1392
1404
 
1393
1405
  await this.emit(`${opts.ref.name}.Requesting`, payload("start", undefined));
1394
- const context: InvokeContext = {
1395
- cancellation: ctx.cancellation,
1406
+ // Derived so the span context keeps whatever `base` carries beyond the span
1407
+ // ids — with tracing off this method returns `base` unchanged, and tracing
1408
+ // must not change what propagates.
1409
+ const context: InvokeContext = deriveContext(ctx, {
1396
1410
  invocationId: spanId,
1397
1411
  parentInvocationId: parentSpanId,
1398
1412
  traceId,
1399
- };
1413
+ });
1400
1414
  let settled = false;
1401
1415
  return {
1402
1416
  context,
@@ -1460,7 +1474,7 @@ export class EvaluationContext implements IEvaluationContext {
1460
1474
  phase === "end" && rootScope ? { ...detail, context: rootScope } : detail,
1461
1475
  );
1462
1476
  const invokeCtx: InvokeContext = tracing
1463
- ? { cancellation: token, invocationId, parentInvocationId, traceId }
1477
+ ? deriveContext(baseCtx, { invocationId, parentInvocationId, traceId })
1464
1478
  : baseCtx;
1465
1479
 
1466
1480
  // Refuse a target reached after the boot run was cancelled.