@telorun/analyzer 0.66.0 → 0.67.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 +37 -2
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +67 -16
- package/dist/cel-scope.d.ts +8 -0
- package/dist/cel-scope.d.ts.map +1 -1
- package/dist/cel-scope.js +66 -8
- package/dist/definition-registry.d.ts +17 -0
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +35 -0
- package/dist/dependency-graph.d.ts.map +1 -1
- package/dist/dependency-graph.js +65 -0
- package/dist/flatten-for-analyzer.d.ts +36 -0
- package/dist/flatten-for-analyzer.d.ts.map +1 -1
- package/dist/flatten-for-analyzer.js +103 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/inline-imports.d.ts.map +1 -1
- package/dist/inline-imports.js +1 -0
- package/dist/manifest-visitor.d.ts +4 -0
- package/dist/manifest-visitor.d.ts.map +1 -1
- package/dist/manifest-visitor.js +28 -0
- package/dist/precompile.d.ts.map +1 -1
- package/dist/precompile.js +8 -0
- package/dist/resolve-ref-sentinels.d.ts.map +1 -1
- package/dist/resolve-ref-sentinels.js +14 -1
- package/dist/resource-input.d.ts +75 -0
- package/dist/resource-input.d.ts.map +1 -0
- package/dist/resource-input.js +90 -0
- package/dist/schema-projection.d.ts +13 -0
- package/dist/schema-projection.d.ts.map +1 -1
- package/dist/schema-projection.js +7 -0
- package/dist/system-kinds.d.ts +7 -2
- package/dist/system-kinds.d.ts.map +1 -1
- package/dist/system-kinds.js +7 -2
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/template-body.d.ts +50 -0
- package/dist/template-body.d.ts.map +1 -0
- package/dist/template-body.js +58 -0
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +68 -8
- package/dist/validate-identifier-names.d.ts.map +1 -1
- package/dist/validate-identifier-names.js +17 -2
- package/dist/validate-references.d.ts +17 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +68 -16
- package/dist/validate-resource-inputs.d.ts +35 -0
- package/dist/validate-resource-inputs.d.ts.map +1 -0
- package/dist/validate-resource-inputs.js +319 -0
- package/dist/validate-template-dispatch.d.ts +27 -0
- package/dist/validate-template-dispatch.d.ts.map +1 -0
- package/dist/validate-template-dispatch.js +95 -0
- package/package.json +3 -3
- package/src/analyzer.ts +45 -2
- package/src/builtins.ts +69 -16
- package/src/cel-scope.ts +90 -14
- package/src/definition-registry.ts +36 -0
- package/src/dependency-graph.ts +66 -0
- package/src/flatten-for-analyzer.ts +116 -3
- package/src/index.ts +12 -0
- package/src/inline-imports.ts +1 -0
- package/src/manifest-visitor.ts +33 -0
- package/src/precompile.ts +8 -0
- package/src/resolve-ref-sentinels.ts +12 -1
- package/src/resource-input.ts +132 -0
- package/src/schema-projection.ts +19 -0
- package/src/system-kinds.ts +7 -2
- package/src/telo-version.ts +1 -1
- package/src/template-body.ts +104 -0
- package/src/validate-cel-context.ts +67 -7
- package/src/validate-identifier-names.ts +18 -3
- package/src/validate-references.ts +70 -14
- package/src/validate-resource-inputs.ts +367 -0
- package/src/validate-template-dispatch.ts +99 -0
package/dist/system-kinds.js
CHANGED
|
@@ -25,8 +25,13 @@ export const DEPENDENCY_GRAPH_SKIP_KINDS = new Set([
|
|
|
25
25
|
/** Skipped by `!ref` sentinel resolution: kinds whose bodies are
|
|
26
26
|
* blueprints or import-time metadata, not resource instances with
|
|
27
27
|
* user-referenced ref slots. Mirrors `REF_VALIDATION_SKIP_KINDS` but
|
|
28
|
-
* also
|
|
29
|
-
*
|
|
28
|
+
* also lists Telo.Import, because walking its field map is pointless —
|
|
29
|
+
* there is no registered kind behind it.
|
|
30
|
+
*
|
|
31
|
+
* `resolveRefSentinels` reaches Telo.Import's `resources:` block AHEAD of this
|
|
32
|
+
* set, deliberately: those are the references an importer supplies for a
|
|
33
|
+
* library's declared inputs, and they resolve exactly like any other. Nothing
|
|
34
|
+
* else on the document is a reference slot, so only that subtree is walked. */
|
|
30
35
|
export const REF_RESOLUTION_SKIP_KINDS = new Set([
|
|
31
36
|
"Telo.Definition",
|
|
32
37
|
"Telo.Abstract",
|
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.
|
|
8
|
+
export const TELO_SURFACE_VERSION = "0.83.0";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ResourceDefinition, ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import type { AliasResolver } from "./alias-resolver.js";
|
|
3
|
+
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
4
|
+
import type { ModuleScopes } from "./alias-resolver.js";
|
|
5
|
+
/**
|
|
6
|
+
* A `Telo.Definition`'s `resources:` entries — the bodies a kind writes for
|
|
7
|
+
* ANOTHER kind — read as what they are: declarations of that other kind.
|
|
8
|
+
*
|
|
9
|
+
* The consequence is the whole point. CEL inside such a body used to be typed in
|
|
10
|
+
* the ENCLOSING definition's scope, against one fixed permissive context
|
|
11
|
+
* (`self`, plus open `request` / `result` / `steps` / `error`). So `inputs` and
|
|
12
|
+
* `item` were undefined wherever the nested kind declares them, while `error`
|
|
13
|
+
* was offered everywhere regardless of whether a `catch:` was in scope — a body
|
|
14
|
+
* of more than one dispatch was unwritable, which is why no standard-library
|
|
15
|
+
* template has one.
|
|
16
|
+
*
|
|
17
|
+
* Resolving through the nested kind's OWN annotations — its `x-telo-context`
|
|
18
|
+
* regions, its step body, its error branches — makes a nested declaration answer
|
|
19
|
+
* exactly as the same declaration written at the top level does, with `self`
|
|
20
|
+
* merged in from the enclosing definition's `schema:`.
|
|
21
|
+
*
|
|
22
|
+
* Browser-safe.
|
|
23
|
+
*/
|
|
24
|
+
export interface TemplateBody {
|
|
25
|
+
/** Concrete path prefix of this entry (`resources[0]`), as CEL sites are
|
|
26
|
+
* addressed. */
|
|
27
|
+
prefix: string;
|
|
28
|
+
/** JSONPath prefix for context scopes (`$.resources[0]`). */
|
|
29
|
+
scopePrefix: string;
|
|
30
|
+
manifest: ResourceManifest;
|
|
31
|
+
definition: ResourceDefinition | undefined;
|
|
32
|
+
}
|
|
33
|
+
/** The template bodies a manifest declares — empty for anything that is not a
|
|
34
|
+
* `Telo.Definition` with a `resources:` array. */
|
|
35
|
+
export declare function templateBodies(m: ResourceManifest, registry: DefinitionRegistry, aliases: AliasResolver | undefined, scopes: ModuleScopes | undefined): TemplateBody[];
|
|
36
|
+
/** True when a CEL path lies at or inside a body's own subtree. */
|
|
37
|
+
export declare function pathInBody(path: string, prefix: string): boolean;
|
|
38
|
+
/** The body a CEL path belongs to, or undefined for a path in the definition's
|
|
39
|
+
* own fields. Generic over what the caller keyed to each body — the resolver
|
|
40
|
+
* carries a step context and an error map, the visitor carries the schema. */
|
|
41
|
+
export declare function bodyForPath<T extends {
|
|
42
|
+
prefix: string;
|
|
43
|
+
}>(bodies: readonly T[], path: string): T | undefined;
|
|
44
|
+
/** `self` is in scope throughout a template body — it is how the body reaches
|
|
45
|
+
* the configuration its enclosing kind was given — so it is merged into every
|
|
46
|
+
* context the nested kind declares, which knows nothing about it. Merged UNDER
|
|
47
|
+
* the nested kind's own properties: a name the nested kind declares wins, since
|
|
48
|
+
* that is what its controller will bind. */
|
|
49
|
+
export declare function withTemplateSelf(contextSchema: Record<string, any>): Record<string, any>;
|
|
50
|
+
//# sourceMappingURL=template-body.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-body.d.ts","sourceRoot":"","sources":["../src/template-body.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,YAAY;IAC3B;qBACiB;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,UAAU,EAAE,kBAAkB,GAAG,SAAS,CAAC;CAC5C;AAED;mDACmD;AACnD,wBAAgB,cAAc,CAC5B,CAAC,EAAE,gBAAgB,EACnB,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,MAAM,EAAE,YAAY,GAAG,SAAS,GAC/B,YAAY,EAAE,CA8BhB;AAED,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAIhE;AAED;;+EAE+E;AAC/E,wBAAgB,WAAW,CAAC,CAAC,SAAS;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EACtD,MAAM,EAAE,SAAS,CAAC,EAAE,EACpB,IAAI,EAAE,MAAM,GACX,CAAC,GAAG,SAAS,CAEf;AAED;;;;6CAI6C;AAC7C,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAQxF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** The template bodies a manifest declares — empty for anything that is not a
|
|
2
|
+
* `Telo.Definition` with a `resources:` array. */
|
|
3
|
+
export function templateBodies(m, registry, aliases, scopes) {
|
|
4
|
+
if (m.kind !== "Telo.Definition")
|
|
5
|
+
return [];
|
|
6
|
+
const bodies = m.resources;
|
|
7
|
+
if (!Array.isArray(bodies))
|
|
8
|
+
return [];
|
|
9
|
+
// A nested kind is written through the alias scope of the module that DECLARED
|
|
10
|
+
// the definition, never the consumer's — the same rule every other kind
|
|
11
|
+
// resolution in a forwarded manifest follows.
|
|
12
|
+
const ownModule = m.metadata?.module;
|
|
13
|
+
const scope = (ownModule && scopes && !scopes.rootModules.has(ownModule)
|
|
14
|
+
? scopes.aliasesByModule.get(ownModule)
|
|
15
|
+
: undefined) ?? aliases;
|
|
16
|
+
const out = [];
|
|
17
|
+
for (let i = 0; i < bodies.length; i++) {
|
|
18
|
+
const body = bodies[i];
|
|
19
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
20
|
+
continue;
|
|
21
|
+
const kind = body.kind;
|
|
22
|
+
if (typeof kind !== "string")
|
|
23
|
+
continue;
|
|
24
|
+
const canonical = scope?.resolveKind(kind);
|
|
25
|
+
const definition = registry.resolve(kind) ?? (canonical ? registry.resolve(canonical) : undefined);
|
|
26
|
+
out.push({
|
|
27
|
+
prefix: `resources[${i}]`,
|
|
28
|
+
scopePrefix: `$.resources[${i}]`,
|
|
29
|
+
manifest: body,
|
|
30
|
+
definition,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
/** True when a CEL path lies at or inside a body's own subtree. */
|
|
36
|
+
export function pathInBody(path, prefix) {
|
|
37
|
+
return (path === prefix || path.startsWith(`${prefix}.`) || path.startsWith(`${prefix}[`));
|
|
38
|
+
}
|
|
39
|
+
/** The body a CEL path belongs to, or undefined for a path in the definition's
|
|
40
|
+
* own fields. Generic over what the caller keyed to each body — the resolver
|
|
41
|
+
* carries a step context and an error map, the visitor carries the schema. */
|
|
42
|
+
export function bodyForPath(bodies, path) {
|
|
43
|
+
return bodies.find((b) => pathInBody(path, b.prefix));
|
|
44
|
+
}
|
|
45
|
+
/** `self` is in scope throughout a template body — it is how the body reaches
|
|
46
|
+
* the configuration its enclosing kind was given — so it is merged into every
|
|
47
|
+
* context the nested kind declares, which knows nothing about it. Merged UNDER
|
|
48
|
+
* the nested kind's own properties: a name the nested kind declares wins, since
|
|
49
|
+
* that is what its controller will bind. */
|
|
50
|
+
export function withTemplateSelf(contextSchema) {
|
|
51
|
+
return {
|
|
52
|
+
...contextSchema,
|
|
53
|
+
properties: {
|
|
54
|
+
self: { "x-telo-context-from-root": "schema" },
|
|
55
|
+
...(contextSchema.properties ?? {}),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -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;AAatF,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,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;AA2B3E,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,CAoLrB;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,CAuBrB;AAqBD;;;;;;;;;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"}
|
|
@@ -7,6 +7,29 @@ import { withRefSlotsAsReadings } from "./ref-slot-reading.js";
|
|
|
7
7
|
// question in one place. Re-exported: this module is where every existing
|
|
8
8
|
// consumer imports them from.
|
|
9
9
|
export { extractCelRegionScopes, pathMatchesScope } from "./eval-paths.js";
|
|
10
|
+
/** True when a node's value is decided at load or dispatch rather than written
|
|
11
|
+
* — a tagged sentinel (`!cel`, `!sql`, an embed) or an already-compiled value.
|
|
12
|
+
* A schema position holding one declares a shape nothing static can read. */
|
|
13
|
+
function isDynamicNode(value) {
|
|
14
|
+
if (!value || typeof value !== "object")
|
|
15
|
+
return false;
|
|
16
|
+
const node = value;
|
|
17
|
+
return node.__tagged === true || node.__compiled !== undefined;
|
|
18
|
+
}
|
|
19
|
+
/** Replace every dynamically-valued property with an OPEN schema, so the name is
|
|
20
|
+
* in scope with its members unconstrained. */
|
|
21
|
+
function openDynamicProperties(props) {
|
|
22
|
+
if (!props || typeof props !== "object")
|
|
23
|
+
return props;
|
|
24
|
+
let out;
|
|
25
|
+
for (const [key, value] of Object.entries(props)) {
|
|
26
|
+
if (!isDynamicNode(value))
|
|
27
|
+
continue;
|
|
28
|
+
out ??= { ...props };
|
|
29
|
+
out[key] = {};
|
|
30
|
+
}
|
|
31
|
+
return out ?? props;
|
|
32
|
+
}
|
|
10
33
|
/**
|
|
11
34
|
* Resolve a type field value (string name, inline type, or raw schema) to a JSON Schema.
|
|
12
35
|
* - String: look up the named type in allManifests (Type.JsonSchema resources)
|
|
@@ -298,8 +321,19 @@ export function resolveContextAnnotations(schema, manifestItem, opts) {
|
|
|
298
321
|
// to be resolved first: the standard library writes it as the inline
|
|
299
322
|
// `{ kind: Type.JsonSchema, schema: … }` wrapper, so merging it verbatim
|
|
300
323
|
// would type the variable as `{ kind, schema }` instead of its properties.
|
|
324
|
+
// The navigated node — or one of the properties in it — may itself be an
|
|
325
|
+
// expression: a route whose `request.schema.body` is `!cel "self.model.schema"`
|
|
326
|
+
// declares a body whose SHAPE is only known once the template is
|
|
327
|
+
// instantiated. Such a name types as OPEN rather than as nothing, and a
|
|
328
|
+
// wholly dynamic map leaves the whole node open. Resolving it to nothing
|
|
329
|
+
// reports `request.body` as undefined, which blames the reader for the
|
|
330
|
+
// writer's dynamism — the same posture a rule takes when a value it reads
|
|
331
|
+
// is dynamic: skip the judgement, never invert it.
|
|
332
|
+
if (isDynamicNode(navigated)) {
|
|
333
|
+
return { ...schema, properties: { ...(schema.properties ?? {}) }, additionalProperties: true };
|
|
334
|
+
}
|
|
301
335
|
const asType = resolveTypeFieldToSchema(navigated, allManifests ?? []);
|
|
302
|
-
const resolved = asType?.properties ?? navigated;
|
|
336
|
+
const resolved = openDynamicProperties(asType?.properties ?? navigated);
|
|
303
337
|
const required = Array.isArray(asType?.required) ? asType.required : undefined;
|
|
304
338
|
return {
|
|
305
339
|
...schema,
|
|
@@ -438,14 +472,40 @@ export function resolveContextAnnotations(schema, manifestItem, opts) {
|
|
|
438
472
|
*/
|
|
439
473
|
export function getManifestItem(exprPath, scope, manifest) {
|
|
440
474
|
const stripped = scope.startsWith("$.") ? scope.slice(2) : scope;
|
|
441
|
-
const
|
|
442
|
-
if (
|
|
443
|
-
return manifest;
|
|
444
|
-
const arrayProp = stripped.slice(0, wildcardIdx); // e.g. "routes"
|
|
445
|
-
const m = exprPath.match(new RegExp(`^${arrayProp}\\[(\\d+)\\]`));
|
|
446
|
-
if (!m)
|
|
475
|
+
const parts = stripped.split("[*]");
|
|
476
|
+
if (parts.length === 1)
|
|
447
477
|
return manifest;
|
|
448
|
-
|
|
478
|
+
// Resolve each `[*]` against the concrete index the expression path carries at
|
|
479
|
+
// the same position, accumulating up to the LAST wildcard: that node is the
|
|
480
|
+
// per-scope item (`routes[2]`), which is what `x-telo-context-from` navigates
|
|
481
|
+
// from. Built by walking the pattern rather than by a regex over the scope —
|
|
482
|
+
// a scope is a path, not a pattern, and its own `[4]` segments are regex
|
|
483
|
+
// character classes, which is what silently made every nested scope resolve
|
|
484
|
+
// to the whole document instead of the item.
|
|
485
|
+
let remaining = exprPath;
|
|
486
|
+
let concrete = "";
|
|
487
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
488
|
+
const part = parts[i];
|
|
489
|
+
if (!remaining.startsWith(part))
|
|
490
|
+
return manifest;
|
|
491
|
+
remaining = remaining.slice(part.length);
|
|
492
|
+
const index = remaining.match(/^\[(\d+)\]/);
|
|
493
|
+
if (!index)
|
|
494
|
+
return manifest;
|
|
495
|
+
remaining = remaining.slice(index[0].length);
|
|
496
|
+
concrete += `${part}${index[0]}`;
|
|
497
|
+
}
|
|
498
|
+
return navigateConcretePath(manifest, concrete) ?? manifest;
|
|
499
|
+
}
|
|
500
|
+
/** Walk a concrete dotted path with `[N]` indices (`resources[4].routes[2]`). */
|
|
501
|
+
function navigateConcretePath(root, path) {
|
|
502
|
+
let cur = root;
|
|
503
|
+
for (const segment of path.match(/[^.[\]]+/g) ?? []) {
|
|
504
|
+
if (cur === null || typeof cur !== "object")
|
|
505
|
+
return undefined;
|
|
506
|
+
cur = cur[segment];
|
|
507
|
+
}
|
|
508
|
+
return cur;
|
|
449
509
|
}
|
|
450
510
|
function navigatePath(obj, segments) {
|
|
451
511
|
let cur = obj;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-identifier-names.d.ts","sourceRoot":"","sources":["../src/validate-identifier-names.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,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-identifier-names.d.ts","sourceRoot":"","sources":["../src/validate-identifier-names.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,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAQnE,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIrD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EACxB,KAAK,EAAE,SAAS,GACf,kBAAkB,EAAE,CAwEtB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TYPE_LEVEL_DOC_KINDS, checkName, } from "./identifier-name.js";
|
|
2
|
+
import { isInjectedDeclaration } from "./resource-input.js";
|
|
2
3
|
const SOURCE = "telo-analyzer";
|
|
3
4
|
/**
|
|
4
5
|
* Every author-written name in one pass — resource instances, kinds, modules,
|
|
@@ -36,6 +37,11 @@ export function validateIdentifierNames(manifests, registry, aliases, rootModule
|
|
|
36
37
|
// would blame them for a spelling this pass's own pipeline chose.
|
|
37
38
|
if (metadata?.xTeloOrigin)
|
|
38
39
|
continue;
|
|
40
|
+
// A kind-only stand-in for a `resources:` entry carries the name the author
|
|
41
|
+
// wrote in that block, and the block's own keys are checked below — on the
|
|
42
|
+
// module doc, where the name is written and a squiggle can land.
|
|
43
|
+
if (isInjectedDeclaration(manifest))
|
|
44
|
+
continue;
|
|
39
45
|
const ownModule = metadata?.module;
|
|
40
46
|
if (ownModule && !rootModules.has(ownModule))
|
|
41
47
|
continue;
|
|
@@ -53,7 +59,10 @@ export function validateIdentifierNames(manifests, registry, aliases, rootModule
|
|
|
53
59
|
// keys are the imported library's declarations, so a violation there is
|
|
54
60
|
// the library author's, reported when their module is analyzed as a root.
|
|
55
61
|
if (manifest.kind === "Telo.Application" || manifest.kind === "Telo.Library") {
|
|
56
|
-
|
|
62
|
+
// `resources:` joins them: a library's resource inputs are named in the
|
|
63
|
+
// same identifier space (`!ref connection`, `resources.connection`) and
|
|
64
|
+
// break the same way.
|
|
65
|
+
for (const field of ["variables", "secrets", "ports", "resources"]) {
|
|
57
66
|
const block = manifest[field];
|
|
58
67
|
if (!block || typeof block !== "object" || Array.isArray(block))
|
|
59
68
|
continue;
|
|
@@ -125,7 +134,13 @@ function surfaceFor(kind) {
|
|
|
125
134
|
}
|
|
126
135
|
}
|
|
127
136
|
function singular(field) {
|
|
128
|
-
return field === "variables"
|
|
137
|
+
return field === "variables"
|
|
138
|
+
? "variable"
|
|
139
|
+
: field === "secrets"
|
|
140
|
+
? "secret"
|
|
141
|
+
: field === "ports"
|
|
142
|
+
? "port"
|
|
143
|
+
: "resource input";
|
|
129
144
|
}
|
|
130
145
|
function push(out, violation, at) {
|
|
131
146
|
if (!violation)
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
2
|
import { type AnalysisDiagnostic, type AnalysisContext } from "./types.js";
|
|
3
|
+
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
4
|
+
/**
|
|
5
|
+
* Liskov substitutability at a kind constraint: is `resolved` (a canonical
|
|
6
|
+
* `<module>.<Kind>`) accepted where `targetKind` is required?
|
|
7
|
+
*
|
|
8
|
+
* THE one implementation — `checkKind` below builds a MESSAGE from it and adds
|
|
9
|
+
* nothing to the rule, so a resource input's injection site and an ordinary ref
|
|
10
|
+
* slot cannot come to disagree about what satisfies a constraint.
|
|
11
|
+
*
|
|
12
|
+
* A value satisfies the slot when it transitively extends the target kind, or —
|
|
13
|
+
* for a CONCRETE target — IS that kind; `getByExtends` is the same transitive
|
|
14
|
+
* subtype index for both, and an abstract is satisfied only by an implementer,
|
|
15
|
+
* never by the abstract itself (which is non-instantiable). Accepts a constraint
|
|
16
|
+
* that resolves to nothing, and an abstract with no loaded implementations:
|
|
17
|
+
* partial context, where a rejection would be a guess.
|
|
18
|
+
*/
|
|
19
|
+
export declare function kindSatisfies(resolved: string, targetKind: string, registry: DefinitionRegistry): boolean;
|
|
3
20
|
/**
|
|
4
21
|
* Phase 3 — Reference validation.
|
|
5
22
|
*
|
|
@@ -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;AAarD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,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;AAE/F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAInE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAwBT;AA8DD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA2gBtB"}
|
|
@@ -7,13 +7,67 @@ import { resolveTypeFieldToSchema } from "./validate-cel-context.js";
|
|
|
7
7
|
import { DiagnosticSeverity } from "./types.js";
|
|
8
8
|
const SOURCE = "telo-analyzer";
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Liskov substitutability at a kind constraint: is `resolved` (a canonical
|
|
11
|
+
* `<module>.<Kind>`) accepted where `targetKind` is required?
|
|
12
|
+
*
|
|
13
|
+
* THE one implementation — `checkKind` below builds a MESSAGE from it and adds
|
|
14
|
+
* nothing to the rule, so a resource input's injection site and an ordinary ref
|
|
15
|
+
* slot cannot come to disagree about what satisfies a constraint.
|
|
16
|
+
*
|
|
17
|
+
* A value satisfies the slot when it transitively extends the target kind, or —
|
|
18
|
+
* for a CONCRETE target — IS that kind; `getByExtends` is the same transitive
|
|
19
|
+
* subtype index for both, and an abstract is satisfied only by an implementer,
|
|
20
|
+
* never by the abstract itself (which is non-instantiable). Accepts a constraint
|
|
21
|
+
* that resolves to nothing, and an abstract with no loaded implementations:
|
|
22
|
+
* partial context, where a rejection would be a guess.
|
|
14
23
|
*/
|
|
24
|
+
export function kindSatisfies(resolved, targetKind, registry) {
|
|
25
|
+
const canonical = registry.resolveRef(targetKind) ?? targetKind;
|
|
26
|
+
const targetDef = registry.resolve(canonical);
|
|
27
|
+
if (!targetDef)
|
|
28
|
+
return true;
|
|
29
|
+
if (targetDef.kind !== "Telo.Abstract" && resolved === canonical)
|
|
30
|
+
return true;
|
|
31
|
+
const subtypes = registry.getByExtends(canonical);
|
|
32
|
+
if (subtypes.some((d) => `${d.metadata.module}.${d.metadata.name}` === resolved))
|
|
33
|
+
return true;
|
|
34
|
+
// Leniency is about the CANDIDATE, not about the population, and it is the
|
|
35
|
+
// same question for an abstract target and a concrete one. Accepting whenever
|
|
36
|
+
// no subtype happened to be loaded silenced the check exactly where it was
|
|
37
|
+
// needed: an app whose imports declare no `Telo.Runnable` is an app whose boot
|
|
38
|
+
// targets are all wrong, and every one of them passed.
|
|
39
|
+
//
|
|
40
|
+
// An UNREGISTERED candidate is not partial context, and the distinction is
|
|
41
|
+
// load-bearing: a kind nobody declared is an unknown kind or an unimported
|
|
42
|
+
// alias prefix (`NotAnAlias.Script`), which is precisely what this check
|
|
43
|
+
// exists to report. Only a kind that IS declared, whose ancestry reaches
|
|
44
|
+
// something this analysis never saw, is undecidable — there the missing hop is
|
|
45
|
+
// where the target it appears not to reach could have been declared.
|
|
46
|
+
// A candidate the registry never saw cannot be judged on what it implements —
|
|
47
|
+
// the analysis simply does not hold it. Whether its NAME is resolvable is a
|
|
48
|
+
// different question, asked of the alias scope by the caller.
|
|
49
|
+
if (!registry.resolve(resolved))
|
|
50
|
+
return true;
|
|
51
|
+
return !registry.ancestryResolved(resolved);
|
|
52
|
+
}
|
|
53
|
+
/** {@link kindSatisfies} at every kind a slot accepts, rendered as messages.
|
|
54
|
+
* The acceptance rule is not restated here — only what to say when it fails,
|
|
55
|
+
* which needs the slot's alternatives and the target's flavour. */
|
|
15
56
|
function checkKind(kind, entry, registry, aliases) {
|
|
16
57
|
const resolved = aliases.resolveKind(kind) ?? kind;
|
|
58
|
+
// A qualified kind whose prefix names no import in this scope is a bad NAME
|
|
59
|
+
// rather than a bad type — `{kind: NotAnAlias.Script}` where only `Lib` is
|
|
60
|
+
// imported — and this check is the only place it surfaces, so it is never
|
|
61
|
+
// waved through as partial context. Asked of the resolver, which reports
|
|
62
|
+
// `unknown` for exactly that case: a kind reached through a DECLARED alias
|
|
63
|
+
// resolves (`ok`) even when the target's definitions are not loaded, and a
|
|
64
|
+
// gated one says so separately.
|
|
65
|
+
const unknownAlias = kind.includes(".") &&
|
|
66
|
+
aliases.resolveKindResult(kind).status === "unknown" &&
|
|
67
|
+
// …and the registry does not hold it under the name as written either. A
|
|
68
|
+
// manifest may carry a canonical `<module>.<Kind>`, which no alias resolves
|
|
69
|
+
// and which is nonetheless perfectly identified.
|
|
70
|
+
registry.resolve(resolved) === undefined;
|
|
17
71
|
const errors = [];
|
|
18
72
|
for (const refStr of entry.refs) {
|
|
19
73
|
const targetKind = registry.resolveRef(refStr);
|
|
@@ -22,20 +76,11 @@ function checkKind(kind, entry, registry, aliases) {
|
|
|
22
76
|
const targetDef = registry.resolve(targetKind);
|
|
23
77
|
if (!targetDef)
|
|
24
78
|
return [];
|
|
25
|
-
|
|
26
|
-
// extends the target kind, or — for a CONCRETE target — IS that kind.
|
|
27
|
-
// `getByExtends` is the same transitive subtype index for abstract and
|
|
28
|
-
// concrete targets alike; an abstract is satisfied only by an implementer,
|
|
29
|
-
// never by the abstract kind itself (which is non-instantiable).
|
|
30
|
-
if (targetDef.kind !== "Telo.Abstract" && resolved === targetKind)
|
|
79
|
+
if (!unknownAlias && kindSatisfies(resolved, targetKind, registry))
|
|
31
80
|
return [];
|
|
32
81
|
const subtypes = registry.getByExtends(targetKind);
|
|
33
82
|
const subtypeKinds = new Set(subtypes.map((d) => `${d.metadata.module}.${d.metadata.name}`));
|
|
34
|
-
if (subtypeKinds.has(resolved))
|
|
35
|
-
return [];
|
|
36
83
|
if (targetDef.kind === "Telo.Abstract") {
|
|
37
|
-
if (subtypes.length === 0)
|
|
38
|
-
return []; // partial context — no implementations loaded yet
|
|
39
84
|
// Suggest only what an author can actually wire: with abstract-extends-
|
|
40
85
|
// abstract real (Telo.Executable over Invocable/Runnable), the transitive
|
|
41
86
|
// subtype list contains abstracts, which are non-instantiable and would
|
|
@@ -43,8 +88,15 @@ function checkKind(kind, entry, registry, aliases) {
|
|
|
43
88
|
const concrete = subtypes
|
|
44
89
|
.filter((d) => d.kind !== "Telo.Abstract")
|
|
45
90
|
.map((d) => `${d.metadata.module}.${d.metadata.name}`);
|
|
46
|
-
const options =
|
|
47
|
-
|
|
91
|
+
const options = concrete.length > 0 ? concrete : [...subtypeKinds];
|
|
92
|
+
// With nothing loaded that implements the target there is no list to
|
|
93
|
+
// offer, so the message says what this kind IS instead — which is the
|
|
94
|
+
// half the author can act on ("it declares 'Telo.Invocable'" points
|
|
95
|
+
// straight at a boot target that should have been an invoke step).
|
|
96
|
+
const declared = registry.resolve(resolved)?.capability;
|
|
97
|
+
errors.push(options.length > 0
|
|
98
|
+
? `'${kind}' does not implement '${targetKind}' (known implementations: ${options.join(", ")})`
|
|
99
|
+
: `'${kind}' does not implement '${targetKind}'${declared ? ` — it declares '${declared}'` : ""}`);
|
|
48
100
|
}
|
|
49
101
|
else {
|
|
50
102
|
const options = subtypeKinds.size > 0 ? ` or a subtype (${[...subtypeKinds].join(", ")})` : "";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import type { AliasResolver } from "./alias-resolver.js";
|
|
3
|
+
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
4
|
+
import { type AnalysisDiagnostic } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* The strict half of {@link readResourceInputs} — the `validate-ref-slots.ts`
|
|
7
|
+
* split applied to the resource-input boundary, and not optional for the same
|
|
8
|
+
* reason: the two sides fail in OPPOSITE directions. An unreadable DECLARATION
|
|
9
|
+
* leaves the library referencing a name nothing stands behind; an unchecked
|
|
10
|
+
* INJECTION hands a library an instance of a kind it never asked for, which
|
|
11
|
+
* surfaces as a method-missing failure inside someone else's module.
|
|
12
|
+
*
|
|
13
|
+
* Two surfaces, scoped the same way every other declaration check is — to the
|
|
14
|
+
* entry's own modules, since a published dependency's block is not the
|
|
15
|
+
* consumer's to fix:
|
|
16
|
+
*
|
|
17
|
+
* - **The library's own declaration.** Each entry's alias-qualified `kind:` must
|
|
18
|
+
* resolve in the DECLARING module's scope (`RESOURCE_INPUT_KIND_UNRESOLVED`).
|
|
19
|
+
* A constraint that resolves to nothing accepts anything, which is the same
|
|
20
|
+
* hole `X_TELO_REF_UNRESOLVED` exists to close one level down.
|
|
21
|
+
* - **The injection site.** Every declared entry must be supplied
|
|
22
|
+
* (`RESOURCE_INPUT_MISSING` — the same failure as a missing required
|
|
23
|
+
* variable), nothing beyond them may be (`RESOURCE_INPUT_UNKNOWN`), the value
|
|
24
|
+
* must name a resource that exists (`RESOURCE_INPUT_UNRESOLVED`), and its
|
|
25
|
+
* kind must satisfy the declared constraint transitively
|
|
26
|
+
* (`RESOURCE_INPUT_KIND_MISMATCH`).
|
|
27
|
+
*
|
|
28
|
+
* Browser-safe.
|
|
29
|
+
*/
|
|
30
|
+
export declare function validateResourceInputs(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver, rootModules: Set<string>,
|
|
31
|
+
/** Kind acceptance, transitively — the `checkKind` rule `validate-references`
|
|
32
|
+
* applies at an ordinary ref slot, passed in rather than re-derived so the
|
|
33
|
+
* two cannot disagree about what satisfies a constraint. */
|
|
34
|
+
acceptsKind: (suppliedKind: string, requiredKind: string) => boolean): AnalysisDiagnostic[];
|
|
35
|
+
//# sourceMappingURL=validate-resource-inputs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-resource-inputs.d.ts","sourceRoot":"","sources":["../src/validate-resource-inputs.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;AAGnE,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,YAAY,CAAC;AASzE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC;AACxB;;6DAE6D;AAC7D,WAAW,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GACnE,kBAAkB,EAAE,CA6LtB"}
|