@telorun/analyzer 0.72.0 → 0.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +76 -20
- package/dist/artifact-axes.d.ts +14 -0
- package/dist/artifact-axes.d.ts.map +1 -0
- package/dist/artifact-axes.js +12 -0
- package/dist/artifact-layer-index.d.ts +2 -2
- package/dist/artifact-layer-index.d.ts.map +1 -1
- package/dist/artifact-layer-index.js +9 -4
- package/dist/artifact-selector.d.ts +47 -21
- package/dist/artifact-selector.d.ts.map +1 -1
- package/dist/artifact-selector.js +85 -16
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +144 -113
- package/dist/call-graph.d.ts +12 -1
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +22 -0
- package/dist/catch-scope.js +1 -1
- package/dist/cel-scope-query.d.ts.map +1 -1
- package/dist/cel-scope-query.js +32 -11
- package/dist/cel-scope.d.ts.map +1 -1
- package/dist/cel-scope.js +1 -0
- package/dist/eval-paths.d.ts +36 -5
- package/dist/eval-paths.d.ts.map +1 -1
- package/dist/eval-paths.js +47 -6
- package/dist/extends-resolution.d.ts +12 -0
- package/dist/extends-resolution.d.ts.map +1 -1
- package/dist/extends-resolution.js +23 -0
- package/dist/forwarded-declaration.d.ts +26 -0
- package/dist/forwarded-declaration.d.ts.map +1 -0
- package/dist/forwarded-declaration.js +31 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/kernel-globals.d.ts.map +1 -1
- package/dist/kernel-globals.js +21 -5
- package/dist/module-alias-scope.d.ts +11 -0
- package/dist/module-alias-scope.d.ts.map +1 -1
- package/dist/module-alias-scope.js +10 -0
- package/dist/module-file-claims.d.ts +17 -1
- package/dist/module-file-claims.d.ts.map +1 -1
- package/dist/module-file-claims.js +38 -3
- package/dist/module-named-files.d.ts +72 -0
- package/dist/module-named-files.d.ts.map +1 -0
- package/dist/module-named-files.js +115 -0
- package/dist/native-entries.d.ts +83 -0
- package/dist/native-entries.d.ts.map +1 -0
- package/dist/native-entries.js +169 -0
- package/dist/nearest-name.d.ts +22 -0
- package/dist/nearest-name.d.ts.map +1 -0
- package/dist/nearest-name.js +43 -0
- package/dist/normalize-inline-resources.d.ts +41 -13
- package/dist/normalize-inline-resources.d.ts.map +1 -1
- package/dist/normalize-inline-resources.js +236 -54
- package/dist/reference-field-map.d.ts +59 -1
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +200 -34
- package/dist/release/destinations.d.ts +59 -0
- package/dist/release/destinations.d.ts.map +1 -0
- package/dist/release/destinations.js +75 -0
- package/dist/release/index.d.ts +6 -2
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +3 -1
- package/dist/release/ledger.d.ts +20 -11
- package/dist/release/ledger.d.ts.map +1 -1
- package/dist/release/ledger.js +43 -14
- package/dist/release/payload-digest.d.ts +2 -1
- package/dist/release/payload-digest.d.ts.map +1 -1
- package/dist/release/payload-digest.js +2 -1
- package/dist/release/release-plan.d.ts +3 -2
- package/dist/release/release-plan.d.ts.map +1 -1
- package/dist/release/release-plan.js +12 -19
- package/dist/release/workspace-config.d.ts +114 -34
- package/dist/release/workspace-config.d.ts.map +1 -1
- package/dist/release/workspace-config.js +322 -38
- package/dist/release/workspace-schema.d.ts +41 -0
- package/dist/release/workspace-schema.d.ts.map +1 -0
- package/dist/release/workspace-schema.js +77 -0
- package/dist/resolve-ref-sentinels.d.ts.map +1 -1
- package/dist/resolve-ref-sentinels.js +8 -5
- package/dist/resolve-throws-union.d.ts +25 -4
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +62 -32
- package/dist/resolve-zone-requirements.d.ts.map +1 -1
- package/dist/resolve-zone-requirements.js +2 -1
- package/dist/rewrite-synthetic-origins.d.ts +6 -1
- package/dist/rewrite-synthetic-origins.d.ts.map +1 -1
- package/dist/rewrite-synthetic-origins.js +107 -13
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +18 -5
- package/dist/schema-error-report.d.ts +7 -0
- package/dist/schema-error-report.d.ts.map +1 -1
- package/dist/schema-error-report.js +9 -4
- package/dist/schema-walk.d.ts +47 -13
- package/dist/schema-walk.d.ts.map +1 -1
- package/dist/schema-walk.js +150 -30
- package/dist/scope-declarations.d.ts +59 -0
- package/dist/scope-declarations.d.ts.map +1 -0
- package/dist/scope-declarations.js +45 -0
- package/dist/source-entries.d.ts +119 -0
- package/dist/source-entries.d.ts.map +1 -0
- package/dist/source-entries.js +349 -0
- package/dist/step-bodies.d.ts +24 -0
- package/dist/step-bodies.d.ts.map +1 -0
- package/dist/step-bodies.js +28 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/template-body.d.ts +10 -0
- package/dist/template-body.d.ts.map +1 -1
- package/dist/template-body.js +25 -0
- package/dist/validate-cel-context.d.ts +23 -8
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +51 -18
- package/dist/validate-exports.d.ts +28 -0
- package/dist/validate-exports.d.ts.map +1 -0
- package/dist/validate-exports.js +146 -0
- package/dist/validate-extends.d.ts.map +1 -1
- package/dist/validate-extends.js +37 -1
- package/dist/validate-module-artifact.d.ts +4 -2
- package/dist/validate-module-artifact.d.ts.map +1 -1
- package/dist/validate-module-artifact.js +43 -4
- package/dist/validate-native-entries.d.ts +15 -0
- package/dist/validate-native-entries.d.ts.map +1 -0
- package/dist/validate-native-entries.js +103 -0
- package/dist/validate-observed-state.d.ts +7 -1
- package/dist/validate-observed-state.d.ts.map +1 -1
- package/dist/validate-observed-state.js +24 -6
- package/dist/validate-provider-coherence.d.ts +4 -8
- package/dist/validate-provider-coherence.d.ts.map +1 -1
- package/dist/validate-provider-coherence.js +12 -123
- package/dist/validate-reference-forms.d.ts.map +1 -1
- package/dist/validate-reference-forms.js +2 -2
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +152 -65
- package/dist/validate-scope-reach.d.ts +31 -0
- package/dist/validate-scope-reach.d.ts.map +1 -0
- package/dist/validate-scope-reach.js +180 -0
- package/dist/validate-source-entries.d.ts +16 -0
- package/dist/validate-source-entries.d.ts.map +1 -0
- package/dist/validate-source-entries.js +90 -0
- package/dist/validate-template-body.d.ts +36 -0
- package/dist/validate-template-body.d.ts.map +1 -0
- package/dist/validate-template-body.js +246 -0
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +21 -39
- package/dist/value-type-keyword.d.ts +22 -5
- package/dist/value-type-keyword.d.ts.map +1 -1
- package/dist/value-type-keyword.js +16 -7
- package/package.json +4 -3
- package/src/analyzer.ts +97 -20
- package/src/artifact-axes.ts +24 -0
- package/src/artifact-layer-index.ts +9 -6
- package/src/artifact-selector.ts +110 -35
- package/src/builtins.ts +160 -113
- package/src/call-graph.ts +30 -0
- package/src/catch-scope.ts +1 -1
- package/src/cel-scope-query.ts +28 -9
- package/src/cel-scope.ts +1 -0
- package/src/eval-paths.ts +53 -6
- package/src/extends-resolution.ts +24 -0
- package/src/forwarded-declaration.ts +32 -0
- package/src/index.ts +37 -1
- package/src/kernel-globals.ts +28 -5
- package/src/migrations/CLAUDE.md +35 -0
- package/src/module-alias-scope.ts +17 -0
- package/src/module-file-claims.ts +63 -4
- package/src/module-named-files.ts +162 -0
- package/src/native-entries.ts +227 -0
- package/src/nearest-name.ts +47 -0
- package/src/normalize-inline-resources.ts +326 -70
- package/src/reference-field-map.ts +257 -52
- package/src/release/CLAUDE.md +32 -0
- package/src/release/destinations.ts +105 -0
- package/src/release/index.ts +33 -2
- package/src/release/ledger.ts +51 -21
- package/src/release/payload-digest.ts +2 -1
- package/src/release/release-plan.ts +16 -26
- package/src/release/workspace-config.ts +483 -47
- package/src/release/workspace-schema.ts +99 -0
- package/src/resolve-ref-sentinels.ts +7 -6
- package/src/resolve-throws-union.ts +87 -32
- package/src/resolve-zone-requirements.ts +2 -1
- package/src/rewrite-synthetic-origins.ts +107 -16
- package/src/schema-compat.ts +18 -4
- package/src/schema-error-report.ts +16 -4
- package/src/schema-walk.ts +162 -34
- package/src/scope-declarations.ts +98 -0
- package/src/source-entries.ts +502 -0
- package/src/step-bodies.ts +55 -0
- package/src/telo-version.ts +1 -1
- package/src/template-body.ts +25 -0
- package/src/validate-cel-context.ts +60 -18
- package/src/validate-exports.ts +185 -0
- package/src/validate-extends.ts +43 -0
- package/src/validate-module-artifact.ts +46 -3
- package/src/validate-native-entries.ts +147 -0
- package/src/validate-observed-state.ts +36 -7
- package/src/validate-provider-coherence.ts +17 -127
- package/src/validate-reference-forms.ts +2 -3
- package/src/validate-references.ts +151 -66
- package/src/validate-scope-reach.ts +213 -0
- package/src/validate-source-entries.ts +120 -0
- package/src/validate-template-body.ts +274 -0
- package/src/validate-throws-coverage.ts +24 -31
- package/src/value-type-keyword.ts +35 -9
- package/dist/validate-template-dispatch.d.ts +0 -27
- package/dist/validate-template-dispatch.d.ts.map +0 -1
- package/dist/validate-template-dispatch.js +0 -95
- package/src/validate-template-dispatch.ts +0 -99
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type ReferenceFieldMap,
|
|
8
8
|
} from "./reference-field-map.js";
|
|
9
9
|
import { REF_RESOLUTION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
10
|
+
import { isForwardedDeclaration, isForwardedExport } from "./forwarded-declaration.js";
|
|
10
11
|
|
|
11
12
|
/** The slice of the definition registry this pass needs: a kind's field map, from
|
|
12
13
|
* which the `x-telo-scope` slots are read. */
|
|
@@ -73,10 +74,10 @@ export function resolveRefSentinels(
|
|
|
73
74
|
): void {
|
|
74
75
|
const moduleOf = (r: ResourceManifest): string | undefined =>
|
|
75
76
|
(r.metadata as { module?: string } | undefined)?.module;
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
// A dependency's code — a forwarded export, or what extraction pulled out of one — is
|
|
78
|
+
// never walked as a local ref source here and never a local name; only an EXPORT is a
|
|
79
|
+
// cross-module resolution target.
|
|
80
|
+
const isForeign = isForwardedDeclaration;
|
|
80
81
|
|
|
81
82
|
// Local resources resolve a bare / `Self.`-qualified name; forwarded foreign exports
|
|
82
83
|
// resolve an `Alias.`-qualified name keyed by (module, name).
|
|
@@ -86,13 +87,13 @@ export function resolveRefSentinels(
|
|
|
86
87
|
if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind)) continue;
|
|
87
88
|
const name = r.metadata.name as string;
|
|
88
89
|
if (isForeign(r)) {
|
|
89
|
-
byModuleName.set(`${moduleOf(r)}\0${name}`, r);
|
|
90
|
+
if (isForwardedExport(r)) byModuleName.set(`${moduleOf(r)}\0${name}`, r);
|
|
90
91
|
} else {
|
|
91
92
|
byName.set(name, r);
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
for (const r of crossModuleTargets) {
|
|
95
|
-
if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind) || !
|
|
96
|
+
if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind) || !isForwardedExport(r)) continue;
|
|
96
97
|
byModuleName.set(`${moduleOf(r)}\0${r.metadata.name as string}`, r);
|
|
97
98
|
}
|
|
98
99
|
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
|
|
2
2
|
import { isTaggedSentinel } from "@telorun/templating";
|
|
3
3
|
import { scopeResolverForModule, type AliasResolver } from "./alias-resolver.js";
|
|
4
|
-
import { resolveScopedName } from "./call-graph.js";
|
|
4
|
+
import { resolveScopedName, resolveSlotUseAt } from "./call-graph.js";
|
|
5
5
|
import { refSentinelTarget, type RefSentinelTarget } from "./ref-sentinel-target.js";
|
|
6
6
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
hasDeclaredUse,
|
|
9
|
+
possibleUses,
|
|
10
|
+
transfersControl,
|
|
11
|
+
type RefSlot,
|
|
12
|
+
type RefUse,
|
|
13
|
+
} from "./ref-slot.js";
|
|
9
14
|
import { forEachDrivenSlot } from "./schema-walk.js";
|
|
15
|
+
import type { StepSlot } from "./step-slot.js";
|
|
10
16
|
|
|
11
17
|
export interface ThrowsCodeMeta {
|
|
12
18
|
data?: Record<string, any>;
|
|
@@ -207,10 +213,10 @@ function codesFromDefinition(definition: ResourceDefinition): Map<string, Throws
|
|
|
207
213
|
}
|
|
208
214
|
|
|
209
215
|
/** Resolve the effective throw union for a named manifest. The result combines
|
|
210
|
-
* explicit `throws.codes`, `throws.inherit: true` dataflow (step
|
|
211
|
-
*
|
|
212
|
-
* unresolvable passthrough call sites. Cycles
|
|
213
|
-
* result so resolution always terminates. */
|
|
216
|
+
* explicit `throws.codes`, `throws.inherit: true` dataflow (step bodies with
|
|
217
|
+
* try/catch subtraction, plus reference slots that hand a failure back), and
|
|
218
|
+
* unbounded markers for unresolvable passthrough call sites. Cycles
|
|
219
|
+
* short-circuit to an empty result so resolution always terminates. */
|
|
214
220
|
export function resolveThrowsUnion(
|
|
215
221
|
manifest: ResourceManifest,
|
|
216
222
|
ctx: ResolveCtx,
|
|
@@ -266,14 +272,42 @@ export function resolveThrowsUnion(
|
|
|
266
272
|
}
|
|
267
273
|
|
|
268
274
|
/**
|
|
269
|
-
*
|
|
275
|
+
* True for a use through which a target's failure reaches the declaring
|
|
276
|
+
* resource's CALLER: `call` returns into my invocation, and `trigger.consumer`
|
|
277
|
+
* rejects the value my caller drains. `detached` and `trigger.inbound` run where
|
|
278
|
+
* no caller of mine awaits them; `dependency` and `schema` dispatch nothing.
|
|
270
279
|
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
280
|
+
* The reduction `inherit` is read through — by the resolver and by the check
|
|
281
|
+
* that a definition declaring it has something to inherit from.
|
|
282
|
+
*/
|
|
283
|
+
export function handsFailureBack(use: RefUse): boolean {
|
|
284
|
+
return use === "call" || use === "trigger.consumer";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The uses a throws consumer reads off a slot. A slot that declares no use —
|
|
289
|
+
* the legacy bare-string form — reads as `call`: the throws union must assume
|
|
290
|
+
* `call` to keep an error path, the direction the call graph takes for the same
|
|
291
|
+
* slot. The zone projection reads it the opposite way, deliberately.
|
|
292
|
+
*
|
|
293
|
+
* `uses` defaults to every use the slot can take; a consumer that resolved the
|
|
294
|
+
* slot's case map for one instance passes that answer instead.
|
|
295
|
+
*/
|
|
296
|
+
export function throwsUses(
|
|
297
|
+
slot: RefSlot,
|
|
298
|
+
uses: readonly RefUse[] = possibleUses(slot),
|
|
299
|
+
): readonly RefUse[] {
|
|
300
|
+
return hasDeclaredUse(slot) ? uses : ["call"];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* `throws.inherit: true` — the union of what the resource dispatches: its step
|
|
305
|
+
* bodies (try/catch subtraction included) and every reference slot whose use,
|
|
306
|
+
* resolved for THIS instance, hands a failure back ({@link handsFailureBack}).
|
|
307
|
+
*
|
|
308
|
+
* Only on a kind that DECLARES `inherit`: it is a claim that the kind lets what
|
|
309
|
+
* it dispatches escape, and a kind catching a slot internally does not make it.
|
|
310
|
+
* What a CATCH SCOPE encloses is a different question, answered below.
|
|
277
311
|
*/
|
|
278
312
|
function resolveInherited(
|
|
279
313
|
manifest: ResourceManifest,
|
|
@@ -282,16 +316,23 @@ function resolveInherited(
|
|
|
282
316
|
ownerModule: string | undefined,
|
|
283
317
|
): ThrowsUnion {
|
|
284
318
|
const result: ThrowsUnion = { codes: new Map(), unbounded: false };
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
319
|
+
const schema = definition.schema as Record<string, any> | undefined;
|
|
320
|
+
|
|
321
|
+
forEachDrivenSlot(schema, manifest, (driven) => {
|
|
322
|
+
if (driven.kind === "step") {
|
|
323
|
+
unionInto(result, stepSiteThrows(driven.data, driven.slots, ctx, ownerModule));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
// Several slots at one site are the branches declaring it; any one that
|
|
327
|
+
// hands a failure back counts, since a branch not applying can only add codes.
|
|
328
|
+
const handsBack = driven.slots.some(({ slot, fieldPath }) =>
|
|
329
|
+
throwsUses(
|
|
330
|
+
slot,
|
|
331
|
+
resolveSlotUseAt(slot, manifest, schema, driven.path, fieldPath).use,
|
|
332
|
+
).some(handsFailureBack),
|
|
333
|
+
);
|
|
334
|
+
if (handsBack) unionInto(result, resolveRefTargetThrows(driven.data, ctx, ownerModule));
|
|
335
|
+
});
|
|
295
336
|
|
|
296
337
|
return result;
|
|
297
338
|
}
|
|
@@ -327,13 +368,15 @@ export function resolveScopeUnion(
|
|
|
327
368
|
|
|
328
369
|
forEachDrivenSlot(definition.schema, manifest, (driven) => {
|
|
329
370
|
if (driven.kind === "step") {
|
|
330
|
-
unionInto(
|
|
331
|
-
result,
|
|
332
|
-
collectStepArrayThrows(driven.data, driven.slot.invoke, undefined, ctx, ownerModule),
|
|
333
|
-
);
|
|
371
|
+
unionInto(result, stepSiteThrows(driven.data, driven.slots, ctx, ownerModule));
|
|
334
372
|
return;
|
|
335
373
|
}
|
|
336
|
-
if (
|
|
374
|
+
if (
|
|
375
|
+
driven.slots.some(({ slot }) => !slot.throwsThrough && throwsUses(slot).some(transfersControl))
|
|
376
|
+
) {
|
|
377
|
+
unionInto(result, resolveRefTargetThrows(driven.data, ctx, ownerModule));
|
|
378
|
+
}
|
|
379
|
+
if (driven.slots.some(({ slot }) => slot.throwsThrough)) {
|
|
337
380
|
const target = resolveRefManifest(driven.data, ctx, ownerModule);
|
|
338
381
|
const targetDef = target
|
|
339
382
|
? definitionFor(
|
|
@@ -347,15 +390,27 @@ export function resolveScopeUnion(
|
|
|
347
390
|
// A target that cannot be resolved says nothing about what it throws, so
|
|
348
391
|
// the scope's union is no longer enumerable.
|
|
349
392
|
else result.unbounded = true;
|
|
350
|
-
return;
|
|
351
393
|
}
|
|
352
|
-
if (!possibleUses(driven.slot).some(transfersControl)) return;
|
|
353
|
-
unionInto(result, resolveRefTargetThrows(driven.data, ctx, ownerModule));
|
|
354
394
|
});
|
|
355
395
|
|
|
356
396
|
return result;
|
|
357
397
|
}
|
|
358
398
|
|
|
399
|
+
/** The union a step-body site reaches — once per distinct invoke field, since
|
|
400
|
+
* several branches may declare a body at one site. */
|
|
401
|
+
function stepSiteThrows(
|
|
402
|
+
steps: unknown[],
|
|
403
|
+
slots: StepSlot[],
|
|
404
|
+
ctx: ResolveCtx,
|
|
405
|
+
ownerModule: string | undefined,
|
|
406
|
+
): ThrowsUnion {
|
|
407
|
+
const result = emptyUnion();
|
|
408
|
+
for (const invoke of new Set(slots.map((slot) => slot.invoke))) {
|
|
409
|
+
unionInto(result, collectStepArrayThrows(steps, invoke, undefined, ctx, ownerModule));
|
|
410
|
+
}
|
|
411
|
+
return result;
|
|
412
|
+
}
|
|
413
|
+
|
|
359
414
|
function collectStepArrayThrows(
|
|
360
415
|
steps: unknown[],
|
|
361
416
|
invokeField: string,
|
|
@@ -32,6 +32,7 @@ import type { ModuleDocuments } from "./module-documents.js";
|
|
|
32
32
|
import { readProvidesZone, readRequiresZone } from "./zone-slot.js";
|
|
33
33
|
import { DiagnosticSeverity, type AnalysisDiagnostic } from "./types.js";
|
|
34
34
|
import { moduleAliasScope } from "./module-alias-scope.js";
|
|
35
|
+
import { isForwardedDeclaration } from "./forwarded-declaration.js";
|
|
35
36
|
|
|
36
37
|
const SOURCE = "telo-analyzer";
|
|
37
38
|
|
|
@@ -616,7 +617,7 @@ export function projectZoneRequirements(args: ProjectionArgs): ProjectionResult
|
|
|
616
617
|
// (with the internal graph in hand) and seeded below — deriving them here
|
|
617
618
|
// against the flattened view would resolve correlation against a graph
|
|
618
619
|
// that no longer holds the library's internals.
|
|
619
|
-
if (
|
|
620
|
+
if (isForwardedDeclaration(node.manifest)) continue;
|
|
620
621
|
const def = resolveDef(node.kind, meta?.module);
|
|
621
622
|
const schema = def?.schema as Record<string, any> | undefined;
|
|
622
623
|
if (!def || !schema) continue;
|
|
@@ -7,9 +7,9 @@ interface XTeloOrigin {
|
|
|
7
7
|
pathFromParent: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
function readOrigin(manifest:
|
|
11
|
-
if (!manifest) return undefined;
|
|
12
|
-
const origin = (manifest
|
|
10
|
+
function readOrigin(manifest: unknown): XTeloOrigin | undefined {
|
|
11
|
+
if (!manifest || typeof manifest !== "object") return undefined;
|
|
12
|
+
const origin = (manifest as { metadata?: { xTeloOrigin?: XTeloOrigin } }).metadata?.xTeloOrigin;
|
|
13
13
|
if (
|
|
14
14
|
!origin ||
|
|
15
15
|
typeof origin.parentKind !== "string" ||
|
|
@@ -26,7 +26,12 @@ function readOrigin(manifest: ResourceManifest | undefined): XTeloOrigin | undef
|
|
|
26
26
|
* `data.resource`, which has no YAML source. Rewrite each such diagnostic
|
|
27
27
|
* back to the chain root: walk up `metadata.xTeloOrigin` until a manifest
|
|
28
28
|
* with no origin is reached, and prepend each hop's `pathFromParent` to
|
|
29
|
-
* `data.path` so position-index lookups against the root doc resolve.
|
|
29
|
+
* `data.path` so position-index lookups against the root doc resolve.
|
|
30
|
+
*
|
|
31
|
+
* An extraction from a scope member lives INSIDE the scope array rather than at
|
|
32
|
+
* the top level, so a diagnostic about it names the top-level owner and a path
|
|
33
|
+
* through an array index the author never wrote (`with[3]`). That prefix is
|
|
34
|
+
* unfolded the same way, into the position the declaration was written at. */
|
|
30
35
|
export function rewriteSyntheticOrigins(
|
|
31
36
|
diagnostics: AnalysisDiagnostic[],
|
|
32
37
|
manifests: ResourceManifest[],
|
|
@@ -45,31 +50,117 @@ export function rewriteSyntheticOrigins(
|
|
|
45
50
|
|
|
46
51
|
let current = byName.get(data.resource.name);
|
|
47
52
|
let origin = readOrigin(current);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
let
|
|
51
|
-
let rootKind: string = origin.parentKind;
|
|
52
|
-
let rootName: string = origin.parentName;
|
|
53
|
+
const path = typeof data.path === "string" ? data.path : "";
|
|
54
|
+
let accumPath = path;
|
|
55
|
+
let root: { kind?: string; name?: string } = data.resource;
|
|
53
56
|
|
|
54
57
|
while (origin) {
|
|
55
58
|
accumPath = accumPath ? `${origin.pathFromParent}.${accumPath}` : origin.pathFromParent;
|
|
56
|
-
|
|
57
|
-
rootName = origin.parentName;
|
|
59
|
+
root = { kind: origin.parentKind, name: origin.parentName };
|
|
58
60
|
current = byName.get(origin.parentName);
|
|
59
61
|
origin = readOrigin(current);
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
+
const rerouted = root !== data.resource;
|
|
65
|
+
const unfolded = current ? unfoldScopedOrigins(current, accumPath) : accumPath;
|
|
66
|
+
if (!rerouted && unfolded === path) return d;
|
|
64
67
|
|
|
65
68
|
return {
|
|
66
69
|
...d,
|
|
67
70
|
data: {
|
|
68
71
|
...data,
|
|
69
|
-
resource:
|
|
70
|
-
filePath:
|
|
71
|
-
|
|
72
|
+
resource: root,
|
|
73
|
+
filePath: rerouted
|
|
74
|
+
? ((current?.metadata as { source?: string } | undefined)?.source ?? data.filePath)
|
|
75
|
+
: data.filePath,
|
|
76
|
+
path: unfolded,
|
|
72
77
|
},
|
|
73
78
|
};
|
|
74
79
|
});
|
|
75
80
|
}
|
|
81
|
+
|
|
82
|
+
type Segment = string | number;
|
|
83
|
+
|
|
84
|
+
/** Replace every scope-array index that holds an extraction with the position
|
|
85
|
+
* the extraction was written at, until none is left. */
|
|
86
|
+
function unfoldScopedOrigins(root: ResourceManifest, path: string): string {
|
|
87
|
+
let segments = parseSegments(path);
|
|
88
|
+
let unfoldedAny = false;
|
|
89
|
+
// Each unfolding replaces one extraction with its parent, which is strictly
|
|
90
|
+
// closer to what the author wrote, so the chain is as long as the nesting.
|
|
91
|
+
for (let hops = 0; hops <= segments.length + 16; hops++) {
|
|
92
|
+
const next = unfoldOnce(root, segments);
|
|
93
|
+
if (!next) break;
|
|
94
|
+
segments = next;
|
|
95
|
+
unfoldedAny = true;
|
|
96
|
+
}
|
|
97
|
+
// Re-formatting is lossy (an empty segment in `entries../x` is dropped), so a
|
|
98
|
+
// path nothing unfolded is returned as written.
|
|
99
|
+
return unfoldedAny ? formatSegments(segments) : path;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function unfoldOnce(root: ResourceManifest, segments: Segment[]): Segment[] | undefined {
|
|
103
|
+
let node: unknown = root;
|
|
104
|
+
for (let i = 0; i < segments.length; i++) {
|
|
105
|
+
const segment = segments[i];
|
|
106
|
+
const container = node;
|
|
107
|
+
node =
|
|
108
|
+
typeof segment === "number"
|
|
109
|
+
? Array.isArray(container)
|
|
110
|
+
? container[segment]
|
|
111
|
+
: undefined
|
|
112
|
+
: container && typeof container === "object" && !Array.isArray(container)
|
|
113
|
+
? (container as Record<string, unknown>)[segment]
|
|
114
|
+
: undefined;
|
|
115
|
+
if (node === undefined || node === null) return undefined;
|
|
116
|
+
if (typeof segment !== "number" || i === 0) continue;
|
|
117
|
+
const origin = readOrigin(node);
|
|
118
|
+
if (!origin) continue;
|
|
119
|
+
|
|
120
|
+
// Its parent is either the resource holding this scope array (a slot of
|
|
121
|
+
// the resource that declares the scope) or a sibling member of the array.
|
|
122
|
+
const holderPath = segments.slice(0, i - 1);
|
|
123
|
+
let parentPath: Segment[] | undefined;
|
|
124
|
+
if (nameOf(at(root, holderPath)) === origin.parentName) {
|
|
125
|
+
parentPath = holderPath;
|
|
126
|
+
} else {
|
|
127
|
+
const sibling = (container as unknown[]).findIndex((m) => nameOf(m) === origin.parentName);
|
|
128
|
+
if (sibling >= 0) parentPath = [...segments.slice(0, i), sibling];
|
|
129
|
+
}
|
|
130
|
+
if (!parentPath) return undefined;
|
|
131
|
+
return [...parentPath, ...parseSegments(origin.pathFromParent), ...segments.slice(i + 1)];
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function at(root: unknown, segments: Segment[]): unknown {
|
|
137
|
+
let node = root;
|
|
138
|
+
for (const segment of segments) {
|
|
139
|
+
if (!node || typeof node !== "object") return undefined;
|
|
140
|
+
node = (node as Record<string | number, unknown>)[segment];
|
|
141
|
+
}
|
|
142
|
+
return node;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function nameOf(value: unknown): string | undefined {
|
|
146
|
+
const name = (value as { metadata?: { name?: unknown } } | undefined)?.metadata?.name;
|
|
147
|
+
return typeof name === "string" ? name : undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** `with[3].steps[0].invoke` → `["with", 3, "steps", 0, "invoke"]`. */
|
|
151
|
+
function parseSegments(path: string): Segment[] {
|
|
152
|
+
const out: Segment[] = [];
|
|
153
|
+
for (const match of path.matchAll(/\[(\d+)\]|([^.[\]]+)/g)) {
|
|
154
|
+
out.push(match[1] !== undefined ? Number(match[1]) : match[2]!);
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function formatSegments(segments: Segment[]): string {
|
|
160
|
+
let out = "";
|
|
161
|
+
for (const segment of segments) {
|
|
162
|
+
if (typeof segment === "number") out += `[${segment}]`;
|
|
163
|
+
else out += out ? `.${segment}` : segment;
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
package/src/schema-compat.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
celTypeOfValueType,
|
|
11
11
|
isCompiledValue,
|
|
12
12
|
readValueTypeSlot,
|
|
13
|
+
VALUE_TYPE_BINDINGS,
|
|
13
14
|
valueBrandBases,
|
|
14
15
|
valueTypeOf,
|
|
15
16
|
valueTypePlaceholder,
|
|
@@ -36,8 +37,9 @@ export function createAjv(): InstanceType<typeof Ajv> {
|
|
|
36
37
|
// One registration site for every Telo keyword — the annotations as no-ops and
|
|
37
38
|
// `x-telo-type` as the one that checks. Registered here and in the kernel's
|
|
38
39
|
// validators from one definition, so a literal at an instance-typed slot is
|
|
39
|
-
// rejected statically and at dispatch by the identical rule.
|
|
40
|
-
|
|
40
|
+
// rejected statically and at dispatch by the identical rule. This instance is
|
|
41
|
+
// static analysis alone, so it also asserts a `live` type: no literal can be one.
|
|
42
|
+
registerTeloKeywords(instance, { assertLive: true });
|
|
41
43
|
instance.addSchema(ManifestRootSchema);
|
|
42
44
|
return instance;
|
|
43
45
|
}
|
|
@@ -508,6 +510,18 @@ function foldedConstraints(schema: Record<string, any>): Record<string, any> {
|
|
|
508
510
|
return out;
|
|
509
511
|
}
|
|
510
512
|
|
|
513
|
+
/** The stand-in for a CEL leaf at a `live` slot. Static analysis asserts a live
|
|
514
|
+
* type like any instance type (a literal can never be one), so the leaf's
|
|
515
|
+
* stand-in has to BE one. A live binding declares no factory, and building an
|
|
516
|
+
* instance would mean knowing its constructor's signature; an object on the
|
|
517
|
+
* constructor's prototype satisfies `instanceof` and is never used as one. */
|
|
518
|
+
function liveValuePlaceholder(schema: Record<string, any>): unknown | undefined {
|
|
519
|
+
const entry = readValueTypeSlot(schema)?.entry;
|
|
520
|
+
if (!entry?.live || entry.representation !== "instance") return undefined;
|
|
521
|
+
const binding = VALUE_TYPE_BINDINGS[entry.binding!];
|
|
522
|
+
return binding ? Object.create(binding.constructor.prototype) : undefined;
|
|
523
|
+
}
|
|
524
|
+
|
|
511
525
|
export function celPlaceholderForSchema(rawSchema: Record<string, any>): unknown {
|
|
512
526
|
const schema = foldedConstraints(rawSchema);
|
|
513
527
|
// An instance-typed slot's placeholder must BE an instance: the same keyword
|
|
@@ -516,8 +530,8 @@ export function celPlaceholderForSchema(rawSchema: Record<string, any>): unknown
|
|
|
516
530
|
// rejected because no YAML literal is a byte buffer, while a value arriving by
|
|
517
531
|
// reference passes. The stand-in comes from the binding table, so a new
|
|
518
532
|
// instance type brings its own rather than adding a branch here; a `live` type
|
|
519
|
-
// declares none, because nothing validates it.
|
|
520
|
-
const placeholder = valueTypePlaceholder(schema);
|
|
533
|
+
// declares none, because nothing validates it at dispatch.
|
|
534
|
+
const placeholder = valueTypePlaceholder(schema) ?? liveValuePlaceholder(schema);
|
|
521
535
|
if (placeholder !== undefined) return placeholder;
|
|
522
536
|
if (schema.default !== undefined) return schema.default;
|
|
523
537
|
// An enum-constrained field needs a placeholder drawn from the enum: the
|
|
@@ -44,6 +44,13 @@ export interface SchemaIssue {
|
|
|
44
44
|
message: string;
|
|
45
45
|
/** Dotted path to the field (e.g. "config.handler"). Empty string means root. */
|
|
46
46
|
path: string;
|
|
47
|
+
/** The AJV keyword that produced it, so a consumer can key on WHAT failed
|
|
48
|
+
* rather than on how the sentence reads. Prose is the renderer's to change;
|
|
49
|
+
* a caller matching on it breaks silently when it does, and mis-fires on any
|
|
50
|
+
* other issue whose text happens to quote the same name. */
|
|
51
|
+
keyword?: string;
|
|
52
|
+
/** For `required`, the property that is missing. */
|
|
53
|
+
missingProperty?: string;
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
const UNION_KEYWORDS = new Set(["anyOf", "oneOf"]);
|
|
@@ -447,10 +454,15 @@ export function ajvErrorToPath(err: AjvErrorLike): string {
|
|
|
447
454
|
|
|
448
455
|
/** Reduced, path-anchored issues — what a diagnostic list is built from. */
|
|
449
456
|
export function schemaIssues(errors: AjvErrorLike[] | null | undefined): SchemaIssue[] {
|
|
450
|
-
return reduceSchemaErrors(errors).map((err) =>
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
457
|
+
return reduceSchemaErrors(errors).map((err) => {
|
|
458
|
+
const missing = (err.params ?? {}).missingProperty;
|
|
459
|
+
return {
|
|
460
|
+
message: formatSingleError(err),
|
|
461
|
+
path: ajvErrorToPath(err),
|
|
462
|
+
...(err.keyword ? { keyword: err.keyword } : {}),
|
|
463
|
+
...(typeof missing === "string" ? { missingProperty: missing } : {}),
|
|
464
|
+
};
|
|
465
|
+
});
|
|
454
466
|
}
|
|
455
467
|
|
|
456
468
|
/** Reduced, rendered as one sentence — what a thrown runtime error carries. */
|