@telorun/analyzer 0.12.1 → 0.13.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/analysis-registry.d.ts +12 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +15 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +64 -84
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +25 -0
- package/dist/cel-environment.d.ts +1 -1
- package/dist/cel-environment.d.ts.map +1 -1
- package/dist/cel-environment.js +40 -2
- package/dist/dependency-graph.d.ts.map +1 -1
- package/dist/dependency-graph.js +41 -62
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/kernel-globals.d.ts +1 -1
- package/dist/kernel-globals.d.ts.map +1 -1
- package/dist/kernel-globals.js +19 -1
- package/dist/manifest-visitor.d.ts +109 -0
- package/dist/manifest-visitor.d.ts.map +1 -0
- package/dist/manifest-visitor.js +110 -0
- package/dist/schema-compat.d.ts +10 -0
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +32 -0
- package/dist/validate-cel-context.d.ts +14 -0
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +38 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +117 -160
- package/dist/validate-unused-declarations.d.ts +25 -0
- package/dist/validate-unused-declarations.d.ts.map +1 -0
- package/dist/validate-unused-declarations.js +91 -0
- package/package.json +2 -2
- package/src/analysis-registry.ts +20 -0
- package/src/analyzer.ts +157 -169
- package/src/builtins.ts +25 -0
- package/src/cel-environment.ts +42 -1
- package/src/dependency-graph.ts +37 -52
- package/src/index.ts +11 -0
- package/src/kernel-globals.ts +22 -1
- package/src/manifest-visitor.ts +251 -0
- package/src/schema-compat.ts +32 -0
- package/src/validate-cel-context.ts +50 -0
- package/src/validate-references.ts +168 -211
- package/src/validate-unused-declarations.ts +95 -0
package/dist/kernel-globals.js
CHANGED
|
@@ -10,7 +10,7 @@ import { residualEntrySchemaMap } from "./residual-schema.js";
|
|
|
10
10
|
* There is no `imports` namespace at runtime — import snapshots are stored
|
|
11
11
|
* under `resources.<alias>`.
|
|
12
12
|
*/
|
|
13
|
-
export const KERNEL_GLOBAL_NAMES = ["variables", "secrets", "resources", "env"];
|
|
13
|
+
export const KERNEL_GLOBAL_NAMES = ["variables", "secrets", "resources", "ports", "env"];
|
|
14
14
|
const SYSTEM_KINDS = new Set([
|
|
15
15
|
"Telo.Definition",
|
|
16
16
|
"Telo.Application",
|
|
@@ -55,10 +55,28 @@ export function buildKernelGlobalsSchema(manifests) {
|
|
|
55
55
|
properties: resourceProps,
|
|
56
56
|
additionalProperties: false,
|
|
57
57
|
},
|
|
58
|
+
ports: buildPortsSchema(moduleManifest?.ports),
|
|
58
59
|
env: { type: "object", additionalProperties: true },
|
|
59
60
|
},
|
|
60
61
|
};
|
|
61
62
|
}
|
|
63
|
+
/** Build the closed `ports` chain-access schema: each declared port is an
|
|
64
|
+
* integer, so `ports.<name>` resolves and `ports.typo` (or member access past
|
|
65
|
+
* a port, like `ports.http.foo`) is flagged. Falls back to an open map when
|
|
66
|
+
* the module declares no ports. */
|
|
67
|
+
function buildPortsSchema(ports) {
|
|
68
|
+
if (!ports || typeof ports !== "object" || Array.isArray(ports)) {
|
|
69
|
+
return { type: "object", additionalProperties: true };
|
|
70
|
+
}
|
|
71
|
+
const props = {};
|
|
72
|
+
for (const name of Object.keys(ports)) {
|
|
73
|
+
props[name] = { type: "integer" };
|
|
74
|
+
}
|
|
75
|
+
if (Object.keys(props).length === 0) {
|
|
76
|
+
return { type: "object", additionalProperties: true };
|
|
77
|
+
}
|
|
78
|
+
return { type: "object", properties: props, additionalProperties: false };
|
|
79
|
+
}
|
|
62
80
|
/** Wrap a JSON Schema property map (like `Telo.Application.variables`) into a
|
|
63
81
|
* closed object schema suitable for chain-access validation. For Application
|
|
64
82
|
* entries the per-entry shape carries kernel-specific keys (`env`, `default`)
|
|
@@ -0,0 +1,109 @@
|
|
|
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 RefFieldEntry, type SchemaFromFieldEntry } from "./reference-field-map.js";
|
|
5
|
+
/**
|
|
6
|
+
* One descent surface over a manifest's resources, emitting the annotation
|
|
7
|
+
* sites every analyzer pass needs. It replaces the iteration scaffolding that
|
|
8
|
+
* `validate-references`, `dependency-graph`, and the analyzer's CEL walk each
|
|
9
|
+
* reimplemented (field-map fetch, scope collection, ref/schema-from iteration,
|
|
10
|
+
* CEL expression walk + context matching).
|
|
11
|
+
*
|
|
12
|
+
* Two discovery mechanics ride one per-resource pass:
|
|
13
|
+
*
|
|
14
|
+
* - **Path-driven** — ref / scope / schema-from sites come from the resource's
|
|
15
|
+
* per-kind field map (`RefSite`, `ScopeBoundary`, `SchemaFromSite`). This is
|
|
16
|
+
* map iteration resolved against the resource value, not a node-by-node tree
|
|
17
|
+
* descent; the field map already unifies all three annotation types.
|
|
18
|
+
* - **Value-tree-driven** — compiled `${{...}}` / `!cel` nodes are found by
|
|
19
|
+
* scanning the resource value tree (`CelSite`). CEL can sit in any string
|
|
20
|
+
* field, including ones the field map never lists, so its discovery is
|
|
21
|
+
* fundamentally not path-driven; the field map only supplies the matched
|
|
22
|
+
* `x-telo-context` schema at the enclosing path.
|
|
23
|
+
*
|
|
24
|
+
* Handlers are optional (Babel-style): the walker computes and emits only what
|
|
25
|
+
* the visitor subscribes to, and skips the work behind absent handlers.
|
|
26
|
+
*
|
|
27
|
+
* **Scope is per-resource.** `ScopeBoundary` is emitted once per resource at
|
|
28
|
+
* enter time, before that resource's `RefSite`s, carrying both the source
|
|
29
|
+
* enclosure prefixes (for refs written *inside* a scope) and the enclosed
|
|
30
|
+
* resource-name set (for consumers that drop edges to scoped targets). No
|
|
31
|
+
* cross-resource ordering or global enclosed-name union is implied — every
|
|
32
|
+
* consumer's scope decision is local to the resource being visited, matching
|
|
33
|
+
* the semantics each pass had before this walker existed.
|
|
34
|
+
*/
|
|
35
|
+
export interface ResourceEnterEvent {
|
|
36
|
+
source: ResourceManifest;
|
|
37
|
+
/** Resolved definition for the resource's kind, or undefined when unknown. */
|
|
38
|
+
definition?: ResourceDefinition;
|
|
39
|
+
}
|
|
40
|
+
export interface ResourceExitEvent {
|
|
41
|
+
source: ResourceManifest;
|
|
42
|
+
}
|
|
43
|
+
export interface ScopeBoundaryEvent {
|
|
44
|
+
source: ResourceManifest;
|
|
45
|
+
/** Dot-form prefixes of every `x-telo-scope` field on this resource. */
|
|
46
|
+
scopePrefixes: string[];
|
|
47
|
+
/** Scope-field JSON Pointer → manifests declared within that scope. */
|
|
48
|
+
manifestsByPointer: Map<string, ResourceManifest[]>;
|
|
49
|
+
/** Names of every resource declared inside this resource's scopes. Used by
|
|
50
|
+
* the dependency graph to drop boot edges to scoped (on-demand) targets. */
|
|
51
|
+
enclosedNames: Set<string>;
|
|
52
|
+
}
|
|
53
|
+
export interface RefSiteEvent {
|
|
54
|
+
source: ResourceManifest;
|
|
55
|
+
/** Field-map path with `[]` / `{}` markers (e.g. `steps[].invoke`). */
|
|
56
|
+
fieldPath: string;
|
|
57
|
+
/** Concrete path with `[N]` / map keys, matching `buildPositionIndex` keys. */
|
|
58
|
+
concretePath: string;
|
|
59
|
+
/** The ref value at this concrete site (sentinel, string, or `{kind,name}`). */
|
|
60
|
+
value: unknown;
|
|
61
|
+
/** The ref constraint (`refs[]`, `isArray`, optional `context`). */
|
|
62
|
+
entry: RefFieldEntry;
|
|
63
|
+
/** True when `fieldPath` falls within one of this resource's scope prefixes —
|
|
64
|
+
* source enclosure, used to scope a ref's candidate set. */
|
|
65
|
+
inScope: boolean;
|
|
66
|
+
/** Scope manifests visible to this ref path (non-empty only when `inScope`). */
|
|
67
|
+
visibleScopeManifests: ResourceManifest[];
|
|
68
|
+
}
|
|
69
|
+
export interface SchemaFromSiteEvent {
|
|
70
|
+
source: ResourceManifest;
|
|
71
|
+
/** Field-map path of the `x-telo-schema-from` slot. */
|
|
72
|
+
fieldPath: string;
|
|
73
|
+
entry: SchemaFromFieldEntry;
|
|
74
|
+
}
|
|
75
|
+
export interface CelSiteEvent {
|
|
76
|
+
source: ResourceManifest;
|
|
77
|
+
/** Concrete dotted path of the expression (from `walkCelExpressions`). */
|
|
78
|
+
path: string;
|
|
79
|
+
/** The CEL source expression. */
|
|
80
|
+
expr: string;
|
|
81
|
+
/** Engine that owns the expression (`cel`, `literal`, …). */
|
|
82
|
+
engineName: string;
|
|
83
|
+
/** Raw `x-telo-context` schema matched at the enclosing path, if any. The
|
|
84
|
+
* consumer resolves `x-telo-context-*` annotations and merges its own
|
|
85
|
+
* globals — the walker only does the path → context match. */
|
|
86
|
+
contextSchema?: Record<string, any>;
|
|
87
|
+
/** Scope of the matched context (e.g. `$.routes[*].handler`), if matched. */
|
|
88
|
+
matchedScope?: string;
|
|
89
|
+
}
|
|
90
|
+
export interface ManifestVisitor {
|
|
91
|
+
onResourceEnter?(e: ResourceEnterEvent): void;
|
|
92
|
+
onScope?(e: ScopeBoundaryEvent): void;
|
|
93
|
+
onRef?(e: RefSiteEvent): void;
|
|
94
|
+
onSchemaFrom?(e: SchemaFromSiteEvent): void;
|
|
95
|
+
onCel?(e: CelSiteEvent): void;
|
|
96
|
+
onResourceExit?(e: ResourceExitEvent): void;
|
|
97
|
+
}
|
|
98
|
+
export interface VisitOptions {
|
|
99
|
+
aliases?: AliasResolver;
|
|
100
|
+
aliasesByModule?: Map<string, AliasResolver>;
|
|
101
|
+
/** Resource kinds to skip entirely (kind blueprints, import metadata, …). */
|
|
102
|
+
skipKinds?: ReadonlySet<string>;
|
|
103
|
+
/** When true, ref / scope sites come from the schema-from-expanded field map
|
|
104
|
+
* so refs nested behind `x-telo-schema-from` are surfaced. `SchemaFromSite`
|
|
105
|
+
* events are always emitted from the base map regardless of this flag. */
|
|
106
|
+
expand?: boolean;
|
|
107
|
+
}
|
|
108
|
+
export declare function visitManifest(resources: ResourceManifest[], registry: DefinitionRegistry, visitor: ManifestVisitor, options?: VisitOptions): void;
|
|
109
|
+
//# sourceMappingURL=manifest-visitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-visitor.d.ts","sourceRoot":"","sources":["../src/manifest-visitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { walkCelExpressions } from "@telorun/templating";
|
|
2
|
+
import { isRefEntry, isSchemaFromEntry, isScopeEntry, resolveFieldEntries, resolveFieldValues, } from "./reference-field-map.js";
|
|
3
|
+
import { extractContextsFromSchema, pathMatchesScope } from "./validate-cel-context.js";
|
|
4
|
+
const scopePrefixOf = (pointer) => pointer.replace(/^\//, "").replace(/\//g, ".");
|
|
5
|
+
const pathUnderPrefix = (fieldPath, prefix) => fieldPath === prefix ||
|
|
6
|
+
fieldPath.startsWith(prefix + ".") ||
|
|
7
|
+
fieldPath.startsWith(prefix + "[");
|
|
8
|
+
export function visitManifest(resources, registry, visitor, options = {}) {
|
|
9
|
+
const { aliases, aliasesByModule, skipKinds, expand } = options;
|
|
10
|
+
const wantsRefs = !!visitor.onRef;
|
|
11
|
+
const wantsScope = !!visitor.onScope;
|
|
12
|
+
const wantsSchemaFrom = !!visitor.onSchemaFrom;
|
|
13
|
+
const wantsCel = !!visitor.onCel;
|
|
14
|
+
for (const r of resources) {
|
|
15
|
+
if (!r.metadata?.name || !r.kind)
|
|
16
|
+
continue;
|
|
17
|
+
if (skipKinds?.has(r.kind))
|
|
18
|
+
continue;
|
|
19
|
+
const resolvedKind = aliases?.resolveKind(r.kind);
|
|
20
|
+
const definition = registry.resolve(r.kind) ??
|
|
21
|
+
(resolvedKind ? registry.resolve(resolvedKind) : undefined);
|
|
22
|
+
visitor.onResourceEnter?.({ source: r, definition });
|
|
23
|
+
if (wantsRefs || wantsScope || wantsSchemaFrom) {
|
|
24
|
+
const baseMap = aliases
|
|
25
|
+
? registry.getFieldMapForKind(r.kind, aliases)
|
|
26
|
+
: registry.getFieldMap(r.kind);
|
|
27
|
+
// Expanded map drives ref/scope sites when requested; schema-from sites
|
|
28
|
+
// come from the base map (expansion replaces them with nested refs).
|
|
29
|
+
const refScopeMap = expand && aliases && aliasesByModule
|
|
30
|
+
? registry.expandedFieldMapForResource(r, aliases, aliasesByModule)
|
|
31
|
+
: baseMap;
|
|
32
|
+
if (refScopeMap && (wantsRefs || wantsScope)) {
|
|
33
|
+
const manifestsByPointer = new Map();
|
|
34
|
+
for (const [fieldPath, entry] of refScopeMap) {
|
|
35
|
+
if (!isScopeEntry(entry))
|
|
36
|
+
continue;
|
|
37
|
+
const raw = resolveFieldValues(r, fieldPath)
|
|
38
|
+
.flatMap((v) => (Array.isArray(v) ? v : [v]))
|
|
39
|
+
.filter((v) => !!v && typeof v === "object");
|
|
40
|
+
const pointers = Array.isArray(entry.scope) ? entry.scope : [entry.scope];
|
|
41
|
+
for (const pointer of pointers)
|
|
42
|
+
manifestsByPointer.set(pointer, raw);
|
|
43
|
+
}
|
|
44
|
+
const scopePrefixes = Array.from(manifestsByPointer.keys()).map(scopePrefixOf);
|
|
45
|
+
if (wantsScope) {
|
|
46
|
+
const enclosedNames = new Set();
|
|
47
|
+
for (const manifests of manifestsByPointer.values()) {
|
|
48
|
+
for (const m of manifests) {
|
|
49
|
+
const name = m.metadata?.name;
|
|
50
|
+
if (typeof name === "string")
|
|
51
|
+
enclosedNames.add(name);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
visitor.onScope({ source: r, scopePrefixes, manifestsByPointer, enclosedNames });
|
|
55
|
+
}
|
|
56
|
+
if (wantsRefs) {
|
|
57
|
+
for (const [fieldPath, entry] of refScopeMap) {
|
|
58
|
+
if (!isRefEntry(entry))
|
|
59
|
+
continue;
|
|
60
|
+
const inScope = scopePrefixes.some((prefix) => pathUnderPrefix(fieldPath, prefix));
|
|
61
|
+
const visibleScopeManifests = [];
|
|
62
|
+
if (inScope) {
|
|
63
|
+
for (const [pointer, manifests] of manifestsByPointer) {
|
|
64
|
+
if (pathUnderPrefix(fieldPath, scopePrefixOf(pointer))) {
|
|
65
|
+
visibleScopeManifests.push(...manifests);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const { value, path: concretePath } of resolveFieldEntries(r, fieldPath)) {
|
|
70
|
+
if (!value)
|
|
71
|
+
continue;
|
|
72
|
+
visitor.onRef({
|
|
73
|
+
source: r,
|
|
74
|
+
fieldPath,
|
|
75
|
+
concretePath,
|
|
76
|
+
value,
|
|
77
|
+
entry,
|
|
78
|
+
inScope,
|
|
79
|
+
visibleScopeManifests,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (wantsSchemaFrom && baseMap) {
|
|
86
|
+
for (const [fieldPath, entry] of baseMap) {
|
|
87
|
+
if (!isSchemaFromEntry(entry))
|
|
88
|
+
continue;
|
|
89
|
+
visitor.onSchemaFrom({ source: r, fieldPath, entry });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (wantsCel) {
|
|
94
|
+
const contexts = definition?.schema ? extractContextsFromSchema(definition.schema) : [];
|
|
95
|
+
walkCelExpressions(r, "", (expr, path, engineName) => {
|
|
96
|
+
let contextSchema;
|
|
97
|
+
let matchedScope;
|
|
98
|
+
for (const ctx of contexts) {
|
|
99
|
+
if (pathMatchesScope(path, ctx.scope)) {
|
|
100
|
+
contextSchema = ctx.schema;
|
|
101
|
+
matchedScope = ctx.scope;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
visitor.onCel({ source: r, path, expr, engineName, contextSchema, matchedScope });
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
visitor.onResourceExit?.({ source: r });
|
|
109
|
+
}
|
|
110
|
+
}
|
package/dist/schema-compat.d.ts
CHANGED
|
@@ -35,6 +35,16 @@ export declare function navigateJsonPointer(schema: unknown, pointer: string): u
|
|
|
35
35
|
* Stops and returns the current node when a union type (`anyOf`/`oneOf`) is reached.
|
|
36
36
|
* Returns `undefined` if any segment cannot be resolved. */
|
|
37
37
|
export declare function navigateSchemaToExprPath(schema: Record<string, any>, path: string): Record<string, any> | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Recognized `x-telo-type` value brands and the CEL primitive each refines.
|
|
40
|
+
* A brand is a nominal type the analyzer registers (see cel-environment.ts) so
|
|
41
|
+
* structurally-identical values (a `TcpPort` and a `UdpPort` are both integers)
|
|
42
|
+
* stay distinct for static wiring checks. Brands carry no runtime effect — the
|
|
43
|
+
* value flows as its base type. Add new brands here (e.g. `Url: "string"`).
|
|
44
|
+
*/
|
|
45
|
+
export declare const VALUE_BRAND_BASE: Record<string, string>;
|
|
46
|
+
/** Read a recognized `x-telo-type` brand off a schema, or undefined. */
|
|
47
|
+
export declare function brandOfSchema(schema: Record<string, any> | undefined): string | undefined;
|
|
38
48
|
/** Map a JSON Schema type annotation to a CEL type string. */
|
|
39
49
|
export declare function jsonSchemaToCelType(schema: Record<string, any> | undefined): string;
|
|
40
50
|
/** Check whether a CEL return type is compatible with a JSON Schema type constraint. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAOpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CAmB/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAOpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CAmB/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGnD,CAAC;AAEF,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGzF;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAyBnF;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAiChG;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAiB5E;AAID,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAOtG;AAED,gGAAgG;AAChG,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAUlF;AAED;iGACiG;AACjG,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,OAAO,CAqCT"}
|
package/dist/schema-compat.js
CHANGED
|
@@ -172,10 +172,29 @@ export function navigateSchemaToExprPath(schema, path) {
|
|
|
172
172
|
}
|
|
173
173
|
return current;
|
|
174
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Recognized `x-telo-type` value brands and the CEL primitive each refines.
|
|
177
|
+
* A brand is a nominal type the analyzer registers (see cel-environment.ts) so
|
|
178
|
+
* structurally-identical values (a `TcpPort` and a `UdpPort` are both integers)
|
|
179
|
+
* stay distinct for static wiring checks. Brands carry no runtime effect — the
|
|
180
|
+
* value flows as its base type. Add new brands here (e.g. `Url: "string"`).
|
|
181
|
+
*/
|
|
182
|
+
export const VALUE_BRAND_BASE = {
|
|
183
|
+
TcpPort: "int",
|
|
184
|
+
UdpPort: "int",
|
|
185
|
+
};
|
|
186
|
+
/** Read a recognized `x-telo-type` brand off a schema, or undefined. */
|
|
187
|
+
export function brandOfSchema(schema) {
|
|
188
|
+
const brand = schema?.["x-telo-type"];
|
|
189
|
+
return typeof brand === "string" && brand in VALUE_BRAND_BASE ? brand : undefined;
|
|
190
|
+
}
|
|
175
191
|
/** Map a JSON Schema type annotation to a CEL type string. */
|
|
176
192
|
export function jsonSchemaToCelType(schema) {
|
|
177
193
|
if (!schema || typeof schema !== "object")
|
|
178
194
|
return "dyn";
|
|
195
|
+
const brand = brandOfSchema(schema);
|
|
196
|
+
if (brand)
|
|
197
|
+
return brand;
|
|
179
198
|
if (schema.anyOf || schema.oneOf || schema.allOf)
|
|
180
199
|
return "dyn";
|
|
181
200
|
if (Array.isArray(schema.type))
|
|
@@ -206,6 +225,19 @@ export function jsonSchemaToCelType(schema) {
|
|
|
206
225
|
export function celTypeSatisfiesJsonSchema(celType, schema) {
|
|
207
226
|
if (celType === "dyn")
|
|
208
227
|
return true;
|
|
228
|
+
// Nominal value brands: when the expression's type is a recognized brand,
|
|
229
|
+
// a branded consuming field must match exactly (a UdpPort wired into a
|
|
230
|
+
// TcpPort-branded field is the error we want). An unbranded field accepts
|
|
231
|
+
// the brand as its base type — gradual typing, so a TcpPort flows freely
|
|
232
|
+
// into a plain integer field. (A plain integer into a branded field is also
|
|
233
|
+
// allowed: only a *conflicting* brand is rejected.)
|
|
234
|
+
const sourceBase = VALUE_BRAND_BASE[celType];
|
|
235
|
+
if (sourceBase) {
|
|
236
|
+
const fieldBrand = brandOfSchema(schema);
|
|
237
|
+
if (fieldBrand)
|
|
238
|
+
return fieldBrand === celType;
|
|
239
|
+
celType = sourceBase;
|
|
240
|
+
}
|
|
209
241
|
if (!schema.type && !schema.anyOf && !schema.oneOf && !schema.allOf)
|
|
210
242
|
return true;
|
|
211
243
|
if (schema.anyOf || schema.oneOf || schema.allOf)
|
|
@@ -83,4 +83,18 @@ export declare function resolveContextAnnotations(schema: Record<string, any>, m
|
|
|
83
83
|
* e.g. exprPath="routes[0].inputs.q", scope="$.routes[*].inputs" → manifest.routes[0]
|
|
84
84
|
*/
|
|
85
85
|
export declare function getManifestItem(exprPath: string, scope: string, manifest: Record<string, any>): Record<string, any>;
|
|
86
|
+
/**
|
|
87
|
+
* Walk a JSON Schema tree and collect all `x-telo-context` annotations,
|
|
88
|
+
* returning them as `{ scope, schema }` pairs using JSONPath-style scopes —
|
|
89
|
+
* the same format the analyzer uses for CEL context validation.
|
|
90
|
+
*
|
|
91
|
+
* Result is sorted by scope specificity (longer scope first) so that the
|
|
92
|
+
* per-expression resolver's first-match-wins logic picks the most-specific
|
|
93
|
+
* context. Without this, a broader ancestor scope (e.g. `$.resources[*]`)
|
|
94
|
+
* could shadow a narrower descendant scope whose activation differs.
|
|
95
|
+
*/
|
|
96
|
+
export declare function extractContextsFromSchema(schema: Record<string, any>, path?: string): Array<{
|
|
97
|
+
scope: string;
|
|
98
|
+
schema: Record<string, any>;
|
|
99
|
+
}>;
|
|
86
100
|
//# sourceMappingURL=validate-cel-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-cel-context.d.ts","sourceRoot":"","sources":["../src/validate-cel-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEtF,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,GAClC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA6BjC;AAED;;;;;;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,CAqGrB;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"}
|
|
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;AAEtF,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,GAClC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA6BjC;AAED;;;;;;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,CAqGrB;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"}
|
|
@@ -219,3 +219,41 @@ function navigatePath(obj, segments) {
|
|
|
219
219
|
}
|
|
220
220
|
return cur;
|
|
221
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Walk a JSON Schema tree and collect all `x-telo-context` annotations,
|
|
224
|
+
* returning them as `{ scope, schema }` pairs using JSONPath-style scopes —
|
|
225
|
+
* the same format the analyzer uses for CEL context validation.
|
|
226
|
+
*
|
|
227
|
+
* Result is sorted by scope specificity (longer scope first) so that the
|
|
228
|
+
* per-expression resolver's first-match-wins logic picks the most-specific
|
|
229
|
+
* context. Without this, a broader ancestor scope (e.g. `$.resources[*]`)
|
|
230
|
+
* could shadow a narrower descendant scope whose activation differs.
|
|
231
|
+
*/
|
|
232
|
+
export function extractContextsFromSchema(schema, path = "$") {
|
|
233
|
+
const all = collectContexts(schema, path);
|
|
234
|
+
return all.sort((a, b) => b.scope.length - a.scope.length);
|
|
235
|
+
}
|
|
236
|
+
function collectContexts(schema, path) {
|
|
237
|
+
if (!schema || typeof schema !== "object")
|
|
238
|
+
return [];
|
|
239
|
+
const results = [];
|
|
240
|
+
if (schema["x-telo-context"]) {
|
|
241
|
+
results.push({ scope: path, schema: schema["x-telo-context"] });
|
|
242
|
+
}
|
|
243
|
+
if (schema.properties) {
|
|
244
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
245
|
+
results.push(...collectContexts(value, `${path}.${key}`));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (schema.items && typeof schema.items === "object") {
|
|
249
|
+
results.push(...collectContexts(schema.items, `${path}[*]`));
|
|
250
|
+
}
|
|
251
|
+
for (const key of ["oneOf", "anyOf", "allOf"]) {
|
|
252
|
+
if (Array.isArray(schema[key])) {
|
|
253
|
+
for (const subschema of schema[key]) {
|
|
254
|
+
results.push(...collectContexts(subschema, path));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return results;
|
|
259
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-references.d.ts","sourceRoot":"","sources":["../src/validate-references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA4C/F;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,eAAe,GACvB,kBAAkB,EAAE,CA0YtB"}
|