@telorun/analyzer 0.46.0 → 0.47.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 +2 -2
- package/dist/resolve-ref-sentinels.d.ts +13 -1
- package/dist/resolve-ref-sentinels.d.ts.map +1 -1
- package/dist/resolve-ref-sentinels.js +56 -5
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +17 -1
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +8 -2
- package/package.json +2 -2
- package/src/analyzer.ts +8 -2
- package/src/resolve-ref-sentinels.ts +68 -4
- package/src/validate-cel-context.ts +20 -1
- package/src/validate-references.ts +8 -2
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AA6B9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAyyB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IA4hCvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AA6B9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAyyB/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IA4hCvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CACP,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,EAI1B,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,GACtC,gBAAgB,EAAE;IAyBrB,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
|
package/dist/analyzer.js
CHANGED
|
@@ -973,7 +973,7 @@ export class StaticAnalyzer {
|
|
|
973
973
|
// {kind, name} objects so downstream phases (validation, dependency graph,
|
|
974
974
|
// kernel controllers) see a uniform shape. Runs after normalize so both
|
|
975
975
|
// original and inline-extracted manifests have their sentinels resolved.
|
|
976
|
-
resolveRefSentinels(allManifests, aliases, aliasesByModule);
|
|
976
|
+
resolveRefSentinels(allManifests, aliases, aliasesByModule, [], defs);
|
|
977
977
|
// Phase 2.6: register each named `Telo.Type` resource's schema under its
|
|
978
978
|
// canonical module-scoped id (`telo://<module>/<name>`), validate
|
|
979
979
|
// `telo://Self|Alias/Type` schema refs resolve to one, then rewrite those
|
|
@@ -1642,7 +1642,7 @@ export class StaticAnalyzer {
|
|
|
1642
1642
|
// Resolve !ref sentinels after normalize so both the original and
|
|
1643
1643
|
// inline-extracted manifests get their refs canonicalized to
|
|
1644
1644
|
// {kind, name} for the kernel that consumes this output.
|
|
1645
|
-
resolveRefSentinels(normalized, ctx.aliases, ctx.aliasesByModule, crossModuleTargets ?? []);
|
|
1645
|
+
resolveRefSentinels(normalized, ctx.aliases, ctx.aliasesByModule, crossModuleTargets ?? [], ctx.definitions);
|
|
1646
1646
|
// Canonicalize import-scoped schema `$ref`s (`telo://Self|Alias/Type`) so the
|
|
1647
1647
|
// kernel that executes this output compiles inputs/outputs against the same
|
|
1648
1648
|
// ids the type controllers register their schemas under.
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
2
|
import type { AliasResolver } from "./alias-resolver.js";
|
|
3
|
+
import { type ReferenceFieldMap } from "./reference-field-map.js";
|
|
4
|
+
/** The slice of the definition registry this pass needs: a kind's field map, from
|
|
5
|
+
* which the `x-telo-scope` slots are read. */
|
|
6
|
+
export interface ScopeFieldMapSource {
|
|
7
|
+
getFieldMapForKind(kind: string, aliases?: {
|
|
8
|
+
resolveKind(k: string): string | undefined;
|
|
9
|
+
}): ReferenceFieldMap | undefined;
|
|
10
|
+
}
|
|
3
11
|
/**
|
|
4
12
|
* Rewrites every `!ref <name>` sentinel in each non-system resource's value tree
|
|
5
13
|
* to `{kind, name}` (local) or `{kind, name, alias}` (cross-module), in place.
|
|
@@ -36,5 +44,9 @@ import type { AliasResolver } from "./alias-resolver.js";
|
|
|
36
44
|
* `metadata.module` that isn't a root module) are resolution TARGETS only — they are not
|
|
37
45
|
* re-walked as sources here, since their own ref slots belong to their own module scope.
|
|
38
46
|
*/
|
|
39
|
-
export declare function resolveRefSentinels(resources: ResourceManifest[], aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>, crossModuleTargets?: ResourceManifest[]
|
|
47
|
+
export declare function resolveRefSentinels(resources: ResourceManifest[], aliases?: AliasResolver, aliasesByModule?: Map<string, AliasResolver>, crossModuleTargets?: ResourceManifest[],
|
|
48
|
+
/** Supplies each kind's `x-telo-scope` slots. Without it a scoped name cannot be
|
|
49
|
+
* told from a module-level one, and a shadowed `!ref` resolves to the resource
|
|
50
|
+
* it shadows — so both call sites pass it. */
|
|
51
|
+
defs?: ScopeFieldMapSource): void;
|
|
40
52
|
//# sourceMappingURL=resolve-ref-sentinels.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-ref-sentinels.d.ts","sourceRoot":"","sources":["../src/resolve-ref-sentinels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"resolve-ref-sentinels.d.ts","sourceRoot":"","sources":["../src/resolve-ref-sentinels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAGlC;+CAC+C;AAC/C,MAAM,WAAW,mBAAmB;IAClC,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,GACvD,iBAAiB,GAAG,SAAS,CAAC;CAClC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAK5C,kBAAkB,GAAE,gBAAgB,EAAO;AAC3C;;+CAE+C;AAC/C,IAAI,CAAC,EAAE,mBAAmB,GACzB,IAAI,CA8HN"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isRefSentinel, isTaggedSentinel } from "@telorun/templating";
|
|
2
|
+
import { isScopeEntry, resolveFieldEntries, } from "./reference-field-map.js";
|
|
2
3
|
import { REF_RESOLUTION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
3
4
|
/**
|
|
4
5
|
* Rewrites every `!ref <name>` sentinel in each non-system resource's value tree
|
|
@@ -41,7 +42,11 @@ export function resolveRefSentinels(resources, aliases, aliasesByModule,
|
|
|
41
42
|
// walked as sources). The kernel passes the analyzer-flattened set here so the runtime
|
|
42
43
|
// pass — which loads the entry module only — can still resolve `!ref Alias.name` against
|
|
43
44
|
// imported libraries' exported instances.
|
|
44
|
-
crossModuleTargets = []
|
|
45
|
+
crossModuleTargets = [],
|
|
46
|
+
/** Supplies each kind's `x-telo-scope` slots. Without it a scoped name cannot be
|
|
47
|
+
* told from a module-level one, and a shadowed `!ref` resolves to the resource
|
|
48
|
+
* it shadows — so both call sites pass it. */
|
|
49
|
+
defs) {
|
|
45
50
|
const moduleOf = (r) => r.metadata?.module;
|
|
46
51
|
// Forwarded exports are flagged by flattenForAnalyzer (`metadata.forwardedExport`); they're
|
|
47
52
|
// cross-module resolution targets only — never walked as local ref sources here.
|
|
@@ -96,11 +101,50 @@ crossModuleTargets = []) {
|
|
|
96
101
|
}
|
|
97
102
|
return undefined;
|
|
98
103
|
};
|
|
104
|
+
/** Names a resource declares in its own execution scopes, read from the kind's
|
|
105
|
+
* `x-telo-scope` slots — the analyzer's single definition of "scope", shared
|
|
106
|
+
* with `manifest-visitor`. Inferring it structurally instead (any array of
|
|
107
|
+
* named inline manifests) would give scope-local shadowing to the first kind
|
|
108
|
+
* that happens to carry such an array without asking for it, and this pass is
|
|
109
|
+
* shared with the kernel, so the guess would be baked into the runtime tree
|
|
110
|
+
* rather than merely reported. */
|
|
111
|
+
const declaredInScopes = (resource) => {
|
|
112
|
+
const fieldMap = defs?.getFieldMapForKind(resource.kind, aliases);
|
|
113
|
+
if (!fieldMap)
|
|
114
|
+
return undefined;
|
|
115
|
+
let declared;
|
|
116
|
+
for (const [fieldPath, entry] of fieldMap) {
|
|
117
|
+
if (!isScopeEntry(entry))
|
|
118
|
+
continue;
|
|
119
|
+
for (const { value } of resolveFieldEntries(resource, fieldPath)) {
|
|
120
|
+
for (const element of Array.isArray(value) ? value : [value]) {
|
|
121
|
+
if (!element || typeof element !== "object" || Array.isArray(element))
|
|
122
|
+
continue;
|
|
123
|
+
const manifest = element;
|
|
124
|
+
const name = manifest.metadata?.name;
|
|
125
|
+
if (typeof manifest.kind === "string" && typeof name === "string") {
|
|
126
|
+
(declared ??= new Map()).set(name, manifest);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return declared;
|
|
132
|
+
};
|
|
99
133
|
// Resolve every `!ref` sentinel in the tree; leave opaque tagged / precompiled
|
|
100
134
|
// nodes (e.g. `!cel`) untouched and don't descend into them.
|
|
101
|
-
|
|
135
|
+
//
|
|
136
|
+
// `scoped` carries the names the enclosing resource declares in its `x-telo-scope`
|
|
137
|
+
// slots, and they SHADOW the module-level ones — the order the runtime resolves
|
|
138
|
+
// in. Baking the module-level kind into a shadowed reference would label traces
|
|
139
|
+
// and `getRefIdentity` with a resource that never runs.
|
|
140
|
+
const walk = (value, scoped) => {
|
|
102
141
|
if (isRefSentinel(value)) {
|
|
103
|
-
|
|
142
|
+
const source = value.source;
|
|
143
|
+
const bare = source.indexOf(".") === -1;
|
|
144
|
+
const shadow = bare ? scoped?.get(source) : undefined;
|
|
145
|
+
if (shadow)
|
|
146
|
+
return { kind: shadow.kind, name: source };
|
|
147
|
+
return resolveTarget(source) ?? value;
|
|
104
148
|
}
|
|
105
149
|
if (value === null || typeof value !== "object")
|
|
106
150
|
return value;
|
|
@@ -110,12 +154,19 @@ crossModuleTargets = []) {
|
|
|
110
154
|
return value;
|
|
111
155
|
if (Array.isArray(value)) {
|
|
112
156
|
for (let i = 0; i < value.length; i++)
|
|
113
|
-
value[i] = walk(value[i]);
|
|
157
|
+
value[i] = walk(value[i], scoped);
|
|
114
158
|
return value;
|
|
115
159
|
}
|
|
116
160
|
const obj = value;
|
|
161
|
+
// A nested inline resource may declare scopes of its own (a `Run.Sequence`
|
|
162
|
+
// inside another sequence's `with:`). Collected before descending, so the
|
|
163
|
+
// declarations are visible to every region of the resource that declares
|
|
164
|
+
// them — a sequence's `with:` names resolve in its `targets:` and `steps:`
|
|
165
|
+
// alike, not only inside `with:` itself.
|
|
166
|
+
const declared = typeof obj.kind === "string" ? declaredInScopes(obj) : undefined;
|
|
167
|
+
const inner = declared ? new Map([...(scoped ?? new Map()), ...declared]) : scoped;
|
|
117
168
|
for (const key of Object.keys(obj))
|
|
118
|
-
obj[key] = walk(obj[key]);
|
|
169
|
+
obj[key] = walk(obj[key], inner);
|
|
119
170
|
return value;
|
|
120
171
|
};
|
|
121
172
|
for (const r of resources) {
|
|
@@ -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;AAGtF,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;AAED;;;;;GAKG;AACH,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,CA0CjC;AAuFD;;;;;;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,
|
|
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;AAGtF,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;AAED;;;;;GAKG;AACH,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,CA0CjC;AAuFD;;;;;;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,CAmIrB;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"}
|
|
@@ -276,13 +276,29 @@ export function resolveContextAnnotations(schema, manifestItem, opts) {
|
|
|
276
276
|
typeof ref.kind === "string" &&
|
|
277
277
|
typeof ref.name === "string" &&
|
|
278
278
|
subpath) {
|
|
279
|
+
const segments = subpath.split("/");
|
|
279
280
|
const refManifest = allManifests.find((m) => m.kind === ref.kind && m.metadata?.name === ref.name);
|
|
280
281
|
if (refManifest) {
|
|
281
|
-
const resolved = resolveTypeFieldToSchema(navigatePath(refManifest,
|
|
282
|
+
const resolved = resolveTypeFieldToSchema(navigatePath(refManifest, segments), allManifests);
|
|
282
283
|
if (resolved && typeof resolved === "object") {
|
|
283
284
|
return resolved;
|
|
284
285
|
}
|
|
285
286
|
}
|
|
287
|
+
// The instance declares nothing, so fall back to its KIND's declaration —
|
|
288
|
+
// the same layering `buildStepContextSchema` applies to `steps.<name>.result`,
|
|
289
|
+
// so a kind with one fixed output shape (declared once on its Telo.Definition)
|
|
290
|
+
// types the context, while a kind that exposes the field for per-instance
|
|
291
|
+
// narrowing keeps winning above.
|
|
292
|
+
if (defs) {
|
|
293
|
+
const canonical = aliases?.resolveKind(ref.kind) ?? ref.kind;
|
|
294
|
+
const def = defs.resolve(canonical);
|
|
295
|
+
if (def) {
|
|
296
|
+
const resolved = resolveTypeFieldToSchema(navigatePath(def, segments), allManifests);
|
|
297
|
+
if (resolved && typeof resolved === "object") {
|
|
298
|
+
return resolved;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
286
302
|
}
|
|
287
303
|
// Fallback: open schema (no false errors when outputType is not declared)
|
|
288
304
|
return { ...schema, additionalProperties: true };
|
|
@@ -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;AAMrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAgD/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,
|
|
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;AAMrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAgD/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CAmetB"}
|
|
@@ -254,8 +254,14 @@ export function validateReferences(resources, context) {
|
|
|
254
254
|
}
|
|
255
255
|
// Local reference (bare name or explicit `Self.`-qualified).
|
|
256
256
|
const localName = aliasPrefix === "Self" ? refName.slice(dot + 1) : refName;
|
|
257
|
-
|
|
258
|
-
|
|
257
|
+
// Scope-local FIRST, enclosing module as the fallback — the order the
|
|
258
|
+
// runtime uses at every name-resolution site (`ScopeContext.getInstance`,
|
|
259
|
+
// `ResourceContext.resolveRef`, and the CEL `resources` layering). Module-first
|
|
260
|
+
// here would validate a shadowed name against the resource the kernel will
|
|
261
|
+
// never bind: a false pass when the outer kind fits and the scoped one does
|
|
262
|
+
// not, a false REFERENCE_KIND_MISMATCH when it is the other way round.
|
|
263
|
+
const target = visibleScopeManifests.find((m) => m.metadata?.name === localName) ??
|
|
264
|
+
byName.get(localName);
|
|
259
265
|
if (!target) {
|
|
260
266
|
diagnostics.push({
|
|
261
267
|
severity: DiagnosticSeverity.Error,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "Telo Analyzer - Static manifest validator for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@types/node": "^20.0.0",
|
|
49
49
|
"typescript": "^5.0.0",
|
|
50
50
|
"vitest": "^2.1.8",
|
|
51
|
-
"@telorun/sdk": "0.
|
|
51
|
+
"@telorun/sdk": "0.59.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@telorun/sdk": "*"
|
package/src/analyzer.ts
CHANGED
|
@@ -1153,7 +1153,7 @@ export class StaticAnalyzer {
|
|
|
1153
1153
|
// {kind, name} objects so downstream phases (validation, dependency graph,
|
|
1154
1154
|
// kernel controllers) see a uniform shape. Runs after normalize so both
|
|
1155
1155
|
// original and inline-extracted manifests have their sentinels resolved.
|
|
1156
|
-
resolveRefSentinels(allManifests, aliases, aliasesByModule);
|
|
1156
|
+
resolveRefSentinels(allManifests, aliases, aliasesByModule, [], defs);
|
|
1157
1157
|
|
|
1158
1158
|
// Phase 2.6: register each named `Telo.Type` resource's schema under its
|
|
1159
1159
|
// canonical module-scoped id (`telo://<module>/<name>`), validate
|
|
@@ -1957,7 +1957,13 @@ export class StaticAnalyzer {
|
|
|
1957
1957
|
// Resolve !ref sentinels after normalize so both the original and
|
|
1958
1958
|
// inline-extracted manifests get their refs canonicalized to
|
|
1959
1959
|
// {kind, name} for the kernel that consumes this output.
|
|
1960
|
-
resolveRefSentinels(
|
|
1960
|
+
resolveRefSentinels(
|
|
1961
|
+
normalized,
|
|
1962
|
+
ctx.aliases,
|
|
1963
|
+
ctx.aliasesByModule,
|
|
1964
|
+
crossModuleTargets ?? [],
|
|
1965
|
+
ctx.definitions!,
|
|
1966
|
+
);
|
|
1961
1967
|
// Canonicalize import-scoped schema `$ref`s (`telo://Self|Alias/Type`) so the
|
|
1962
1968
|
// kernel that executes this output compiles inputs/outputs against the same
|
|
1963
1969
|
// ids the type controllers register their schemas under.
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
2
|
import { isRefSentinel, isTaggedSentinel } from "@telorun/templating";
|
|
3
3
|
import type { AliasResolver } from "./alias-resolver.js";
|
|
4
|
+
import {
|
|
5
|
+
isScopeEntry,
|
|
6
|
+
resolveFieldEntries,
|
|
7
|
+
type ReferenceFieldMap,
|
|
8
|
+
} from "./reference-field-map.js";
|
|
4
9
|
import { REF_RESOLUTION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
5
10
|
|
|
11
|
+
/** The slice of the definition registry this pass needs: a kind's field map, from
|
|
12
|
+
* which the `x-telo-scope` slots are read. */
|
|
13
|
+
export interface ScopeFieldMapSource {
|
|
14
|
+
getFieldMapForKind(
|
|
15
|
+
kind: string,
|
|
16
|
+
aliases?: { resolveKind(k: string): string | undefined },
|
|
17
|
+
): ReferenceFieldMap | undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
/** Resolved ref shape written in place of a `!ref` sentinel. `alias` is set only for
|
|
7
21
|
* cross-module references (resolved into an imported library's exported instance). */
|
|
8
22
|
type ResolvedRef = { kind: string; name: string; alias?: string };
|
|
@@ -52,6 +66,10 @@ export function resolveRefSentinels(
|
|
|
52
66
|
// pass — which loads the entry module only — can still resolve `!ref Alias.name` against
|
|
53
67
|
// imported libraries' exported instances.
|
|
54
68
|
crossModuleTargets: ResourceManifest[] = [],
|
|
69
|
+
/** Supplies each kind's `x-telo-scope` slots. Without it a scoped name cannot be
|
|
70
|
+
* told from a module-level one, and a shadowed `!ref` resolves to the resource
|
|
71
|
+
* it shadows — so both call sites pass it. */
|
|
72
|
+
defs?: ScopeFieldMapSource,
|
|
55
73
|
): void {
|
|
56
74
|
const moduleOf = (r: ResourceManifest): string | undefined =>
|
|
57
75
|
(r.metadata as { module?: string } | undefined)?.module;
|
|
@@ -109,21 +127,67 @@ export function resolveRefSentinels(
|
|
|
109
127
|
return undefined;
|
|
110
128
|
};
|
|
111
129
|
|
|
130
|
+
/** Names a resource declares in its own execution scopes, read from the kind's
|
|
131
|
+
* `x-telo-scope` slots — the analyzer's single definition of "scope", shared
|
|
132
|
+
* with `manifest-visitor`. Inferring it structurally instead (any array of
|
|
133
|
+
* named inline manifests) would give scope-local shadowing to the first kind
|
|
134
|
+
* that happens to carry such an array without asking for it, and this pass is
|
|
135
|
+
* shared with the kernel, so the guess would be baked into the runtime tree
|
|
136
|
+
* rather than merely reported. */
|
|
137
|
+
const declaredInScopes = (
|
|
138
|
+
resource: ResourceManifest,
|
|
139
|
+
): Map<string, ResourceManifest> | undefined => {
|
|
140
|
+
const fieldMap = defs?.getFieldMapForKind(resource.kind, aliases);
|
|
141
|
+
if (!fieldMap) return undefined;
|
|
142
|
+
let declared: Map<string, ResourceManifest> | undefined;
|
|
143
|
+
for (const [fieldPath, entry] of fieldMap) {
|
|
144
|
+
if (!isScopeEntry(entry)) continue;
|
|
145
|
+
for (const { value } of resolveFieldEntries(resource, fieldPath)) {
|
|
146
|
+
for (const element of Array.isArray(value) ? value : [value]) {
|
|
147
|
+
if (!element || typeof element !== "object" || Array.isArray(element)) continue;
|
|
148
|
+
const manifest = element as ResourceManifest;
|
|
149
|
+
const name = (manifest.metadata as { name?: string } | undefined)?.name;
|
|
150
|
+
if (typeof manifest.kind === "string" && typeof name === "string") {
|
|
151
|
+
(declared ??= new Map()).set(name, manifest);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return declared;
|
|
157
|
+
};
|
|
158
|
+
|
|
112
159
|
// Resolve every `!ref` sentinel in the tree; leave opaque tagged / precompiled
|
|
113
160
|
// nodes (e.g. `!cel`) untouched and don't descend into them.
|
|
114
|
-
|
|
161
|
+
//
|
|
162
|
+
// `scoped` carries the names the enclosing resource declares in its `x-telo-scope`
|
|
163
|
+
// slots, and they SHADOW the module-level ones — the order the runtime resolves
|
|
164
|
+
// in. Baking the module-level kind into a shadowed reference would label traces
|
|
165
|
+
// and `getRefIdentity` with a resource that never runs.
|
|
166
|
+
const walk = (value: unknown, scoped?: Map<string, ResourceManifest>): unknown => {
|
|
115
167
|
if (isRefSentinel(value)) {
|
|
116
|
-
|
|
168
|
+
const source = value.source;
|
|
169
|
+
const bare = source.indexOf(".") === -1;
|
|
170
|
+
const shadow = bare ? scoped?.get(source) : undefined;
|
|
171
|
+
if (shadow) return { kind: shadow.kind as string, name: source };
|
|
172
|
+
return resolveTarget(source) ?? value;
|
|
117
173
|
}
|
|
118
174
|
if (value === null || typeof value !== "object") return value;
|
|
119
175
|
if (isTaggedSentinel(value)) return value;
|
|
120
176
|
if ((value as { __compiled?: unknown }).__compiled) return value;
|
|
121
177
|
if (Array.isArray(value)) {
|
|
122
|
-
for (let i = 0; i < value.length; i++) value[i] = walk(value[i]);
|
|
178
|
+
for (let i = 0; i < value.length; i++) value[i] = walk(value[i], scoped);
|
|
123
179
|
return value;
|
|
124
180
|
}
|
|
125
181
|
const obj = value as Record<string, unknown>;
|
|
126
|
-
|
|
182
|
+
// A nested inline resource may declare scopes of its own (a `Run.Sequence`
|
|
183
|
+
// inside another sequence's `with:`). Collected before descending, so the
|
|
184
|
+
// declarations are visible to every region of the resource that declares
|
|
185
|
+
// them — a sequence's `with:` names resolve in its `targets:` and `steps:`
|
|
186
|
+
// alike, not only inside `with:` itself.
|
|
187
|
+
const declared =
|
|
188
|
+
typeof obj.kind === "string" ? declaredInScopes(obj as ResourceManifest) : undefined;
|
|
189
|
+
const inner = declared ? new Map([...(scoped ?? new Map()), ...declared]) : scoped;
|
|
190
|
+
for (const key of Object.keys(obj)) obj[key] = walk(obj[key], inner);
|
|
127
191
|
return value;
|
|
128
192
|
};
|
|
129
193
|
|
|
@@ -323,18 +323,37 @@ export function resolveContextAnnotations(
|
|
|
323
323
|
typeof ref.name === "string" &&
|
|
324
324
|
subpath
|
|
325
325
|
) {
|
|
326
|
+
const segments = subpath.split("/");
|
|
326
327
|
const refManifest = allManifests.find(
|
|
327
328
|
(m) => m.kind === ref.kind && (m.metadata as any)?.name === ref.name,
|
|
328
329
|
) as Record<string, any> | undefined;
|
|
329
330
|
if (refManifest) {
|
|
330
331
|
const resolved = resolveTypeFieldToSchema(
|
|
331
|
-
navigatePath(refManifest,
|
|
332
|
+
navigatePath(refManifest, segments) as unknown,
|
|
332
333
|
allManifests,
|
|
333
334
|
);
|
|
334
335
|
if (resolved && typeof resolved === "object") {
|
|
335
336
|
return resolved;
|
|
336
337
|
}
|
|
337
338
|
}
|
|
339
|
+
// The instance declares nothing, so fall back to its KIND's declaration —
|
|
340
|
+
// the same layering `buildStepContextSchema` applies to `steps.<name>.result`,
|
|
341
|
+
// so a kind with one fixed output shape (declared once on its Telo.Definition)
|
|
342
|
+
// types the context, while a kind that exposes the field for per-instance
|
|
343
|
+
// narrowing keeps winning above.
|
|
344
|
+
if (defs) {
|
|
345
|
+
const canonical = aliases?.resolveKind(ref.kind) ?? ref.kind;
|
|
346
|
+
const def = defs.resolve(canonical) as Record<string, unknown> | undefined;
|
|
347
|
+
if (def) {
|
|
348
|
+
const resolved = resolveTypeFieldToSchema(
|
|
349
|
+
navigatePath(def, segments) as unknown,
|
|
350
|
+
allManifests,
|
|
351
|
+
);
|
|
352
|
+
if (resolved && typeof resolved === "object") {
|
|
353
|
+
return resolved;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
338
357
|
}
|
|
339
358
|
// Fallback: open schema (no false errors when outputType is not declared)
|
|
340
359
|
return { ...schema, additionalProperties: true };
|
|
@@ -267,9 +267,15 @@ export function validateReferences(
|
|
|
267
267
|
|
|
268
268
|
// Local reference (bare name or explicit `Self.`-qualified).
|
|
269
269
|
const localName = aliasPrefix === "Self" ? refName.slice(dot + 1) : refName;
|
|
270
|
+
// Scope-local FIRST, enclosing module as the fallback — the order the
|
|
271
|
+
// runtime uses at every name-resolution site (`ScopeContext.getInstance`,
|
|
272
|
+
// `ResourceContext.resolveRef`, and the CEL `resources` layering). Module-first
|
|
273
|
+
// here would validate a shadowed name against the resource the kernel will
|
|
274
|
+
// never bind: a false pass when the outer kind fits and the scoped one does
|
|
275
|
+
// not, a false REFERENCE_KIND_MISMATCH when it is the other way round.
|
|
270
276
|
const target =
|
|
271
|
-
|
|
272
|
-
|
|
277
|
+
visibleScopeManifests.find((m) => m.metadata?.name === localName) ??
|
|
278
|
+
byName.get(localName);
|
|
273
279
|
if (!target) {
|
|
274
280
|
diagnostics.push({
|
|
275
281
|
severity: DiagnosticSeverity.Error,
|