@telorun/analyzer 0.66.0 → 0.68.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 +60 -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/extends-resolution.d.ts +20 -0
- package/dist/extends-resolution.d.ts.map +1 -1
- package/dist/extends-resolution.js +29 -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 +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/inline-imports.d.ts.map +1 -1
- package/dist/inline-imports.js +1 -0
- package/dist/invocation-contract.d.ts +20 -0
- package/dist/invocation-contract.d.ts.map +1 -1
- package/dist/invocation-contract.js +74 -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-keywords.d.ts +16 -1
- package/dist/schema-keywords.d.ts.map +1 -1
- package/dist/schema-keywords.js +20 -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-extends.d.ts.map +1 -1
- package/dist/validate-extends.js +42 -1
- 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-sensitive-slots.d.ts +28 -0
- package/dist/validate-sensitive-slots.d.ts.map +1 -0
- package/dist/validate-sensitive-slots.js +75 -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/dist/value-type-keyword.d.ts +1 -1
- package/dist/value-type-keyword.d.ts.map +1 -1
- package/dist/value-type-keyword.js +1 -0
- package/package.json +3 -3
- package/src/analyzer.ts +71 -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/extends-resolution.ts +33 -0
- package/src/flatten-for-analyzer.ts +116 -3
- package/src/index.ts +16 -0
- package/src/inline-imports.ts +1 -0
- package/src/invocation-contract.ts +71 -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-keywords.ts +33 -1
- 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-extends.ts +51 -1
- 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-sensitive-slots.ts +109 -0
- package/src/validate-template-dispatch.ts +99 -0
- package/src/value-type-keyword.ts +1 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { SCHEMA_REGION_KEYS } from "./schema-region.js";
|
|
2
|
+
const ANNOTATION = "x-telo-sensitive";
|
|
3
|
+
/** The only regions the kernel reads the annotation from. */
|
|
4
|
+
const CONTRACT_KEYS = new Set(["inputType", "outputType"]);
|
|
5
|
+
export function validateSensitiveSlots(manifest) {
|
|
6
|
+
const issues = [];
|
|
7
|
+
const walk = (node, path, seen) => {
|
|
8
|
+
if (!node || typeof node !== "object")
|
|
9
|
+
return;
|
|
10
|
+
if (seen.has(node))
|
|
11
|
+
return;
|
|
12
|
+
seen.add(node);
|
|
13
|
+
if (Array.isArray(node)) {
|
|
14
|
+
node.forEach((child, i) => walk(child, [...path, i], seen));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const record = node;
|
|
18
|
+
if (Object.hasOwn(record, ANNOTATION)) {
|
|
19
|
+
const dotted = path.join(".");
|
|
20
|
+
if (record[ANNOTATION] !== true) {
|
|
21
|
+
issues.push({
|
|
22
|
+
code: "SENSITIVE_ANNOTATION_INVALID",
|
|
23
|
+
manifest,
|
|
24
|
+
path: dotted,
|
|
25
|
+
message: `'${ANNOTATION}' must be \`true\`; got ${JSON.stringify(record[ANNOTATION])}. ` +
|
|
26
|
+
`It is a marker, not a level — a value other than \`true\` reads as "not sensitive".`,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const region = enclosingSchemaRegion(path);
|
|
31
|
+
if (region === undefined || !CONTRACT_KEYS.has(region)) {
|
|
32
|
+
issues.push({
|
|
33
|
+
code: "SENSITIVE_ANNOTATION_MISPLACED",
|
|
34
|
+
manifest,
|
|
35
|
+
path: dotted,
|
|
36
|
+
message: `'${ANNOTATION}' is only read from a resource's declared contract ` +
|
|
37
|
+
`(\`inputType\` / \`outputType\`), and this node is ` +
|
|
38
|
+
(region === undefined
|
|
39
|
+
? "not inside a schema at all"
|
|
40
|
+
: `inside \`${region}\``) +
|
|
41
|
+
`. The kernel will not redact it, so the value would still reach trace ` +
|
|
42
|
+
`payloads and the debug wire. Move the mark onto the contract property ` +
|
|
43
|
+
`that carries the value.`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const [key, child] of Object.entries(record)) {
|
|
49
|
+
walk(child, [...path, key], seen);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
walk(manifest, [], new Set());
|
|
53
|
+
return issues;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The OUTERMOST segment naming a schema-valued key, or `undefined` when the node
|
|
57
|
+
* is not inside a schema.
|
|
58
|
+
*
|
|
59
|
+
* Outermost, not nearest, because a contract is routinely written in the inline
|
|
60
|
+
* `{kind: Telo.JsonSchema, schema: …}` form — so the path to a marked property is
|
|
61
|
+
* `outputType.schema.properties.headers`, and the nearest region key is that
|
|
62
|
+
* wrapper's own `schema`. Reading it as a kind's configuration would report every
|
|
63
|
+
* correctly-marked contract as misplaced, which is how this check first behaved.
|
|
64
|
+
*
|
|
65
|
+
* It is the same reasoning `expandManifestFragments` uses when it keys on the
|
|
66
|
+
* top-level slot: the object a contract resolver is handed is the one under
|
|
67
|
+
* `inputType` / `outputType`, whatever nesting the authoring form adds beneath.
|
|
68
|
+
*/
|
|
69
|
+
function enclosingSchemaRegion(path) {
|
|
70
|
+
for (const segment of path) {
|
|
71
|
+
if (typeof segment === "string" && SCHEMA_REGION_KEYS.includes(segment))
|
|
72
|
+
return segment;
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import { type AnalysisDiagnostic } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* A `!ref` at a definition's dispatch slot must name a sibling `resources:`
|
|
5
|
+
* entry.
|
|
6
|
+
*
|
|
7
|
+
* `!ref` is the one spelling an author — and the editor's rename, completion and
|
|
8
|
+
* go-to-definition — expects to be RESOLVED. But a `Telo.Definition` is in both
|
|
9
|
+
* `REF_VALIDATION_SKIP_KINDS` and `REF_RESOLUTION_SKIP_KINDS`, and the dispatch
|
|
10
|
+
* slots carry no `x-telo-ref` (the accepted targets are the definition's own
|
|
11
|
+
* template-internal entries, not resources of any module), so no reference pass
|
|
12
|
+
* reaches them. Introducing the tag at a slot nothing resolves would be worse
|
|
13
|
+
* than the string form it replaces: a typo that used to be an obvious runtime
|
|
14
|
+
* miss becomes a typo in a construct that advertises static resolution.
|
|
15
|
+
*
|
|
16
|
+
* Decidable only when every sibling name is LITERAL. A template routinely names
|
|
17
|
+
* its entries with CEL (`name: !cel "self.name + '-query'"`), and an expression
|
|
18
|
+
* could expand to the referenced name — so one dynamic sibling switches the
|
|
19
|
+
* check off for that definition rather than inventing a miss.
|
|
20
|
+
*
|
|
21
|
+
* Entry-module-scoped, like every other declaration check: a published
|
|
22
|
+
* dependency's template body is not the consumer's to fix.
|
|
23
|
+
*
|
|
24
|
+
* Browser-safe.
|
|
25
|
+
*/
|
|
26
|
+
export declare function validateTemplateDispatch(manifests: ResourceManifest[], rootModules: ReadonlySet<string>): AnalysisDiagnostic[];
|
|
27
|
+
//# sourceMappingURL=validate-template-dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-template-dispatch.d.ts","sourceRoot":"","sources":["../src/validate-template-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AASzE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,GAC/B,kBAAkB,EAAE,CAgDtB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { isRefSentinel } from "@telorun/templating";
|
|
2
|
+
import { distance } from "./levenshtein.js";
|
|
3
|
+
import { DiagnosticSeverity } from "./types.js";
|
|
4
|
+
const SOURCE = "telo-analyzer";
|
|
5
|
+
/** The four slots a `Telo.Definition` names its dispatch target in. Each takes
|
|
6
|
+
* the same grammar; which one is legal for a given capability is
|
|
7
|
+
* `validate-provider-coherence`'s question, not this one's. */
|
|
8
|
+
const DISPATCH_FIELDS = ["invoke", "run", "provide", "mount"];
|
|
9
|
+
/**
|
|
10
|
+
* A `!ref` at a definition's dispatch slot must name a sibling `resources:`
|
|
11
|
+
* entry.
|
|
12
|
+
*
|
|
13
|
+
* `!ref` is the one spelling an author — and the editor's rename, completion and
|
|
14
|
+
* go-to-definition — expects to be RESOLVED. But a `Telo.Definition` is in both
|
|
15
|
+
* `REF_VALIDATION_SKIP_KINDS` and `REF_RESOLUTION_SKIP_KINDS`, and the dispatch
|
|
16
|
+
* slots carry no `x-telo-ref` (the accepted targets are the definition's own
|
|
17
|
+
* template-internal entries, not resources of any module), so no reference pass
|
|
18
|
+
* reaches them. Introducing the tag at a slot nothing resolves would be worse
|
|
19
|
+
* than the string form it replaces: a typo that used to be an obvious runtime
|
|
20
|
+
* miss becomes a typo in a construct that advertises static resolution.
|
|
21
|
+
*
|
|
22
|
+
* Decidable only when every sibling name is LITERAL. A template routinely names
|
|
23
|
+
* its entries with CEL (`name: !cel "self.name + '-query'"`), and an expression
|
|
24
|
+
* could expand to the referenced name — so one dynamic sibling switches the
|
|
25
|
+
* check off for that definition rather than inventing a miss.
|
|
26
|
+
*
|
|
27
|
+
* Entry-module-scoped, like every other declaration check: a published
|
|
28
|
+
* dependency's template body is not the consumer's to fix.
|
|
29
|
+
*
|
|
30
|
+
* Browser-safe.
|
|
31
|
+
*/
|
|
32
|
+
export function validateTemplateDispatch(manifests, rootModules) {
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const m of manifests) {
|
|
35
|
+
if (m.kind !== "Telo.Definition")
|
|
36
|
+
continue;
|
|
37
|
+
const meta = m.metadata;
|
|
38
|
+
const name = meta?.name;
|
|
39
|
+
if (!name)
|
|
40
|
+
continue;
|
|
41
|
+
if (meta?.module && !rootModules.has(meta.module))
|
|
42
|
+
continue;
|
|
43
|
+
const bodies = m.resources;
|
|
44
|
+
if (!Array.isArray(bodies))
|
|
45
|
+
continue;
|
|
46
|
+
const siblings = [];
|
|
47
|
+
let anyDynamic = false;
|
|
48
|
+
for (const entry of bodies) {
|
|
49
|
+
const entryName = entry?.metadata?.name;
|
|
50
|
+
if (typeof entryName === "string" && !entryName.includes("${{"))
|
|
51
|
+
siblings.push(entryName);
|
|
52
|
+
else if (entryName !== undefined)
|
|
53
|
+
anyDynamic = true;
|
|
54
|
+
}
|
|
55
|
+
if (anyDynamic)
|
|
56
|
+
continue;
|
|
57
|
+
for (const field of DISPATCH_FIELDS) {
|
|
58
|
+
const value = m[field];
|
|
59
|
+
if (!isRefSentinel(value))
|
|
60
|
+
continue;
|
|
61
|
+
const source = value.source;
|
|
62
|
+
const target = source.startsWith("Self.") ? source.slice("Self.".length) : source;
|
|
63
|
+
if (siblings.includes(target))
|
|
64
|
+
continue;
|
|
65
|
+
const suggestion = nearest(target, siblings);
|
|
66
|
+
out.push({
|
|
67
|
+
severity: DiagnosticSeverity.Error,
|
|
68
|
+
code: "TEMPLATE_DISPATCH_UNKNOWN",
|
|
69
|
+
source: SOURCE,
|
|
70
|
+
message: `${m.kind}/${name}: '${field}: !ref ${source}' names no entry in 'resources:'. ` +
|
|
71
|
+
`Available: ${siblings.join(", ") || "(none)"}.` +
|
|
72
|
+
(suggestion ? ` Did you mean '${suggestion}'?` : ""),
|
|
73
|
+
data: {
|
|
74
|
+
resource: { kind: m.kind, name },
|
|
75
|
+
filePath: meta?.source,
|
|
76
|
+
path: field,
|
|
77
|
+
...(suggestion ? { fix: { replacement: suggestion } } : {}),
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
/** The closest sibling name, when one is close enough to be a typo rather than a
|
|
85
|
+
* different name. */
|
|
86
|
+
function nearest(target, candidates) {
|
|
87
|
+
let best;
|
|
88
|
+
for (const candidate of candidates) {
|
|
89
|
+
const d = distance(target, candidate);
|
|
90
|
+
if (!best || d < best.d)
|
|
91
|
+
best = { name: candidate, d };
|
|
92
|
+
}
|
|
93
|
+
const limit = Math.max(1, Math.floor(target.length / 3));
|
|
94
|
+
return best && best.d <= limit ? best.name : undefined;
|
|
95
|
+
}
|
|
@@ -28,7 +28,7 @@ import type { KeywordDefinition } from "ajv";
|
|
|
28
28
|
*
|
|
29
29
|
* `x-telo-type` is deliberately absent — it is the one that emits code.
|
|
30
30
|
*/
|
|
31
|
-
export declare const ANNOTATION_KEYWORDS: readonly ["x-telo-bindings-from", "x-telo-catches-for", "x-telo-context", "x-telo-context-collection-from", "x-telo-context-element-from", "x-telo-context-from", "x-telo-context-from-ref-kind", "x-telo-context-from-root", "x-telo-context-ref-from", "x-telo-error-context", "x-telo-eval", "x-telo-inline", "x-telo-outcome-list", "x-telo-provides-zone", "x-telo-ref", "x-telo-requires-zone", "x-telo-resource-rules", "x-telo-schema-from", "x-telo-schema-map", "x-telo-schema-projection", "x-telo-schema-projection-from", "x-telo-scope", "x-telo-step-context", "x-telo-topology-role", "x-telo-value-schema-from", "x-telo-widget"];
|
|
31
|
+
export declare const ANNOTATION_KEYWORDS: readonly ["x-telo-bindings-from", "x-telo-catches-for", "x-telo-context", "x-telo-context-collection-from", "x-telo-context-element-from", "x-telo-context-from", "x-telo-context-from-ref-kind", "x-telo-context-from-root", "x-telo-context-ref-from", "x-telo-error-context", "x-telo-eval", "x-telo-inline", "x-telo-outcome-list", "x-telo-provides-zone", "x-telo-ref", "x-telo-requires-zone", "x-telo-resource-rules", "x-telo-schema-from", "x-telo-schema-map", "x-telo-schema-projection", "x-telo-schema-projection-from", "x-telo-scope", "x-telo-sensitive", "x-telo-step-context", "x-telo-topology-role", "x-telo-value-schema-from", "x-telo-widget"];
|
|
32
32
|
/**
|
|
33
33
|
* The `x-telo-type` keyword.
|
|
34
34
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"value-type-keyword.d.ts","sourceRoot":"","sources":["../src/value-type-keyword.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;+EAqB+E;AAG/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,KAAK,CAAC;AAiB7C;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,
|
|
1
|
+
{"version":3,"file":"value-type-keyword.d.ts","sourceRoot":"","sources":["../src/value-type-keyword.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;+EAqB+E;AAG/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,KAAK,CAAC;AAiB7C;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,woBA4BtB,CAAC;AAEX;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,IAAI,iBAAiB,CA+BpD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE;IACxC,UAAU,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC;CACzD,GAAG,IAAI,CAGP"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.68.0",
|
|
4
4
|
"description": "Telo Analyzer - Static manifest validator for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"jsonpath-plus": "^10.3.0",
|
|
44
44
|
"packageurl-js": "^2.0.1",
|
|
45
45
|
"yaml": "^2.8.3",
|
|
46
|
-
"@telorun/templating": "0.
|
|
46
|
+
"@telorun/templating": "0.18.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^20.0.0",
|
|
50
50
|
"typescript": "^5.0.0",
|
|
51
51
|
"vitest": "^2.1.8",
|
|
52
|
-
"@telorun/sdk": "0.
|
|
52
|
+
"@telorun/sdk": "0.83.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@telorun/sdk": "*"
|
package/src/analyzer.ts
CHANGED
|
@@ -108,6 +108,10 @@ import {
|
|
|
108
108
|
validateValueTypeSlots,
|
|
109
109
|
type ValueTypeSlotIssue,
|
|
110
110
|
} from "./validate-value-type-slots.js";
|
|
111
|
+
import {
|
|
112
|
+
validateSensitiveSlots,
|
|
113
|
+
type SensitiveSlotIssue,
|
|
114
|
+
} from "./validate-sensitive-slots.js";
|
|
111
115
|
import { resolveSchemaTypeRefs } from "./resolve-schema-type-refs.js";
|
|
112
116
|
import { validateSchemaTypeRefs } from "./validate-schema-type-refs.js";
|
|
113
117
|
import { rewriteSyntheticOrigins } from "./rewrite-synthetic-origins.js";
|
|
@@ -157,8 +161,11 @@ import { validateInvocationContract } from "./validate-invocation-contract.js";
|
|
|
157
161
|
import { collectRefInputIssues, collectStepInputIssues } from "./validate-step-inputs.js";
|
|
158
162
|
import { validateNestedInlineResources } from "./validate-nested-inline.js";
|
|
159
163
|
import { validateProviderCoherence } from "./validate-provider-coherence.js";
|
|
160
|
-
import { validateReferences } from "./validate-references.js";
|
|
164
|
+
import { kindSatisfies, validateReferences } from "./validate-references.js";
|
|
161
165
|
import { validateReferenceForms } from "./validate-reference-forms.js";
|
|
166
|
+
import { isInjectedDeclaration } from "./resource-input.js";
|
|
167
|
+
import { validateResourceInputs } from "./validate-resource-inputs.js";
|
|
168
|
+
import { validateTemplateDispatch } from "./validate-template-dispatch.js";
|
|
162
169
|
import { validateUnusedDeclarations } from "./validate-unused-declarations.js";
|
|
163
170
|
import { validateThrowsCoverage } from "./validate-throws-coverage.js";
|
|
164
171
|
import { readStepSlot } from "./step-slot.js";
|
|
@@ -976,10 +983,16 @@ export class StaticAnalyzer {
|
|
|
976
983
|
// definition's `schema:` does. Same scoping as every other schema issue —
|
|
977
984
|
// the entry's own modules, since a dependency is not the consumer's to fix.
|
|
978
985
|
const valueTypeSlotIssues: ValueTypeSlotIssue[] = [];
|
|
986
|
+
// `x-telo-sensitive` rides the same walk and the same scoping. It is read by
|
|
987
|
+
// ONE consumer — the kernel, off a bound contract — so an occurrence outside
|
|
988
|
+
// `inputType` / `outputType` is inert; for a security control, inert-and-
|
|
989
|
+
// silent is the failure worth reporting.
|
|
990
|
+
const sensitiveSlotIssues: SensitiveSlotIssue[] = [];
|
|
979
991
|
for (const m of manifests) {
|
|
980
992
|
const declaringModule = (m.metadata as { module?: string } | undefined)?.module;
|
|
981
993
|
if (!declaringModule || rootModules.has(declaringModule)) {
|
|
982
994
|
valueTypeSlotIssues.push(...validateValueTypeSlots(m as unknown as ResourceManifest));
|
|
995
|
+
sensitiveSlotIssues.push(...validateSensitiveSlots(m as unknown as ResourceManifest));
|
|
983
996
|
}
|
|
984
997
|
}
|
|
985
998
|
for (const m of manifests) {
|
|
@@ -1209,6 +1222,22 @@ export class StaticAnalyzer {
|
|
|
1209
1222
|
},
|
|
1210
1223
|
});
|
|
1211
1224
|
}
|
|
1225
|
+
for (const issue of sensitiveSlotIssues) {
|
|
1226
|
+
diagnostics.push({
|
|
1227
|
+
severity: DiagnosticSeverity.Error,
|
|
1228
|
+
code: issue.code,
|
|
1229
|
+
source: SOURCE,
|
|
1230
|
+
message: issue.message,
|
|
1231
|
+
data: {
|
|
1232
|
+
resource: {
|
|
1233
|
+
kind: issue.manifest.kind,
|
|
1234
|
+
name: issue.manifest.metadata?.name as string,
|
|
1235
|
+
},
|
|
1236
|
+
filePath: (issue.manifest.metadata as { source?: string } | undefined)?.source,
|
|
1237
|
+
path: issue.path,
|
|
1238
|
+
},
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1212
1241
|
// Same split for the two zone annotations. Unreadable ones fail in
|
|
1213
1242
|
// OPPOSITE directions — a dropped requirement is silently unenforced, a
|
|
1214
1243
|
// dropped provision invents failures — so neither can be left to
|
|
@@ -1627,6 +1656,16 @@ export class StaticAnalyzer {
|
|
|
1627
1656
|
const runReachable = reportsObservedState
|
|
1628
1657
|
? collectRunReachableNames(getCallGraph())
|
|
1629
1658
|
: new Set<string>();
|
|
1659
|
+
// "Nothing starts this" is DECLARATION-derived, and a library does not
|
|
1660
|
+
// declare its injected inputs — whether the application starts the instance
|
|
1661
|
+
// it hands down is answerable only where that instance is declared. Inside
|
|
1662
|
+
// the library the question has no answer, so the name is treated as
|
|
1663
|
+
// reachable rather than reported on a `targets:` list the author cannot
|
|
1664
|
+
// write. The check still runs at the injection site, against the real
|
|
1665
|
+
// declaration.
|
|
1666
|
+
for (const m of allManifests) {
|
|
1667
|
+
if (isInjectedDeclaration(m)) runReachable.add(m.metadata?.name as string);
|
|
1668
|
+
}
|
|
1630
1669
|
|
|
1631
1670
|
// Build typed kernel globals schema so x-telo-context chain validation
|
|
1632
1671
|
// recognises variables, secrets, resources, env automatically
|
|
@@ -1762,6 +1801,15 @@ export class StaticAnalyzer {
|
|
|
1762
1801
|
continue;
|
|
1763
1802
|
}
|
|
1764
1803
|
|
|
1804
|
+
// A kind-only stand-in for a `resources:` entry is a DECLARATION, not an
|
|
1805
|
+
// instantiation: its kind is routinely an abstract and its configuration
|
|
1806
|
+
// is the importer's to supply, so validating it here would report a
|
|
1807
|
+
// non-instantiable kind and a page of missing required fields against a
|
|
1808
|
+
// block that is correct. Its kind is checked where it was written, by
|
|
1809
|
+
// `validate-resource-inputs`; it participates here only as a resolution
|
|
1810
|
+
// target and as a CEL type.
|
|
1811
|
+
if (isInjectedDeclaration(m)) continue;
|
|
1812
|
+
|
|
1765
1813
|
const resource = { kind: m.kind, name: m.metadata?.name as string };
|
|
1766
1814
|
|
|
1767
1815
|
// Resolve kind through alias if needed; direct lookup takes priority so that
|
|
@@ -1861,6 +1909,11 @@ export class StaticAnalyzer {
|
|
|
1861
1909
|
) as Record<string, any>;
|
|
1862
1910
|
if (!ownModule || rootModules.has(ownModule)) {
|
|
1863
1911
|
for (const failure of projectionFailures) {
|
|
1912
|
+
// A projection through a library's own resource INPUT is
|
|
1913
|
+
// unanswerable here — the entries belong to the declaration the
|
|
1914
|
+
// importer supplies — so it is not a defect in the block that named
|
|
1915
|
+
// it. The check runs at the injection site instead.
|
|
1916
|
+
if (failure.reason === "injected") continue;
|
|
1864
1917
|
diagnostics.push({
|
|
1865
1918
|
severity: DiagnosticSeverity.Error,
|
|
1866
1919
|
code: "SCHEMA_PROJECTION_FROM_UNRESOLVED",
|
|
@@ -2556,7 +2609,7 @@ export class StaticAnalyzer {
|
|
|
2556
2609
|
}
|
|
2557
2610
|
},
|
|
2558
2611
|
},
|
|
2559
|
-
{ aliases },
|
|
2612
|
+
{ aliases, aliasesByModule, rootModules },
|
|
2560
2613
|
);
|
|
2561
2614
|
|
|
2562
2615
|
// The two halves of "does this expression fit the slot it flows into" meet
|
|
@@ -2645,6 +2698,22 @@ export class StaticAnalyzer {
|
|
|
2645
2698
|
// Warn about declared variables / secrets / ports that no CEL references.
|
|
2646
2699
|
diagnostics.push(...validateUnusedDeclarations(allManifests, this.celEnv));
|
|
2647
2700
|
|
|
2701
|
+
// A `!ref` at a definition's dispatch slot must name a sibling `resources:`
|
|
2702
|
+
// entry — the slot no reference pass reaches, so the tag would otherwise
|
|
2703
|
+
// advertise a resolution nothing performs.
|
|
2704
|
+
diagnostics.push(...validateTemplateDispatch(allManifests, rootModules));
|
|
2705
|
+
|
|
2706
|
+
// A library's declared resource inputs, and every import that supplies them.
|
|
2707
|
+
diagnostics.push(
|
|
2708
|
+
...validateResourceInputs(
|
|
2709
|
+
allManifests,
|
|
2710
|
+
defs,
|
|
2711
|
+
aliases,
|
|
2712
|
+
rootModules,
|
|
2713
|
+
(supplied, required) => kindSatisfies(supplied, required, defs),
|
|
2714
|
+
),
|
|
2715
|
+
);
|
|
2716
|
+
|
|
2648
2717
|
// Reroute diagnostics on synthetic (inline-extracted) resources back to
|
|
2649
2718
|
// the chain root so position-index lookups land on the parent doc.
|
|
2650
2719
|
return rewriteSyntheticOrigins(
|
package/src/builtins.ts
CHANGED
|
@@ -232,6 +232,35 @@ const ROOT_LOGGING_SCHEMA = {
|
|
|
232
232
|
additionalProperties: false,
|
|
233
233
|
};
|
|
234
234
|
|
|
235
|
+
/** A `Telo.Library`'s declared resource inputs — the instances it requires from
|
|
236
|
+
* whoever imports it, the inward half of the symmetry `exports.resources`
|
|
237
|
+
* already had outward. Each entry is constrained by KIND ONLY, through the same
|
|
238
|
+
* alias-qualified grammar `extends:` and `x-telo-ref` use; there is no `use:`,
|
|
239
|
+
* because the boundary is a dependency edge for init order whatever the library
|
|
240
|
+
* does with the instance. See `analyzer/nodejs/src/resource-input.ts`. */
|
|
241
|
+
const LIBRARY_RESOURCE_INPUTS_SCHEMA = {
|
|
242
|
+
type: "object",
|
|
243
|
+
additionalProperties: {
|
|
244
|
+
type: "object",
|
|
245
|
+
required: ["kind"],
|
|
246
|
+
properties: {
|
|
247
|
+
kind: { type: "string" },
|
|
248
|
+
description: { type: "string" },
|
|
249
|
+
},
|
|
250
|
+
additionalProperties: false,
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/** The importer's side of the same block: entry name → `!ref` to the instance
|
|
255
|
+
* supplied for it. Left open because the accepted KIND is declared by the
|
|
256
|
+
* target library, not by this schema — the constraint is checked by
|
|
257
|
+
* `validate-resource-inputs.ts`, which reads the target's declared block off
|
|
258
|
+
* the `metadata.requiredResources` stamp. */
|
|
259
|
+
const IMPORT_RESOURCE_INPUTS_SCHEMA = {
|
|
260
|
+
type: "object",
|
|
261
|
+
additionalProperties: {},
|
|
262
|
+
};
|
|
263
|
+
|
|
235
264
|
export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
236
265
|
{ kind: "Telo.Abstract", metadata: { name: "Template", module: "Telo" } },
|
|
237
266
|
// "Control can be transferred to this" — the parent of Invocable and Runnable,
|
|
@@ -427,28 +456,26 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
427
456
|
items: {
|
|
428
457
|
type: "object",
|
|
429
458
|
additionalProperties: true,
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
//
|
|
433
|
-
//
|
|
434
|
-
//
|
|
459
|
+
// A `resources:` entry is a DECLARATION of another kind, so the
|
|
460
|
+
// CEL inside it belongs to THAT kind: its `x-telo-context` regions
|
|
461
|
+
// are rebased under this entry's path and take precedence (they are
|
|
462
|
+
// deeper), which is what puts `inputs`, `item`, `request`, `steps`
|
|
463
|
+
// and a `catch:`'s `error` in scope exactly where the nested kind
|
|
464
|
+
// declares them — see `analyzer/nodejs/src/template-body.ts`.
|
|
435
465
|
//
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
//
|
|
466
|
+
// What stays here is `self` alone, in force throughout the entry:
|
|
467
|
+
// it is how a body reaches the configuration its enclosing template
|
|
468
|
+
// was given, and no nested kind knows about it. The four names that
|
|
469
|
+
// used to sit beside it (`request` / `result` / `steps` / `error`)
|
|
470
|
+
// were a fixed permissive stand-in for the nested kind's own
|
|
471
|
+
// regions — which is why `error` was offered outside every `catch:`
|
|
472
|
+
// while `inputs` and `item` were undefined wherever a body actually
|
|
473
|
+
// reads them.
|
|
443
474
|
"x-telo-context": {
|
|
444
475
|
type: "object",
|
|
445
476
|
additionalProperties: false,
|
|
446
477
|
properties: {
|
|
447
478
|
self: { "x-telo-context-from-root": "schema" },
|
|
448
|
-
request: {},
|
|
449
|
-
result: {},
|
|
450
|
-
steps: {},
|
|
451
|
-
error: {},
|
|
452
479
|
},
|
|
453
480
|
},
|
|
454
481
|
},
|
|
@@ -611,6 +638,7 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
611
638
|
integrity: { type: "string" },
|
|
612
639
|
variables: { type: "object" },
|
|
613
640
|
secrets: { type: "object" },
|
|
641
|
+
resources: IMPORT_RESOURCE_INPUTS_SCHEMA,
|
|
614
642
|
runtime: {
|
|
615
643
|
oneOf: [
|
|
616
644
|
{ type: "string" },
|
|
@@ -757,6 +785,7 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
757
785
|
integrity: { type: "string" },
|
|
758
786
|
variables: { type: "object" },
|
|
759
787
|
secrets: { type: "object" },
|
|
788
|
+
resources: IMPORT_RESOURCE_INPUTS_SCHEMA,
|
|
760
789
|
runtime: {
|
|
761
790
|
oneOf: [
|
|
762
791
|
{ type: "string" },
|
|
@@ -872,6 +901,29 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
872
901
|
},
|
|
873
902
|
variables: { type: "object" },
|
|
874
903
|
secrets: { type: "object" },
|
|
904
|
+
// How many times this library is instantiated in one application.
|
|
905
|
+
//
|
|
906
|
+
// `isolated` (the default) is what every published module was written
|
|
907
|
+
// against: each import declaration builds its own child scope with its
|
|
908
|
+
// own instances, so two libraries importing a third get two of
|
|
909
|
+
// everything in it. `shared` makes the library a SINGLETON — every
|
|
910
|
+
// import of it resolves to one instantiation, owned by the root and
|
|
911
|
+
// torn down after everything that borrowed it.
|
|
912
|
+
//
|
|
913
|
+
// Default `isolated` rather than `shared` — the opposite of the
|
|
914
|
+
// Application field's — because flipping it would silently collapse
|
|
915
|
+
// every existing app's resource graph and turn per-import `variables:`
|
|
916
|
+
// into a conflict. The `exports.kinds` precedent: private-by-default is
|
|
917
|
+
// the better end state and still needs the ecosystem republished first.
|
|
918
|
+
lifecycle: {
|
|
919
|
+
type: "string",
|
|
920
|
+
enum: ["shared", "isolated"],
|
|
921
|
+
default: "isolated",
|
|
922
|
+
},
|
|
923
|
+
// The inward half of `exports.resources`: instances this library
|
|
924
|
+
// requires from whoever imports it. Library-only — an Application is a
|
|
925
|
+
// root with no importer, so it owns its instances outright.
|
|
926
|
+
resources: LIBRARY_RESOURCE_INPUTS_SCHEMA,
|
|
875
927
|
include: {
|
|
876
928
|
type: "array",
|
|
877
929
|
items: { type: "string" },
|
|
@@ -902,6 +954,7 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
902
954
|
integrity: { type: "string" },
|
|
903
955
|
variables: { type: "object" },
|
|
904
956
|
secrets: { type: "object" },
|
|
957
|
+
resources: IMPORT_RESOURCE_INPUTS_SCHEMA,
|
|
905
958
|
runtime: {
|
|
906
959
|
oneOf: [
|
|
907
960
|
{ type: "string" },
|