@telorun/analyzer 0.47.0 → 0.49.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 +22 -11
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +36 -39
- package/dist/analyzer.d.ts +38 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +121 -83
- package/dist/artifact-layer-index.d.ts +55 -0
- package/dist/artifact-layer-index.d.ts.map +1 -0
- package/dist/artifact-layer-index.js +116 -0
- package/dist/artifact-selector.d.ts +81 -0
- package/dist/artifact-selector.d.ts.map +1 -0
- package/dist/artifact-selector.js +122 -0
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +130 -20
- package/dist/extends-resolution.d.ts +41 -0
- package/dist/extends-resolution.d.ts.map +1 -1
- package/dist/extends-resolution.js +68 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/invocation-contract.d.ts +100 -0
- package/dist/invocation-contract.d.ts.map +1 -0
- package/dist/invocation-contract.js +208 -0
- package/dist/schema-compat.d.ts +12 -4
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +185 -9
- package/dist/validate-base-mapping.js +11 -1
- package/dist/validate-cel-context.d.ts +0 -6
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +51 -4
- package/dist/validate-invocation-contract.d.ts +30 -0
- package/dist/validate-invocation-contract.d.ts.map +1 -0
- package/dist/validate-invocation-contract.js +394 -0
- package/dist/validate-module-artifact.d.ts +27 -0
- package/dist/validate-module-artifact.d.ts.map +1 -0
- package/dist/validate-module-artifact.js +131 -0
- package/dist/validate-step-inputs.d.ts +24 -0
- package/dist/validate-step-inputs.d.ts.map +1 -0
- package/dist/validate-step-inputs.js +87 -0
- package/dist/validate-throws-coverage.d.ts +1 -1
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +9 -1
- package/package.json +2 -2
- package/src/analysis-registry.ts +44 -34
- package/src/analyzer.ts +177 -100
- package/src/artifact-layer-index.ts +162 -0
- package/src/artifact-selector.ts +171 -0
- package/src/builtins.ts +135 -20
- package/src/extends-resolution.ts +86 -0
- package/src/index.ts +38 -1
- package/src/invocation-contract.ts +275 -0
- package/src/schema-compat.ts +191 -8
- package/src/validate-base-mapping.ts +14 -1
- package/src/validate-cel-context.ts +49 -4
- package/src/validate-invocation-contract.ts +450 -0
- package/src/validate-module-artifact.ts +141 -0
- package/src/validate-step-inputs.ts +117 -0
- package/src/validate-throws-coverage.ts +12 -2
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { AliasResolver, ModuleScopes } from "./alias-resolver.js";
|
|
2
|
+
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
3
|
+
import type { ContractDirection } from "./extends-resolution.js";
|
|
4
|
+
import { resolveContract } from "./invocation-contract.js";
|
|
5
|
+
import { substituteCelFields, validateAgainstSchema } from "./schema-compat.js";
|
|
6
|
+
import {
|
|
7
|
+
analyzerContractScope,
|
|
8
|
+
containerOf,
|
|
9
|
+
gatherPropertySchemas,
|
|
10
|
+
missingRequired,
|
|
11
|
+
resolveLocalRef,
|
|
12
|
+
walkStepArray,
|
|
13
|
+
} from "./analyzer.js";
|
|
14
|
+
|
|
15
|
+
export interface StepInputIssue {
|
|
16
|
+
path: string;
|
|
17
|
+
targetLabel: string;
|
|
18
|
+
message: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate every step's `inputs:` against the invoked target's declared input
|
|
23
|
+
* contract — the static half of what the kernel enforces at dispatch.
|
|
24
|
+
*
|
|
25
|
+
* Worth doing statically because a call site is where the mistake is made and
|
|
26
|
+
* where the author can see both sides: a misspelled key or a wrong-shaped value
|
|
27
|
+
* would otherwise surface at runtime inside the callee, several steps from its
|
|
28
|
+
* cause, naming a resource the author may not have written.
|
|
29
|
+
*
|
|
30
|
+
* CEL leaves are replaced by schema-shaped placeholders first (`substituteCelFields`),
|
|
31
|
+
* so an expression is never a false positive — only structural disagreement is
|
|
32
|
+
* reported. Nothing is hardcoded about `Run.Sequence`: the invoke field comes
|
|
33
|
+
* from `x-telo-step-context`, and the paired inputs field from whichever sibling
|
|
34
|
+
* property carries `x-telo-topology-role: inputs`.
|
|
35
|
+
*/
|
|
36
|
+
export function collectStepInputIssues(
|
|
37
|
+
manifest: Record<string, any>,
|
|
38
|
+
defSchema: Record<string, any>,
|
|
39
|
+
allManifests: Record<string, any>[],
|
|
40
|
+
defs: DefinitionRegistry,
|
|
41
|
+
aliases: AliasResolver,
|
|
42
|
+
scopes: ModuleScopes,
|
|
43
|
+
): StepInputIssue[] {
|
|
44
|
+
const out: StepInputIssue[] = [];
|
|
45
|
+
const props = defSchema.properties as Record<string, any> | undefined;
|
|
46
|
+
if (!props) return out;
|
|
47
|
+
|
|
48
|
+
const contractScope = analyzerContractScope(defs, aliases, scopes, allManifests);
|
|
49
|
+
const readingModule = (manifest.metadata as { module?: string } | undefined)?.module;
|
|
50
|
+
|
|
51
|
+
for (const [fieldName, fieldSchema] of Object.entries(props)) {
|
|
52
|
+
const stepCtx = fieldSchema["x-telo-step-context"] as Record<string, string> | undefined;
|
|
53
|
+
if (!stepCtx?.invoke) continue;
|
|
54
|
+
const steps = manifest[fieldName];
|
|
55
|
+
if (!Array.isArray(steps)) continue;
|
|
56
|
+
|
|
57
|
+
const stepItemSchema = resolveLocalRef(
|
|
58
|
+
fieldSchema.items as Record<string, any> | undefined,
|
|
59
|
+
defSchema,
|
|
60
|
+
);
|
|
61
|
+
if (!stepItemSchema) continue;
|
|
62
|
+
|
|
63
|
+
// The inputs field is whichever sibling declares the role — never the literal
|
|
64
|
+
// name, so a composer that spells it differently still gets checked.
|
|
65
|
+
let inputsField: string | undefined;
|
|
66
|
+
for (const [key, sub] of gatherPropertySchemas(stepItemSchema)) {
|
|
67
|
+
if (sub?.["x-telo-topology-role"] === "inputs") inputsField = key;
|
|
68
|
+
}
|
|
69
|
+
if (!inputsField) continue;
|
|
70
|
+
|
|
71
|
+
walkStepArray(steps, stepItemSchema, defSchema, fieldName, (step, stepPath) => {
|
|
72
|
+
const invoke = step[stepCtx.invoke] as Record<string, any> | undefined;
|
|
73
|
+
const values = step[inputsField!];
|
|
74
|
+
if (!invoke || typeof invoke !== "object") return;
|
|
75
|
+
if (!values || typeof values !== "object" || Array.isArray(values)) return;
|
|
76
|
+
|
|
77
|
+
const invokedKind = invoke.kind as string | undefined;
|
|
78
|
+
const invokedName = invoke.name as string | undefined;
|
|
79
|
+
const invokedManifest = invokedName
|
|
80
|
+
? (allManifests.find(
|
|
81
|
+
(m) =>
|
|
82
|
+
(m.metadata as any)?.name === invokedName && (!invokedKind || m.kind === invokedKind),
|
|
83
|
+
) as Record<string, any> | undefined)
|
|
84
|
+
: (invoke as Record<string, any>);
|
|
85
|
+
const invokedDef = invokedKind
|
|
86
|
+
? contractScope.resolveIn(invokedKind, readingModule)
|
|
87
|
+
: undefined;
|
|
88
|
+
const contract = resolveContract("inputType", invokedManifest, invokedDef, contractScope);
|
|
89
|
+
if (!contract) return;
|
|
90
|
+
|
|
91
|
+
// Findings AT a substituted path are about a placeholder, not about
|
|
92
|
+
// anything the author wrote — a `pattern`-constrained string or a `oneOf`
|
|
93
|
+
// of unrelated shapes cannot be satisfied by any stand-in. Structural
|
|
94
|
+
// findings (missing required, unknown property) are located at the
|
|
95
|
+
// container and survive the filter.
|
|
96
|
+
const celPaths = new Set<string>();
|
|
97
|
+
const substituted = substituteCelFields(values, contract.schema, undefined, (p) =>
|
|
98
|
+
celPaths.add(p),
|
|
99
|
+
);
|
|
100
|
+
for (const issue of validateAgainstSchema(substituted, contract.schema)) {
|
|
101
|
+
if (celPaths.has(issue.path)) continue;
|
|
102
|
+
// A missing-required issue names the property that ISN'T there, so
|
|
103
|
+
// anchoring on it finds no node and the diagnostic degrades to 1:1 —
|
|
104
|
+
// losing the location of the most common contract mistake. Anchor on the
|
|
105
|
+
// container that should have held it, which does exist.
|
|
106
|
+
const anchor = missingRequired(issue) ? containerOf(issue.path) : issue.path;
|
|
107
|
+
out.push({
|
|
108
|
+
path: anchor ? `${stepPath}.${inputsField}.${anchor}` : `${stepPath}.${inputsField}`,
|
|
109
|
+
targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
|
|
110
|
+
message: issue.message,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { ASTNode, Environment } from "@marcbachmann/cel-js";
|
|
2
2
|
import { isTaggedSentinel } from "@telorun/templating";
|
|
3
|
-
import
|
|
3
|
+
import {
|
|
4
|
+
AMBIENT_CONTRACT_ERROR_CODES,
|
|
5
|
+
isAmbientContractErrorCode,
|
|
6
|
+
type ResourceManifest,
|
|
7
|
+
} from "@telorun/sdk";
|
|
4
8
|
import { scopeResolverForModule, type AliasResolver } from "./alias-resolver.js";
|
|
5
9
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
6
10
|
import {
|
|
@@ -250,12 +254,18 @@ function checkCatchesCoverage(
|
|
|
250
254
|
const { proven, codes } = extractCoveredCodes(e.when, env);
|
|
251
255
|
if (proven) {
|
|
252
256
|
for (const c of codes) {
|
|
257
|
+
// An ambient kernel code (contract violations) is raised by the kernel,
|
|
258
|
+
// not declared by the kind, so naming it is legal and still typo-checked
|
|
259
|
+
// — but it is NOT part of the declared union, so it never counts toward
|
|
260
|
+
// coverage. Folding these into every union would make every bounded
|
|
261
|
+
// catches: block in the standard library incomplete overnight.
|
|
262
|
+
if (isAmbientContractErrorCode(c)) continue;
|
|
253
263
|
if (!declaredCodes.has(c)) {
|
|
254
264
|
diagnostics.push({
|
|
255
265
|
severity: DiagnosticSeverity.Error,
|
|
256
266
|
code: "UNDECLARED_THROW_CODE",
|
|
257
267
|
source: SOURCE,
|
|
258
|
-
message: `catches[${i}] references code '${c}' which is not in the handler's declared throw union {${[...declaredCodes].sort().join(", ") || "∅"}}${union.unbounded ? "
|
|
268
|
+
message: `catches[${i}] references code '${c}' which is not in the handler's declared throw union {${[...declaredCodes].sort().join(", ") || "∅"}} (ambient kernel codes ${AMBIENT_CONTRACT_ERROR_CODES.join(", ")} may also be named)${union.unbounded ? "; the union is unbounded, so a catch-all is required" : ""}.`,
|
|
259
269
|
data: { resource, filePath, path: `${arrayPath}[${i}].when` },
|
|
260
270
|
});
|
|
261
271
|
} else {
|