@telorun/analyzer 0.57.0 → 0.59.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.map +1 -1
- package/dist/analyzer.js +91 -9
- package/dist/cel-environment.d.ts.map +1 -1
- package/dist/cel-environment.js +6 -3
- package/dist/definition-registry.d.ts +5 -0
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +9 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/invocation-contract.d.ts +58 -13
- package/dist/invocation-contract.d.ts.map +1 -1
- package/dist/invocation-contract.js +123 -46
- package/dist/loaded-types.d.ts +13 -0
- package/dist/loaded-types.d.ts.map +1 -1
- package/dist/manifest-loader.d.ts +8 -1
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +33 -6
- package/dist/migrations/driver.d.ts +69 -0
- package/dist/migrations/driver.d.ts.map +1 -0
- package/dist/migrations/driver.js +200 -0
- package/dist/migrations/entries/index.d.ts +3 -0
- package/dist/migrations/entries/index.d.ts.map +1 -0
- package/dist/migrations/entries/index.js +9 -0
- package/dist/migrations/entries/normalize-value-types.json +48 -0
- package/dist/migrations/entries/ref-slot-scalar-type.json +20 -0
- package/dist/migrations/entry-data.d.ts +25 -0
- package/dist/migrations/entry-data.d.ts.map +1 -0
- package/dist/migrations/entry-data.js +171 -0
- package/dist/migrations/index.d.ts +22 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +19 -0
- package/dist/migrations/match.d.ts +162 -0
- package/dist/migrations/match.d.ts.map +1 -0
- package/dist/migrations/match.js +295 -0
- package/dist/migrations/patch.d.ts +68 -0
- package/dist/migrations/patch.d.ts.map +1 -0
- package/dist/migrations/patch.js +236 -0
- package/dist/migrations/provenance.d.ts +25 -0
- package/dist/migrations/provenance.d.ts.map +1 -0
- package/dist/migrations/provenance.js +118 -0
- package/dist/migrations/registry.d.ts +35 -0
- package/dist/migrations/registry.d.ts.map +1 -0
- package/dist/migrations/registry.js +35 -0
- package/dist/migrations/report.d.ts +28 -0
- package/dist/migrations/report.d.ts.map +1 -0
- package/dist/migrations/report.js +144 -0
- package/dist/migrations/types.d.ts +115 -0
- package/dist/migrations/types.d.ts.map +1 -0
- package/dist/migrations/types.js +24 -0
- package/dist/migrations/yaml-edit.d.ts +21 -0
- package/dist/migrations/yaml-edit.d.ts.map +1 -0
- package/dist/migrations/yaml-edit.js +245 -0
- package/dist/parse-loaded-file.d.ts +8 -0
- package/dist/parse-loaded-file.d.ts.map +1 -1
- package/dist/parse-loaded-file.js +22 -13
- package/dist/resolve-schema-type-refs.d.ts +29 -12
- package/dist/resolve-schema-type-refs.d.ts.map +1 -1
- package/dist/resolve-schema-type-refs.js +89 -16
- package/dist/schema-compat.d.ts +53 -9
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +245 -66
- package/dist/schema-region.d.ts +33 -0
- package/dist/schema-region.d.ts.map +1 -0
- package/dist/schema-region.js +45 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +97 -15
- package/dist/validate-step-inputs.d.ts +8 -1
- package/dist/validate-step-inputs.d.ts.map +1 -1
- package/dist/validate-step-inputs.js +47 -2
- package/dist/validate-value-type-slots.d.ts +43 -0
- package/dist/validate-value-type-slots.d.ts.map +1 -0
- package/dist/validate-value-type-slots.js +142 -0
- package/dist/value-type-keyword.d.ts +60 -0
- package/dist/value-type-keyword.d.ts.map +1 -0
- package/dist/value-type-keyword.js +119 -0
- package/dist/yaml-source-edit.d.ts +56 -0
- package/dist/yaml-source-edit.d.ts.map +1 -0
- package/dist/yaml-source-edit.js +93 -0
- package/package.json +3 -3
- package/src/analyzer.ts +99 -6
- package/src/cel-environment.ts +6 -3
- package/src/definition-registry.ts +10 -0
- package/src/index.ts +34 -3
- package/src/invocation-contract.ts +134 -48
- package/src/loaded-types.ts +13 -0
- package/src/manifest-loader.ts +37 -6
- package/src/migrations/driver.ts +262 -0
- package/src/migrations/entries/index.ts +10 -0
- package/src/migrations/entries/normalize-value-types.json +48 -0
- package/src/migrations/entries/ref-slot-scalar-type.json +20 -0
- package/src/migrations/entry-data.ts +207 -0
- package/src/migrations/index.ts +22 -0
- package/src/migrations/match.ts +382 -0
- package/src/migrations/patch.ts +265 -0
- package/src/migrations/provenance.ts +167 -0
- package/src/migrations/registry.ts +40 -0
- package/src/migrations/report.ts +169 -0
- package/src/migrations/types.ts +144 -0
- package/src/migrations/yaml-edit.ts +260 -0
- package/src/parse-loaded-file.ts +31 -12
- package/src/resolve-schema-type-refs.ts +93 -16
- package/src/schema-compat.ts +277 -75
- package/src/schema-region.ts +46 -0
- package/src/types.ts +17 -0
- package/src/validate-cel-context.ts +117 -15
- package/src/validate-step-inputs.ts +56 -1
- package/src/validate-value-type-slots.ts +182 -0
- package/src/value-type-keyword.ts +135 -0
- package/src/yaml-source-edit.ts +106 -0
- package/dist/binary-slot.d.ts +0 -39
- package/dist/binary-slot.d.ts.map +0 -1
- package/dist/binary-slot.js +0 -58
- package/src/binary-slot.ts +0 -71
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where author-written JSON Schema lives in a manifest — one definition, read by
|
|
3
|
+
* every surface that has to bound itself to schema.
|
|
4
|
+
*
|
|
5
|
+
* The keys are the KERNEL's own schema-valued manifest keys, which no resource
|
|
6
|
+
* kind owns. That is what makes the rule generic: a surface using it learns no
|
|
7
|
+
* resource kind, and a module that invents a schema-bearing field of its own
|
|
8
|
+
* reaches it through one of these or not at all — against the topology-driven
|
|
9
|
+
* constraint, an enumeration of the standard library's kinds would be both
|
|
10
|
+
* incomplete and knowledge the analyzer must not hold.
|
|
11
|
+
*
|
|
12
|
+
* A schema fragment is NOT confined to kind documents. An inline `inputType:` /
|
|
13
|
+
* `outputType:` sits on any kind that declares one, an API route carries
|
|
14
|
+
* `request.schema.body`, a `Telo.JsonSchema` carries `schema`. So a check that
|
|
15
|
+
* walks a manifest's ROOT keys covers a fraction of the sites an author writes —
|
|
16
|
+
* which is a silent hole in exactly the checks that exist to stop a silent
|
|
17
|
+
* degrade. Containment is by ANCESTRY instead: a node is in a schema region when
|
|
18
|
+
* some key on the path to it is one of these.
|
|
19
|
+
*
|
|
20
|
+
* Browser-safe: no Node built-ins.
|
|
21
|
+
*/
|
|
22
|
+
/** The kernel's schema-valued manifest keys. */
|
|
23
|
+
export declare const SCHEMA_REGION_KEYS: readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* True when `path` reaches into a schema region — some ANCESTOR segment is a
|
|
26
|
+
* schema-valued key.
|
|
27
|
+
*
|
|
28
|
+
* Ancestors only, so a rule keyed on a region key itself still means "inside a
|
|
29
|
+
* schema" rather than "is one". `path` is the walk's own segment list; numeric
|
|
30
|
+
* segments (array indices) never equal a key name, so they need no special case.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isInSchemaRegion(path: readonly (string | number)[]): boolean;
|
|
33
|
+
//# sourceMappingURL=schema-region.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-region.d.ts","sourceRoot":"","sources":["../src/schema-region.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,gDAAgD;AAChD,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAM/C,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,OAAO,CAM5E"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where author-written JSON Schema lives in a manifest — one definition, read by
|
|
3
|
+
* every surface that has to bound itself to schema.
|
|
4
|
+
*
|
|
5
|
+
* The keys are the KERNEL's own schema-valued manifest keys, which no resource
|
|
6
|
+
* kind owns. That is what makes the rule generic: a surface using it learns no
|
|
7
|
+
* resource kind, and a module that invents a schema-bearing field of its own
|
|
8
|
+
* reaches it through one of these or not at all — against the topology-driven
|
|
9
|
+
* constraint, an enumeration of the standard library's kinds would be both
|
|
10
|
+
* incomplete and knowledge the analyzer must not hold.
|
|
11
|
+
*
|
|
12
|
+
* A schema fragment is NOT confined to kind documents. An inline `inputType:` /
|
|
13
|
+
* `outputType:` sits on any kind that declares one, an API route carries
|
|
14
|
+
* `request.schema.body`, a `Telo.JsonSchema` carries `schema`. So a check that
|
|
15
|
+
* walks a manifest's ROOT keys covers a fraction of the sites an author writes —
|
|
16
|
+
* which is a silent hole in exactly the checks that exist to stop a silent
|
|
17
|
+
* degrade. Containment is by ANCESTRY instead: a node is in a schema region when
|
|
18
|
+
* some key on the path to it is one of these.
|
|
19
|
+
*
|
|
20
|
+
* Browser-safe: no Node built-ins.
|
|
21
|
+
*/
|
|
22
|
+
/** The kernel's schema-valued manifest keys. */
|
|
23
|
+
export const SCHEMA_REGION_KEYS = [
|
|
24
|
+
"schema",
|
|
25
|
+
"status",
|
|
26
|
+
"inputType",
|
|
27
|
+
"outputType",
|
|
28
|
+
"itemType",
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* True when `path` reaches into a schema region — some ANCESTOR segment is a
|
|
32
|
+
* schema-valued key.
|
|
33
|
+
*
|
|
34
|
+
* Ancestors only, so a rule keyed on a region key itself still means "inside a
|
|
35
|
+
* schema" rather than "is one". `path` is the walk's own segment list; numeric
|
|
36
|
+
* segments (array indices) never equal a key name, so they need no special case.
|
|
37
|
+
*/
|
|
38
|
+
export function isInSchemaRegion(path) {
|
|
39
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
40
|
+
const segment = path[i];
|
|
41
|
+
if (typeof segment === "string" && SCHEMA_REGION_KEYS.includes(segment))
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -101,11 +101,28 @@ export interface LoadOptions {
|
|
|
101
101
|
* pairs manifests to YAML nodes by index. Folded into the file cache key so a
|
|
102
102
|
* desugared and a raw load of the same file never collide. */
|
|
103
103
|
desugarImports?: boolean;
|
|
104
|
+
/** When true, the loader's migration phase rewrites legacy spellings in each
|
|
105
|
+
* parsed document before anything else reads the tree. On for every resolved
|
|
106
|
+
* consumer — the kernel's analysis and runtime loads, `telo check`, the
|
|
107
|
+
* analyzer — so one rewrite serves the definition registry, the runtime and
|
|
108
|
+
* the editor's analysis alike.
|
|
109
|
+
*
|
|
110
|
+
* **Off for a round-trip view.** The editor pairs manifests to YAML nodes by
|
|
111
|
+
* index and writes the pair back on save; migrating one half of that pair
|
|
112
|
+
* would silently change the author's file. `telo migrate` is likewise a raw
|
|
113
|
+
* consumer — it rewrites the YAML itself and must see the legacy spelling to
|
|
114
|
+
* find it. Folded into the file cache key so a migrated and a raw load of
|
|
115
|
+
* the same file never collide. */
|
|
116
|
+
migrate?: boolean;
|
|
104
117
|
}
|
|
105
118
|
export interface LoaderInitOptions {
|
|
106
119
|
/** Handlers for CEL stdlib functions (e.g. `sha256`). Analyzer-only callers may
|
|
107
120
|
* omit this and get throwing stubs; runtime callers (kernel) must supply real impls. */
|
|
108
121
|
celHandlers?: import("./cel-environment.js").CelHandlers;
|
|
122
|
+
/** Migration set for `LoadOptions.migrate` loads. Defaults to the analyzer's
|
|
123
|
+
* own `CORE_MIGRATIONS`. A host supplies its own once module-shipped entries
|
|
124
|
+
* are aggregated alongside the core ones. */
|
|
125
|
+
migrations?: readonly import("./migrations/types.js").MigrationEntry[];
|
|
109
126
|
}
|
|
110
127
|
export interface AnalysisOptions {
|
|
111
128
|
strictContexts?: boolean;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE;qHACqH;AACrH,eAAO,MAAM,kBAAkB;;;;;CAKrB,CAAC;AACX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC;AAE9F,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD,MAAM,WAAW,QAAQ;IACvB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,QAAQ,CAAC;IAChB,GAAG,EAAE,QAAQ,CAAC;CACf;AAED;;oDAEoD;AACpD,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE/C;6EAC6E;AAC7E;;;;;;;;;;;;;;kEAckE;AAClE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;kEACkE;AAClE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,2BAA2B;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;8BAC8B;AAC9B,wBAAgB,aAAa,CAAC,CAAC,EAAE,kBAAkB,GAAG,aAAa,GAAG,SAAS,CAG9E;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAExD;;qEAEiE;IACjE,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEjE;;qEAEiE;IACjE,cAAc,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B;;;+EAG2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;mEAO+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -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 } 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.
|
|
@@ -63,6 +63,31 @@ export function resolveTypeFieldToSchema(value, allManifests, ancestry = new Set
|
|
|
63
63
|
}
|
|
64
64
|
if (typeof value === "object" && value !== null) {
|
|
65
65
|
const obj = value;
|
|
66
|
+
// A canonical, module-scoped id — what `resolveSchemaTypeRefs` normalizes
|
|
67
|
+
// both the reference tag and the legacy `telo://` authority form into.
|
|
68
|
+
//
|
|
69
|
+
// This is the ALIAS-AWARE path, and it is why the canonical form exists:
|
|
70
|
+
// the id names the OWNING MODULE, so two libraries declaring a shape of the
|
|
71
|
+
// same name stay distinct. The bare-name lookup below cannot do that — it
|
|
72
|
+
// matches the first manifest with that `metadata.name` in a flattened list,
|
|
73
|
+
// which is how an alias got silently dropped.
|
|
74
|
+
const canonical = parseCanonicalTypeSchemaId(obj.$ref);
|
|
75
|
+
if (canonical) {
|
|
76
|
+
if (ancestry.has(obj.$ref))
|
|
77
|
+
return undefined;
|
|
78
|
+
const owned = allManifests.find((m) => m.metadata?.name === canonical.typeName &&
|
|
79
|
+
m.metadata?.module === canonical.moduleName &&
|
|
80
|
+
isTypeKind(m.kind, allManifests) &&
|
|
81
|
+
typeof m.schema === "object" &&
|
|
82
|
+
m.schema !== null);
|
|
83
|
+
if (owned) {
|
|
84
|
+
return applyExtends(owned.schema, owned.extends, allManifests, new Set(ancestry).add(obj.$ref));
|
|
85
|
+
}
|
|
86
|
+
// A module that is not in this set — a single-file analysis, or an import
|
|
87
|
+
// whose library was not forwarded. Fall through to the bare name, which is
|
|
88
|
+
// what this resolved before the canonical form existed.
|
|
89
|
+
return resolveTypeFieldToSchema(canonical.typeName, allManifests, ancestry);
|
|
90
|
+
}
|
|
66
91
|
// Inline type resource: { kind: "Type.JsonSchema", schema: {...} }
|
|
67
92
|
if (obj.schema && typeof obj.schema === "object") {
|
|
68
93
|
return applyExtends(obj.schema, obj.extends, allManifests, ancestry);
|
|
@@ -71,8 +96,9 @@ export function resolveTypeFieldToSchema(value, allManifests, ancestry = new Set
|
|
|
71
96
|
if (obj.type || obj.properties) {
|
|
72
97
|
return obj;
|
|
73
98
|
}
|
|
74
|
-
// Named type reference resolved from a `!ref` → { kind, name }
|
|
75
|
-
//
|
|
99
|
+
// Named type reference resolved from a `!ref` → { kind, name }. Reached only
|
|
100
|
+
// where the canonical rewrite could not run (a scope whose aliases are not in
|
|
101
|
+
// hand); resolves by bare name, as it always has.
|
|
76
102
|
if (typeof obj.name === "string") {
|
|
77
103
|
return resolveTypeFieldToSchema(obj.name, allManifests, ancestry);
|
|
78
104
|
}
|
|
@@ -142,22 +168,64 @@ function schemaAtChain(chain, root) {
|
|
|
142
168
|
}
|
|
143
169
|
return cur && typeof cur === "object" ? cur : undefined;
|
|
144
170
|
}
|
|
145
|
-
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
|
|
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) {
|
|
150
183
|
const chain = purePathChain(manifestRoot?.[field]);
|
|
151
184
|
if (!chain || chain[0] !== "inputs")
|
|
152
185
|
return undefined;
|
|
153
|
-
const
|
|
154
|
-
|
|
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)
|
|
155
193
|
return undefined;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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")
|
|
206
|
+
return undefined;
|
|
207
|
+
if (collection.type === "array") {
|
|
208
|
+
return collection.items && typeof collection.items === "object"
|
|
209
|
+
? collection.items
|
|
210
|
+
: undefined;
|
|
159
211
|
}
|
|
160
|
-
|
|
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));
|
|
161
229
|
}
|
|
162
230
|
/**
|
|
163
231
|
* Returns true when a CEL expression path (from walkCelExpressions, e.g. "routes[0].inputs.q")
|
|
@@ -271,9 +339,18 @@ export function resolveContextAnnotations(schema, manifestItem, opts) {
|
|
|
271
339
|
// untyped sources fall back to `dyn` so a wrong element type is never invented.
|
|
272
340
|
const elementFrom = schema["x-telo-context-element-from"];
|
|
273
341
|
if (elementFrom) {
|
|
274
|
-
const items =
|
|
342
|
+
const items = elementOfCollection(resolveCollectionSchema(manifestRoot, elementFrom, allManifests));
|
|
275
343
|
return items ?? {};
|
|
276
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
|
+
}
|
|
277
354
|
const fromRoot = schema["x-telo-context-from-root"];
|
|
278
355
|
const fromRefKindRaw = schema["x-telo-context-from-ref-kind"];
|
|
279
356
|
const fromRefKinds = fromRefKindRaw == null
|
|
@@ -358,6 +435,11 @@ export function resolveContextAnnotations(schema, manifestItem, opts) {
|
|
|
358
435
|
if (schema.properties) {
|
|
359
436
|
const props = {};
|
|
360
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;
|
|
361
443
|
props[k] = resolveContextAnnotations(v, manifestItem, normalizedOpts);
|
|
362
444
|
}
|
|
363
445
|
return { ...schema, properties: props };
|
|
@@ -4,6 +4,9 @@ export interface StepInputIssue {
|
|
|
4
4
|
path: string;
|
|
5
5
|
targetLabel: string;
|
|
6
6
|
message: string;
|
|
7
|
+
/** Set when the issue is a type-argument disagreement rather than a contract
|
|
8
|
+
* shape violation — the two read differently and deserve their own code. */
|
|
9
|
+
code?: "CEL_TYPE_ARGUMENT_MISMATCH";
|
|
7
10
|
}
|
|
8
11
|
/**
|
|
9
12
|
* Validate every step's `inputs:` against the invoked target's declared input
|
|
@@ -20,5 +23,9 @@ export interface StepInputIssue {
|
|
|
20
23
|
* from `x-telo-step-context`, and the paired inputs field from whichever sibling
|
|
21
24
|
* property carries `x-telo-topology-role: inputs`.
|
|
22
25
|
*/
|
|
23
|
-
export declare function collectStepInputIssues(manifest: Record<string, any>, defSchema: Record<string, any>, allManifests: Record<string, any>[], defs: DefinitionRegistry, aliases: AliasResolver, scopes: ModuleScopes
|
|
26
|
+
export declare function collectStepInputIssues(manifest: Record<string, any>, defSchema: Record<string, any>, allManifests: Record<string, any>[], defs: DefinitionRegistry, aliases: AliasResolver, scopes: ModuleScopes,
|
|
27
|
+
/** The typed `steps.<name>.result` context for this resource. Supplied by the
|
|
28
|
+
* caller because building it is analyzer state; without it the contract check
|
|
29
|
+
* still runs and only the type-argument comparison is skipped. */
|
|
30
|
+
stepContext?: Record<string, any>): StepInputIssue[];
|
|
24
31
|
//# 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;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,5 +1,7 @@
|
|
|
1
1
|
import { resolveContract } from "./invocation-contract.js";
|
|
2
|
-
import { substituteCelFields, validateAgainstSchema } from "./schema-compat.js";
|
|
2
|
+
import { checkSchemaCompatibility, navigateSchemaToExprPath, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
|
|
3
|
+
import { plainChainOf } from "@telorun/templating";
|
|
4
|
+
import { valueTypeOf } from "@telorun/sdk";
|
|
3
5
|
import { analyzerContractScope, containerOf, gatherPropertySchemas, missingRequired, resolveLocalRef, walkStepArray, } from "./analyzer.js";
|
|
4
6
|
/**
|
|
5
7
|
* Validate every step's `inputs:` against the invoked target's declared input
|
|
@@ -16,7 +18,11 @@ import { analyzerContractScope, containerOf, gatherPropertySchemas, missingRequi
|
|
|
16
18
|
* from `x-telo-step-context`, and the paired inputs field from whichever sibling
|
|
17
19
|
* property carries `x-telo-topology-role: inputs`.
|
|
18
20
|
*/
|
|
19
|
-
export function collectStepInputIssues(manifest, defSchema, allManifests, defs, aliases, scopes
|
|
21
|
+
export function collectStepInputIssues(manifest, defSchema, allManifests, defs, aliases, scopes,
|
|
22
|
+
/** The typed `steps.<name>.result` context for this resource. Supplied by the
|
|
23
|
+
* caller because building it is analyzer state; without it the contract check
|
|
24
|
+
* still runs and only the type-argument comparison is skipped. */
|
|
25
|
+
stepContext) {
|
|
20
26
|
const out = [];
|
|
21
27
|
const props = defSchema.properties;
|
|
22
28
|
if (!props)
|
|
@@ -67,6 +73,45 @@ export function collectStepInputIssues(manifest, defSchema, allManifests, defs,
|
|
|
67
73
|
// container and survive the filter.
|
|
68
74
|
const celPaths = new Set();
|
|
69
75
|
const substituted = substituteCelFields(values, contract.schema, undefined, (p) => celPaths.add(p));
|
|
76
|
+
// The type-argument check, at the one site where a produced value's schema
|
|
77
|
+
// meets a consuming slot's. A CEL leaf's placeholder says nothing about
|
|
78
|
+
// what the expression yields, so AJV above is silent here by design — and
|
|
79
|
+
// that silence is exactly where a stream of the wrong element used to
|
|
80
|
+
// flow. The comparison is covariant and gradual: an omitted argument is
|
|
81
|
+
// *any* in both directions, so only a definite conflict is reported.
|
|
82
|
+
if (stepContext) {
|
|
83
|
+
for (const [inputName, inputValue] of Object.entries(values)) {
|
|
84
|
+
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."))
|
|
90
|
+
continue;
|
|
91
|
+
const produced = navigateSchemaToExprPath(stepContext, chain.slice("steps.".length));
|
|
92
|
+
const slotSchema = contract.schema.properties?.[inputName];
|
|
93
|
+
if (!produced || !slotSchema)
|
|
94
|
+
continue;
|
|
95
|
+
// ONLY a type-argument disagreement, which is what the code says. The
|
|
96
|
+
// comparator is a general structural comparison, so running it on any
|
|
97
|
+
// pair would report a missing required property as "disagreeing type
|
|
98
|
+
// arguments" — and would turn every plain-chain wiring site into a
|
|
99
|
+
// broad new Error-severity check hidden behind an argument-specific
|
|
100
|
+
// name. Both sides must declare a value type for the question to be
|
|
101
|
+
// about arguments at all.
|
|
102
|
+
if (!valueTypeOf(produced) || !valueTypeOf(slotSchema))
|
|
103
|
+
continue;
|
|
104
|
+
const { compatible, issues } = checkSchemaCompatibility(produced, slotSchema, (ref) => defs.schemaForId(ref));
|
|
105
|
+
if (compatible)
|
|
106
|
+
continue;
|
|
107
|
+
out.push({
|
|
108
|
+
path: `${stepPath}.${inputsField}.${inputName}`,
|
|
109
|
+
targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
|
|
110
|
+
message: issues.join("; "),
|
|
111
|
+
code: "CEL_TYPE_ARGUMENT_MISMATCH",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
70
115
|
for (const issue of validateAgainstSchema(substituted, contract.schema)) {
|
|
71
116
|
if (celPaths.has(issue.path))
|
|
72
117
|
continue;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static validation of the `x-telo-type` annotation itself — the strict half of
|
|
3
|
+
* the accessor split, and the `validate-ref-slots.ts` precedent.
|
|
4
|
+
*
|
|
5
|
+
* `readValueTypeSlot` is deliberately lenient: it reports whatever it can read,
|
|
6
|
+
* returning a slot with no `entry` for a name it does not know. That leniency is
|
|
7
|
+
* what lets every surface keep working; this pass is what stops it becoming
|
|
8
|
+
* silence. Before the unification an unrecognized brand simply resolved to
|
|
9
|
+
* `undefined` and the slot quietly lost its identity — the same class of failure
|
|
10
|
+
* `X_TELO_REF_INVALID_USE` exists to prevent for `use` tokens.
|
|
11
|
+
*
|
|
12
|
+
* The vocabulary is CLOSED and `Telo.`-qualified, so there is nothing here to
|
|
13
|
+
* resolve against an alias scope: a name is a built-in or it is a mistake. A
|
|
14
|
+
* SHAPE is a different thing entirely and is named with the reference tag, which
|
|
15
|
+
* carries its own resolution and its own diagnostics — this pass never sees one,
|
|
16
|
+
* because `resolveSchemaTypeRefs` has already turned it into a `$ref`.
|
|
17
|
+
*
|
|
18
|
+
* Scoping follows `X_TELO_REF_UNRESOLVED`: reported only for manifests in the
|
|
19
|
+
* entry's own modules, since a published dependency is not the consumer's to fix.
|
|
20
|
+
*
|
|
21
|
+
* Browser-safe: no Node built-ins.
|
|
22
|
+
*/
|
|
23
|
+
import { type ResourceManifest } from "@telorun/sdk";
|
|
24
|
+
export interface ValueTypeSlotIssue {
|
|
25
|
+
code: "X_TELO_TYPE_UNKNOWN" | "X_TELO_TYPE_ARGUMENT_UNKNOWN";
|
|
26
|
+
manifest: ResourceManifest;
|
|
27
|
+
/** Dotted path to the annotated schema node, e.g. `schema.properties.body`. */
|
|
28
|
+
path: string;
|
|
29
|
+
message: string;
|
|
30
|
+
/** The whole-value replacement that repairs it, when one is derivable. A
|
|
31
|
+
* misspelled name has a single correct spelling and the annotation's value is
|
|
32
|
+
* that name, so the repair is the primitive `DiagnosticFix` already carries —
|
|
33
|
+
* computing a suggestion and printing it in prose alone leaves the author to
|
|
34
|
+
* retype what the analyzer already knows. Only for the bare-name spelling: the
|
|
35
|
+
* object form's name is nested, and a whole-value replacement there would
|
|
36
|
+
* discard the type arguments beside it. */
|
|
37
|
+
fix?: {
|
|
38
|
+
replacement: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Every `x-telo-type` problem in one manifest, wherever a schema is written. */
|
|
42
|
+
export declare function validateValueTypeSlots(manifest: ResourceManifest): ValueTypeSlotIssue[];
|
|
43
|
+
//# sourceMappingURL=validate-value-type-slots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-value-type-slots.d.ts","sourceRoot":"","sources":["../src/validate-value-type-slots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAA+C,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIlG,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,qBAAqB,GAAG,8BAA8B,CAAC;IAC7D,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;gDAM4C;IAC5C,GAAG,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/B;AAwID,iFAAiF;AACjF,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,kBAAkB,EAAE,CAIvF"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static validation of the `x-telo-type` annotation itself — the strict half of
|
|
3
|
+
* the accessor split, and the `validate-ref-slots.ts` precedent.
|
|
4
|
+
*
|
|
5
|
+
* `readValueTypeSlot` is deliberately lenient: it reports whatever it can read,
|
|
6
|
+
* returning a slot with no `entry` for a name it does not know. That leniency is
|
|
7
|
+
* what lets every surface keep working; this pass is what stops it becoming
|
|
8
|
+
* silence. Before the unification an unrecognized brand simply resolved to
|
|
9
|
+
* `undefined` and the slot quietly lost its identity — the same class of failure
|
|
10
|
+
* `X_TELO_REF_INVALID_USE` exists to prevent for `use` tokens.
|
|
11
|
+
*
|
|
12
|
+
* The vocabulary is CLOSED and `Telo.`-qualified, so there is nothing here to
|
|
13
|
+
* resolve against an alias scope: a name is a built-in or it is a mistake. A
|
|
14
|
+
* SHAPE is a different thing entirely and is named with the reference tag, which
|
|
15
|
+
* carries its own resolution and its own diagnostics — this pass never sees one,
|
|
16
|
+
* because `resolveSchemaTypeRefs` has already turned it into a `$ref`.
|
|
17
|
+
*
|
|
18
|
+
* Scoping follows `X_TELO_REF_UNRESOLVED`: reported only for manifests in the
|
|
19
|
+
* entry's own modules, since a published dependency is not the consumer's to fix.
|
|
20
|
+
*
|
|
21
|
+
* Browser-safe: no Node built-ins.
|
|
22
|
+
*/
|
|
23
|
+
import { readValueTypeSlot, VALUE_TYPES, X_TELO_TYPE } from "@telorun/sdk";
|
|
24
|
+
import { distance } from "./levenshtein.js";
|
|
25
|
+
import { isInSchemaRegion } from "./schema-region.js";
|
|
26
|
+
/** Schema regions are reached by ANCESTRY, not by root key — see
|
|
27
|
+
* `schema-region.ts`. Walking a manifest's root fields covers only a fraction of
|
|
28
|
+
* the sites an author writes a schema at: an API route's `request.schema.body`
|
|
29
|
+
* sits under `routes`, and a check that never reaches it is a hole in exactly
|
|
30
|
+
* the diagnostic that exists to stop an unknown name degrading silently. */
|
|
31
|
+
/** The closest declared type name within an edit-distance threshold, or
|
|
32
|
+
* undefined. Mirrors `computeSuggestKind`: case-sensitive, and silent on a tie,
|
|
33
|
+
* because a coin-flip suggestion is worse than none. */
|
|
34
|
+
function suggestValueType(name) {
|
|
35
|
+
if (!name)
|
|
36
|
+
return undefined;
|
|
37
|
+
const threshold = Math.min(3, Math.floor(name.length / 3));
|
|
38
|
+
if (threshold < 1)
|
|
39
|
+
return undefined;
|
|
40
|
+
let best;
|
|
41
|
+
let bestDist = threshold + 1;
|
|
42
|
+
let tied = false;
|
|
43
|
+
for (const candidate of VALUE_TYPES.keys()) {
|
|
44
|
+
const d = distance(name, candidate);
|
|
45
|
+
if (d < bestDist) {
|
|
46
|
+
best = candidate;
|
|
47
|
+
bestDist = d;
|
|
48
|
+
tied = false;
|
|
49
|
+
}
|
|
50
|
+
else if (d === bestDist) {
|
|
51
|
+
tied = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return !best || bestDist > threshold || tied ? undefined : best;
|
|
55
|
+
}
|
|
56
|
+
function declaredNames() {
|
|
57
|
+
return [...VALUE_TYPES.keys()].join(", ");
|
|
58
|
+
}
|
|
59
|
+
/** Report the annotation on one schema node. */
|
|
60
|
+
function checkNode(node, manifest, path, issues) {
|
|
61
|
+
const slot = readValueTypeSlot(node);
|
|
62
|
+
if (!slot)
|
|
63
|
+
return;
|
|
64
|
+
if (!slot.entry) {
|
|
65
|
+
const suggestion = suggestValueType(slot.name);
|
|
66
|
+
// Only the bare-name spelling can carry the repair: it IS the annotation's
|
|
67
|
+
// whole value, which is the only shape `DiagnosticFix` describes.
|
|
68
|
+
const bareName = typeof node[X_TELO_TYPE] === "string";
|
|
69
|
+
issues.push({
|
|
70
|
+
code: "X_TELO_TYPE_UNKNOWN",
|
|
71
|
+
manifest,
|
|
72
|
+
path,
|
|
73
|
+
message: `'${slot.name || "(missing name)"}' is not a value type. ` +
|
|
74
|
+
(suggestion ? `Did you mean '${suggestion}'? ` : "") +
|
|
75
|
+
`Declared types: ${declaredNames()}. A value type names how a value is ` +
|
|
76
|
+
`REPRESENTED and is kernel-owned; to name a shape, reference it with !ref.`,
|
|
77
|
+
...(suggestion && bareName ? { fix: { replacement: suggestion } } : {}),
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const declared = new Set(slot.entry.parameters.map((p) => p.name));
|
|
82
|
+
for (const argument of Object.keys(slot.args)) {
|
|
83
|
+
if (declared.has(argument))
|
|
84
|
+
continue;
|
|
85
|
+
issues.push({
|
|
86
|
+
code: "X_TELO_TYPE_ARGUMENT_UNKNOWN",
|
|
87
|
+
manifest,
|
|
88
|
+
path,
|
|
89
|
+
message: `'${slot.entry.name}' declares no type parameter '${argument}'. ` +
|
|
90
|
+
(declared.size > 0
|
|
91
|
+
? `Its parameters: ${[...declared].join(", ")}.`
|
|
92
|
+
: `It takes no type parameters.`),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Walk a schema value, reporting every annotation it carries.
|
|
97
|
+
*
|
|
98
|
+
* Descends through every container rather than through a keyword list: a value
|
|
99
|
+
* type is legal at any schema position — a property, an item, a union branch, a
|
|
100
|
+
* `$defs` entry, a type argument — and enumerating positions is how a check
|
|
101
|
+
* ends up not covering the one an author used. */
|
|
102
|
+
function walk(value, manifest, path, segments, seen, issues) {
|
|
103
|
+
if (value === null || typeof value !== "object")
|
|
104
|
+
return;
|
|
105
|
+
if (seen.has(value))
|
|
106
|
+
return;
|
|
107
|
+
seen.add(value);
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
value.forEach((item, i) => walk(item, manifest, `${path}[${i}]`, [...segments, i], seen, issues));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const node = value;
|
|
113
|
+
// Report only inside a schema region. The walk itself is unbounded — reporting
|
|
114
|
+
// is safe anywhere, unlike a rewrite — but an `x-telo-type` key sitting in a
|
|
115
|
+
// resource's own configuration is not a schema annotation and is not this
|
|
116
|
+
// check's to judge.
|
|
117
|
+
if (isInSchemaRegion([...segments, X_TELO_TYPE])) {
|
|
118
|
+
checkNode(node, manifest, path, issues);
|
|
119
|
+
}
|
|
120
|
+
for (const [key, child] of Object.entries(node)) {
|
|
121
|
+
// The annotation's own value is read by `checkNode`; descending into it
|
|
122
|
+
// would report the type ARGUMENTS as if they were annotated nodes of their
|
|
123
|
+
// own. Their turn comes below, as ordinary schema nodes.
|
|
124
|
+
if (key === X_TELO_TYPE) {
|
|
125
|
+
// Walk the NORMALIZED arguments, so a bare-name argument (`of: Telo.Bytes`)
|
|
126
|
+
// is checked exactly as its expanded form is — the sugar must not be a
|
|
127
|
+
// hole in the check that exists to catch a misspelled name.
|
|
128
|
+
const slot = readValueTypeSlot(node);
|
|
129
|
+
for (const [argName, argValue] of Object.entries(slot?.args ?? {})) {
|
|
130
|
+
walk(argValue, manifest, `${path}.${X_TELO_TYPE}.${argName}`, [...segments, X_TELO_TYPE, argName], seen, issues);
|
|
131
|
+
}
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
walk(child, manifest, path ? `${path}.${key}` : key, [...segments, key], seen, issues);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Every `x-telo-type` problem in one manifest, wherever a schema is written. */
|
|
138
|
+
export function validateValueTypeSlots(manifest) {
|
|
139
|
+
const issues = [];
|
|
140
|
+
walk(manifest, manifest, "", [], new Set(), issues);
|
|
141
|
+
return issues;
|
|
142
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** The AJV half of `x-telo-type`, and the single place any Telo keyword is
|
|
2
|
+
* registered on an AJV instance.
|
|
3
|
+
*
|
|
4
|
+
* WHY CODEGEN RATHER THAN A `validate` FUNCTION. The kernel compiles standalone
|
|
5
|
+
* validators and caches them on disk; a function-valued keyword does not survive
|
|
6
|
+
* that serialization, so the check would silently be absent from exactly the
|
|
7
|
+
* validators the runtime uses. Codegen inlines instead.
|
|
8
|
+
*
|
|
9
|
+
* WHY THE CODEGEN LIVES HERE AND THE VOCABULARY DOES NOT. The SDK cannot depend
|
|
10
|
+
* on ajv, and an entry declares a REPRESENTATION rather than a code fragment —
|
|
11
|
+
* which is what lets one JSON file be read by a Rust kernel that has no AJV at
|
|
12
|
+
* all. The split is the whole point: `sdk/value-types/*.json` says *what a value
|
|
13
|
+
* is*, `sdk/nodejs/src/value-type.ts` says *what that means in this runtime*,
|
|
14
|
+
* and this file turns the pair into a check.
|
|
15
|
+
*
|
|
16
|
+
* ONE REGISTRATION SITE. {@link registerTeloKeywords} replaced five drifted
|
|
17
|
+
* ones — the analyzer's `createAjv` and the kernel's `schema-validator`,
|
|
18
|
+
* `resource-context`, `observed-state` and `manifest-schemas` — which registered
|
|
19
|
+
* overlapping lists of twelve, four, one and one. Drift there is not cosmetic: a
|
|
20
|
+
* no-op registration is what keeps `strict` mode quiet about an annotation, and
|
|
21
|
+
* a keyword that emits code is missing entirely from any instance that forgot
|
|
22
|
+
* it, so the same schema validated two ways depending on which AJV saw it. */
|
|
23
|
+
import type { KeywordDefinition } from "ajv";
|
|
24
|
+
/**
|
|
25
|
+
* Annotations that carry no validation at all: analyzer, editor and topology
|
|
26
|
+
* metadata. Registered as no-ops so AJV does not treat them as unknown keywords,
|
|
27
|
+
* and listed in one place so an instance cannot know about half of them.
|
|
28
|
+
*
|
|
29
|
+
* `x-telo-type` is deliberately absent — it is the one that emits code.
|
|
30
|
+
*/
|
|
31
|
+
export declare const ANNOTATION_KEYWORDS: readonly ["x-telo-bindings-from", "x-telo-catches-for", "x-telo-context", "x-telo-context-element-from", "x-telo-context-from", "x-telo-context-from-ref-kind", "x-telo-context-from-root", "x-telo-context-ref-from", "x-telo-error-context", "x-telo-eval", "x-telo-inline", "x-telo-outcome-list", "x-telo-provides-zone", "x-telo-ref", "x-telo-requires-zone", "x-telo-schema-from", "x-telo-scope", "x-telo-step-context", "x-telo-topology-role", "x-telo-value-schema-from", "x-telo-widget"];
|
|
32
|
+
/**
|
|
33
|
+
* The `x-telo-type` keyword.
|
|
34
|
+
*
|
|
35
|
+
* Three postures, decided by the entry and never by this code:
|
|
36
|
+
*
|
|
37
|
+
* - a `json` representation validates through its own declared schema, so the
|
|
38
|
+
* keyword emits nothing — the name carries nominal identity for static wiring
|
|
39
|
+
* and has no runtime existence at all;
|
|
40
|
+
* - a `live` instance is EXEMPT: its value is never traversed, because iterating
|
|
41
|
+
* a stream to check it is precisely what the exemption is for;
|
|
42
|
+
* - every other instance is ASSERTED against the constructor its binding names.
|
|
43
|
+
*
|
|
44
|
+
* An unknown name emits nothing here. It is a hard diagnostic in the analyzer
|
|
45
|
+
* (`X_TELO_TYPE_UNKNOWN`), which is where a name can be reported against the
|
|
46
|
+
* manifest that wrote it; failing compilation instead would take out every
|
|
47
|
+
* validator in a module for one typo in one slot.
|
|
48
|
+
*/
|
|
49
|
+
export declare function valueTypeKeyword(): KeywordDefinition;
|
|
50
|
+
/**
|
|
51
|
+
* Register every Telo keyword on an AJV instance: the annotations as no-ops and
|
|
52
|
+
* `x-telo-type` as the one that checks.
|
|
53
|
+
*
|
|
54
|
+
* Every AJV instance in the runtime and the analyzer goes through this, so a
|
|
55
|
+
* schema means the same thing wherever it is validated.
|
|
56
|
+
*/
|
|
57
|
+
export declare function registerTeloKeywords(ajv: {
|
|
58
|
+
addKeyword: (keyword: any, definition?: any) => unknown;
|
|
59
|
+
}): void;
|
|
60
|
+
//# sourceMappingURL=value-type-keyword.d.ts.map
|