@telorun/analyzer 0.65.0 → 0.66.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 +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +157 -63
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +7 -1
- package/dist/cel-environment.d.ts +12 -0
- package/dist/cel-environment.d.ts.map +1 -1
- package/dist/cel-environment.js +35 -19
- package/dist/cel-scope.d.ts.map +1 -1
- package/dist/cel-scope.js +24 -4
- package/dist/definition-registry.d.ts +24 -0
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +41 -0
- package/dist/eval-paths.d.ts +68 -0
- package/dist/eval-paths.d.ts.map +1 -1
- package/dist/eval-paths.js +138 -0
- package/dist/flatten-for-analyzer.d.ts +1 -1
- package/dist/flatten-for-analyzer.js +1 -1
- package/dist/import-resolution-diagnostics.d.ts +1 -1
- package/dist/import-resolution-diagnostics.js +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -5
- package/dist/manifest-visitor.d.ts.map +1 -1
- package/dist/manifest-visitor.js +8 -1
- package/dist/migrations/entries/index.d.ts.map +1 -1
- package/dist/migrations/entries/index.js +2 -0
- package/dist/migrations/entries/schema-prepare-bucket.json +27 -0
- package/dist/peer-binding.d.ts +232 -0
- package/dist/peer-binding.d.ts.map +1 -0
- package/dist/peer-binding.js +418 -0
- package/dist/ref-slot.d.ts +13 -0
- package/dist/ref-slot.d.ts.map +1 -1
- package/dist/ref-slot.js +26 -0
- package/dist/reference-field-map.d.ts +28 -0
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +25 -0
- package/dist/referrer-rule.d.ts +17 -0
- package/dist/referrer-rule.d.ts.map +1 -1
- package/dist/referrer-rule.js +4 -1
- package/dist/release/index.d.ts +1 -1
- package/dist/release/index.js +1 -1
- package/dist/resource-rule.d.ts +40 -27
- package/dist/resource-rule.d.ts.map +1 -1
- package/dist/resource-rule.js +61 -7
- package/dist/rule-condition.d.ts +11 -0
- package/dist/rule-condition.d.ts.map +1 -1
- package/dist/rule-condition.js +13 -0
- package/dist/schema-projection.d.ts +106 -4
- package/dist/schema-projection.d.ts.map +1 -1
- package/dist/schema-projection.js +206 -18
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/validate-cel-context.d.ts +1 -17
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +5 -70
- package/dist/validate-reference-forms.d.ts +10 -0
- package/dist/validate-reference-forms.d.ts.map +1 -1
- package/dist/validate-reference-forms.js +23 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +9 -2
- package/dist/validate-referrer-rules.d.ts +38 -4
- package/dist/validate-referrer-rules.d.ts.map +1 -1
- package/dist/validate-referrer-rules.js +139 -31
- package/dist/validate-resource-rules.d.ts +11 -2
- package/dist/validate-resource-rules.d.ts.map +1 -1
- package/dist/validate-resource-rules.js +18 -12
- package/dist/validate-schema-projection.d.ts.map +1 -1
- package/dist/validate-schema-projection.js +33 -2
- package/dist/with-synthetic-positions.d.ts +1 -1
- package/dist/with-synthetic-positions.js +1 -1
- package/package.json +3 -3
- package/src/analyzer.ts +191 -68
- package/src/call-graph.ts +7 -1
- package/src/cel-environment.ts +48 -19
- package/src/cel-scope.ts +24 -6
- package/src/definition-registry.ts +42 -0
- package/src/eval-paths.ts +152 -0
- package/src/flatten-for-analyzer.ts +1 -1
- package/src/import-resolution-diagnostics.ts +1 -1
- package/src/index.ts +45 -4
- package/src/manifest-visitor.ts +8 -1
- package/src/migrations/entries/index.ts +2 -0
- package/src/migrations/entries/schema-prepare-bucket.json +27 -0
- package/src/peer-binding.ts +641 -0
- package/src/ref-slot.ts +36 -0
- package/src/reference-field-map.ts +42 -0
- package/src/referrer-rule.ts +20 -1
- package/src/release/index.ts +1 -1
- package/src/resource-rule.ts +69 -7
- package/src/rule-condition.ts +15 -0
- package/src/schema-projection.ts +283 -20
- package/src/telo-version.ts +1 -1
- package/src/validate-cel-context.ts +6 -71
- package/src/validate-reference-forms.ts +22 -0
- package/src/validate-references.ts +15 -2
- package/src/validate-referrer-rules.ts +215 -28
- package/src/validate-resource-rules.ts +26 -12
- package/src/validate-schema-projection.ts +50 -1
- package/src/with-synthetic-positions.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-projection.d.ts","sourceRoot":"","sources":["../src/schema-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;
|
|
1
|
+
{"version":3,"file":"schema-projection.d.ts","sourceRoot":"","sources":["../src/schema-projection.ts"],"names":[],"mappings":"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"}
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
* row shape lives in one instance's own `columns:`, which no definition-level
|
|
35
35
|
* derivation can reach.
|
|
36
36
|
*/
|
|
37
|
+
import { isRefSentinel } from "@telorun/templating";
|
|
37
38
|
function isObject(value) {
|
|
38
39
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
39
40
|
}
|
|
@@ -55,6 +56,20 @@ export function readSchemaProjection(definition) {
|
|
|
55
56
|
nameField: typeof raw.name === "string" ? raw.name : undefined,
|
|
56
57
|
nullable: typeof raw.nullable === "string" ? raw.nullable : undefined,
|
|
57
58
|
array: typeof raw.array === "string" ? raw.array : undefined,
|
|
59
|
+
reference: readProjectionReference(raw.reference),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function readProjectionReference(raw) {
|
|
63
|
+
if (!isObject(raw))
|
|
64
|
+
return undefined;
|
|
65
|
+
const { from, keyword, base, baseFrom } = raw;
|
|
66
|
+
if (typeof from !== "string" || typeof keyword !== "string")
|
|
67
|
+
return undefined;
|
|
68
|
+
return {
|
|
69
|
+
from,
|
|
70
|
+
keyword,
|
|
71
|
+
base: isObject(base) ? base : undefined,
|
|
72
|
+
baseFrom: typeof baseFrom === "string" ? baseFrom : undefined,
|
|
58
73
|
};
|
|
59
74
|
}
|
|
60
75
|
/** The annotation as written, from either position — the document (canonical)
|
|
@@ -79,7 +94,37 @@ export function schemaProjectionIsMisplaced(definition) {
|
|
|
79
94
|
const schema = definition.schema;
|
|
80
95
|
return isObject(schema) && schema["x-telo-schema-projection"] !== undefined;
|
|
81
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* The schema node that CARRIES the value vocabulary — the node itself, or the
|
|
99
|
+
* branch of a union that declares the map.
|
|
100
|
+
*
|
|
101
|
+
* A slot unioning a closed value vocabulary with a reference keeps its map on the
|
|
102
|
+
* value branch, exactly as the ref-slot reader peels the same union for its
|
|
103
|
+
* constraint. Exported because the strict half checks the map against the same
|
|
104
|
+
* branch's `enum`, and two implementations of "which branch is the value one"
|
|
105
|
+
* would eventually disagree — silently, since the failure of missing one is a
|
|
106
|
+
* completeness check that quietly stops running.
|
|
107
|
+
*/
|
|
108
|
+
export function schemaMapBranch(node) {
|
|
109
|
+
if (!isObject(node))
|
|
110
|
+
return undefined;
|
|
111
|
+
if (node["x-telo-schema-map"] !== undefined)
|
|
112
|
+
return node;
|
|
113
|
+
for (const key of ["oneOf", "anyOf"]) {
|
|
114
|
+
const branches = node[key];
|
|
115
|
+
if (!Array.isArray(branches))
|
|
116
|
+
continue;
|
|
117
|
+
for (const branch of branches) {
|
|
118
|
+
if (isObject(branch) && branch["x-telo-schema-map"] !== undefined)
|
|
119
|
+
return branch;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
82
124
|
export function readSchemaMap(node) {
|
|
125
|
+
return ownSchemaMap(schemaMapBranch(node));
|
|
126
|
+
}
|
|
127
|
+
function ownSchemaMap(node) {
|
|
83
128
|
if (!isObject(node))
|
|
84
129
|
return undefined;
|
|
85
130
|
const raw = node["x-telo-schema-map"];
|
|
@@ -134,6 +179,66 @@ export function projectionKeyMap(kindSchema, projection) {
|
|
|
134
179
|
return undefined;
|
|
135
180
|
return readSchemaMap(entry.properties[projection.key]);
|
|
136
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* The node an entry whose keyed field holds a REFERENCE projects to.
|
|
184
|
+
*
|
|
185
|
+
* This is the one place a projection crosses to another declaration, and it is
|
|
186
|
+
* a deliberate exception to the projection's lossiness: length, precision and
|
|
187
|
+
* collation stop at the boundary because the database enforces them, while a
|
|
188
|
+
* domain crosses because it IS the type at the granularity a consumer acts on —
|
|
189
|
+
* the enum in a CRUD model's OpenAPI operation, a completion list in the editor,
|
|
190
|
+
* a filter a repository can reject before the query.
|
|
191
|
+
*
|
|
192
|
+
* **A reference that cannot be read projects OPEN, never to nothing**, and that
|
|
193
|
+
* is the opposite of the rule an unmapped VALUE follows. The two failures are
|
|
194
|
+
* not the same failure: an unmapped value is a gap in the kind's own vocabulary,
|
|
195
|
+
* so there is no entry to speak of, while an unreadable reference names an entry
|
|
196
|
+
* the declaration plainly HAS and only leaves its type unknown. Dropping it made
|
|
197
|
+
* the projection deny the entry exists — a table whose enum reference had a typo
|
|
198
|
+
* reported `'status' is not allowed` against a column declared three lines up,
|
|
199
|
+
* blaming the seed row for the reference's mistake. Open is the honest
|
|
200
|
+
* under-approximation, and the reason is reported alongside.
|
|
201
|
+
*/
|
|
202
|
+
function referencedNode(value, entryName, projection, map, options) {
|
|
203
|
+
const reference = projection.reference;
|
|
204
|
+
if (!reference || !isObject(value))
|
|
205
|
+
return undefined;
|
|
206
|
+
// Through the single reader, so the name in the diagnostic is the one the
|
|
207
|
+
// author wrote whichever shape the slot holds — reading `value.name` here
|
|
208
|
+
// reported `<unnamed>` for an unresolved `!ref`, which is precisely the case
|
|
209
|
+
// that produces the diagnostic.
|
|
210
|
+
const name = readProjectionRef(value)?.name ?? "<unnamed>";
|
|
211
|
+
const report = () => {
|
|
212
|
+
options?.failures?.push({
|
|
213
|
+
reason: "entry-reference",
|
|
214
|
+
// The EMPTY pointer means the projected declaration is the one carrying
|
|
215
|
+
// the diagnostic, so the entry's own path is a real anchor in that file —
|
|
216
|
+
// the column, not the document root. Any other pointer names a slot
|
|
217
|
+
// holding a reference to a DIFFERENT manifest, whose entry paths mean
|
|
218
|
+
// nothing here, so the slot stays the anchor.
|
|
219
|
+
pointer: options?.pointer === ""
|
|
220
|
+
? `${projection.entries}/${entryName}`
|
|
221
|
+
: (options?.pointer ?? projection.entries),
|
|
222
|
+
entry: entryName,
|
|
223
|
+
name,
|
|
224
|
+
});
|
|
225
|
+
return {};
|
|
226
|
+
};
|
|
227
|
+
const found = options?.scope?.resolveManifest(value);
|
|
228
|
+
if (!found || "ambiguous" in found)
|
|
229
|
+
return report();
|
|
230
|
+
const values = found.manifest[reference.from];
|
|
231
|
+
if (!Array.isArray(values) || values.length === 0)
|
|
232
|
+
return report();
|
|
233
|
+
let base = reference.base;
|
|
234
|
+
if (reference.baseFrom !== undefined) {
|
|
235
|
+
const declared = found.manifest[reference.baseFrom];
|
|
236
|
+
base = typeof declared === "string" ? map[declared] : undefined;
|
|
237
|
+
}
|
|
238
|
+
if (!base)
|
|
239
|
+
return report();
|
|
240
|
+
return { ...base, [reference.keyword]: values };
|
|
241
|
+
}
|
|
137
242
|
/**
|
|
138
243
|
* Project one declaration to an object schema.
|
|
139
244
|
*
|
|
@@ -148,7 +253,12 @@ export function projectionKeyMap(kindSchema, projection) {
|
|
|
148
253
|
* enough to double as a validator would move the domain's semantics into the
|
|
149
254
|
* type layer.
|
|
150
255
|
*/
|
|
151
|
-
export function projectEntries(manifest, projection, map
|
|
256
|
+
export function projectEntries(manifest, projection, map,
|
|
257
|
+
/** What a REFERENCE at the keyed field is resolved through, and where a
|
|
258
|
+
* failure to resolve one is reported. A caller with no scope cannot resolve
|
|
259
|
+
* one, so such an entry projects OPEN — present, untyped — rather than
|
|
260
|
+
* vanishing from the row. */
|
|
261
|
+
options) {
|
|
152
262
|
const entries = navigate(manifest, projection.entries);
|
|
153
263
|
if (entries === undefined)
|
|
154
264
|
return undefined;
|
|
@@ -157,7 +267,9 @@ export function projectEntries(manifest, projection, map) {
|
|
|
157
267
|
if (!isObject(entry) || name === undefined)
|
|
158
268
|
return;
|
|
159
269
|
const key = entry[projection.key];
|
|
160
|
-
const mapped = typeof key === "string"
|
|
270
|
+
const mapped = typeof key === "string"
|
|
271
|
+
? map[key]
|
|
272
|
+
: referencedNode(key, name, projection, map, options);
|
|
161
273
|
// A value with no map entry projects to nothing rather than to `any`: the
|
|
162
274
|
// vocabulary is the kind's own enum, so an unmapped value is a gap in the
|
|
163
275
|
// kind's declaration, not a shape to guess at.
|
|
@@ -193,11 +305,26 @@ export function projectEntries(manifest, projection, map) {
|
|
|
193
305
|
additionalProperties: false,
|
|
194
306
|
};
|
|
195
307
|
}
|
|
196
|
-
/**
|
|
197
|
-
*
|
|
308
|
+
/**
|
|
309
|
+
* The `{kind, name, alias?}` reference a value holds, or undefined. Exported so
|
|
310
|
+
* a host whose slot may hold EITHER shape can fall back to this reading.
|
|
311
|
+
*
|
|
312
|
+
* The unresolved `!ref` SENTINEL is read too. `resolveRefSentinels` normally
|
|
313
|
+
* rewrites one before this pass, but not when the reference names nothing — and
|
|
314
|
+
* that is exactly when a projection failure is reported, so reading only the
|
|
315
|
+
* resolved shape made the diagnostic name the target `<unnamed>`, which is the
|
|
316
|
+
* one fact the author needed from it. A round-trip host (`compile` off) carries
|
|
317
|
+
* the sentinel for every reference, resolved or not.
|
|
318
|
+
*/
|
|
198
319
|
export function readProjectionRef(value) {
|
|
199
320
|
if (!isObject(value))
|
|
200
321
|
return undefined;
|
|
322
|
+
if (isRefSentinel(value)) {
|
|
323
|
+
const dot = value.source.indexOf(".");
|
|
324
|
+
return dot > 0
|
|
325
|
+
? { name: value.source.slice(dot + 1), alias: value.source.slice(0, dot) }
|
|
326
|
+
: { name: value.source };
|
|
327
|
+
}
|
|
201
328
|
const name = value.name;
|
|
202
329
|
if (typeof name !== "string")
|
|
203
330
|
return undefined;
|
|
@@ -258,10 +385,22 @@ function refTarget(value, scope, pointer) {
|
|
|
258
385
|
return { reason: "no-projection", pointer, kind: manifest.kind };
|
|
259
386
|
return { manifest, definition };
|
|
260
387
|
}
|
|
388
|
+
/** The declaration the annotation is written on, as a projection target. */
|
|
389
|
+
function ownTarget(manifest, scope) {
|
|
390
|
+
if (typeof manifest.kind !== "string") {
|
|
391
|
+
return { reason: "no-ref", pointer: "" };
|
|
392
|
+
}
|
|
393
|
+
const definition = scope.resolveDefinition(manifest.kind);
|
|
394
|
+
if (!definition)
|
|
395
|
+
return { reason: "no-projection", pointer: "", kind: manifest.kind };
|
|
396
|
+
return { manifest, definition };
|
|
397
|
+
}
|
|
261
398
|
export function describeProjectionFailure(failure) {
|
|
262
399
|
switch (failure.reason) {
|
|
263
400
|
case "no-ref":
|
|
264
|
-
return
|
|
401
|
+
return failure.pointer === ""
|
|
402
|
+
? "this resource declares no 'kind:', so there is no definition to project it through."
|
|
403
|
+
: `'${failure.pointer}' does not hold a reference, so there is no declaration to project.`;
|
|
265
404
|
case "unresolved":
|
|
266
405
|
return `'${failure.pointer}' references '${failure.name}', which resolves to no resource.`;
|
|
267
406
|
case "ambiguous":
|
|
@@ -270,6 +409,17 @@ export function describeProjectionFailure(failure) {
|
|
|
270
409
|
case "no-projection":
|
|
271
410
|
return (`'${failure.pointer}' references a resource of kind '${failure.kind}', which declares no ` +
|
|
272
411
|
`'x-telo-schema-projection' — so there is nothing for this slot to be typed from.`);
|
|
412
|
+
case "no-projection-map":
|
|
413
|
+
return (`kind '${failure.kind}' declares an 'x-telo-schema-projection' whose key field carries ` +
|
|
414
|
+
`no 'x-telo-schema-map', so there is no vocabulary to project its entries through and ` +
|
|
415
|
+
`'${failure.pointer || "this declaration"}' cannot be typed from it.`);
|
|
416
|
+
case "no-entries":
|
|
417
|
+
return (`'${failure.entries}' holds no entry collection on this ${failure.kind}, so the ` +
|
|
418
|
+
`projection has nothing to type '${failure.pointer || "this declaration"}' from.`);
|
|
419
|
+
case "entry-reference":
|
|
420
|
+
return (`entry '${failure.entry}' at '${failure.pointer}' references '${failure.name}', which ` +
|
|
421
|
+
`resolves to no declaration this analysis can read — so that entry is projected as an ` +
|
|
422
|
+
`open value and nothing typed from it is checked against the shape it was meant to have.`);
|
|
273
423
|
}
|
|
274
424
|
}
|
|
275
425
|
/**
|
|
@@ -282,36 +432,74 @@ export function describeProjectionFailure(failure) {
|
|
|
282
432
|
* `failures`, because degrading SILENTLY is the failure this whole mechanism
|
|
283
433
|
* exists to move earlier: the consumer's contract quietly reopens and a
|
|
284
434
|
* misspelled field passes `telo check` exactly as it did before.
|
|
435
|
+
*
|
|
436
|
+
* **A node that projected NOTHING is returned by IDENTITY**, and that is a
|
|
437
|
+
* correctness property of the caller rather than a micro-optimization:
|
|
438
|
+
* `DefinitionRegistry` memoizes a compiled AJV validator per schema OBJECT,
|
|
439
|
+
* because every resource of a kind is checked against the same one at keystroke
|
|
440
|
+
* time. Rebuilding each node unconditionally — which this did — misses that memo
|
|
441
|
+
* for every resource in the analysis, so AJV recompiled the whole kind schema
|
|
442
|
+
* once per resource: on `apps/hub` that was 197 compiles instead of 54, and 723
|
|
443
|
+
* ms instead of 97. Returning the input where nothing changed restores it for
|
|
444
|
+
* every kind that declares no projection at all, which is nearly all of them.
|
|
285
445
|
*/
|
|
286
446
|
export function resolveSchemaProjections(schema, manifest, scope, failures) {
|
|
287
447
|
if (Array.isArray(schema)) {
|
|
288
|
-
|
|
448
|
+
let moved = false;
|
|
449
|
+
const items = schema.map((item) => {
|
|
450
|
+
const next = resolveSchemaProjections(item, manifest, scope, failures);
|
|
451
|
+
if (next !== item)
|
|
452
|
+
moved = true;
|
|
453
|
+
return next;
|
|
454
|
+
});
|
|
455
|
+
return moved ? items : schema;
|
|
289
456
|
}
|
|
290
457
|
if (!isObject(schema))
|
|
291
458
|
return schema;
|
|
292
459
|
const pointer = readProjectionFrom(schema);
|
|
293
|
-
if (pointer && manifest) {
|
|
294
|
-
|
|
460
|
+
if (pointer !== undefined && manifest) {
|
|
461
|
+
// The EMPTY pointer names the declaration this annotation is written on,
|
|
462
|
+
// rather than a slot holding a reference to another. Resolution is skipped
|
|
463
|
+
// because the declaration is already in hand — which is what lets a kind
|
|
464
|
+
// type its own data against its own entries (a table's seed rows against its
|
|
465
|
+
// columns), something no reference could reach.
|
|
466
|
+
const target = pointer === ""
|
|
467
|
+
? ownTarget(manifest, scope)
|
|
468
|
+
: refTarget(navigate(manifest, pointer), scope, pointer);
|
|
295
469
|
if ("reason" in target) {
|
|
296
470
|
failures?.push(target);
|
|
297
471
|
}
|
|
298
472
|
else {
|
|
473
|
+
const kind = String(target.manifest.kind ?? "<unknown>");
|
|
299
474
|
const projection = readSchemaProjection(target.definition);
|
|
300
475
|
const map = projection && projectionKeyMap(target.definition.schema, projection);
|
|
301
|
-
const projected = projection && map
|
|
476
|
+
const projected = projection && map
|
|
477
|
+
? projectEntries(target.manifest, projection, map, { scope, pointer, failures })
|
|
478
|
+
: undefined;
|
|
302
479
|
if (projected) {
|
|
303
480
|
const { ["x-telo-schema-projection-from"]: _dropped, ...rest } = schema;
|
|
304
481
|
return { ...rest, ...projected };
|
|
305
482
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
483
|
+
// Three distinct omissions, three repairs by three different authors: the
|
|
484
|
+
// kind declares no projection, the kind declares one the key field has no
|
|
485
|
+
// vocabulary for, or this DECLARATION simply lists no entries.
|
|
486
|
+
if (!projection)
|
|
487
|
+
failures?.push({ reason: "no-projection", pointer, kind });
|
|
488
|
+
else if (!map)
|
|
489
|
+
failures?.push({ reason: "no-projection-map", pointer, kind });
|
|
490
|
+
else {
|
|
491
|
+
failures?.push({ reason: "no-entries", pointer, kind, entries: projection.entries });
|
|
492
|
+
}
|
|
311
493
|
}
|
|
312
494
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
key.startsWith("x-telo-")
|
|
316
|
-
|
|
495
|
+
let moved = false;
|
|
496
|
+
const entries = Object.entries(schema).map(([key, value]) => {
|
|
497
|
+
const next = key.startsWith("x-telo-")
|
|
498
|
+
? value
|
|
499
|
+
: resolveSchemaProjections(value, manifest, scope, failures);
|
|
500
|
+
if (next !== value)
|
|
501
|
+
moved = true;
|
|
502
|
+
return [key, next];
|
|
503
|
+
});
|
|
504
|
+
return moved ? Object.fromEntries(entries) : schema;
|
|
317
505
|
}
|
package/dist/telo-version.d.ts
CHANGED
package/dist/telo-version.js
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
// its release identity, this is the scale a module's `requires.telo` range is
|
|
6
6
|
// written against, and every kernel in every language reports the same scale.
|
|
7
7
|
/** The surface generation this analyzer implements. */
|
|
8
|
-
export const TELO_SURFACE_VERSION = "0.82.
|
|
8
|
+
export const TELO_SURFACE_VERSION = "0.82.1";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { extractAccessChains, validateChainAgainstSchema } from "@telorun/templating";
|
|
2
|
+
export { extractCelRegionScopes, pathMatchesScope } from "./eval-paths.js";
|
|
2
3
|
export interface ContextResolveOpts {
|
|
3
4
|
/** When provided, used to resolve `x-telo-context-from-root` annotations against the
|
|
4
5
|
* root manifest. When omitted, defaults to `manifestItem`. */
|
|
@@ -15,14 +16,6 @@ export interface ContextResolveOpts {
|
|
|
15
16
|
allManifests?: Record<string, any>[];
|
|
16
17
|
}
|
|
17
18
|
export declare function resolveTypeFieldToSchema(value: unknown, allManifests: Record<string, any>[], ancestry?: ReadonlySet<string>): Record<string, any> | undefined;
|
|
18
|
-
/**
|
|
19
|
-
* Returns true when a CEL expression path (from walkCelExpressions, e.g. "routes[0].inputs.q")
|
|
20
|
-
* falls within the scope of a context (e.g. "$.routes[*].inputs").
|
|
21
|
-
*
|
|
22
|
-
* The scope is matched directly (no sibling sharing): a context at "$.routes[*].inputs" only
|
|
23
|
-
* applies to expressions whose path starts with "routes[N].inputs", not to other sibling fields.
|
|
24
|
-
*/
|
|
25
|
-
export declare function pathMatchesScope(exprPath: string, scope: string): boolean;
|
|
26
19
|
/**
|
|
27
20
|
* Resolves `x-telo-context-*` annotations in a context schema using the concrete
|
|
28
21
|
* manifest item (per-scope) and the manifest root.
|
|
@@ -91,13 +84,4 @@ export declare function extractContextsFromSchema(schema: Record<string, any>, p
|
|
|
91
84
|
scope: string;
|
|
92
85
|
schema: Record<string, any>;
|
|
93
86
|
}>;
|
|
94
|
-
/**
|
|
95
|
-
* Walk a JSON Schema tree and collect the JSONPath scopes of every field that
|
|
96
|
-
* declares a CEL-bearing region (`x-telo-context` / `x-telo-step-context` /
|
|
97
|
-
* `x-telo-error-context`). Used — alongside `x-telo-eval` paths — to decide
|
|
98
|
-
* whether a `!cel` expression sits in a slot the runtime actually evaluates.
|
|
99
|
-
* Scopes use the same `$.a.b[*]` form as `extractContextsFromSchema`, matched
|
|
100
|
-
* against expression paths with `pathMatchesScope`.
|
|
101
|
-
*/
|
|
102
|
-
export declare function extractCelRegionScopes(schema: Record<string, any>, path?: string): string[];
|
|
103
87
|
//# sourceMappingURL=validate-cel-context.d.ts.map
|
|
@@ -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;AAatF,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAG3E,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CAyKrB;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"}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export { extractAccessChains, validateChainAgainstSchema } from "@telorun/templating";
|
|
2
2
|
import { elementSchemaOf, isLiveSlot, mergeTypeSchemas, parseCanonicalTypeSchemaId, } from "@telorun/sdk";
|
|
3
3
|
import { KERNEL_BUILTINS } from "./builtins.js";
|
|
4
|
-
import { isStepSlot } from "./step-slot.js";
|
|
5
4
|
import { withRefSlotsAsReadings } from "./ref-slot-reading.js";
|
|
5
|
+
// Where CEL is evaluated is one reader (`eval-paths.ts`), and the region half of
|
|
6
|
+
// it moved there so the scope walk and the `x-telo-eval` walk answer the same
|
|
7
|
+
// question in one place. Re-exported: this module is where every existing
|
|
8
|
+
// consumer imports them from.
|
|
9
|
+
export { extractCelRegionScopes, pathMatchesScope } from "./eval-paths.js";
|
|
6
10
|
/**
|
|
7
11
|
* Resolve a type field value (string name, inline type, or raw schema) to a JSON Schema.
|
|
8
12
|
* - String: look up the named type in allManifests (Type.JsonSchema resources)
|
|
@@ -229,36 +233,6 @@ function collectionBindingWithheld(schema, manifestRoot, allManifests) {
|
|
|
229
233
|
return false;
|
|
230
234
|
return isLiveSlot(resolveCollectionSchema(manifestRoot, from, allManifests));
|
|
231
235
|
}
|
|
232
|
-
/**
|
|
233
|
-
* Returns true when a CEL expression path (from walkCelExpressions, e.g. "routes[0].inputs.q")
|
|
234
|
-
* falls within the scope of a context (e.g. "$.routes[*].inputs").
|
|
235
|
-
*
|
|
236
|
-
* The scope is matched directly (no sibling sharing): a context at "$.routes[*].inputs" only
|
|
237
|
-
* applies to expressions whose path starts with "routes[N].inputs", not to other sibling fields.
|
|
238
|
-
*/
|
|
239
|
-
export function pathMatchesScope(exprPath, scope) {
|
|
240
|
-
const stripped = scope.startsWith("$.") ? scope.slice(2) : scope;
|
|
241
|
-
if (!stripped)
|
|
242
|
-
return false;
|
|
243
|
-
// Split on wildcard array segments; each [*] must match a concrete [N] in exprPath
|
|
244
|
-
const parts = stripped.split("[*]");
|
|
245
|
-
let remaining = exprPath;
|
|
246
|
-
for (let i = 0; i < parts.length; i++) {
|
|
247
|
-
const part = parts[i];
|
|
248
|
-
if (!remaining.startsWith(part))
|
|
249
|
-
return false;
|
|
250
|
-
remaining = remaining.slice(part.length);
|
|
251
|
-
if (i < parts.length - 1) {
|
|
252
|
-
// Expect a concrete array index like [0], [12], ...
|
|
253
|
-
const m = remaining.match(/^\[\d+\]/);
|
|
254
|
-
if (!m)
|
|
255
|
-
return false;
|
|
256
|
-
remaining = remaining.slice(m[0].length);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
// Expression must end here or continue into a child path
|
|
260
|
-
return remaining === "" || remaining[0] === "." || remaining[0] === "[";
|
|
261
|
-
}
|
|
262
236
|
/**
|
|
263
237
|
* Resolves `x-telo-context-*` annotations in a context schema using the concrete
|
|
264
238
|
* manifest item (per-scope) and the manifest root.
|
|
@@ -496,45 +470,6 @@ export function extractContextsFromSchema(schema, path = "$") {
|
|
|
496
470
|
const all = collectContexts(schema, path);
|
|
497
471
|
return all.sort((a, b) => b.scope.length - a.scope.length);
|
|
498
472
|
}
|
|
499
|
-
/** Schema keys that declare a CEL-bearing region: a field carrying any of these
|
|
500
|
-
* is evaluated at runtime, so a `!cel` inside it (or a descendant) is live. A
|
|
501
|
-
* STEP BODY is one too, and says so through the grammar its items point at
|
|
502
|
-
* rather than through a key — {@link isStepSlot} reads either spelling. */
|
|
503
|
-
const CEL_REGION_KEYS = [
|
|
504
|
-
"x-telo-context",
|
|
505
|
-
"x-telo-step-context",
|
|
506
|
-
"x-telo-error-context",
|
|
507
|
-
];
|
|
508
|
-
/**
|
|
509
|
-
* Walk a JSON Schema tree and collect the JSONPath scopes of every field that
|
|
510
|
-
* declares a CEL-bearing region (`x-telo-context` / `x-telo-step-context` /
|
|
511
|
-
* `x-telo-error-context`). Used — alongside `x-telo-eval` paths — to decide
|
|
512
|
-
* whether a `!cel` expression sits in a slot the runtime actually evaluates.
|
|
513
|
-
* Scopes use the same `$.a.b[*]` form as `extractContextsFromSchema`, matched
|
|
514
|
-
* against expression paths with `pathMatchesScope`.
|
|
515
|
-
*/
|
|
516
|
-
export function extractCelRegionScopes(schema, path = "$") {
|
|
517
|
-
if (!schema || typeof schema !== "object")
|
|
518
|
-
return [];
|
|
519
|
-
const out = [];
|
|
520
|
-
if (CEL_REGION_KEYS.some((k) => schema[k]) || isStepSlot(schema))
|
|
521
|
-
out.push(path);
|
|
522
|
-
if (schema.properties) {
|
|
523
|
-
for (const [key, value] of Object.entries(schema.properties)) {
|
|
524
|
-
out.push(...extractCelRegionScopes(value, `${path}.${key}`));
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
if (schema.items && typeof schema.items === "object") {
|
|
528
|
-
out.push(...extractCelRegionScopes(schema.items, `${path}[*]`));
|
|
529
|
-
}
|
|
530
|
-
for (const key of ["oneOf", "anyOf", "allOf"]) {
|
|
531
|
-
if (Array.isArray(schema[key])) {
|
|
532
|
-
for (const subschema of schema[key])
|
|
533
|
-
out.push(...extractCelRegionScopes(subschema, path));
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
return out;
|
|
537
|
-
}
|
|
538
473
|
function collectContexts(schema, path) {
|
|
539
474
|
if (!schema || typeof schema !== "object")
|
|
540
475
|
return [];
|
|
@@ -23,6 +23,16 @@ import { type AnalysisDiagnostic } from "./types.js";
|
|
|
23
23
|
* Rejected, each with an actionable diagnostic pointing at `!ref`:
|
|
24
24
|
* - the object form `{ kind, name }` (the old reference object), and
|
|
25
25
|
* - a bare string (the old name / dotted-FQN reference).
|
|
26
|
+
*
|
|
27
|
+
* **A value-or-reference union slot is the one exception**, and it is decided by
|
|
28
|
+
* the value rather than by a new spelling: where the reference constraint is a
|
|
29
|
+
* *branch* of a union (`type:` on a column, holding either a storage class or a
|
|
30
|
+
* `!ref` to a declared enum), a SCALAR is a value and is left to that branch,
|
|
31
|
+
* while an object is still checked for the removed `{kind, name}` form unless a
|
|
32
|
+
* branch describes it. The rule guards a REMOVED spelling — a bare string is
|
|
33
|
+
* simply not a reference in Telo — so admitting one takes nothing away: what it
|
|
34
|
+
* costs is the answer when an author meant a reference and wrote a bare name,
|
|
35
|
+
* which against a closed branch AJV reports as an unknown value.
|
|
26
36
|
*/
|
|
27
37
|
export declare function validateReferenceForms(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>): AnalysisDiagnostic[];
|
|
28
38
|
//# sourceMappingURL=validate-reference-forms.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-reference-forms.d.ts","sourceRoot":"","sources":["../src/validate-reference-forms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-reference-forms.d.ts","sourceRoot":"","sources":["../src/validate-reference-forms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC3C,kBAAkB,EAAE,CA0EtB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isTaggedSentinel } from "@telorun/templating";
|
|
2
2
|
import { visitManifest } from "./manifest-visitor.js";
|
|
3
|
+
import { satisfiesValueBranch } from "./reference-field-map.js";
|
|
3
4
|
import { REF_VALIDATION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
4
5
|
import { DiagnosticSeverity } from "./types.js";
|
|
5
6
|
const SOURCE = "telo-analyzer";
|
|
@@ -24,6 +25,16 @@ const SOURCE = "telo-analyzer";
|
|
|
24
25
|
* Rejected, each with an actionable diagnostic pointing at `!ref`:
|
|
25
26
|
* - the object form `{ kind, name }` (the old reference object), and
|
|
26
27
|
* - a bare string (the old name / dotted-FQN reference).
|
|
28
|
+
*
|
|
29
|
+
* **A value-or-reference union slot is the one exception**, and it is decided by
|
|
30
|
+
* the value rather than by a new spelling: where the reference constraint is a
|
|
31
|
+
* *branch* of a union (`type:` on a column, holding either a storage class or a
|
|
32
|
+
* `!ref` to a declared enum), a SCALAR is a value and is left to that branch,
|
|
33
|
+
* while an object is still checked for the removed `{kind, name}` form unless a
|
|
34
|
+
* branch describes it. The rule guards a REMOVED spelling — a bare string is
|
|
35
|
+
* simply not a reference in Telo — so admitting one takes nothing away: what it
|
|
36
|
+
* costs is the answer when an author meant a reference and wrote a bare name,
|
|
37
|
+
* which against a closed branch AJV reports as an unknown value.
|
|
27
38
|
*/
|
|
28
39
|
export function validateReferenceForms(resources, registry, aliases, aliasesByModule) {
|
|
29
40
|
if (!aliases)
|
|
@@ -37,6 +48,18 @@ export function validateReferenceForms(resources, registry, aliases, aliasesByMo
|
|
|
37
48
|
// `!ref` and `!cel`/`${{ }}` sentinels are the supported shapes.
|
|
38
49
|
if (isTaggedSentinel(value))
|
|
39
50
|
return;
|
|
51
|
+
// A SCALAR at a slot whose union has a value branch is a value, and the
|
|
52
|
+
// value branch is what judges it. Reporting a mistyped storage class as
|
|
53
|
+
// a malformed reference — "write it as '!ref txt'" — instructs the author
|
|
54
|
+
// to convert a typo into a reference, and AJV has already said what the
|
|
55
|
+
// value must be one of. The object form below is not ambiguous the same
|
|
56
|
+
// way: `{kind, name}` is the removed reference object, so it is still
|
|
57
|
+
// reported unless a branch genuinely describes that shape.
|
|
58
|
+
const hasValueBranch = (e.entry.valueBranches?.length ?? 0) > 0;
|
|
59
|
+
if (hasValueBranch && typeof value !== "object")
|
|
60
|
+
return;
|
|
61
|
+
if (satisfiesValueBranch(value, e.entry.valueBranches, registry))
|
|
62
|
+
return;
|
|
40
63
|
const r = e.source;
|
|
41
64
|
const resourceLabel = `${r.kind}/${r.metadata.name}`;
|
|
42
65
|
const resourceData = { kind: r.kind, name: r.metadata.name };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAarD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAuD/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA2gBtB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isRefSentinel } from "@telorun/templating";
|
|
2
2
|
import { visitManifest } from "./manifest-visitor.js";
|
|
3
|
-
import { isInlineResource, resolveFieldEntries, resolveFieldValues } from "./reference-field-map.js";
|
|
3
|
+
import { isInlineResource, resolveFieldEntries, resolveFieldValues, satisfiesValueBranch, } from "./reference-field-map.js";
|
|
4
4
|
import { navigateJsonPointer, substituteCelFields } from "./schema-compat.js";
|
|
5
5
|
import { REF_VALIDATION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
6
6
|
import { resolveTypeFieldToSchema } from "./validate-cel-context.js";
|
|
@@ -91,7 +91,7 @@ export function validateReferences(resources, context) {
|
|
|
91
91
|
// Group manifests by name to detect collisions. Two subtleties:
|
|
92
92
|
//
|
|
93
93
|
// 1. Some analyzer hosts emit the SAME physical document twice through
|
|
94
|
-
// their pipeline — e.g.
|
|
94
|
+
// their pipeline — e.g. telo studio's `toAnalysisManifests` walks
|
|
95
95
|
// each workspace module's documents independently, and a file
|
|
96
96
|
// reachable from two angles (entry module + `include:` partial)
|
|
97
97
|
// shows up twice. The fingerprint includes `sourceLine` so identical
|
|
@@ -278,6 +278,13 @@ export function validateReferences(resources, context) {
|
|
|
278
278
|
if (typeof val !== "object")
|
|
279
279
|
return;
|
|
280
280
|
const refVal = val;
|
|
281
|
+
// A value the slot's own union describes is a value, not a reference —
|
|
282
|
+
// the same narrowing `validateReferenceForms` applies, through the same
|
|
283
|
+
// function, because it has to be applied here too: an OBJECT-shaped value
|
|
284
|
+
// branch would otherwise reach the structural check below and be reported
|
|
285
|
+
// as a reference missing 'kind' and 'name'.
|
|
286
|
+
if (satisfiesValueBranch(val, entry.valueBranches, registry))
|
|
287
|
+
return;
|
|
281
288
|
// Skip inline resources — Phase 2 normalization hasn't run yet.
|
|
282
289
|
if (isInlineResource(refVal))
|
|
283
290
|
return;
|