@intentset/core 0.6.1 → 0.7.1

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,44 @@ 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
+ /**
303
+ * Which resource files a subject hashes (Core §8, ADR 0013 as amended 2026-10-15): `exercised` (the default) counts a
304
+ * resource's files where the verification names the resource in `exercises` or an implementing slice owns it; `used`
305
+ * counts the files of every resource an included slice lists in `usesResources`.
306
+ */
307
+ export const EVIDENCE_RESOURCES = ["exercised", "used"] as const;
308
+ export type EvidenceResources = (typeof EVIDENCE_RESOURCES)[number];
309
+
310
+ /**
311
+ * What a slice reached only through `dependsOn` contributes to a subject (Core §8, ADR 0013 as amended 2026-10-15, second):
312
+ * `entrypoint` its binding fields, its entrypoint files and its contract records, not its other claimed files; `files`
313
+ * every file its claims resolve to, as release 0.7.0 did. Slices that implement a verified behavior and slices a
314
+ * verification exercises always contribute every file.
315
+ */
316
+ export const EVIDENCE_DEPENDENCIES = ["entrypoint", "files"] as const;
317
+ export type EvidenceDependencies = (typeof EVIDENCE_DEPENDENCIES)[number];
318
+
319
+ /** The rule a repository gets when its configuration says nothing (the one place the default is written). */
320
+ export const DEFAULT_EVIDENCE_DEPENDENCIES: EvidenceDependencies = "files";
321
+
322
+ /** `evidence:` in `.intentset/config.yaml`; every member optional. */
323
+ export interface EvidenceConfig {
324
+ /** The binding a repository asks for; null leaves the default (subject where slices exist, else commit). */
325
+ binding: EvidenceBinding | null;
326
+ /** Files whose change affects every check and that belong to no slice: paths or patterns, mixed into every subject. */
327
+ globalInputs: string[];
328
+ /** Which resource files a subject hashes; `exercised` unless the repository asks for `used`. */
329
+ resources: EvidenceResources;
330
+ /** What a dependency slice contributes to a subject; `DEFAULT_EVIDENCE_DEPENDENCIES` unless the repository says otherwise. */
331
+ dependencies: EvidenceDependencies;
289
332
  }
290
333
 
291
334
  /** Conformance levels, Core §11. Checks above the requested level do not run. */
@@ -336,8 +379,11 @@ export interface ImpactReport {
336
379
  * the first version a consumer could pin: 0.1 carried untyped report slots and
337
380
  * restricted artifacts by default (ADR 0008). 0.3 added the measure type, the
338
381
  * `evidenceSources` registry, and `measure` and `tips` on every artifact (ADR 0012).
382
+ * 0.4 carries the evidence binding: which binding judged each verification, the
383
+ * subject it was judged at, each record's binding and subject hash, and a
384
+ * verification's `exercises` (ADR 0014).
339
385
  */
340
- export const EXPORT_CONTRACT = "intentset/export/0.3";
386
+ export const EXPORT_CONTRACT = "intentset/export/0.4";
341
387
 
342
388
  /** The contracts this implementation reads. A consumer rejects every other value (spec/export.md §5). */
343
389
  export const SUPPORTED_EXPORT_CONTRACTS: readonly string[] = [EXPORT_CONTRACT];
@@ -367,7 +413,8 @@ export interface ExportArtifact {
367
413
  withheldLinks: number;
368
414
  availability: Availability | null;
369
415
  slice: SliceMeta | null;
370
- verification: VerificationMeta | null;
416
+ /** `exercises` is the list as authored, less withheld IDs (counted in `withheldLinks`); empty when the record names none. */
417
+ verification: (Omit<VerificationMeta, "exercises"> & { exercises: string[] }) | null;
371
418
  /** Null unless the artifact is a measure; `direction` is null when the author set none. */
372
419
  measure: (Omit<MeasureMeta, "direction"> & { direction: MeasureDirection | null }) | null;
373
420
  /** Null unless the artifact is knowledge with tips (Core §9). */
@@ -377,7 +424,7 @@ export interface ExportArtifact {
377
424
  body?: string;
378
425
  }
379
426
 
380
- /** A run record as the evidence report carries it: spec/evidence.schema.json's record, unchanged. */
427
+ /** A run record as the evidence report carries it: spec/evidence.schema.json's record, unchanged, `binding` and `subjectHash` included. */
381
428
  export interface ExportRunRecord {
382
429
  evidenceId: string;
383
430
  verificationId: string;
@@ -391,10 +438,18 @@ export interface ExportRunRecord {
391
438
  finishedAt: string;
392
439
  result: "pass" | "fail" | "skip" | "error";
393
440
  uri: string;
441
+ /** What the record is bound to (Core §8); absent means commit. */
442
+ binding?: EvidenceBinding;
443
+ /** The hash of the subject the run checked; present when `binding` is subject. */
444
+ subjectHash?: string;
394
445
  rationale?: string;
395
446
  extensions?: Record<string, unknown>;
396
447
  }
397
448
 
449
+ /** 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). */
450
+ export const EVIDENCE_BINDING_REASONS = ["configured", "default", "no-slices"] as const;
451
+ export type EvidenceBindingReason = (typeof EVIDENCE_BINDING_REASONS)[number];
452
+
398
453
  /** Core §8 freshness, as the evidence check classifies it. Only current-pass is a pass. */
399
454
  export const EXPORT_EVIDENCE_STATUSES = [
400
455
  "current-pass",
@@ -416,6 +471,10 @@ export interface EvidenceReportSection {
416
471
  scope: { product: string; release: string } | null;
417
472
  /** Run records read, before classification. */
418
473
  records: number;
474
+ /** The binding the repository judged by (Core §8): every verification below was judged by it. */
475
+ binding: EvidenceBinding;
476
+ /** Why: `no-slices` is the fallback to commit binding (EVID004), the only way a repository that asked for nothing is judged by commit. */
477
+ bindingReason: EvidenceBindingReason;
419
478
  /** One per verification artifact, sorted by ID. */
420
479
  verifications: ExportVerificationEvidence[];
421
480
  /** One per behavior, rule and scenario that is not retired, sorted by ID. */
@@ -426,7 +485,11 @@ export interface ExportVerificationEvidence {
426
485
  id: string;
427
486
  method: "automated" | "manual";
428
487
  status: ExportEvidenceStatus;
429
- /** Why the status is qualified, or what was set aside; null when nothing was. */
488
+ /** The binding that judged it. */
489
+ binding: EvidenceBinding;
490
+ /** 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. */
491
+ subject: string | null;
492
+ /** 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
493
  note: string | null;
431
494
  /** The record that decided the status: the latest at the snapshot, else the latest in scope; null when none. */
432
495
  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",