@telorun/analyzer 0.58.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/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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/schema-compat.d.ts +18 -0
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +69 -6
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +69 -13
- package/package.json +3 -3
- package/src/index.ts +9 -2
- package/src/invocation-contract.ts +92 -1
- package/src/schema-compat.ts +68 -4
- package/src/validate-cel-context.ts +82 -13
package/dist/index.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ export { moduleScopedDefResolver, scopeResolverForModule } from "./alias-resolve
|
|
|
13
13
|
export type { ModuleScopes } from "./alias-resolver.js";
|
|
14
14
|
export { ancestorChain, contractDeclarer, controllerBearingAncestor, effectiveAuthorSchema, effectiveContractField, effectiveStatusSchema, hasOwnControllerOrTemplate, inheritedCapability, isInheritedDelegation, mappingFieldFor, needsContractMapping, resolveParent, } from "./extends-resolution.js";
|
|
15
15
|
export type { ContractDirection, DefResolver } from "./extends-resolution.js";
|
|
16
|
-
export { defaultBearingPaths, PERMISSIVE_CONTRACT, resolveContract, resolveContractSchema, withLiveValuesSkipped, } from "./invocation-contract.js";
|
|
17
|
-
export type { ContractOrigin, ContractScope, ResolvedContract } from "./invocation-contract.js";
|
|
16
|
+
export { defaultBearingPaths, declaredScalarPaths, PERMISSIVE_CONTRACT, resolveContract, resolveContractSchema, withLiveValuesSkipped, } from "./invocation-contract.js";
|
|
17
|
+
export type { ContractOrigin, ContractScope, DeclaredScalarForm, DeclaredScalarPath, ResolvedContract, } from "./invocation-contract.js";
|
|
18
18
|
export { hasIntermediateWildcard, parseRedactionPath, RedactionPathError, } from "./redaction-path.js";
|
|
19
19
|
export type { RedactionSegment } from "./redaction-path.js";
|
|
20
20
|
export { buildCallGraph, projectToPairs, resourceId } from "./call-graph.js";
|
|
@@ -37,7 +37,7 @@ export { validateDynamicSelectors, validateRefSlotDeclarations } from "./validat
|
|
|
37
37
|
export type { RefSlotIssue } from "./validate-ref-slots.js";
|
|
38
38
|
export { validateValueTypeSlots } from "./validate-value-type-slots.js";
|
|
39
39
|
export type { ValueTypeSlotIssue } from "./validate-value-type-slots.js";
|
|
40
|
-
export { checkSchemaCompatibility } from "./schema-compat.js";
|
|
40
|
+
export { checkSchemaCompatibility, selectUnionBranch } from "./schema-compat.js";
|
|
41
41
|
export type { CompatibilityResult } from "./schema-compat.js";
|
|
42
42
|
export { visitManifest } from "./manifest-visitor.js";
|
|
43
43
|
export type { CelSiteEvent, ManifestVisitor, RefSiteEvent, ResourceEnterEvent, ResourceExitEvent, ScopeBoundaryEvent, SchemaFromSiteEvent, VisitOptions, } from "./manifest-visitor.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AACjF,YAAY,EACR,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,UAAU,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACH,0BAA0B,EAC1B,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,gCAAgC,EAChC,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,YAAY,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,iCAAiC,EACjC,wBAAwB,EACxB,iBAAiB,EACjB,iCAAiC,EACjC,qBAAqB,GACtB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,0BAA0B,EAC1B,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,oBAAoB,EACpB,aAAa,GACd,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAClC,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export { BINDINGS_ANNOTATION, bindingContextProperties, bindingDependencies, fin
|
|
|
7
7
|
export { applyObservedStateNode, buildObservedStateIndex, buildObservedStateResourcesSchema, collectRunReachableNames, observedStateRead, validateObservedStateDeclarations, OBSERVED_STATE_SCHEMA, } from "./validate-observed-state.js";
|
|
8
8
|
export { moduleScopedDefResolver, scopeResolverForModule } from "./alias-resolver.js";
|
|
9
9
|
export { ancestorChain, contractDeclarer, controllerBearingAncestor, effectiveAuthorSchema, effectiveContractField, effectiveStatusSchema, hasOwnControllerOrTemplate, inheritedCapability, isInheritedDelegation, mappingFieldFor, needsContractMapping, resolveParent, } from "./extends-resolution.js";
|
|
10
|
-
export { defaultBearingPaths, PERMISSIVE_CONTRACT, resolveContract, resolveContractSchema, withLiveValuesSkipped, } from "./invocation-contract.js";
|
|
10
|
+
export { defaultBearingPaths, declaredScalarPaths, PERMISSIVE_CONTRACT, resolveContract, resolveContractSchema, withLiveValuesSkipped, } from "./invocation-contract.js";
|
|
11
11
|
export { hasIntermediateWildcard, parseRedactionPath, RedactionPathError, } from "./redaction-path.js";
|
|
12
12
|
export { buildCallGraph, projectToPairs, resourceId } from "./call-graph.js";
|
|
13
13
|
export { buildReferenceFieldMap, isRefEntry, isScopeEntry } from "./reference-field-map.js";
|
|
@@ -19,7 +19,7 @@ export { deriveLibraryExportRequirements, projectZoneRequirements, runZoneAnalys
|
|
|
19
19
|
export { validateZoneSlotDeclarations } from "./validate-zone-slots.js";
|
|
20
20
|
export { validateDynamicSelectors, validateRefSlotDeclarations } from "./validate-ref-slots.js";
|
|
21
21
|
export { validateValueTypeSlots } from "./validate-value-type-slots.js";
|
|
22
|
-
export { checkSchemaCompatibility } from "./schema-compat.js";
|
|
22
|
+
export { checkSchemaCompatibility, selectUnionBranch } from "./schema-compat.js";
|
|
23
23
|
export { visitManifest } from "./manifest-visitor.js";
|
|
24
24
|
export { Loader } from "./manifest-loader.js";
|
|
25
25
|
export { isModuleKind, MODULE_KINDS } from "./module-kinds.js";
|
|
@@ -105,4 +105,41 @@ export declare function defaultBearingPaths(schema: Record<string, any>,
|
|
|
105
105
|
* declares its defaults behind the reference, and a walk that cannot follow
|
|
106
106
|
* it would report none, leaving the caller's data shared where a fill lands. */
|
|
107
107
|
resolveRef?: (ref: string) => Record<string, any> | undefined): string[][];
|
|
108
|
+
/** The scalar form a declared node's value takes at runtime. `int64` and
|
|
109
|
+
* `double` are the two JSON scalars whose runtime representation is NOT decided
|
|
110
|
+
* by the value that arrives: a CEL integer is a BigInt and a CEL double a JS
|
|
111
|
+
* number, and both are `typeof "number" | "bigint"` away from what a declaration
|
|
112
|
+
* says they are. Everything else — string, boolean, object, array, and every
|
|
113
|
+
* `instance` value type — is already its own representation, so it is not
|
|
114
|
+
* listed and never rewritten. */
|
|
115
|
+
export type DeclaredScalarForm = "int64" | "double";
|
|
116
|
+
export interface DeclaredScalarPath {
|
|
117
|
+
readonly path: string[];
|
|
118
|
+
readonly form: DeclaredScalarForm;
|
|
119
|
+
}
|
|
120
|
+
/** Every path in `schema` whose declared type fixes a scalar representation —
|
|
121
|
+
* the leaves a value must carry in that form for the contract to be TRUE, not
|
|
122
|
+
* merely to pass.
|
|
123
|
+
*
|
|
124
|
+
* A declared shape says what the value IS, the same service a JSON Schema gives
|
|
125
|
+
* an HTTP response serializer. Telo's CEL layer takes it literally:
|
|
126
|
+
* `jsonSchemaToCelType` maps `integer` to CEL `int` and `number` to `double`,
|
|
127
|
+
* and a CEL int IS a BigInt — so a controller handing back a plain JS number at
|
|
128
|
+
* an `integer` slot makes the declaration a lie that surfaces nowhere until an
|
|
129
|
+
* expression composes it, as `result.n + 1` type-checking statically and then
|
|
130
|
+
* dying at dispatch with `no such overload: dyn<double> + int`.
|
|
131
|
+
*
|
|
132
|
+
* A VALUE TYPE is read through the same rule rather than beside it: a `json`
|
|
133
|
+
* representation refines a JSON type and contributes its `base` (so
|
|
134
|
+
* `Telo.TcpPort` is an `int64` slot), while an `instance` representation
|
|
135
|
+
* REPLACES the JSON layer — a byte buffer and a stream handle are already their
|
|
136
|
+
* own representation, nothing converts to them, and the walk stops there rather
|
|
137
|
+
* than descending into a value that is not a plain container.
|
|
138
|
+
*
|
|
139
|
+
* Bounded by the schema's declarations rather than by the size of the payload,
|
|
140
|
+
* exactly as {@link defaultBearingPaths} is: a contract declaring no such
|
|
141
|
+
* scalar walks nothing at dispatch. A union counts when it names exactly one of
|
|
142
|
+
* the two — `["integer", "null"]` is still the integer branch, while
|
|
143
|
+
* `["integer", "number"]` fixes nothing and is left alone. */
|
|
144
|
+
export declare function declaredScalarPaths(schema: Record<string, any>, resolveRef?: (ref: string) => Record<string, any> | undefined): DeclaredScalarPath[];
|
|
108
145
|
//# sourceMappingURL=invocation-contract.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invocation-contract.d.ts","sourceRoot":"","sources":["../src/invocation-contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,kBAAkB,
|
|
1
|
+
{"version":3,"file":"invocation-contract.d.ts","sourceRoot":"","sources":["../src/invocation-contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,kBAAkB,EAAe,MAAM,cAAc,CAAC;AAChF,OAAO,EACL,KAAK,iBAAiB,EAEtB,KAAK,WAAW,EAEjB,MAAM,yBAAyB,CAAC;AAGjC,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAElC;;;;;;;;;;;;GAYG;AAEH;;oCAEoC;AACpC,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,MAAM,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,MAAM,EAAE,cAAc,CAAC;IACvB;gFAC4E;IAC5E,UAAU,CAAC,EAAE,kBAAkB,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B;;kEAE8D;IAC9D,iBAAiB,EAAE,WAAW,CAAC;IAC/B;;;;;;;;;OASG;IACH,gBAAgB,CAAC,GAAG,EAAE,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CAC9E;AAED;;uCAEuC;AACvC,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAGnD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,iBAAiB,EAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EACzC,UAAU,EAAE,kBAAkB,GAAG,SAAS,EAC1C,KAAK,EAAE,aAAa,GACnB,gBAAgB,GAAG,SAAS,CAa9B;AAED;;mDAEmD;AACnD,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,iBAAiB,EAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EACzC,UAAU,EAAE,kBAAkB,GAAG,SAAS,EAC1C,KAAK,EAAE,aAAa,GACnB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAErB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;AAC3B;;;;6CAI6C;AAC7C,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAC5D,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAErB;AAoFD;;;;;gEAKgE;AAChE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;AAC3B;;iFAEiF;AACjF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAC5D,MAAM,EAAE,EAAE,CA+BZ;AAED;;;;;;kCAMkC;AAClC,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEpD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;+DAuB+D;AAC/D,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAC5D,kBAAkB,EAAE,CAoCtB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isLiveSlot } from "@telorun/sdk";
|
|
1
|
+
import { isLiveSlot, valueTypeOf } from "@telorun/sdk";
|
|
2
2
|
import { contractDeclarer, effectiveContractField, } from "./extends-resolution.js";
|
|
3
3
|
import { resolveTypeFieldToSchema } from "./validate-cel-context.js";
|
|
4
4
|
/** The fallback for a target that declares no contract: anything goes. Not
|
|
@@ -203,3 +203,83 @@ resolveRef) {
|
|
|
203
203
|
walk(schema, [], []);
|
|
204
204
|
return out;
|
|
205
205
|
}
|
|
206
|
+
/** Every path in `schema` whose declared type fixes a scalar representation —
|
|
207
|
+
* the leaves a value must carry in that form for the contract to be TRUE, not
|
|
208
|
+
* merely to pass.
|
|
209
|
+
*
|
|
210
|
+
* A declared shape says what the value IS, the same service a JSON Schema gives
|
|
211
|
+
* an HTTP response serializer. Telo's CEL layer takes it literally:
|
|
212
|
+
* `jsonSchemaToCelType` maps `integer` to CEL `int` and `number` to `double`,
|
|
213
|
+
* and a CEL int IS a BigInt — so a controller handing back a plain JS number at
|
|
214
|
+
* an `integer` slot makes the declaration a lie that surfaces nowhere until an
|
|
215
|
+
* expression composes it, as `result.n + 1` type-checking statically and then
|
|
216
|
+
* dying at dispatch with `no such overload: dyn<double> + int`.
|
|
217
|
+
*
|
|
218
|
+
* A VALUE TYPE is read through the same rule rather than beside it: a `json`
|
|
219
|
+
* representation refines a JSON type and contributes its `base` (so
|
|
220
|
+
* `Telo.TcpPort` is an `int64` slot), while an `instance` representation
|
|
221
|
+
* REPLACES the JSON layer — a byte buffer and a stream handle are already their
|
|
222
|
+
* own representation, nothing converts to them, and the walk stops there rather
|
|
223
|
+
* than descending into a value that is not a plain container.
|
|
224
|
+
*
|
|
225
|
+
* Bounded by the schema's declarations rather than by the size of the payload,
|
|
226
|
+
* exactly as {@link defaultBearingPaths} is: a contract declaring no such
|
|
227
|
+
* scalar walks nothing at dispatch. A union counts when it names exactly one of
|
|
228
|
+
* the two — `["integer", "null"]` is still the integer branch, while
|
|
229
|
+
* `["integer", "number"]` fixes nothing and is left alone. */
|
|
230
|
+
export function declaredScalarPaths(schema, resolveRef) {
|
|
231
|
+
const out = [];
|
|
232
|
+
const walk = (node, path, chain) => {
|
|
233
|
+
if (!node || typeof node !== "object")
|
|
234
|
+
return;
|
|
235
|
+
let s = node;
|
|
236
|
+
if (chain.includes(s))
|
|
237
|
+
return;
|
|
238
|
+
if (resolveRef && typeof s.$ref === "string") {
|
|
239
|
+
const target = resolveRef(s.$ref);
|
|
240
|
+
if (!target || chain.includes(target))
|
|
241
|
+
return;
|
|
242
|
+
s = { ...target, ...s, $ref: undefined };
|
|
243
|
+
}
|
|
244
|
+
const here = [...chain, node];
|
|
245
|
+
const entry = valueTypeOf(s);
|
|
246
|
+
// An `instance` representation replaces the JSON layer: it is its own
|
|
247
|
+
// representation, so there is nothing to normalize to and nothing below it
|
|
248
|
+
// that is a plain container to walk.
|
|
249
|
+
if (entry && entry.representation === "instance")
|
|
250
|
+
return;
|
|
251
|
+
const form = scalarFormOf(entry && entry.base !== undefined ? entry.base : s.type);
|
|
252
|
+
if (form && path.length > 0)
|
|
253
|
+
out.push({ path, form });
|
|
254
|
+
const properties = s.properties;
|
|
255
|
+
if (properties) {
|
|
256
|
+
for (const [key, child] of Object.entries(properties))
|
|
257
|
+
walk(child, [...path, key], here);
|
|
258
|
+
}
|
|
259
|
+
for (const branch of ["allOf", "anyOf", "oneOf"]) {
|
|
260
|
+
const list = s[branch];
|
|
261
|
+
if (Array.isArray(list))
|
|
262
|
+
for (const child of list)
|
|
263
|
+
walk(child, path, here);
|
|
264
|
+
}
|
|
265
|
+
if (s.items)
|
|
266
|
+
walk(s.items, [...path, "[]"], here);
|
|
267
|
+
};
|
|
268
|
+
walk(schema, [], []);
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
function scalarFormOf(declared) {
|
|
272
|
+
if (declared === "integer")
|
|
273
|
+
return "int64";
|
|
274
|
+
if (declared === "number")
|
|
275
|
+
return "double";
|
|
276
|
+
if (!Array.isArray(declared))
|
|
277
|
+
return undefined;
|
|
278
|
+
const integer = declared.includes("integer");
|
|
279
|
+
const number = declared.includes("number");
|
|
280
|
+
// A union naming both fixes nothing — either representation satisfies it, so
|
|
281
|
+
// rewriting one into the other would be a choice the declaration never made.
|
|
282
|
+
if (integer === number)
|
|
283
|
+
return undefined;
|
|
284
|
+
return integer ? "int64" : "double";
|
|
285
|
+
}
|
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":"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;AAYD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAC5D,mBAAmB,CAIrB;AAuKD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAaD,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CAsB/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAqB,CAAC;AAE1E;;iEAEiE;AACjE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGzF;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CA6BnF;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAkDhG;AAkED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CA+D/E;AAqBD,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAOtG;AAED,gGAAgG;AAChG;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACxB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAgCrB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAwBlF;AAED;iGACiG;AACjG,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;AAChC;;;;;;;;6DAQ6D;AAC7D,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EACrC,IAAI,SAAK,GACR,OAAO,CAiET"}
|
package/dist/schema-compat.js
CHANGED
|
@@ -27,6 +27,16 @@ export function createAjv() {
|
|
|
27
27
|
}
|
|
28
28
|
const ajv = createAjv();
|
|
29
29
|
const compiledSchemaValidators = new WeakMap();
|
|
30
|
+
/** The alternatives a union node declares, or undefined when it is not one.
|
|
31
|
+
* `anyOf` and `oneOf` are one question here — which branches could accept this
|
|
32
|
+
* value — and their difference (exactly-one vs at-least-one) is a validation
|
|
33
|
+
* rule, not a compatibility one. */
|
|
34
|
+
function unionBranches(schema) {
|
|
35
|
+
const branches = schema.anyOf ?? schema.oneOf;
|
|
36
|
+
if (!Array.isArray(branches) || branches.length === 0)
|
|
37
|
+
return undefined;
|
|
38
|
+
return branches.filter((b) => b && typeof b === "object");
|
|
39
|
+
}
|
|
30
40
|
/**
|
|
31
41
|
* Conservative structural JSON Schema compatibility check — is a value shaped
|
|
32
42
|
* like `source` acceptable where `target` is declared?
|
|
@@ -80,6 +90,37 @@ function compare(rawSource, rawTarget, path, issues, resolveRef, seen) {
|
|
|
80
90
|
}
|
|
81
91
|
const source = deref(rawSource, resolveRef);
|
|
82
92
|
const target = deref(rawTarget, resolveRef);
|
|
93
|
+
// A union is ALTERNATIVES, so it is compared by distributing over branches on
|
|
94
|
+
// both sides: a definite conflict is one where no source-branch/target-branch
|
|
95
|
+
// pair agrees. Returning silently the moment either side was a union — which
|
|
96
|
+
// is what this did — switched the whole comparison off for any slot that
|
|
97
|
+
// accepts more than one shape, and those are exactly the slots where a value
|
|
98
|
+
// type carries the only information distinguishing the branches.
|
|
99
|
+
//
|
|
100
|
+
// `allOf` is a conjunction rather than a choice, so it keeps the old posture:
|
|
101
|
+
// it says too little to judge and stays compatible.
|
|
102
|
+
if (source.allOf || target.allOf)
|
|
103
|
+
return;
|
|
104
|
+
const sourceBranches = unionBranches(source);
|
|
105
|
+
const targetBranches = unionBranches(target);
|
|
106
|
+
if (sourceBranches || targetBranches) {
|
|
107
|
+
const lefts = sourceBranches ?? [source];
|
|
108
|
+
const rights = targetBranches ?? [target];
|
|
109
|
+
const reasons = [];
|
|
110
|
+
for (const left of lefts) {
|
|
111
|
+
for (const right of rights) {
|
|
112
|
+
const probe = [];
|
|
113
|
+
// A fresh `seen` per probe: a pair rejected on one branch must not mark
|
|
114
|
+
// a reference pair visited for the next, which would silently pass it.
|
|
115
|
+
compare(left, right, path, probe, resolveRef, new Set(seen));
|
|
116
|
+
if (probe.length === 0)
|
|
117
|
+
return;
|
|
118
|
+
reasons.push(...probe);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
issues.push(`${path || "/"}: no alternative matches — ${[...new Set(reasons)].join("; ")}`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
83
124
|
// Value types first: an `instance` representation has no JSON `type` to
|
|
84
125
|
// compare, so its identity IS the comparison — and its arguments are where the
|
|
85
126
|
// real information lives.
|
|
@@ -127,10 +168,6 @@ function compare(rawSource, rawTarget, path, issues, resolveRef, seen) {
|
|
|
127
168
|
issues.push(`${path || "/"}: type mismatch — source is '${source.type}', target expects '${target.type}'`);
|
|
128
169
|
return;
|
|
129
170
|
}
|
|
130
|
-
if (source.anyOf || source.oneOf || source.allOf)
|
|
131
|
-
return;
|
|
132
|
-
if (target.anyOf || target.oneOf || target.allOf)
|
|
133
|
-
return;
|
|
134
171
|
// An array's element, which the old comparison never looked at — so every
|
|
135
172
|
// nested shape passed regardless of what it contained.
|
|
136
173
|
if (target.items && source.items) {
|
|
@@ -370,8 +407,16 @@ export function celTypeSatisfiesJsonSchema(celType, schema) {
|
|
|
370
407
|
}
|
|
371
408
|
if (!schema.type && !schema.anyOf && !schema.oneOf && !schema.allOf)
|
|
372
409
|
return true;
|
|
373
|
-
|
|
410
|
+
// `allOf` is a conjunction and says too little to judge from a single CEL
|
|
411
|
+
// type. A union is a CHOICE, so it is satisfied by satisfying any branch —
|
|
412
|
+
// distributed for the same reason `compare` does it: accepting every union
|
|
413
|
+
// outright turns the check off for exactly the slots that admit more than one
|
|
414
|
+
// shape, and those are the ones where the branches carry the information.
|
|
415
|
+
if (schema.allOf)
|
|
374
416
|
return true;
|
|
417
|
+
const branches = unionBranches(schema);
|
|
418
|
+
if (branches)
|
|
419
|
+
return branches.some((branch) => celTypeSatisfiesJsonSchema(celType, branch));
|
|
375
420
|
const schemaTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
376
421
|
const accepted = {
|
|
377
422
|
int: ["integer", "number"],
|
|
@@ -482,6 +527,24 @@ export function celPlaceholderForSchema(rawSchema) {
|
|
|
482
527
|
// the real value is checked at runtime once the expression resolves.
|
|
483
528
|
if (Array.isArray(schema.enum) && schema.enum.length > 0)
|
|
484
529
|
return schema.enum[0];
|
|
530
|
+
// A UNION with no `type` of its own. Without this, a whole-field CEL leaf at
|
|
531
|
+
// such a slot gets `null`, which every branch then rejects — so a field
|
|
532
|
+
// declared `anyOf: [array, boolean]` could not be written as an expression at
|
|
533
|
+
// all, while one whose union happens to contain a `live` branch escaped by
|
|
534
|
+
// accident (nothing validates a live value, so `null` passed). The first
|
|
535
|
+
// branch that yields a placeholder wins: the same conservative posture
|
|
536
|
+
// `selectUnionBranch` takes, and enough for AJV, whose question is only
|
|
537
|
+
// whether SOME branch accepts the stand-in.
|
|
538
|
+
if (schema.type === undefined) {
|
|
539
|
+
const branches = unionBranches(schema);
|
|
540
|
+
if (branches) {
|
|
541
|
+
for (const branch of branches) {
|
|
542
|
+
const candidate = celPlaceholderForSchema(branch);
|
|
543
|
+
if (candidate !== null)
|
|
544
|
+
return candidate;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
485
548
|
switch (schema.type) {
|
|
486
549
|
case "integer":
|
|
487
550
|
case "number":
|
|
@@ -553,7 +616,7 @@ export function resolveRef(schema, root) {
|
|
|
553
616
|
* — an ambiguous union is one the analyzer should not resolve on the author's
|
|
554
617
|
* behalf.
|
|
555
618
|
*/
|
|
556
|
-
function selectUnionBranch(schema, data, root) {
|
|
619
|
+
export function selectUnionBranch(schema, data, root) {
|
|
557
620
|
const branches = (schema.oneOf ?? schema.anyOf);
|
|
558
621
|
if (!Array.isArray(branches) || branches.length === 0)
|
|
559
622
|
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.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.15.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.74.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@telorun/sdk": "*"
|
package/src/index.ts
CHANGED
|
@@ -62,12 +62,19 @@ export {
|
|
|
62
62
|
export type { ContractDirection, DefResolver } from "./extends-resolution.js";
|
|
63
63
|
export {
|
|
64
64
|
defaultBearingPaths,
|
|
65
|
+
declaredScalarPaths,
|
|
65
66
|
PERMISSIVE_CONTRACT,
|
|
66
67
|
resolveContract,
|
|
67
68
|
resolveContractSchema,
|
|
68
69
|
withLiveValuesSkipped,
|
|
69
70
|
} from "./invocation-contract.js";
|
|
70
|
-
export type {
|
|
71
|
+
export type {
|
|
72
|
+
ContractOrigin,
|
|
73
|
+
ContractScope,
|
|
74
|
+
DeclaredScalarForm,
|
|
75
|
+
DeclaredScalarPath,
|
|
76
|
+
ResolvedContract,
|
|
77
|
+
} from "./invocation-contract.js";
|
|
71
78
|
export {
|
|
72
79
|
hasIntermediateWildcard,
|
|
73
80
|
parseRedactionPath,
|
|
@@ -136,7 +143,7 @@ export { validateDynamicSelectors, validateRefSlotDeclarations } from "./validat
|
|
|
136
143
|
export type { RefSlotIssue } from "./validate-ref-slots.js";
|
|
137
144
|
export { validateValueTypeSlots } from "./validate-value-type-slots.js";
|
|
138
145
|
export type { ValueTypeSlotIssue } from "./validate-value-type-slots.js";
|
|
139
|
-
export { checkSchemaCompatibility } from "./schema-compat.js";
|
|
146
|
+
export { checkSchemaCompatibility, selectUnionBranch } from "./schema-compat.js";
|
|
140
147
|
export type { CompatibilityResult } from "./schema-compat.js";
|
|
141
148
|
export { visitManifest } from "./manifest-visitor.js";
|
|
142
149
|
export type {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isLiveSlot, type ResourceDefinition } from "@telorun/sdk";
|
|
1
|
+
import { isLiveSlot, type ResourceDefinition, valueTypeOf } from "@telorun/sdk";
|
|
2
2
|
import {
|
|
3
3
|
type ContractDirection,
|
|
4
4
|
contractDeclarer,
|
|
@@ -268,3 +268,94 @@ export function defaultBearingPaths(
|
|
|
268
268
|
walk(schema, [], []);
|
|
269
269
|
return out;
|
|
270
270
|
}
|
|
271
|
+
|
|
272
|
+
/** The scalar form a declared node's value takes at runtime. `int64` and
|
|
273
|
+
* `double` are the two JSON scalars whose runtime representation is NOT decided
|
|
274
|
+
* by the value that arrives: a CEL integer is a BigInt and a CEL double a JS
|
|
275
|
+
* number, and both are `typeof "number" | "bigint"` away from what a declaration
|
|
276
|
+
* says they are. Everything else — string, boolean, object, array, and every
|
|
277
|
+
* `instance` value type — is already its own representation, so it is not
|
|
278
|
+
* listed and never rewritten. */
|
|
279
|
+
export type DeclaredScalarForm = "int64" | "double";
|
|
280
|
+
|
|
281
|
+
export interface DeclaredScalarPath {
|
|
282
|
+
readonly path: string[];
|
|
283
|
+
readonly form: DeclaredScalarForm;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Every path in `schema` whose declared type fixes a scalar representation —
|
|
287
|
+
* the leaves a value must carry in that form for the contract to be TRUE, not
|
|
288
|
+
* merely to pass.
|
|
289
|
+
*
|
|
290
|
+
* A declared shape says what the value IS, the same service a JSON Schema gives
|
|
291
|
+
* an HTTP response serializer. Telo's CEL layer takes it literally:
|
|
292
|
+
* `jsonSchemaToCelType` maps `integer` to CEL `int` and `number` to `double`,
|
|
293
|
+
* and a CEL int IS a BigInt — so a controller handing back a plain JS number at
|
|
294
|
+
* an `integer` slot makes the declaration a lie that surfaces nowhere until an
|
|
295
|
+
* expression composes it, as `result.n + 1` type-checking statically and then
|
|
296
|
+
* dying at dispatch with `no such overload: dyn<double> + int`.
|
|
297
|
+
*
|
|
298
|
+
* A VALUE TYPE is read through the same rule rather than beside it: a `json`
|
|
299
|
+
* representation refines a JSON type and contributes its `base` (so
|
|
300
|
+
* `Telo.TcpPort` is an `int64` slot), while an `instance` representation
|
|
301
|
+
* REPLACES the JSON layer — a byte buffer and a stream handle are already their
|
|
302
|
+
* own representation, nothing converts to them, and the walk stops there rather
|
|
303
|
+
* than descending into a value that is not a plain container.
|
|
304
|
+
*
|
|
305
|
+
* Bounded by the schema's declarations rather than by the size of the payload,
|
|
306
|
+
* exactly as {@link defaultBearingPaths} is: a contract declaring no such
|
|
307
|
+
* scalar walks nothing at dispatch. A union counts when it names exactly one of
|
|
308
|
+
* the two — `["integer", "null"]` is still the integer branch, while
|
|
309
|
+
* `["integer", "number"]` fixes nothing and is left alone. */
|
|
310
|
+
export function declaredScalarPaths(
|
|
311
|
+
schema: Record<string, any>,
|
|
312
|
+
resolveRef?: (ref: string) => Record<string, any> | undefined,
|
|
313
|
+
): DeclaredScalarPath[] {
|
|
314
|
+
const out: DeclaredScalarPath[] = [];
|
|
315
|
+
|
|
316
|
+
const walk = (node: unknown, path: string[], chain: readonly object[]): void => {
|
|
317
|
+
if (!node || typeof node !== "object") return;
|
|
318
|
+
let s = node as Record<string, any>;
|
|
319
|
+
if (chain.includes(s)) return;
|
|
320
|
+
if (resolveRef && typeof s.$ref === "string") {
|
|
321
|
+
const target = resolveRef(s.$ref);
|
|
322
|
+
if (!target || chain.includes(target)) return;
|
|
323
|
+
s = { ...target, ...s, $ref: undefined };
|
|
324
|
+
}
|
|
325
|
+
const here = [...chain, node as object];
|
|
326
|
+
|
|
327
|
+
const entry = valueTypeOf(s);
|
|
328
|
+
// An `instance` representation replaces the JSON layer: it is its own
|
|
329
|
+
// representation, so there is nothing to normalize to and nothing below it
|
|
330
|
+
// that is a plain container to walk.
|
|
331
|
+
if (entry && entry.representation === "instance") return;
|
|
332
|
+
|
|
333
|
+
const form = scalarFormOf(entry && entry.base !== undefined ? entry.base : s.type);
|
|
334
|
+
if (form && path.length > 0) out.push({ path, form });
|
|
335
|
+
|
|
336
|
+
const properties = s.properties as Record<string, any> | undefined;
|
|
337
|
+
if (properties) {
|
|
338
|
+
for (const [key, child] of Object.entries(properties)) walk(child, [...path, key], here);
|
|
339
|
+
}
|
|
340
|
+
for (const branch of ["allOf", "anyOf", "oneOf"] as const) {
|
|
341
|
+
const list = s[branch];
|
|
342
|
+
if (Array.isArray(list)) for (const child of list) walk(child, path, here);
|
|
343
|
+
}
|
|
344
|
+
if (s.items) walk(s.items, [...path, "[]"], here);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
walk(schema, [], []);
|
|
348
|
+
return out;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function scalarFormOf(declared: unknown): DeclaredScalarForm | undefined {
|
|
352
|
+
if (declared === "integer") return "int64";
|
|
353
|
+
if (declared === "number") return "double";
|
|
354
|
+
if (!Array.isArray(declared)) return undefined;
|
|
355
|
+
const integer = declared.includes("integer");
|
|
356
|
+
const number = declared.includes("number");
|
|
357
|
+
// A union naming both fixes nothing — either representation satisfies it, so
|
|
358
|
+
// rewriting one into the other would be a choice the declaration never made.
|
|
359
|
+
if (integer === number) return undefined;
|
|
360
|
+
return integer ? "int64" : "double";
|
|
361
|
+
}
|
package/src/schema-compat.ts
CHANGED
|
@@ -48,6 +48,16 @@ export interface CompatibilityResult {
|
|
|
48
48
|
issues: string[];
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/** The alternatives a union node declares, or undefined when it is not one.
|
|
52
|
+
* `anyOf` and `oneOf` are one question here — which branches could accept this
|
|
53
|
+
* value — and their difference (exactly-one vs at-least-one) is a validation
|
|
54
|
+
* rule, not a compatibility one. */
|
|
55
|
+
function unionBranches(schema: Record<string, any>): Record<string, any>[] | undefined {
|
|
56
|
+
const branches = schema.anyOf ?? schema.oneOf;
|
|
57
|
+
if (!Array.isArray(branches) || branches.length === 0) return undefined;
|
|
58
|
+
return branches.filter((b) => b && typeof b === "object") as Record<string, any>[];
|
|
59
|
+
}
|
|
60
|
+
|
|
51
61
|
/**
|
|
52
62
|
* Conservative structural JSON Schema compatibility check — is a value shaped
|
|
53
63
|
* like `source` acceptable where `target` is declared?
|
|
@@ -115,6 +125,38 @@ function compare(
|
|
|
115
125
|
const source = deref(rawSource, resolveRef);
|
|
116
126
|
const target = deref(rawTarget, resolveRef);
|
|
117
127
|
|
|
128
|
+
// A union is ALTERNATIVES, so it is compared by distributing over branches on
|
|
129
|
+
// both sides: a definite conflict is one where no source-branch/target-branch
|
|
130
|
+
// pair agrees. Returning silently the moment either side was a union — which
|
|
131
|
+
// is what this did — switched the whole comparison off for any slot that
|
|
132
|
+
// accepts more than one shape, and those are exactly the slots where a value
|
|
133
|
+
// type carries the only information distinguishing the branches.
|
|
134
|
+
//
|
|
135
|
+
// `allOf` is a conjunction rather than a choice, so it keeps the old posture:
|
|
136
|
+
// it says too little to judge and stays compatible.
|
|
137
|
+
if (source.allOf || target.allOf) return;
|
|
138
|
+
const sourceBranches = unionBranches(source);
|
|
139
|
+
const targetBranches = unionBranches(target);
|
|
140
|
+
if (sourceBranches || targetBranches) {
|
|
141
|
+
const lefts = sourceBranches ?? [source];
|
|
142
|
+
const rights = targetBranches ?? [target];
|
|
143
|
+
const reasons: string[] = [];
|
|
144
|
+
for (const left of lefts) {
|
|
145
|
+
for (const right of rights) {
|
|
146
|
+
const probe: string[] = [];
|
|
147
|
+
// A fresh `seen` per probe: a pair rejected on one branch must not mark
|
|
148
|
+
// a reference pair visited for the next, which would silently pass it.
|
|
149
|
+
compare(left, right, path, probe, resolveRef, new Set(seen));
|
|
150
|
+
if (probe.length === 0) return;
|
|
151
|
+
reasons.push(...probe);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
issues.push(
|
|
155
|
+
`${path || "/"}: no alternative matches — ${[...new Set(reasons)].join("; ")}`,
|
|
156
|
+
);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
118
160
|
// Value types first: an `instance` representation has no JSON `type` to
|
|
119
161
|
// compare, so its identity IS the comparison — and its arguments are where the
|
|
120
162
|
// real information lives.
|
|
@@ -180,8 +222,6 @@ function compare(
|
|
|
180
222
|
);
|
|
181
223
|
return;
|
|
182
224
|
}
|
|
183
|
-
if (source.anyOf || source.oneOf || source.allOf) return;
|
|
184
|
-
if (target.anyOf || target.oneOf || target.allOf) return;
|
|
185
225
|
|
|
186
226
|
// An array's element, which the old comparison never looked at — so every
|
|
187
227
|
// nested shape passed regardless of what it contained.
|
|
@@ -425,7 +465,14 @@ export function celTypeSatisfiesJsonSchema(celType: string, schema: Record<strin
|
|
|
425
465
|
return true;
|
|
426
466
|
}
|
|
427
467
|
if (!schema.type && !schema.anyOf && !schema.oneOf && !schema.allOf) return true;
|
|
428
|
-
|
|
468
|
+
// `allOf` is a conjunction and says too little to judge from a single CEL
|
|
469
|
+
// type. A union is a CHOICE, so it is satisfied by satisfying any branch —
|
|
470
|
+
// distributed for the same reason `compare` does it: accepting every union
|
|
471
|
+
// outright turns the check off for exactly the slots that admit more than one
|
|
472
|
+
// shape, and those are the ones where the branches carry the information.
|
|
473
|
+
if (schema.allOf) return true;
|
|
474
|
+
const branches = unionBranches(schema);
|
|
475
|
+
if (branches) return branches.some((branch) => celTypeSatisfiesJsonSchema(celType, branch));
|
|
429
476
|
const schemaTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
430
477
|
const accepted: Record<string, string[]> = {
|
|
431
478
|
int: ["integer", "number"],
|
|
@@ -528,6 +575,23 @@ export function celPlaceholderForSchema(rawSchema: Record<string, any>): unknown
|
|
|
528
575
|
// member chosen is irrelevant — only its acceptability to AJV matters, since
|
|
529
576
|
// the real value is checked at runtime once the expression resolves.
|
|
530
577
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
578
|
+
// A UNION with no `type` of its own. Without this, a whole-field CEL leaf at
|
|
579
|
+
// such a slot gets `null`, which every branch then rejects — so a field
|
|
580
|
+
// declared `anyOf: [array, boolean]` could not be written as an expression at
|
|
581
|
+
// all, while one whose union happens to contain a `live` branch escaped by
|
|
582
|
+
// accident (nothing validates a live value, so `null` passed). The first
|
|
583
|
+
// branch that yields a placeholder wins: the same conservative posture
|
|
584
|
+
// `selectUnionBranch` takes, and enough for AJV, whose question is only
|
|
585
|
+
// whether SOME branch accepts the stand-in.
|
|
586
|
+
if (schema.type === undefined) {
|
|
587
|
+
const branches = unionBranches(schema);
|
|
588
|
+
if (branches) {
|
|
589
|
+
for (const branch of branches) {
|
|
590
|
+
const candidate = celPlaceholderForSchema(branch);
|
|
591
|
+
if (candidate !== null) return candidate;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
531
595
|
switch (schema.type) {
|
|
532
596
|
case "integer":
|
|
533
597
|
case "number":
|
|
@@ -603,7 +667,7 @@ export function resolveRef(schema: Record<string, any>, root: Record<string, any
|
|
|
603
667
|
* — an ambiguous union is one the analyzer should not resolve on the author's
|
|
604
668
|
* behalf.
|
|
605
669
|
*/
|
|
606
|
-
function selectUnionBranch(
|
|
670
|
+
export function selectUnionBranch(
|
|
607
671
|
schema: Record<string, any>,
|
|
608
672
|
data: unknown,
|
|
609
673
|
root: Record<string, any>,
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export { extractAccessChains, validateChainAgainstSchema } from "@telorun/templating";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
elementSchemaOf,
|
|
4
|
+
isLiveSlot,
|
|
5
|
+
mergeTypeSchemas,
|
|
6
|
+
parseCanonicalTypeSchemaId,
|
|
7
|
+
} from "@telorun/sdk";
|
|
3
8
|
import { KERNEL_BUILTINS } from "./builtins.js";
|
|
4
9
|
|
|
5
10
|
export interface ContextResolveOpts {
|
|
@@ -206,23 +211,73 @@ function schemaAtChain(
|
|
|
206
211
|
return cur && typeof cur === "object" ? cur : undefined;
|
|
207
212
|
}
|
|
208
213
|
|
|
209
|
-
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
|
|
214
|
+
/**
|
|
215
|
+
* The schema of a sibling collection expression, when statically known.
|
|
216
|
+
*
|
|
217
|
+
* Resolves `inputs.*` chains against the resource's DECLARED contract, falling
|
|
218
|
+
* back to the legacy `inputs:` property map some kinds still carry. Reading the
|
|
219
|
+
* contract is what makes this work at all for a kind that declares `inputType:`
|
|
220
|
+
* — resolving only the property map left `item` untyped in every such kind,
|
|
221
|
+
* silently, which reads as "element typing is permissive here" rather than as a
|
|
222
|
+
* gap. Returns undefined for a non-chain or untyped collection, and the caller
|
|
223
|
+
* substitutes `dyn` rather than inventing an element type.
|
|
224
|
+
*/
|
|
225
|
+
function resolveCollectionSchema(
|
|
214
226
|
manifestRoot: Record<string, any>,
|
|
215
227
|
field: string,
|
|
228
|
+
allManifests: Record<string, any>[] | undefined,
|
|
216
229
|
): Record<string, any> | undefined {
|
|
217
230
|
const chain = purePathChain(manifestRoot?.[field]);
|
|
218
231
|
if (!chain || chain[0] !== "inputs") return undefined;
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
232
|
+
const declared = resolveTypeFieldToSchema(manifestRoot.inputType, allManifests ?? []);
|
|
233
|
+
const root =
|
|
234
|
+
declared && typeof declared === "object"
|
|
235
|
+
? declared
|
|
236
|
+
: manifestRoot.inputs && typeof manifestRoot.inputs === "object"
|
|
237
|
+
? { type: "object", properties: manifestRoot.inputs }
|
|
238
|
+
: undefined;
|
|
239
|
+
if (!root) return undefined;
|
|
240
|
+
return schemaAtChain(chain.slice(1), root);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* What ITERATING a collection schema yields.
|
|
245
|
+
*
|
|
246
|
+
* An array answers with `items`; anything else answers through the value-type
|
|
247
|
+
* vocabulary, which is where "what is the element of this" is declared. No type
|
|
248
|
+
* is named here on purpose: a future iterable value type is covered by declaring
|
|
249
|
+
* `element` on one of its parameters, with nothing to change in the analyzer.
|
|
250
|
+
*/
|
|
251
|
+
function elementOfCollection(
|
|
252
|
+
collection: Record<string, any> | undefined,
|
|
253
|
+
): Record<string, any> | undefined {
|
|
254
|
+
if (!collection || typeof collection !== "object") return undefined;
|
|
255
|
+
if (collection.type === "array") {
|
|
256
|
+
return collection.items && typeof collection.items === "object"
|
|
257
|
+
? (collection.items as Record<string, any>)
|
|
258
|
+
: undefined;
|
|
224
259
|
}
|
|
225
|
-
|
|
260
|
+
const element = elementSchemaOf(collection);
|
|
261
|
+
return element && typeof element === "object" ? (element as Record<string, any>) : undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* True when a context binding naming this collection must NOT be bound.
|
|
266
|
+
*
|
|
267
|
+
* A binding that re-exposes the collection can only hand over the value the
|
|
268
|
+
* consumer is already draining, and passing that on is an ordinary
|
|
269
|
+
* pass-through no member-access rule catches — so the drain is silent. `live` is
|
|
270
|
+
* exactly the property that makes a value unsafe to re-expose, and it is already
|
|
271
|
+
* in the vocabulary, so no consumer names a type to decide this.
|
|
272
|
+
*/
|
|
273
|
+
function collectionBindingWithheld(
|
|
274
|
+
schema: Record<string, any>,
|
|
275
|
+
manifestRoot: Record<string, any>,
|
|
276
|
+
allManifests: Record<string, any>[] | undefined,
|
|
277
|
+
): boolean {
|
|
278
|
+
const from = schema?.["x-telo-context-collection-from"] as string | undefined;
|
|
279
|
+
if (!from) return false;
|
|
280
|
+
return isLiveSlot(resolveCollectionSchema(manifestRoot, from, allManifests));
|
|
226
281
|
}
|
|
227
282
|
|
|
228
283
|
/**
|
|
@@ -342,10 +397,20 @@ export function resolveContextAnnotations(
|
|
|
342
397
|
// untyped sources fall back to `dyn` so a wrong element type is never invented.
|
|
343
398
|
const elementFrom = schema["x-telo-context-element-from"] as string | undefined;
|
|
344
399
|
if (elementFrom) {
|
|
345
|
-
const items =
|
|
400
|
+
const items = elementOfCollection(resolveCollectionSchema(manifestRoot, elementFrom, allManifests));
|
|
346
401
|
return items ?? {};
|
|
347
402
|
}
|
|
348
403
|
|
|
404
|
+
// The collection itself, as opposed to its element. Typed from whatever the
|
|
405
|
+
// sibling resolves to, so an array binding keeps the precision it has today
|
|
406
|
+
// instead of degrading to `dyn` now that the slot admits more than one shape.
|
|
407
|
+
// A `live` collection never reaches here — the property is dropped before
|
|
408
|
+
// recursion, so the name is simply not in scope.
|
|
409
|
+
const collectionFrom = schema["x-telo-context-collection-from"] as string | undefined;
|
|
410
|
+
if (collectionFrom) {
|
|
411
|
+
return resolveCollectionSchema(manifestRoot, collectionFrom, allManifests) ?? {};
|
|
412
|
+
}
|
|
413
|
+
|
|
349
414
|
const fromRoot = schema["x-telo-context-from-root"] as string | undefined;
|
|
350
415
|
const fromRefKindRaw = schema["x-telo-context-from-ref-kind"] as
|
|
351
416
|
| string
|
|
@@ -445,6 +510,10 @@ export function resolveContextAnnotations(
|
|
|
445
510
|
if (schema.properties) {
|
|
446
511
|
const props: Record<string, any> = {};
|
|
447
512
|
for (const [k, v] of Object.entries(schema.properties)) {
|
|
513
|
+
// Withholding happens HERE rather than inside the child resolver, because
|
|
514
|
+
// this is the only level that owns the property map — a child can return a
|
|
515
|
+
// schema but cannot remove itself from one.
|
|
516
|
+
if (collectionBindingWithheld(v as Record<string, any>, manifestRoot, allManifests)) continue;
|
|
448
517
|
props[k] = resolveContextAnnotations(v as Record<string, any>, manifestItem, normalizedOpts);
|
|
449
518
|
}
|
|
450
519
|
return { ...schema, properties: props };
|