@telorun/analyzer 0.62.1 → 0.64.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/analysis-registry.d.ts +24 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +41 -0
- package/dist/analyzer.d.ts +3 -37
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +396 -476
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +2 -7
- package/dist/cel-scope-query.d.ts +109 -0
- package/dist/cel-scope-query.d.ts.map +1 -0
- package/dist/cel-scope-query.js +270 -0
- package/dist/cel-scope.d.ts +166 -0
- package/dist/cel-scope.d.ts.map +1 -0
- package/dist/cel-scope.js +377 -0
- package/dist/definition-registry.d.ts +15 -0
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +25 -9
- package/dist/find-manifest.d.ts +10 -0
- package/dist/find-manifest.d.ts.map +1 -0
- package/dist/find-manifest.js +12 -0
- package/dist/flatten-for-analyzer.js +1 -1
- package/dist/import-resolution-diagnostics.d.ts +9 -2
- package/dist/import-resolution-diagnostics.d.ts.map +1 -1
- package/dist/import-resolution-diagnostics.js +26 -7
- package/dist/index.d.ts +22 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -1
- package/dist/invocation-contract.d.ts +17 -1
- package/dist/invocation-contract.d.ts.map +1 -1
- package/dist/invocation-contract.js +42 -4
- package/dist/loaded-types.d.ts +12 -3
- package/dist/loaded-types.d.ts.map +1 -1
- package/dist/manifest-analysis.d.ts +73 -0
- package/dist/manifest-analysis.d.ts.map +1 -0
- package/dist/manifest-analysis.js +78 -0
- package/dist/manifest-loader.d.ts +0 -1
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +62 -41
- package/dist/manifest-path.d.ts +18 -0
- package/dist/manifest-path.d.ts.map +1 -0
- package/dist/manifest-path.js +37 -0
- package/dist/manifest-schemas.d.ts +699 -0
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +306 -14
- package/dist/module-compatibility.d.ts +40 -0
- package/dist/module-compatibility.d.ts.map +1 -0
- package/dist/module-compatibility.js +52 -0
- package/dist/ref-slot-reading.d.ts +30 -0
- package/dist/ref-slot-reading.d.ts.map +1 -0
- package/dist/ref-slot-reading.js +139 -0
- package/dist/referrer-rule.d.ts +67 -0
- package/dist/referrer-rule.d.ts.map +1 -0
- package/dist/referrer-rule.js +96 -0
- package/dist/release/index.d.ts +1 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +1 -1
- package/dist/release/version-stamp.d.ts +16 -0
- package/dist/release/version-stamp.d.ts.map +1 -1
- package/dist/release/version-stamp.js +52 -0
- package/dist/release/workspace-config.d.ts +10 -6
- package/dist/release/workspace-config.d.ts.map +1 -1
- package/dist/release/workspace-config.js +10 -6
- package/dist/resolve-schema-ref-kinds.d.ts +4 -3
- package/dist/resolve-schema-ref-kinds.d.ts.map +1 -1
- package/dist/resolve-schema-ref-kinds.js +16 -0
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +4 -3
- package/dist/resolve-zone-containment.d.ts +99 -0
- package/dist/resolve-zone-containment.d.ts.map +1 -0
- package/dist/resolve-zone-containment.js +212 -0
- package/dist/resolve-zone-requirements.d.ts +5 -0
- package/dist/resolve-zone-requirements.d.ts.map +1 -1
- package/dist/resolve-zone-requirements.js +48 -7
- package/dist/resource-rule.d.ts +106 -0
- package/dist/resource-rule.d.ts.map +1 -0
- package/dist/resource-rule.js +236 -0
- package/dist/rule-condition.d.ts +50 -0
- package/dist/rule-condition.d.ts.map +1 -0
- package/dist/rule-condition.js +108 -0
- package/dist/schema-keywords.d.ts.map +1 -1
- package/dist/schema-keywords.js +20 -0
- package/dist/schema-projection.d.ts +171 -0
- package/dist/schema-projection.d.ts.map +1 -0
- package/dist/schema-projection.js +317 -0
- package/dist/schema-walk.d.ts +25 -0
- package/dist/schema-walk.d.ts.map +1 -0
- package/dist/schema-walk.js +126 -0
- package/dist/step-slot.d.ts +39 -0
- package/dist/step-slot.d.ts.map +1 -0
- package/dist/step-slot.js +49 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +17 -3
- package/dist/validate-durable-regions.d.ts +15 -0
- package/dist/validate-durable-regions.d.ts.map +1 -0
- package/dist/validate-durable-regions.js +336 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +44 -2
- package/dist/validate-referrer-rules.d.ts +120 -0
- package/dist/validate-referrer-rules.d.ts.map +1 -0
- package/dist/validate-referrer-rules.js +255 -0
- package/dist/validate-resource-rules.d.ts +114 -0
- package/dist/validate-resource-rules.d.ts.map +1 -0
- package/dist/validate-resource-rules.js +283 -0
- package/dist/validate-schema-projection.d.ts +32 -0
- package/dist/validate-schema-projection.d.ts.map +1 -0
- package/dist/validate-schema-projection.js +110 -0
- package/dist/validate-step-inputs.d.ts +19 -1
- package/dist/validate-step-inputs.d.ts.map +1 -1
- package/dist/validate-step-inputs.js +102 -9
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +11 -5
- package/dist/validate-zone-slots.d.ts +2 -2
- package/dist/validate-zone-slots.d.ts.map +1 -1
- package/dist/validate-zone-slots.js +202 -6
- package/dist/validate-zone-violations.d.ts +12 -0
- package/dist/validate-zone-violations.d.ts.map +1 -0
- package/dist/validate-zone-violations.js +169 -0
- package/dist/value-type-keyword.d.ts +1 -1
- package/dist/value-type-keyword.d.ts.map +1 -1
- package/dist/value-type-keyword.js +4 -0
- package/dist/zone-slot.d.ts +61 -0
- package/dist/zone-slot.d.ts.map +1 -1
- package/dist/zone-slot.js +86 -5
- package/package.json +2 -2
- package/src/analysis-registry.ts +43 -0
- package/src/analyzer.ts +452 -595
- package/src/call-graph.ts +2 -8
- package/src/cel-scope-query.ts +337 -0
- package/src/cel-scope.ts +570 -0
- package/src/definition-registry.ts +31 -13
- package/src/find-manifest.ts +19 -0
- package/src/flatten-for-analyzer.ts +1 -1
- package/src/import-resolution-diagnostics.ts +25 -7
- package/src/index.ts +86 -0
- package/src/invocation-contract.ts +67 -2
- package/src/loaded-types.ts +12 -3
- package/src/manifest-analysis.ts +132 -0
- package/src/manifest-loader.ts +87 -55
- package/src/manifest-path.ts +34 -0
- package/src/manifest-schemas.ts +328 -14
- package/src/module-compatibility.ts +72 -0
- package/src/ref-slot-reading.ts +156 -0
- package/src/referrer-rule.ts +116 -0
- package/src/release/index.ts +1 -0
- package/src/release/version-stamp.ts +58 -0
- package/src/release/workspace-config.ts +10 -6
- package/src/resolve-schema-ref-kinds.ts +28 -4
- package/src/resolve-throws-union.ts +4 -3
- package/src/resolve-zone-containment.ts +329 -0
- package/src/resolve-zone-requirements.ts +67 -7
- package/src/resource-rule.ts +252 -0
- package/src/rule-condition.ts +126 -0
- package/src/schema-keywords.ts +24 -0
- package/src/schema-projection.ts +403 -0
- package/src/schema-walk.ts +144 -0
- package/src/step-slot.ts +64 -0
- package/src/telo-version.ts +1 -1
- package/src/validate-cel-context.ts +17 -3
- package/src/validate-durable-regions.ts +401 -0
- package/src/validate-references.ts +57 -2
- package/src/validate-referrer-rules.ts +373 -0
- package/src/validate-resource-rules.ts +401 -0
- package/src/validate-schema-projection.ts +184 -0
- package/src/validate-step-inputs.ts +148 -10
- package/src/validate-throws-coverage.ts +12 -5
- package/src/validate-zone-slots.ts +192 -8
- package/src/validate-zone-violations.ts +190 -0
- package/src/value-type-keyword.ts +4 -0
- package/src/zone-slot.ts +114 -5
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static validation of the schema-projection annotations themselves — the
|
|
3
|
+
* strict half of the accessor split, mirroring `validate-ref-slots.ts` and
|
|
4
|
+
* `validate-zone-slots.ts`.
|
|
5
|
+
*
|
|
6
|
+
* `readSchemaProjection` / `readSchemaMap` are deliberately lenient: anything
|
|
7
|
+
* they cannot read reads as absent. Without this pass that leniency is silent in
|
|
8
|
+
* the direction that matters — a projection nothing can read does not fail, it
|
|
9
|
+
* simply stops typing the consumers that were counting on it, so a misspelled
|
|
10
|
+
* column reaches the database instead of `telo check`. The whole point of the
|
|
11
|
+
* projection is to move that failure earlier; an unreported malformed
|
|
12
|
+
* declaration puts it back.
|
|
13
|
+
*
|
|
14
|
+
* A map that is merely INCOMPLETE is the same failure per value: a declared type
|
|
15
|
+
* with no entry projects to nothing, so a column of that type silently vanishes
|
|
16
|
+
* from every consumer's view of the row.
|
|
17
|
+
*
|
|
18
|
+
* Scoping follows `X_TELO_REF_UNRESOLVED`: reported only for definitions in the
|
|
19
|
+
* entry's own modules — a published dependency's annotation is not the
|
|
20
|
+
* consumer's to fix.
|
|
21
|
+
*
|
|
22
|
+
* Browser-safe: no Node built-ins.
|
|
23
|
+
*/
|
|
24
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
25
|
+
export interface SchemaProjectionIssue {
|
|
26
|
+
code: "SCHEMA_PROJECTION_INVALID" | "SCHEMA_MAP_INCOMPLETE";
|
|
27
|
+
manifest: ResourceManifest;
|
|
28
|
+
path: string;
|
|
29
|
+
message: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function validateSchemaProjection(manifest: ResourceManifest): SchemaProjectionIssue[];
|
|
32
|
+
//# sourceMappingURL=validate-schema-projection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-schema-projection.d.ts","sourceRoot":"","sources":["../src/validate-schema-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAQrD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,2BAA2B,GAAG,uBAAuB,CAAC;IAC5D,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAQD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,qBAAqB,EAAE,CA2G5F"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { projectionKeyMap, rawSchemaProjection, readSchemaProjection, schemaProjectionIsMisplaced, } from "./schema-projection.js";
|
|
2
|
+
const PROJECTION = "x-telo-schema-projection";
|
|
3
|
+
function isObject(value) {
|
|
4
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
export function validateSchemaProjection(manifest) {
|
|
7
|
+
const doc = manifest;
|
|
8
|
+
const raw = rawSchemaProjection(doc);
|
|
9
|
+
if (raw === undefined)
|
|
10
|
+
return [];
|
|
11
|
+
const misplaced = schemaProjectionIsMisplaced(doc);
|
|
12
|
+
const base = misplaced ? `schema.${PROJECTION}` : PROJECTION;
|
|
13
|
+
const issue = (code, path, message) => ({ code, manifest, path, message });
|
|
14
|
+
const issues = [];
|
|
15
|
+
// Read from both positions so a misplaced annotation still WORKS, and
|
|
16
|
+
// reported so it does not stay misplaced. The alternative — reading only the
|
|
17
|
+
// document — makes the inner spelling silently inert, and the resulting
|
|
18
|
+
// failure surfaces on a consumer's slot as "this kind declares no projection",
|
|
19
|
+
// which blames an author who wrote the annotation correctly enough to mean it.
|
|
20
|
+
if (misplaced) {
|
|
21
|
+
issues.push(issue("SCHEMA_PROJECTION_INVALID", base, `'${PROJECTION}' belongs on the kind DOCUMENT, beside 'schema:', not inside it — ` +
|
|
22
|
+
`it describes the whole declaration rather than one field. It is read from here ` +
|
|
23
|
+
`too, so nothing is broken; move it up a level.`));
|
|
24
|
+
}
|
|
25
|
+
if (!isObject(raw)) {
|
|
26
|
+
return [
|
|
27
|
+
...issues,
|
|
28
|
+
issue("SCHEMA_PROJECTION_INVALID", base, `'${PROJECTION}' must be an object naming the entry collection and the field that keys it.`),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
const missing = ["entries", "key"].filter((field) => typeof raw[field] !== "string");
|
|
32
|
+
if (missing.length > 0) {
|
|
33
|
+
return [
|
|
34
|
+
...issues,
|
|
35
|
+
issue("SCHEMA_PROJECTION_INVALID", base, `'${PROJECTION}' is missing ${missing.map((f) => `'${f}'`).join(" and ")}. ` +
|
|
36
|
+
`'entries' is a JSON Pointer to the collection, 'key' the entry field whose value ` +
|
|
37
|
+
`selects an 'x-telo-schema-map' entry.`),
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
const projection = readSchemaProjection(doc);
|
|
41
|
+
const schema = doc.schema;
|
|
42
|
+
// An ARRAY-shaped collection has no key to name its entries by, so without
|
|
43
|
+
// `name` every entry projects to nothing and the whole projection is an object
|
|
44
|
+
// schema with no properties and `additionalProperties: false` — one that
|
|
45
|
+
// rejects every value. A keyed map needs no `name`: the map key IS the
|
|
46
|
+
// identity.
|
|
47
|
+
if (collectionIsArray(schema, projection.entries) && !projection.nameField) {
|
|
48
|
+
return [
|
|
49
|
+
...issues,
|
|
50
|
+
issue("SCHEMA_PROJECTION_INVALID", base, `'${PROJECTION}' names '${projection.entries}', which is an array, but declares no ` +
|
|
51
|
+
`'name'. An array's entries have no key, so 'name' must say which field holds an ` +
|
|
52
|
+
`entry's identity — without it the projection yields a schema that rejects every value.`),
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
const map = projectionKeyMap(schema, projection);
|
|
56
|
+
if (!map) {
|
|
57
|
+
return [
|
|
58
|
+
...issues,
|
|
59
|
+
issue("SCHEMA_PROJECTION_INVALID", base, `'${PROJECTION}' names '${projection.entries}' keyed on '${projection.key}', but that ` +
|
|
60
|
+
`field carries no 'x-telo-schema-map'. The map declares the JSON Schema each of its ` +
|
|
61
|
+
`values means, and without it the projection types nothing.`),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
// An `enum` on the keyed field is the kind's own closed vocabulary, so every
|
|
65
|
+
// member of it is a value the map has to answer for. Only an enum is checked:
|
|
66
|
+
// an open string field has no set to be complete against.
|
|
67
|
+
const keyField = keySchema(schema, projection.entries, projection.key);
|
|
68
|
+
const values = Array.isArray(keyField?.enum) ? keyField.enum : [];
|
|
69
|
+
const unmapped = values.filter((value) => typeof value === "string" && !(value in map));
|
|
70
|
+
if (unmapped.length === 0)
|
|
71
|
+
return issues;
|
|
72
|
+
return [
|
|
73
|
+
...issues,
|
|
74
|
+
issue("SCHEMA_MAP_INCOMPLETE", base, `'x-telo-schema-map' has no entry for ${unmapped.map((v) => `'${v}'`).join(", ")}. ` +
|
|
75
|
+
`An entry declaring an unmapped value projects to nothing, so it disappears from every ` +
|
|
76
|
+
`consumer's view of the shape.`),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
function collectionIsArray(schema, entries) {
|
|
80
|
+
let node = schema;
|
|
81
|
+
for (const segment of entries.split("/")) {
|
|
82
|
+
if (segment === "")
|
|
83
|
+
continue;
|
|
84
|
+
if (!isObject(node) || !isObject(node.properties))
|
|
85
|
+
return false;
|
|
86
|
+
node = node.properties[segment];
|
|
87
|
+
}
|
|
88
|
+
return isObject(node) && isObject(node.items);
|
|
89
|
+
}
|
|
90
|
+
function keySchema(schema, entries, key) {
|
|
91
|
+
let node = schema;
|
|
92
|
+
for (const segment of entries.split("/")) {
|
|
93
|
+
if (segment === "")
|
|
94
|
+
continue;
|
|
95
|
+
if (!isObject(node) || !isObject(node.properties))
|
|
96
|
+
return undefined;
|
|
97
|
+
node = node.properties[segment];
|
|
98
|
+
}
|
|
99
|
+
if (!isObject(node))
|
|
100
|
+
return undefined;
|
|
101
|
+
const entry = isObject(node.additionalProperties)
|
|
102
|
+
? node.additionalProperties
|
|
103
|
+
: isObject(node.items)
|
|
104
|
+
? node.items
|
|
105
|
+
: undefined;
|
|
106
|
+
if (!isObject(entry) || !isObject(entry.properties))
|
|
107
|
+
return undefined;
|
|
108
|
+
const field = entry.properties[key];
|
|
109
|
+
return isObject(field) ? field : undefined;
|
|
110
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AliasResolver, ModuleScopes } from "./alias-resolver.js";
|
|
2
2
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
3
|
+
import { type ReferenceFieldMap } from "./reference-field-map.js";
|
|
3
4
|
export interface StepInputIssue {
|
|
4
5
|
path: string;
|
|
5
6
|
targetLabel: string;
|
|
@@ -20,7 +21,8 @@ export interface StepInputIssue {
|
|
|
20
21
|
* CEL leaves are replaced by schema-shaped placeholders first (`substituteCelFields`),
|
|
21
22
|
* so an expression is never a false positive — only structural disagreement is
|
|
22
23
|
* reported. Nothing is hardcoded about `Run.Sequence`: the invoke field comes
|
|
23
|
-
* from
|
|
24
|
+
* from the step slot (the shared grammar, or the legacy `x-telo-step-context`),
|
|
25
|
+
* and the paired inputs field from whichever sibling
|
|
24
26
|
* property carries `x-telo-topology-role: inputs`.
|
|
25
27
|
*/
|
|
26
28
|
export declare function collectStepInputIssues(manifest: Record<string, any>, defSchema: Record<string, any>, allManifests: Record<string, any>[], defs: DefinitionRegistry, aliases: AliasResolver, scopes: ModuleScopes,
|
|
@@ -28,4 +30,20 @@ export declare function collectStepInputIssues(manifest: Record<string, any>, de
|
|
|
28
30
|
* caller because building it is analyzer state; without it the contract check
|
|
29
31
|
* still runs and only the type-argument comparison is skipped. */
|
|
30
32
|
stepContext?: Record<string, any>): StepInputIssue[];
|
|
33
|
+
/**
|
|
34
|
+
* Validate the argument map of every call this resource makes through a
|
|
35
|
+
* REFERENCE SLOT, as opposed to a step.
|
|
36
|
+
*
|
|
37
|
+
* A slot that transfers control names its argument slot on its own `x-telo-ref`
|
|
38
|
+
* (`inputs:`, a JSON Pointer relative to the object enclosing the slot). That
|
|
39
|
+
* annotation is the only thing tying an otherwise-open `inputs:` map to the
|
|
40
|
+
* resource it holds arguments for — an HTTP route's `handler:` + `inputs:` pair
|
|
41
|
+
* is exactly this shape, and nothing about it is a step.
|
|
42
|
+
*
|
|
43
|
+
* Discovery is driven by the annotation rather than by any kind's topology, so
|
|
44
|
+
* a composer that names its argument slot gets its call sites checked without
|
|
45
|
+
* the analyzer learning what a route is. It is the same check the step driver
|
|
46
|
+
* runs, because it is the same question.
|
|
47
|
+
*/
|
|
48
|
+
export declare function collectRefInputIssues(manifest: Record<string, any>, fieldMap: ReferenceFieldMap | undefined, allManifests: Record<string, any>[], defs: DefinitionRegistry, aliases: AliasResolver, scopes: ModuleScopes): StepInputIssue[];
|
|
31
49
|
//# sourceMappingURL=validate-step-inputs.d.ts.map
|
|
@@ -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;AAsBnE,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAElC,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;;;;;;;;;;;;;;;GAeG;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,CA4DlB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,QAAQ,EAAE,iBAAiB,GAAG,SAAS,EACvC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EACnC,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,YAAY,GACnB,cAAc,EAAE,CAoClB"}
|
|
@@ -4,6 +4,9 @@ import { plainChainOf } from "@telorun/templating";
|
|
|
4
4
|
import { isLiveSlot, valueTypeOf } from "@telorun/sdk";
|
|
5
5
|
import { manifestFragmentOf } from "./manifest-schemas.js";
|
|
6
6
|
import { analyzerContractScope, containerOf, gatherPropertySchemas, missingRequired, resolveLocalRef, walkStepArray, } from "./analyzer.js";
|
|
7
|
+
import { readStepSlot } from "./step-slot.js";
|
|
8
|
+
import { navigateConcretePath } from "./manifest-path.js";
|
|
9
|
+
import { isRefEntry, resolveFieldEntries, } from "./reference-field-map.js";
|
|
7
10
|
/**
|
|
8
11
|
* Validate every step's `inputs:` against the invoked target's declared input
|
|
9
12
|
* contract — the static half of what the kernel enforces at dispatch.
|
|
@@ -16,7 +19,8 @@ import { analyzerContractScope, containerOf, gatherPropertySchemas, missingRequi
|
|
|
16
19
|
* CEL leaves are replaced by schema-shaped placeholders first (`substituteCelFields`),
|
|
17
20
|
* so an expression is never a false positive — only structural disagreement is
|
|
18
21
|
* reported. Nothing is hardcoded about `Run.Sequence`: the invoke field comes
|
|
19
|
-
* from
|
|
22
|
+
* from the step slot (the shared grammar, or the legacy `x-telo-step-context`),
|
|
23
|
+
* and the paired inputs field from whichever sibling
|
|
20
24
|
* property carries `x-telo-topology-role: inputs`.
|
|
21
25
|
*/
|
|
22
26
|
export function collectStepInputIssues(manifest, defSchema, allManifests, defs, aliases, scopes,
|
|
@@ -30,9 +34,17 @@ stepContext) {
|
|
|
30
34
|
return out;
|
|
31
35
|
const contractScope = analyzerContractScope(defs, aliases, scopes, allManifests);
|
|
32
36
|
const readingModule = manifest.metadata?.module;
|
|
37
|
+
const ctx = {
|
|
38
|
+
manifest,
|
|
39
|
+
allManifests,
|
|
40
|
+
defs,
|
|
41
|
+
contractScope,
|
|
42
|
+
readingModule,
|
|
43
|
+
stepContext,
|
|
44
|
+
};
|
|
33
45
|
for (const [fieldName, fieldSchema] of Object.entries(props)) {
|
|
34
|
-
const stepCtx = fieldSchema
|
|
35
|
-
if (!stepCtx
|
|
46
|
+
const stepCtx = readStepSlot(fieldSchema);
|
|
47
|
+
if (!stepCtx)
|
|
36
48
|
continue;
|
|
37
49
|
const steps = manifest[fieldName];
|
|
38
50
|
if (!Array.isArray(steps))
|
|
@@ -56,6 +68,87 @@ stepContext) {
|
|
|
56
68
|
return;
|
|
57
69
|
if (!values || typeof values !== "object" || Array.isArray(values))
|
|
58
70
|
return;
|
|
71
|
+
out.push(...checkCallSite({
|
|
72
|
+
inputsPath: `${stepPath}.${inputsField}`,
|
|
73
|
+
values: values,
|
|
74
|
+
invoke,
|
|
75
|
+
// Only a step declares a re-attempt policy, so only a step can carry
|
|
76
|
+
// the live-value-retried finding.
|
|
77
|
+
declaredRetryFor: (invokedManifest, invokedDef) => declaredRetry(step, stepItemSchema, invokedManifest, invokedDef),
|
|
78
|
+
}, ctx));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Validate the argument map of every call this resource makes through a
|
|
85
|
+
* REFERENCE SLOT, as opposed to a step.
|
|
86
|
+
*
|
|
87
|
+
* A slot that transfers control names its argument slot on its own `x-telo-ref`
|
|
88
|
+
* (`inputs:`, a JSON Pointer relative to the object enclosing the slot). That
|
|
89
|
+
* annotation is the only thing tying an otherwise-open `inputs:` map to the
|
|
90
|
+
* resource it holds arguments for — an HTTP route's `handler:` + `inputs:` pair
|
|
91
|
+
* is exactly this shape, and nothing about it is a step.
|
|
92
|
+
*
|
|
93
|
+
* Discovery is driven by the annotation rather than by any kind's topology, so
|
|
94
|
+
* a composer that names its argument slot gets its call sites checked without
|
|
95
|
+
* the analyzer learning what a route is. It is the same check the step driver
|
|
96
|
+
* runs, because it is the same question.
|
|
97
|
+
*/
|
|
98
|
+
export function collectRefInputIssues(manifest, fieldMap, allManifests, defs, aliases, scopes) {
|
|
99
|
+
const out = [];
|
|
100
|
+
if (!fieldMap)
|
|
101
|
+
return out;
|
|
102
|
+
const contractScope = analyzerContractScope(defs, aliases, scopes, allManifests);
|
|
103
|
+
const ctx = {
|
|
104
|
+
manifest,
|
|
105
|
+
allManifests,
|
|
106
|
+
defs,
|
|
107
|
+
contractScope,
|
|
108
|
+
readingModule: manifest.metadata?.module,
|
|
109
|
+
};
|
|
110
|
+
for (const [fieldPath, entry] of fieldMap) {
|
|
111
|
+
if (!isRefEntry(entry) || !entry.inputs)
|
|
112
|
+
continue;
|
|
113
|
+
const pointer = pointerSegments(entry.inputs);
|
|
114
|
+
if (!pointer)
|
|
115
|
+
continue;
|
|
116
|
+
for (const { value: invoke, path: slotPath } of resolveFieldEntries(manifest, fieldPath)) {
|
|
117
|
+
if (!invoke || typeof invoke !== "object" || Array.isArray(invoke))
|
|
118
|
+
continue;
|
|
119
|
+
// Relative to the object ENCLOSING the slot, which is the annotation's
|
|
120
|
+
// documented anchor.
|
|
121
|
+
const enclosing = slotPath.slice(0, Math.max(0, slotPath.lastIndexOf(".")));
|
|
122
|
+
const inputsPath = [enclosing, ...pointer].filter(Boolean).join(".");
|
|
123
|
+
const values = navigateConcretePath(manifest, inputsPath);
|
|
124
|
+
if (!values || typeof values !== "object" || Array.isArray(values))
|
|
125
|
+
continue;
|
|
126
|
+
out.push(...checkCallSite({ inputsPath, values: values, invoke: invoke }, ctx));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
/** A JSON Pointer naming a sibling FIELD path. An array index is not a field,
|
|
132
|
+
* so a pointer carrying one names nothing this can resolve. */
|
|
133
|
+
function pointerSegments(pointer) {
|
|
134
|
+
if (!pointer.startsWith("/"))
|
|
135
|
+
return undefined;
|
|
136
|
+
const segments = pointer
|
|
137
|
+
.slice(1)
|
|
138
|
+
.split("/")
|
|
139
|
+
.map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
140
|
+
return segments.every((s) => s.length > 0 && !/^\d+$/.test(s)) ? segments : undefined;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The check itself, shared by both drivers: the arguments written at a call site
|
|
144
|
+
* against the contract the target declares.
|
|
145
|
+
*/
|
|
146
|
+
function checkCallSite(site, ctx) {
|
|
147
|
+
const out = [];
|
|
148
|
+
const { manifest, allManifests, defs, contractScope, readingModule, stepContext } = ctx;
|
|
149
|
+
const { invoke, values } = site;
|
|
150
|
+
{
|
|
151
|
+
{
|
|
59
152
|
const invokedKind = invoke.kind;
|
|
60
153
|
const invokedName = invoke.name;
|
|
61
154
|
const invokedManifest = invokedName
|
|
@@ -66,7 +159,7 @@ stepContext) {
|
|
|
66
159
|
: undefined;
|
|
67
160
|
const contract = resolveContract("inputType", invokedManifest, invokedDef, contractScope);
|
|
68
161
|
if (!contract)
|
|
69
|
-
return;
|
|
162
|
+
return out;
|
|
70
163
|
// Findings AT a substituted path are about a placeholder, not about
|
|
71
164
|
// anything the author wrote — a `pattern`-constrained string or a `oneOf`
|
|
72
165
|
// of unrelated shapes cannot be satisfied by any stand-in. Structural
|
|
@@ -118,10 +211,10 @@ stepContext) {
|
|
|
118
211
|
// already declared: the value's liveness by its value type, and the
|
|
119
212
|
// re-attempt by the retry policy. No kind is named.
|
|
120
213
|
if (isLiveSlot(produced)) {
|
|
121
|
-
const retry =
|
|
214
|
+
const retry = site.declaredRetryFor?.(invokedManifest, invokedDef);
|
|
122
215
|
if (retry !== undefined) {
|
|
123
216
|
out.push({
|
|
124
|
-
path: `${
|
|
217
|
+
path: `${site.inputsPath}.${inputName}`,
|
|
125
218
|
targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
|
|
126
219
|
message: `'${inputName}' is a live value, which is consumed by reading and so exists ` +
|
|
127
220
|
`once — but ${retry} re-attempts the dispatch, and a re-attempt would pass ` +
|
|
@@ -138,7 +231,7 @@ stepContext) {
|
|
|
138
231
|
if (compatible)
|
|
139
232
|
continue;
|
|
140
233
|
out.push({
|
|
141
|
-
path: `${
|
|
234
|
+
path: `${site.inputsPath}.${inputName}`,
|
|
142
235
|
targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
|
|
143
236
|
message: issues.join("; "),
|
|
144
237
|
code: "CEL_TYPE_ARGUMENT_MISMATCH",
|
|
@@ -154,12 +247,12 @@ stepContext) {
|
|
|
154
247
|
// container that should have held it, which does exist.
|
|
155
248
|
const anchor = missingRequired(issue) ? containerOf(issue.path) : issue.path;
|
|
156
249
|
out.push({
|
|
157
|
-
path: anchor ? `${
|
|
250
|
+
path: anchor ? `${site.inputsPath}.${anchor}` : site.inputsPath,
|
|
158
251
|
targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
|
|
159
252
|
message: issue.message,
|
|
160
253
|
});
|
|
161
254
|
}
|
|
162
|
-
}
|
|
255
|
+
}
|
|
163
256
|
}
|
|
164
257
|
return out;
|
|
165
258
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-throws-coverage.d.ts","sourceRoot":"","sources":["../src/validate-throws-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAOnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAsfzE,oDAAoD;AACpD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,IAAI,EAAE,kBAAkB,EACxB,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,WAAW,EAChB,eAAe,GAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAa,EACvD,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,GACnC,kBAAkB,EAAE,CAkDtB"}
|
|
@@ -4,6 +4,7 @@ import { scopeResolverForModule } from "./alias-resolver.js";
|
|
|
4
4
|
import { createResolveCtx, resolveThrowsUnion, } from "./resolve-throws-union.js";
|
|
5
5
|
import { DiagnosticSeverity } from "./types.js";
|
|
6
6
|
import { extractAccessChains, validateChainAgainstSchema } from "./validate-cel-context.js";
|
|
7
|
+
import { isStepSlot } from "./step-slot.js";
|
|
7
8
|
const SOURCE = "telo-analyzer";
|
|
8
9
|
const TEMPLATE_REGEX = /\$\{\{\s*([^}]+?)\s*\}\}/g;
|
|
9
10
|
/** Walk `definition.schema` and `data` in tandem, invoking `onOutcome` each
|
|
@@ -366,9 +367,11 @@ function checkCelChainAgainstDataSchema(entry, dataSchema, resource, filePath, e
|
|
|
366
367
|
return diagnostics;
|
|
367
368
|
}
|
|
368
369
|
/** Rule 8 extension: `inherit: true` only makes sense on a definition whose
|
|
369
|
-
* schema
|
|
370
|
-
*
|
|
371
|
-
*
|
|
370
|
+
* schema declares at least one STEP BODY — an array whose items point at the
|
|
371
|
+
* shared grammar, or, for a module published before that fragment existed, one
|
|
372
|
+
* carrying the legacy `x-telo-step-context` annotation. That is what drives the
|
|
373
|
+
* resolver's generic step traversal; a definition with `inherit: true` and no
|
|
374
|
+
* such array has no invocables to inherit from. */
|
|
372
375
|
function validateThrowsDeclarations(manifests) {
|
|
373
376
|
const diagnostics = [];
|
|
374
377
|
for (const m of manifests) {
|
|
@@ -386,7 +389,10 @@ function validateThrowsDeclarations(manifests) {
|
|
|
386
389
|
severity: DiagnosticSeverity.Error,
|
|
387
390
|
code: "INHERIT_WITHOUT_STEP_CONTEXT",
|
|
388
391
|
source: SOURCE,
|
|
389
|
-
message: `Telo.Definition '${name}' declares throws.inherit: true but its schema
|
|
392
|
+
message: `Telo.Definition '${name}' declares throws.inherit: true but its schema declares no step ` +
|
|
393
|
+
`body. inherit is only meaningful on a definition that drives invocables through steps — ` +
|
|
394
|
+
`give an array field 'items: { $ref: "telo://manifest#/$defs/Step" }' (the legacy ` +
|
|
395
|
+
`x-telo-step-context annotation is also recognised).`,
|
|
390
396
|
data: { resource: { kind: m.kind, name }, filePath, path: "throws.inherit" },
|
|
391
397
|
});
|
|
392
398
|
}
|
|
@@ -397,7 +403,7 @@ function validateThrowsDeclarations(manifests) {
|
|
|
397
403
|
function schemaHasStepContext(schema) {
|
|
398
404
|
if (!schema || typeof schema !== "object")
|
|
399
405
|
return false;
|
|
400
|
-
if (
|
|
406
|
+
if (isStepSlot(schema))
|
|
401
407
|
return true;
|
|
402
408
|
const props = schema.properties;
|
|
403
409
|
if (props && typeof props === "object") {
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
*
|
|
27
27
|
* Browser-safe: no Node built-ins.
|
|
28
28
|
*/
|
|
29
|
-
import type
|
|
29
|
+
import { type ResourceManifest } from "@telorun/sdk";
|
|
30
30
|
export interface ZoneSlotIssue {
|
|
31
|
-
code: "ZONE_ANNOTATION_INVALID";
|
|
31
|
+
code: "ZONE_ANNOTATION_INVALID" | "ZONE_ATTRIBUTE_UNKNOWN" | "ZONE_ATTRIBUTE_INCOMPLETE";
|
|
32
32
|
manifest: ResourceManifest;
|
|
33
33
|
/** Schema path of the annotated slot. */
|
|
34
34
|
path: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-zone-slots.d.ts","sourceRoot":"","sources":["../src/validate-zone-slots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"validate-zone-slots.d.ts","sourceRoot":"","sources":["../src/validate-zone-slots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAG1F,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,yBAAyB,GAAG,wBAAwB,GAAG,2BAA2B,CAAC;IACzF,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AA0TD,iFAAiF;AACjF,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,gBAAgB,GAAG,aAAa,EAAE,CAM1F"}
|