@astra-spec/sdk 0.0.1 → 0.0.3
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/helpers.d.ts +17 -22
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +41 -62
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -9
- package/dist/index.js.map +1 -1
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/narrative.d.ts +1 -2
- package/dist/validation/narrative.d.ts.map +1 -1
- package/dist/validation/narrative.js +6 -18
- package/dist/validation/narrative.js.map +1 -1
- package/dist/validation/schema.d.ts.map +1 -1
- package/dist/validation/schema.js +3 -3
- package/dist/validation/schema.js.map +1 -1
- package/dist/validation/semantic.d.ts +3 -4
- package/dist/validation/semantic.d.ts.map +1 -1
- package/dist/validation/semantic.js +44 -71
- package/dist/validation/semantic.js.map +1 -1
- package/package.json +1 -1
- package/src/helpers.ts +53 -81
- package/src/index.ts +0 -8
- package/src/validation/index.ts +2 -2
- package/src/validation/narrative.ts +13 -24
- package/src/validation/schema.ts +2 -3
- package/src/validation/semantic.ts +91 -125
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
// section-required-when-data-present rule.
|
|
1
|
+
import { dirname } from "node:path";
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type Dict,
|
|
5
|
+
asArray,
|
|
6
|
+
asDict,
|
|
7
|
+
getInputIds,
|
|
8
|
+
getOutputIds,
|
|
9
|
+
loadYaml,
|
|
10
|
+
resolveAnalysisTree,
|
|
11
|
+
} from "../helpers.js";
|
|
5
12
|
import { SemanticError } from "./semantic.js";
|
|
6
13
|
|
|
7
14
|
const HREF_RE = /\[[^\]]*\]\(([^)\s]+)\)/g;
|
|
@@ -29,16 +36,6 @@ const COVERAGE_LABELS: Record<string, string> = {
|
|
|
29
36
|
|
|
30
37
|
const NARRATIVE_SECTIONS = ["summary", "findings", "methods", "inputs", "outputs"] as const;
|
|
31
38
|
|
|
32
|
-
type Dict = Record<string, unknown>;
|
|
33
|
-
|
|
34
|
-
function asDict(v: unknown): Dict | undefined {
|
|
35
|
-
return v && typeof v === "object" && !Array.isArray(v) ? (v as Dict) : undefined;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function asArray(v: unknown): unknown[] {
|
|
39
|
-
return Array.isArray(v) ? v : [];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
39
|
export class NarrativeWarning {
|
|
43
40
|
constructor(
|
|
44
41
|
public readonly code: string,
|
|
@@ -105,14 +102,8 @@ function getNodeAt(root: Dict, path: string[]): Dict | null {
|
|
|
105
102
|
}
|
|
106
103
|
|
|
107
104
|
function lookupElement(node: Dict, category: string, elementId: string, optionId: string | null): boolean {
|
|
108
|
-
if (category === "inputs")
|
|
109
|
-
|
|
110
|
-
return ids.includes(elementId);
|
|
111
|
-
}
|
|
112
|
-
if (category === "outputs") {
|
|
113
|
-
const ids = (asArray(node.outputs) as Dict[]).map((o) => o?.id as string | undefined).filter(Boolean);
|
|
114
|
-
return ids.includes(elementId);
|
|
115
|
-
}
|
|
105
|
+
if (category === "inputs") return getInputIds(node).has(elementId);
|
|
106
|
+
if (category === "outputs") return getOutputIds(node).has(elementId);
|
|
116
107
|
if (category === "decisions") {
|
|
117
108
|
const decisions = asDict(node.decisions) ?? {};
|
|
118
109
|
if (!(elementId in decisions)) return false;
|
|
@@ -291,7 +282,7 @@ function* iterCoverageIds(node: Dict, category: string): Generator<string> {
|
|
|
291
282
|
yield did;
|
|
292
283
|
}
|
|
293
284
|
} else if (category === "outputs") {
|
|
294
|
-
for (const out of asArray(node.outputs)
|
|
285
|
+
for (const out of asArray<Dict>(node.outputs)) {
|
|
295
286
|
const oid = out?.id as string | undefined;
|
|
296
287
|
if (oid) yield oid;
|
|
297
288
|
}
|
|
@@ -374,8 +365,6 @@ function walkSectionRequirements(node: Dict, path: string[], errors: SemanticErr
|
|
|
374
365
|
}
|
|
375
366
|
}
|
|
376
367
|
|
|
377
|
-
import { dirname } from "node:path";
|
|
378
|
-
|
|
379
368
|
export function validateNarrativeAnchorsFile(filePath: string): SemanticError[] {
|
|
380
369
|
return validateNarrativeAnchors(loadYaml(filePath), { basePath: dirname(filePath) });
|
|
381
370
|
}
|
package/src/validation/schema.ts
CHANGED
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
loadAstraSchema,
|
|
14
14
|
} from "../schema/index.js";
|
|
15
15
|
import {
|
|
16
|
-
deepClone,
|
|
17
16
|
injectAnalysisIdsInPlace,
|
|
18
17
|
injectUniverseIdsInPlace,
|
|
19
18
|
loadYaml,
|
|
@@ -81,7 +80,7 @@ export async function validateAnalysisData(
|
|
|
81
80
|
): Promise<string[]> {
|
|
82
81
|
const schema = await resolveSchema(opts);
|
|
83
82
|
const { analysis } = compileFor(schema);
|
|
84
|
-
const prepared =
|
|
83
|
+
const prepared = structuredClone(data);
|
|
85
84
|
if (prepared.id === undefined) prepared.id = "root";
|
|
86
85
|
injectAnalysisIdsInPlace(prepared);
|
|
87
86
|
if (analysis(prepared)) return [];
|
|
@@ -94,7 +93,7 @@ export async function validateUniverseData(
|
|
|
94
93
|
): Promise<string[]> {
|
|
95
94
|
const schema = await resolveSchema(opts);
|
|
96
95
|
const { universe } = compileFor(schema);
|
|
97
|
-
const prepared =
|
|
96
|
+
const prepared = structuredClone(data);
|
|
98
97
|
injectUniverseIdsInPlace(prepared);
|
|
99
98
|
if (universe(prepared)) return [];
|
|
100
99
|
return (universe.errors ?? []).map(formatAjvError);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
// `from:`-path direction rules. Operates on parsed dict-like data
|
|
3
|
-
// (matching the Python validator) so it doesn't depend on type narrowing.
|
|
1
|
+
import { dirname } from "node:path";
|
|
4
2
|
|
|
5
3
|
import {
|
|
4
|
+
type Dict,
|
|
5
|
+
asArray,
|
|
6
|
+
asDict,
|
|
6
7
|
collectNodeDecisions,
|
|
7
8
|
getInputIds,
|
|
8
9
|
getOutputIds,
|
|
@@ -26,16 +27,6 @@ export class SemanticError {
|
|
|
26
27
|
|
|
27
28
|
const ID_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
28
29
|
|
|
29
|
-
type Dict = Record<string, unknown>;
|
|
30
|
-
|
|
31
|
-
function asDict(v: unknown): Dict | undefined {
|
|
32
|
-
return v && typeof v === "object" && !Array.isArray(v) ? (v as Dict) : undefined;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function asArray(v: unknown): unknown[] {
|
|
36
|
-
return Array.isArray(v) ? v : [];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
30
|
/** Parse a `../scope.id` style path. Returns null on malformed input. */
|
|
40
31
|
function parseFromPath(ref: string): { up: number; segments: string[] } | null {
|
|
41
32
|
let up = 0;
|
|
@@ -101,8 +92,8 @@ export function validateAnalysis(
|
|
|
101
92
|
}
|
|
102
93
|
}
|
|
103
94
|
|
|
104
|
-
const inputs = asArray(working.inputs)
|
|
105
|
-
const outputs = asArray(working.outputs)
|
|
95
|
+
const inputs = asArray<Dict>(working.inputs);
|
|
96
|
+
const outputs = asArray<Dict>(working.outputs);
|
|
106
97
|
const priorInsights = asDict(working.prior_insights) ?? {};
|
|
107
98
|
|
|
108
99
|
const inputIds = new Set<string>();
|
|
@@ -138,7 +129,7 @@ export function validateAnalysis(
|
|
|
138
129
|
for (const [analysisId, raw] of Object.entries(subAnalyses)) {
|
|
139
130
|
const sub = asDict(raw);
|
|
140
131
|
if (!sub) continue;
|
|
141
|
-
for (const out of asArray(sub.outputs)
|
|
132
|
+
for (const out of asArray<Dict>(sub.outputs)) {
|
|
142
133
|
const oid = out?.id as string | undefined;
|
|
143
134
|
if (oid) subOutputIds.add(`${analysisId}.${oid}`);
|
|
144
135
|
}
|
|
@@ -178,7 +169,6 @@ function _validateAnalysisNode(
|
|
|
178
169
|
const errors: SemanticError[] = [];
|
|
179
170
|
const nodePath = `${pathPrefix}.${nodeId}`;
|
|
180
171
|
|
|
181
|
-
// Path-only stub (not yet resolved): skip deep checks.
|
|
182
172
|
if (node.path && !node.inputs && !node.outputs) return errors;
|
|
183
173
|
|
|
184
174
|
for (const field of ["inputs", "outputs"]) {
|
|
@@ -193,7 +183,6 @@ function _validateAnalysisNode(
|
|
|
193
183
|
}
|
|
194
184
|
}
|
|
195
185
|
|
|
196
|
-
// Decision `from:` checks.
|
|
197
186
|
const allDecisions = (asDict(node.decisions) ?? {}) as Record<string, Dict>;
|
|
198
187
|
for (const [decisionId, decision] of Object.entries(allDecisions)) {
|
|
199
188
|
const ref = decision?.from as string | undefined;
|
|
@@ -204,8 +193,7 @@ function _validateAnalysisNode(
|
|
|
204
193
|
}
|
|
205
194
|
}
|
|
206
195
|
|
|
207
|
-
|
|
208
|
-
const nodeInputs = asArray(node.inputs) as Dict[];
|
|
196
|
+
const nodeInputs = asArray<Dict>(node.inputs);
|
|
209
197
|
const nodeInputIds = new Set<string>();
|
|
210
198
|
for (const inp of nodeInputs) {
|
|
211
199
|
const id = inp?.id as string | undefined;
|
|
@@ -214,9 +202,8 @@ function _validateAnalysisNode(
|
|
|
214
202
|
if (ref) errors.push(..._validateInputFrom(ref, ancestorChain, nodeId, nodePath));
|
|
215
203
|
}
|
|
216
204
|
|
|
217
|
-
// Output IDs unique.
|
|
218
205
|
const nodeOutputIds = new Set<string>();
|
|
219
|
-
for (const out of asArray(node.outputs)
|
|
206
|
+
for (const out of asArray<Dict>(node.outputs)) {
|
|
220
207
|
const id = out?.id as string | undefined;
|
|
221
208
|
if (id && nodeOutputIds.has(id)) {
|
|
222
209
|
errors.push(
|
|
@@ -230,13 +217,13 @@ function _validateAnalysisNode(
|
|
|
230
217
|
if (id) nodeOutputIds.add(id);
|
|
231
218
|
}
|
|
232
219
|
|
|
233
|
-
const nodeOutputs = asArray(node.outputs)
|
|
220
|
+
const nodeOutputs = asArray<Dict>(node.outputs);
|
|
234
221
|
errors.push(..._validateOutputsFrom(nodeOutputs, node, nodePath));
|
|
235
222
|
|
|
236
223
|
const nodeDecisions = collectNodeDecisions(node) as Record<string, Dict>;
|
|
237
224
|
|
|
238
225
|
// Build the constraint scope: locally-defined decisions plus any `from:`
|
|
239
|
-
// alias resolved one ancestor up (matches the Python
|
|
226
|
+
// alias resolved one ancestor up (matches the Python constraint_scope).
|
|
240
227
|
const constraintScope: Record<string, Dict> = { ...nodeDecisions };
|
|
241
228
|
for (const [decisionId, decision] of Object.entries(allDecisions)) {
|
|
242
229
|
const ref = decision?.from as string | undefined;
|
|
@@ -272,7 +259,7 @@ function _validateAnalysisNode(
|
|
|
272
259
|
for (const [subId, raw] of Object.entries(subAnalyses)) {
|
|
273
260
|
const sub = asDict(raw);
|
|
274
261
|
if (!sub) continue;
|
|
275
|
-
for (const out of asArray(sub.outputs)
|
|
262
|
+
for (const out of asArray<Dict>(sub.outputs)) {
|
|
276
263
|
const oid = out?.id as string | undefined;
|
|
277
264
|
if (oid) subOutputIds.add(`${subId}.${oid}`);
|
|
278
265
|
}
|
|
@@ -334,7 +321,7 @@ function _validateInsightArtifacts(
|
|
|
334
321
|
const insight = asDict(raw);
|
|
335
322
|
if (!insight) continue;
|
|
336
323
|
const insightPath = `${prefix}.${insightId}`;
|
|
337
|
-
const evidenceList = asArray(insight.evidence)
|
|
324
|
+
const evidenceList = asArray<Dict>(insight.evidence);
|
|
338
325
|
evidenceList.forEach((ev, i) => {
|
|
339
326
|
const artifactRef = ev?.artifact as string | undefined;
|
|
340
327
|
if (artifactRef !== undefined && !outputIds.has(artifactRef)) {
|
|
@@ -376,57 +363,20 @@ function _validateDecisions(
|
|
|
376
363
|
);
|
|
377
364
|
}
|
|
378
365
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
new SemanticError(
|
|
388
|
-
"INVALID_WHEN_REF",
|
|
389
|
-
`Decision 'when' condition '${cond}' has invalid format`,
|
|
390
|
-
decisionPath,
|
|
391
|
-
),
|
|
392
|
-
);
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
const [whenDecisionId, whenOptionId] = parts as [string, string];
|
|
396
|
-
if (!(whenDecisionId in decisions) && !(whenDecisionId in scope)) {
|
|
397
|
-
errors.push(
|
|
398
|
-
new SemanticError(
|
|
399
|
-
"INVALID_WHEN_REF",
|
|
400
|
-
`'when' references non-existent decision '${whenDecisionId}'`,
|
|
401
|
-
decisionPath,
|
|
402
|
-
),
|
|
403
|
-
);
|
|
404
|
-
} else {
|
|
405
|
-
const refDecision = decisions[whenDecisionId] ?? scope[whenDecisionId];
|
|
406
|
-
const refOptions = refDecision ? (asDict(refDecision.options) ?? {}) : {};
|
|
407
|
-
if (refDecision && !(whenOptionId in refOptions)) {
|
|
408
|
-
errors.push(
|
|
409
|
-
new SemanticError(
|
|
410
|
-
"INVALID_WHEN_REF",
|
|
411
|
-
`'when' references non-existent option '${whenOptionId}' in decision '${whenDecisionId}'`,
|
|
412
|
-
decisionPath,
|
|
413
|
-
),
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
if (whenDecisionId === decisionId) {
|
|
418
|
-
errors.push(
|
|
419
|
-
new SemanticError("INVALID_WHEN_REF", "'when' cannot reference own decision", decisionPath),
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
366
|
+
errors.push(
|
|
367
|
+
..._validateWhenRefs(decision.when, {
|
|
368
|
+
decisions: { ...scope, ...decisions },
|
|
369
|
+
path: decisionPath,
|
|
370
|
+
ownerKind: "Decision",
|
|
371
|
+
forbidSelfRef: decisionId,
|
|
372
|
+
}),
|
|
373
|
+
);
|
|
424
374
|
|
|
425
375
|
for (const [optionId, optionRaw] of Object.entries(options)) {
|
|
426
376
|
const option = optionRaw;
|
|
427
377
|
const optionPath = `${decisionPath}.options.${optionId}`;
|
|
428
378
|
|
|
429
|
-
const insightRefs = asArray(option.insights)
|
|
379
|
+
const insightRefs = asArray<string>(option.insights);
|
|
430
380
|
insightRefs.forEach((insightRef, i) => {
|
|
431
381
|
if (!(insightRef in priorInsights)) {
|
|
432
382
|
errors.push(
|
|
@@ -439,10 +389,10 @@ function _validateDecisions(
|
|
|
439
389
|
}
|
|
440
390
|
});
|
|
441
391
|
|
|
442
|
-
for (const ref of asArray(option.incompatible_with)
|
|
392
|
+
for (const ref of asArray<string>(option.incompatible_with)) {
|
|
443
393
|
errors.push(..._validateConstraintRef(ref, scope, optionPath));
|
|
444
394
|
}
|
|
445
|
-
for (const ref of asArray(option.requires)
|
|
395
|
+
for (const ref of asArray<string>(option.requires)) {
|
|
446
396
|
errors.push(..._validateConstraintRef(ref, scope, optionPath));
|
|
447
397
|
}
|
|
448
398
|
|
|
@@ -492,52 +442,78 @@ function _validateOutputWhen(
|
|
|
492
442
|
): SemanticError[] {
|
|
493
443
|
const errors: SemanticError[] = [];
|
|
494
444
|
const outputsPrefix = pathPrefix ? `${pathPrefix}.outputs` : "outputs";
|
|
495
|
-
|
|
496
445
|
for (const out of outputs) {
|
|
497
446
|
const id = out?.id as string | undefined;
|
|
498
447
|
if (!id) continue;
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
448
|
+
errors.push(
|
|
449
|
+
..._validateWhenRefs(out.when, {
|
|
450
|
+
decisions,
|
|
451
|
+
path: `${outputsPrefix}.${id}`,
|
|
452
|
+
ownerKind: "Output",
|
|
453
|
+
}),
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
return errors;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
interface WhenRefContext {
|
|
460
|
+
decisions: Record<string, Dict>;
|
|
461
|
+
path: string;
|
|
462
|
+
ownerKind: "Decision" | "Output";
|
|
463
|
+
/** Decision ID that may not appear in its own `when` clause. */
|
|
464
|
+
forbidSelfRef?: string;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function _validateWhenRefs(
|
|
468
|
+
when: unknown,
|
|
469
|
+
ctx: WhenRefContext,
|
|
470
|
+
): SemanticError[] {
|
|
471
|
+
if (when == null) return [];
|
|
472
|
+
const conds = typeof when === "string" ? [when] : Array.isArray(when) ? (when as string[]) : [];
|
|
473
|
+
const errors: SemanticError[] = [];
|
|
474
|
+
for (const cond of conds) {
|
|
475
|
+
const ref = cond.startsWith("~") ? cond.slice(1) : cond;
|
|
476
|
+
const parts = ref.split(".");
|
|
477
|
+
if (parts.length !== 2) {
|
|
478
|
+
errors.push(
|
|
479
|
+
new SemanticError(
|
|
480
|
+
"INVALID_WHEN_REF",
|
|
481
|
+
`${ctx.ownerKind} 'when' condition '${cond}' has invalid format`,
|
|
482
|
+
ctx.path,
|
|
483
|
+
),
|
|
484
|
+
);
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const [decisionId, optionId] = parts as [string, string];
|
|
488
|
+
const referenced = ctx.decisions[decisionId];
|
|
489
|
+
if (!referenced) {
|
|
490
|
+
const subject = ctx.ownerKind === "Output" ? `${ctx.ownerKind} 'when'` : "'when'";
|
|
491
|
+
errors.push(
|
|
492
|
+
new SemanticError(
|
|
493
|
+
"INVALID_WHEN_REF",
|
|
494
|
+
`${subject} references non-existent decision '${decisionId}'`,
|
|
495
|
+
ctx.path,
|
|
496
|
+
),
|
|
497
|
+
);
|
|
498
|
+
} else {
|
|
499
|
+
const refOptions = asDict(referenced.options) ?? {};
|
|
500
|
+
if (!(optionId in refOptions)) {
|
|
501
|
+
const subject = ctx.ownerKind === "Output" ? `${ctx.ownerKind} 'when'` : "'when'";
|
|
518
502
|
errors.push(
|
|
519
503
|
new SemanticError(
|
|
520
504
|
"INVALID_WHEN_REF",
|
|
521
|
-
|
|
522
|
-
|
|
505
|
+
`${subject} references non-existent option '${optionId}' in decision '${decisionId}'`,
|
|
506
|
+
ctx.path,
|
|
523
507
|
),
|
|
524
508
|
);
|
|
525
|
-
} else {
|
|
526
|
-
const refDecision = decisions[decisionId]!;
|
|
527
|
-
const refOptions = (asDict(refDecision.options) ?? {}) as Record<string, Dict>;
|
|
528
|
-
if (!(optionId in refOptions)) {
|
|
529
|
-
errors.push(
|
|
530
|
-
new SemanticError(
|
|
531
|
-
"INVALID_WHEN_REF",
|
|
532
|
-
`Output 'when' references non-existent option '${optionId}' in decision '${decisionId}'`,
|
|
533
|
-
outputPath,
|
|
534
|
-
),
|
|
535
|
-
);
|
|
536
|
-
}
|
|
537
509
|
}
|
|
538
510
|
}
|
|
511
|
+
if (ctx.forbidSelfRef && decisionId === ctx.forbidSelfRef) {
|
|
512
|
+
errors.push(
|
|
513
|
+
new SemanticError("INVALID_WHEN_REF", "'when' cannot reference own decision", ctx.path),
|
|
514
|
+
);
|
|
515
|
+
}
|
|
539
516
|
}
|
|
540
|
-
|
|
541
517
|
return errors;
|
|
542
518
|
}
|
|
543
519
|
|
|
@@ -573,7 +549,7 @@ function _validateOutputDependencies(
|
|
|
573
549
|
continue;
|
|
574
550
|
}
|
|
575
551
|
|
|
576
|
-
const declaredInputs = asArray(out.inputs)
|
|
552
|
+
const declaredInputs = asArray<string>(out.inputs);
|
|
577
553
|
depGraph[id] = declaredInputs.filter((i) => siblingOrExtra.has(i));
|
|
578
554
|
|
|
579
555
|
for (const inpId of declaredInputs) {
|
|
@@ -588,7 +564,7 @@ function _validateOutputDependencies(
|
|
|
588
564
|
}
|
|
589
565
|
}
|
|
590
566
|
|
|
591
|
-
const declaredDecisions = asArray(out.decisions)
|
|
567
|
+
const declaredDecisions = asArray<string>(out.decisions);
|
|
592
568
|
for (const decId of declaredDecisions) {
|
|
593
569
|
if (!(decId in decisionsInScope)) {
|
|
594
570
|
errors.push(
|
|
@@ -918,10 +894,6 @@ function _validateConstraintRef(
|
|
|
918
894
|
return [];
|
|
919
895
|
}
|
|
920
896
|
|
|
921
|
-
// ---------------------------------------------------------------------------
|
|
922
|
-
// Universe validation
|
|
923
|
-
// ---------------------------------------------------------------------------
|
|
924
|
-
|
|
925
897
|
export function validateUniverse(
|
|
926
898
|
universeData: Dict,
|
|
927
899
|
analysisData: Dict,
|
|
@@ -1082,7 +1054,7 @@ function _validateNodeUniverseConstraints(
|
|
|
1082
1054
|
if (!option) continue;
|
|
1083
1055
|
const path = `${pathPrefix}.${decisionId}`;
|
|
1084
1056
|
|
|
1085
|
-
for (const ref of asArray(option.incompatible_with)
|
|
1057
|
+
for (const ref of asArray<string>(option.incompatible_with)) {
|
|
1086
1058
|
const parts = ref.split(".");
|
|
1087
1059
|
if (parts.length === 2 && universeDecisions[parts[0]!] === parts[1]!) {
|
|
1088
1060
|
errors.push(
|
|
@@ -1094,7 +1066,7 @@ function _validateNodeUniverseConstraints(
|
|
|
1094
1066
|
);
|
|
1095
1067
|
}
|
|
1096
1068
|
}
|
|
1097
|
-
for (const ref of asArray(option.requires)
|
|
1069
|
+
for (const ref of asArray<string>(option.requires)) {
|
|
1098
1070
|
const parts = ref.split(".");
|
|
1099
1071
|
if (parts.length === 2 && universeDecisions[parts[0]!] !== parts[1]!) {
|
|
1100
1072
|
const actual = universeDecisions[parts[0]!] ?? "(not set)";
|
|
@@ -1111,17 +1083,11 @@ function _validateNodeUniverseConstraints(
|
|
|
1111
1083
|
return errors;
|
|
1112
1084
|
}
|
|
1113
1085
|
|
|
1114
|
-
|
|
1115
|
-
// File-level wrappers
|
|
1116
|
-
// ---------------------------------------------------------------------------
|
|
1117
|
-
|
|
1118
|
-
import { dirname } from "node:path";
|
|
1119
|
-
|
|
1120
|
-
export function validateAnalysisFile(filePath: string): SemanticError[] {
|
|
1086
|
+
export function semanticValidateAnalysisFile(filePath: string): SemanticError[] {
|
|
1121
1087
|
return validateAnalysis(loadYaml(filePath), { basePath: dirname(filePath) });
|
|
1122
1088
|
}
|
|
1123
1089
|
|
|
1124
|
-
export function
|
|
1090
|
+
export function semanticValidateUniverseFile(
|
|
1125
1091
|
universePath: string,
|
|
1126
1092
|
analysisPath: string,
|
|
1127
1093
|
): SemanticError[] {
|