@telorun/analyzer 0.72.0 → 0.74.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 (208) hide show
  1. package/dist/analyzer.d.ts.map +1 -1
  2. package/dist/analyzer.js +76 -20
  3. package/dist/artifact-axes.d.ts +14 -0
  4. package/dist/artifact-axes.d.ts.map +1 -0
  5. package/dist/artifact-axes.js +12 -0
  6. package/dist/artifact-layer-index.d.ts +2 -2
  7. package/dist/artifact-layer-index.d.ts.map +1 -1
  8. package/dist/artifact-layer-index.js +9 -4
  9. package/dist/artifact-selector.d.ts +47 -21
  10. package/dist/artifact-selector.d.ts.map +1 -1
  11. package/dist/artifact-selector.js +85 -16
  12. package/dist/builtins.d.ts.map +1 -1
  13. package/dist/builtins.js +144 -113
  14. package/dist/call-graph.d.ts +12 -1
  15. package/dist/call-graph.d.ts.map +1 -1
  16. package/dist/call-graph.js +22 -0
  17. package/dist/catch-scope.js +1 -1
  18. package/dist/cel-scope-query.d.ts.map +1 -1
  19. package/dist/cel-scope-query.js +32 -11
  20. package/dist/cel-scope.d.ts.map +1 -1
  21. package/dist/cel-scope.js +1 -0
  22. package/dist/eval-paths.d.ts +36 -5
  23. package/dist/eval-paths.d.ts.map +1 -1
  24. package/dist/eval-paths.js +47 -6
  25. package/dist/extends-resolution.d.ts +12 -0
  26. package/dist/extends-resolution.d.ts.map +1 -1
  27. package/dist/extends-resolution.js +23 -0
  28. package/dist/forwarded-declaration.d.ts +26 -0
  29. package/dist/forwarded-declaration.d.ts.map +1 -0
  30. package/dist/forwarded-declaration.js +31 -0
  31. package/dist/index.d.ts +10 -4
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +5 -2
  34. package/dist/kernel-globals.d.ts.map +1 -1
  35. package/dist/kernel-globals.js +21 -5
  36. package/dist/module-alias-scope.d.ts +11 -0
  37. package/dist/module-alias-scope.d.ts.map +1 -1
  38. package/dist/module-alias-scope.js +10 -0
  39. package/dist/module-file-claims.d.ts +17 -1
  40. package/dist/module-file-claims.d.ts.map +1 -1
  41. package/dist/module-file-claims.js +38 -3
  42. package/dist/module-named-files.d.ts +72 -0
  43. package/dist/module-named-files.d.ts.map +1 -0
  44. package/dist/module-named-files.js +115 -0
  45. package/dist/native-entries.d.ts +83 -0
  46. package/dist/native-entries.d.ts.map +1 -0
  47. package/dist/native-entries.js +169 -0
  48. package/dist/nearest-name.d.ts +22 -0
  49. package/dist/nearest-name.d.ts.map +1 -0
  50. package/dist/nearest-name.js +43 -0
  51. package/dist/normalize-inline-resources.d.ts +41 -13
  52. package/dist/normalize-inline-resources.d.ts.map +1 -1
  53. package/dist/normalize-inline-resources.js +236 -54
  54. package/dist/reference-field-map.d.ts +59 -1
  55. package/dist/reference-field-map.d.ts.map +1 -1
  56. package/dist/reference-field-map.js +200 -34
  57. package/dist/release/destinations.d.ts +59 -0
  58. package/dist/release/destinations.d.ts.map +1 -0
  59. package/dist/release/destinations.js +75 -0
  60. package/dist/release/index.d.ts +6 -2
  61. package/dist/release/index.d.ts.map +1 -1
  62. package/dist/release/index.js +3 -1
  63. package/dist/release/ledger.d.ts +20 -11
  64. package/dist/release/ledger.d.ts.map +1 -1
  65. package/dist/release/ledger.js +43 -14
  66. package/dist/release/payload-digest.d.ts +2 -1
  67. package/dist/release/payload-digest.d.ts.map +1 -1
  68. package/dist/release/payload-digest.js +2 -1
  69. package/dist/release/release-plan.d.ts +3 -2
  70. package/dist/release/release-plan.d.ts.map +1 -1
  71. package/dist/release/release-plan.js +12 -19
  72. package/dist/release/workspace-config.d.ts +114 -34
  73. package/dist/release/workspace-config.d.ts.map +1 -1
  74. package/dist/release/workspace-config.js +322 -38
  75. package/dist/release/workspace-schema.d.ts +41 -0
  76. package/dist/release/workspace-schema.d.ts.map +1 -0
  77. package/dist/release/workspace-schema.js +77 -0
  78. package/dist/resolve-ref-sentinels.d.ts.map +1 -1
  79. package/dist/resolve-ref-sentinels.js +8 -5
  80. package/dist/resolve-throws-union.d.ts +25 -4
  81. package/dist/resolve-throws-union.d.ts.map +1 -1
  82. package/dist/resolve-throws-union.js +62 -32
  83. package/dist/resolve-zone-requirements.d.ts.map +1 -1
  84. package/dist/resolve-zone-requirements.js +2 -1
  85. package/dist/rewrite-synthetic-origins.d.ts +6 -1
  86. package/dist/rewrite-synthetic-origins.d.ts.map +1 -1
  87. package/dist/rewrite-synthetic-origins.js +107 -13
  88. package/dist/schema-compat.d.ts.map +1 -1
  89. package/dist/schema-compat.js +18 -5
  90. package/dist/schema-error-report.d.ts +7 -0
  91. package/dist/schema-error-report.d.ts.map +1 -1
  92. package/dist/schema-error-report.js +9 -4
  93. package/dist/schema-walk.d.ts +47 -13
  94. package/dist/schema-walk.d.ts.map +1 -1
  95. package/dist/schema-walk.js +150 -30
  96. package/dist/scope-declarations.d.ts +59 -0
  97. package/dist/scope-declarations.d.ts.map +1 -0
  98. package/dist/scope-declarations.js +45 -0
  99. package/dist/source-entries.d.ts +119 -0
  100. package/dist/source-entries.d.ts.map +1 -0
  101. package/dist/source-entries.js +349 -0
  102. package/dist/step-bodies.d.ts +24 -0
  103. package/dist/step-bodies.d.ts.map +1 -0
  104. package/dist/step-bodies.js +28 -0
  105. package/dist/telo-version.d.ts +1 -1
  106. package/dist/telo-version.js +1 -1
  107. package/dist/template-body.d.ts +10 -0
  108. package/dist/template-body.d.ts.map +1 -1
  109. package/dist/template-body.js +25 -0
  110. package/dist/validate-cel-context.d.ts +23 -8
  111. package/dist/validate-cel-context.d.ts.map +1 -1
  112. package/dist/validate-cel-context.js +51 -18
  113. package/dist/validate-exports.d.ts +28 -0
  114. package/dist/validate-exports.d.ts.map +1 -0
  115. package/dist/validate-exports.js +146 -0
  116. package/dist/validate-extends.d.ts.map +1 -1
  117. package/dist/validate-extends.js +37 -1
  118. package/dist/validate-module-artifact.d.ts +4 -2
  119. package/dist/validate-module-artifact.d.ts.map +1 -1
  120. package/dist/validate-module-artifact.js +43 -4
  121. package/dist/validate-native-entries.d.ts +15 -0
  122. package/dist/validate-native-entries.d.ts.map +1 -0
  123. package/dist/validate-native-entries.js +103 -0
  124. package/dist/validate-observed-state.d.ts +7 -1
  125. package/dist/validate-observed-state.d.ts.map +1 -1
  126. package/dist/validate-observed-state.js +24 -6
  127. package/dist/validate-provider-coherence.d.ts +4 -8
  128. package/dist/validate-provider-coherence.d.ts.map +1 -1
  129. package/dist/validate-provider-coherence.js +12 -123
  130. package/dist/validate-reference-forms.d.ts.map +1 -1
  131. package/dist/validate-reference-forms.js +2 -2
  132. package/dist/validate-references.d.ts.map +1 -1
  133. package/dist/validate-references.js +152 -65
  134. package/dist/validate-scope-reach.d.ts +31 -0
  135. package/dist/validate-scope-reach.d.ts.map +1 -0
  136. package/dist/validate-scope-reach.js +180 -0
  137. package/dist/validate-source-entries.d.ts +16 -0
  138. package/dist/validate-source-entries.d.ts.map +1 -0
  139. package/dist/validate-source-entries.js +90 -0
  140. package/dist/validate-template-body.d.ts +36 -0
  141. package/dist/validate-template-body.d.ts.map +1 -0
  142. package/dist/validate-template-body.js +246 -0
  143. package/dist/validate-throws-coverage.d.ts.map +1 -1
  144. package/dist/validate-throws-coverage.js +21 -39
  145. package/dist/value-type-keyword.d.ts +22 -5
  146. package/dist/value-type-keyword.d.ts.map +1 -1
  147. package/dist/value-type-keyword.js +16 -7
  148. package/package.json +4 -3
  149. package/src/analyzer.ts +97 -20
  150. package/src/artifact-axes.ts +24 -0
  151. package/src/artifact-layer-index.ts +9 -6
  152. package/src/artifact-selector.ts +110 -35
  153. package/src/builtins.ts +160 -113
  154. package/src/call-graph.ts +30 -0
  155. package/src/catch-scope.ts +1 -1
  156. package/src/cel-scope-query.ts +28 -9
  157. package/src/cel-scope.ts +1 -0
  158. package/src/eval-paths.ts +53 -6
  159. package/src/extends-resolution.ts +24 -0
  160. package/src/forwarded-declaration.ts +32 -0
  161. package/src/index.ts +37 -1
  162. package/src/kernel-globals.ts +28 -5
  163. package/src/migrations/CLAUDE.md +35 -0
  164. package/src/module-alias-scope.ts +17 -0
  165. package/src/module-file-claims.ts +63 -4
  166. package/src/module-named-files.ts +162 -0
  167. package/src/native-entries.ts +227 -0
  168. package/src/nearest-name.ts +47 -0
  169. package/src/normalize-inline-resources.ts +326 -70
  170. package/src/reference-field-map.ts +257 -52
  171. package/src/release/CLAUDE.md +32 -0
  172. package/src/release/destinations.ts +105 -0
  173. package/src/release/index.ts +33 -2
  174. package/src/release/ledger.ts +51 -21
  175. package/src/release/payload-digest.ts +2 -1
  176. package/src/release/release-plan.ts +16 -26
  177. package/src/release/workspace-config.ts +483 -47
  178. package/src/release/workspace-schema.ts +99 -0
  179. package/src/resolve-ref-sentinels.ts +7 -6
  180. package/src/resolve-throws-union.ts +87 -32
  181. package/src/resolve-zone-requirements.ts +2 -1
  182. package/src/rewrite-synthetic-origins.ts +107 -16
  183. package/src/schema-compat.ts +18 -4
  184. package/src/schema-error-report.ts +16 -4
  185. package/src/schema-walk.ts +162 -34
  186. package/src/scope-declarations.ts +98 -0
  187. package/src/source-entries.ts +502 -0
  188. package/src/step-bodies.ts +55 -0
  189. package/src/telo-version.ts +1 -1
  190. package/src/template-body.ts +25 -0
  191. package/src/validate-cel-context.ts +60 -18
  192. package/src/validate-exports.ts +185 -0
  193. package/src/validate-extends.ts +43 -0
  194. package/src/validate-module-artifact.ts +46 -3
  195. package/src/validate-native-entries.ts +147 -0
  196. package/src/validate-observed-state.ts +36 -7
  197. package/src/validate-provider-coherence.ts +17 -127
  198. package/src/validate-reference-forms.ts +2 -3
  199. package/src/validate-references.ts +151 -66
  200. package/src/validate-scope-reach.ts +213 -0
  201. package/src/validate-source-entries.ts +120 -0
  202. package/src/validate-template-body.ts +274 -0
  203. package/src/validate-throws-coverage.ts +24 -31
  204. package/src/value-type-keyword.ts +35 -9
  205. package/dist/validate-template-dispatch.d.ts +0 -27
  206. package/dist/validate-template-dispatch.d.ts.map +0 -1
  207. package/dist/validate-template-dispatch.js +0 -95
  208. package/src/validate-template-dispatch.ts +0 -99
@@ -10,11 +10,14 @@ import { scopeResolverForModule, type AliasResolver } from "./alias-resolver.js"
10
10
  import type { DefinitionRegistry } from "./definition-registry.js";
11
11
  import {
12
12
  createResolveCtx,
13
+ handsFailureBack,
13
14
  resolveScopeUnion,
14
15
  resolveThrowsUnion,
16
+ throwsUses,
15
17
  type ThrowsCodeMeta,
16
18
  type ThrowsUnion,
17
19
  } from "./resolve-throws-union.js";
20
+ import { forEachDeclaredSlot } from "./schema-walk.js";
18
21
  import {
19
22
  buildEnclosers,
20
23
  collectScopedManifests,
@@ -24,7 +27,6 @@ import {
24
27
  } from "./catch-scope.js";
25
28
  import { DiagnosticSeverity, type AnalysisDiagnostic } from "./types.js";
26
29
  import { extractAccessChains, validateChainAgainstSchema } from "./validate-cel-context.js";
27
- import { isStepSlot } from "./step-slot.js";
28
30
 
29
31
  const SOURCE = "telo-analyzer";
30
32
  const TEMPLATE_REGEX = /\$\{\{\s*([^}]+?)\s*\}\}/g;
@@ -507,12 +509,6 @@ function checkCelChainAgainstDataSchema(
507
509
  return diagnostics;
508
510
  }
509
511
 
510
- /** Rule 8 extension: `inherit: true` only makes sense on a definition whose
511
- * schema declares at least one STEP BODY — an array whose items point at the
512
- * shared grammar, or, for a module published before that fragment existed, one
513
- * carrying the legacy `x-telo-step-context` annotation. That is what drives the
514
- * resolver's generic step traversal; a definition with `inherit: true` and no
515
- * such array has no invocables to inherit from. */
516
512
  /** The capabilities whose lifecycle includes a dispatch a caller can catch. On
517
513
  * every other one a thrown error is a boot-time failure, not a structured
518
514
  * runtime error for a downstream caller — a provider resolves configuration, a
@@ -574,10 +570,14 @@ function validateThrowsDeclarations(manifests: ResourceManifest[]): AnalysisDiag
574
570
  code: "INHERIT_WITHOUT_STEP_CONTEXT",
575
571
  source: SOURCE,
576
572
  message:
577
- `Telo.Definition '${name}' declares throws.inherit: true but its schema declares no step ` +
578
- `body. inherit is only meaningful on a definition that drives invocables through steps ` +
579
- `give an array field 'items: { $ref: "telo://manifest#/$defs/Step" }' (the legacy ` +
580
- `x-telo-step-context annotation is also recognised).`,
573
+ `Telo.Definition '${name}' declares throws.inherit: true but its schema dispatches nothing ` +
574
+ `a failure can come back through. inherit makes the kind's union the union of what it ` +
575
+ `dispatches, so the schema needs a step body (an array field with ` +
576
+ `'items: { $ref: "telo://manifest#/$defs/Step" }', or the legacy x-telo-step-context ` +
577
+ `annotation) or a reference slot whose use includes 'call' or 'trigger.consumer' ` +
578
+ `(for a use case map, in any case; a slot declaring no use counts as 'call'). ` +
579
+ `'detached' and 'trigger.inbound' run where no caller awaits them, and ` +
580
+ `'dependency' / 'schema' dispatch nothing.`,
581
581
  data: { resource: { kind: m.kind, name }, filePath, path: "throws.inherit" },
582
582
  });
583
583
  }
@@ -586,28 +586,21 @@ function validateThrowsDeclarations(manifests: ResourceManifest[]): AnalysisDiag
586
586
  return diagnostics;
587
587
  }
588
588
 
589
+ /** Rule 8 extension: `inherit: true` needs something to inherit from — a step
590
+ * body, or a reference slot whose use (any case of a case map) hands a failure
591
+ * back. The schema-only mode of the walk the resolver runs per instance, so the
592
+ * check and the union reach the same slots. */
589
593
  function schemaDrivesInvocables(schema: Record<string, any> | undefined): boolean {
590
- if (!schema || typeof schema !== "object") return false;
591
- if (isStepSlot(schema)) return true;
592
- const props = schema.properties;
593
- if (props && typeof props === "object") {
594
- for (const v of Object.values(props as Record<string, any>)) {
595
- if (schemaDrivesInvocables(v)) return true;
594
+ let drives = false;
595
+ forEachDeclaredSlot(schema, (declared) => {
596
+ if (
597
+ declared.kind === "step" ||
598
+ declared.slots.some((slot) => throwsUses(slot).some(handsFailureBack))
599
+ ) {
600
+ drives = true;
596
601
  }
597
- }
598
- if (schema.items && schemaDrivesInvocables(schema.items)) return true;
599
- for (const key of ["oneOf", "anyOf", "allOf"] as const) {
600
- const arr = schema[key];
601
- if (Array.isArray(arr)) {
602
- for (const sub of arr) if (schemaDrivesInvocables(sub)) return true;
603
- }
604
- }
605
- if (schema.$defs && typeof schema.$defs === "object") {
606
- for (const v of Object.values(schema.$defs as Record<string, any>)) {
607
- if (schemaDrivesInvocables(v)) return true;
608
- }
609
- }
610
- return false;
602
+ });
603
+ return drives;
611
604
  }
612
605
 
613
606
  /** Entry point — invoked once per analyze() run. */
@@ -76,6 +76,21 @@ export const ANNOTATION_KEYWORDS = [
76
76
  "x-telo-widget",
77
77
  ] as const;
78
78
 
79
+ /** How an AJV instance treats a `live` value type. */
80
+ export interface TeloKeywordOptions {
81
+ /**
82
+ * Assert a `live` type as an instance too, so a literal at such a slot is
83
+ * refused — a value written in a manifest can never be a live instance.
84
+ *
85
+ * STATIC analysis only, where a CEL leaf's stand-in is an instance
86
+ * (`celPlaceholderForSchema`). The kernel's instances leave it off: at
87
+ * dispatch a live value is exempt from validation, and the stage that pulls
88
+ * from it refuses a non-stream itself — so the static half refuses a strict
89
+ * subset of what the runtime refuses, and the two cannot disagree.
90
+ */
91
+ readonly assertLive?: boolean;
92
+ }
93
+
79
94
  /**
80
95
  * The `x-telo-type` keyword.
81
96
  *
@@ -84,8 +99,10 @@ export const ANNOTATION_KEYWORDS = [
84
99
  * - a `json` representation validates through its own declared schema, so the
85
100
  * keyword emits nothing — the name carries nominal identity for static wiring
86
101
  * and has no runtime existence at all;
87
- * - a `live` instance is EXEMPT: its value is never traversed, because iterating
88
- * a stream to check it is precisely what the exemption is for;
102
+ * - a `live` instance is EXEMPT at dispatch: its value is never traversed,
103
+ * because iterating a stream to check it is precisely what the exemption is
104
+ * for. Statically ({@link TeloKeywordOptions.assertLive}) it is asserted like
105
+ * any other instance, which checks what the value IS and never iterates it;
89
106
  * - every other instance is ASSERTED against the constructor its binding names.
90
107
  *
91
108
  * An unknown name emits nothing here. It is a hard diagnostic in the analyzer
@@ -93,7 +110,7 @@ export const ANNOTATION_KEYWORDS = [
93
110
  * manifest that wrote it; failing compilation instead would take out every
94
111
  * validator in a module for one typo in one slot.
95
112
  */
96
- export function valueTypeKeyword(): KeywordDefinition {
113
+ export function valueTypeKeyword(options: TeloKeywordOptions = {}): KeywordDefinition {
97
114
  return {
98
115
  keyword: X_TELO_TYPE,
99
116
  // Both spellings: a bare name, or the object form carrying type arguments.
@@ -102,7 +119,8 @@ export function valueTypeKeyword(): KeywordDefinition {
102
119
  const entry: ValueTypeEntry | undefined = readValueTypeSlot({
103
120
  [X_TELO_TYPE]: cxt.schema,
104
121
  })?.entry;
105
- if (!entry || entry.representation !== "instance" || entry.live) return;
122
+ if (!entry || entry.representation !== "instance") return;
123
+ if (entry.live && !options.assertLive) return;
106
124
  const binding = VALUE_TYPE_BINDINGS[entry.binding!];
107
125
  if (!binding) return;
108
126
  // The constructor reaches generated code through AJV's value scope, which
@@ -118,6 +136,12 @@ export function valueTypeKeyword(): KeywordDefinition {
118
136
  error: {
119
137
  message: (cxt: any) => {
120
138
  const entry = readValueTypeSlot({ [X_TELO_TYPE]: cxt.schema })?.entry;
139
+ if (entry?.live) {
140
+ return (
141
+ `must be a live ${entry.name} — a value written in the manifest can never be one; ` +
142
+ "pass the result of a step that produces it, with a !cel expression"
143
+ );
144
+ }
121
145
  return entry?.binding === "bytes"
122
146
  ? "must be raw bytes (a Uint8Array) — bytes cannot be written inline in a manifest"
123
147
  : `must be a ${entry?.name ?? "declared value type"} — this value is not writable inline in a manifest`;
@@ -131,11 +155,13 @@ export function valueTypeKeyword(): KeywordDefinition {
131
155
  * `x-telo-type` as the one that checks.
132
156
  *
133
157
  * Every AJV instance in the runtime and the analyzer goes through this, so a
134
- * schema means the same thing wherever it is validated.
158
+ * schema means the same thing wherever it is validated — apart from the one
159
+ * posture {@link TeloKeywordOptions} names, which only static analysis takes.
135
160
  */
136
- export function registerTeloKeywords(ajv: {
137
- addKeyword: (keyword: any, definition?: any) => unknown;
138
- }): void {
161
+ export function registerTeloKeywords(
162
+ ajv: { addKeyword: (keyword: any, definition?: any) => unknown },
163
+ options: TeloKeywordOptions = {},
164
+ ): void {
139
165
  for (const keyword of ANNOTATION_KEYWORDS) ajv.addKeyword(keyword);
140
- ajv.addKeyword(valueTypeKeyword());
166
+ ajv.addKeyword(valueTypeKeyword(options));
141
167
  }
@@ -1,27 +0,0 @@
1
- import type { ResourceManifest } from "@telorun/sdk";
2
- import { type AnalysisDiagnostic } from "./types.js";
3
- /**
4
- * A `!ref` at a definition's dispatch slot must name a sibling `resources:`
5
- * entry.
6
- *
7
- * `!ref` is the one spelling an author — and the editor's rename, completion and
8
- * go-to-definition — expects to be RESOLVED. But a `Telo.Definition` is in both
9
- * `REF_VALIDATION_SKIP_KINDS` and `REF_RESOLUTION_SKIP_KINDS`, and the dispatch
10
- * slots carry no `x-telo-ref` (the accepted targets are the definition's own
11
- * template-internal entries, not resources of any module), so no reference pass
12
- * reaches them. Introducing the tag at a slot nothing resolves would be worse
13
- * than the string form it replaces: a typo that used to be an obvious runtime
14
- * miss becomes a typo in a construct that advertises static resolution.
15
- *
16
- * Decidable only when every sibling name is LITERAL. A template routinely names
17
- * its entries with CEL (`name: !cel "self.name + '-query'"`), and an expression
18
- * could expand to the referenced name — so one dynamic sibling switches the
19
- * check off for that definition rather than inventing a miss.
20
- *
21
- * Entry-module-scoped, like every other declaration check: a published
22
- * dependency's template body is not the consumer's to fix.
23
- *
24
- * Browser-safe.
25
- */
26
- export declare function validateTemplateDispatch(manifests: ResourceManifest[], rootModules: ReadonlySet<string>): AnalysisDiagnostic[];
27
- //# sourceMappingURL=validate-template-dispatch.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validate-template-dispatch.d.ts","sourceRoot":"","sources":["../src/validate-template-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AASzE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,GAC/B,kBAAkB,EAAE,CAgDtB"}
@@ -1,95 +0,0 @@
1
- import { isRefSentinel } from "@telorun/templating";
2
- import { distance } from "./levenshtein.js";
3
- import { DiagnosticSeverity } from "./types.js";
4
- const SOURCE = "telo-analyzer";
5
- /** The four slots a `Telo.Definition` names its dispatch target in. Each takes
6
- * the same grammar; which one is legal for a given capability is
7
- * `validate-provider-coherence`'s question, not this one's. */
8
- const DISPATCH_FIELDS = ["invoke", "run", "provide", "mount"];
9
- /**
10
- * A `!ref` at a definition's dispatch slot must name a sibling `resources:`
11
- * entry.
12
- *
13
- * `!ref` is the one spelling an author — and the editor's rename, completion and
14
- * go-to-definition — expects to be RESOLVED. But a `Telo.Definition` is in both
15
- * `REF_VALIDATION_SKIP_KINDS` and `REF_RESOLUTION_SKIP_KINDS`, and the dispatch
16
- * slots carry no `x-telo-ref` (the accepted targets are the definition's own
17
- * template-internal entries, not resources of any module), so no reference pass
18
- * reaches them. Introducing the tag at a slot nothing resolves would be worse
19
- * than the string form it replaces: a typo that used to be an obvious runtime
20
- * miss becomes a typo in a construct that advertises static resolution.
21
- *
22
- * Decidable only when every sibling name is LITERAL. A template routinely names
23
- * its entries with CEL (`name: !cel "self.name + '-query'"`), and an expression
24
- * could expand to the referenced name — so one dynamic sibling switches the
25
- * check off for that definition rather than inventing a miss.
26
- *
27
- * Entry-module-scoped, like every other declaration check: a published
28
- * dependency's template body is not the consumer's to fix.
29
- *
30
- * Browser-safe.
31
- */
32
- export function validateTemplateDispatch(manifests, rootModules) {
33
- const out = [];
34
- for (const m of manifests) {
35
- if (m.kind !== "Telo.Definition")
36
- continue;
37
- const meta = m.metadata;
38
- const name = meta?.name;
39
- if (!name)
40
- continue;
41
- if (meta?.module && !rootModules.has(meta.module))
42
- continue;
43
- const bodies = m.resources;
44
- if (!Array.isArray(bodies))
45
- continue;
46
- const siblings = [];
47
- let anyDynamic = false;
48
- for (const entry of bodies) {
49
- const entryName = entry?.metadata?.name;
50
- if (typeof entryName === "string" && !entryName.includes("${{"))
51
- siblings.push(entryName);
52
- else if (entryName !== undefined)
53
- anyDynamic = true;
54
- }
55
- if (anyDynamic)
56
- continue;
57
- for (const field of DISPATCH_FIELDS) {
58
- const value = m[field];
59
- if (!isRefSentinel(value))
60
- continue;
61
- const source = value.source;
62
- const target = source.startsWith("Self.") ? source.slice("Self.".length) : source;
63
- if (siblings.includes(target))
64
- continue;
65
- const suggestion = nearest(target, siblings);
66
- out.push({
67
- severity: DiagnosticSeverity.Error,
68
- code: "TEMPLATE_DISPATCH_UNKNOWN",
69
- source: SOURCE,
70
- message: `${m.kind}/${name}: '${field}: !ref ${source}' names no entry in 'resources:'. ` +
71
- `Available: ${siblings.join(", ") || "(none)"}.` +
72
- (suggestion ? ` Did you mean '${suggestion}'?` : ""),
73
- data: {
74
- resource: { kind: m.kind, name },
75
- filePath: meta?.source,
76
- path: field,
77
- ...(suggestion ? { fix: { replacement: suggestion } } : {}),
78
- },
79
- });
80
- }
81
- }
82
- return out;
83
- }
84
- /** The closest sibling name, when one is close enough to be a typo rather than a
85
- * different name. */
86
- function nearest(target, candidates) {
87
- let best;
88
- for (const candidate of candidates) {
89
- const d = distance(target, candidate);
90
- if (!best || d < best.d)
91
- best = { name: candidate, d };
92
- }
93
- const limit = Math.max(1, Math.floor(target.length / 3));
94
- return best && best.d <= limit ? best.name : undefined;
95
- }
@@ -1,99 +0,0 @@
1
- import type { ResourceManifest } from "@telorun/sdk";
2
- import { isRefSentinel } from "@telorun/templating";
3
- import { distance } from "./levenshtein.js";
4
- import { DiagnosticSeverity, type AnalysisDiagnostic } from "./types.js";
5
-
6
- const SOURCE = "telo-analyzer";
7
-
8
- /** The four slots a `Telo.Definition` names its dispatch target in. Each takes
9
- * the same grammar; which one is legal for a given capability is
10
- * `validate-provider-coherence`'s question, not this one's. */
11
- const DISPATCH_FIELDS = ["invoke", "run", "provide", "mount"] as const;
12
-
13
- /**
14
- * A `!ref` at a definition's dispatch slot must name a sibling `resources:`
15
- * entry.
16
- *
17
- * `!ref` is the one spelling an author — and the editor's rename, completion and
18
- * go-to-definition — expects to be RESOLVED. But a `Telo.Definition` is in both
19
- * `REF_VALIDATION_SKIP_KINDS` and `REF_RESOLUTION_SKIP_KINDS`, and the dispatch
20
- * slots carry no `x-telo-ref` (the accepted targets are the definition's own
21
- * template-internal entries, not resources of any module), so no reference pass
22
- * reaches them. Introducing the tag at a slot nothing resolves would be worse
23
- * than the string form it replaces: a typo that used to be an obvious runtime
24
- * miss becomes a typo in a construct that advertises static resolution.
25
- *
26
- * Decidable only when every sibling name is LITERAL. A template routinely names
27
- * its entries with CEL (`name: !cel "self.name + '-query'"`), and an expression
28
- * could expand to the referenced name — so one dynamic sibling switches the
29
- * check off for that definition rather than inventing a miss.
30
- *
31
- * Entry-module-scoped, like every other declaration check: a published
32
- * dependency's template body is not the consumer's to fix.
33
- *
34
- * Browser-safe.
35
- */
36
- export function validateTemplateDispatch(
37
- manifests: ResourceManifest[],
38
- rootModules: ReadonlySet<string>,
39
- ): AnalysisDiagnostic[] {
40
- const out: AnalysisDiagnostic[] = [];
41
-
42
- for (const m of manifests) {
43
- if (m.kind !== "Telo.Definition") continue;
44
- const meta = m.metadata as { name?: string; module?: string; source?: string } | undefined;
45
- const name = meta?.name;
46
- if (!name) continue;
47
- if (meta?.module && !rootModules.has(meta.module)) continue;
48
-
49
- const bodies = (m as Record<string, unknown>).resources;
50
- if (!Array.isArray(bodies)) continue;
51
-
52
- const siblings: string[] = [];
53
- let anyDynamic = false;
54
- for (const entry of bodies) {
55
- const entryName = (entry as { metadata?: { name?: unknown } } | undefined)?.metadata?.name;
56
- if (typeof entryName === "string" && !entryName.includes("${{")) siblings.push(entryName);
57
- else if (entryName !== undefined) anyDynamic = true;
58
- }
59
- if (anyDynamic) continue;
60
-
61
- for (const field of DISPATCH_FIELDS) {
62
- const value = (m as Record<string, unknown>)[field];
63
- if (!isRefSentinel(value)) continue;
64
- const source = value.source;
65
- const target = source.startsWith("Self.") ? source.slice("Self.".length) : source;
66
- if (siblings.includes(target)) continue;
67
- const suggestion = nearest(target, siblings);
68
- out.push({
69
- severity: DiagnosticSeverity.Error,
70
- code: "TEMPLATE_DISPATCH_UNKNOWN",
71
- source: SOURCE,
72
- message:
73
- `${m.kind}/${name}: '${field}: !ref ${source}' names no entry in 'resources:'. ` +
74
- `Available: ${siblings.join(", ") || "(none)"}.` +
75
- (suggestion ? ` Did you mean '${suggestion}'?` : ""),
76
- data: {
77
- resource: { kind: m.kind, name },
78
- filePath: meta?.source,
79
- path: field,
80
- ...(suggestion ? { fix: { replacement: suggestion } } : {}),
81
- },
82
- });
83
- }
84
- }
85
-
86
- return out;
87
- }
88
-
89
- /** The closest sibling name, when one is close enough to be a typo rather than a
90
- * different name. */
91
- function nearest(target: string, candidates: readonly string[]): string | undefined {
92
- let best: { name: string; d: number } | undefined;
93
- for (const candidate of candidates) {
94
- const d = distance(target, candidate);
95
- if (!best || d < best.d) best = { name: candidate, d };
96
- }
97
- const limit = Math.max(1, Math.floor(target.length / 3));
98
- return best && best.d <= limit ? best.name : undefined;
99
- }