@telorun/analyzer 0.58.0 → 0.60.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 (89) hide show
  1. package/dist/analyzer.d.ts +9 -2
  2. package/dist/analyzer.d.ts.map +1 -1
  3. package/dist/analyzer.js +38 -10
  4. package/dist/builtins.d.ts.map +1 -1
  5. package/dist/builtins.js +16 -42
  6. package/dist/cel-environment.d.ts.map +1 -1
  7. package/dist/cel-environment.js +35 -0
  8. package/dist/flatten-for-analyzer.d.ts +4 -0
  9. package/dist/flatten-for-analyzer.d.ts.map +1 -1
  10. package/dist/flatten-for-analyzer.js +8 -0
  11. package/dist/index.d.ts +6 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +8 -2
  14. package/dist/invocation-contract.d.ts +37 -0
  15. package/dist/invocation-contract.d.ts.map +1 -1
  16. package/dist/invocation-contract.js +81 -1
  17. package/dist/kernel-globals.d.ts +1 -1
  18. package/dist/kernel-globals.d.ts.map +1 -1
  19. package/dist/kernel-globals.js +15 -1
  20. package/dist/manifest-schemas.d.ts +407 -0
  21. package/dist/manifest-schemas.d.ts.map +1 -0
  22. package/dist/manifest-schemas.js +396 -0
  23. package/dist/module-file-claims.d.ts +6 -0
  24. package/dist/module-file-claims.d.ts.map +1 -1
  25. package/dist/module-file-claims.js +4 -0
  26. package/dist/module-metadata-scope.d.ts +44 -0
  27. package/dist/module-metadata-scope.d.ts.map +1 -0
  28. package/dist/module-metadata-scope.js +82 -0
  29. package/dist/parse-loaded-file.d.ts.map +1 -1
  30. package/dist/parse-loaded-file.js +16 -0
  31. package/dist/release/bump-level.d.ts +46 -0
  32. package/dist/release/bump-level.d.ts.map +1 -0
  33. package/dist/release/bump-level.js +80 -0
  34. package/dist/release/changelog.d.ts +36 -0
  35. package/dist/release/changelog.d.ts.map +1 -0
  36. package/dist/release/changelog.js +53 -0
  37. package/dist/release/fragment.d.ts +48 -0
  38. package/dist/release/fragment.d.ts.map +1 -0
  39. package/dist/release/fragment.js +84 -0
  40. package/dist/release/index.d.ts +26 -0
  41. package/dist/release/index.d.ts.map +1 -0
  42. package/dist/release/index.js +18 -0
  43. package/dist/release/ledger.d.ts +53 -0
  44. package/dist/release/ledger.d.ts.map +1 -0
  45. package/dist/release/ledger.js +109 -0
  46. package/dist/release/payload-digest.d.ts +63 -0
  47. package/dist/release/payload-digest.d.ts.map +1 -0
  48. package/dist/release/payload-digest.js +65 -0
  49. package/dist/release/release-plan.d.ts +128 -0
  50. package/dist/release/release-plan.d.ts.map +1 -0
  51. package/dist/release/release-plan.js +277 -0
  52. package/dist/release/version-stamp.d.ts +50 -0
  53. package/dist/release/version-stamp.d.ts.map +1 -0
  54. package/dist/release/version-stamp.js +119 -0
  55. package/dist/release/workspace-config.d.ts +41 -0
  56. package/dist/release/workspace-config.d.ts.map +1 -0
  57. package/dist/release/workspace-config.js +60 -0
  58. package/dist/schema-compat.d.ts +18 -0
  59. package/dist/schema-compat.d.ts.map +1 -1
  60. package/dist/schema-compat.js +71 -7
  61. package/dist/validate-cel-context.d.ts.map +1 -1
  62. package/dist/validate-cel-context.js +69 -13
  63. package/dist/validate-step-inputs.d.ts +1 -1
  64. package/dist/validate-step-inputs.d.ts.map +1 -1
  65. package/dist/validate-step-inputs.js +99 -8
  66. package/package.json +3 -3
  67. package/src/analyzer.ts +41 -10
  68. package/src/builtins.ts +16 -42
  69. package/src/cel-environment.ts +37 -0
  70. package/src/flatten-for-analyzer.ts +12 -0
  71. package/src/index.ts +18 -3
  72. package/src/invocation-contract.ts +92 -1
  73. package/src/kernel-globals.ts +23 -1
  74. package/src/manifest-schemas.ts +408 -0
  75. package/src/module-file-claims.ts +10 -0
  76. package/src/module-metadata-scope.ts +88 -0
  77. package/src/parse-loaded-file.ts +16 -0
  78. package/src/release/bump-level.ts +95 -0
  79. package/src/release/changelog.ts +62 -0
  80. package/src/release/fragment.ts +130 -0
  81. package/src/release/index.ts +66 -0
  82. package/src/release/ledger.ts +142 -0
  83. package/src/release/payload-digest.ts +83 -0
  84. package/src/release/release-plan.ts +392 -0
  85. package/src/release/version-stamp.ts +142 -0
  86. package/src/release/workspace-config.ts +81 -0
  87. package/src/schema-compat.ts +69 -5
  88. package/src/validate-cel-context.ts +82 -13
  89. package/src/validate-step-inputs.ts +109 -9
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `telo-workspace.yaml` — the release anchor.
3
+ *
4
+ * Every path the release system names — a module key, a ledger entry, a
5
+ * fragment's `modules:` — is relative to this file's directory. That is the
6
+ * whole of its job: **its location is the anchor**, and its one field names the
7
+ * subtrees that may hold modules, which is not derivable (a whole-tree scan
8
+ * would read every example and every cached `.telo/manifests` copy as a released
9
+ * module).
10
+ *
11
+ * The file is **optional and read only by `telo release`**. Nothing else — not
12
+ * `run`, `check`, `publish`, `install`, `upgrade`, `migrate`, `module`, and not
13
+ * the kernel — looks for it, so a single-manifest repo, a bare `examples/`
14
+ * directory and a third-party module checkout keep working with nothing added.
15
+ * Any field added later must be true of the whole tree, not derivable from it,
16
+ * and harmless by its absence.
17
+ *
18
+ * Parsing lives here, in the browser-safe half, because the editor answers
19
+ * "what does changing this library bump?" from the same model. Finding the file
20
+ * on disk is the CLI's half — this side takes text.
21
+ */
22
+ export declare const WORKSPACE_FILENAME = "telo-workspace.yaml";
23
+ export interface WorkspaceConfig {
24
+ /**
25
+ * Gitignore-style patterns, workspace-relative, naming the subtrees that may
26
+ * hold modules (`modules/*`, `apps/*`). A pattern names a place to look, never
27
+ * a module: what makes a directory a module is its `telo.yaml`.
28
+ */
29
+ readonly modules: readonly string[];
30
+ }
31
+ export declare class WorkspaceConfigError extends Error {
32
+ }
33
+ /**
34
+ * Parse the marker file's text.
35
+ *
36
+ * Strict about its one field, because there is nothing here to be lenient with:
37
+ * an empty or absent `modules:` names no subtree, so discovery would find no
38
+ * module and every gate would silently pass over a whole repo.
39
+ */
40
+ export declare function parseWorkspaceConfig(text: string, where: string): WorkspaceConfig;
41
+ //# sourceMappingURL=workspace-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-config.d.ts","sourceRoot":"","sources":["../../src/release/workspace-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,eAAO,MAAM,kBAAkB,wBAAwB,CAAC;AAExD,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,qBAAa,oBAAqB,SAAQ,KAAK;CAAG;AAElD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,CAoCjF"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * `telo-workspace.yaml` — the release anchor.
3
+ *
4
+ * Every path the release system names — a module key, a ledger entry, a
5
+ * fragment's `modules:` — is relative to this file's directory. That is the
6
+ * whole of its job: **its location is the anchor**, and its one field names the
7
+ * subtrees that may hold modules, which is not derivable (a whole-tree scan
8
+ * would read every example and every cached `.telo/manifests` copy as a released
9
+ * module).
10
+ *
11
+ * The file is **optional and read only by `telo release`**. Nothing else — not
12
+ * `run`, `check`, `publish`, `install`, `upgrade`, `migrate`, `module`, and not
13
+ * the kernel — looks for it, so a single-manifest repo, a bare `examples/`
14
+ * directory and a third-party module checkout keep working with nothing added.
15
+ * Any field added later must be true of the whole tree, not derivable from it,
16
+ * and harmless by its absence.
17
+ *
18
+ * Parsing lives here, in the browser-safe half, because the editor answers
19
+ * "what does changing this library bump?" from the same model. Finding the file
20
+ * on disk is the CLI's half — this side takes text.
21
+ */
22
+ import { parseDocument } from "yaml";
23
+ export const WORKSPACE_FILENAME = "telo-workspace.yaml";
24
+ export class WorkspaceConfigError extends Error {
25
+ }
26
+ /**
27
+ * Parse the marker file's text.
28
+ *
29
+ * Strict about its one field, because there is nothing here to be lenient with:
30
+ * an empty or absent `modules:` names no subtree, so discovery would find no
31
+ * module and every gate would silently pass over a whole repo.
32
+ */
33
+ export function parseWorkspaceConfig(text, where) {
34
+ let value;
35
+ try {
36
+ value = parseDocument(text).toJSON();
37
+ }
38
+ catch (err) {
39
+ throw new WorkspaceConfigError(`${where} is not valid YAML: ${err instanceof Error ? err.message : String(err)}`);
40
+ }
41
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
42
+ throw new WorkspaceConfigError(`${where} must be a YAML mapping.`);
43
+ }
44
+ const record = value;
45
+ for (const key of Object.keys(record)) {
46
+ if (key !== "modules") {
47
+ throw new WorkspaceConfigError(`${where}: unknown field '${key}'. The workspace marker carries only 'modules:' — ` +
48
+ `the subtrees that may hold modules.`);
49
+ }
50
+ }
51
+ const modules = record.modules;
52
+ if (!Array.isArray(modules) || modules.some((entry) => typeof entry !== "string")) {
53
+ throw new WorkspaceConfigError(`${where}: 'modules' must be a list of path patterns, e.g. [modules/*, apps/*].`);
54
+ }
55
+ if (modules.length === 0) {
56
+ throw new WorkspaceConfigError(`${where}: 'modules' is empty, so no directory can ever be discovered as a module. ` +
57
+ `List the subtrees that hold them, e.g. [modules/*, apps/*].`);
58
+ }
59
+ return { modules: modules };
60
+ }
@@ -78,6 +78,24 @@ export declare function celTypeSatisfiesJsonSchema(celType: string, schema: Reco
78
78
  export declare function celPlaceholderForSchema(rawSchema: Record<string, any>): unknown;
79
79
  /** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
80
80
  export declare function resolveRef(schema: Record<string, any>, root: Record<string, any>): Record<string, any>;
81
+ /** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
82
+ /**
83
+ * The `oneOf` / `anyOf` branch a value is written against, when exactly one fits.
84
+ *
85
+ * A union carries no `type` / `properties` / `items` of its own, so a walker that
86
+ * ignores it descends with an empty schema and hands every CEL leaf underneath a
87
+ * `null` placeholder — which then fails every branch and reports a pile of
88
+ * violations against a value that is perfectly valid. Picking the branch first
89
+ * is what lets the leaves be typed.
90
+ *
91
+ * Selection is structural and conservative: a branch must agree with the data's
92
+ * kind, and for an object every `required` key must be present (which is what
93
+ * separates a `{type, text}` part from a `{type, data, mediaType}` one). If that
94
+ * leaves anything other than exactly one branch, the union is returned unchanged
95
+ * — an ambiguous union is one the analyzer should not resolve on the author's
96
+ * behalf.
97
+ */
98
+ export declare function selectUnionBranch(schema: Record<string, any>, data: unknown, root: Record<string, any>): Record<string, any>;
81
99
  export declare function collectProperties(schema: Record<string, any>): Record<string, any>;
82
100
  /** Deep-clone `data`, replacing every pure CEL template string (`${{ expr }}`) with a
83
101
  * schema-appropriate placeholder so AJV can validate non-CEL fields without false positives. */
@@ -1 +1 @@
1
- {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import AjvModule from "ajv";
2
2
  import addFormats from "ajv-formats";
3
- import { isRefSentinel, isTaggedSentinel, ManifestRootSchema, producedTypeOf, } from "@telorun/templating";
3
+ import { isRefSentinel, isTaggedSentinel, producedTypeOf, } from "@telorun/templating";
4
4
  import { celBaseOfValueType, celTypeOfValueType, readValueTypeSlot, valueBrandBases, valueTypeOf, valueTypePlaceholder, } from "@telorun/sdk";
5
+ import { ManifestRootSchema } from "./manifest-schemas.js";
5
6
  import { registerTeloKeywords } from "./value-type-keyword.js";
6
7
  const Ajv = AjvModule.default ?? AjvModule;
7
8
  /** Creates a configured AJV instance (allErrors, strict: false, with formats).
@@ -27,6 +28,16 @@ export function createAjv() {
27
28
  }
28
29
  const ajv = createAjv();
29
30
  const compiledSchemaValidators = new WeakMap();
31
+ /** The alternatives a union node declares, or undefined when it is not one.
32
+ * `anyOf` and `oneOf` are one question here — which branches could accept this
33
+ * value — and their difference (exactly-one vs at-least-one) is a validation
34
+ * rule, not a compatibility one. */
35
+ function unionBranches(schema) {
36
+ const branches = schema.anyOf ?? schema.oneOf;
37
+ if (!Array.isArray(branches) || branches.length === 0)
38
+ return undefined;
39
+ return branches.filter((b) => b && typeof b === "object");
40
+ }
30
41
  /**
31
42
  * Conservative structural JSON Schema compatibility check — is a value shaped
32
43
  * like `source` acceptable where `target` is declared?
@@ -80,6 +91,37 @@ function compare(rawSource, rawTarget, path, issues, resolveRef, seen) {
80
91
  }
81
92
  const source = deref(rawSource, resolveRef);
82
93
  const target = deref(rawTarget, resolveRef);
94
+ // A union is ALTERNATIVES, so it is compared by distributing over branches on
95
+ // both sides: a definite conflict is one where no source-branch/target-branch
96
+ // pair agrees. Returning silently the moment either side was a union — which
97
+ // is what this did — switched the whole comparison off for any slot that
98
+ // accepts more than one shape, and those are exactly the slots where a value
99
+ // type carries the only information distinguishing the branches.
100
+ //
101
+ // `allOf` is a conjunction rather than a choice, so it keeps the old posture:
102
+ // it says too little to judge and stays compatible.
103
+ if (source.allOf || target.allOf)
104
+ return;
105
+ const sourceBranches = unionBranches(source);
106
+ const targetBranches = unionBranches(target);
107
+ if (sourceBranches || targetBranches) {
108
+ const lefts = sourceBranches ?? [source];
109
+ const rights = targetBranches ?? [target];
110
+ const reasons = [];
111
+ for (const left of lefts) {
112
+ for (const right of rights) {
113
+ const probe = [];
114
+ // A fresh `seen` per probe: a pair rejected on one branch must not mark
115
+ // a reference pair visited for the next, which would silently pass it.
116
+ compare(left, right, path, probe, resolveRef, new Set(seen));
117
+ if (probe.length === 0)
118
+ return;
119
+ reasons.push(...probe);
120
+ }
121
+ }
122
+ issues.push(`${path || "/"}: no alternative matches — ${[...new Set(reasons)].join("; ")}`);
123
+ return;
124
+ }
83
125
  // Value types first: an `instance` representation has no JSON `type` to
84
126
  // compare, so its identity IS the comparison — and its arguments are where the
85
127
  // real information lives.
@@ -127,10 +169,6 @@ function compare(rawSource, rawTarget, path, issues, resolveRef, seen) {
127
169
  issues.push(`${path || "/"}: type mismatch — source is '${source.type}', target expects '${target.type}'`);
128
170
  return;
129
171
  }
130
- if (source.anyOf || source.oneOf || source.allOf)
131
- return;
132
- if (target.anyOf || target.oneOf || target.allOf)
133
- return;
134
172
  // An array's element, which the old comparison never looked at — so every
135
173
  // nested shape passed regardless of what it contained.
136
174
  if (target.items && source.items) {
@@ -370,8 +408,16 @@ export function celTypeSatisfiesJsonSchema(celType, schema) {
370
408
  }
371
409
  if (!schema.type && !schema.anyOf && !schema.oneOf && !schema.allOf)
372
410
  return true;
373
- if (schema.anyOf || schema.oneOf || schema.allOf)
411
+ // `allOf` is a conjunction and says too little to judge from a single CEL
412
+ // type. A union is a CHOICE, so it is satisfied by satisfying any branch —
413
+ // distributed for the same reason `compare` does it: accepting every union
414
+ // outright turns the check off for exactly the slots that admit more than one
415
+ // shape, and those are the ones where the branches carry the information.
416
+ if (schema.allOf)
374
417
  return true;
418
+ const branches = unionBranches(schema);
419
+ if (branches)
420
+ return branches.some((branch) => celTypeSatisfiesJsonSchema(celType, branch));
375
421
  const schemaTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
376
422
  const accepted = {
377
423
  int: ["integer", "number"],
@@ -482,6 +528,24 @@ export function celPlaceholderForSchema(rawSchema) {
482
528
  // the real value is checked at runtime once the expression resolves.
483
529
  if (Array.isArray(schema.enum) && schema.enum.length > 0)
484
530
  return schema.enum[0];
531
+ // A UNION with no `type` of its own. Without this, a whole-field CEL leaf at
532
+ // such a slot gets `null`, which every branch then rejects — so a field
533
+ // declared `anyOf: [array, boolean]` could not be written as an expression at
534
+ // all, while one whose union happens to contain a `live` branch escaped by
535
+ // accident (nothing validates a live value, so `null` passed). The first
536
+ // branch that yields a placeholder wins: the same conservative posture
537
+ // `selectUnionBranch` takes, and enough for AJV, whose question is only
538
+ // whether SOME branch accepts the stand-in.
539
+ if (schema.type === undefined) {
540
+ const branches = unionBranches(schema);
541
+ if (branches) {
542
+ for (const branch of branches) {
543
+ const candidate = celPlaceholderForSchema(branch);
544
+ if (candidate !== null)
545
+ return candidate;
546
+ }
547
+ }
548
+ }
485
549
  switch (schema.type) {
486
550
  case "integer":
487
551
  case "number":
@@ -553,7 +617,7 @@ export function resolveRef(schema, root) {
553
617
  * — an ambiguous union is one the analyzer should not resolve on the author's
554
618
  * behalf.
555
619
  */
556
- function selectUnionBranch(schema, data, root) {
620
+ export function selectUnionBranch(schema, data, root) {
557
621
  const branches = (schema.oneOf ?? schema.anyOf);
558
622
  if (!Array.isArray(branches) || branches.length === 0)
559
623
  return schema;
@@ -1 +1 @@
1
- {"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAItF,MAAM,WAAW,kBAAkB;IACjC;mEAC+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC;;kDAE8C;IAC9C,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;KACxD,CAAC;IACF,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;KAC/C,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CACtC;AA4CD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GACxC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA0EjC;AAuFD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,IAAI,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAChD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAiJrB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAQrB;AAWD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,SAAM,GACT,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAAC,CAGvD;AAUD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,SAAM,GAAG,MAAM,EAAE,CAqBxF"}
1
+ {"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAStF,MAAM,WAAW,kBAAkB;IACjC;mEAC+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC;;kDAE8C;IAC9C,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;KACxD,CAAC;IACF,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;KAC/C,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CACtC;AA4CD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GACxC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA0EjC;AAyID;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAoBzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,IAAI,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAChD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CA+JrB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAQrB;AAWD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,SAAM,GACT,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,CAAC,CAGvD;AAUD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,SAAM,GAAG,MAAM,EAAE,CAqBxF"}
@@ -1,5 +1,5 @@
1
1
  export { extractAccessChains, validateChainAgainstSchema } from "@telorun/templating";
2
- import { mergeTypeSchemas, parseCanonicalTypeSchemaId } from "@telorun/sdk";
2
+ import { elementSchemaOf, isLiveSlot, mergeTypeSchemas, parseCanonicalTypeSchemaId, } from "@telorun/sdk";
3
3
  import { KERNEL_BUILTINS } from "./builtins.js";
4
4
  /**
5
5
  * Resolve a type field value (string name, inline type, or raw schema) to a JSON Schema.
@@ -168,22 +168,64 @@ function schemaAtChain(chain, root) {
168
168
  }
169
169
  return cur && typeof cur === "object" ? cur : undefined;
170
170
  }
171
- /** The element schema of a sibling collection expression, when statically known.
172
- * Resolves `inputs.*` chains against the resource's `inputs:` contract and
173
- * returns the array's `items`. Returns undefined for non-chain or untyped
174
- * collections (caller substitutes `dyn`). */
175
- function resolveCollectionElementSchema(manifestRoot, field) {
171
+ /**
172
+ * The schema of a sibling collection expression, when statically known.
173
+ *
174
+ * Resolves `inputs.*` chains against the resource's DECLARED contract, falling
175
+ * back to the legacy `inputs:` property map some kinds still carry. Reading the
176
+ * contract is what makes this work at all for a kind that declares `inputType:`
177
+ * — resolving only the property map left `item` untyped in every such kind,
178
+ * silently, which reads as "element typing is permissive here" rather than as a
179
+ * gap. Returns undefined for a non-chain or untyped collection, and the caller
180
+ * substitutes `dyn` rather than inventing an element type.
181
+ */
182
+ function resolveCollectionSchema(manifestRoot, field, allManifests) {
176
183
  const chain = purePathChain(manifestRoot?.[field]);
177
184
  if (!chain || chain[0] !== "inputs")
178
185
  return undefined;
179
- const contract = manifestRoot.inputs;
180
- if (!contract || typeof contract !== "object")
186
+ const declared = resolveTypeFieldToSchema(manifestRoot.inputType, allManifests ?? []);
187
+ const root = declared && typeof declared === "object"
188
+ ? declared
189
+ : manifestRoot.inputs && typeof manifestRoot.inputs === "object"
190
+ ? { type: "object", properties: manifestRoot.inputs }
191
+ : undefined;
192
+ if (!root)
193
+ return undefined;
194
+ return schemaAtChain(chain.slice(1), root);
195
+ }
196
+ /**
197
+ * What ITERATING a collection schema yields.
198
+ *
199
+ * An array answers with `items`; anything else answers through the value-type
200
+ * vocabulary, which is where "what is the element of this" is declared. No type
201
+ * is named here on purpose: a future iterable value type is covered by declaring
202
+ * `element` on one of its parameters, with nothing to change in the analyzer.
203
+ */
204
+ function elementOfCollection(collection) {
205
+ if (!collection || typeof collection !== "object")
181
206
  return undefined;
182
- const terminal = schemaAtChain(chain.slice(1), { type: "object", properties: contract });
183
- if (terminal && terminal.type === "array" && terminal.items && typeof terminal.items === "object") {
184
- return terminal.items;
207
+ if (collection.type === "array") {
208
+ return collection.items && typeof collection.items === "object"
209
+ ? collection.items
210
+ : undefined;
185
211
  }
186
- return undefined;
212
+ const element = elementSchemaOf(collection);
213
+ return element && typeof element === "object" ? element : undefined;
214
+ }
215
+ /**
216
+ * True when a context binding naming this collection must NOT be bound.
217
+ *
218
+ * A binding that re-exposes the collection can only hand over the value the
219
+ * consumer is already draining, and passing that on is an ordinary
220
+ * pass-through no member-access rule catches — so the drain is silent. `live` is
221
+ * exactly the property that makes a value unsafe to re-expose, and it is already
222
+ * in the vocabulary, so no consumer names a type to decide this.
223
+ */
224
+ function collectionBindingWithheld(schema, manifestRoot, allManifests) {
225
+ const from = schema?.["x-telo-context-collection-from"];
226
+ if (!from)
227
+ return false;
228
+ return isLiveSlot(resolveCollectionSchema(manifestRoot, from, allManifests));
187
229
  }
188
230
  /**
189
231
  * Returns true when a CEL expression path (from walkCelExpressions, e.g. "routes[0].inputs.q")
@@ -297,9 +339,18 @@ export function resolveContextAnnotations(schema, manifestItem, opts) {
297
339
  // untyped sources fall back to `dyn` so a wrong element type is never invented.
298
340
  const elementFrom = schema["x-telo-context-element-from"];
299
341
  if (elementFrom) {
300
- const items = resolveCollectionElementSchema(manifestRoot, elementFrom);
342
+ const items = elementOfCollection(resolveCollectionSchema(manifestRoot, elementFrom, allManifests));
301
343
  return items ?? {};
302
344
  }
345
+ // The collection itself, as opposed to its element. Typed from whatever the
346
+ // sibling resolves to, so an array binding keeps the precision it has today
347
+ // instead of degrading to `dyn` now that the slot admits more than one shape.
348
+ // A `live` collection never reaches here — the property is dropped before
349
+ // recursion, so the name is simply not in scope.
350
+ const collectionFrom = schema["x-telo-context-collection-from"];
351
+ if (collectionFrom) {
352
+ return resolveCollectionSchema(manifestRoot, collectionFrom, allManifests) ?? {};
353
+ }
303
354
  const fromRoot = schema["x-telo-context-from-root"];
304
355
  const fromRefKindRaw = schema["x-telo-context-from-ref-kind"];
305
356
  const fromRefKinds = fromRefKindRaw == null
@@ -384,6 +435,11 @@ export function resolveContextAnnotations(schema, manifestItem, opts) {
384
435
  if (schema.properties) {
385
436
  const props = {};
386
437
  for (const [k, v] of Object.entries(schema.properties)) {
438
+ // Withholding happens HERE rather than inside the child resolver, because
439
+ // this is the only level that owns the property map — a child can return a
440
+ // schema but cannot remove itself from one.
441
+ if (collectionBindingWithheld(v, manifestRoot, allManifests))
442
+ continue;
387
443
  props[k] = resolveContextAnnotations(v, manifestItem, normalizedOpts);
388
444
  }
389
445
  return { ...schema, properties: props };
@@ -6,7 +6,7 @@ export interface StepInputIssue {
6
6
  message: string;
7
7
  /** Set when the issue is a type-argument disagreement rather than a contract
8
8
  * shape violation — the two read differently and deserve their own code. */
9
- code?: "CEL_TYPE_ARGUMENT_MISMATCH";
9
+ code?: "CEL_TYPE_ARGUMENT_MISMATCH" | "LIVE_VALUE_RETRIED";
10
10
  }
11
11
  /**
12
12
  * Validate every step's `inputs:` against the invoked target's declared input
@@ -1 +1 @@
1
- {"version":3,"file":"validate-step-inputs.d.ts","sourceRoot":"","sources":["../src/validate-step-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAoBnE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB;iFAC6E;IAC7E,IAAI,CAAC,EAAE,4BAA4B,CAAC;CACrC;AAGD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,YAAY;AACpB;;mEAEmE;AACnE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAChC,cAAc,EAAE,CAiHlB"}
1
+ {"version":3,"file":"validate-step-inputs.d.ts","sourceRoot":"","sources":["../src/validate-step-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAqBnE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB;iFAC6E;IAC7E,IAAI,CAAC,EAAE,4BAA4B,GAAG,oBAAoB,CAAC;CAC5D;AAGD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC9B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,YAAY;AACpB;;mEAEmE;AACnE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAChC,cAAc,EAAE,CAsJlB"}
@@ -1,7 +1,8 @@
1
1
  import { resolveContract } from "./invocation-contract.js";
2
2
  import { checkSchemaCompatibility, navigateSchemaToExprPath, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
3
3
  import { plainChainOf } from "@telorun/templating";
4
- import { valueTypeOf } from "@telorun/sdk";
4
+ import { isLiveSlot, valueTypeOf } from "@telorun/sdk";
5
+ import { manifestFragmentOf } from "./manifest-schemas.js";
5
6
  import { analyzerContractScope, containerOf, gatherPropertySchemas, missingRequired, resolveLocalRef, walkStepArray, } from "./analyzer.js";
6
7
  /**
7
8
  * Validate every step's `inputs:` against the invoked target's declared input
@@ -79,16 +80,27 @@ stepContext) {
79
80
  // that silence is exactly where a stream of the wrong element used to
80
81
  // flow. The comparison is covariant and gradual: an omitted argument is
81
82
  // *any* in both directions, so only a definite conflict is reported.
82
- if (stepContext) {
83
+ // The roots a plain chain may name here, each paired with the schema it is
84
+ // navigated against. `steps.` is the step map (analyzer state, supplied by
85
+ // the caller). `inputs.` is the ENCLOSING kind's own declared inputType,
86
+ // which is how a value produced OUTSIDE this resource reaches a step at
87
+ // all: an HTTP route maps `request.body` into its handler's inputs, and the
88
+ // handler forwards `inputs.body` onward — the shape a live value most often
89
+ // arrives in, and the one covering only `steps.` missed entirely. A root
90
+ // this cannot resolve contributes nothing rather than guessing at a schema.
91
+ const roots = [];
92
+ if (stepContext)
93
+ roots.push(["steps.", stepContext]);
94
+ const ownContract = resolveContract("inputType", manifest, contractScope.resolveIn(manifest.kind, readingModule), contractScope);
95
+ if (ownContract)
96
+ roots.push(["inputs.", ownContract.schema]);
97
+ if (roots.length > 0) {
83
98
  for (const [inputName, inputValue] of Object.entries(values)) {
84
99
  const chain = plainChainOf(inputValue);
85
- // The step context is rooted at the STEP MAP, so a `steps.` prefix is
86
- // the namespace name and not a property of it. Only that namespace is
87
- // navigated: `inputs.` and a named binding resolve elsewhere, and
88
- // guessing at a root this does not hold would compare the wrong schema.
89
- if (!chain?.startsWith("steps."))
100
+ const root = chain ? roots.find(([prefix]) => chain.startsWith(prefix)) : undefined;
101
+ if (!chain || !root)
90
102
  continue;
91
- const produced = navigateSchemaToExprPath(stepContext, chain.slice("steps.".length));
103
+ const produced = navigateSchemaToExprPath(root[1], chain.slice(root[0].length));
92
104
  const slotSchema = contract.schema.properties?.[inputName];
93
105
  if (!produced || !slotSchema)
94
106
  continue;
@@ -99,6 +111,27 @@ stepContext) {
99
111
  // broad new Error-severity check hidden behind an argument-specific
100
112
  // name. Both sides must declare a value type for the question to be
101
113
  // about arguments at all.
114
+ // A LIVE value is consumed by reading, so it exists exactly once —
115
+ // that is what `live` says in the vocabulary, and re-attempting a
116
+ // dispatch that already read it re-sends nothing. Reported here rather
117
+ // than through a slot-specific annotation because both facts are
118
+ // already declared: the value's liveness by its value type, and the
119
+ // re-attempt by the retry policy. No kind is named.
120
+ if (isLiveSlot(produced)) {
121
+ const retry = declaredRetry(step, stepItemSchema, invokedManifest, invokedDef);
122
+ if (retry !== undefined) {
123
+ out.push({
124
+ path: `${stepPath}.${inputsField}.${inputName}`,
125
+ targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
126
+ message: `'${inputName}' is a live value, which is consumed by reading and so exists ` +
127
+ `once — but ${retry} re-attempts the dispatch, and a re-attempt would pass ` +
128
+ `nothing. Collect it to a value first, or chunk the work so each attempt ` +
129
+ `carries its own replayable piece.`,
130
+ code: "LIVE_VALUE_RETRIED",
131
+ });
132
+ continue;
133
+ }
134
+ }
102
135
  if (!valueTypeOf(produced) || !valueTypeOf(slotSchema))
103
136
  continue;
104
137
  const { compatible, issues } = checkSchemaCompatibility(produced, slotSchema, (ref) => defs.schemaForId(ref));
@@ -130,3 +163,61 @@ stepContext) {
130
163
  }
131
164
  return out;
132
165
  }
166
+ /**
167
+ * Where a re-attempt is declared for this dispatch, described for a diagnostic,
168
+ * or undefined when none is.
169
+ *
170
+ * A field declares one when its schema was expanded from a shared retry fragment
171
+ * — the shape the author pointed at, rather than a marker they had to remember to
172
+ * write beside it. Which fragment also says WHERE the budget is, so the two
173
+ * spellings a kind may carry (a policy object, or the deprecated bare count) need
174
+ * no guessing between them and no rule about which one wins.
175
+ *
176
+ * Two sites are consulted because there are two real ones: the STEP's own policy
177
+ * — `retry` on the kernel-owned dispatch site — and the TARGET's, a field on an
178
+ * arbitrary kind, because `Http.Request` re-attempts inside its own `invoke()`
179
+ * where only it can tell a 429 from a 500. A live value is equally doomed by
180
+ * either. EVERY retry-bearing field at a site is checked, not the first, since
181
+ * `Http.Request` carries both spellings and property order must not decide which
182
+ * is seen.
183
+ *
184
+ * Only a STATICALLY KNOWN non-zero budget counts. An `attempts` written as CEL
185
+ * says nothing here, and guessing would report a conflict against a manifest that
186
+ * may never retry — the same posture the `use` case-map selector takes.
187
+ */
188
+ function declaredRetry(step, stepItemSchema, invokedManifest, invokedDef) {
189
+ for (const [field, budget] of retryFields(stepItemSchema)) {
190
+ if (budget(step?.[field]) > 0)
191
+ return `the step's \`${field}\``;
192
+ }
193
+ for (const [field, budget] of retryFields(invokedDef?.schema)) {
194
+ if (budget(invokedManifest?.[field]) > 0)
195
+ return `the target's \`${field}\``;
196
+ }
197
+ return undefined;
198
+ }
199
+ /** How each shared retry fragment carries its budget. Keyed on fragment name —
200
+ * the analyzer's own built-ins, never a module's kind — so a kind that adopts a
201
+ * shape is covered without naming it here. */
202
+ const RETRY_BUDGET = {
203
+ RetryPolicy: (value) => {
204
+ if (!value || typeof value !== "object")
205
+ return 0;
206
+ const attempts = value.attempts;
207
+ return typeof attempts === "number" ? attempts : 0;
208
+ },
209
+ RetryAttempts: (value) => (typeof value === "number" ? value : 0),
210
+ };
211
+ /** Every property of `schema` whose shape came from a retry fragment, paired with
212
+ * the reader for that fragment's budget. */
213
+ function retryFields(schema) {
214
+ if (!schema)
215
+ return [];
216
+ const out = [];
217
+ for (const [key, sub] of gatherPropertySchemas(schema)) {
218
+ const budget = RETRY_BUDGET[manifestFragmentOf(sub) ?? ""];
219
+ if (budget)
220
+ out.push([key, budget]);
221
+ }
222
+ return out;
223
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/analyzer",
3
- "version": "0.58.0",
3
+ "version": "0.60.0",
4
4
  "description": "Telo Analyzer - Static manifest validator for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
@@ -43,13 +43,13 @@
43
43
  "jsonpath-plus": "^10.3.0",
44
44
  "packageurl-js": "^2.0.1",
45
45
  "yaml": "^2.8.3",
46
- "@telorun/templating": "0.14.0"
46
+ "@telorun/templating": "0.16.0"
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.73.0"
52
+ "@telorun/sdk": "0.75.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@telorun/sdk": "*"