@telorun/analyzer 0.68.0 → 0.70.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 (82) hide show
  1. package/dist/analysis-registry.d.ts +12 -0
  2. package/dist/analysis-registry.d.ts.map +1 -1
  3. package/dist/analysis-registry.js +26 -0
  4. package/dist/call-graph.d.ts +80 -1
  5. package/dist/call-graph.d.ts.map +1 -1
  6. package/dist/call-graph.js +145 -12
  7. package/dist/canonical-json.d.ts +18 -0
  8. package/dist/canonical-json.d.ts.map +1 -0
  9. package/dist/canonical-json.js +26 -0
  10. package/dist/cel-access-chains.d.ts +14 -0
  11. package/dist/cel-access-chains.d.ts.map +1 -0
  12. package/dist/cel-access-chains.js +45 -0
  13. package/dist/import-resolution-diagnostics.d.ts.map +1 -1
  14. package/dist/import-resolution-diagnostics.js +22 -8
  15. package/dist/index.d.ts +7 -5
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +5 -4
  18. package/dist/loaded-types.d.ts +4 -4
  19. package/dist/loaded-types.d.ts.map +1 -1
  20. package/dist/manifest-analysis.d.ts +19 -0
  21. package/dist/manifest-analysis.d.ts.map +1 -1
  22. package/dist/manifest-analysis.js +27 -0
  23. package/dist/manifest-diff.d.ts +111 -0
  24. package/dist/manifest-diff.d.ts.map +1 -0
  25. package/dist/manifest-diff.js +130 -0
  26. package/dist/manifest-loader.d.ts +3 -4
  27. package/dist/manifest-loader.d.ts.map +1 -1
  28. package/dist/manifest-loader.js +4 -5
  29. package/dist/manifest-schemas.d.ts +2 -0
  30. package/dist/manifest-schemas.d.ts.map +1 -1
  31. package/dist/manifest-schemas.js +4 -0
  32. package/dist/module-graph.d.ts +500 -0
  33. package/dist/module-graph.d.ts.map +1 -0
  34. package/dist/module-graph.js +1389 -0
  35. package/dist/reconcile-module-versions.d.ts.map +1 -1
  36. package/dist/reconcile-module-versions.js +10 -11
  37. package/dist/release/release-plan.d.ts +1 -1
  38. package/dist/resolve-zone-containment.d.ts +9 -1
  39. package/dist/resolve-zone-containment.d.ts.map +1 -1
  40. package/dist/resolve-zone-containment.js +34 -6
  41. package/dist/resolve-zone-requirements.d.ts.map +1 -1
  42. package/dist/resolve-zone-requirements.js +4 -2
  43. package/dist/sources/default-sources.d.ts +6 -6
  44. package/dist/sources/default-sources.d.ts.map +1 -1
  45. package/dist/sources/default-sources.js +7 -8
  46. package/dist/sources/integrity.d.ts +3 -2
  47. package/dist/sources/integrity.d.ts.map +1 -1
  48. package/dist/sources/integrity.js +26 -3
  49. package/dist/sources/versioned-ref.d.ts +17 -12
  50. package/dist/sources/versioned-ref.d.ts.map +1 -1
  51. package/dist/sources/versioned-ref.js +22 -24
  52. package/dist/telo-version.d.ts +1 -1
  53. package/dist/telo-version.js +1 -1
  54. package/package.json +2 -2
  55. package/src/analysis-registry.ts +37 -0
  56. package/src/call-graph.ts +207 -14
  57. package/src/canonical-json.ts +24 -0
  58. package/src/cel-access-chains.ts +47 -0
  59. package/src/import-resolution-diagnostics.ts +24 -7
  60. package/src/index.ts +46 -5
  61. package/src/loaded-types.ts +4 -4
  62. package/src/manifest-analysis.ts +39 -0
  63. package/src/manifest-diff.ts +219 -0
  64. package/src/manifest-loader.ts +4 -5
  65. package/src/manifest-schemas.ts +4 -0
  66. package/src/module-graph.ts +1983 -0
  67. package/src/reconcile-module-versions.ts +10 -11
  68. package/src/release/release-plan.ts +1 -1
  69. package/src/resolve-zone-containment.ts +49 -9
  70. package/src/resolve-zone-requirements.ts +7 -2
  71. package/src/sources/default-sources.ts +7 -8
  72. package/src/sources/integrity.ts +28 -3
  73. package/src/sources/versioned-ref.ts +26 -28
  74. package/src/telo-version.ts +1 -1
  75. package/dist/sources/module-ref.d.ts +0 -21
  76. package/dist/sources/module-ref.d.ts.map +0 -1
  77. package/dist/sources/module-ref.js +0 -36
  78. package/dist/sources/registry-source.d.ts +0 -14
  79. package/dist/sources/registry-source.d.ts.map +0 -1
  80. package/dist/sources/registry-source.js +0 -45
  81. package/src/sources/module-ref.ts +0 -49
  82. package/src/sources/registry-source.ts +0 -52
@@ -1,4 +1,5 @@
1
1
  import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
2
+ import type { BuildModuleGraphOptions, ModuleGraphDeps } from "./module-graph.js";
2
3
  import { ManifestAnalysis } from "./manifest-analysis.js";
3
4
  import { type ManifestVisitor } from "./manifest-visitor.js";
4
5
  import type { AnalysisContext } from "./types.js";
@@ -138,6 +139,17 @@ export declare class AnalysisRegistry {
138
139
  * function of the whole manifest set.
139
140
  */
140
141
  analysisOf(manifests: ResourceManifest[]): ManifestAnalysis;
142
+ /**
143
+ * What the projection needs that only this registry knows: how a kind
144
+ * resolves, what its reference slots are, which capabilities they target, and
145
+ * what a resource can raise.
146
+ *
147
+ * The GRAPH itself is built by `ManifestAnalysis.moduleGraph`, which is the
148
+ * pairing of a registry with a manifest set — a fifth
149
+ * `registry.x(manifests, …)` factory is the accretion naming that class
150
+ * stopped. This is the half the registry owns.
151
+ */
152
+ moduleGraphDeps(manifests: ResourceManifest[], options?: BuildModuleGraphOptions): ModuleGraphDeps;
141
153
  /**
142
154
  * Returns the built-in kernel definitions. The underlying DefinitionRegistry already
143
155
  * seeds these on construction; this method exposes them so callers (e.g. the kernel's
@@ -1 +1 @@
1
- {"version":3,"file":"analysis-registry.d.ts","sourceRoot":"","sources":["../src/analysis-registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"analysis-registry.d.ts","sourceRoot":"","sources":["../src/analysis-registry.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import { DefinitionRegistry } from "./definition-registry.js";
5
5
  import { computeSuggestKind, computeValidUserFacingKinds } from "./kind-suggest.js";
6
6
  import { visitManifest as runVisitManifest } from "./manifest-visitor.js";
7
7
  import { resolveContract } from "./invocation-contract.js";
8
+ import { createResolveCtx, resolveThrowsUnion } from "./resolve-throws-union.js";
8
9
  import { isRefEntry, isScopeEntry } from "./reference-field-map.js";
9
10
  import { resolveSchemaTypeRefs as resolveSchemaTypeRefsIn } from "./resolve-schema-type-refs.js";
10
11
  /**
@@ -226,6 +227,31 @@ export class AnalysisRegistry {
226
227
  aliasesByModule: this.aliasesByModule,
227
228
  });
228
229
  }
230
+ /**
231
+ * What the projection needs that only this registry knows: how a kind
232
+ * resolves, what its reference slots are, which capabilities they target, and
233
+ * what a resource can raise.
234
+ *
235
+ * The GRAPH itself is built by `ManifestAnalysis.moduleGraph`, which is the
236
+ * pairing of a registry with a manifest set — a fifth
237
+ * `registry.x(manifests, …)` factory is the accretion naming that class
238
+ * stopped. This is the half the registry owns.
239
+ */
240
+ moduleGraphDeps(manifests, options = {}) {
241
+ // The error contract is resolved through the SAME resolver `telo check`
242
+ // uses, memoized across the whole graph — so what a box says it can raise
243
+ // is what a route's `catches:` is validated against.
244
+ const throwsCtx = createResolveCtx(manifests, this.defs, this.aliases, this.aliasesByModule, new Set(options.entryModule ? [options.entryModule] : []));
245
+ return {
246
+ refFields: (resource) => this.refFieldsForResource(resource),
247
+ definition: (kind, module) => this.resolveDefinitionIn(kind, module),
248
+ aliasesForModule: (module) => this.aliases.aliasesFor(module),
249
+ throwsOf: (resource) => {
250
+ const union = resolveThrowsUnion(resource, throwsCtx);
251
+ return { codes: [...union.codes.keys()], unbounded: union.unbounded };
252
+ },
253
+ };
254
+ }
229
255
  /**
230
256
  * Returns the built-in kernel definitions. The underlying DefinitionRegistry already
231
257
  * seeds these on construction; this method exposes them so callers (e.g. the kernel's
@@ -74,6 +74,47 @@ export interface StepGraphNode {
74
74
  index: number;
75
75
  /** The step value as written. */
76
76
  step: Record<string, unknown>;
77
+ /**
78
+ * Which branch of the step grammar this step IS — the branch's own title
79
+ * (`invoke`, `if/then/else`, `while/do`, `switch/cases/default`,
80
+ * `try/catch/finally`, `throw`, `value`).
81
+ *
82
+ * Matched on the branch's REQUIRED keys, and IS those keys — sorted and
83
+ * joined (`if+then`, `invoke`, `do+while`). A machine identity, so a consumer
84
+ * may branch on it; it was the branch's `title`, which is prose an author may
85
+ * reword at any time, and a view was splitting it on `/` to recover a keyword.
86
+ * Absent for a step matching no branch — one just added, and still empty.
87
+ */
88
+ variant?: string;
89
+ /** The branch's `title` as its author wrote it, for a view to render verbatim.
90
+ * Never parsed: it is a label, and the moment anything reads its SHAPE it
91
+ * becomes a vocabulary the author cannot change. */
92
+ variantLabel?: string;
93
+ /**
94
+ * The expression that decides whether or how this step runs, as written: an
95
+ * `if:`, a `while:`, a `switch:`, or the `when:` guard on a dispatch.
96
+ *
97
+ * Found through `x-telo-topology-role`, never by keyword, so a third-party
98
+ * composer annotating its own predicate is read the same way.
99
+ */
100
+ predicate?: string;
101
+ /**
102
+ * Every reference slot this step's grammar declares — filled, empty, or
103
+ * holding a declaration written at the site.
104
+ *
105
+ * Recorded here because this is the one place a step's item schema and its
106
+ * value are both in hand: a step array's items sit behind a local `$ref`, so
107
+ * the reference field map deliberately never reaches them, and a consumer
108
+ * asking "what may this step dispatch to, and where is that written" has
109
+ * nowhere else to look. An EMPTY slot is listed for exactly that reason — it
110
+ * is a site an editor can offer to fill, and an edge says nothing about one.
111
+ */
112
+ refSlots?: {
113
+ key: string;
114
+ path: string;
115
+ kinds: string[];
116
+ inline?: boolean;
117
+ }[];
77
118
  }
78
119
  export type CallGraphNode = ResourceGraphNode | StepGraphNode;
79
120
  export interface CallGraphEdge {
@@ -133,7 +174,15 @@ export interface CallGraph {
133
174
  /** Edges arriving at a resource node. */
134
175
  edgesTo(id: string): CallGraphEdge[];
135
176
  resource(kind: string, name: string): ResourceGraphNode | undefined;
136
- resourceByName(name: string): ResourceGraphNode | undefined;
177
+ /**
178
+ * The resource a bare name addresses, read from `fromModule`'s scope.
179
+ *
180
+ * A name is module-scoped, so the module is how two declarations of one name
181
+ * are told apart — see {@link resolveScopedName}. Omitting it answers for a
182
+ * name declared once and, where several modules declare it, declines rather
183
+ * than taking whichever came first.
184
+ */
185
+ resourceByName(name: string, fromModule?: string): ResourceGraphNode | undefined;
137
186
  /** Step nodes declared by a resource, in lexical order. */
138
187
  steps(resourceId: string): StepGraphNode[];
139
188
  /** Every edge whose `use` includes at least one control transfer, plus every
@@ -141,6 +190,36 @@ export interface CallGraph {
141
190
  controlEdges(): CallGraphEdge[];
142
191
  }
143
192
  export declare const resourceId: (kind: string, name: string) => string;
193
+ /**
194
+ * The id a manifest's node is keyed by.
195
+ *
196
+ * **A resource name is MODULE-SCOPED**, so `(kind, name)` is unique inside one
197
+ * module and not across a flattened set: two libraries each exporting an
198
+ * `Http.Api` named `routes` are two declarations, and keying both as
199
+ * `http.Api\0routes` silently made them one — the second overwrote the first,
200
+ * taking its edges and its steps with it. The module is part of the identity
201
+ * wherever the loader stamped one; a manifest with no stamp keeps the bare form,
202
+ * so nothing that never crossed a module boundary moves.
203
+ */
204
+ export declare function nodeIdFor(manifest: ResourceManifest): string;
205
+ /**
206
+ * A bare `<name>` read from a resource declared in `fromModule`.
207
+ *
208
+ * **Same module first**, because that is what a bare name MEANS: it is resolved
209
+ * in the scope it was written in, and a cross-module reference is written with
210
+ * an alias. A name declared in exactly one module resolves to it whatever the
211
+ * reader's scope — which is what keeps an unstamped manifest set (every
212
+ * fixture, every single-module load) behaving as it did. A name declared in
213
+ * SEVERAL other modules and none of the reader's resolves to NOTHING: guessing
214
+ * between them attributes a reference to a resource the author never named, and
215
+ * some other pass reports the dangling name honestly.
216
+ *
217
+ * Exported because more than one pass resolves a bare name — the call graph's
218
+ * own sites, and the projection's inline declarations and CEL state reads. Two
219
+ * spellings of this rule is how a fix in one leaves a first-wins lookup in the
220
+ * other, which is exactly the collision it exists to prevent.
221
+ */
222
+ export declare function resolveScopedName<T>(candidates: readonly T[] | undefined, moduleOf: (item: T) => string | undefined, fromModule: string | undefined): T | undefined;
144
223
  export interface BuildCallGraphOptions {
145
224
  aliases?: AliasResolver;
146
225
  aliasesByModule?: Map<string, AliasResolver>;
@@ -1 +1 @@
1
- {"version":3,"file":"call-graph.d.ts","sourceRoot":"","sources":["../src/call-graph.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"call-graph.d.ts","sourceRoot":"","sources":["../src/call-graph.ts"],"names":[],"mappings":"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"}
@@ -3,9 +3,52 @@ import { enclosingOf, propertySchemas, resolveLocalRef, } from "./manifest-navig
3
3
  import { visitManifest } from "./manifest-visitor.js";
4
4
  import { possibleUses, readRefSlot, transfersControl, } from "./ref-slot.js";
5
5
  import { isStepSlot } from "./step-slot.js";
6
- import { isRefEntry, resolveFieldEntries } from "./reference-field-map.js";
6
+ import { isInlineResource, isRefEntry, resolveFieldEntries, } from "./reference-field-map.js";
7
7
  import { DEPENDENCY_GRAPH_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
8
8
  export const resourceId = (kind, name) => `${kind}\0${name}`;
9
+ /**
10
+ * The id a manifest's node is keyed by.
11
+ *
12
+ * **A resource name is MODULE-SCOPED**, so `(kind, name)` is unique inside one
13
+ * module and not across a flattened set: two libraries each exporting an
14
+ * `Http.Api` named `routes` are two declarations, and keying both as
15
+ * `http.Api\0routes` silently made them one — the second overwrote the first,
16
+ * taking its edges and its steps with it. The module is part of the identity
17
+ * wherever the loader stamped one; a manifest with no stamp keeps the bare form,
18
+ * so nothing that never crossed a module boundary moves.
19
+ */
20
+ export function nodeIdFor(manifest) {
21
+ const kind = manifest.kind;
22
+ const name = manifest.metadata?.name;
23
+ const module = manifest.metadata?.module;
24
+ return module ? `${module}\0${resourceId(kind, name)}` : resourceId(kind, name);
25
+ }
26
+ /** The module a manifest was declared in, when the loader stamped one. */
27
+ const declaringModule = (manifest) => manifest.metadata?.module;
28
+ /**
29
+ * A bare `<name>` read from a resource declared in `fromModule`.
30
+ *
31
+ * **Same module first**, because that is what a bare name MEANS: it is resolved
32
+ * in the scope it was written in, and a cross-module reference is written with
33
+ * an alias. A name declared in exactly one module resolves to it whatever the
34
+ * reader's scope — which is what keeps an unstamped manifest set (every
35
+ * fixture, every single-module load) behaving as it did. A name declared in
36
+ * SEVERAL other modules and none of the reader's resolves to NOTHING: guessing
37
+ * between them attributes a reference to a resource the author never named, and
38
+ * some other pass reports the dangling name honestly.
39
+ *
40
+ * Exported because more than one pass resolves a bare name — the call graph's
41
+ * own sites, and the projection's inline declarations and CEL state reads. Two
42
+ * spellings of this rule is how a fix in one leaves a first-wins lookup in the
43
+ * other, which is exactly the collision it exists to prevent.
44
+ */
45
+ export function resolveScopedName(candidates, moduleOf, fromModule) {
46
+ if (!candidates || candidates.length === 0)
47
+ return undefined;
48
+ if (candidates.length === 1)
49
+ return candidates[0];
50
+ return candidates.find((candidate) => moduleOf(candidate) === fromModule);
51
+ }
9
52
  /**
10
53
  * Does control reach this edge's target?
11
54
  *
@@ -141,6 +184,64 @@ function isPlainRefValue(value) {
141
184
  return false;
142
185
  return Object.keys(value).every((k) => k === "kind" || k === "name" || k === "alias" || k === "__ref");
143
186
  }
187
+ /**
188
+ * What a step IS, and what decides whether it runs.
189
+ *
190
+ * Both come off the schema rather than off a keyword list: the variant is the
191
+ * `oneOf` branch whose REQUIRED keys the step carries, and the predicate is the
192
+ * field annotated as one. A composer that declares a step body of its own is
193
+ * therefore described in its own words, and nothing here knows that `while`
194
+ * exists.
195
+ *
196
+ * A step matching several branches takes the FIRST — the grammar is a `oneOf`,
197
+ * so more than one match is a manifest the checker rejects, and guessing among
198
+ * them would be a second opinion on a question the checker already answers.
199
+ */
200
+ function classifyStep(step, itemSchema, rootSchema) {
201
+ if (!itemSchema)
202
+ return {};
203
+ const branches = Array.isArray(itemSchema.oneOf) ? itemSchema.oneOf : [];
204
+ let variant;
205
+ let variantLabel;
206
+ for (const raw of branches) {
207
+ const branch = resolveLocalRef(raw, rootSchema);
208
+ const required = Array.isArray(branch?.required) ? branch.required : [];
209
+ if (required.length === 0 || !required.every((key) => step[key] !== undefined))
210
+ continue;
211
+ // The keys are what the branch IS; the title is what it is called.
212
+ variant = [...required].sort().join("+");
213
+ if (typeof branch?.title === "string")
214
+ variantLabel = branch.title;
215
+ break;
216
+ }
217
+ let predicate;
218
+ for (const [key, propSchema] of propertySchemas(itemSchema)) {
219
+ const role = propSchema?.["x-telo-topology-role"];
220
+ if (role !== "predicate" && role !== "discriminator")
221
+ continue;
222
+ const written = expressionSource(step[key]);
223
+ if (written !== undefined) {
224
+ predicate = written;
225
+ break;
226
+ }
227
+ }
228
+ return {
229
+ ...(variant ? { variant } : {}),
230
+ ...(variantLabel ? { variantLabel } : {}),
231
+ ...(predicate ? { predicate } : {}),
232
+ };
233
+ }
234
+ /** A predicate as the author wrote it. A CEL value reaches here as a tagged
235
+ * sentinel or as an already-compiled node, and the SOURCE is the only part of
236
+ * either worth showing. */
237
+ function expressionSource(value) {
238
+ if (typeof value === "string")
239
+ return value;
240
+ if (value === null || typeof value !== "object")
241
+ return undefined;
242
+ const record = value;
243
+ return typeof record.source === "string" ? record.source : undefined;
244
+ }
144
245
  /**
145
246
  * Emit the edges a single step's own ref slots declare.
146
247
  *
@@ -158,7 +259,20 @@ function emitStepEdges(node, ctx) {
158
259
  const slot = readRefSlot(propSchema);
159
260
  if (!slot || slot.kinds.length === 0)
160
261
  continue;
161
- const targetName = refTargetName(node.step[key]);
262
+ const written = node.step[key];
263
+ const inline = written !== null &&
264
+ typeof written === "object" &&
265
+ !Array.isArray(written) &&
266
+ isInlineResource(written);
267
+ // Every declared slot is recorded, whatever is in it: an empty one is a
268
+ // site an editor can fill, and a declaration written at the site is a real
269
+ // dispatch that emits no edge — which is what made an inline step read
270
+ // exactly like one that dispatches nothing.
271
+ node.refSlots = [
272
+ ...(node.refSlots ?? []),
273
+ { key, path: `${node.path}.${key}`, kinds: slot.kinds, ...(inline ? { inline: true } : {}) },
274
+ ];
275
+ const targetName = refTargetName(written);
162
276
  if (targetName === undefined)
163
277
  continue;
164
278
  const entry = {
@@ -252,6 +366,13 @@ function walkSteps(steps, arrayPath, parent, ctx) {
252
366
  };
253
367
  if (typeof value.name === "string")
254
368
  node.name = value.name;
369
+ const { variant, variantLabel, predicate } = classifyStep(value, ctx.itemSchema, ctx.rootSchema);
370
+ if (variant)
371
+ node.variant = variant;
372
+ if (variantLabel)
373
+ node.variantLabel = variantLabel;
374
+ if (predicate)
375
+ node.predicate = predicate;
255
376
  if (parent)
256
377
  node.parent = parent;
257
378
  ctx.nodes.set(id, node);
@@ -296,6 +417,8 @@ function ownerStepOf(steps, concretePath) {
296
417
  export function buildCallGraph(resources, registry, options = {}) {
297
418
  const nodes = new Map();
298
419
  const edges = [];
420
+ /** Every declaration of a bare name, in declaration order — a name may be
421
+ * declared once per module, so this is a list rather than one node. */
299
422
  const byName = new Map();
300
423
  const stepsByOwner = new Map();
301
424
  const stepEdgesByPath = new Map();
@@ -325,14 +448,17 @@ export function buildCallGraph(resources, registry, options = {}) {
325
448
  continue;
326
449
  const node = {
327
450
  type: "resource",
328
- id: resourceId(manifest.kind, name),
451
+ id: nodeIdFor(manifest),
329
452
  kind: manifest.kind,
330
453
  name: name,
331
454
  manifest,
332
455
  };
456
+ if (nodes.has(node.id))
457
+ continue;
333
458
  nodes.set(node.id, node);
334
- byName.set(node.name, node);
459
+ byName.set(node.name, [...(byName.get(node.name) ?? []), node]);
335
460
  }
461
+ const resolveByName = (name, fromModule) => resolveScopedName(byName.get(name), (node) => declaringModule(node.manifest), fromModule);
336
462
  // --- step nodes ---
337
463
  const collectStepsFor = (node, resolveName) => {
338
464
  const definition = definitionFor(node.manifest);
@@ -362,7 +488,7 @@ export function buildCallGraph(resources, registry, options = {}) {
362
488
  stepsByOwner.set(node.id, collected);
363
489
  };
364
490
  for (const node of [...nodes.values()]) {
365
- collectStepsFor(node, (name) => byName.get(name));
491
+ collectStepsFor(node, (name) => resolveByName(name, declaringModule(node.manifest)));
366
492
  }
367
493
  // --- edges ---
368
494
  // Scope-local nodes of the CURRENT resource. The visitor fires `onScope`
@@ -386,7 +512,7 @@ export function buildCallGraph(resources, registry, options = {}) {
386
512
  const ownerName = event.source.metadata?.name;
387
513
  if (!ownerName || !event.source.kind)
388
514
  return;
389
- const ownerId = resourceId(event.source.kind, ownerName);
515
+ const ownerId = nodeIdFor(event.source);
390
516
  // Inline declarations inside `x-telo-scope` arrays become nodes of
391
517
  // their own, keyed by their scope site — the declaration-site identity
392
518
  // the zones plan correlates on. They are excluded from init ordering
@@ -413,7 +539,8 @@ export function buildCallGraph(resources, registry, options = {}) {
413
539
  }
414
540
  if (scopeLocal.size === 0)
415
541
  return;
416
- const resolveScoped = (name) => scopeLocal.get(name) ?? byName.get(name);
542
+ const ownerModule = declaringModule(event.source);
543
+ const resolveScoped = (name) => scopeLocal.get(name) ?? resolveByName(name, ownerModule);
417
544
  // The owner's own step edges were emitted before this scope was seen
418
545
  // (step collection precedes the visit), so their names resolved
419
546
  // module-level. Re-resolve them now that the scope exists: scope-local
@@ -474,7 +601,7 @@ export function buildCallGraph(resources, registry, options = {}) {
474
601
  const sourceName = event.source.metadata?.name;
475
602
  if (!sourceName || !event.source.kind)
476
603
  return;
477
- const sourceId = resourceId(event.source.kind, sourceName);
604
+ const sourceId = nodeIdFor(event.source);
478
605
  if (!nodes.has(sourceId))
479
606
  return;
480
607
  // A site inside a step was already emitted by the step walk, which
@@ -507,9 +634,10 @@ export function buildCallGraph(resources, registry, options = {}) {
507
634
  path: event.concretePath,
508
635
  use,
509
636
  };
637
+ const sourceModule = declaringModule(event.source);
510
638
  const target = scopedNames.has(targetName)
511
- ? (scopeLocal.get(targetName) ?? byName.get(targetName))
512
- : byName.get(targetName);
639
+ ? (scopeLocal.get(targetName) ?? resolveByName(targetName, sourceModule))
640
+ : resolveByName(targetName, sourceModule);
513
641
  if (target)
514
642
  edge.to = target.id;
515
643
  if (unresolved)
@@ -552,8 +680,13 @@ export function buildCallGraph(resources, registry, options = {}) {
552
680
  edges,
553
681
  edgesFrom: (id) => fromIndex.get(id) ?? [],
554
682
  edgesTo: (id) => toIndex.get(id) ?? [],
555
- resource: (kind, name) => nodes.get(resourceId(kind, name)),
556
- resourceByName: (name) => byName.get(name),
683
+ // Both accessors take a name with no module, so both answer for the one
684
+ // declaration that name has — and, where a flattened set has several, the
685
+ // first in declaration order. A caller that must tell two apart holds a
686
+ // node id, which carries the module.
687
+ resource: (kind, name) => nodes.get(resourceId(kind, name)) ??
688
+ byName.get(name)?.find((n) => n.kind === kind),
689
+ resourceByName: (name, fromModule) => resolveScopedName(byName.get(name), (node) => declaringModule(node.manifest), fromModule),
557
690
  steps: (ownerId) => stepsByOwner.get(ownerId) ?? [],
558
691
  controlEdges: () => edges.filter(reachesTarget),
559
692
  };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Canonical JSON: a rendering under which two structurally equal values produce
3
+ * the same string — object keys sorted, `undefined`-valued entries dropped.
4
+ *
5
+ * Shared rather than duplicated because both readers compare the RESULT for
6
+ * equality: `contentKey` anchors a module-graph row's identity to its content so
7
+ * an inserted sibling does not move it, and `declarationSignature` decides
8
+ * whether a resource has to be rebuilt. Two implementations would eventually
9
+ * disagree about key order or about `undefined`, and the disagreement surfaces
10
+ * as a row that silently changes identity, or as a resource that silently fails
11
+ * to restart.
12
+ *
13
+ * Browser-safe: no hashing, no `node:crypto`. Callers that only need equality
14
+ * compare the strings, which cannot collide; a caller that needs a short key
15
+ * hashes it itself.
16
+ */
17
+ export declare function canonicalJson(value: unknown): string;
18
+ //# sourceMappingURL=canonical-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical-json.d.ts","sourceRoot":"","sources":["../src/canonical-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAOpD"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Canonical JSON: a rendering under which two structurally equal values produce
3
+ * the same string — object keys sorted, `undefined`-valued entries dropped.
4
+ *
5
+ * Shared rather than duplicated because both readers compare the RESULT for
6
+ * equality: `contentKey` anchors a module-graph row's identity to its content so
7
+ * an inserted sibling does not move it, and `declarationSignature` decides
8
+ * whether a resource has to be rebuilt. Two implementations would eventually
9
+ * disagree about key order or about `undefined`, and the disagreement surfaces
10
+ * as a row that silently changes identity, or as a resource that silently fails
11
+ * to restart.
12
+ *
13
+ * Browser-safe: no hashing, no `node:crypto`. Callers that only need equality
14
+ * compare the strings, which cannot collide; a caller that needs a short key
15
+ * hashes it itself.
16
+ */
17
+ export function canonicalJson(value) {
18
+ if (value === null || typeof value !== "object")
19
+ return JSON.stringify(value) ?? "null";
20
+ if (Array.isArray(value))
21
+ return `[${value.map(canonicalJson).join(",")}]`;
22
+ const entries = Object.entries(value)
23
+ .filter(([, v]) => v !== undefined)
24
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
25
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
26
+ }
@@ -0,0 +1,14 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ /** Access chains an expression reads, or none when it does not parse — a syntax
3
+ * error is the engine pass's to report, not this one's. */
4
+ export declare function accessChains(source: string): string[][];
5
+ /**
6
+ * The bare resource names a manifest's expressions read through
7
+ * `resources.<name>`, deduplicated.
8
+ *
9
+ * Read from the DECLARATION: a compile-eval field is expanded before a
10
+ * controller sees it, so by then `resources.db.dsn` is a literal with nothing
11
+ * left to say where it came from.
12
+ */
13
+ export declare function celResourceReads(manifest: ResourceManifest): string[];
14
+ //# sourceMappingURL=cel-access-chains.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cel-access-chains.d.ts","sourceRoot":"","sources":["../src/cel-access-chains.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMrD;4DAC4D;AAC5D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAOvD;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAQrE"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * What a manifest's CEL READS, as opposed to what its slots reference.
3
+ *
4
+ * A dependency a manifest states without a slot: `!cel "resources.db.dsn"` makes
5
+ * the reader depend on `db` as surely as a `!ref` would, and no reference walk
6
+ * can see it. Two consumers ask for it — the module graph draws it as a `data`
7
+ * edge, and the kernel needs it to know who becomes invalid when a resource is
8
+ * rebuilt — so the extraction is here rather than in either of them.
9
+ *
10
+ * The expensive half is the CEL environment used to parse, which is built once
11
+ * per process and evaluates nothing.
12
+ */
13
+ import { extractAccessChains } from "@telorun/templating";
14
+ import { walkCelExpressions } from "@telorun/templating";
15
+ import { buildCelEnvironment } from "./cel-environment.js";
16
+ let parseEnv;
17
+ /** Access chains an expression reads, or none when it does not parse — a syntax
18
+ * error is the engine pass's to report, not this one's. */
19
+ export function accessChains(source) {
20
+ try {
21
+ parseEnv ??= buildCelEnvironment();
22
+ return extractAccessChains(parseEnv.parse(source).ast);
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ }
28
+ /**
29
+ * The bare resource names a manifest's expressions read through
30
+ * `resources.<name>`, deduplicated.
31
+ *
32
+ * Read from the DECLARATION: a compile-eval field is expanded before a
33
+ * controller sees it, so by then `resources.db.dsn` is a literal with nothing
34
+ * left to say where it came from.
35
+ */
36
+ export function celResourceReads(manifest) {
37
+ const names = new Set();
38
+ walkCelExpressions(manifest, "", (source) => {
39
+ for (const chain of accessChains(source)) {
40
+ if (chain[0] === "resources" && chain.length >= 2 && chain[1])
41
+ names.add(chain[1]);
42
+ }
43
+ });
44
+ return [...names];
45
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"import-resolution-diagnostics.d.ts","sourceRoot":"","sources":["../src/import-resolution-diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAyCzE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,WAAW,GAAG,kBAAkB,EAAE,CAepF"}
1
+ {"version":3,"file":"import-resolution-diagnostics.d.ts","sourceRoot":"","sources":["../src/import-resolution-diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA2DzE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,WAAW,GAAG,kBAAkB,EAAE,CAepF"}
@@ -1,15 +1,13 @@
1
1
  import { isLocalPathSource } from "./sources/local-path-ref.js";
2
- import { isRegistryRef } from "./sources/module-ref.js";
3
2
  import { isOciRef } from "./sources/oci-ref.js";
4
3
  import { DiagnosticSeverity } from "./types.js";
5
4
  const SOURCE = "telo-analyzer";
6
- /** True when `source` is a shape some transport claims — a registry ref, an OCI
7
- * ref, an HTTP(S) URL, or a relative/absolute path. A source matching none of
8
- * these is malformed (no transport can ever resolve it), which we report
9
- * differently from a well-formed ref that simply failed to fetch. */
5
+ /** True when `source` is a shape some transport claims — an OCI ref, an HTTP(S)
6
+ * URL, or a relative/absolute path. A source matching none of these is
7
+ * malformed (no transport can ever resolve it), which we report differently
8
+ * from a well-formed ref that simply failed to fetch. */
10
9
  function isRecognizedSourceShape(source) {
11
- return (isRegistryRef(source) ||
12
- isOciRef(source) ||
10
+ return (isOciRef(source) ||
13
11
  source.startsWith("http://") ||
14
12
  source.startsWith("https://") ||
15
13
  isLocalPathSource(source));
@@ -21,12 +19,28 @@ function classify(e) {
21
19
  return "INVALID_IMPORT_TARGET";
22
20
  return isRecognizedSourceShape(e.source ?? e.url) ? "IMPORT_UNRESOLVED" : "INVALID_IMPORT_SOURCE";
23
21
  }
22
+ /** The removed bare `<namespace>/<name>@<version>` ref: no scheme, not a path,
23
+ * carrying both a `/` and an `@`. Recognised only to explain itself — no
24
+ * migration can rewrite it, because the OCI host it moved to is not derivable
25
+ * from the ref, and the failing line is frequently inside a dependency the
26
+ * consumer cannot edit. The message is the whole remedy available to them. */
27
+ function isRemovedBareRef(source) {
28
+ return (!source.includes("://") &&
29
+ !source.startsWith("/") &&
30
+ !source.startsWith(".") &&
31
+ source.includes("@") &&
32
+ source.includes("/"));
33
+ }
24
34
  function messageFor(e, code) {
25
35
  const authored = e.source ?? e.url;
26
36
  const via = e.alias ? `import '${e.alias}' → '${authored}'` : `'${authored}'`;
27
37
  if (code === "INVALID_IMPORT_SOURCE") {
38
+ const removedForm = isRemovedBareRef(authored)
39
+ ? ` The bare '<namespace>/<name>@<version>' form was removed — rewrite it to the ` +
40
+ `module's 'oci://' ref (\`telo search <name>\` reports it).`
41
+ : "";
28
42
  return (`Cannot resolve ${via}: not a recognized module reference. Expected ` +
29
- `'namespace/name@version', 'oci://host/repo@tag', 'https://…', or a relative path.`);
43
+ `'oci://host/repo@tag', 'https://…', or a relative path.${removedForm}`);
30
44
  }
31
45
  // The target WAS obtained, so "cannot resolve" would name the wrong problem —
32
46
  // and the wrong person to fix it. The loader's message already says what was