@intentset/core 0.4.0 → 0.6.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/LICENSE +21 -0
- package/dist/artifact.d.ts.map +1 -1
- package/dist/artifact.js +110 -1
- package/dist/artifact.js.map +1 -1
- package/dist/export.d.ts +1 -1
- package/dist/export.js +13 -1
- package/dist/export.js.map +1 -1
- package/dist/read-export.d.ts.map +1 -1
- package/dist/read-export.js +38 -3
- package/dist/read-export.js.map +1 -1
- package/dist/registries.d.ts +2 -2
- package/dist/registries.js +1 -1
- package/dist/registries.js.map +1 -1
- package/dist/types.d.ts +38 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -4
- package/dist/types.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +58 -5
- package/dist/validate.js.map +1 -1
- package/package.json +2 -2
- package/src/artifact.ts +152 -0
- package/src/export.ts +14 -1
- package/src/read-export.ts +38 -2
- package/src/registries.ts +3 -3
- package/src/types.ts +44 -4
- package/src/validate.ts +64 -7
package/src/validate.ts
CHANGED
|
@@ -112,6 +112,7 @@ export function validate(
|
|
|
112
112
|
const graph = buildGraph(artifacts);
|
|
113
113
|
const context: Context = { graph, registries, unreadable, locators, diagnostics, levelIndex: LEVELS.indexOf(level) };
|
|
114
114
|
checkRelationships(context);
|
|
115
|
+
checkTips(context);
|
|
115
116
|
checkCycles(context);
|
|
116
117
|
checkLifecycle(context);
|
|
117
118
|
checkAttachment(context);
|
|
@@ -262,6 +263,24 @@ function checkRelationships(context: Context): void {
|
|
|
262
263
|
}
|
|
263
264
|
}
|
|
264
265
|
|
|
266
|
+
/** Core §9: every tip on a knowledge record is keyed by an ID the record explains (CORE003). */
|
|
267
|
+
function checkTips(context: Context): void {
|
|
268
|
+
for (const artifact of sortedArtifacts(context.graph)) {
|
|
269
|
+
const { meta } = artifact;
|
|
270
|
+
if (meta.tips === undefined) continue;
|
|
271
|
+
const explained = new Set(meta.links.explains ?? []);
|
|
272
|
+
for (const key of Object.keys(meta.tips).sort(compareStrings)) {
|
|
273
|
+
if (explained.has(key)) continue;
|
|
274
|
+
reportOn(context, artifact, {
|
|
275
|
+
code: "CORE003",
|
|
276
|
+
field: `/intentset/tips/${key}`,
|
|
277
|
+
message: `${meta.id} tips ${key}, which it does not explain.`,
|
|
278
|
+
remediation: `Add ${key} to links.explains, or key the tip by an ID the knowledge explains (Core §9).`,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
265
284
|
/** Core §5: cycles in `parent`, `replacedBy` and `requires`, a self-reference being the shortest. */
|
|
266
285
|
function checkCycles(context: Context): void {
|
|
267
286
|
const { graph } = context;
|
|
@@ -395,6 +414,18 @@ function checkLifecycle(context: Context): void {
|
|
|
395
414
|
});
|
|
396
415
|
}
|
|
397
416
|
}
|
|
417
|
+
if (meta.measure !== undefined) {
|
|
418
|
+
if (registries.evidenceSources.length === 0) use("evidenceSources");
|
|
419
|
+
else if (!registries.evidenceSources.includes(meta.measure.source)) {
|
|
420
|
+
reportOn(context, artifact, {
|
|
421
|
+
code: "CORE005",
|
|
422
|
+
field: "/intentset/measure/source",
|
|
423
|
+
message: `measure source ${meta.measure.source} is not in the evidenceSources registry.`,
|
|
424
|
+
remediation:
|
|
425
|
+
"Add the analytics product, study or survey to registries.yaml evidenceSources, or name a registered source.",
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
398
429
|
meta.slice?.usesResources.forEach((resource, i) => {
|
|
399
430
|
if (registries.resources.length === 0) use("resources");
|
|
400
431
|
else if (!resourceIds.has(resource)) {
|
|
@@ -446,12 +477,22 @@ interface Requirement {
|
|
|
446
477
|
remediation: string;
|
|
447
478
|
/** Core §5 names no active obligation for this type; only the draft warning applies. */
|
|
448
479
|
draftOnly?: boolean;
|
|
480
|
+
/** Only edges from these types count: an outcome is judged by its measures, not by the capabilities under it. */
|
|
481
|
+
fromTypes?: readonly ArtifactType[];
|
|
449
482
|
}
|
|
450
483
|
|
|
451
484
|
function requirement(artifact: Artifact): Requirement | null {
|
|
452
485
|
const { meta } = artifact;
|
|
453
486
|
const id = meta.id;
|
|
454
487
|
switch (meta.type) {
|
|
488
|
+
case "outcome":
|
|
489
|
+
return {
|
|
490
|
+
kinds: ["parent"],
|
|
491
|
+
direction: "in",
|
|
492
|
+
fromTypes: ["measure"],
|
|
493
|
+
message: "no measure names it as parent, so nothing says how it will be judged",
|
|
494
|
+
remediation: `Add a measure record with \`parent: ${id}\` carrying metric, baseline, target, window and source (Core §6).`,
|
|
495
|
+
};
|
|
455
496
|
case "rule":
|
|
456
497
|
return {
|
|
457
498
|
kinds: ["governedBy"],
|
|
@@ -510,18 +551,27 @@ function requirement(artifact: Artifact): Requirement | null {
|
|
|
510
551
|
}
|
|
511
552
|
|
|
512
553
|
/**
|
|
513
|
-
* Core §5 and VSA §3: active rules, scenarios, verifications, knowledge and
|
|
514
|
-
* product slices are attached
|
|
515
|
-
* and decisions nothing refers to, are
|
|
554
|
+
* Core §5, §6 and VSA §3: active rules, scenarios, verifications, knowledge and
|
|
555
|
+
* product slices are attached, and an active outcome has a measure (CORE003).
|
|
556
|
+
* Their drafts, and draft contracts and decisions nothing refers to, are
|
|
557
|
+
* unattached drafts (CORE009, warning).
|
|
516
558
|
*/
|
|
517
559
|
function checkAttachment(context: Context): void {
|
|
518
560
|
const { graph } = context;
|
|
519
561
|
for (const artifact of sortedArtifacts(graph)) {
|
|
520
562
|
const need = requirement(artifact);
|
|
521
563
|
if (need === null || artifact.meta.status === "retired") continue;
|
|
522
|
-
const edges = need.kinds
|
|
523
|
-
|
|
524
|
-
|
|
564
|
+
const edges = need.kinds
|
|
565
|
+
.flatMap((kind) =>
|
|
566
|
+
need.direction === "in"
|
|
567
|
+
? incomingEdges(graph, artifact.meta.id, kind)
|
|
568
|
+
: outgoing(graph, artifact.meta.id, kind),
|
|
569
|
+
)
|
|
570
|
+
.filter((edge) => {
|
|
571
|
+
if (need.fromTypes === undefined) return true;
|
|
572
|
+
const from = graph.artifacts.get(edge.from);
|
|
573
|
+
return from !== undefined && need.fromTypes.includes(from.meta.type);
|
|
574
|
+
});
|
|
525
575
|
if (edges.length > 0) continue;
|
|
526
576
|
const field = need.direction === "out" ? `/intentset/links/${need.kinds[0]}` : undefined;
|
|
527
577
|
if (artifact.meta.status === "draft") {
|
|
@@ -553,7 +603,7 @@ export function patternProblem(pattern: string): string | null {
|
|
|
553
603
|
return null;
|
|
554
604
|
}
|
|
555
605
|
|
|
556
|
-
/** VSA §3
|
|
606
|
+
/** VSA §3, Core §6 and §8: type-specific blocks on their own type only; technical slices give a rationale and implement nothing; patterns are plain. */
|
|
557
607
|
function checkSlices(context: Context): void {
|
|
558
608
|
const { graph } = context;
|
|
559
609
|
for (const artifact of sortedArtifacts(graph)) {
|
|
@@ -567,6 +617,13 @@ function checkSlices(context: Context): void {
|
|
|
567
617
|
"Remove the verification block, or change the type (Core §8).",
|
|
568
618
|
);
|
|
569
619
|
}
|
|
620
|
+
if (artifact.meta.measure !== undefined && artifact.meta.type !== "measure") {
|
|
621
|
+
profile(
|
|
622
|
+
"/intentset/measure",
|
|
623
|
+
`${id} is ${aType(artifact.meta.type)} and carries measure metadata, which only a measure has.`,
|
|
624
|
+
"Remove the measure block, or change the type (Core §6).",
|
|
625
|
+
);
|
|
626
|
+
}
|
|
570
627
|
const slice = artifact.meta.slice;
|
|
571
628
|
if (slice === undefined) continue;
|
|
572
629
|
if (artifact.meta.type !== "slice") {
|