@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.
- package/dist/analyzer.d.ts +9 -2
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +38 -10
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +16 -42
- package/dist/cel-environment.d.ts.map +1 -1
- package/dist/cel-environment.js +35 -0
- package/dist/flatten-for-analyzer.d.ts +4 -0
- package/dist/flatten-for-analyzer.d.ts.map +1 -1
- package/dist/flatten-for-analyzer.js +8 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/invocation-contract.d.ts +37 -0
- package/dist/invocation-contract.d.ts.map +1 -1
- package/dist/invocation-contract.js +81 -1
- package/dist/kernel-globals.d.ts +1 -1
- package/dist/kernel-globals.d.ts.map +1 -1
- package/dist/kernel-globals.js +15 -1
- package/dist/manifest-schemas.d.ts +407 -0
- package/dist/manifest-schemas.d.ts.map +1 -0
- package/dist/manifest-schemas.js +396 -0
- package/dist/module-file-claims.d.ts +6 -0
- package/dist/module-file-claims.d.ts.map +1 -1
- package/dist/module-file-claims.js +4 -0
- package/dist/module-metadata-scope.d.ts +44 -0
- package/dist/module-metadata-scope.d.ts.map +1 -0
- package/dist/module-metadata-scope.js +82 -0
- package/dist/parse-loaded-file.d.ts.map +1 -1
- package/dist/parse-loaded-file.js +16 -0
- package/dist/release/bump-level.d.ts +46 -0
- package/dist/release/bump-level.d.ts.map +1 -0
- package/dist/release/bump-level.js +80 -0
- package/dist/release/changelog.d.ts +36 -0
- package/dist/release/changelog.d.ts.map +1 -0
- package/dist/release/changelog.js +53 -0
- package/dist/release/fragment.d.ts +48 -0
- package/dist/release/fragment.d.ts.map +1 -0
- package/dist/release/fragment.js +84 -0
- package/dist/release/index.d.ts +26 -0
- package/dist/release/index.d.ts.map +1 -0
- package/dist/release/index.js +18 -0
- package/dist/release/ledger.d.ts +53 -0
- package/dist/release/ledger.d.ts.map +1 -0
- package/dist/release/ledger.js +109 -0
- package/dist/release/payload-digest.d.ts +63 -0
- package/dist/release/payload-digest.d.ts.map +1 -0
- package/dist/release/payload-digest.js +65 -0
- package/dist/release/release-plan.d.ts +128 -0
- package/dist/release/release-plan.d.ts.map +1 -0
- package/dist/release/release-plan.js +277 -0
- package/dist/release/version-stamp.d.ts +50 -0
- package/dist/release/version-stamp.d.ts.map +1 -0
- package/dist/release/version-stamp.js +119 -0
- package/dist/release/workspace-config.d.ts +41 -0
- package/dist/release/workspace-config.d.ts.map +1 -0
- package/dist/release/workspace-config.js +60 -0
- package/dist/schema-compat.d.ts +18 -0
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +71 -7
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +69 -13
- package/dist/validate-step-inputs.d.ts +1 -1
- package/dist/validate-step-inputs.d.ts.map +1 -1
- package/dist/validate-step-inputs.js +99 -8
- package/package.json +3 -3
- package/src/analyzer.ts +41 -10
- package/src/builtins.ts +16 -42
- package/src/cel-environment.ts +37 -0
- package/src/flatten-for-analyzer.ts +12 -0
- package/src/index.ts +18 -3
- package/src/invocation-contract.ts +92 -1
- package/src/kernel-globals.ts +23 -1
- package/src/manifest-schemas.ts +408 -0
- package/src/module-file-claims.ts +10 -0
- package/src/module-metadata-scope.ts +88 -0
- package/src/parse-loaded-file.ts +16 -0
- package/src/release/bump-level.ts +95 -0
- package/src/release/changelog.ts +62 -0
- package/src/release/fragment.ts +130 -0
- package/src/release/index.ts +66 -0
- package/src/release/ledger.ts +142 -0
- package/src/release/payload-digest.ts +83 -0
- package/src/release/release-plan.ts +392 -0
- package/src/release/version-stamp.ts +142 -0
- package/src/release/workspace-config.ts +81 -0
- package/src/schema-compat.ts +69 -5
- package/src/validate-cel-context.ts +82 -13
- 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
|
+
}
|
package/dist/schema-compat.d.ts
CHANGED
|
@@ -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":"AAkBA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAYpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;
|
|
1
|
+
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"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"}
|
package/dist/schema-compat.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import AjvModule from "ajv";
|
|
2
2
|
import addFormats from "ajv-formats";
|
|
3
|
-
import { isRefSentinel, isTaggedSentinel,
|
|
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
|
-
|
|
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;
|
|
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
|
-
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
|
|
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
|
|
180
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
207
|
+
if (collection.type === "array") {
|
|
208
|
+
return collection.items && typeof collection.items === "object"
|
|
209
|
+
? collection.items
|
|
210
|
+
: undefined;
|
|
185
211
|
}
|
|
186
|
-
|
|
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 =
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
52
|
+
"@telorun/sdk": "0.75.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@telorun/sdk": "*"
|