@telorun/analyzer 0.53.0 → 0.55.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 +20 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +36 -3
- package/dist/analyzer.d.ts +3 -2
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +188 -26
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +32 -12
- package/dist/call-graph.d.ts +189 -0
- package/dist/call-graph.d.ts.map +1 -0
- package/dist/call-graph.js +617 -0
- package/dist/dependency-graph.d.ts +17 -7
- package/dist/dependency-graph.d.ts.map +1 -1
- package/dist/dependency-graph.js +36 -65
- package/dist/flatten-for-analyzer.d.ts +8 -0
- package/dist/flatten-for-analyzer.d.ts.map +1 -1
- package/dist/flatten-for-analyzer.js +32 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/manifest-navigation.d.ts +32 -0
- package/dist/manifest-navigation.d.ts.map +1 -0
- package/dist/manifest-navigation.js +91 -0
- package/dist/manifest-visitor.js +1 -1
- package/dist/ref-slot.d.ts +125 -0
- package/dist/ref-slot.d.ts.map +1 -0
- package/dist/ref-slot.js +226 -0
- package/dist/reference-field-map.d.ts +15 -1
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +29 -35
- package/dist/resolve-schema-ref-kinds.d.ts +4 -0
- package/dist/resolve-schema-ref-kinds.d.ts.map +1 -1
- package/dist/resolve-schema-ref-kinds.js +31 -8
- package/dist/resolve-zone-requirements.d.ts +110 -0
- package/dist/resolve-zone-requirements.d.ts.map +1 -0
- package/dist/resolve-zone-requirements.js +541 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-observed-state.d.ts +14 -13
- package/dist/validate-observed-state.d.ts.map +1 -1
- package/dist/validate-observed-state.js +21 -88
- package/dist/validate-ref-slots.d.ts +48 -0
- package/dist/validate-ref-slots.d.ts.map +1 -0
- package/dist/validate-ref-slots.js +219 -0
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +8 -1
- package/dist/validate-zone-slots.d.ts +39 -0
- package/dist/validate-zone-slots.d.ts.map +1 -0
- package/dist/validate-zone-slots.js +114 -0
- package/dist/zone-module-documents.d.ts +27 -0
- package/dist/zone-module-documents.d.ts.map +1 -0
- package/dist/zone-module-documents.js +1 -0
- package/dist/zone-slot.d.ts +61 -0
- package/dist/zone-slot.d.ts.map +1 -0
- package/dist/zone-slot.js +91 -0
- package/package.json +3 -3
- package/src/analysis-registry.ts +36 -2
- package/src/analyzer.ts +206 -24
- package/src/builtins.ts +32 -12
- package/src/call-graph.ts +827 -0
- package/src/dependency-graph.ts +34 -68
- package/src/flatten-for-analyzer.ts +32 -0
- package/src/index.ts +47 -0
- package/src/manifest-navigation.ts +91 -0
- package/src/manifest-visitor.ts +1 -1
- package/src/ref-slot.ts +273 -0
- package/src/reference-field-map.ts +39 -36
- package/src/resolve-schema-ref-kinds.ts +34 -7
- package/src/resolve-zone-requirements.ts +781 -0
- package/src/types.ts +8 -0
- package/src/validate-observed-state.ts +26 -92
- package/src/validate-ref-slots.ts +293 -0
- package/src/validate-references.ts +8 -1
- package/src/validate-zone-slots.ts +175 -0
- package/src/zone-module-documents.ts +27 -0
- package/src/zone-slot.ts +116 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single reader of the two execution-zone annotations —
|
|
3
|
+
* `x-telo-provides-zone` and `x-telo-requires-zone` (see
|
|
4
|
+
* `kernel/specs/execution-zones.md`). The analyzer's zone projection, the
|
|
5
|
+
* kernel's `withZone` / `requireZone`, and any editor surface all recognise a
|
|
6
|
+
* zone slot here and nowhere else, the same one-accessor rule `ref-slot.ts`
|
|
7
|
+
* established for `x-telo-ref`. Browser-safe: no Node built-ins.
|
|
8
|
+
*
|
|
9
|
+
* Accepted shapes:
|
|
10
|
+
*
|
|
11
|
+
* x-telo-provides-zone: true # uncorrelated — the zone is the kind
|
|
12
|
+
* x-telo-provides-zone: /connection # correlation-key pointer (own field)
|
|
13
|
+
*
|
|
14
|
+
* x-telo-requires-zone: Self.Transaction # uncorrelated string form
|
|
15
|
+
* x-telo-requires-zone: # object form
|
|
16
|
+
* zone: Self.Transaction
|
|
17
|
+
* key: [/connection, /transaction/connection] # ordered, first hit wins
|
|
18
|
+
* reason: the statement would execute outside any transaction
|
|
19
|
+
*/
|
|
20
|
+
/** A body slot that establishes the declaring kind's zone when dispatched
|
|
21
|
+
* through. The zone's identity is always the declaring kind — the annotation
|
|
22
|
+
* never names one, so provision-on-behalf-of is unrepresentable. */
|
|
23
|
+
export interface ProvidesZoneSlot {
|
|
24
|
+
/** Self-relative JSON pointer to the declaring kind's own field whose resolved
|
|
25
|
+
* reference the zone carries as its correlation payload. Absent =
|
|
26
|
+
* uncorrelated (`true`). */
|
|
27
|
+
key?: string;
|
|
28
|
+
}
|
|
29
|
+
/** A field declaring that its resource must be reached through a zone. */
|
|
30
|
+
export interface RequiresZoneSlot {
|
|
31
|
+
/** The providing kind, alias-qualified as authored (`Self.Transaction`,
|
|
32
|
+
* `<Alias>.<Kind>`) — canonical `<module>.<Kind>` once
|
|
33
|
+
* `resolveSchemaRefKinds` has rewritten it in the declaring scope. */
|
|
34
|
+
zone: string;
|
|
35
|
+
/** Ordered self-relative JSON pointers tried in order, first hit winning; a
|
|
36
|
+
* pointer may traverse a `!ref` into the referenced resource's own field.
|
|
37
|
+
* Empty = uncorrelated. */
|
|
38
|
+
key: string[];
|
|
39
|
+
/** The runtime consequence, quoted after the path in diagnostics. */
|
|
40
|
+
reason?: string;
|
|
41
|
+
}
|
|
42
|
+
/** Reads a schema node's provides-zone declaration, or undefined when it has
|
|
43
|
+
* none or the value is malformed (`validate-zone-slots` reports those). */
|
|
44
|
+
export declare function readProvidesZone(node: Record<string, any> | undefined): ProvidesZoneSlot | undefined;
|
|
45
|
+
/** True when the node carries `x-telo-provides-zone` in any shape, valid or not
|
|
46
|
+
* — the recognition test validation needs before it judges the value. */
|
|
47
|
+
export declare function hasProvidesZone(node: Record<string, any> | undefined): boolean;
|
|
48
|
+
/** Reads a schema node's requires-zone declaration, or undefined when it has
|
|
49
|
+
* none or the value is malformed (`validate-zone-slots` reports those). */
|
|
50
|
+
export declare function readRequiresZone(node: Record<string, any> | undefined): RequiresZoneSlot | undefined;
|
|
51
|
+
/** True when the node carries `x-telo-requires-zone` in any shape. */
|
|
52
|
+
export declare function hasRequiresZone(node: Record<string, any> | undefined): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Rewrites the requires-zone kind name in place, in whichever shape it is
|
|
55
|
+
* written — the write-side twin of {@link readRequiresZone}, mirroring
|
|
56
|
+
* `rewriteRefSlotKinds` so `resolveSchemaRefKinds` canonicalizes both
|
|
57
|
+
* annotations in one walk. `map` returns the replacement or `undefined` to
|
|
58
|
+
* leave the authored name untouched (idempotence + quotable diagnostics).
|
|
59
|
+
*/
|
|
60
|
+
export declare function rewriteRequiresZoneKind(annotationHolder: Record<string, any>, map: (kind: string) => string | undefined): void;
|
|
61
|
+
//# sourceMappingURL=zone-slot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zone-slot.d.ts","sourceRoot":"","sources":["../src/zone-slot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH;;qEAEqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B;;iCAE6B;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC/B;;2EAEuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb;;gCAE4B;IAC5B,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAYD;4EAC4E;AAC5E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CAKpG;AAED;0EAC0E;AAC1E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,OAAO,CAE9E;AAED;4EAC4E;AAC5E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,gBAAgB,GAAG,SAAS,CAWpG;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,OAAO,CAE9E;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACrC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACxC,IAAI,CAaN"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single reader of the two execution-zone annotations —
|
|
3
|
+
* `x-telo-provides-zone` and `x-telo-requires-zone` (see
|
|
4
|
+
* `kernel/specs/execution-zones.md`). The analyzer's zone projection, the
|
|
5
|
+
* kernel's `withZone` / `requireZone`, and any editor surface all recognise a
|
|
6
|
+
* zone slot here and nowhere else, the same one-accessor rule `ref-slot.ts`
|
|
7
|
+
* established for `x-telo-ref`. Browser-safe: no Node built-ins.
|
|
8
|
+
*
|
|
9
|
+
* Accepted shapes:
|
|
10
|
+
*
|
|
11
|
+
* x-telo-provides-zone: true # uncorrelated — the zone is the kind
|
|
12
|
+
* x-telo-provides-zone: /connection # correlation-key pointer (own field)
|
|
13
|
+
*
|
|
14
|
+
* x-telo-requires-zone: Self.Transaction # uncorrelated string form
|
|
15
|
+
* x-telo-requires-zone: # object form
|
|
16
|
+
* zone: Self.Transaction
|
|
17
|
+
* key: [/connection, /transaction/connection] # ordered, first hit wins
|
|
18
|
+
* reason: the statement would execute outside any transaction
|
|
19
|
+
*/
|
|
20
|
+
const PROVIDES = "x-telo-provides-zone";
|
|
21
|
+
const REQUIRES = "x-telo-requires-zone";
|
|
22
|
+
/** A self-relative JSON Pointer — the only correlation-key spelling the
|
|
23
|
+
* analyzer and the kernel read identically. Applied to BOTH the scalar and the
|
|
24
|
+
* list form: the kernel's walk splits on `/` and drops empty segments, so a
|
|
25
|
+
* bare `connection` would resolve there while the checker skipped it, and the
|
|
26
|
+
* two halves would disagree about what the manifest means. `validate-zone-slots`
|
|
27
|
+
* reports what this rejects. */
|
|
28
|
+
function isPointer(value) {
|
|
29
|
+
return typeof value === "string" && value.startsWith("/") && value.length > 1;
|
|
30
|
+
}
|
|
31
|
+
/** Reads a schema node's provides-zone declaration, or undefined when it has
|
|
32
|
+
* none or the value is malformed (`validate-zone-slots` reports those). */
|
|
33
|
+
export function readProvidesZone(node) {
|
|
34
|
+
const raw = node?.[PROVIDES];
|
|
35
|
+
if (raw === true)
|
|
36
|
+
return {};
|
|
37
|
+
if (isPointer(raw))
|
|
38
|
+
return { key: raw };
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/** True when the node carries `x-telo-provides-zone` in any shape, valid or not
|
|
42
|
+
* — the recognition test validation needs before it judges the value. */
|
|
43
|
+
export function hasProvidesZone(node) {
|
|
44
|
+
return node?.[PROVIDES] !== undefined;
|
|
45
|
+
}
|
|
46
|
+
/** Reads a schema node's requires-zone declaration, or undefined when it has
|
|
47
|
+
* none or the value is malformed (`validate-zone-slots` reports those). */
|
|
48
|
+
export function readRequiresZone(node) {
|
|
49
|
+
const raw = node?.[REQUIRES];
|
|
50
|
+
if (typeof raw === "string" && raw)
|
|
51
|
+
return { zone: raw, key: [] };
|
|
52
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
53
|
+
return undefined;
|
|
54
|
+
const obj = raw;
|
|
55
|
+
if (typeof obj.zone !== "string" || !obj.zone)
|
|
56
|
+
return undefined;
|
|
57
|
+
// One filter for both spellings — see `isPointer`.
|
|
58
|
+
const key = (Array.isArray(obj.key) ? obj.key : [obj.key]).filter(isPointer);
|
|
59
|
+
const slot = { zone: obj.zone, key };
|
|
60
|
+
if (typeof obj.reason === "string")
|
|
61
|
+
slot.reason = obj.reason;
|
|
62
|
+
return slot;
|
|
63
|
+
}
|
|
64
|
+
/** True when the node carries `x-telo-requires-zone` in any shape. */
|
|
65
|
+
export function hasRequiresZone(node) {
|
|
66
|
+
return node?.[REQUIRES] !== undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Rewrites the requires-zone kind name in place, in whichever shape it is
|
|
70
|
+
* written — the write-side twin of {@link readRequiresZone}, mirroring
|
|
71
|
+
* `rewriteRefSlotKinds` so `resolveSchemaRefKinds` canonicalizes both
|
|
72
|
+
* annotations in one walk. `map` returns the replacement or `undefined` to
|
|
73
|
+
* leave the authored name untouched (idempotence + quotable diagnostics).
|
|
74
|
+
*/
|
|
75
|
+
export function rewriteRequiresZoneKind(annotationHolder, map) {
|
|
76
|
+
const raw = annotationHolder[REQUIRES];
|
|
77
|
+
if (typeof raw === "string") {
|
|
78
|
+
const next = map(raw);
|
|
79
|
+
if (next !== undefined)
|
|
80
|
+
annotationHolder[REQUIRES] = next;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
84
|
+
return;
|
|
85
|
+
const obj = raw;
|
|
86
|
+
if (typeof obj.zone === "string") {
|
|
87
|
+
const next = map(obj.zone);
|
|
88
|
+
if (next !== undefined)
|
|
89
|
+
obj.zone = next;
|
|
90
|
+
}
|
|
91
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.0",
|
|
4
4
|
"description": "Telo Analyzer - Static manifest validator for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -42,13 +42,13 @@
|
|
|
42
42
|
"ajv-formats": "^3.0.1",
|
|
43
43
|
"jsonpath-plus": "^10.3.0",
|
|
44
44
|
"yaml": "^2.8.3",
|
|
45
|
-
"@telorun/templating": "0.
|
|
45
|
+
"@telorun/templating": "0.12.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
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.68.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@telorun/sdk": "*"
|
package/src/analysis-registry.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { visitManifest as runVisitManifest, type ManifestVisitor } from "./manif
|
|
|
7
7
|
import type { ContractDirection, DefResolver } from "./extends-resolution.js";
|
|
8
8
|
import { resolveContract } from "./invocation-contract.js";
|
|
9
9
|
import { isRefEntry, isScopeEntry } from "./reference-field-map.js";
|
|
10
|
+
import { resolveSchemaTypeRefs as resolveSchemaTypeRefsIn } from "./resolve-schema-type-refs.js";
|
|
10
11
|
import type { AnalysisContext } from "./types.js";
|
|
11
12
|
|
|
12
13
|
/** One reference field declared by a resource's definition, derived purely from
|
|
@@ -61,6 +62,21 @@ export class AnalysisRegistry {
|
|
|
61
62
|
return this.aliases.resolveKind(kind);
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
/** Rewrite `telo://Self/<type>` / `telo://<Alias>/<type>` schema references in
|
|
66
|
+
* `manifests` to the canonical id their type registered under, in the scope
|
|
67
|
+
* each doc was declared in. Idempotent — an already-canonical ref resolves to
|
|
68
|
+
* no module and is left alone.
|
|
69
|
+
*
|
|
70
|
+
* `analyze()` runs this over its own view; a caller holding a separately
|
|
71
|
+
* flattened projection (the kernel's build-time validator warm, which must
|
|
72
|
+
* resolve a `$ref` contract exactly as the runtime will) needs it applied to
|
|
73
|
+
* that projection too. Exposed as a method because the alias tables are this
|
|
74
|
+
* registry's, and handing them out would make every consumer's scoping its
|
|
75
|
+
* own problem. */
|
|
76
|
+
resolveSchemaTypeRefs(manifests: ResourceManifest[]): void {
|
|
77
|
+
resolveSchemaTypeRefsIn(manifests, this.aliases, this.aliasesByModule);
|
|
78
|
+
}
|
|
79
|
+
|
|
64
80
|
/**
|
|
65
81
|
* Iterates a resource's reference and scope fields as declared by its definition.
|
|
66
82
|
* Calls onRef for each plain reference field and onScope for each scope field.
|
|
@@ -177,12 +193,30 @@ export class AnalysisRegistry {
|
|
|
177
193
|
private capabilitiesForRefs(refs: string[]): string[] {
|
|
178
194
|
const out: string[] = [];
|
|
179
195
|
for (const ref of refs) {
|
|
180
|
-
const cap
|
|
181
|
-
|
|
196
|
+
for (const cap of this.leafCapabilitiesForRef(ref)) {
|
|
197
|
+
if (!out.includes(cap)) out.push(cap);
|
|
198
|
+
}
|
|
182
199
|
}
|
|
183
200
|
return out;
|
|
184
201
|
}
|
|
185
202
|
|
|
203
|
+
/** Like {@link capabilityForRef}, but a capability GROUP — an abstract with no
|
|
204
|
+
* `capability` of its own that other capability abstracts extend, i.e.
|
|
205
|
+
* `Telo.Executable` over Invocable and Runnable — expands to its leaves.
|
|
206
|
+
* Classification consumers (the editor's port flavor) match against leaf
|
|
207
|
+
* capabilities, so without expansion every `Telo.Executable` slot would fall
|
|
208
|
+
* out of both classification sets and silently stop rendering — and so would
|
|
209
|
+
* slots constrained to the next abstract-of-abstracts. */
|
|
210
|
+
private leafCapabilitiesForRef(xTeloRef: string): string[] {
|
|
211
|
+
const cap = this.capabilityForRef(xTeloRef);
|
|
212
|
+
if (!cap) return [];
|
|
213
|
+
const leaves = this.defs
|
|
214
|
+
.getByExtends(cap)
|
|
215
|
+
.filter((d) => d.kind === "Telo.Abstract" && !d.capability)
|
|
216
|
+
.map((d) => `${(d.metadata as { module?: string }).module}.${d.metadata.name}`);
|
|
217
|
+
return leaves.length > 0 ? leaves : [cap];
|
|
218
|
+
}
|
|
219
|
+
|
|
186
220
|
/**
|
|
187
221
|
* Walks a manifest's annotation sites (refs, scopes, schema-from, CEL) via
|
|
188
222
|
* the shared manifest visitor, bound to this registry's definitions and
|
package/src/analyzer.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
PERMISSIVE_CONTRACT,
|
|
23
23
|
resolveContract,
|
|
24
24
|
} from "./invocation-contract.js";
|
|
25
|
+
import { buildCallGraph } from "./call-graph.js";
|
|
25
26
|
import { buildDependencyGraph, formatCycle } from "./dependency-graph.js";
|
|
26
27
|
import {
|
|
27
28
|
buildKernelGlobalsSchema,
|
|
@@ -42,6 +43,13 @@ import { normalizeInlineResources } from "./normalize-inline-resources.js";
|
|
|
42
43
|
import { REF_VALIDATION_SKIP_KINDS } from "./system-kinds.js";
|
|
43
44
|
import { resolveRefSentinels } from "./resolve-ref-sentinels.js";
|
|
44
45
|
import { resolveSchemaRefKinds, type RefConstraintIssue } from "./resolve-schema-ref-kinds.js";
|
|
46
|
+
import { runZoneAnalysis, type ZoneExportCache } from "./resolve-zone-requirements.js";
|
|
47
|
+
import { validateZoneSlotDeclarations, type ZoneSlotIssue } from "./validate-zone-slots.js";
|
|
48
|
+
import {
|
|
49
|
+
validateDynamicSelectors,
|
|
50
|
+
validateRefSlotDeclarations,
|
|
51
|
+
type RefSlotIssue,
|
|
52
|
+
} from "./validate-ref-slots.js";
|
|
45
53
|
import { resolveSchemaTypeRefs } from "./resolve-schema-type-refs.js";
|
|
46
54
|
import { validateSchemaTypeRefs } from "./validate-schema-type-refs.js";
|
|
47
55
|
import { rewriteSyntheticOrigins } from "./rewrite-synthetic-origins.js";
|
|
@@ -521,24 +529,76 @@ function buildStepContextSchema(
|
|
|
521
529
|
return undefined;
|
|
522
530
|
}
|
|
523
531
|
|
|
524
|
-
/**
|
|
525
|
-
* Capabilities whose instances structurally expose no `invoke`/`run` method, so
|
|
526
|
-
* a step `invoke` of one always fails at runtime with ERR_RESOURCE_NOT_INVOKABLE
|
|
527
|
-
* (kernel dispatch checks method presence, not capability — evaluation-context.ts).
|
|
528
|
-
* `Telo.Service` is intentionally absent: some services are invocable (a function
|
|
529
|
-
* handler dispatched directly, e.g. `Lambda.Function`), so it can't be rejected
|
|
530
|
-
* statically without false positives. This is the sound subset of the runtime rule.
|
|
531
|
-
*/
|
|
532
532
|
/** The built-in namespace: globally resolvable, crossing no import boundary. */
|
|
533
533
|
const TELO_BUILTIN_MODULE = "Telo";
|
|
534
534
|
|
|
535
|
-
|
|
536
|
-
|
|
535
|
+
/** One mapping from a ref-slot issue to a diagnostic — the shape is identical
|
|
536
|
+
* for the schema-level and the manifest-level (dynamic selector) checks. */
|
|
537
|
+
function refSlotIssueDiagnostic(issue: RefSlotIssue): AnalysisDiagnostic {
|
|
538
|
+
return {
|
|
539
|
+
severity: DiagnosticSeverity.Error,
|
|
540
|
+
code: issue.code,
|
|
541
|
+
source: SOURCE,
|
|
542
|
+
message: issue.message,
|
|
543
|
+
data: {
|
|
544
|
+
resource: { kind: issue.manifest.kind, name: issue.manifest.metadata?.name as string },
|
|
545
|
+
filePath: (issue.manifest.metadata as { source?: string } | undefined)?.source,
|
|
546
|
+
path: issue.path,
|
|
547
|
+
},
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Is this kind acceptable in a slot that transfers control?
|
|
553
|
+
*
|
|
554
|
+
* The executable side is a POSITIVE test against the `Telo.Executable`
|
|
555
|
+
* hierarchy: a capability that is, or `extends`, `Telo.Executable` is
|
|
556
|
+
* executable, so a future executable capability opts in with one `extends:`
|
|
557
|
+
* edge — no name list here to keep in step. This replaced
|
|
558
|
+
* `NON_INVOKABLE_CAPABILITIES`, a maintained set of capabilities the analyzer
|
|
559
|
+
* believed could never be invoked, which was unsound in the direction that
|
|
560
|
+
* rejects working manifests: it listed `Telo.Provider`, and `Ai.Model` is a
|
|
561
|
+
* Provider the agent controller invokes directly — the divergence being
|
|
562
|
+
* structural versus nominal, since the kernel tests method presence at dispatch
|
|
563
|
+
* while any static test can only read a declared capability.
|
|
564
|
+
*
|
|
565
|
+
* Outside the hierarchy nothing is guessed: `Telo.Provider` (entry points by
|
|
566
|
+
* convention) and `Telo.Service` (some services are invocable) pass, an unknown
|
|
567
|
+
* capability passes (rejecting it would be the wrong polarity for third-party
|
|
568
|
+
* extensions), and only the kernel-owned capabilities whose CONTRACT has no
|
|
569
|
+
* entry point — `Telo.Type`, `Telo.Mount`, `Telo.Template`, `Telo.Sink` (a sink
|
|
570
|
+
* is written to through a direct contract on the controller instance, never
|
|
571
|
+
* dispatched) — are rejected. That last set is the capabilities' own
|
|
572
|
+
* definition, not a belief about controllers.
|
|
573
|
+
*/
|
|
574
|
+
function isExecutableKind(kind: string, defs: DefinitionRegistry, aliases: AliasResolver): boolean {
|
|
575
|
+
const canonical = aliases.resolveKind(kind) ?? kind;
|
|
576
|
+
const def = defs.resolve(canonical);
|
|
577
|
+
if (!def) return true;
|
|
578
|
+
const capability = def.capability as string | undefined;
|
|
579
|
+
if (!capability) return true;
|
|
580
|
+
if (capabilityExtendsExecutable(capability, defs)) return true;
|
|
581
|
+
return !NO_ENTRY_POINT_CAPABILITIES.has(capability);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/** Does this capability name `Telo.Executable` or extend it, transitively?
|
|
585
|
+
* Derived from the abstract hierarchy at call time, never from a name list. */
|
|
586
|
+
function capabilityExtendsExecutable(capability: string, defs: DefinitionRegistry): boolean {
|
|
587
|
+
let current: string | undefined = capability;
|
|
588
|
+
const seen = new Set<string>();
|
|
589
|
+
while (current && !seen.has(current)) {
|
|
590
|
+
if (current === "Telo.Executable") return true;
|
|
591
|
+
seen.add(current);
|
|
592
|
+
current = defs.resolve(current)?.extends as string | undefined;
|
|
593
|
+
}
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/** Kernel-owned capabilities whose contract declares no entry point. */
|
|
598
|
+
const NO_ENTRY_POINT_CAPABILITIES = new Set([
|
|
537
599
|
"Telo.Mount",
|
|
538
600
|
"Telo.Type",
|
|
539
601
|
"Telo.Template",
|
|
540
|
-
// A sink is written to through a direct contract on the controller instance,
|
|
541
|
-
// never dispatched — so invoking one is statically wrong.
|
|
542
602
|
"Telo.Sink",
|
|
543
603
|
]);
|
|
544
604
|
|
|
@@ -557,9 +617,10 @@ const NON_INVOKABLE_CAPABILITIES = new Set([
|
|
|
557
617
|
* *kind* instead of an exported instance (`!ref Stream.Of`) — is a still-a-
|
|
558
618
|
* sentinel after Phase 2.5 resolution → `UNRESOLVED_REFERENCE` (runtime
|
|
559
619
|
* `ERR_RESOURCE_NOT_FOUND`).
|
|
560
|
-
* - Invokability: a resolved instance whose
|
|
561
|
-
*
|
|
562
|
-
*
|
|
620
|
+
* - Invokability: a resolved instance whose kind fails `isExecutableKind` —
|
|
621
|
+
* outside the `Telo.Executable` hierarchy AND declaring a capability whose
|
|
622
|
+
* contract has no entry point → `REFERENCE_KIND_MISMATCH` (runtime
|
|
623
|
+
* `ERR_RESOURCE_NOT_INVOKABLE`).
|
|
563
624
|
*
|
|
564
625
|
* Generic and topology-driven — it walks steps via the same `x-telo-step-context`
|
|
565
626
|
* / `x-telo-topology-role` annotations `buildStepContextSchema` uses (through the
|
|
@@ -656,15 +717,13 @@ function validateStepInvokeReferences(
|
|
|
656
717
|
// Resolved `{kind, name}` (or an inline `{kind, …}` definition) — the
|
|
657
718
|
// instance exists. Mirror the kernel's ERR_RESOURCE_NOT_INVOKABLE, which
|
|
658
719
|
// fires when the instance has neither an `invoke` nor a `run` method
|
|
659
|
-
// (evaluation-context.ts). That is a per-instance property,
|
|
660
|
-
//
|
|
661
|
-
// are rejected statically — Service is intentionally excluded, since some
|
|
662
|
-
// services are invocable (e.g. a function handler dispatched directly).
|
|
720
|
+
// (evaluation-context.ts). That is a per-instance property, so the static
|
|
721
|
+
// test only rejects a kind whose declared capability names no entry point.
|
|
663
722
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
664
723
|
const kind = (value as Record<string, unknown>).kind;
|
|
665
724
|
if (typeof kind !== "string") return;
|
|
666
|
-
|
|
667
|
-
|
|
725
|
+
if (!isExecutableKind(kind, defs, aliases)) {
|
|
726
|
+
const capability = defs.resolve(aliases.resolveKind(kind) ?? kind)?.capability;
|
|
668
727
|
diagnostics.push({
|
|
669
728
|
severity: DiagnosticSeverity.Error,
|
|
670
729
|
code: "REFERENCE_KIND_MISMATCH",
|
|
@@ -986,6 +1045,7 @@ export class StaticAnalyzer {
|
|
|
986
1045
|
manifests: ResourceManifest[],
|
|
987
1046
|
options?: AnalysisOptions,
|
|
988
1047
|
registry?: AnalysisRegistry,
|
|
1048
|
+
zoneExportCache?: ZoneExportCache,
|
|
989
1049
|
): AnalysisDiagnostic[] {
|
|
990
1050
|
assertManifestPositions(manifests);
|
|
991
1051
|
const diagnostics: AnalysisDiagnostic[] = [];
|
|
@@ -1164,6 +1224,8 @@ export class StaticAnalyzer {
|
|
|
1164
1224
|
// of alias choices. `capability` covers the legacy implements-this-abstract overload;
|
|
1165
1225
|
// `extends` is the canonical first-class form.
|
|
1166
1226
|
const refConstraintIssues: RefConstraintIssue[] = [];
|
|
1227
|
+
const refSlotIssues: RefSlotIssue[] = [];
|
|
1228
|
+
const zoneSlotIssues: ZoneSlotIssue[] = [];
|
|
1167
1229
|
for (const m of manifests) {
|
|
1168
1230
|
if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract") continue;
|
|
1169
1231
|
const def = m as unknown as ResourceDefinition;
|
|
@@ -1180,10 +1242,38 @@ export class StaticAnalyzer {
|
|
|
1180
1242
|
// still on the deprecated form — or with a constraint that no longer
|
|
1181
1243
|
// resolves — is not the consumer's to fix, and every import would
|
|
1182
1244
|
// otherwise flood `telo check` with unactionable noise.
|
|
1183
|
-
if (!ownModule || rootModules.has(ownModule))
|
|
1245
|
+
if (!ownModule || rootModules.has(ownModule)) {
|
|
1246
|
+
refConstraintIssues.push(...issues);
|
|
1247
|
+
refSlotIssues.push(...validateRefSlotDeclarations(m as unknown as ResourceManifest));
|
|
1248
|
+
zoneSlotIssues.push(...validateZoneSlotDeclarations(m as unknown as ResourceManifest));
|
|
1249
|
+
}
|
|
1184
1250
|
const resolvedCapability = def.capability
|
|
1185
1251
|
? (scopeResolver.resolveKind(def.capability) ?? def.capability)
|
|
1186
1252
|
: def.capability;
|
|
1253
|
+
// `Telo.Executable` is a slot constraint — the x-telo-ref parent of
|
|
1254
|
+
// Invocable and Runnable — and names no lifecycle role. The kernel
|
|
1255
|
+
// rejects it at load; without this the analyzer would report "no issues"
|
|
1256
|
+
// on a manifest that cannot boot.
|
|
1257
|
+
if (
|
|
1258
|
+
resolvedCapability === "Telo.Executable" &&
|
|
1259
|
+
(!ownModule || rootModules.has(ownModule))
|
|
1260
|
+
) {
|
|
1261
|
+
diagnostics.push({
|
|
1262
|
+
severity: DiagnosticSeverity.Error,
|
|
1263
|
+
code: "CAPABILITY_NOT_DECLARABLE",
|
|
1264
|
+
source: SOURCE,
|
|
1265
|
+
message:
|
|
1266
|
+
`'${def.metadata?.name}' declares capability: Telo.Executable, which is an ` +
|
|
1267
|
+
`x-telo-ref slot constraint (the parent Telo.Invocable and Telo.Runnable extend), ` +
|
|
1268
|
+
`not a declarable lifecycle role. Declare 'Telo.Invocable' (invoke) or ` +
|
|
1269
|
+
`'Telo.Runnable' (run) instead.`,
|
|
1270
|
+
data: {
|
|
1271
|
+
resource: { kind: m.kind, name: m.metadata?.name as string },
|
|
1272
|
+
filePath: (m.metadata as { source?: string } | undefined)?.source,
|
|
1273
|
+
path: "capability",
|
|
1274
|
+
},
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1187
1277
|
const resolvedExtends = def.extends
|
|
1188
1278
|
? (scopeResolver.resolveKind(def.extends) ?? def.extends)
|
|
1189
1279
|
: def.extends;
|
|
@@ -1213,6 +1303,24 @@ export class StaticAnalyzer {
|
|
|
1213
1303
|
};
|
|
1214
1304
|
const filePath = (issue.manifest.metadata as { source?: string } | undefined)?.source;
|
|
1215
1305
|
const data = { resource, filePath, path: issue.path };
|
|
1306
|
+
if (issue.annotation === "zone") {
|
|
1307
|
+
// Mirrors X_TELO_REF_UNRESOLVED: an unresolvable provider kind would
|
|
1308
|
+
// leave the requirement silently unenforced — no provider ever
|
|
1309
|
+
// matches a kind that does not exist.
|
|
1310
|
+
diagnostics.push({
|
|
1311
|
+
severity: DiagnosticSeverity.Error,
|
|
1312
|
+
code: "ZONE_PROVIDER_UNRESOLVED",
|
|
1313
|
+
source: SOURCE,
|
|
1314
|
+
message:
|
|
1315
|
+
`x-telo-requires-zone '${issue.ref}' at '${issue.path}' names no kind. The prefix ` +
|
|
1316
|
+
`must be an import alias declared in this file's 'imports:' map, 'Self' for a kind ` +
|
|
1317
|
+
`in this library, or 'Telo' for a built-in. An unresolvable zone would leave the ` +
|
|
1318
|
+
`requirement silently unenforced. Known aliases: ` +
|
|
1319
|
+
`${issue.knownAliases?.join(", ") || "(none)"}.`,
|
|
1320
|
+
data,
|
|
1321
|
+
});
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1216
1324
|
if (issue.reason === "legacy") {
|
|
1217
1325
|
diagnostics.push({
|
|
1218
1326
|
severity: DiagnosticSeverity.Warning,
|
|
@@ -1253,6 +1361,30 @@ export class StaticAnalyzer {
|
|
|
1253
1361
|
}
|
|
1254
1362
|
}
|
|
1255
1363
|
diagnostics.push(...validateReferenceForms(manifests, defs, aliases, aliasesByModule));
|
|
1364
|
+
// The x-telo-ref annotation's own validity — the strict half of the
|
|
1365
|
+
// accessor split; `readRefSlot` stays lenient so surfaces keep working
|
|
1366
|
+
// mid-migration, and this reports what leniency would silently absorb.
|
|
1367
|
+
for (const issue of refSlotIssues) diagnostics.push(refSlotIssueDiagnostic(issue));
|
|
1368
|
+
// Same split for the two zone annotations. Unreadable ones fail in
|
|
1369
|
+
// OPPOSITE directions — a dropped requirement is silently unenforced, a
|
|
1370
|
+
// dropped provision invents failures — so neither can be left to
|
|
1371
|
+
// leniency.
|
|
1372
|
+
for (const issue of zoneSlotIssues) {
|
|
1373
|
+
diagnostics.push({
|
|
1374
|
+
severity: DiagnosticSeverity.Error,
|
|
1375
|
+
code: issue.code,
|
|
1376
|
+
source: SOURCE,
|
|
1377
|
+
message: issue.message,
|
|
1378
|
+
data: {
|
|
1379
|
+
resource: {
|
|
1380
|
+
kind: issue.manifest.kind,
|
|
1381
|
+
name: issue.manifest.metadata?.name as string,
|
|
1382
|
+
},
|
|
1383
|
+
filePath: (issue.manifest.metadata as { source?: string } | undefined)?.source,
|
|
1384
|
+
path: issue.path,
|
|
1385
|
+
},
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1256
1388
|
}
|
|
1257
1389
|
|
|
1258
1390
|
// Phase 2: extract inline resources from x-telo-ref slots into first-class manifests
|
|
@@ -1264,6 +1396,55 @@ export class StaticAnalyzer {
|
|
|
1264
1396
|
// original and inline-extracted manifests have their sentinels resolved.
|
|
1265
1397
|
resolveRefSentinels(allManifests, aliases, aliasesByModule, [], defs);
|
|
1266
1398
|
|
|
1399
|
+
// ONE typed reference graph per analysis. Both graph consumers in this
|
|
1400
|
+
// pass (the dynamic-selector check and run-reachability) read the same
|
|
1401
|
+
// build — constructing it per consumer is strictly more work than the
|
|
1402
|
+
// walks it replaced, and performance is a core goal. Lazy, so a
|
|
1403
|
+
// skipValidation pass with no observed state builds nothing.
|
|
1404
|
+
let callGraphMemo: ReturnType<typeof buildCallGraph> | undefined;
|
|
1405
|
+
const getCallGraph = () =>
|
|
1406
|
+
(callGraphMemo ??= buildCallGraph(allManifests as unknown as ResourceManifest[], defs, {
|
|
1407
|
+
aliases,
|
|
1408
|
+
aliasesByModule,
|
|
1409
|
+
}));
|
|
1410
|
+
|
|
1411
|
+
// A `use` case map's selector written in CEL is a hard diagnostic — a call
|
|
1412
|
+
// graph known only at runtime is not statically analyzable, and no fallback
|
|
1413
|
+
// is conservative for every consumer. Scoped to the entry's own modules:
|
|
1414
|
+
// a published dependency's manifest is not the consumer's to fix.
|
|
1415
|
+
if (!options?.skipValidation) {
|
|
1416
|
+
for (const issue of validateDynamicSelectors(
|
|
1417
|
+
allManifests as unknown as ResourceManifest[],
|
|
1418
|
+
defs,
|
|
1419
|
+
aliases,
|
|
1420
|
+
aliasesByModule,
|
|
1421
|
+
getCallGraph(),
|
|
1422
|
+
)) {
|
|
1423
|
+
const ownModule = (issue.manifest.metadata as { module?: string } | undefined)?.module;
|
|
1424
|
+
if (ownModule && !rootModules.has(ownModule)) continue;
|
|
1425
|
+
diagnostics.push(refSlotIssueDiagnostic(issue));
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// Zone requirements — a projection over the same graph: propagate along
|
|
1429
|
+
// `call` edges, discharge at providing slots under correlation, fire at
|
|
1430
|
+
// terminating edges and boot. Imported libraries' export contracts are
|
|
1431
|
+
// derived over their full documents (options.moduleDocuments — the
|
|
1432
|
+
// flattened view no longer holds their internal dispatch chains), cached
|
|
1433
|
+
// per library in the host-lifetime `zoneExportCache`.
|
|
1434
|
+
diagnostics.push(
|
|
1435
|
+
...runZoneAnalysis({
|
|
1436
|
+
manifests: allManifests as unknown as ResourceManifest[],
|
|
1437
|
+
graph: getCallGraph(),
|
|
1438
|
+
defs,
|
|
1439
|
+
aliases,
|
|
1440
|
+
aliasesByModule,
|
|
1441
|
+
rootModules,
|
|
1442
|
+
moduleDocuments: options?.moduleDocuments,
|
|
1443
|
+
cache: zoneExportCache,
|
|
1444
|
+
}),
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1267
1448
|
// Phase 2.6: register each named `Telo.Type` resource's schema under its
|
|
1268
1449
|
// canonical module-scoped id (`telo://<module>/<name>`), validate
|
|
1269
1450
|
// `telo://Self|Alias/Type` schema refs resolve to one, then rewrite those
|
|
@@ -1427,7 +1608,7 @@ export class StaticAnalyzer {
|
|
|
1427
1608
|
const observedState = buildObservedStateIndex(allManifests, defs, aliases, moduleScopes);
|
|
1428
1609
|
const reportsObservedState = [...observedState.values()].some((r) => r.status);
|
|
1429
1610
|
const runReachable = reportsObservedState
|
|
1430
|
-
? collectRunReachableNames(
|
|
1611
|
+
? collectRunReachableNames(getCallGraph())
|
|
1431
1612
|
: new Set<string>();
|
|
1432
1613
|
|
|
1433
1614
|
// Build typed kernel globals schema so x-telo-context chain validation
|
|
@@ -2158,8 +2339,9 @@ export class StaticAnalyzer {
|
|
|
2158
2339
|
manifests: ResourceManifest[],
|
|
2159
2340
|
options?: AnalysisOptions,
|
|
2160
2341
|
registry?: AnalysisRegistry,
|
|
2342
|
+
zoneExportCache?: ZoneExportCache,
|
|
2161
2343
|
): AnalysisDiagnostic[] {
|
|
2162
|
-
return this.analyze(manifests, options, registry).filter(
|
|
2344
|
+
return this.analyze(manifests, options, registry, zoneExportCache).filter(
|
|
2163
2345
|
(d) => d.severity === DiagnosticSeverity.Error,
|
|
2164
2346
|
);
|
|
2165
2347
|
}
|
package/src/builtins.ts
CHANGED
|
@@ -145,7 +145,9 @@ const ROOT_LOGGING_SCHEMA = {
|
|
|
145
145
|
type: "array",
|
|
146
146
|
items: {
|
|
147
147
|
type: "object",
|
|
148
|
-
|
|
148
|
+
// A sink is written to directly by the logging pipeline, never through
|
|
149
|
+
// `ctx.invoke` — so from the Application's side it is held, not called.
|
|
150
|
+
"x-telo-ref": { kind: "Telo.LogSink", use: "dependency" },
|
|
149
151
|
"x-telo-inline": true,
|
|
150
152
|
},
|
|
151
153
|
},
|
|
@@ -155,9 +157,22 @@ const ROOT_LOGGING_SCHEMA = {
|
|
|
155
157
|
|
|
156
158
|
export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
157
159
|
{ kind: "Telo.Abstract", metadata: { name: "Template", module: "Telo" } },
|
|
158
|
-
|
|
160
|
+
// "Control can be transferred to this" — the parent of Invocable and Runnable,
|
|
161
|
+
// and the only thing a slot that accepts either needs to say. It is a SLOT
|
|
162
|
+
// CONSTRAINT, never a lifecycle role: `capability: Telo.Executable` is rejected
|
|
163
|
+
// because it is absent from the kernel's `KNOWN_CAPABILITIES` enum, which is
|
|
164
|
+
// what keeps "what a resource is" and "what a slot does with it" separate.
|
|
165
|
+
//
|
|
166
|
+
// `Telo.Service` is deliberately NOT under it. A Service's `run()` is a
|
|
167
|
+
// lifecycle start the kernel dispatches differently (no ambient scope, so
|
|
168
|
+
// inbound work roots its own trace), and admitting it here would make every
|
|
169
|
+
// step's `invoke:` slot accept a service. Boot-target slots that genuinely take
|
|
170
|
+
// `Runnable | Service` stay kind lists — the honest shape for a heterogeneous
|
|
171
|
+
// set.
|
|
172
|
+
{ kind: "Telo.Abstract", metadata: { name: "Executable", module: "Telo" } },
|
|
173
|
+
{ kind: "Telo.Abstract", metadata: { name: "Runnable", module: "Telo" }, extends: "Telo.Executable" },
|
|
159
174
|
{ kind: "Telo.Abstract", metadata: { name: "Service", module: "Telo" } },
|
|
160
|
-
{ kind: "Telo.Abstract", metadata: { name: "Invocable", module: "Telo" } },
|
|
175
|
+
{ kind: "Telo.Abstract", metadata: { name: "Invocable", module: "Telo" }, extends: "Telo.Executable" },
|
|
161
176
|
{ kind: "Telo.Abstract", metadata: { name: "Mount", module: "Telo" } },
|
|
162
177
|
{ kind: "Telo.Abstract", metadata: { name: "Type", module: "Telo" } },
|
|
163
178
|
{
|
|
@@ -558,12 +573,16 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
558
573
|
"x-telo-step-context": { invoke: "invoke", outputType: "outputType" },
|
|
559
574
|
type: "array",
|
|
560
575
|
items: {
|
|
576
|
+
// A genuinely heterogeneous set stays a kind list: `Telo.Service` is
|
|
577
|
+
// deliberately outside `Telo.Executable`, since a service's `run()`
|
|
578
|
+
// is a lifecycle start the kernel dispatches without an ambient
|
|
579
|
+
// scope.
|
|
580
|
+
"x-telo-ref": { kind: ["Telo.Runnable", "Telo.Service"], use: "call" },
|
|
561
581
|
anyOf: [
|
|
562
|
-
{ type: "string"
|
|
563
|
-
{ type: "string", "x-telo-ref": "Telo.Service" },
|
|
582
|
+
{ type: "string" },
|
|
564
583
|
// Post-resolution shape that `resolveRefSentinels`
|
|
565
584
|
// substitutes a `!ref <name>` sentinel into. The
|
|
566
|
-
// adjacent `x-telo-ref`
|
|
585
|
+
// adjacent `x-telo-ref` constraint governs the kind
|
|
567
586
|
// check; this branch only admits the structural form so
|
|
568
587
|
// AJV doesn't reject a resolved ref.
|
|
569
588
|
{
|
|
@@ -583,9 +602,9 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
583
602
|
required: ["ref"],
|
|
584
603
|
properties: {
|
|
585
604
|
ref: {
|
|
605
|
+
"x-telo-ref": { kind: ["Telo.Runnable", "Telo.Service"], use: "call" },
|
|
586
606
|
anyOf: [
|
|
587
|
-
{ type: "string"
|
|
588
|
-
{ type: "string", "x-telo-ref": "Telo.Service" },
|
|
607
|
+
{ type: "string" },
|
|
589
608
|
{
|
|
590
609
|
type: "object",
|
|
591
610
|
required: ["kind", "name"],
|
|
@@ -617,6 +636,11 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
617
636
|
name: { type: "string" },
|
|
618
637
|
invoke: {
|
|
619
638
|
"x-telo-topology-role": "invoke",
|
|
639
|
+
"x-telo-ref": {
|
|
640
|
+
kind: "Telo.Executable",
|
|
641
|
+
use: "call",
|
|
642
|
+
inputs: "/inputs",
|
|
643
|
+
},
|
|
620
644
|
type: "object",
|
|
621
645
|
required: ["kind", "name"],
|
|
622
646
|
properties: {
|
|
@@ -624,10 +648,6 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
624
648
|
name: { type: "string" },
|
|
625
649
|
},
|
|
626
650
|
additionalProperties: true,
|
|
627
|
-
anyOf: [
|
|
628
|
-
{ "x-telo-ref": "Telo.Invocable" },
|
|
629
|
-
{ "x-telo-ref": "Telo.Runnable" },
|
|
630
|
-
],
|
|
631
651
|
},
|
|
632
652
|
inputs: {
|
|
633
653
|
// Same annotation Run.Sequence steps carry: it is what makes
|