@telorun/analyzer 0.61.0 → 0.62.1

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 (73) hide show
  1. package/dist/analyzer.d.ts.map +1 -1
  2. package/dist/analyzer.js +130 -9
  3. package/dist/builtins.d.ts.map +1 -1
  4. package/dist/builtins.js +69 -12
  5. package/dist/cel-bindings.d.ts +0 -6
  6. package/dist/cel-bindings.d.ts.map +1 -1
  7. package/dist/cel-bindings.js +3 -28
  8. package/dist/definition-registry.d.ts +17 -0
  9. package/dist/definition-registry.d.ts.map +1 -1
  10. package/dist/definition-registry.js +31 -2
  11. package/dist/identifier-name.d.ts +114 -0
  12. package/dist/identifier-name.d.ts.map +1 -0
  13. package/dist/identifier-name.js +183 -0
  14. package/dist/index.d.ts +10 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +8 -0
  17. package/dist/manifest-schemas.d.ts +81 -0
  18. package/dist/manifest-schemas.d.ts.map +1 -1
  19. package/dist/manifest-schemas.js +208 -6
  20. package/dist/release/payload-digest.d.ts +7 -3
  21. package/dist/release/payload-digest.d.ts.map +1 -1
  22. package/dist/release/payload-digest.js +7 -3
  23. package/dist/requires-block.d.ts +125 -0
  24. package/dist/requires-block.d.ts.map +1 -0
  25. package/dist/requires-block.js +182 -0
  26. package/dist/schema-keywords.d.ts +68 -0
  27. package/dist/schema-keywords.d.ts.map +1 -0
  28. package/dist/schema-keywords.js +324 -0
  29. package/dist/schema-region.d.ts +12 -1
  30. package/dist/schema-region.d.ts.map +1 -1
  31. package/dist/schema-region.js +12 -1
  32. package/dist/telo-version.d.ts +3 -0
  33. package/dist/telo-version.d.ts.map +1 -0
  34. package/dist/telo-version.js +8 -0
  35. package/dist/types.d.ts +31 -0
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/validate-identifier-names.d.ts +31 -0
  38. package/dist/validate-identifier-names.d.ts.map +1 -0
  39. package/dist/validate-identifier-names.js +144 -0
  40. package/dist/validate-observed-state.d.ts +9 -2
  41. package/dist/validate-observed-state.d.ts.map +1 -1
  42. package/dist/validate-observed-state.js +9 -2
  43. package/dist/validate-references.d.ts.map +1 -1
  44. package/dist/validate-references.js +5 -26
  45. package/dist/validate-requires.d.ts +49 -0
  46. package/dist/validate-requires.d.ts.map +1 -0
  47. package/dist/validate-requires.js +99 -0
  48. package/dist/value-type-keyword.d.ts +1 -1
  49. package/dist/value-type-keyword.d.ts.map +1 -1
  50. package/dist/value-type-keyword.js +1 -0
  51. package/dist/version-range.d.ts +88 -0
  52. package/dist/version-range.d.ts.map +1 -0
  53. package/dist/version-range.js +173 -0
  54. package/package.json +2 -2
  55. package/src/analyzer.ts +146 -10
  56. package/src/builtins.ts +73 -12
  57. package/src/cel-bindings.ts +3 -28
  58. package/src/definition-registry.ts +30 -2
  59. package/src/identifier-name.ts +228 -0
  60. package/src/index.ts +34 -0
  61. package/src/manifest-schemas.ts +223 -4
  62. package/src/release/payload-digest.ts +7 -3
  63. package/src/requires-block.ts +253 -0
  64. package/src/schema-keywords.ts +359 -0
  65. package/src/schema-region.ts +12 -1
  66. package/src/telo-version.ts +9 -0
  67. package/src/types.ts +32 -0
  68. package/src/validate-identifier-names.ts +173 -0
  69. package/src/validate-observed-state.ts +9 -2
  70. package/src/validate-references.ts +5 -26
  71. package/src/validate-requires.ts +129 -0
  72. package/src/value-type-keyword.ts +1 -0
  73. package/src/version-range.ts +238 -0
@@ -1 +1 @@
1
- {"version":3,"file":"validate-observed-state.d.ts","sourceRoot":"","sources":["../src/validate-observed-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAgBjD;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB;;;CAGjC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,SAAS,gBAAgB,EAAE,GACrC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAmB3E;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC;+CAC2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,iBAAiB,GAAG,SAAS,CAOzF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,CAQtE;AAGD;;4EAE4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,SAAS,gBAAgB,EAAE,EACtC,IAAI,EAAE;IAAE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAAA;CAAE,EAC/D,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAAC,cAAc,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,EAC/G,MAAM,CAAC,EAAE,YAAY,GACpB,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAsC/B;AAyDD;;;gDAGgD;AAChD,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC5C,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASrB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,IAAI,CAmBN"}
1
+ {"version":3,"file":"validate-observed-state.d.ts","sourceRoot":"","sources":["../src/validate-observed-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAgBjD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB;;;CAGjC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,SAAS,gBAAgB,EAAE,GACrC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAmB3E;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC;+CAC2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,iBAAiB,GAAG,SAAS,CAOzF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,CAQtE;AAGD;;4EAE4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,SAAS,gBAAgB,EAAE,EACtC,IAAI,EAAE;IAAE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAAA;CAAE,EAC/D,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAAC,cAAc,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,EAC/G,MAAM,CAAC,EAAE,YAAY,GACpB,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAsC/B;AAyDD;;;gDAGgD;AAChD,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC5C,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASrB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,IAAI,CAmBN"}
@@ -16,8 +16,15 @@ const SYSTEM_KINDS = new Set([
16
16
  * {@link validateObservedStateDeclarations} rather than here, so the author gets
17
17
  * a message naming the rule and the fix instead of AJV's "must NOT be valid".
18
18
  *
19
- * Exported from the analyzer and re-used by the kernel's manifest schemas, so
20
- * the rule has one definition rather than two kept in sync by hand.
19
+ * THE KERNEL'S SHAPE CHECK, not the analyzer's. The analyzer's builtins point
20
+ * their `status:` slot at the `JsonSchema7` fragment, which describes the same
21
+ * block far more precisely; the kernel keeps this permissive one, and the two do
22
+ * not drift into disagreement because the fragment only ever NARROWS what this
23
+ * accepts. That split is the same one the `required:` rule above draws: the
24
+ * loader answers "is this the right shape at all", and the check that can name
25
+ * the offending keyword and its line stays with `telo check`. Wiring the fragment
26
+ * into the kernel too would turn a check-time diagnostic into a boot failure for
27
+ * every already-published manifest carrying a sloppy keyword.
21
28
  */
22
29
  export const OBSERVED_STATE_SCHEMA = {
23
30
  type: "object",
@@ -1 +1 @@
1
- {"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAuD/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CAmetB"}
1
+ {"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAuD/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA8ctB"}
@@ -188,32 +188,11 @@ export function validateReferences(resources, context) {
188
188
  });
189
189
  }
190
190
  }
191
- // A resource name must contain no dot. The `!ref` resolver splits the tag's source on
192
- // the first dot to separate an import alias from the resource name, so a dotted name
193
- // would mis-resolve into a cross-module lookup. This is the load-bearing invariant of
194
- // the reference grammar, so it is enforced here rather than left to the (unenforced)
195
- // casing convention.
196
- for (const [name, list] of byNameAll) {
197
- if (!name.includes("."))
198
- continue;
199
- for (const r of list) {
200
- const m = r.metadata;
201
- const range = typeof m?.sourceLine === "number"
202
- ? {
203
- start: { line: m.sourceLine, character: 0 },
204
- end: { line: m.sourceLine, character: Number.MAX_SAFE_INTEGER },
205
- }
206
- : undefined;
207
- diagnostics.push({
208
- severity: DiagnosticSeverity.Error,
209
- code: "INVALID_RESOURCE_NAME",
210
- source: SOURCE,
211
- message: `${r.kind}/${name}: resource name must not contain '.' — in a '!ref' the '.' separates an import alias from the resource name`,
212
- ...(range ? { range } : {}),
213
- data: { resource: { kind: r.kind, name }, filePath: m?.source, path: "metadata.name" },
214
- });
215
- }
216
- }
191
+ // The dot rule that used to live here is now the strictest special case of
192
+ // the identifier grammar in `validate-identifier-names.ts` a dot is one of
193
+ // several characters that make a name unreferenceable, and checking one of
194
+ // them here while the rest went unchecked is what let a hyphenated name
195
+ // through to silently evaluate as arithmetic.
217
196
  // Single-resource map for the resolution / scope lookups below — when a
218
197
  // collision exists, falling back to the first occurrence keeps the rest
219
198
  // of the pass behaving the same as before the duplicate diagnostic was
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The strict half of `requires-block.ts` — the accessor/validator split
3
+ * `zone-slot.ts` / `validate-zone-slots.ts` established, and for the same reason:
4
+ * the reader must not throw on a malformed block (every consumer would have to
5
+ * guard), while a malformed block must not pass silently either.
6
+ *
7
+ * Two distinct outputs, and conflating them was the whole failure this mechanism
8
+ * addresses:
9
+ *
10
+ * - **`MODULE_REQUIRES_NEWER_RUNTIME`** — the module is fine and *this runtime*
11
+ * is too old. Raised per module before that module's own validation, so the
12
+ * version message wins over the vocabulary errors it would otherwise be buried
13
+ * in (`ZONE_ANNOTATION_INVALID`, an `additionalProperties` violation against a
14
+ * kernel-owned schema, an unknown `use` token). Those errors are true but
15
+ * blame the module author for a version skew.
16
+ * - **`REQUIRES_INVALID`** — the declaration itself is malformed. Severity turns
17
+ * on ownership rather than presence: an ERROR on the entry's own modules,
18
+ * whose author can fix it, and a WARNING on a dependency, whose author is the
19
+ * only one who can. It is not silent on a dependency, because `readRequires`
20
+ * drops an unparseable range and the gate then reads the module as satisfied —
21
+ * so it loads while stating a requirement it failed to state, and `telo
22
+ * upgrade` (which refuses such a version) would be holding it back for a
23
+ * reason the load path never mentions.
24
+ *
25
+ * **Unknown axes are suppressed while the `telo` requirement is unmet.** An older
26
+ * runtime not recognising a newer host axis is a *consequence* of the version
27
+ * skew — the axis exists, this runtime is simply too old to know it — so
28
+ * reporting it beside the gate diagnostic would manufacture a second defect from
29
+ * one cause. Once `telo` is satisfied, an unrecognized axis is a real error: a
30
+ * runtime at or above the declared generation is expected to know every axis that
31
+ * generation defines.
32
+ */
33
+ import type { ResourceManifest } from "@telorun/sdk";
34
+ import { type HostVersions } from "./requires-block.js";
35
+ import { type AnalysisDiagnostic } from "./types.js";
36
+ export interface ValidateRequiresOptions {
37
+ /** The surface generation the analyzing runtime implements. Defaults to this
38
+ * build's own — see `AnalysisOptions.teloVersion`. */
39
+ teloVersion?: string;
40
+ /** Versions the running host can speak for. Absent in a browser, where there
41
+ * is no host to report; supplied by the kernel and CLI. */
42
+ hostVersions?: HostVersions;
43
+ /** Module names owned by the entry, when the caller can distinguish them.
44
+ * `REQUIRES_INVALID` is limited to these; the gate is not, because a
45
+ * dependency this runtime cannot read still stops the consumer dead. */
46
+ entryModules?: ReadonlySet<string>;
47
+ }
48
+ export declare function validateRequires(manifests: ResourceManifest[], options?: ValidateRequiresOptions): AnalysisDiagnostic[];
49
+ //# sourceMappingURL=validate-requires.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-requires.d.ts","sourceRoot":"","sources":["../src/validate-requires.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,EAAkC,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExF,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE,MAAM,WAAW,uBAAuB;IACtC;2DACuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;gEAC4D;IAC5D,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;6EAEyE;IACzE,YAAY,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACpC;AAED,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,GAAE,uBAA4B,GACpC,kBAAkB,EAAE,CAuEtB"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The strict half of `requires-block.ts` — the accessor/validator split
3
+ * `zone-slot.ts` / `validate-zone-slots.ts` established, and for the same reason:
4
+ * the reader must not throw on a malformed block (every consumer would have to
5
+ * guard), while a malformed block must not pass silently either.
6
+ *
7
+ * Two distinct outputs, and conflating them was the whole failure this mechanism
8
+ * addresses:
9
+ *
10
+ * - **`MODULE_REQUIRES_NEWER_RUNTIME`** — the module is fine and *this runtime*
11
+ * is too old. Raised per module before that module's own validation, so the
12
+ * version message wins over the vocabulary errors it would otherwise be buried
13
+ * in (`ZONE_ANNOTATION_INVALID`, an `additionalProperties` violation against a
14
+ * kernel-owned schema, an unknown `use` token). Those errors are true but
15
+ * blame the module author for a version skew.
16
+ * - **`REQUIRES_INVALID`** — the declaration itself is malformed. Severity turns
17
+ * on ownership rather than presence: an ERROR on the entry's own modules,
18
+ * whose author can fix it, and a WARNING on a dependency, whose author is the
19
+ * only one who can. It is not silent on a dependency, because `readRequires`
20
+ * drops an unparseable range and the gate then reads the module as satisfied —
21
+ * so it loads while stating a requirement it failed to state, and `telo
22
+ * upgrade` (which refuses such a version) would be holding it back for a
23
+ * reason the load path never mentions.
24
+ *
25
+ * **Unknown axes are suppressed while the `telo` requirement is unmet.** An older
26
+ * runtime not recognising a newer host axis is a *consequence* of the version
27
+ * skew — the axis exists, this runtime is simply too old to know it — so
28
+ * reporting it beside the gate diagnostic would manufacture a second defect from
29
+ * one cause. Once `telo` is satisfied, an unrecognized axis is a real error: a
30
+ * runtime at or above the declared generation is expected to know every axis that
31
+ * generation defines.
32
+ */
33
+ import { evaluateRequires, readRequires } from "./requires-block.js";
34
+ import { TELO_SURFACE_VERSION } from "./telo-version.js";
35
+ import { DiagnosticSeverity } from "./types.js";
36
+ const SOURCE = "telo-analyzer";
37
+ export function validateRequires(manifests, options = {}) {
38
+ const running = options.teloVersion ?? TELO_SURFACE_VERSION;
39
+ const out = [];
40
+ for (const manifest of manifests) {
41
+ if (manifest.kind !== "Telo.Application" && manifest.kind !== "Telo.Library")
42
+ continue;
43
+ const doc = manifest;
44
+ const { declared, block, issues } = readRequires(doc);
45
+ if (!declared)
46
+ continue;
47
+ const metadata = (manifest.metadata ?? {});
48
+ const name = typeof metadata.name === "string" ? metadata.name : undefined;
49
+ const filePath = typeof metadata.source === "string" ? metadata.source : undefined;
50
+ const label = `${manifest.kind}/${name ?? "(unnamed)"}`;
51
+ const resource = { kind: manifest.kind, name };
52
+ const verdict = evaluateRequires(block, running, options.hostVersions);
53
+ if (!verdict.satisfied) {
54
+ const axis = verdict.axis === "telo" ? "telo" : `host.${verdict.axis}`;
55
+ const remedy = verdict.axis === "telo"
56
+ ? `Upgrade telo, or pin ${name ?? "this module"} to a version whose range accepts ` +
57
+ `${verdict.running}.`
58
+ : `Upgrade ${verdict.axis}, or pin ${name ?? "this module"} to a version whose range ` +
59
+ `accepts ${verdict.running}.`;
60
+ out.push({
61
+ severity: DiagnosticSeverity.Error,
62
+ code: "MODULE_REQUIRES_NEWER_RUNTIME",
63
+ source: SOURCE,
64
+ message: `${label} requires ${axis} '${verdict.declared.raw}'; this runtime reports ` +
65
+ `${verdict.running}. ${remedy}`,
66
+ data: { resource, filePath, path: `requires.${axis}` },
67
+ });
68
+ }
69
+ const owned = options.entryModules === undefined ||
70
+ (typeof metadata.module === "string"
71
+ ? options.entryModules.has(metadata.module)
72
+ : name === undefined || options.entryModules.has(name));
73
+ for (const issue of issues) {
74
+ // See the header: an unknown axis is a symptom while the gate is failing.
75
+ if (issue.unknownAxis && !verdict.satisfied)
76
+ continue;
77
+ // A DEPENDENCY's malformed block is not the consumer's to fix, but it must
78
+ // not be silent either. `readRequires` drops an unparseable range from the
79
+ // block, so the gate reads it as satisfied and the module loads while
80
+ // stating a requirement it failed to state — and `telo upgrade`, which
81
+ // refuses to select such a version, would then be holding a version back
82
+ // for a reason nothing on the load path ever mentions. A warning is what
83
+ // makes those two halves agree about the manifest without handing the
84
+ // consumer an error only its publisher can fix.
85
+ out.push({
86
+ severity: owned ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning,
87
+ code: "REQUIRES_INVALID",
88
+ source: SOURCE,
89
+ message: owned
90
+ ? `${label}: ${issue.message}` + (issue.hint ? ` Write '${issue.hint}' instead.` : "")
91
+ : `${label}: ${issue.message} Its declared requirement cannot be read, so it is not ` +
92
+ `enforced here and \`telo upgrade\` will not select this version. Only the module's ` +
93
+ `publisher can fix it.`,
94
+ data: { resource, filePath, path: issue.path },
95
+ });
96
+ }
97
+ }
98
+ return out;
99
+ }
@@ -28,7 +28,7 @@ import type { KeywordDefinition } from "ajv";
28
28
  *
29
29
  * `x-telo-type` is deliberately absent — it is the one that emits code.
30
30
  */
31
- export declare const ANNOTATION_KEYWORDS: readonly ["x-telo-bindings-from", "x-telo-catches-for", "x-telo-context", "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-schema-from", "x-telo-scope", "x-telo-step-context", "x-telo-topology-role", "x-telo-value-schema-from", "x-telo-widget"];
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-schema-from", "x-telo-scope", "x-telo-step-context", "x-telo-topology-role", "x-telo-value-schema-from", "x-telo-widget"];
32
32
  /**
33
33
  * The `x-telo-type` keyword.
34
34
  *
@@ -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,ueAsBtB,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,ygBAuBtB,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"}
@@ -35,6 +35,7 @@ export const ANNOTATION_KEYWORDS = [
35
35
  "x-telo-bindings-from",
36
36
  "x-telo-catches-for",
37
37
  "x-telo-context",
38
+ "x-telo-context-collection-from",
38
39
  "x-telo-context-element-from",
39
40
  "x-telo-context-from",
40
41
  "x-telo-context-from-ref-kind",
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The version-range grammar `requires:` declares, and the only one Telo reads.
3
+ *
4
+ * Built on `module-version-order.ts`'s precedence rather than the `semver`
5
+ * package, for that file's reason: the analyzer is browser-safe, so the editor
6
+ * reaches the identical rule the kernel does. A second implementation of "does
7
+ * this version satisfy this range" would eventually disagree about what a
8
+ * manifest means, which is the whole failure this mechanism exists to prevent.
9
+ *
10
+ * **A range is a CONJUNCTION of explicit comparators** — `>=0.80.0`,
11
+ * `>=0.40.0 <0.50.0`, `>=0.40.0,<0.50.0`. That is deliberately narrower than
12
+ * npm's grammar, and every exclusion pays for itself:
13
+ *
14
+ * - **`^` and `~` are rejected**, not reinterpreted. On a `0.x` version both
15
+ * mean a single minor (`^0.40.0` is `>=0.40.0 <0.41.0`, identical to
16
+ * `~0.40.0`), and Telo ships breaking changes as minor bumps deliberately. So
17
+ * the caret reading is *correct* and therefore useless — every module would
18
+ * pin to one breaking-change generation and nobody could move telo without
19
+ * the whole standard library republishing. It is also the spelling semver
20
+ * intuition reaches for first, so accepting it would make the failure both
21
+ * common and silent.
22
+ * - **A bare version is rejected.** npm reads `0.80.0` as an exact pin, which is
23
+ * the same trap in a different costume; reading it as `>=` would contradict
24
+ * every other semver consumer. Refusing it and naming the two spellings is
25
+ * the only answer that cannot be misread.
26
+ * - **`||`, hyphen ranges, `*` and `x` are rejected.** A disjunction has no
27
+ * single low or high edge, and the edges are load-bearing: verification runs
28
+ * the CLI *at* them, and a declared upper bound must be checked for existence.
29
+ * A grammar whose edges are undefined cannot be verified, and an unverifiable
30
+ * bound is what this design forbids everywhere else.
31
+ *
32
+ * **Prereleases compare by plain precedence**, with no equivalent of npm's rule
33
+ * that a prerelease only satisfies a range mentioning one at the same
34
+ * `[major, minor, patch]`. That rule exists to stop a caret range dragging a
35
+ * consumer onto an `-rc` build; here the version being tested is *the runtime the
36
+ * user is already running*, not a candidate being selected for them, so the
37
+ * honest answer is the precedence one — a developer on `0.81.0-rc.1` satisfies
38
+ * `>=0.80.0` because they genuinely are past it.
39
+ */
40
+ import { type ParsedModuleVersion } from "./module-version-order.js";
41
+ /** The comparison a single term applies. */
42
+ export type ComparatorOperator = ">=" | ">" | "<=" | "<";
43
+ export interface VersionComparator {
44
+ operator: ComparatorOperator;
45
+ /** The version as authored, for diagnostics and for the existence check. */
46
+ raw: string;
47
+ parsed: ParsedModuleVersion;
48
+ }
49
+ /** A parsed range: every comparator must hold. Never empty. */
50
+ export interface VersionRange {
51
+ /** The range exactly as authored, quoted verbatim in diagnostics. */
52
+ raw: string;
53
+ comparators: VersionComparator[];
54
+ }
55
+ /** Why a range string was refused. `hint` is the spelling to use instead, when
56
+ * there is an unambiguous one — a rejection that cannot say what to write
57
+ * instead is a worse diagnostic than the value it rejects. */
58
+ export interface VersionRangeError {
59
+ message: string;
60
+ hint?: string;
61
+ }
62
+ export type VersionRangeResult = {
63
+ ok: true;
64
+ range: VersionRange;
65
+ } | {
66
+ ok: false;
67
+ error: VersionRangeError;
68
+ };
69
+ /**
70
+ * Parse a range, or explain why it cannot be one. Never throws and never
71
+ * degrades to a permissive reading: a range this refuses is reported, not
72
+ * silently treated as "no requirement".
73
+ */
74
+ export declare function parseVersionRange(raw: unknown): VersionRangeResult;
75
+ /** True when `version` satisfies every comparator. An unparseable `version` is
76
+ * `false` — never a pass, since the caller is asking whether a real runtime is
77
+ * admitted and "cannot tell" must not read as "yes". */
78
+ export declare function rangeAccepts(range: VersionRange, version: string): boolean;
79
+ /** The lower-bound comparator, or `undefined` when the range is open below.
80
+ * This is the version verification installs and runs as the low edge. */
81
+ export declare function lowerBound(range: VersionRange): VersionComparator | undefined;
82
+ /** The upper-bound comparator, or `undefined` when the range is open above —
83
+ * the normal case, where the high edge is HEAD and normal CI already covers it. */
84
+ export declare function upperBound(range: VersionRange): VersionComparator | undefined;
85
+ /** True when no version can satisfy the range — `>=0.90.0 <0.80.0`. Reported
86
+ * rather than left to fail mysteriously at every consumer. */
87
+ export declare function isUnsatisfiable(range: VersionRange): boolean;
88
+ //# sourceMappingURL=version-range.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-range.d.ts","sourceRoot":"","sources":["../src/version-range.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AAEnC,4CAA4C;AAC5C,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,4EAA4E;IAC5E,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAED,+DAA+D;AAC/D,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,iBAAiB,EAAE,CAAC;CAClC;AAED;;+DAE+D;AAC/D,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GACjC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAuB5C;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,kBAAkB,CAkElE;AAED;;yDAEyD;AACzD,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAgB1E;AAED;0EAC0E;AAC1E,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,iBAAiB,GAAG,SAAS,CAE7E;AAED;oFACoF;AACpF,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,iBAAiB,GAAG,SAAS,CAE7E;AAoBD;+DAC+D;AAC/D,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAS5D"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * The version-range grammar `requires:` declares, and the only one Telo reads.
3
+ *
4
+ * Built on `module-version-order.ts`'s precedence rather than the `semver`
5
+ * package, for that file's reason: the analyzer is browser-safe, so the editor
6
+ * reaches the identical rule the kernel does. A second implementation of "does
7
+ * this version satisfy this range" would eventually disagree about what a
8
+ * manifest means, which is the whole failure this mechanism exists to prevent.
9
+ *
10
+ * **A range is a CONJUNCTION of explicit comparators** — `>=0.80.0`,
11
+ * `>=0.40.0 <0.50.0`, `>=0.40.0,<0.50.0`. That is deliberately narrower than
12
+ * npm's grammar, and every exclusion pays for itself:
13
+ *
14
+ * - **`^` and `~` are rejected**, not reinterpreted. On a `0.x` version both
15
+ * mean a single minor (`^0.40.0` is `>=0.40.0 <0.41.0`, identical to
16
+ * `~0.40.0`), and Telo ships breaking changes as minor bumps deliberately. So
17
+ * the caret reading is *correct* and therefore useless — every module would
18
+ * pin to one breaking-change generation and nobody could move telo without
19
+ * the whole standard library republishing. It is also the spelling semver
20
+ * intuition reaches for first, so accepting it would make the failure both
21
+ * common and silent.
22
+ * - **A bare version is rejected.** npm reads `0.80.0` as an exact pin, which is
23
+ * the same trap in a different costume; reading it as `>=` would contradict
24
+ * every other semver consumer. Refusing it and naming the two spellings is
25
+ * the only answer that cannot be misread.
26
+ * - **`||`, hyphen ranges, `*` and `x` are rejected.** A disjunction has no
27
+ * single low or high edge, and the edges are load-bearing: verification runs
28
+ * the CLI *at* them, and a declared upper bound must be checked for existence.
29
+ * A grammar whose edges are undefined cannot be verified, and an unverifiable
30
+ * bound is what this design forbids everywhere else.
31
+ *
32
+ * **Prereleases compare by plain precedence**, with no equivalent of npm's rule
33
+ * that a prerelease only satisfies a range mentioning one at the same
34
+ * `[major, minor, patch]`. That rule exists to stop a caret range dragging a
35
+ * consumer onto an `-rc` build; here the version being tested is *the runtime the
36
+ * user is already running*, not a candidate being selected for them, so the
37
+ * honest answer is the precedence one — a developer on `0.81.0-rc.1` satisfies
38
+ * `>=0.80.0` because they genuinely are past it.
39
+ */
40
+ import { compareParsedModuleVersions, parseModuleVersion, } from "./module-version-order.js";
41
+ /** Operators longest-first, so `>=` is matched before `>`. */
42
+ const OPERATORS = [">=", "<=", ">", "<"];
43
+ function refuse(message, hint) {
44
+ return { ok: false, error: hint === undefined ? { message } : { message, hint } };
45
+ }
46
+ /**
47
+ * Refuse with `>=<version>` as the hint — but only when that hint would itself
48
+ * parse.
49
+ *
50
+ * A hint is a repair the author is meant to paste, so one this parser would
51
+ * reject on the next run is worse than no hint at all: it converts a diagnostic
52
+ * the author can act on into a loop. Everything unparseable (a two-segment
53
+ * version, a date, a leftover placeholder) falls back to the message alone,
54
+ * which still names what is wrong.
55
+ */
56
+ function hintedRefusal(message, version) {
57
+ return parseModuleVersion(version) ? refuse(message, `>=${version}`) : refuse(message);
58
+ }
59
+ /**
60
+ * Parse a range, or explain why it cannot be one. Never throws and never
61
+ * degrades to a permissive reading: a range this refuses is reported, not
62
+ * silently treated as "no requirement".
63
+ */
64
+ export function parseVersionRange(raw) {
65
+ if (typeof raw !== "string" || raw.trim() === "") {
66
+ return refuse(`expected a version range string, got ${describe(raw)}`);
67
+ }
68
+ const text = raw.trim();
69
+ if (text.includes("||")) {
70
+ return refuse(`'${text}' is a disjunction. A range must be a conjunction of comparators, because ` +
71
+ `verification runs the CLI at its lowest and highest bound and a disjunction has neither`);
72
+ }
73
+ if (/\s-\s/.test(text)) {
74
+ return refuse(`'${text}' is a hyphen range`, `${text.split(/\s-\s/)[0]?.trim() ?? ""} rewritten as '>=' and '<=' comparators`);
75
+ }
76
+ const terms = text.split(/[\s,]+/).filter((t) => t !== "");
77
+ const comparators = [];
78
+ for (const term of terms) {
79
+ if (term.startsWith("^") || term.startsWith("~")) {
80
+ const version = term.slice(1);
81
+ const parsed = parseModuleVersion(version);
82
+ const upper = parsed && parsed.major === 0 ? `0.${parsed.minor + 1}.0` : `${(parsed?.major ?? 0) + 1}.0.0`;
83
+ return refuse(`'${term}' is not accepted. Pre-1.0, '${term[0]}' allows only ${version} up to ` +
84
+ `${upper}, and Telo ships breaking changes as minor bumps — so it pins this module to ` +
85
+ `a single release generation, which is almost never what is meant`, `>=${version}`);
86
+ }
87
+ // Any `x`/`*` placeholder, at any position — `*`, `x`, `1.x`, `1.2.x`,
88
+ // `1.2.*`. Catching only some of them sent the rest to the no-comparator
89
+ // branch below, which suggested `>=1.2.*` — a hint this very parser rejects,
90
+ // which is worse than the value it was rejecting.
91
+ if (/(^|\.)[x*]$/i.test(term) || /(^|\.)[x*]\./i.test(term)) {
92
+ return refuse(`'${term}' is a wildcard range; write explicit comparators instead`);
93
+ }
94
+ if (term.startsWith("=")) {
95
+ return hintedRefusal(`'${term}' pins one exact version`, term.replace(/^=+/, ""));
96
+ }
97
+ const operator = OPERATORS.find((op) => term.startsWith(op));
98
+ if (!operator) {
99
+ return hintedRefusal(`'${term}' has no comparator. A bare version reads as an exact pin in semver, which is ` +
100
+ `almost never meant here`, term);
101
+ }
102
+ const version = term.slice(operator.length).trim();
103
+ const parsed = parseModuleVersion(version);
104
+ if (!parsed) {
105
+ return refuse(`'${version}' in '${term}' is not a three-part version (X.Y.Z, optionally -prerelease)`);
106
+ }
107
+ comparators.push({ operator, raw: version, parsed });
108
+ }
109
+ if (comparators.length === 0)
110
+ return refuse(`'${text}' declares no comparator`);
111
+ return { ok: true, range: { raw: text, comparators } };
112
+ }
113
+ /** True when `version` satisfies every comparator. An unparseable `version` is
114
+ * `false` — never a pass, since the caller is asking whether a real runtime is
115
+ * admitted and "cannot tell" must not read as "yes". */
116
+ export function rangeAccepts(range, version) {
117
+ const parsed = parseModuleVersion(version);
118
+ if (!parsed)
119
+ return false;
120
+ return range.comparators.every((c) => {
121
+ const cmp = compareParsedModuleVersions(parsed, c.parsed);
122
+ switch (c.operator) {
123
+ case ">=":
124
+ return cmp >= 0;
125
+ case ">":
126
+ return cmp > 0;
127
+ case "<=":
128
+ return cmp <= 0;
129
+ case "<":
130
+ return cmp < 0;
131
+ }
132
+ });
133
+ }
134
+ /** The lower-bound comparator, or `undefined` when the range is open below.
135
+ * This is the version verification installs and runs as the low edge. */
136
+ export function lowerBound(range) {
137
+ return highest(range.comparators.filter((c) => c.operator === ">=" || c.operator === ">"));
138
+ }
139
+ /** The upper-bound comparator, or `undefined` when the range is open above —
140
+ * the normal case, where the high edge is HEAD and normal CI already covers it. */
141
+ export function upperBound(range) {
142
+ return lowest(range.comparators.filter((c) => c.operator === "<=" || c.operator === "<"));
143
+ }
144
+ /** The tightest of several same-direction bounds wins; a range may legitimately
145
+ * state more than one, and only the binding bound is an edge worth testing. */
146
+ function highest(list) {
147
+ return list.reduce((best, c) => best === undefined || compareParsedModuleVersions(c.parsed, best.parsed) > 0 ? c : best, undefined);
148
+ }
149
+ function lowest(list) {
150
+ return list.reduce((best, c) => best === undefined || compareParsedModuleVersions(c.parsed, best.parsed) < 0 ? c : best, undefined);
151
+ }
152
+ /** True when no version can satisfy the range — `>=0.90.0 <0.80.0`. Reported
153
+ * rather than left to fail mysteriously at every consumer. */
154
+ export function isUnsatisfiable(range) {
155
+ const low = lowerBound(range);
156
+ const high = upperBound(range);
157
+ if (!low || !high)
158
+ return false;
159
+ const cmp = compareParsedModuleVersions(low.parsed, high.parsed);
160
+ if (cmp > 0)
161
+ return true;
162
+ // `>=X <X` and `>X <=X` admit nothing; `>=X <=X` admits exactly X.
163
+ if (cmp === 0)
164
+ return low.operator === ">" || high.operator === "<";
165
+ return false;
166
+ }
167
+ function describe(value) {
168
+ if (value === null)
169
+ return "null";
170
+ if (Array.isArray(value))
171
+ return "an array";
172
+ return typeof value;
173
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.61.0",
3
+ "version": "0.62.1",
4
4
  "description": "Telo Analyzer - Static manifest validator for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
@@ -49,7 +49,7 @@
49
49
  "@types/node": "^20.0.0",
50
50
  "typescript": "^5.0.0",
51
51
  "vitest": "^2.1.8",
52
- "@telorun/sdk": "0.75.0"
52
+ "@telorun/sdk": "0.77.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@telorun/sdk": "*"