@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
@@ -1,11 +1,11 @@
1
1
  import { isTaggedSentinel } from "@telorun/templating";
2
2
  import { AMBIENT_CONTRACT_ERROR_CODES, isAmbientContractErrorCode, } from "@telorun/sdk";
3
3
  import { scopeResolverForModule } from "./alias-resolver.js";
4
- import { createResolveCtx, resolveScopeUnion, resolveThrowsUnion, } from "./resolve-throws-union.js";
4
+ import { createResolveCtx, handsFailureBack, resolveScopeUnion, resolveThrowsUnion, throwsUses, } from "./resolve-throws-union.js";
5
+ import { forEachDeclaredSlot } from "./schema-walk.js";
5
6
  import { buildEnclosers, collectScopedManifests, enclosingCoverage, } from "./catch-scope.js";
6
7
  import { DiagnosticSeverity } from "./types.js";
7
8
  import { extractAccessChains, validateChainAgainstSchema } from "./validate-cel-context.js";
8
- import { isStepSlot } from "./step-slot.js";
9
9
  const SOURCE = "telo-analyzer";
10
10
  const TEMPLATE_REGEX = /\$\{\{\s*([^}]+?)\s*\}\}/g;
11
11
  /** Walk `definition.schema` and `data` in tandem, invoking `onOutcome` each
@@ -401,12 +401,6 @@ function checkCelChainAgainstDataSchema(entry, dataSchema, resource, filePath, e
401
401
  }
402
402
  return diagnostics;
403
403
  }
404
- /** Rule 8 extension: `inherit: true` only makes sense on a definition whose
405
- * schema declares at least one STEP BODY — an array whose items point at the
406
- * shared grammar, or, for a module published before that fragment existed, one
407
- * carrying the legacy `x-telo-step-context` annotation. That is what drives the
408
- * resolver's generic step traversal; a definition with `inherit: true` and no
409
- * such array has no invocables to inherit from. */
410
404
  /** The capabilities whose lifecycle includes a dispatch a caller can catch. On
411
405
  * every other one a thrown error is a boot-time failure, not a structured
412
406
  * runtime error for a downstream caller — a provider resolves configuration, a
@@ -466,10 +460,14 @@ function validateThrowsDeclarations(manifests) {
466
460
  severity: DiagnosticSeverity.Error,
467
461
  code: "INHERIT_WITHOUT_STEP_CONTEXT",
468
462
  source: SOURCE,
469
- message: `Telo.Definition '${name}' declares throws.inherit: true but its schema declares no step ` +
470
- `body. inherit is only meaningful on a definition that drives invocables through steps ` +
471
- `give an array field 'items: { $ref: "telo://manifest#/$defs/Step" }' (the legacy ` +
472
- `x-telo-step-context annotation is also recognised).`,
463
+ message: `Telo.Definition '${name}' declares throws.inherit: true but its schema dispatches nothing ` +
464
+ `a failure can come back through. inherit makes the kind's union the union of what it ` +
465
+ `dispatches, so the schema needs a step body (an array field with ` +
466
+ `'items: { $ref: "telo://manifest#/$defs/Step" }', or the legacy x-telo-step-context ` +
467
+ `annotation) or a reference slot whose use includes 'call' or 'trigger.consumer' ` +
468
+ `(for a use case map, in any case; a slot declaring no use counts as 'call'). ` +
469
+ `'detached' and 'trigger.inbound' run where no caller awaits them, and ` +
470
+ `'dependency' / 'schema' dispatch nothing.`,
473
471
  data: { resource: { kind: m.kind, name }, filePath, path: "throws.inherit" },
474
472
  });
475
473
  }
@@ -477,35 +475,19 @@ function validateThrowsDeclarations(manifests) {
477
475
  }
478
476
  return diagnostics;
479
477
  }
478
+ /** Rule 8 extension: `inherit: true` needs something to inherit from — a step
479
+ * body, or a reference slot whose use (any case of a case map) hands a failure
480
+ * back. The schema-only mode of the walk the resolver runs per instance, so the
481
+ * check and the union reach the same slots. */
480
482
  function schemaDrivesInvocables(schema) {
481
- if (!schema || typeof schema !== "object")
482
- return false;
483
- if (isStepSlot(schema))
484
- return true;
485
- const props = schema.properties;
486
- if (props && typeof props === "object") {
487
- for (const v of Object.values(props)) {
488
- if (schemaDrivesInvocables(v))
489
- return true;
490
- }
491
- }
492
- if (schema.items && schemaDrivesInvocables(schema.items))
493
- return true;
494
- for (const key of ["oneOf", "anyOf", "allOf"]) {
495
- const arr = schema[key];
496
- if (Array.isArray(arr)) {
497
- for (const sub of arr)
498
- if (schemaDrivesInvocables(sub))
499
- return true;
483
+ let drives = false;
484
+ forEachDeclaredSlot(schema, (declared) => {
485
+ if (declared.kind === "step" ||
486
+ declared.slots.some((slot) => throwsUses(slot).some(handsFailureBack))) {
487
+ drives = true;
500
488
  }
501
- }
502
- if (schema.$defs && typeof schema.$defs === "object") {
503
- for (const v of Object.values(schema.$defs)) {
504
- if (schemaDrivesInvocables(v))
505
- return true;
506
- }
507
- }
508
- return false;
489
+ });
490
+ return drives;
509
491
  }
510
492
  /** Entry point — invoked once per analyze() run. */
511
493
  export function validateThrowsCoverage(manifests, defs, aliases, env, aliasesByModule = new Map(), rootModules = new Set(),
@@ -29,6 +29,20 @@ import type { KeywordDefinition } from "ajv";
29
29
  * `x-telo-type` is deliberately absent — it is the one that emits code.
30
30
  */
31
31
  export declare const ANNOTATION_KEYWORDS: readonly ["x-telo-bindings-from", "x-telo-catches-for", "x-telo-context", "x-telo-context-collection-from", "x-telo-context-element-from", "x-telo-context-from", "x-telo-context-from-ref-kind", "x-telo-context-from-root", "x-telo-context-ref-from", "x-telo-error-context", "x-telo-eval", "x-telo-inline", "x-telo-outcome-list", "x-telo-provides-zone", "x-telo-ref", "x-telo-requires-zone", "x-telo-resource-rules", "x-telo-schema-from", "x-telo-schema-map", "x-telo-schema-projection", "x-telo-schema-projection-from", "x-telo-scope", "x-telo-sensitive", "x-telo-step-context", "x-telo-topology-role", "x-telo-value-schema-from", "x-telo-widget"];
32
+ /** How an AJV instance treats a `live` value type. */
33
+ export interface TeloKeywordOptions {
34
+ /**
35
+ * Assert a `live` type as an instance too, so a literal at such a slot is
36
+ * refused — a value written in a manifest can never be a live instance.
37
+ *
38
+ * STATIC analysis only, where a CEL leaf's stand-in is an instance
39
+ * (`celPlaceholderForSchema`). The kernel's instances leave it off: at
40
+ * dispatch a live value is exempt from validation, and the stage that pulls
41
+ * from it refuses a non-stream itself — so the static half refuses a strict
42
+ * subset of what the runtime refuses, and the two cannot disagree.
43
+ */
44
+ readonly assertLive?: boolean;
45
+ }
32
46
  /**
33
47
  * The `x-telo-type` keyword.
34
48
  *
@@ -37,8 +51,10 @@ export declare const ANNOTATION_KEYWORDS: readonly ["x-telo-bindings-from", "x-t
37
51
  * - a `json` representation validates through its own declared schema, so the
38
52
  * keyword emits nothing — the name carries nominal identity for static wiring
39
53
  * and has no runtime existence at all;
40
- * - a `live` instance is EXEMPT: its value is never traversed, because iterating
41
- * a stream to check it is precisely what the exemption is for;
54
+ * - a `live` instance is EXEMPT at dispatch: its value is never traversed,
55
+ * because iterating a stream to check it is precisely what the exemption is
56
+ * for. Statically ({@link TeloKeywordOptions.assertLive}) it is asserted like
57
+ * any other instance, which checks what the value IS and never iterates it;
42
58
  * - every other instance is ASSERTED against the constructor its binding names.
43
59
  *
44
60
  * An unknown name emits nothing here. It is a hard diagnostic in the analyzer
@@ -46,15 +62,16 @@ export declare const ANNOTATION_KEYWORDS: readonly ["x-telo-bindings-from", "x-t
46
62
  * manifest that wrote it; failing compilation instead would take out every
47
63
  * validator in a module for one typo in one slot.
48
64
  */
49
- export declare function valueTypeKeyword(): KeywordDefinition;
65
+ export declare function valueTypeKeyword(options?: TeloKeywordOptions): KeywordDefinition;
50
66
  /**
51
67
  * Register every Telo keyword on an AJV instance: the annotations as no-ops and
52
68
  * `x-telo-type` as the one that checks.
53
69
  *
54
70
  * Every AJV instance in the runtime and the analyzer goes through this, so a
55
- * schema means the same thing wherever it is validated.
71
+ * schema means the same thing wherever it is validated — apart from the one
72
+ * posture {@link TeloKeywordOptions} names, which only static analysis takes.
56
73
  */
57
74
  export declare function registerTeloKeywords(ajv: {
58
75
  addKeyword: (keyword: any, definition?: any) => unknown;
59
- }): void;
76
+ }, options?: TeloKeywordOptions): void;
60
77
  //# sourceMappingURL=value-type-keyword.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"value-type-keyword.d.ts","sourceRoot":"","sources":["../src/value-type-keyword.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;+EAqB+E;AAG/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,KAAK,CAAC;AAiB7C;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,woBA4BtB,CAAC;AAEX;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,IAAI,iBAAiB,CA+BpD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE;IACxC,UAAU,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;CACzD,GAAG,IAAI,CAGP"}
1
+ {"version":3,"file":"value-type-keyword.d.ts","sourceRoot":"","sources":["../src/value-type-keyword.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;+EAqB+E;AAG/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,KAAK,CAAC;AAiB7C;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,woBA4BtB,CAAC;AAEX,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;OASG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,kBAAuB,GAAG,iBAAiB,CAsCpF;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE;IAAE,UAAU,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,EAAE,GAAG,KAAK,OAAO,CAAA;CAAE,EAChE,OAAO,GAAE,kBAAuB,GAC/B,IAAI,CAGN"}
@@ -68,8 +68,10 @@ export const ANNOTATION_KEYWORDS = [
68
68
  * - a `json` representation validates through its own declared schema, so the
69
69
  * keyword emits nothing — the name carries nominal identity for static wiring
70
70
  * and has no runtime existence at all;
71
- * - a `live` instance is EXEMPT: its value is never traversed, because iterating
72
- * a stream to check it is precisely what the exemption is for;
71
+ * - a `live` instance is EXEMPT at dispatch: its value is never traversed,
72
+ * because iterating a stream to check it is precisely what the exemption is
73
+ * for. Statically ({@link TeloKeywordOptions.assertLive}) it is asserted like
74
+ * any other instance, which checks what the value IS and never iterates it;
73
75
  * - every other instance is ASSERTED against the constructor its binding names.
74
76
  *
75
77
  * An unknown name emits nothing here. It is a hard diagnostic in the analyzer
@@ -77,7 +79,7 @@ export const ANNOTATION_KEYWORDS = [
77
79
  * manifest that wrote it; failing compilation instead would take out every
78
80
  * validator in a module for one typo in one slot.
79
81
  */
80
- export function valueTypeKeyword() {
82
+ export function valueTypeKeyword(options = {}) {
81
83
  return {
82
84
  keyword: X_TELO_TYPE,
83
85
  // Both spellings: a bare name, or the object form carrying type arguments.
@@ -86,7 +88,9 @@ export function valueTypeKeyword() {
86
88
  const entry = readValueTypeSlot({
87
89
  [X_TELO_TYPE]: cxt.schema,
88
90
  })?.entry;
89
- if (!entry || entry.representation !== "instance" || entry.live)
91
+ if (!entry || entry.representation !== "instance")
92
+ return;
93
+ if (entry.live && !options.assertLive)
90
94
  return;
91
95
  const binding = VALUE_TYPE_BINDINGS[entry.binding];
92
96
  if (!binding)
@@ -104,6 +108,10 @@ export function valueTypeKeyword() {
104
108
  error: {
105
109
  message: (cxt) => {
106
110
  const entry = readValueTypeSlot({ [X_TELO_TYPE]: cxt.schema })?.entry;
111
+ if (entry?.live) {
112
+ return (`must be a live ${entry.name} — a value written in the manifest can never be one; ` +
113
+ "pass the result of a step that produces it, with a !cel expression");
114
+ }
107
115
  return entry?.binding === "bytes"
108
116
  ? "must be raw bytes (a Uint8Array) — bytes cannot be written inline in a manifest"
109
117
  : `must be a ${entry?.name ?? "declared value type"} — this value is not writable inline in a manifest`;
@@ -116,10 +124,11 @@ export function valueTypeKeyword() {
116
124
  * `x-telo-type` as the one that checks.
117
125
  *
118
126
  * Every AJV instance in the runtime and the analyzer goes through this, so a
119
- * schema means the same thing wherever it is validated.
127
+ * schema means the same thing wherever it is validated — apart from the one
128
+ * posture {@link TeloKeywordOptions} names, which only static analysis takes.
120
129
  */
121
- export function registerTeloKeywords(ajv) {
130
+ export function registerTeloKeywords(ajv, options = {}) {
122
131
  for (const keyword of ANNOTATION_KEYWORDS)
123
132
  ajv.addKeyword(keyword);
124
- ajv.addKeyword(valueTypeKeyword());
133
+ ajv.addKeyword(valueTypeKeyword(options));
125
134
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.72.0",
3
+ "version": "0.74.0",
4
4
  "description": "Telo Analyzer - Static manifest validator for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
@@ -43,13 +43,14 @@
43
43
  "jsonpath-plus": "^10.3.0",
44
44
  "packageurl-js": "^2.0.1",
45
45
  "yaml": "^2.8.3",
46
- "@telorun/templating": "0.19.0"
46
+ "@telorun/templating": "0.20.1"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^20.0.0",
50
50
  "typescript": "^5.0.0",
51
51
  "vitest": "^2.1.8",
52
- "@telorun/sdk": "0.87.0"
52
+ "@telorun/glob": "0.3.0",
53
+ "@telorun/sdk": "0.90.0"
53
54
  },
54
55
  "peerDependencies": {
55
56
  "@telorun/sdk": "*"
package/src/analyzer.ts CHANGED
@@ -25,7 +25,13 @@ import {
25
25
  } from "./cel-environment.js";
26
26
  import { DefinitionRegistry } from "./definition-registry.js";
27
27
  import { readDeprecation } from "./deprecation.js";
28
- import { type ContractDirection, effectiveAuthorSchema } from "./extends-resolution.js";
28
+ import {
29
+ type ContractDirection,
30
+ type DefResolver,
31
+ effectiveAuthorSchema,
32
+ inheritedCapability,
33
+ inheritedRequiredFields,
34
+ } from "./extends-resolution.js";
29
35
  import {
30
36
  analyzerContractScope,
31
37
  type ContractScope,
@@ -43,6 +49,7 @@ import {
43
49
  buildObservedStateIndex,
44
50
  buildObservedStateResourcesSchema,
45
51
  collectRunReachableNames,
52
+ forwardedResourceKey,
46
53
  observedStateRead,
47
54
  validateObservedStateDeclarations,
48
55
  } from "./validate-observed-state.js";
@@ -142,6 +149,7 @@ import {
142
149
  import {
143
150
  celEvalModeAt,
144
151
  celEvalSites,
152
+ implicitEvalSites,
145
153
  mergeCelEvalSites,
146
154
  NO_CEL_EVAL_SITES,
147
155
  type CelEvalSites,
@@ -160,6 +168,8 @@ import { validateIdentifierNames } from "./validate-identifier-names.js";
160
168
  import { validateExtends } from "./validate-extends.js";
161
169
  import { validateLogging } from "./validate-logging.js";
162
170
  import { validateModuleArtifact } from "./validate-module-artifact.js";
171
+ import { validateNativeEntries } from "./validate-native-entries.js";
172
+ import { validateSourceEntries } from "./validate-source-entries.js";
163
173
  import { validateIncludePlacement } from "./validate-include-placement.js";
164
174
  import { validateModuleMetadata } from "./validate-module-metadata.js";
165
175
  import { validateRequires } from "./validate-requires.js";
@@ -172,8 +182,11 @@ import { kindSatisfies, validateReferences } from "./validate-references.js";
172
182
  import { validateReferenceForms } from "./validate-reference-forms.js";
173
183
  import { isInjectedDeclaration } from "./resource-input.js";
174
184
  import { validateResourceInputs } from "./validate-resource-inputs.js";
175
- import { validateTemplateDispatch } from "./validate-template-dispatch.js";
185
+ import { validateExports } from "./validate-exports.js";
186
+ import { validateTemplateBody } from "./validate-template-body.js";
176
187
  import { validateUnusedDeclarations } from "./validate-unused-declarations.js";
188
+ import { validateScopedNameReach } from "./validate-scope-reach.js";
189
+ import { isForwardedDeclaration } from "./forwarded-declaration.js";
177
190
  import { validateThrowsCoverage } from "./validate-throws-coverage.js";
178
191
  import { readStepSlot } from "./step-slot.js";
179
192
 
@@ -398,9 +411,9 @@ function validateStepInvokeReferences(
398
411
  if (typeof m === "string") loadedModules.add(m);
399
412
  continue;
400
413
  }
401
- const meta = r.metadata as { name?: unknown; module?: unknown; forwardedExport?: unknown };
414
+ const meta = r.metadata as { name?: unknown; module?: unknown };
402
415
  if (typeof meta?.name !== "string" || REF_VALIDATION_SKIP_KINDS.has(r.kind)) continue;
403
- if (meta.forwardedExport === true) {
416
+ if (isForwardedDeclaration(r)) {
404
417
  if (typeof meta.module === "string") loadedModules.add(meta.module);
405
418
  continue;
406
419
  }
@@ -468,11 +481,11 @@ function validateStepInvokeReferences(
468
481
  };
469
482
 
470
483
  for (const m of allManifests) {
471
- const meta = m.metadata as { name?: unknown; source?: unknown; forwardedExport?: unknown };
484
+ const meta = m.metadata as { name?: unknown; source?: unknown };
472
485
  if (
473
486
  typeof meta?.name !== "string" ||
474
487
  REF_VALIDATION_SKIP_KINDS.has(m.kind) ||
475
- meta.forwardedExport === true
488
+ isForwardedDeclaration(m)
476
489
  )
477
490
  continue;
478
491
  const def = defs.resolve(aliases.resolveKind(m.kind) ?? m.kind);
@@ -1526,6 +1539,11 @@ export class StaticAnalyzer {
1526
1539
  // would otherwise fail on a consumer's machine — or, for a mistyped platform
1527
1540
  // axis, silently offer one platform's binary to every host.
1528
1541
  diagnostics.push(...validateModuleArtifact(allManifests));
1542
+ // The module doc's `native:` block: each rule decides whether a platform's
1543
+ // file can ever match a host, or whether two layers would write one path.
1544
+ diagnostics.push(...validateNativeEntries(allManifests, rootModules));
1545
+ // The module doc's `sources:` block: where each staged file comes from.
1546
+ diagnostics.push(...validateSourceEntries(allManifests, rootModules));
1529
1547
  // The descriptive `metadata:` surface. Nothing in the kernel branches on
1530
1548
  // these fields, which is precisely why they need a check: a mistyped one
1531
1549
  // has no runtime failure mode that would ever surface it.
@@ -1807,9 +1825,7 @@ export class StaticAnalyzer {
1807
1825
  // `kind`/CEL are authored in that module's scope (e.g. `Self.X` → that module, not the
1808
1826
  // consumer). Re-validating against the consumer's scope yields false UNDEFINED_KIND /
1809
1827
  // scope-mismatch errors, so skip — they participate here only as resolution targets.
1810
- if ((m.metadata as { forwardedExport?: boolean } | undefined)?.forwardedExport === true) {
1811
- continue;
1812
- }
1828
+ if (isForwardedDeclaration(m)) continue;
1813
1829
 
1814
1830
  // A kind-only stand-in for a `resources:` entry is a DECLARATION, not an
1815
1831
  // instantiation: its kind is routinely an abstract and its configuration
@@ -2029,12 +2045,41 @@ export class StaticAnalyzer {
2029
2045
  allManifests as Record<string, any>[],
2030
2046
  );
2031
2047
  const issues = [...ajvIssues, ...valueSchemaIssues];
2048
+ // WHY A FIELD THIS KIND EXISTS TO SUPPLY IS STILL REQUIRED OF ITS
2049
+ // CONSUMER. A child that `extends` and declares no `base:` is authored
2050
+ // against merge(parent, own), so the parent's `required` stays on the
2051
+ // CHILD's surface — and a kind written to wire that field internally
2052
+ // then demands it from the consumer anyway. The `base:` mapping is what
2053
+ // narrows (with it the surface is the child's own schema alone), and
2054
+ // nothing in "is missing required property" points there, which is what
2055
+ // turns one mistake into a three-step dead end.
2056
+ //
2057
+ // Matched against names DERIVED from the parent, never by parsing the
2058
+ // message: which fields are inherited is a fact about the definition,
2059
+ // and a check that reads a validator's prose breaks when the prose does.
2060
+ const inheritedRequired = inheritedRequiredFields(definition, (k) =>
2061
+ defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k),
2062
+ );
2063
+ const parentKind = (definition as { extends?: string } | undefined)?.extends;
2032
2064
  for (const issue of issues) {
2065
+ // Keyed on the STRUCTURED failure, never on the sentence: the field
2066
+ // names are derived from the parent, and matching them against the
2067
+ // validator's prose would break when the prose changes and mis-fire on
2068
+ // any other issue quoting the same name (an `additionalProperties`
2069
+ // rejection, an `enum` listing).
2070
+ const inherited =
2071
+ issue.keyword === "required" && issue.missingProperty !== undefined
2072
+ ? inheritedRequired.filter((f) => f === issue.missingProperty)
2073
+ : [];
2074
+ const hint =
2075
+ inherited.length > 0 && parentKind
2076
+ ? ` — ${inherited.map((f) => `'${f}'`).join(", ")} ${inherited.length > 1 ? "come" : "comes"} from '${parentKind}', which this kind extends without a 'base:' mapping, so the parent's required fields stay on this kind's author surface. Add 'base:' to set them internally; that also narrows the surface to this kind's own schema.`
2077
+ : "";
2033
2078
  diagnostics.push({
2034
2079
  severity: DiagnosticSeverity.Error,
2035
2080
  code: "SCHEMA_VIOLATION",
2036
2081
  source: SOURCE,
2037
- message: `${m.kind}/${resource.name}: ${issue.message}`,
2082
+ message: `${m.kind}/${resource.name}: ${issue.message}${hint}`,
2038
2083
  data: { resource, filePath, path: issue.path },
2039
2084
  });
2040
2085
  }
@@ -2472,8 +2517,15 @@ export class StaticAnalyzer {
2472
2517
 
2473
2518
  // The non-eval-field check only applies to runtime resource instances:
2474
2519
  // structural / templating kinds (capability `Telo.Template`, or no
2475
- // definition) carry CEL the kernel evaluates by other rules.
2476
- const capability = e.definition?.capability;
2520
+ // definition) carry CEL the kernel evaluates by other rules. The
2521
+ // capability is INHERITED along `extends` — an inheritance kind
2522
+ // writes none of its own — and reading the declared one left the
2523
+ // rule off for every such kind, and its expressions untyped.
2524
+ const resolveDef: DefResolver = (k) =>
2525
+ defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k);
2526
+ const capability = e.definition
2527
+ ? inheritedCapability(e.definition as unknown as ResourceDefinition, resolveDef)
2528
+ : undefined;
2477
2529
  celRuleApplies =
2478
2530
  !!e.definition?.schema && capability !== undefined && capability !== "Telo.Template";
2479
2531
  if (celRuleApplies) {
@@ -2487,15 +2539,18 @@ export class StaticAnalyzer {
2487
2539
  // disagreeing about what the manifest means.
2488
2540
  const ownSchema = effectiveAuthorSchema(
2489
2541
  e.definition as unknown as ResourceDefinition,
2490
- (k) => defs.resolve(aliases.resolveKind(k) ?? k) ?? defs.resolve(k),
2542
+ resolveDef,
2491
2543
  ) as Record<string, any>;
2492
2544
  const capabilityDef = capability ? defs.resolve(capability) : undefined;
2493
2545
  // A `Telo.Provider`'s fields are implicitly compile-eval — the
2494
2546
  // capability abstract carries the root annotation — so its reads are
2495
- // covered here without the provider restating anything.
2547
+ // covered here without the provider restating anything. A base-form
2548
+ // child's own fields are too (`implicitEvalSites`): `base:` reads
2549
+ // them once at create().
2496
2550
  celSites = mergeCelEvalSites(
2497
2551
  celEvalSites(ownSchema),
2498
2552
  celEvalSites(capabilityDef?.schema as Record<string, any> | undefined),
2553
+ implicitEvalSites(e.definition as { base?: unknown }),
2499
2554
  );
2500
2555
  } else {
2501
2556
  celSites = NO_CEL_EVAL_SITES;
@@ -2539,9 +2594,17 @@ export class StaticAnalyzer {
2539
2594
  if (!read) continue;
2540
2595
  // An import's exported instance is indexed under `<Alias>.<name>`,
2541
2596
  // the two-level shape it publishes under, so a cross-module read
2542
- // is checked exactly like a local one.
2597
+ // is checked exactly like a local one. A bare name read inside a
2598
+ // dependency's own manifest names that dependency's resource.
2599
+ const readerModule = isForwardedDeclaration(m)
2600
+ ? (m.metadata?.module as string | undefined)
2601
+ : undefined;
2543
2602
  const reported = observedState.get(
2544
- read.alias ? `${read.alias}.${read.name}` : read.name,
2603
+ read.alias
2604
+ ? `${read.alias}.${read.name}`
2605
+ : readerModule !== undefined
2606
+ ? forwardedResourceKey(readerModule, read.name)
2607
+ : read.name,
2545
2608
  );
2546
2609
 
2547
2610
  if (celRuleApplies && celEvalModeAt(celSites, path) === "compile") {
@@ -2801,10 +2864,16 @@ export class StaticAnalyzer {
2801
2864
  // Warn about declared variables / secrets / ports that no CEL references.
2802
2865
  diagnostics.push(...validateUnusedDeclarations(allManifests, this.celEnv));
2803
2866
 
2804
- // A `!ref` at a definition's dispatch slot must name a sibling `resources:`
2805
- // entry — the slot no reference pass reaches, so the tag would otherwise
2806
- // advertise a resolution nothing performs.
2807
- diagnostics.push(...validateTemplateDispatch(allManifests, rootModules));
2867
+ // A template body's reference surface its entry names, its dispatch slots
2868
+ // and the ref slots inside each entry — which no reference pass reaches,
2869
+ // since a `Telo.Definition` is in both skip sets.
2870
+ diagnostics.push(
2871
+ ...validateTemplateBody(allManifests, defs, aliases, aliasesByModule, rootModules),
2872
+ );
2873
+
2874
+ // A library's export list, resolved against what it declares — otherwise a
2875
+ // listed name that exists nowhere fails in the consumer's file.
2876
+ diagnostics.push(...validateExports(allManifests, defs, aliases, rootModules));
2808
2877
 
2809
2878
  // A library's declared resource inputs, and every import that supplies them.
2810
2879
  diagnostics.push(
@@ -2818,6 +2887,14 @@ export class StaticAnalyzer {
2818
2887
  ),
2819
2888
  );
2820
2889
 
2890
+ // An inline declaration referencing a name declared by a scope it was
2891
+ // written inside but is created outside of.
2892
+ diagnostics.push(
2893
+ ...validateScopedNameReach(allManifests, defs, aliases, aliasesByModule, rootModules, (expr) =>
2894
+ celAccessChains(this.celEnv, expr),
2895
+ ),
2896
+ );
2897
+
2821
2898
  // Reroute diagnostics on synthetic (inline-extracted) resources back to
2822
2899
  // the chain root so position-index lookups land on the parent doc.
2823
2900
  return rewriteSyntheticOrigins(
@@ -0,0 +1,24 @@
1
+ // GENERATED by scripts/generate-artifact-axes.mjs from analyzer/artifact-axes/axes.json — do not edit.
2
+
3
+ /** The selector platform axes, in canonical order. Closed as a set of axis
4
+ * names; the set of values stays open. */
5
+ export const PLATFORM_AXES = ["os", "arch", "libc", "abi"] as const;
6
+
7
+ export type PlatformAxis = (typeof PLATFORM_AXES)[number];
8
+
9
+ /** A form an axis value must take beyond the shared token grammar. */
10
+ export interface AxisValueForm {
11
+ readonly pattern: RegExp;
12
+ /** The form as a reader writes it, e.g. `<family>-<version>`. */
13
+ readonly form: string;
14
+ readonly examples: readonly string[];
15
+ }
16
+
17
+ /** The axes whose values carry a form of their own. */
18
+ export const AXIS_VALUE_FORMS: Readonly<Partial<Record<PlatformAxis, AxisValueForm>>> = {
19
+ abi: {
20
+ pattern: new RegExp("^[a-z][a-z0-9_]*-[0-9]+(\\.[0-9]+)*$"),
21
+ form: "<family>-<version>",
22
+ examples: ["node-137", "telo-2"],
23
+ },
24
+ };
@@ -29,7 +29,6 @@
29
29
  */
30
30
 
31
31
  import {
32
- LAYER_ROLES,
33
32
  isLayerRole,
34
33
  normalizeSelector,
35
34
  roleCarriesSelector,
@@ -48,7 +47,7 @@ const CONTENT_DIGEST = /^sha256-[A-Za-z0-9_-]{43}$/;
48
47
 
49
48
  export interface ArtifactLayer {
50
49
  role: LayerRole;
51
- /** Present on the code-bearing roles (`controller`, `library`) only. */
50
+ /** Present on the selector-keyed roles (`controller`, `library`, `native`) only. */
52
51
  selector?: ArtifactSelector;
53
52
  /** OCI blob digest — addresses the layer and verifies the transfer. */
54
53
  blob: string;
@@ -101,10 +100,12 @@ export function parseLayerIndex(value: unknown, describe = "layers"): ArtifactLa
101
100
  const entry = raw as Record<string, unknown>;
102
101
  if (typeof entry.role !== "string" || entry.role === "") {
103
102
  throw new LayerIndexError(
104
- `${where}: role is required and must be one of ${LAYER_ROLES.map((r) => `'${r}'`).join(", ")}; ` +
103
+ `${where}: role is required and must be a non-empty string; ` +
105
104
  `got ${entry.role === undefined ? "nothing" : `'${String(entry.role)}'`}.`,
106
105
  );
107
106
  }
107
+ const blob = digest("blob", entry.blob, where);
108
+ const integrity = digest("integrity", entry.integrity, where);
108
109
  // A role this runtime does not know is SKIPPED, never rejected. Roles are
109
110
  // added over time, and a runtime that cannot name one cannot need it — while
110
111
  // throwing would make the whole manifest unreadable, so a module gaining a
@@ -120,6 +121,8 @@ export function parseLayerIndex(value: unknown, describe = "layers"): ArtifactLa
120
121
  throw new LayerIndexError(`${where}: a ${role} layer must declare a selector.`);
121
122
  }
122
123
  selector = normalizeSelector(entry.selector, where);
124
+ // An unknown axis skips the entry as an unknown role does (spec §3.1).
125
+ if (selector === undefined) return;
123
126
  // Scoped by role: a module's `js` controller layer and its `js` library
124
127
  // layer are different layers with the same selector, and only a collision
125
128
  // *within* one role means two layers claim one address.
@@ -146,8 +149,8 @@ export function parseLayerIndex(value: unknown, describe = "layers"): ArtifactLa
146
149
  layers.push({
147
150
  role,
148
151
  ...(selector ? { selector } : {}),
149
- blob: digest("blob", entry.blob, where),
150
- integrity: digest("integrity", entry.integrity, where),
152
+ blob,
153
+ integrity,
151
154
  });
152
155
  });
153
156
 
@@ -157,7 +160,7 @@ export function parseLayerIndex(value: unknown, describe = "layers"): ArtifactLa
157
160
  /** The singleton layer for a role, or undefined when the artifact has none. */
158
161
  export function singletonLayer(
159
162
  layers: readonly ArtifactLayer[],
160
- role: Exclude<LayerRole, "controller" | "library">,
163
+ role: Exclude<LayerRole, "controller" | "library" | "native">,
161
164
  ): ArtifactLayer | undefined {
162
165
  return layers.find((l) => l.role === role);
163
166
  }