@telorun/analyzer 0.10.1 → 0.12.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 (92) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +3 -3
  3. package/dist/adapters/http-adapter.d.ts +10 -0
  4. package/dist/adapters/http-adapter.d.ts.map +1 -0
  5. package/dist/adapters/http-adapter.js +18 -0
  6. package/dist/adapters/node-adapter.d.ts +17 -0
  7. package/dist/adapters/node-adapter.d.ts.map +1 -0
  8. package/dist/adapters/node-adapter.js +71 -0
  9. package/dist/adapters/registry-adapter.d.ts +15 -0
  10. package/dist/adapters/registry-adapter.d.ts.map +1 -0
  11. package/dist/adapters/registry-adapter.js +53 -0
  12. package/dist/analysis-registry.d.ts +7 -0
  13. package/dist/analysis-registry.d.ts.map +1 -1
  14. package/dist/analysis-registry.js +38 -0
  15. package/dist/analyzer.d.ts +15 -0
  16. package/dist/analyzer.d.ts.map +1 -1
  17. package/dist/analyzer.js +268 -7
  18. package/dist/builtins.d.ts.map +1 -1
  19. package/dist/builtins.js +172 -1
  20. package/dist/definition-registry.d.ts.map +1 -1
  21. package/dist/definition-registry.js +16 -0
  22. package/dist/dependency-graph.d.ts.map +1 -1
  23. package/dist/dependency-graph.js +27 -13
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -0
  27. package/dist/kernel-globals.d.ts.map +1 -1
  28. package/dist/kernel-globals.js +9 -11
  29. package/dist/manifest-loader.d.ts +23 -1
  30. package/dist/manifest-loader.d.ts.map +1 -1
  31. package/dist/manifest-loader.js +66 -3
  32. package/dist/normalize-inline-resources.d.ts.map +1 -1
  33. package/dist/normalize-inline-resources.js +26 -14
  34. package/dist/position-metadata.d.ts +11 -2
  35. package/dist/position-metadata.d.ts.map +1 -1
  36. package/dist/position-metadata.js +18 -3
  37. package/dist/precompile.d.ts.map +1 -1
  38. package/dist/precompile.js +9 -1
  39. package/dist/reference-field-map.d.ts +21 -4
  40. package/dist/reference-field-map.d.ts.map +1 -1
  41. package/dist/reference-field-map.js +93 -25
  42. package/dist/residual-schema.d.ts +23 -0
  43. package/dist/residual-schema.d.ts.map +1 -0
  44. package/dist/residual-schema.js +45 -0
  45. package/dist/resolve-ref-sentinels.d.ts +27 -0
  46. package/dist/resolve-ref-sentinels.d.ts.map +1 -0
  47. package/dist/resolve-ref-sentinels.js +114 -0
  48. package/dist/rewrite-synthetic-origins.d.ts +10 -0
  49. package/dist/rewrite-synthetic-origins.d.ts.map +1 -0
  50. package/dist/rewrite-synthetic-origins.js +55 -0
  51. package/dist/schema-compat.d.ts +7 -1
  52. package/dist/schema-compat.d.ts.map +1 -1
  53. package/dist/schema-compat.js +19 -2
  54. package/dist/system-kinds.d.ts +25 -0
  55. package/dist/system-kinds.d.ts.map +1 -0
  56. package/dist/system-kinds.js +34 -0
  57. package/dist/types.d.ts +12 -0
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/validate-cel-context.d.ts +61 -7
  60. package/dist/validate-cel-context.d.ts.map +1 -1
  61. package/dist/validate-cel-context.js +90 -8
  62. package/dist/validate-provider-coherence.d.ts +23 -0
  63. package/dist/validate-provider-coherence.d.ts.map +1 -0
  64. package/dist/validate-provider-coherence.js +148 -0
  65. package/dist/validate-references.d.ts.map +1 -1
  66. package/dist/validate-references.js +141 -36
  67. package/dist/with-synthetic-positions.d.ts +28 -0
  68. package/dist/with-synthetic-positions.d.ts.map +1 -0
  69. package/dist/with-synthetic-positions.js +45 -0
  70. package/package.json +7 -4
  71. package/src/analysis-registry.ts +37 -0
  72. package/src/analyzer.ts +313 -9
  73. package/src/builtins.ts +172 -1
  74. package/src/definition-registry.ts +15 -0
  75. package/src/dependency-graph.ts +27 -14
  76. package/src/index.ts +2 -0
  77. package/src/kernel-globals.ts +9 -11
  78. package/src/manifest-loader.ts +69 -4
  79. package/src/normalize-inline-resources.ts +48 -13
  80. package/src/position-metadata.ts +18 -3
  81. package/src/precompile.ts +8 -1
  82. package/src/reference-field-map.ts +129 -24
  83. package/src/residual-schema.ts +49 -0
  84. package/src/resolve-ref-sentinels.ts +127 -0
  85. package/src/rewrite-synthetic-origins.ts +75 -0
  86. package/src/schema-compat.ts +19 -2
  87. package/src/system-kinds.ts +37 -0
  88. package/src/types.ts +12 -0
  89. package/src/validate-cel-context.ts +111 -8
  90. package/src/validate-provider-coherence.ts +166 -0
  91. package/src/validate-references.ts +138 -35
  92. package/src/with-synthetic-positions.ts +48 -0
package/dist/analyzer.js CHANGED
@@ -7,13 +7,49 @@ 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 { REF_VALIDATION_SKIP_KINDS } from "./system-kinds.js";
11
+ import { resolveRefSentinels } from "./resolve-ref-sentinels.js";
12
+ import { rewriteSyntheticOrigins } from "./rewrite-synthetic-origins.js";
10
13
  import { celTypeSatisfiesJsonSchema, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
11
14
  import { DiagnosticSeverity } from "./types.js";
12
15
  import { getManifestItem, pathMatchesScope, resolveContextAnnotations, resolveTypeFieldToSchema, } from "./validate-cel-context.js";
13
16
  import { validateExtends } from "./validate-extends.js";
17
+ import { validateProviderCoherence } from "./validate-provider-coherence.js";
14
18
  import { validateReferences } from "./validate-references.js";
15
19
  import { validateThrowsCoverage } from "./validate-throws-coverage.js";
16
20
  const SELF_PREFIX = "Self.";
21
+ /**
22
+ * `StaticAnalyzer.analyze()` requires `metadata.source` (non-empty) and
23
+ * `metadata.sourceLine` (number) on every non-system manifest — see the
24
+ * JSDoc on `analyze()`. Production callers stamp these via the `Loader` /
25
+ * `flattenForAnalyzer` / `emitDocsFor` paths; programmatic callers (tests,
26
+ * scripts) should pre-process inputs with `withSyntheticPositions(...)`.
27
+ * Surfacing the violation here turns silent dedup misbehaviour into a
28
+ * loud, actionable error.
29
+ */
30
+ function assertManifestPositions(manifests) {
31
+ for (let i = 0; i < manifests.length; i++) {
32
+ const m = manifests[i];
33
+ if (REF_VALIDATION_SKIP_KINDS.has(m.kind))
34
+ continue;
35
+ const meta = m.metadata;
36
+ const okSource = typeof meta?.source === "string" && meta.source.length > 0;
37
+ const okLine = typeof meta?.sourceLine === "number";
38
+ if (okSource && okLine)
39
+ continue;
40
+ const label = `${m.kind}/${m.metadata?.name ?? "(unnamed)"}`;
41
+ const missing = [
42
+ !okSource ? "metadata.source" : null,
43
+ !okLine ? "metadata.sourceLine" : null,
44
+ ]
45
+ .filter(Boolean)
46
+ .join(" and ");
47
+ throw new Error(`StaticAnalyzer.analyze(): manifest #${i} (${label}) is missing ${missing}. ` +
48
+ `Real callers stamp positions automatically; programmatic callers ` +
49
+ `(tests, ad-hoc scripts) should pass inputs through ` +
50
+ `\`withSyntheticPositions(manifests)\` before calling analyze().`);
51
+ }
52
+ }
17
53
  /** Resolve an alias-prefixed kind value (e.g. `Self.Encoder` or `Ai.Model`)
18
54
  * to its canonical form. `Self.<Name>` resolves to `<ownModule>.<Name>` —
19
55
  * the magic alias for "this library's own module" — and other prefixes
@@ -37,12 +73,89 @@ function lookupDefinitionTypeField(invokedKind, fieldName, defs, aliases, allMan
37
73
  return resolveTypeFieldToSchema(value, allManifests);
38
74
  }
39
75
  const SOURCE = "telo-analyzer";
76
+ /** Build a closed JSON Schema for the `self` CEL variable available inside a
77
+ * `Telo.Definition` template body. Mirrors the runtime template controller's
78
+ * `const self = { ...resource, name: resource.metadata.name };` — every
79
+ * property the user declared in `schema:` plus synthetic `name` / `kind` and
80
+ * the metadata sub-object (kept open since metadata legitimately carries
81
+ * arbitrary user-added fields). */
82
+ function buildSelfSchema(definition) {
83
+ const userSchema = (definition.schema ?? {});
84
+ const userProps = (userSchema.properties ?? {});
85
+ const userRequired = Array.isArray(userSchema.required) ? userSchema.required : [];
86
+ return {
87
+ type: "object",
88
+ additionalProperties: false,
89
+ properties: {
90
+ ...userProps,
91
+ name: { type: "string" },
92
+ kind: { type: "string" },
93
+ metadata: {
94
+ type: "object",
95
+ additionalProperties: true,
96
+ properties: { name: { type: "string" } },
97
+ },
98
+ },
99
+ required: [...userRequired, "name", "kind"],
100
+ };
101
+ }
102
+ /** Build the JSON Schema for the `inputs` CEL variable available inside an
103
+ * invocable template body. Three-layer fallback mirroring the runtime's
104
+ * caller-supplied inputs:
105
+ * 1. The definition's own `inputType:` field (preferred).
106
+ * 2. The `extends:`-declared abstract's `inputType:` (so a concrete
107
+ * definition inheriting a contract gets typed inputs without
108
+ * redeclaring them).
109
+ * 3. Undefined — caller signals opaque `map<string, dyn>` upstream. */
110
+ function lookupTemplateInputsSchema(definition, defs, aliases, allManifests) {
111
+ const own = resolveTypeFieldToSchema(definition.inputType, allManifests);
112
+ if (own)
113
+ return own;
114
+ const ext = definition.extends;
115
+ if (typeof ext === "string" && ext.length > 0) {
116
+ const canonical = aliases.resolveKind(ext) ?? ext;
117
+ const abstractDef = defs.resolve(canonical);
118
+ if (abstractDef) {
119
+ const inherited = resolveTypeFieldToSchema(abstractDef.inputType, allManifests);
120
+ if (inherited)
121
+ return inherited;
122
+ }
123
+ }
124
+ return undefined;
125
+ }
126
+ /** Returns a "resolver-facing" view of the manifest where the fields used as
127
+ * navigation roots by Telo.Definition's `x-telo-context-from-root` annotations
128
+ * have been pre-augmented:
129
+ * - `schema` → augmented `self` schema (synthetic `name`/`kind`/metadata).
130
+ * - `inputType` → resolved with extends fallback when the field isn't
131
+ * declared directly on the definition.
132
+ *
133
+ * For non-definition manifests the original object is returned. */
134
+ function manifestRootForResolver(m, defs, aliases, allManifests) {
135
+ if (m.kind !== "Telo.Definition")
136
+ return m;
137
+ const inputs = lookupTemplateInputsSchema(m, defs, aliases, allManifests);
138
+ return {
139
+ ...m,
140
+ schema: buildSelfSchema(m),
141
+ ...(inputs ? { inputType: inputs } : {}),
142
+ };
143
+ }
40
144
  /**
41
145
  * Walk a JSON Schema tree and collect all `x-telo-context` annotations,
42
146
  * returning them as `{ scope, schema }` pairs using JSONPath-style scopes —
43
147
  * the same format the analyzer uses for CEL context validation.
148
+ *
149
+ * Result is sorted by scope specificity (longer scope first) so that the
150
+ * per-expression resolver's first-match-wins logic picks the most-specific
151
+ * context. Without this, a broader ancestor scope (e.g. `$.resources[*]`)
152
+ * could shadow a narrower descendant scope whose activation differs.
44
153
  */
45
154
  function extractContextsFromSchema(schema, path = "$") {
155
+ const all = collectContexts(schema, path);
156
+ return all.sort((a, b) => b.scope.length - a.scope.length);
157
+ }
158
+ function collectContexts(schema, path) {
46
159
  if (!schema || typeof schema !== "object")
47
160
  return [];
48
161
  const results = [];
@@ -51,16 +164,16 @@ function extractContextsFromSchema(schema, path = "$") {
51
164
  }
52
165
  if (schema.properties) {
53
166
  for (const [key, value] of Object.entries(schema.properties)) {
54
- results.push(...extractContextsFromSchema(value, `${path}.${key}`));
167
+ results.push(...collectContexts(value, `${path}.${key}`));
55
168
  }
56
169
  }
57
170
  if (schema.items && typeof schema.items === "object") {
58
- results.push(...extractContextsFromSchema(schema.items, `${path}[*]`));
171
+ results.push(...collectContexts(schema.items, `${path}[*]`));
59
172
  }
60
173
  for (const key of ["oneOf", "anyOf", "allOf"]) {
61
174
  if (Array.isArray(schema[key])) {
62
175
  for (const subschema of schema[key]) {
63
- results.push(...extractContextsFromSchema(subschema, path));
176
+ results.push(...collectContexts(subschema, path));
64
177
  }
65
178
  }
66
179
  }
@@ -296,7 +409,23 @@ export class StaticAnalyzer {
296
409
  constructor(options = {}) {
297
410
  this.celEnv = buildCelEnvironment(options.celHandlers);
298
411
  }
412
+ /**
413
+ * Run static analysis over a flattened manifest list.
414
+ *
415
+ * **Contract**: every non-system manifest (anything outside `Telo.Definition`,
416
+ * `Telo.Abstract`) must carry `metadata.source` (non-empty string) and
417
+ * `metadata.sourceLine` (number). The dedup that backs
418
+ * `DUPLICATE_RESOURCE_NAME` reads those fields to tell a pipeline echo
419
+ * apart from a genuine collision, and downstream diagnostic positioning
420
+ * depends on them too. Real callers stamp positions already (the `Loader`,
421
+ * `flattenForAnalyzer`, the telo-editor's `emitDocsFor`, the VSCode
422
+ * extension). Programmatic callers — tests, ad-hoc scripts — should pass
423
+ * their inputs through `withSyntheticPositions(...)` before calling
424
+ * `analyze()`. A missing position throws a clear error rather than
425
+ * silently producing wrong diagnostics.
426
+ */
299
427
  analyze(manifests, options, registry) {
428
+ assertManifestPositions(manifests);
300
429
  const diagnostics = [];
301
430
  // Use pre-seeded registries from the provided AnalysisRegistry, or create fresh ones.
302
431
  // New aliases/definitions found in the manifests are accumulated into the provided instance
@@ -413,6 +542,20 @@ export class StaticAnalyzer {
413
542
  }
414
543
  // Phase 2: extract inline resources from x-telo-ref slots into first-class manifests
415
544
  const allManifests = normalizeInlineResources(manifests, defs, aliases, aliasesByModule);
545
+ // Phase 2.5: resolve `!ref <name>` sentinels at every ref slot to canonical
546
+ // {kind, name} objects so downstream phases (validation, dependency graph,
547
+ // kernel controllers) see a uniform shape. Runs after normalize so both
548
+ // original and inline-extracted manifests have their sentinels resolved.
549
+ resolveRefSentinels(allManifests, defs, aliases, aliasesByModule);
550
+ // Trusted-input fast path: when the caller has already attested that
551
+ // this exact manifest set passes analysis (e.g. via the kernel's
552
+ // hash-stamped `.validated.json` cache), skip the validation walk.
553
+ // Registration of identities / aliases / definitions and inline-resource
554
+ // normalisation have already run above; that's all downstream
555
+ // consumers (prepare, init loop) require.
556
+ if (options?.skipValidation) {
557
+ return diagnostics;
558
+ }
416
559
  // Build a name→manifest map for looking up referenced resources
417
560
  const byName = new Map();
418
561
  for (const m of allManifests) {
@@ -420,6 +563,36 @@ export class StaticAnalyzer {
420
563
  byName.set(m.metadata.name, m);
421
564
  }
422
565
  }
566
+ // Library env: rejection — `env:` on a Library `variables` / `secrets`
567
+ // entry is forbidden. The Library entry schema is otherwise open so that
568
+ // any JSON Schema property schema is valid; this targeted check produces
569
+ // a clear diagnostic instead of a generic "additional property" error.
570
+ for (const m of allManifests) {
571
+ if (m.kind !== "Telo.Library")
572
+ continue;
573
+ const filePath = m.metadata?.source;
574
+ const moduleName = m.metadata?.name;
575
+ const resource = moduleName ? { kind: m.kind, name: moduleName } : undefined;
576
+ for (const block of ["variables", "secrets"]) {
577
+ const entries = m[block];
578
+ if (!entries || typeof entries !== "object" || Array.isArray(entries))
579
+ continue;
580
+ for (const [entryName, entry] of Object.entries(entries)) {
581
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
582
+ continue;
583
+ if ("env" in entry) {
584
+ diagnostics.push({
585
+ severity: DiagnosticSeverity.Error,
586
+ code: "LIBRARY_ENV_KEY_REJECTED",
587
+ source: SOURCE,
588
+ message: `Telo.Library ${block}/${entryName}: 'env:' is only permitted on Telo.Application entries. ` +
589
+ `Libraries must receive values from importers via the parent manifest's variables / secrets block.`,
590
+ data: { resource, filePath, path: `${block}.${entryName}.env` },
591
+ });
592
+ }
593
+ }
594
+ }
595
+ }
423
596
  // Build typed kernel globals schema so x-telo-context chain validation
424
597
  // recognises variables, secrets, resources, env automatically
425
598
  const kernelGlobals = buildKernelGlobalsSchema(allManifests);
@@ -436,7 +609,11 @@ export class StaticAnalyzer {
436
609
  });
437
610
  continue;
438
611
  }
439
- if (m.kind === "Telo.Definition" || m.kind === "Telo.Abstract") {
612
+ // Abstracts carry only inputType / outputType schema fields and no template
613
+ // body — nothing for the per-resource walk to validate. Definitions are now
614
+ // walked: their template bodies (`resources` / `invoke` / `run` / `provide`)
615
+ // contain CEL that must be checked against `self` / `inputs` / `result`.
616
+ if (m.kind === "Telo.Abstract") {
440
617
  continue;
441
618
  }
442
619
  const resource = { kind: m.kind, name: m.metadata?.name };
@@ -489,6 +666,75 @@ export class StaticAnalyzer {
489
666
  }
490
667
  // (Invocation context compatibility check is handled via x-telo-context in the CEL pass below)
491
668
  }
669
+ // Template-body structural validations: check that template entry-points produce
670
+ // values matching the contract of their dispatch target and (for `provide:`)
671
+ // the abstract this definition `extends`. CEL fields inside the templated
672
+ // values are replaced with type-appropriate placeholders before AJV runs —
673
+ // same pattern as the per-resource schema validation above.
674
+ for (const m of allManifests) {
675
+ if (m.kind !== "Telo.Definition")
676
+ continue;
677
+ const filePath = m.metadata?.source;
678
+ const name = m.metadata?.name;
679
+ if (!name)
680
+ continue;
681
+ const resource = { kind: m.kind, name };
682
+ const md = m;
683
+ const emitTargetMismatch = (targetKind, valueSchema, value, path) => {
684
+ const substituted = substituteCelFields(value, valueSchema);
685
+ const issues = validateAgainstSchema(substituted, valueSchema);
686
+ for (const issue of issues) {
687
+ diagnostics.push({
688
+ severity: DiagnosticSeverity.Error,
689
+ code: "TEMPLATE_TARGET_MISMATCH",
690
+ source: SOURCE,
691
+ message: `${m.kind}/${name}: ${path} does not satisfy ${targetKind}'s contract: ${issue.message}`,
692
+ data: { resource, filePath, path: issue.path ? `${path}.${issue.path}` : path },
693
+ });
694
+ }
695
+ };
696
+ // Resolve the dispatch target's kind, if statically known. Object-form
697
+ // `invoke: { kind, name }` and `provide: { kind, name }` carry it; the
698
+ // string-form `invoke: "name"` does not (the matching resource entry would
699
+ // need to be located by expanded name — out of scope here).
700
+ const invoke = md.invoke;
701
+ const provide = md.provide;
702
+ let dispatchKind;
703
+ if (invoke && typeof invoke === "object" && !Array.isArray(invoke) && typeof invoke.kind === "string") {
704
+ dispatchKind = invoke.kind;
705
+ }
706
+ else if (provide &&
707
+ typeof provide === "object" &&
708
+ !Array.isArray(provide) &&
709
+ typeof provide.kind === "string") {
710
+ dispatchKind = provide.kind;
711
+ }
712
+ // Top-level `inputs:` (sibling of `invoke:` / `provide:`) carries the
713
+ // values passed to the dispatch target's invoke(). Validate against the
714
+ // target's declared `inputType` when both sides have one.
715
+ if (dispatchKind && md.inputs && typeof md.inputs === "object") {
716
+ const targetSchema = lookupDefinitionTypeField(dispatchKind, "inputType", defs, aliases, allManifests);
717
+ if (targetSchema) {
718
+ emitTargetMismatch(dispatchKind, targetSchema, md.inputs, "inputs");
719
+ }
720
+ }
721
+ // Top-level `result:` is a post-call mapping that must satisfy the abstract
722
+ // this definition `extends` (`outputType`). It's a sibling of whichever
723
+ // dispatch entry-point declared a kind-typed target (`provide:` or
724
+ // `invoke:`). The target's outputType lives on the dispatcher's `kind`
725
+ // and is what `result` is typed against *inside* CEL — separate role.
726
+ const hasDispatchObject = (provide && typeof provide === "object" && !Array.isArray(provide)) ||
727
+ (invoke && typeof invoke === "object" && !Array.isArray(invoke));
728
+ if (hasDispatchObject && md.result && typeof md.result === "object") {
729
+ const extendsValue = md.extends;
730
+ if (typeof extendsValue === "string" && extendsValue.length > 0) {
731
+ const abstractSchema = lookupDefinitionTypeField(extendsValue, "outputType", defs, aliases, allManifests);
732
+ if (abstractSchema) {
733
+ emitTargetMismatch(extendsValue, abstractSchema, md.result, "result");
734
+ }
735
+ }
736
+ }
737
+ }
492
738
  // Validate CEL syntax and context variable access in all manifests
493
739
  for (const m of allManifests) {
494
740
  const resource = { kind: m.kind, name: m.metadata?.name };
@@ -533,7 +779,13 @@ export class StaticAnalyzer {
533
779
  const manifestItem = matchedScope
534
780
  ? getManifestItem(path, matchedScope, m)
535
781
  : m;
536
- const resolvedContext = resolveContextAnnotations(matchedContext, manifestItem, allManifests);
782
+ const rootForResolver = manifestRootForResolver(m, defs, aliases, allManifests);
783
+ const resolvedContext = resolveContextAnnotations(matchedContext, manifestItem, {
784
+ manifestRoot: rootForResolver,
785
+ defs,
786
+ aliases,
787
+ allManifests: allManifests,
788
+ });
537
789
  effectiveContext = mergeKernelGlobalsIntoContext(resolvedContext, kernelGlobals);
538
790
  }
539
791
  const engine = defaultRegistry().get(engineName);
@@ -578,16 +830,25 @@ export class StaticAnalyzer {
578
830
  diagnostics.push(...validateReferences(allManifests, { aliases, definitions: defs, aliasesByModule }));
579
831
  // Validate `extends` fields and flag legacy `capability: <UserAbstract>` overload.
580
832
  diagnostics.push(...validateExtends(allManifests, defs, aliases));
833
+ // Validate provider coherence rules for `provide:` template-target definitions.
834
+ diagnostics.push(...validateProviderCoherence(allManifests, defs, aliases));
581
835
  // Validate throws: declarations and catches: coverage (rules 1, 2, 4, 7)
582
836
  diagnostics.push(...validateThrowsCoverage(allManifests, defs, aliases, this.celEnv));
583
- return diagnostics;
837
+ // Reroute diagnostics on synthetic (inline-extracted) resources back to
838
+ // the chain root so position-index lookups land on the parent doc.
839
+ return rewriteSyntheticOrigins(diagnostics, allManifests);
584
840
  }
585
841
  analyzeErrors(manifests, options, registry) {
586
842
  return this.analyze(manifests, options, registry).filter((d) => d.severity === DiagnosticSeverity.Error);
587
843
  }
588
844
  normalize(manifests, registry) {
589
845
  const ctx = registry._context();
590
- return normalizeInlineResources(manifests, ctx.definitions, ctx.aliases, ctx.aliasesByModule);
846
+ const normalized = normalizeInlineResources(manifests, ctx.definitions, ctx.aliases, ctx.aliasesByModule);
847
+ // Resolve !ref sentinels after normalize so both the original and
848
+ // inline-extracted manifests get their refs canonicalized to
849
+ // {kind, name} for the kernel that consumes this output.
850
+ resolveRefSentinels(normalized, ctx.definitions, ctx.aliases, ctx.aliasesByModule);
851
+ return normalized;
591
852
  }
592
853
  prepare(manifests, registry) {
593
854
  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,EAsJ/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
@@ -38,7 +38,126 @@ export const KERNEL_BUILTINS = [
38
38
  kind: "Telo.Definition",
39
39
  metadata: { name: "Definition", module: "Telo" },
40
40
  capability: "Telo.Template",
41
- schema: { type: "object" },
41
+ // Top-level shape stays open (`additionalProperties: true`) so this change
42
+ // attaches x-telo-context annotations to known template-body fields without
43
+ // tightening the Telo.Definition shape itself. The annotations drive
44
+ // static CEL validation of expressions inside `resources:` / `invoke:` /
45
+ // `run:` / `provide:` / top-level `inputs:` / top-level `result:` against
46
+ // `self` (typed from `schema:`) and `inputs` (typed from `inputType:`,
47
+ // falling back to the extends-declared abstract).
48
+ //
49
+ // `inputs:` and `result:` live as top-level siblings of `invoke:` / `provide:`,
50
+ // matching how Run.Sequence steps factor dispatch from data. The dispatch
51
+ // entry-point (`invoke` / `provide` / `run`) determines how `inputs`/`result`
52
+ // are interpreted at runtime. See analyzer/nodejs/plans/template-internal-cel-validation.md.
53
+ schema: {
54
+ type: "object",
55
+ additionalProperties: true,
56
+ properties: {
57
+ resources: {
58
+ type: "array",
59
+ items: {
60
+ type: "object",
61
+ additionalProperties: true,
62
+ "x-telo-context": {
63
+ type: "object",
64
+ additionalProperties: false,
65
+ properties: {
66
+ self: { "x-telo-context-from-root": "schema" },
67
+ inputs: { "x-telo-context-from-root": "inputType" },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ invoke: {
73
+ oneOf: [
74
+ {
75
+ type: "string",
76
+ "x-telo-context": {
77
+ type: "object",
78
+ additionalProperties: false,
79
+ properties: {
80
+ self: { "x-telo-context-from-root": "schema" },
81
+ },
82
+ },
83
+ },
84
+ {
85
+ type: "object",
86
+ additionalProperties: true,
87
+ properties: {
88
+ kind: { type: "string" },
89
+ name: {
90
+ type: "string",
91
+ "x-telo-context": {
92
+ type: "object",
93
+ additionalProperties: false,
94
+ properties: {
95
+ self: { "x-telo-context-from-root": "schema" },
96
+ },
97
+ },
98
+ },
99
+ },
100
+ },
101
+ ],
102
+ },
103
+ provide: {
104
+ type: "object",
105
+ additionalProperties: true,
106
+ properties: {
107
+ kind: { type: "string" },
108
+ name: {
109
+ type: "string",
110
+ "x-telo-context": {
111
+ type: "object",
112
+ additionalProperties: false,
113
+ properties: {
114
+ self: { "x-telo-context-from-root": "schema" },
115
+ },
116
+ },
117
+ },
118
+ },
119
+ },
120
+ run: {
121
+ type: "string",
122
+ "x-telo-context": {
123
+ type: "object",
124
+ additionalProperties: false,
125
+ properties: {
126
+ self: { "x-telo-context-from-root": "schema" },
127
+ },
128
+ },
129
+ },
130
+ inputs: {
131
+ type: "object",
132
+ additionalProperties: true,
133
+ "x-telo-context": {
134
+ type: "object",
135
+ additionalProperties: false,
136
+ properties: {
137
+ self: { "x-telo-context-from-root": "schema" },
138
+ inputs: { "x-telo-context-from-root": "inputType" },
139
+ },
140
+ },
141
+ },
142
+ result: {
143
+ type: "object",
144
+ additionalProperties: true,
145
+ "x-telo-context": {
146
+ type: "object",
147
+ additionalProperties: false,
148
+ properties: {
149
+ self: { "x-telo-context-from-root": "schema" },
150
+ result: {
151
+ "x-telo-context-from-ref-kind": [
152
+ "provide/kind#outputType",
153
+ "invoke/kind#outputType",
154
+ ],
155
+ },
156
+ },
157
+ },
158
+ },
159
+ },
160
+ },
42
161
  },
43
162
  {
44
163
  kind: "Telo.Definition",
@@ -99,6 +218,20 @@ export const KERNEL_BUILTINS = [
99
218
  anyOf: [
100
219
  { type: "string", "x-telo-ref": "telo#Runnable" },
101
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
+ },
102
235
  ],
103
236
  },
104
237
  },
@@ -106,6 +239,44 @@ export const KERNEL_BUILTINS = [
106
239
  type: "array",
107
240
  items: { type: "string" },
108
241
  },
242
+ // Application-level environment contract. Each entry layers `env:`
243
+ // (required, names the source env var) and `default:` (optional, used
244
+ // when the env var is unset) on top of an open JSON Schema property
245
+ // schema. `type:` constrains the coercion rule applied to the raw env
246
+ // string (scalars per-type; `object` / `array` via JSON.parse with the
247
+ // matching top-level type). All other JSON Schema keywords are passed
248
+ // through unchanged and applied to the coerced value via the standard
249
+ // schema validator. See kernel/nodejs/src/application-env.ts.
250
+ variables: {
251
+ type: "object",
252
+ additionalProperties: {
253
+ type: "object",
254
+ required: ["env", "type"],
255
+ properties: {
256
+ env: { type: "string" },
257
+ type: {
258
+ type: "string",
259
+ enum: ["string", "integer", "number", "boolean", "object", "array"],
260
+ },
261
+ default: {},
262
+ },
263
+ },
264
+ },
265
+ secrets: {
266
+ type: "object",
267
+ additionalProperties: {
268
+ type: "object",
269
+ required: ["env", "type"],
270
+ properties: {
271
+ env: { type: "string" },
272
+ type: {
273
+ type: "string",
274
+ enum: ["string", "integer", "number", "boolean", "object", "array"],
275
+ },
276
+ default: {},
277
+ },
278
+ },
279
+ },
109
280
  },
110
281
  required: ["metadata"],
111
282
  additionalProperties: false,
@@ -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"}