@telorun/analyzer 1.2.0 → 1.4.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.
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAc9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA+c/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAudvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,gBAAgB,GAAG,gBAAgB,EAAE;IAUxF,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAe9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA+c/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IA6dvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,gBAAgB,GAAG,gBAAgB,EAAE;IAexF,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
package/dist/analyzer.js CHANGED
@@ -7,6 +7,7 @@ import { buildKernelGlobalsSchema, mergeKernelGlobalsIntoContext } from "./kerne
7
7
  import { computeSuggestKind } from "./kind-suggest.js";
8
8
  import { isModuleKind } from "./module-kinds.js";
9
9
  import { normalizeInlineResources } from "./normalize-inline-resources.js";
10
+ import { resolveRefSentinels } from "./resolve-ref-sentinels.js";
10
11
  import { rewriteSyntheticOrigins } from "./rewrite-synthetic-origins.js";
11
12
  import { celTypeSatisfiesJsonSchema, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
12
13
  import { DiagnosticSeverity } from "./types.js";
@@ -492,6 +493,11 @@ export class StaticAnalyzer {
492
493
  }
493
494
  // Phase 2: extract inline resources from x-telo-ref slots into first-class manifests
494
495
  const allManifests = normalizeInlineResources(manifests, defs, aliases, aliasesByModule);
496
+ // Phase 2.5: resolve `!ref <name>` sentinels at every ref slot to canonical
497
+ // {kind, name} objects so downstream phases (validation, dependency graph,
498
+ // kernel controllers) see a uniform shape. Runs after normalize so both
499
+ // original and inline-extracted manifests have their sentinels resolved.
500
+ resolveRefSentinels(allManifests, defs, aliases, aliasesByModule);
495
501
  // Trusted-input fast path: when the caller has already attested that
496
502
  // this exact manifest set passes analysis (e.g. via the kernel's
497
503
  // hash-stamped `.validated.json` cache), skip the validation walk.
@@ -788,7 +794,12 @@ export class StaticAnalyzer {
788
794
  }
789
795
  normalize(manifests, registry) {
790
796
  const ctx = registry._context();
791
- return normalizeInlineResources(manifests, ctx.definitions, ctx.aliases, ctx.aliasesByModule);
797
+ const normalized = normalizeInlineResources(manifests, ctx.definitions, ctx.aliases, ctx.aliasesByModule);
798
+ // Resolve !ref sentinels after normalize so both the original and
799
+ // inline-extracted manifests get their refs canonicalized to
800
+ // {kind, name} for the kernel that consumes this output.
801
+ resolveRefSentinels(normalized, ctx.definitions, ctx.aliases, ctx.aliasesByModule);
802
+ return normalized;
792
803
  }
793
804
  prepare(manifests, registry) {
794
805
  const ctx = registry._context();
@@ -1 +1 @@
1
- {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EAmT/C,CAAC"}
1
+ {"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EAiU/C,CAAC"}
package/dist/builtins.js CHANGED
@@ -218,6 +218,20 @@ export const KERNEL_BUILTINS = [
218
218
  anyOf: [
219
219
  { type: "string", "x-telo-ref": "telo#Runnable" },
220
220
  { type: "string", "x-telo-ref": "telo#Service" },
221
+ // Post-resolution shape that `resolveRefSentinels`
222
+ // substitutes a `!ref <name>` sentinel into. The
223
+ // adjacent `x-telo-ref` constraints govern the kind
224
+ // check; this branch only admits the structural form so
225
+ // AJV doesn't reject a resolved ref.
226
+ {
227
+ type: "object",
228
+ required: ["kind", "name"],
229
+ properties: {
230
+ kind: { type: "string" },
231
+ name: { type: "string" },
232
+ },
233
+ additionalProperties: true,
234
+ },
221
235
  ],
222
236
  },
223
237
  },
@@ -1 +1 @@
1
- {"version":3,"file":"definition-registry.d.ts","sourceRoot":"","sources":["../src/definition-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAGlC,+EAA+E;AAC/E,qBAAa,kBAAkB;;IAK7B;;sFAEkF;IAClF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwC;IAClE,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+B;IAC1D;6DACyD;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD;;0EAEsE;IACtE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAEhE,QAAQ,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI;IAiC9C,OAAO,CAAC,aAAa;IASrB;;;yFAGqF;IACrF,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAgB1E;4EACwE;IACxE,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAMnE;wFACoF;IACpF,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE;IAWtE,OAAO,CAAC,iBAAiB;IAczB;;;;;;;;4FAQwF;IACxF,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAUhD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAIrD,+FAA+F;IAC/F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAIxD,gGAAgG;IAChG,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,GACvD,iBAAiB,GAAG,SAAS;IAOhC;;;;;;;;qFAQiF;IACjF,2BAA2B,CACzB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC1C,iBAAiB,GAAG,SAAS;IAuBhC,OAAO,CAAC,uBAAuB;IA+B/B;;qEAEiE;IACjE,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAgBxD,KAAK,IAAI,MAAM,EAAE;CAGlB"}
1
+ {"version":3,"file":"definition-registry.d.ts","sourceRoot":"","sources":["../src/definition-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAGlC,+EAA+E;AAC/E,qBAAa,kBAAkB;;IAK7B;;sFAEkF;IAClF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwC;IAClE,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+B;IAC1D;6DACyD;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD;;0EAEsE;IACtE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAEhE,QAAQ,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI;IAiC9C,OAAO,CAAC,aAAa;IASrB;;;yFAGqF;IACrF,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IA+B1E;4EACwE;IACxE,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAMnE;wFACoF;IACpF,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE;IAWtE,OAAO,CAAC,iBAAiB;IAczB;;;;;;;;4FAQwF;IACxF,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAUhD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAIrD,+FAA+F;IAC/F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAIxD,gGAAgG;IAChG,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,GACvD,iBAAiB,GAAG,SAAS;IAOhC;;;;;;;;qFAQiF;IACjF,2BAA2B,CACzB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC1C,iBAAiB,GAAG,SAAS;IAuBhC,OAAO,CAAC,uBAAuB;IA+B/B;;qEAEiE;IACjE,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAgBxD,KAAK,IAAI,MAAM,EAAE;CAGlB"}
@@ -70,6 +70,22 @@ export class DefinitionRegistry {
70
70
  * @param namespace The module's metadata.namespace (e.g. "std"), or null for telo built-ins.
71
71
  * @param moduleName The module's metadata.name (e.g. "pipeline", "http-server"). */
72
72
  registerModuleIdentity(namespace, moduleName) {
73
+ // The "telo" identity is reserved for the Telo built-in module and gets
74
+ // populated automatically when a Telo.Abstract definition registers (see
75
+ // `register` below). A user app / library without a namespace must NOT
76
+ // claim it — silently overwriting the built-in entry breaks every
77
+ // x-telo-ref that resolves through "telo#…". Concretely, the
78
+ // `Http.Api.routes[].handler` slot in the http-server schema carries
79
+ // `x-telo-ref: "telo#Invocable"`. If the entry application is, say,
80
+ // `Telo.Application/HelloApi` (no namespace), this method previously
81
+ // overwrote `"telo" → "Telo"` with `"telo" → "HelloApi"`. The handler's
82
+ // ref then resolved to a nonexistent `HelloApi.Invocable`, the
83
+ // kind-mismatch check inside `validate-references.ts` short-circuited
84
+ // on partial context, and the analyzer reported zero issues for a
85
+ // manifest that explodes at runtime. Skip non-Telo no-namespace modules;
86
+ // they have no x-telo-ref identity to declare anyway.
87
+ if (!namespace && moduleName !== "Telo")
88
+ return;
73
89
  const identity = namespace ? `${namespace}/${moduleName}` : "telo";
74
90
  this.identityMap.set(identity, moduleName);
75
91
  this.reverseIdentityMap.set(moduleName, identity);
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-graph.d.ts","sourceRoot":"","sources":["../src/dependency-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAGnE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B;kDAC8C;IAC9C,KAAK,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACpC;oFACgF;IAChF,KAAK,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACrC;AAcD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,eAAe,CA6FjB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,MAAM,CAOtE"}
1
+ {"version":3,"file":"dependency-graph.d.ts","sourceRoot":"","sources":["../src/dependency-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAInE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B;kDAC8C;IAC9C,KAAK,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACpC;oFACgF;IAChF,KAAK,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACrC;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,eAAe,CAkHjB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,MAAM,CAOtE"}
@@ -1,13 +1,6 @@
1
+ import { isRefSentinel } from "@telorun/templating";
1
2
  import { isRefEntry, isScopeEntry, resolveFieldValues } from "./reference-field-map.js";
2
- /** System resource kinds that are not runtime nodes in the dependency graph.
3
- * Module-identity docs (Telo.Application, Telo.Library) are intentionally
4
- * not in this set: an Application's `targets` use `x-telo-ref` to real
5
- * Runnable/Service resources, so the Application legitimately depends on
6
- * them in boot order — modeling that as a graph edge is correct. A Library
7
- * has no `targets`, so it becomes a zero-edge node, which is harmless.
8
- * If the graph is ever consumed as "things to init", skip these kinds at
9
- * the consumer site; the controller already runs them separately. */
10
- const SYSTEM_KINDS = new Set(["Telo.Definition", "Telo.Import"]);
3
+ import { DEPENDENCY_GRAPH_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
11
4
  const nodeKey = (kind, name) => `${kind}\0${name}`;
12
5
  /**
13
6
  * Builds a directed acyclic graph (DAG) of runtime resource dependencies and
@@ -22,13 +15,19 @@ const nodeKey = (kind, name) => `${kind}\0${name}`;
22
15
  * not pre-compute or pass field maps separately.
23
16
  */
24
17
  export function buildDependencyGraph(resources, registry, aliases, aliasesByModule) {
25
- // --- Build node set ---
18
+ // --- Build node set + name index ---
26
19
  const nodes = new Map();
20
+ // Sentinel lookup (`!ref <name>`) needs to resolve a bare name to its
21
+ // declared kind. Names are unique within a manifest scope, so a flat
22
+ // map suffices and lets the sentinel branch below avoid a full
23
+ // O(N) scan of the node set on every reference.
24
+ const nodesByName = new Map();
27
25
  for (const r of resources) {
28
26
  if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind))
29
27
  continue;
30
- const key = nodeKey(r.kind, r.metadata.name);
31
- nodes.set(key, { kind: r.kind, name: r.metadata.name });
28
+ const node = { kind: r.kind, name: r.metadata.name };
29
+ nodes.set(nodeKey(node.kind, node.name), node);
30
+ nodesByName.set(node.name, node);
32
31
  }
33
32
  // --- Build adjacency: from → deps (from depends on dep) ---
34
33
  const deps = new Map();
@@ -66,7 +65,22 @@ export function buildDependencyGraph(resources, registry, aliases, aliasesByModu
66
65
  if (!isRefEntry(entry))
67
66
  continue;
68
67
  for (const val of resolveFieldValues(r, fieldPath)) {
69
- if (!val || typeof val !== "object")
68
+ if (!val)
69
+ continue;
70
+ // `!ref <name>` sentinel — look up the target's kind from the
71
+ // name (resources are unique by name) so the edge carries the
72
+ // concrete kind, matching the {kind, name} edge shape below.
73
+ if (isRefSentinel(val)) {
74
+ const refName = val.source;
75
+ if (scopedNames.has(refName))
76
+ continue;
77
+ const node = nodesByName.get(refName);
78
+ if (node) {
79
+ deps.get(sourceKey).add(nodeKey(node.kind, node.name));
80
+ }
81
+ continue;
82
+ }
83
+ if (typeof val !== "object")
70
84
  continue;
71
85
  const ref = val;
72
86
  if (!ref.kind || !ref.name)
@@ -15,7 +15,12 @@ export interface DocumentPosition {
15
15
  export declare function buildDocumentPositions(text: string, parsedDocs: Document[]): DocumentPosition[];
16
16
  /** Line numbers (0-indexed) where each YAML document in a multi-doc file
17
17
  * starts. The first document is always at line 0; subsequent entries point
18
- * to the line after each `---` directive. */
18
+ * to the line after each `---` separator.
19
+ *
20
+ * A `---` at line 0 is the doc-start marker for doc 0 (the parser still
21
+ * emits a single document), not a separator before an empty doc — skipping
22
+ * it keeps `offsets.length === parsedDocs.length` so diagnostics for doc N
23
+ * don't land inside doc N-1's text. */
19
24
  export declare function documentLineOffsets(text: string): number[];
20
25
  /** Byte-offset → start-of-line lookup table. Index `i` is the byte offset of
21
26
  * the first character on line `i`. Used with `offsetToPosition` to turn a
@@ -1 +1 @@
1
- {"version":3,"file":"position-metadata.d.ts","sourceRoot":"","sources":["../src/position-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,MAAM,CAAC;AACrE,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;oBAKoB;AAEpB,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,kEAAkE;AAClE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,QAAQ,EAAE,GACrB,gBAAgB,EAAE,CAOpB;AAED;;8CAE8C;AAC9C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1D;AAED;;kDAEkD;AAClD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAaD;;;;;+CAK+C;AAC/C,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,aAAa,CA0CtF"}
1
+ {"version":3,"file":"position-metadata.d.ts","sourceRoot":"","sources":["../src/position-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,MAAM,CAAC;AACrE,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;oBAKoB;AAEpB,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,kEAAkE;AAClE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,QAAQ,EAAE,GACrB,gBAAgB,EAAE,CAOpB;AAED;;;;;;;wCAOwC;AACxC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAW1D;AAED;;kDAEkD;AAClD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAaD;;;;;+CAK+C;AAC/C,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,aAAa,CA0CtF"}
@@ -10,14 +10,22 @@ export function buildDocumentPositions(text, parsedDocs) {
10
10
  }
11
11
  /** Line numbers (0-indexed) where each YAML document in a multi-doc file
12
12
  * starts. The first document is always at line 0; subsequent entries point
13
- * to the line after each `---` directive. */
13
+ * to the line after each `---` separator.
14
+ *
15
+ * A `---` at line 0 is the doc-start marker for doc 0 (the parser still
16
+ * emits a single document), not a separator before an empty doc — skipping
17
+ * it keeps `offsets.length === parsedDocs.length` so diagnostics for doc N
18
+ * don't land inside doc N-1's text. */
14
19
  export function documentLineOffsets(text) {
15
20
  const offsets = [0];
16
21
  const lines = text.split("\n");
17
22
  for (let i = 0; i < lines.length; i++) {
18
23
  const t = lines[i].trimEnd();
19
- if (t === "---" || t.startsWith("--- "))
24
+ if (t === "---" || t.startsWith("--- ")) {
25
+ if (i === 0)
26
+ continue;
20
27
  offsets.push(i + 1);
28
+ }
21
29
  }
22
30
  return offsets;
23
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"precompile.d.ts","sourceRoot":"","sources":["../src/precompile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAIxD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAmCrE"}
1
+ {"version":3,"file":"precompile.d.ts","sourceRoot":"","sources":["../src/precompile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAIxD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CA0CrE"}
@@ -1,5 +1,5 @@
1
1
  import { isCompiledValue } from "@telorun/sdk";
2
- import { compileString, defaultRegistry, isTaggedSentinel } from "@telorun/templating";
2
+ import { compileString, defaultRegistry, isRefSentinel, isTaggedSentinel } from "@telorun/templating";
3
3
  /**
4
4
  * Walks a raw YAML document and replaces all `${{ expr }}` strings (and
5
5
  * `!cel`-tagged sentinels) with CompiledValue wrappers. Throws on CEL syntax
@@ -19,6 +19,14 @@ export function precompileDoc(doc, env) {
19
19
  // analyzer's diagnostic walk can identify it on compiled trees too;
20
20
  // engines returning plain values (e.g. `literal` → a string) pass through
21
21
  // verbatim — the runtime contract is "any scalar value is fine."
22
+ // `!ref` sentinels are identity markers, not templating values. They must
23
+ // survive precompile intact so the analyzer's `resolveRefSentinels` pass
24
+ // can substitute them with `{kind, name}` objects against the resolved
25
+ // resource manifest. Running the engine's `compile` here would prematurely
26
+ // collapse the sentinel into its source string and the ref slot would
27
+ // arrive at the controller as a bare name with no kind.
28
+ if (isRefSentinel(doc))
29
+ return doc;
22
30
  if (isTaggedSentinel(doc)) {
23
31
  const engine = defaultRegistry().get(doc.engine);
24
32
  if (!engine) {
@@ -98,7 +98,7 @@ export function buildReferenceFieldMap(schema) {
98
98
  const map = new Map();
99
99
  if (schema.properties) {
100
100
  for (const [key, propSchema] of Object.entries(schema.properties)) {
101
- traverseNode(propSchema, key, map);
101
+ traverseNode(propSchema, key, map, schema);
102
102
  }
103
103
  }
104
104
  return map;
@@ -123,10 +123,23 @@ function collectRefs(node) {
123
123
  * hid behind the schema-from indirection. */
124
124
  export function buildFieldMapAtPath(schema, pathPrefix) {
125
125
  const map = new Map();
126
- traverseNode(schema, pathPrefix, map);
126
+ traverseNode(schema, pathPrefix, map, schema);
127
127
  return map;
128
128
  }
129
- function traverseNode(node, path, map) {
129
+ function traverseNode(node, path, map, root, visitedRefs = new Set()) {
130
+ // Local `$ref` is intentionally NOT followed. Descending into shared
131
+ // `$defs` (notably `Run.Sequence`'s `step` definition) would surface
132
+ // ref slots like `steps[].invoke` that Phase 5 then injects live
133
+ // instances into; today's `Run.Sequence` controller calls
134
+ // `instance.invoke()` directly when handed an instance, bypassing
135
+ // the kernel's `runInvoke` emit-Invoked path. The walker fix and the
136
+ // dispatcher fix need to land together — see the follow-up in
137
+ // [kernel/nodejs/plans/reference-syntax-unification.md] and the
138
+ // stopgap in `resource-context.ts:resolveChildren`. `visitedRefs`
139
+ // stays as a parameter so the recursive calls below thread the right
140
+ // signature; turning the descent back on is a single-branch change.
141
+ if (typeof node?.$ref === "string")
142
+ return;
130
143
  // Scope slot — record and stop; do not recurse into scope contents
131
144
  if ("x-telo-scope" in node) {
132
145
  map.set(path, { scope: node["x-telo-scope"] });
@@ -148,12 +161,32 @@ function traverseNode(node, path, map) {
148
161
  }
149
162
  // Array — recurse into items
150
163
  if (node.type === "array" && node.items) {
151
- traverseNode(node.items, path + "[]", map);
164
+ traverseNode(node.items, path + "[]", map, root, visitedRefs);
152
165
  }
153
166
  // Object — recurse into properties
154
167
  if (node.properties) {
155
168
  for (const [key, propSchema] of Object.entries(node.properties)) {
156
- traverseNode(propSchema, `${path}.${key}`, map);
169
+ traverseNode(propSchema, `${path}.${key}`, map, root, visitedRefs);
170
+ }
171
+ }
172
+ // Variant branches — descend into every alternative's properties / items.
173
+ // Schemas that discriminate on shape (Run.Sequence's step kinds:
174
+ // `oneOf: [{properties: {invoke}}, {properties: {try}}, ...]`) hide ref
175
+ // slots inside the branch. Walking each branch surfaces those slots into
176
+ // the field map so downstream passes (ref validation, sentinel
177
+ // resolution, dependency graph) cover them without a runtime fallback.
178
+ // The same field path may be added by multiple branches; the later
179
+ // assignment wins, which is fine — branches with the same field path
180
+ // share the same ref/context configuration (any divergence is already
181
+ // a schema bug).
182
+ for (const variantKey of ["oneOf", "anyOf", "allOf"]) {
183
+ const variants = node[variantKey];
184
+ if (!Array.isArray(variants))
185
+ continue;
186
+ for (const variant of variants) {
187
+ if (!variant || typeof variant !== "object")
188
+ continue;
189
+ traverseVariant(variant, path, map, root, visitedRefs);
157
190
  }
158
191
  }
159
192
  // Map — `additionalProperties: { ... }` describes every value in an
@@ -162,6 +195,25 @@ function traverseNode(node, path, map) {
162
195
  if (node.additionalProperties &&
163
196
  typeof node.additionalProperties === "object" &&
164
197
  !Array.isArray(node.additionalProperties)) {
165
- traverseNode(node.additionalProperties, `${path}.{}`, map);
198
+ traverseNode(node.additionalProperties, `${path}.{}`, map, root, visitedRefs);
199
+ }
200
+ }
201
+ /** Walk a single variant of a `oneOf` / `anyOf` / `allOf` branch. Only
202
+ * the properties / items / map slots are followed — collectRefs at the
203
+ * variant root is handled by the parent's `collectRefs(node)` already
204
+ * (anyOf of x-telo-ref branches is the canonical multi-ref shape). */
205
+ function traverseVariant(variant, path, map, root, visitedRefs = new Set()) {
206
+ if (variant.properties) {
207
+ for (const [key, propSchema] of Object.entries(variant.properties)) {
208
+ traverseNode(propSchema, `${path}.${key}`, map, root, visitedRefs);
209
+ }
210
+ }
211
+ if (variant.type === "array" && variant.items) {
212
+ traverseNode(variant.items, path + "[]", map, root, visitedRefs);
213
+ }
214
+ if (variant.additionalProperties &&
215
+ typeof variant.additionalProperties === "object" &&
216
+ !Array.isArray(variant.additionalProperties)) {
217
+ traverseNode(variant.additionalProperties, `${path}.{}`, map, root, visitedRefs);
166
218
  }
167
219
  }
@@ -0,0 +1,27 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import type { AliasResolver } from "./alias-resolver.js";
3
+ import type { DefinitionRegistry } from "./definition-registry.js";
4
+ /**
5
+ * Walks every `x-telo-ref` slot in every non-system resource and rewrites
6
+ * `!ref <name>` sentinels in-place to `{kind: <resolved-kind>, name}`.
7
+ *
8
+ * The downstream pipeline (inline normalization, dependency graph, kernel
9
+ * controllers) expects every ref-slot value to be either a `{kind, name}`
10
+ * object, an inline-definition object, or a legacy bare string — resolving
11
+ * sentinels here keeps that contract intact so each consumer doesn't need
12
+ * its own sentinel branch.
13
+ *
14
+ * The walker assigns `kind` by name lookup (resource names are unique
15
+ * within a manifest scope). When the name doesn't resolve in the local
16
+ * `byName` map, the sentinel is left in place so `validateReferences`
17
+ * can emit the `UNRESOLVED_REFERENCE` diagnostic with full context.
18
+ *
19
+ * Mutation strategy: the field-path walker descends the resource tree
20
+ * directly and replaces the sentinel on its parent container. Re-parsing
21
+ * a string-encoded concrete path (the earlier shape) coupled the writer
22
+ * to the path-encoding rules of `resolveFieldEntries` — any new path
23
+ * marker would silently break this writer. Descending directly avoids
24
+ * that coupling.
25
+ */
26
+ export declare function resolveRefSentinels(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>): void;
27
+ //# sourceMappingURL=resolve-ref-sentinels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-ref-sentinels.d.ts","sourceRoot":"","sources":["../src/resolve-ref-sentinels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAInE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,IAAI,CAsBN"}
@@ -0,0 +1,114 @@
1
+ import { isRefSentinel } from "@telorun/templating";
2
+ import { isRefEntry } from "./reference-field-map.js";
3
+ import { REF_RESOLUTION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
4
+ /**
5
+ * Walks every `x-telo-ref` slot in every non-system resource and rewrites
6
+ * `!ref <name>` sentinels in-place to `{kind: <resolved-kind>, name}`.
7
+ *
8
+ * The downstream pipeline (inline normalization, dependency graph, kernel
9
+ * controllers) expects every ref-slot value to be either a `{kind, name}`
10
+ * object, an inline-definition object, or a legacy bare string — resolving
11
+ * sentinels here keeps that contract intact so each consumer doesn't need
12
+ * its own sentinel branch.
13
+ *
14
+ * The walker assigns `kind` by name lookup (resource names are unique
15
+ * within a manifest scope). When the name doesn't resolve in the local
16
+ * `byName` map, the sentinel is left in place so `validateReferences`
17
+ * can emit the `UNRESOLVED_REFERENCE` diagnostic with full context.
18
+ *
19
+ * Mutation strategy: the field-path walker descends the resource tree
20
+ * directly and replaces the sentinel on its parent container. Re-parsing
21
+ * a string-encoded concrete path (the earlier shape) coupled the writer
22
+ * to the path-encoding rules of `resolveFieldEntries` — any new path
23
+ * marker would silently break this writer. Descending directly avoids
24
+ * that coupling.
25
+ */
26
+ export function resolveRefSentinels(resources, registry, aliases, aliasesByModule) {
27
+ const byName = new Map();
28
+ for (const r of resources) {
29
+ if (r.metadata?.name && !SYSTEM_KINDS.has(r.kind)) {
30
+ byName.set(r.metadata.name, r);
31
+ }
32
+ }
33
+ for (const r of resources) {
34
+ if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind))
35
+ continue;
36
+ const fieldMap = aliases && aliasesByModule
37
+ ? registry.expandedFieldMapForResource(r, aliases, aliasesByModule)
38
+ : registry.getFieldMapForKind(r.kind, aliases);
39
+ if (!fieldMap)
40
+ continue;
41
+ for (const [fieldPath, entry] of fieldMap) {
42
+ if (!isRefEntry(entry))
43
+ continue;
44
+ replaceSentinelsAtPath(r, fieldPath, byName);
45
+ }
46
+ }
47
+ }
48
+ /** Walks `obj` along `fieldPath` (dot notation with `[]` for arrays and
49
+ * `{}` for additionalProperties-typed maps) and replaces any `!ref`
50
+ * sentinel value at the terminal slot with `{kind, name}` looked up
51
+ * via `byName`. Mutates the parent container in place; no string-path
52
+ * round-trip. */
53
+ function replaceSentinelsAtPath(obj, fieldPath, byName) {
54
+ const parts = fieldPath.split(".");
55
+ descend(obj, parts, byName);
56
+ }
57
+ function descend(obj, parts, byName) {
58
+ if (obj == null || typeof obj !== "object" || parts.length === 0)
59
+ return;
60
+ const [head, ...rest] = parts;
61
+ // Map iteration: descend into every value of the current object.
62
+ if (head === "{}") {
63
+ const container = obj;
64
+ for (const key of Object.keys(container)) {
65
+ const child = container[key];
66
+ if (rest.length === 0) {
67
+ if (isRefSentinel(child)) {
68
+ const target = byName.get(child.source);
69
+ if (target)
70
+ container[key] = { kind: target.kind, name: child.source };
71
+ }
72
+ }
73
+ else {
74
+ descend(child, rest, byName);
75
+ }
76
+ }
77
+ return;
78
+ }
79
+ const isArr = head.endsWith("[]");
80
+ const key = isArr ? head.slice(0, -2) : head;
81
+ const container = obj;
82
+ const val = container[key];
83
+ if (val == null)
84
+ return;
85
+ if (isArr) {
86
+ if (!Array.isArray(val))
87
+ return;
88
+ for (let i = 0; i < val.length; i++) {
89
+ if (rest.length === 0) {
90
+ const elem = val[i];
91
+ if (isRefSentinel(elem)) {
92
+ const target = byName.get(elem.source);
93
+ if (target)
94
+ val[i] = { kind: target.kind, name: elem.source };
95
+ }
96
+ }
97
+ else {
98
+ descend(val[i], rest, byName);
99
+ }
100
+ }
101
+ }
102
+ else {
103
+ if (rest.length === 0) {
104
+ if (isRefSentinel(val)) {
105
+ const target = byName.get(val.source);
106
+ if (target)
107
+ container[key] = { kind: target.kind, name: val.source };
108
+ }
109
+ }
110
+ else {
111
+ descend(val, rest, byName);
112
+ }
113
+ }
114
+ }
@@ -1,6 +1,12 @@
1
1
  declare const Ajv: any;
2
2
  /** Creates a configured AJV instance (allErrors, strict: false, with formats).
3
- * Called once for the module-level instance and once per DefinitionRegistry instance. */
3
+ * Also registers the kernel manifest root schema under `telo://manifest` so
4
+ * module YAMLs can `$ref` into the shared `$defs/ResourceRef` (and any future
5
+ * shared fragments) from this analyzer's AJV without each module having to
6
+ * bundle its own copy.
7
+ *
8
+ * Called once for the module-level instance and once per
9
+ * DefinitionRegistry instance. */
4
10
  export declare function createAjv(): InstanceType<typeof Ajv>;
5
11
  export interface CompatibilityResult {
6
12
  compatible: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;0FAC0F;AAC1F,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAMpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CAe/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAuBnF;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAqBhG;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAiB5E;AA2BD;iGACiG;AACjG,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,OAAO,CA2BT"}
1
+ {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAOpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CAe/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAuBnF;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAqBhG;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAiB5E;AA2BD;iGACiG;AACjG,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,OAAO,CAqCT"}
@@ -1,14 +1,21 @@
1
1
  import AjvModule from "ajv";
2
2
  import addFormats from "ajv-formats";
3
- import { isTaggedSentinel } from "@telorun/templating";
3
+ import { isRefSentinel, isTaggedSentinel, ManifestRootSchema } from "@telorun/templating";
4
4
  const Ajv = AjvModule.default ?? AjvModule;
5
5
  /** Creates a configured AJV instance (allErrors, strict: false, with formats).
6
- * Called once for the module-level instance and once per DefinitionRegistry instance. */
6
+ * Also registers the kernel manifest root schema under `telo://manifest` so
7
+ * module YAMLs can `$ref` into the shared `$defs/ResourceRef` (and any future
8
+ * shared fragments) from this analyzer's AJV without each module having to
9
+ * bundle its own copy.
10
+ *
11
+ * Called once for the module-level instance and once per
12
+ * DefinitionRegistry instance. */
7
13
  export function createAjv() {
8
14
  const instance = new Ajv({ allErrors: true, strict: false });
9
15
  addFormats.default
10
16
  ? addFormats.default(instance)
11
17
  : addFormats(instance);
18
+ instance.addSchema(ManifestRootSchema);
12
19
  return instance;
13
20
  }
14
21
  const ajv = createAjv();
@@ -270,6 +277,16 @@ export function substituteCelFields(data, schema, rootSchema) {
270
277
  if (typeof data === "string" && CEL_PURE_RE.test(data)) {
271
278
  return celPlaceholderForSchema(resolved);
272
279
  }
280
+ // `!ref <name>` sentinels are identity markers, not runtime values —
281
+ // schemas that opt into `$ref: "telo://manifest#/$defs/ResourceRef"`
282
+ // (or `anyOf` it alongside other shapes) need the actual sentinel
283
+ // object so AJV validates it against ResourceRefSchema. Collapsing it
284
+ // to a CEL placeholder would either fail the schema (when the slot
285
+ // expects the ResourceRef shape) or mask validation errors (when the
286
+ // slot expects something else entirely).
287
+ if (isRefSentinel(data)) {
288
+ return data;
289
+ }
273
290
  if (isTaggedSentinel(data)) {
274
291
  return celPlaceholderForSchema(resolved);
275
292
  }
@@ -0,0 +1,25 @@
1
+ /** Resource-kind sets used by analysis passes to decide what counts as a
2
+ * user-defined instance vs. a system-level blueprint. Pulled into one
3
+ * place so the three passes (reference validation, dependency graph,
4
+ * ref-sentinel resolution) don't drift; each pass exports its own
5
+ * scoped view with a comment explaining what's in and what's out. */
6
+ /** Skipped by reference validation: type blueprints whose own ref slots
7
+ * belong to a different phase (definition schema validation rather than
8
+ * per-resource validation). Telo.Application and Telo.Library
9
+ * intentionally fall through — Application has `targets` (real refs) and
10
+ * Library is harmless (no ref-bearing fields). Telo.Import is also
11
+ * intentionally not skipped — its `source` is not an x-telo-ref slot, so
12
+ * walking it is cheap and consistent. */
13
+ export declare const REF_VALIDATION_SKIP_KINDS: ReadonlySet<string>;
14
+ /** Excluded from the dependency graph: kinds that are not runtime nodes.
15
+ * Telo.Abstract is intentionally not in this set today — abstracts have
16
+ * no resource manifests, so they never reach graph construction; if
17
+ * that ever changes, add it explicitly. */
18
+ export declare const DEPENDENCY_GRAPH_SKIP_KINDS: ReadonlySet<string>;
19
+ /** Skipped by `!ref` sentinel resolution: kinds whose bodies are
20
+ * blueprints or import-time metadata, not resource instances with
21
+ * user-referenced ref slots. Mirrors `REF_VALIDATION_SKIP_KINDS` but
22
+ * also drops Telo.Import (its `source` isn't a ref slot, and walking
23
+ * the field map on it is pointless since there's no registered kind). */
24
+ export declare const REF_RESOLUTION_SKIP_KINDS: ReadonlySet<string>;
25
+ //# sourceMappingURL=system-kinds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-kinds.d.ts","sourceRoot":"","sources":["../src/system-kinds.ts"],"names":[],"mappings":"AAAA;;;;sEAIsE;AAEtE;;;;;;0CAM0C;AAC1C,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAGxD,CAAC;AAEH;;;4CAG4C;AAC5C,eAAO,MAAM,2BAA2B,EAAE,WAAW,CAAC,MAAM,CAG1D,CAAC;AAEH;;;;0EAI0E;AAC1E,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAIxD,CAAC"}
@@ -0,0 +1,34 @@
1
+ /** Resource-kind sets used by analysis passes to decide what counts as a
2
+ * user-defined instance vs. a system-level blueprint. Pulled into one
3
+ * place so the three passes (reference validation, dependency graph,
4
+ * ref-sentinel resolution) don't drift; each pass exports its own
5
+ * scoped view with a comment explaining what's in and what's out. */
6
+ /** Skipped by reference validation: type blueprints whose own ref slots
7
+ * belong to a different phase (definition schema validation rather than
8
+ * per-resource validation). Telo.Application and Telo.Library
9
+ * intentionally fall through — Application has `targets` (real refs) and
10
+ * Library is harmless (no ref-bearing fields). Telo.Import is also
11
+ * intentionally not skipped — its `source` is not an x-telo-ref slot, so
12
+ * walking it is cheap and consistent. */
13
+ export const REF_VALIDATION_SKIP_KINDS = new Set([
14
+ "Telo.Definition",
15
+ "Telo.Abstract",
16
+ ]);
17
+ /** Excluded from the dependency graph: kinds that are not runtime nodes.
18
+ * Telo.Abstract is intentionally not in this set today — abstracts have
19
+ * no resource manifests, so they never reach graph construction; if
20
+ * that ever changes, add it explicitly. */
21
+ export const DEPENDENCY_GRAPH_SKIP_KINDS = new Set([
22
+ "Telo.Definition",
23
+ "Telo.Import",
24
+ ]);
25
+ /** Skipped by `!ref` sentinel resolution: kinds whose bodies are
26
+ * blueprints or import-time metadata, not resource instances with
27
+ * user-referenced ref slots. Mirrors `REF_VALIDATION_SKIP_KINDS` but
28
+ * also drops Telo.Import (its `source` isn't a ref slot, and walking
29
+ * the field map on it is pointless since there's no registered kind). */
30
+ export const REF_RESOLUTION_SKIP_KINDS = new Set([
31
+ "Telo.Definition",
32
+ "Telo.Abstract",
33
+ "Telo.Import",
34
+ ]);
@@ -1 +1 @@
1
- {"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAkD/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA2UtB"}
1
+ {"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA4C/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CAkZtB"}