@telorun/analyzer 0.63.0 → 0.65.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 +24 -0
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +35 -0
- package/dist/analyzer.d.ts +3 -37
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +76 -470
- package/dist/cel-scope-query.d.ts +109 -0
- package/dist/cel-scope-query.d.ts.map +1 -0
- package/dist/cel-scope-query.js +270 -0
- package/dist/cel-scope.d.ts +166 -0
- package/dist/cel-scope.d.ts.map +1 -0
- package/dist/cel-scope.js +377 -0
- package/dist/definition-registry.d.ts +38 -6
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +66 -22
- package/dist/find-manifest.d.ts +10 -0
- package/dist/find-manifest.d.ts.map +1 -0
- package/dist/find-manifest.js +12 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/invocation-contract.d.ts +11 -0
- package/dist/invocation-contract.d.ts.map +1 -1
- package/dist/invocation-contract.js +15 -0
- package/dist/manifest-analysis.d.ts +73 -0
- package/dist/manifest-analysis.d.ts.map +1 -0
- package/dist/manifest-analysis.js +78 -0
- package/dist/manifest-path.d.ts +18 -0
- package/dist/manifest-path.d.ts.map +1 -0
- package/dist/manifest-path.js +37 -0
- package/dist/schema-compat.d.ts +59 -22
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +60 -75
- package/dist/schema-error-report.d.ts +68 -0
- package/dist/schema-error-report.d.ts.map +1 -0
- package/dist/schema-error-report.js +356 -0
- package/dist/schema-walk.d.ts +25 -0
- package/dist/schema-walk.d.ts.map +1 -0
- package/dist/schema-walk.js +126 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/validate-nested-inline.d.ts +22 -1
- package/dist/validate-nested-inline.d.ts.map +1 -1
- package/dist/validate-nested-inline.js +17 -9
- package/dist/validate-step-inputs.d.ts +17 -0
- package/dist/validate-step-inputs.d.ts.map +1 -1
- package/dist/validate-step-inputs.js +108 -9
- package/package.json +2 -2
- package/src/analysis-registry.ts +37 -0
- package/src/analyzer.ts +83 -587
- package/src/cel-scope-query.ts +337 -0
- package/src/cel-scope.ts +570 -0
- package/src/definition-registry.ts +79 -24
- package/src/find-manifest.ts +19 -0
- package/src/index.ts +23 -2
- package/src/invocation-contract.ts +22 -0
- package/src/manifest-analysis.ts +132 -0
- package/src/manifest-path.ts +34 -0
- package/src/schema-compat.ts +92 -79
- package/src/schema-error-report.ts +417 -0
- package/src/schema-walk.ts +144 -0
- package/src/telo-version.ts +1 -1
- package/src/validate-nested-inline.ts +35 -14
- package/src/validate-step-inputs.ts +153 -11
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
validateAgainstSchema,
|
|
10
10
|
} from "./schema-compat.js";
|
|
11
11
|
import { plainChainOf } from "@telorun/templating";
|
|
12
|
-
import { isLiveSlot, valueTypeOf } from "@telorun/sdk";
|
|
12
|
+
import { isLiveSlot, valueTypeOf, type ResourceDefinition } from "@telorun/sdk";
|
|
13
13
|
import { manifestFragmentOf } from "./manifest-schemas.js";
|
|
14
14
|
import {
|
|
15
15
|
analyzerContractScope,
|
|
@@ -20,6 +20,12 @@ import {
|
|
|
20
20
|
walkStepArray,
|
|
21
21
|
} from "./analyzer.js";
|
|
22
22
|
import { readStepSlot } from "./step-slot.js";
|
|
23
|
+
import { navigateConcretePath } from "./manifest-path.js";
|
|
24
|
+
import {
|
|
25
|
+
isRefEntry,
|
|
26
|
+
resolveFieldEntries,
|
|
27
|
+
type ReferenceFieldMap,
|
|
28
|
+
} from "./reference-field-map.js";
|
|
23
29
|
|
|
24
30
|
export interface StepInputIssue {
|
|
25
31
|
path: string;
|
|
@@ -66,6 +72,15 @@ export function collectStepInputIssues(
|
|
|
66
72
|
const contractScope = analyzerContractScope(defs, aliases, scopes, allManifests);
|
|
67
73
|
const readingModule = (manifest.metadata as { module?: string } | undefined)?.module;
|
|
68
74
|
|
|
75
|
+
const ctx: CallCheckContext = {
|
|
76
|
+
manifest,
|
|
77
|
+
allManifests,
|
|
78
|
+
defs,
|
|
79
|
+
contractScope,
|
|
80
|
+
readingModule,
|
|
81
|
+
stepContext,
|
|
82
|
+
};
|
|
83
|
+
|
|
69
84
|
for (const [fieldName, fieldSchema] of Object.entries(props)) {
|
|
70
85
|
const stepCtx = readStepSlot(fieldSchema);
|
|
71
86
|
if (!stepCtx) continue;
|
|
@@ -92,6 +107,127 @@ export function collectStepInputIssues(
|
|
|
92
107
|
if (!invoke || typeof invoke !== "object") return;
|
|
93
108
|
if (!values || typeof values !== "object" || Array.isArray(values)) return;
|
|
94
109
|
|
|
110
|
+
out.push(
|
|
111
|
+
...checkCallSite(
|
|
112
|
+
{
|
|
113
|
+
inputsPath: `${stepPath}.${inputsField}`,
|
|
114
|
+
values: values as Record<string, any>,
|
|
115
|
+
invoke,
|
|
116
|
+
// Only a step declares a re-attempt policy, so only a step can carry
|
|
117
|
+
// the live-value-retried finding.
|
|
118
|
+
declaredRetryFor: (invokedManifest, invokedDef) =>
|
|
119
|
+
declaredRetry(step, stepItemSchema, invokedManifest, invokedDef),
|
|
120
|
+
},
|
|
121
|
+
ctx,
|
|
122
|
+
),
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Validate the argument map of every call this resource makes through a
|
|
131
|
+
* REFERENCE SLOT, as opposed to a step.
|
|
132
|
+
*
|
|
133
|
+
* A slot that transfers control names its argument slot on its own `x-telo-ref`
|
|
134
|
+
* (`inputs:`, a JSON Pointer relative to the object enclosing the slot). That
|
|
135
|
+
* annotation is the only thing tying an otherwise-open `inputs:` map to the
|
|
136
|
+
* resource it holds arguments for — an HTTP route's `handler:` + `inputs:` pair
|
|
137
|
+
* is exactly this shape, and nothing about it is a step.
|
|
138
|
+
*
|
|
139
|
+
* Discovery is driven by the annotation rather than by any kind's topology, so
|
|
140
|
+
* a composer that names its argument slot gets its call sites checked without
|
|
141
|
+
* the analyzer learning what a route is. It is the same check the step driver
|
|
142
|
+
* runs, because it is the same question.
|
|
143
|
+
*/
|
|
144
|
+
export function collectRefInputIssues(
|
|
145
|
+
manifest: Record<string, any>,
|
|
146
|
+
fieldMap: ReferenceFieldMap | undefined,
|
|
147
|
+
allManifests: Record<string, any>[],
|
|
148
|
+
defs: DefinitionRegistry,
|
|
149
|
+
aliases: AliasResolver,
|
|
150
|
+
scopes: ModuleScopes,
|
|
151
|
+
): StepInputIssue[] {
|
|
152
|
+
const out: StepInputIssue[] = [];
|
|
153
|
+
if (!fieldMap) return out;
|
|
154
|
+
|
|
155
|
+
const contractScope = analyzerContractScope(defs, aliases, scopes, allManifests);
|
|
156
|
+
const ctx: CallCheckContext = {
|
|
157
|
+
manifest,
|
|
158
|
+
allManifests,
|
|
159
|
+
defs,
|
|
160
|
+
contractScope,
|
|
161
|
+
readingModule: (manifest.metadata as { module?: string } | undefined)?.module,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
for (const [fieldPath, entry] of fieldMap) {
|
|
165
|
+
if (!isRefEntry(entry) || !entry.inputs) continue;
|
|
166
|
+
const pointer = pointerSegments(entry.inputs);
|
|
167
|
+
if (!pointer) continue;
|
|
168
|
+
|
|
169
|
+
for (const { value: invoke, path: slotPath } of resolveFieldEntries(manifest, fieldPath)) {
|
|
170
|
+
if (!invoke || typeof invoke !== "object" || Array.isArray(invoke)) continue;
|
|
171
|
+
// Relative to the object ENCLOSING the slot, which is the annotation's
|
|
172
|
+
// documented anchor.
|
|
173
|
+
const enclosing = slotPath.slice(0, Math.max(0, slotPath.lastIndexOf(".")));
|
|
174
|
+
const inputsPath = [enclosing, ...pointer].filter(Boolean).join(".");
|
|
175
|
+
const values = navigateConcretePath(manifest, inputsPath);
|
|
176
|
+
if (!values || typeof values !== "object" || Array.isArray(values)) continue;
|
|
177
|
+
|
|
178
|
+
out.push(
|
|
179
|
+
...checkCallSite(
|
|
180
|
+
{ inputsPath, values: values as Record<string, any>, invoke: invoke as Record<string, any> },
|
|
181
|
+
ctx,
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** A JSON Pointer naming a sibling FIELD path. An array index is not a field,
|
|
190
|
+
* so a pointer carrying one names nothing this can resolve. */
|
|
191
|
+
function pointerSegments(pointer: string): string[] | undefined {
|
|
192
|
+
if (!pointer.startsWith("/")) return undefined;
|
|
193
|
+
const segments = pointer
|
|
194
|
+
.slice(1)
|
|
195
|
+
.split("/")
|
|
196
|
+
.map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
197
|
+
return segments.every((s) => s.length > 0 && !/^\d+$/.test(s)) ? segments : undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** One call site: the arguments written, and the reference they are for. */
|
|
201
|
+
interface CallSite {
|
|
202
|
+
/** Concrete path of the argument map, for anchoring a diagnostic. */
|
|
203
|
+
inputsPath: string;
|
|
204
|
+
values: Record<string, any>;
|
|
205
|
+
invoke: Record<string, any>;
|
|
206
|
+
declaredRetryFor?(
|
|
207
|
+
invokedManifest: Record<string, any> | undefined,
|
|
208
|
+
invokedDef: ResourceDefinition | undefined,
|
|
209
|
+
): string | undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
interface CallCheckContext {
|
|
213
|
+
manifest: Record<string, any>;
|
|
214
|
+
allManifests: Record<string, any>[];
|
|
215
|
+
defs: DefinitionRegistry;
|
|
216
|
+
contractScope: ReturnType<typeof analyzerContractScope>;
|
|
217
|
+
readingModule: string | undefined;
|
|
218
|
+
stepContext?: Record<string, any>;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* The check itself, shared by both drivers: the arguments written at a call site
|
|
223
|
+
* against the contract the target declares.
|
|
224
|
+
*/
|
|
225
|
+
function checkCallSite(site: CallSite, ctx: CallCheckContext): StepInputIssue[] {
|
|
226
|
+
const out: StepInputIssue[] = [];
|
|
227
|
+
const { manifest, allManifests, defs, contractScope, readingModule, stepContext } = ctx;
|
|
228
|
+
const { invoke, values } = site;
|
|
229
|
+
{
|
|
230
|
+
{
|
|
95
231
|
const invokedKind = invoke.kind as string | undefined;
|
|
96
232
|
const invokedName = invoke.name as string | undefined;
|
|
97
233
|
const invokedManifest = invokedName
|
|
@@ -104,7 +240,7 @@ export function collectStepInputIssues(
|
|
|
104
240
|
? contractScope.resolveIn(invokedKind, readingModule)
|
|
105
241
|
: undefined;
|
|
106
242
|
const contract = resolveContract("inputType", invokedManifest, invokedDef, contractScope);
|
|
107
|
-
if (!contract) return;
|
|
243
|
+
if (!contract) return out;
|
|
108
244
|
|
|
109
245
|
// Findings AT a substituted path are about a placeholder, not about
|
|
110
246
|
// anything the author wrote — a `pattern`-constrained string or a `oneOf`
|
|
@@ -112,9 +248,15 @@ export function collectStepInputIssues(
|
|
|
112
248
|
// findings (missing required, unknown property) are located at the
|
|
113
249
|
// container and survive the filter.
|
|
114
250
|
const celPaths = new Set<string>();
|
|
115
|
-
const substituted = substituteCelFields(values, contract.schema, undefined,
|
|
116
|
-
celPaths.add(p),
|
|
117
|
-
|
|
251
|
+
const substituted = substituteCelFields(values, contract.schema, undefined, {
|
|
252
|
+
onSubstitute: (p) => celPaths.add(p),
|
|
253
|
+
// A contract may name a shape declared elsewhere. Both halves need the
|
|
254
|
+
// resolver or they disagree about the same slot: the stand-in walk hands
|
|
255
|
+
// its expressions a typeless value, and the check below compiles nothing
|
|
256
|
+
// at all — so a step's arguments went unchecked against exactly the
|
|
257
|
+
// contracts that describe them most precisely.
|
|
258
|
+
external: (ref) => defs.schemaForId(ref),
|
|
259
|
+
});
|
|
118
260
|
// The type-argument check, at the one site where a produced value's schema
|
|
119
261
|
// meets a consuming slot's. A CEL leaf's placeholder says nothing about
|
|
120
262
|
// what the expression yields, so AJV above is silent here by design — and
|
|
@@ -163,10 +305,10 @@ export function collectStepInputIssues(
|
|
|
163
305
|
// already declared: the value's liveness by its value type, and the
|
|
164
306
|
// re-attempt by the retry policy. No kind is named.
|
|
165
307
|
if (isLiveSlot(produced)) {
|
|
166
|
-
const retry =
|
|
308
|
+
const retry = site.declaredRetryFor?.(invokedManifest, invokedDef);
|
|
167
309
|
if (retry !== undefined) {
|
|
168
310
|
out.push({
|
|
169
|
-
path: `${
|
|
311
|
+
path: `${site.inputsPath}.${inputName}`,
|
|
170
312
|
targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
|
|
171
313
|
message:
|
|
172
314
|
`'${inputName}' is a live value, which is consumed by reading and so exists ` +
|
|
@@ -184,7 +326,7 @@ export function collectStepInputIssues(
|
|
|
184
326
|
);
|
|
185
327
|
if (compatible) continue;
|
|
186
328
|
out.push({
|
|
187
|
-
path: `${
|
|
329
|
+
path: `${site.inputsPath}.${inputName}`,
|
|
188
330
|
targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
|
|
189
331
|
message: issues.join("; "),
|
|
190
332
|
code: "CEL_TYPE_ARGUMENT_MISMATCH",
|
|
@@ -192,7 +334,7 @@ export function collectStepInputIssues(
|
|
|
192
334
|
}
|
|
193
335
|
}
|
|
194
336
|
|
|
195
|
-
for (const issue of
|
|
337
|
+
for (const issue of defs.validateResourceConfig(substituted, contract.schema)) {
|
|
196
338
|
if (celPaths.has(issue.path)) continue;
|
|
197
339
|
// A missing-required issue names the property that ISN'T there, so
|
|
198
340
|
// anchoring on it finds no node and the diagnostic degrades to 1:1 —
|
|
@@ -200,12 +342,12 @@ export function collectStepInputIssues(
|
|
|
200
342
|
// container that should have held it, which does exist.
|
|
201
343
|
const anchor = missingRequired(issue) ? containerOf(issue.path) : issue.path;
|
|
202
344
|
out.push({
|
|
203
|
-
path: anchor ? `${
|
|
345
|
+
path: anchor ? `${site.inputsPath}.${anchor}` : site.inputsPath,
|
|
204
346
|
targetLabel: invokedName ?? invokedKind ?? "the invoked resource",
|
|
205
347
|
message: issue.message,
|
|
206
348
|
});
|
|
207
349
|
}
|
|
208
|
-
}
|
|
350
|
+
}
|
|
209
351
|
}
|
|
210
352
|
return out;
|
|
211
353
|
}
|