@intentset/core 0.6.1 → 0.7.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/src/types.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  /**
2
- * The vocabulary of Core v0.1 (spec/core-0.1.md §2, §4, §5, §7) and the shapes
2
+ * The vocabulary of Core v0.2 (spec/core-0.2.md §2, §4, §5, §7) and the shapes
3
3
  * the packages exchange. This file is the contract; it carries no logic.
4
4
  */
5
5
  import type { Diagnostic } from "./diagnostics.ts";
6
6
 
7
- export const SPEC_VERSION = "0.1";
7
+ export const SPEC_VERSION = "0.2";
8
+
9
+ /** The version `SPEC_VERSION` followed (Core §1), named so the error for a record still at it can point to `intentset migrate`. */
10
+ export const PREVIOUS_SPEC_VERSION = "0.1";
8
11
 
9
12
  export const ARTIFACT_TYPES = [
10
13
  "product",
@@ -136,6 +139,8 @@ export interface VerificationMeta {
136
139
  method: "automated" | "manual";
137
140
  locator: string;
138
141
  selector: string;
142
+ /** Slice or resource IDs the check drives beyond what its behaviors' slices reach (Core §8, ADR 0013); unique. */
143
+ exercises?: string[];
139
144
  }
140
145
 
141
146
  export const MEASURE_DIRECTIONS = ["increase", "decrease"] as const;
@@ -286,6 +291,20 @@ export interface Config {
286
291
  registries: string | null;
287
292
  /** Globs excluded from scope and from source enumeration. */
288
293
  ignore: string[];
294
+ /** How run evidence is judged current (Core §8, ADR 0013). */
295
+ evidence: EvidenceConfig;
296
+ }
297
+
298
+ /** The two bindings of run evidence to what it checks (Core §8). */
299
+ export const EVIDENCE_BINDINGS = ["subject", "commit"] as const;
300
+ export type EvidenceBinding = (typeof EVIDENCE_BINDINGS)[number];
301
+
302
+ /** `evidence:` in `.intentset/config.yaml`; both members optional. */
303
+ export interface EvidenceConfig {
304
+ /** The binding a repository asks for; null leaves the default (subject where slices exist, else commit). */
305
+ binding: EvidenceBinding | null;
306
+ /** Files whose change affects every check and that belong to no slice: paths or patterns, mixed into every subject. */
307
+ globalInputs: string[];
289
308
  }
290
309
 
291
310
  /** Conformance levels, Core §11. Checks above the requested level do not run. */
@@ -336,8 +355,11 @@ export interface ImpactReport {
336
355
  * the first version a consumer could pin: 0.1 carried untyped report slots and
337
356
  * restricted artifacts by default (ADR 0008). 0.3 added the measure type, the
338
357
  * `evidenceSources` registry, and `measure` and `tips` on every artifact (ADR 0012).
358
+ * 0.4 carries the evidence binding: which binding judged each verification, the
359
+ * subject it was judged at, each record's binding and subject hash, and a
360
+ * verification's `exercises` (ADR 0014).
339
361
  */
340
- export const EXPORT_CONTRACT = "intentset/export/0.3";
362
+ export const EXPORT_CONTRACT = "intentset/export/0.4";
341
363
 
342
364
  /** The contracts this implementation reads. A consumer rejects every other value (spec/export.md §5). */
343
365
  export const SUPPORTED_EXPORT_CONTRACTS: readonly string[] = [EXPORT_CONTRACT];
@@ -367,7 +389,8 @@ export interface ExportArtifact {
367
389
  withheldLinks: number;
368
390
  availability: Availability | null;
369
391
  slice: SliceMeta | null;
370
- verification: VerificationMeta | null;
392
+ /** `exercises` is the list as authored, less withheld IDs (counted in `withheldLinks`); empty when the record names none. */
393
+ verification: (Omit<VerificationMeta, "exercises"> & { exercises: string[] }) | null;
371
394
  /** Null unless the artifact is a measure; `direction` is null when the author set none. */
372
395
  measure: (Omit<MeasureMeta, "direction"> & { direction: MeasureDirection | null }) | null;
373
396
  /** Null unless the artifact is knowledge with tips (Core §9). */
@@ -377,7 +400,7 @@ export interface ExportArtifact {
377
400
  body?: string;
378
401
  }
379
402
 
380
- /** A run record as the evidence report carries it: spec/evidence.schema.json's record, unchanged. */
403
+ /** A run record as the evidence report carries it: spec/evidence.schema.json's record, unchanged, `binding` and `subjectHash` included. */
381
404
  export interface ExportRunRecord {
382
405
  evidenceId: string;
383
406
  verificationId: string;
@@ -391,10 +414,18 @@ export interface ExportRunRecord {
391
414
  finishedAt: string;
392
415
  result: "pass" | "fail" | "skip" | "error";
393
416
  uri: string;
417
+ /** What the record is bound to (Core §8); absent means commit. */
418
+ binding?: EvidenceBinding;
419
+ /** The hash of the subject the run checked; present when `binding` is subject. */
420
+ subjectHash?: string;
394
421
  rationale?: string;
395
422
  extensions?: Record<string, unknown>;
396
423
  }
397
424
 
425
+ /** Why the binding of a report is the one in force: the repository asked, the default (subject, since slices claim files), or the fallback to commit for want of slice claims (EVID004). */
426
+ export const EVIDENCE_BINDING_REASONS = ["configured", "default", "no-slices"] as const;
427
+ export type EvidenceBindingReason = (typeof EVIDENCE_BINDING_REASONS)[number];
428
+
398
429
  /** Core §8 freshness, as the evidence check classifies it. Only current-pass is a pass. */
399
430
  export const EXPORT_EVIDENCE_STATUSES = [
400
431
  "current-pass",
@@ -416,6 +447,10 @@ export interface EvidenceReportSection {
416
447
  scope: { product: string; release: string } | null;
417
448
  /** Run records read, before classification. */
418
449
  records: number;
450
+ /** The binding the repository judged by (Core §8): every verification below was judged by it. */
451
+ binding: EvidenceBinding;
452
+ /** Why: `no-slices` is the fallback to commit binding (EVID004), the only way a repository that asked for nothing is judged by commit. */
453
+ bindingReason: EvidenceBindingReason;
419
454
  /** One per verification artifact, sorted by ID. */
420
455
  verifications: ExportVerificationEvidence[];
421
456
  /** One per behavior, rule and scenario that is not retired, sorted by ID. */
@@ -426,7 +461,11 @@ export interface ExportVerificationEvidence {
426
461
  id: string;
427
462
  method: "automated" | "manual";
428
463
  status: ExportEvidenceStatus;
429
- /** Why the status is qualified, or what was set aside; null when nothing was. */
464
+ /** The binding that judged it. */
465
+ binding: EvidenceBinding;
466
+ /** Under subject binding, the SHA-256 of the verification's subject at this snapshot, which a `subject` record's `subjectHash` must equal to be current; null under commit binding, and when none could be computed. */
467
+ subject: string | null;
468
+ /** Why the status is qualified, or what was set aside, and for a stale subject record which part of the subject moved; null when nothing was. */
430
469
  note: string | null;
431
470
  /** The record that decided the status: the latest at the snapshot, else the latest in scope; null when none. */
432
471
  latest: ExportRunRecord | null;
package/src/validate.ts CHANGED
@@ -438,6 +438,17 @@ function checkLifecycle(context: Context): void {
438
438
  });
439
439
  }
440
440
  });
441
+ meta.verification?.exercises?.forEach((target, i) => {
442
+ if (graph.artifacts.get(target)?.meta.type !== "slice" && !resourceIds.has(target)) {
443
+ reportOn(context, artifact, {
444
+ code: "CORE003",
445
+ field: `/intentset/verification/exercises/${i}`,
446
+ message: `exercises names ${target}, which is neither a slice in the graph nor a resource in the registries.`,
447
+ remediation:
448
+ "Name the slice or registry resource the check drives, so a change to its files makes the evidence stale (Core §8), or remove the entry.",
449
+ });
450
+ }
451
+ });
441
452
  if (meta.status === "retired" && (meta.links.replacedBy ?? []).length === 0) {
442
453
  reportOn(context, artifact, {
443
454
  code: "CORE005",