@intentset/verification 0.1.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.
Files changed (56) hide show
  1. package/dist/adapters/common.d.ts +51 -0
  2. package/dist/adapters/common.d.ts.map +1 -0
  3. package/dist/adapters/common.js +95 -0
  4. package/dist/adapters/common.js.map +1 -0
  5. package/dist/adapters/node-test.d.ts +3 -0
  6. package/dist/adapters/node-test.d.ts.map +1 -0
  7. package/dist/adapters/node-test.js +119 -0
  8. package/dist/adapters/node-test.js.map +1 -0
  9. package/dist/adapters/vitest.d.ts +3 -0
  10. package/dist/adapters/vitest.d.ts.map +1 -0
  11. package/dist/adapters/vitest.js +71 -0
  12. package/dist/adapters/vitest.js.map +1 -0
  13. package/dist/check.d.ts +33 -0
  14. package/dist/check.d.ts.map +1 -0
  15. package/dist/check.js +95 -0
  16. package/dist/check.js.map +1 -0
  17. package/dist/classify.d.ts +69 -0
  18. package/dist/classify.d.ts.map +1 -0
  19. package/dist/classify.js +130 -0
  20. package/dist/classify.js.map +1 -0
  21. package/dist/coverage.d.ts +88 -0
  22. package/dist/coverage.d.ts.map +1 -0
  23. package/dist/coverage.js +114 -0
  24. package/dist/coverage.js.map +1 -0
  25. package/dist/diagnostic.d.ts +21 -0
  26. package/dist/diagnostic.d.ts.map +1 -0
  27. package/dist/diagnostic.js +20 -0
  28. package/dist/diagnostic.js.map +1 -0
  29. package/dist/fixture.d.ts +43 -0
  30. package/dist/fixture.d.ts.map +1 -0
  31. package/dist/fixture.js +42 -0
  32. package/dist/fixture.js.map +1 -0
  33. package/dist/index.d.ts +16 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +16 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/placeholders.d.ts +39 -0
  38. package/dist/placeholders.d.ts.map +1 -0
  39. package/dist/placeholders.js +47 -0
  40. package/dist/placeholders.js.map +1 -0
  41. package/dist/records.d.ts +77 -0
  42. package/dist/records.d.ts.map +1 -0
  43. package/dist/records.js +354 -0
  44. package/dist/records.js.map +1 -0
  45. package/package.json +33 -0
  46. package/src/adapters/common.ts +139 -0
  47. package/src/adapters/node-test.ts +139 -0
  48. package/src/adapters/vitest.ts +82 -0
  49. package/src/check.ts +137 -0
  50. package/src/classify.ts +222 -0
  51. package/src/coverage.ts +181 -0
  52. package/src/diagnostic.ts +39 -0
  53. package/src/fixture.ts +64 -0
  54. package/src/index.ts +15 -0
  55. package/src/placeholders.ts +51 -0
  56. package/src/records.ts +482 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placeholders.js","sourceRoot":"","sources":["../src/placeholders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,OAAO,EAAiB,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,0CAA0C,CAAC;AAOzE;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAI,OAAqB,EAAE,QAA6B;IACtF,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QAC5B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,MAAM,CAAC;QAC1F,MAAM,IAAI,GAA4B,EAAE,GAAI,MAAkC,EAAE,CAAC;QACjF,IAAI,IAAI,CAAC,SAAS,KAAK,OAAO;YAAE,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;QACpE,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,IAAI,QAAQ,CAAC,MAAM,KAAK,IAAI;YAAE,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;QACvF,OAAO,IAAS,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+GAA+G;AAC/G,MAAM,UAAU,cAAc,CAAC,OAAkE;IAC/F,IAAI,OAAO,EAAE,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACjF,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;AAChE,CAAC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Run records (Core §8): evidence is a separate record from the verification
3
+ * definition, because runs change more often than product semantics. This
4
+ * module is the record's shape and its reader; spec/evidence.schema.json is
5
+ * the same shape for other tools, and a test holds the two in agreement.
6
+ *
7
+ * The reader accepts the literal `@current` for `commit` and `graphHash`.
8
+ * That is the fixture placeholder (see placeholders.ts): it is bound to the
9
+ * snapshot before classification, and a record still carrying it classifies
10
+ * as unresolved, never as current.
11
+ */
12
+ import { type Diagnostic } from "@intentset/core";
13
+ export declare const RUN_RESULTS: readonly ["pass", "fail", "skip", "error"];
14
+ export type RunResult = (typeof RUN_RESULTS)[number];
15
+ /** The literal a fixture writes for "the snapshot's commit" or "the snapshot's graph hash". */
16
+ export declare const CURRENT = "@current";
17
+ export interface RunScope {
18
+ /** The product's artifact ID. */
19
+ product: string;
20
+ /** The exact release label; v0.1 matches release labels exactly (Core §7). */
21
+ release: string;
22
+ }
23
+ export interface RunTool {
24
+ name: string;
25
+ version: string;
26
+ }
27
+ /** One run of one verification, Core §8. Members in this order on output. */
28
+ export interface RunRecord {
29
+ evidenceId: string;
30
+ verificationId: string;
31
+ commit: string;
32
+ /** Lowercase hex SHA-256 over the canonical graph (ADR 0005), or the `@current` fixture placeholder. */
33
+ graphHash: string;
34
+ environment: string;
35
+ scope: RunScope;
36
+ /** The tool and version that ran an automated check; null for a review performed by a person alone. */
37
+ tool: RunTool | null;
38
+ /** The reviewer's identity; set exactly when the record is a manual review, which then needs a rationale. */
39
+ reviewer: string | null;
40
+ /** UTC ISO 8601 with a `Z` suffix. */
41
+ startedAt: string;
42
+ finishedAt: string;
43
+ result: RunResult;
44
+ uri: string;
45
+ rationale?: string;
46
+ /** Namespaced extensions (`org.example/key`), preserved and never interpreted. */
47
+ extensions?: Record<string, unknown>;
48
+ }
49
+ /** True for a UTC timestamp `YYYY-MM-DDTHH:MM:SS[.fraction]Z` that names a real instant. */
50
+ export declare function isUtcTimestamp(value: unknown): value is string;
51
+ /**
52
+ * A key that orders UTC timestamps exactly, fraction included: the date and
53
+ * time, then the fraction padded to nanoseconds. Plain string comparison of
54
+ * the timestamps would put `10:00:00.5Z` before `10:00:00Z`.
55
+ */
56
+ export declare function timeKey(timestamp: string): string;
57
+ /**
58
+ * Record order everywhere in this package: finishedAt, then evidenceId. The
59
+ * last is the latest. Two records agreeing on both (a repeated evidence ID the
60
+ * reader would have refused) fall back to their canonical JSON, so the order
61
+ * never depends on input order.
62
+ */
63
+ export declare function compareRecords(a: RunRecord, b: RunRecord): number;
64
+ export interface ReadRunRecords {
65
+ /** The well-formed records, in input order; a malformed record is dropped and reported. */
66
+ records: RunRecord[];
67
+ /** EVID001 errors, sorted by core's order. */
68
+ diagnostics: Diagnostic[];
69
+ }
70
+ /**
71
+ * Read run records from parsed JSON (an array of objects). Every problem is
72
+ * one EVID001 error with a JSON pointer into the input as its field; a record
73
+ * with any problem is not returned, so it can never count as evidence. Of two
74
+ * records with one evidence ID the first is kept and the later ones reported.
75
+ */
76
+ export declare function readRunRecords(json: unknown, path: string): ReadRunRecords;
77
+ //# sourceMappingURL=records.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAiB,KAAK,UAAU,EAA+B,MAAM,iBAAiB,CAAC;AAG9F,eAAO,MAAM,WAAW,YAAI,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAU,CAAC;AACtE,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,+FAA+F;AAC/F,eAAO,MAAM,OAAO,aAAa,CAAC;AAElC,MAAM,WAAW,QAAQ;IACvB,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,wGAAwG;IACxG,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,QAAQ,CAAC;IAChB,uGAAuG;IACvG,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IACrB,6GAA6G;IAC7G,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAwBD,4FAA4F;AAC5F,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAQ9D;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,MAAM,CAMjE;AAED,MAAM,WAAW,cAAc;IAC7B,2FAA2F;IAC3F,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,8CAA8C;IAC9C,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,CAwC1E"}
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Run records (Core §8): evidence is a separate record from the verification
3
+ * definition, because runs change more often than product semantics. This
4
+ * module is the record's shape and its reader; spec/evidence.schema.json is
5
+ * the same shape for other tools, and a test holds the two in agreement.
6
+ *
7
+ * The reader accepts the literal `@current` for `commit` and `graphHash`.
8
+ * That is the fixture placeholder (see placeholders.ts): it is bound to the
9
+ * snapshot before classification, and a record still carrying it classifies
10
+ * as unresolved, never as current.
11
+ */
12
+ import { canonicalJson, ID_PATTERN, sortDiagnostics } from "@intentset/core";
13
+ import { compareStrings, evidenceDiagnostic, pointerToken } from "./diagnostic.js";
14
+ export const RUN_RESULTS = ["pass", "fail", "skip", "error"];
15
+ /** The literal a fixture writes for "the snapshot's commit" or "the snapshot's graph hash". */
16
+ export const CURRENT = "@current";
17
+ const REQUIRED = [
18
+ "evidenceId",
19
+ "verificationId",
20
+ "commit",
21
+ "graphHash",
22
+ "environment",
23
+ "scope",
24
+ "tool",
25
+ "reviewer",
26
+ "startedAt",
27
+ "finishedAt",
28
+ "result",
29
+ "uri",
30
+ ];
31
+ const KNOWN = new Set([...REQUIRED, "rationale", "extensions"]);
32
+ const TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?Z$/;
33
+ const GRAPH_HASH = /^[0-9a-f]{64}$/;
34
+ const NO_SPACE = /^\S+$/;
35
+ const URI = /^[A-Za-z][A-Za-z0-9+.-]*:\S+$/;
36
+ const EXTENSION_KEY = /^[^/]+\/.+$/;
37
+ /** True for a UTC timestamp `YYYY-MM-DDTHH:MM:SS[.fraction]Z` that names a real instant. */
38
+ export function isUtcTimestamp(value) {
39
+ if (typeof value !== "string")
40
+ return false;
41
+ const match = TIMESTAMP.exec(value);
42
+ if (match === null)
43
+ return false;
44
+ const [year, month, day, hour, minute, second] = match.slice(1, 7).map(Number);
45
+ if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59)
46
+ return false;
47
+ const days = new Date(Date.UTC(year, month, 0)).getUTCDate();
48
+ return day >= 1 && day <= days;
49
+ }
50
+ /**
51
+ * A key that orders UTC timestamps exactly, fraction included: the date and
52
+ * time, then the fraction padded to nanoseconds. Plain string comparison of
53
+ * the timestamps would put `10:00:00.5Z` before `10:00:00Z`.
54
+ */
55
+ export function timeKey(timestamp) {
56
+ const match = TIMESTAMP.exec(timestamp);
57
+ if (match === null)
58
+ return timestamp;
59
+ return `${timestamp.slice(0, 19)}.${(match[7] ?? "").padEnd(9, "0")}`;
60
+ }
61
+ /**
62
+ * Record order everywhere in this package: finishedAt, then evidenceId. The
63
+ * last is the latest. Two records agreeing on both (a repeated evidence ID the
64
+ * reader would have refused) fall back to their canonical JSON, so the order
65
+ * never depends on input order.
66
+ */
67
+ export function compareRecords(a, b) {
68
+ return (compareStrings(timeKey(a.finishedAt), timeKey(b.finishedAt)) ||
69
+ compareStrings(a.evidenceId, b.evidenceId) ||
70
+ compareStrings(canonicalJson(a), canonicalJson(b)));
71
+ }
72
+ /**
73
+ * Read run records from parsed JSON (an array of objects). Every problem is
74
+ * one EVID001 error with a JSON pointer into the input as its field; a record
75
+ * with any problem is not returned, so it can never count as evidence. Of two
76
+ * records with one evidence ID the first is kept and the later ones reported.
77
+ */
78
+ export function readRunRecords(json, path) {
79
+ const diagnostics = [];
80
+ if (!Array.isArray(json)) {
81
+ diagnostics.push(evidenceDiagnostic({
82
+ code: "EVID001",
83
+ artifact: null,
84
+ path,
85
+ field: "",
86
+ message: `The evidence in ${path} is ${describe(json)}, not an array of run records.`,
87
+ remediation: "Write the run records as a JSON array of objects, one per run (Core §8).",
88
+ }));
89
+ return { records: [], diagnostics };
90
+ }
91
+ const records = [];
92
+ const seen = new Map();
93
+ json.forEach((entry, index) => {
94
+ const problems = [];
95
+ const record = readRecord(entry, index, problems);
96
+ if (record !== null) {
97
+ const first = seen.get(record.evidenceId);
98
+ if (first !== undefined) {
99
+ problems.push({
100
+ field: `/${index}/evidenceId`,
101
+ message: `Evidence ID ${record.evidenceId} at entry ${index} repeats entry ${first}.`,
102
+ remediation: "Give every run its own evidence ID; tools never merge two records into one.",
103
+ });
104
+ }
105
+ else {
106
+ seen.set(record.evidenceId, index);
107
+ }
108
+ }
109
+ const artifact = verificationOf(entry);
110
+ for (const problem of problems) {
111
+ diagnostics.push(evidenceDiagnostic({ code: "EVID001", artifact, path, ...problem }));
112
+ }
113
+ if (record !== null && problems.length === 0)
114
+ records.push(record);
115
+ });
116
+ return { records, diagnostics: sortDiagnostics(diagnostics) };
117
+ }
118
+ function verificationOf(entry) {
119
+ if (!isRecord(entry))
120
+ return null;
121
+ const id = entry.verificationId;
122
+ return typeof id === "string" && ID_PATTERN.test(id) ? id : null;
123
+ }
124
+ function readRecord(entry, index, problems) {
125
+ const at = (key) => `/${index}/${pointerToken(key)}`;
126
+ if (!isRecord(entry)) {
127
+ problems.push({
128
+ field: `/${index}`,
129
+ message: `Evidence entry ${index} is ${describe(entry)}, not a run record.`,
130
+ remediation: "Write each run as an object with the fields Core §8 lists.",
131
+ });
132
+ return null;
133
+ }
134
+ const problem = (key, message, remediation) => problems.push({ field: at(key), message, remediation });
135
+ for (const key of Object.keys(entry).sort(compareStrings)) {
136
+ if (!KNOWN.has(key)) {
137
+ problem(key, `Run record ${index} has the unknown key \`${key}\`.`, "Remove it, or move it under `extensions` with a namespaced key such as `org.example/build`.");
138
+ }
139
+ }
140
+ for (const key of REQUIRED) {
141
+ if (!Object.hasOwn(entry, key)) {
142
+ problem(key, `Run record ${index} has no \`${key}\`.`, `Add \`${key}\`; Core §8 requires it on every run record${key === "tool" || key === "reviewer" ? ", as null when it does not apply" : ""}.`);
143
+ }
144
+ }
145
+ const text = (key, pattern, what, remediation) => {
146
+ if (!Object.hasOwn(entry, key))
147
+ return undefined;
148
+ const value = entry[key];
149
+ if (typeof value === "string" && pattern.test(value))
150
+ return value;
151
+ problem(key, `\`${key}\` of run record ${index} must be ${what}, but it is ${describe(value)}.`, remediation);
152
+ return undefined;
153
+ };
154
+ const evidenceId = text("evidenceId", NO_SPACE, "a non-empty string without spaces", "Give the run a stable evidence ID, such as the one the adapter derives.");
155
+ const verificationId = text("verificationId", ID_PATTERN, "a verification artifact ID", "Name the verification this run executed, such as TEST-ASMT-SCHEDULE.");
156
+ const commit = text("commit", NO_SPACE, "a non-empty commit identifier", "Record the source commit the run executed against.");
157
+ const graphHash = readGraphHash(entry, problem, index);
158
+ const environment = text("environment", /\S/, "a non-empty string", "Name the environment the run executed in, such as ci or pilot.");
159
+ const uri = text("uri", URI, "a URI with a scheme", "Link the run's report, such as https://ci.example/runs/42.");
160
+ const scope = readScope(entry, at, problems, index);
161
+ const tool = readTool(entry, at, problems, index);
162
+ const reviewer = readReviewer(entry, problem, index);
163
+ const startedAt = readTime(entry, "startedAt", problem, index);
164
+ const finishedAt = readTime(entry, "finishedAt", problem, index);
165
+ let result;
166
+ if (Object.hasOwn(entry, "result")) {
167
+ if (RUN_RESULTS.includes(entry.result))
168
+ result = entry.result;
169
+ else
170
+ problem("result", `\`result\` of run record ${index} must be pass, fail, skip or error, but it is ${describe(entry.result)}.`, "Record one of the four results; anything else is not evidence (Core §8).");
171
+ }
172
+ let rationale;
173
+ if (Object.hasOwn(entry, "rationale")) {
174
+ if (typeof entry.rationale === "string" && /\S/.test(entry.rationale))
175
+ rationale = entry.rationale;
176
+ else
177
+ problem("rationale", `\`rationale\` of run record ${index} must be a non-empty string, but it is ${describe(entry.rationale)}.`, "Say what the reviewer examined and why it supports the result, or remove the key.");
178
+ }
179
+ let extensions;
180
+ if (Object.hasOwn(entry, "extensions")) {
181
+ const value = entry.extensions;
182
+ if (!isRecord(value)) {
183
+ problem("extensions", `\`extensions\` of run record ${index} must be a mapping.`, "Write namespaced keys under `extensions`.");
184
+ }
185
+ else {
186
+ for (const key of Object.keys(value).sort(compareStrings)) {
187
+ if (!EXTENSION_KEY.test(key)) {
188
+ problems.push({
189
+ field: `${at("extensions")}/${pointerToken(key)}`,
190
+ message: `Extension key \`${key}\` of run record ${index} is not namespaced.`,
191
+ remediation: "Prefix it with a namespace you control, such as `org.example/build`.",
192
+ });
193
+ }
194
+ }
195
+ extensions = value;
196
+ }
197
+ }
198
+ if (reviewer === null && Object.hasOwn(entry, "tool") && entry.tool === null) {
199
+ problem("tool", `Run record ${index} names neither a tool nor a reviewer, so nothing identifies who or what produced it.`, "Record the tool and version for an automated run, or the reviewer and rationale for a manual review (Core §8).");
200
+ }
201
+ if (typeof reviewer === "string" && rationale === undefined && !Object.hasOwn(entry, "rationale")) {
202
+ problem("rationale", `Manual run record ${index} by ${reviewer} gives no rationale.`, "Add the review rationale; a manual record must identify its reviewer and why the result holds (Core §8).");
203
+ }
204
+ if (startedAt !== undefined &&
205
+ finishedAt !== undefined &&
206
+ compareStrings(timeKey(finishedAt), timeKey(startedAt)) < 0) {
207
+ problem("finishedAt", `Run record ${index} finished at ${finishedAt}, before it started at ${startedAt}.`, "Record the run's real start and end in UTC; the end cannot precede the start.");
208
+ }
209
+ if (problems.length > 0)
210
+ return null;
211
+ const record = {
212
+ evidenceId: evidenceId,
213
+ verificationId: verificationId,
214
+ commit: commit,
215
+ graphHash: graphHash,
216
+ environment: environment,
217
+ scope: scope,
218
+ tool: tool,
219
+ reviewer: reviewer,
220
+ startedAt: startedAt,
221
+ finishedAt: finishedAt,
222
+ result: result,
223
+ uri: uri,
224
+ };
225
+ if (rationale !== undefined)
226
+ record.rationale = rationale;
227
+ if (extensions !== undefined)
228
+ record.extensions = extensions;
229
+ return record;
230
+ }
231
+ function readGraphHash(entry, problem, index) {
232
+ if (!Object.hasOwn(entry, "graphHash"))
233
+ return undefined;
234
+ const value = entry.graphHash;
235
+ if (typeof value === "string" && (GRAPH_HASH.test(value) || value === CURRENT))
236
+ return value;
237
+ problem("graphHash", `\`graphHash\` of run record ${index} must be 64 lowercase hex characters, but it is ${describe(value)}.`, "Record the graph hash of the snapshot the run assessed, as `intentset export` reports it (ADR 0005).");
238
+ return undefined;
239
+ }
240
+ function readTime(entry, key, problem, index) {
241
+ if (!Object.hasOwn(entry, key))
242
+ return undefined;
243
+ const value = entry[key];
244
+ if (isUtcTimestamp(value))
245
+ return value;
246
+ problem(key, `\`${key}\` of run record ${index} must be a UTC timestamp such as 2026-10-02T09:30:00Z, but it is ${describe(value)}.`, "Write the instant in UTC with a Z suffix; local offsets are not accepted.");
247
+ return undefined;
248
+ }
249
+ function readReviewer(entry, problem, index) {
250
+ if (!Object.hasOwn(entry, "reviewer"))
251
+ return undefined;
252
+ const value = entry.reviewer;
253
+ if (value === null)
254
+ return null;
255
+ if (typeof value === "string" && /\S/.test(value))
256
+ return value;
257
+ problem("reviewer", `\`reviewer\` of run record ${index} must be a reviewer's identity or null, but it is ${describe(value)}.`, "Name the person who performed the review, or write null for an automated run.");
258
+ return undefined;
259
+ }
260
+ function readScope(entry, at, problems, index) {
261
+ if (!Object.hasOwn(entry, "scope"))
262
+ return undefined;
263
+ const value = entry.scope;
264
+ if (!isRecord(value)) {
265
+ problems.push({
266
+ field: at("scope"),
267
+ message: `\`scope\` of run record ${index} must be a mapping with product and release, but it is ${describe(value)}.`,
268
+ remediation: "Record the exact product and release the run assessed (Core §8).",
269
+ });
270
+ return undefined;
271
+ }
272
+ let ok = true;
273
+ for (const key of Object.keys(value).sort(compareStrings)) {
274
+ if (key !== "product" && key !== "release") {
275
+ problems.push({
276
+ field: `${at("scope")}/${pointerToken(key)}`,
277
+ message: `\`scope\` of run record ${index} has the unknown key \`${key}\`.`,
278
+ remediation: "A scope names a product and a release and nothing else.",
279
+ });
280
+ ok = false;
281
+ }
282
+ }
283
+ if (typeof value.product !== "string" || !ID_PATTERN.test(value.product)) {
284
+ problems.push({
285
+ field: `${at("scope")}/product`,
286
+ message: `\`scope.product\` of run record ${index} must be a product ID, but it is ${describe(value.product)}.`,
287
+ remediation: "Name the product artifact, such as PRD-LANTERN.",
288
+ });
289
+ ok = false;
290
+ }
291
+ if (typeof value.release !== "string" || !/\S/.test(value.release)) {
292
+ problems.push({
293
+ field: `${at("scope")}/release`,
294
+ message: `\`scope.release\` of run record ${index} must be a release label, but it is ${describe(value.release)}.`,
295
+ remediation: "Give the exact release label the run assessed, such as pilot-1.",
296
+ });
297
+ ok = false;
298
+ }
299
+ return ok ? { product: value.product, release: value.release } : undefined;
300
+ }
301
+ function readTool(entry, at, problems, index) {
302
+ if (!Object.hasOwn(entry, "tool"))
303
+ return undefined;
304
+ const value = entry.tool;
305
+ if (value === null)
306
+ return null;
307
+ if (!isRecord(value)) {
308
+ problems.push({
309
+ field: at("tool"),
310
+ message: `\`tool\` of run record ${index} must be a mapping with name and version, or null, but it is ${describe(value)}.`,
311
+ remediation: "Record the tool that ran the check and its version, such as node:test and v24.15.0.",
312
+ });
313
+ return undefined;
314
+ }
315
+ let ok = true;
316
+ for (const key of Object.keys(value).sort(compareStrings)) {
317
+ if (key !== "name" && key !== "version") {
318
+ problems.push({
319
+ field: `${at("tool")}/${pointerToken(key)}`,
320
+ message: `\`tool\` of run record ${index} has the unknown key \`${key}\`.`,
321
+ remediation: "A tool is a name and a version and nothing else.",
322
+ });
323
+ ok = false;
324
+ }
325
+ }
326
+ for (const key of ["name", "version"]) {
327
+ if (typeof value[key] !== "string" || !/\S/.test(value[key])) {
328
+ problems.push({
329
+ field: `${at("tool")}/${key}`,
330
+ message: `\`tool.${key}\` of run record ${index} must be a non-empty string, but it is ${describe(value[key])}.`,
331
+ remediation: `Record the tool's ${key}.`,
332
+ });
333
+ ok = false;
334
+ }
335
+ }
336
+ return ok ? { name: value.name, version: value.version } : undefined;
337
+ }
338
+ function isRecord(value) {
339
+ return typeof value === "object" && value !== null && !Array.isArray(value);
340
+ }
341
+ function describe(value) {
342
+ if (value === undefined)
343
+ return "absent";
344
+ if (value === null)
345
+ return "null";
346
+ if (Array.isArray(value))
347
+ return "an array";
348
+ if (typeof value === "string")
349
+ return value === "" ? "an empty string" : JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}…` : value);
350
+ if (typeof value === "object")
351
+ return "a mapping";
352
+ return `the ${typeof value} ${String(value)}`;
353
+ }
354
+ //# sourceMappingURL=records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.js","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@intentset/verification",
3
+ "version": "0.1.0",
4
+ "description": "Run records, evidence freshness classification, and test-reporter adapters.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=22.18"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/intentset/intentset.git",
13
+ "directory": "packages/verification"
14
+ },
15
+ "homepage": "https://intentset.org/",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "src"
22
+ ],
23
+ "exports": {
24
+ ".": {
25
+ "intentset-source": "./src/index.ts",
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ }
29
+ },
30
+ "dependencies": {
31
+ "@intentset/core": "^0.1.0"
32
+ }
33
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * From a test runner's results to run records. A verification's `selector`
3
+ * is matched against each test's full title; no test API is invented for it
4
+ * (no `describe.behavior`). The parsers in node-test.ts and vitest.ts reduce
5
+ * their runner's output to `ParsedRun`, and `toRunRecords` does the rest.
6
+ */
7
+ import { sha256Hex } from "@intentset/core";
8
+ import { compareStrings } from "../diagnostic.ts";
9
+ import { readRunRecords, type RunRecord, type RunResult, type RunScope, type RunTool } from "../records.ts";
10
+
11
+ export interface ParsedTest {
12
+ /** Ancestor titles and the test's own, joined with " > " (Vitest's own `fullName` when it gives one). */
13
+ fullName: string;
14
+ result: RunResult;
15
+ durationMs: number | null;
16
+ }
17
+
18
+ export interface ParsedRun {
19
+ tests: ParsedTest[];
20
+ /** Failures the output reports but no test title carries, such as a file that failed to load. */
21
+ problems: string[];
22
+ }
23
+
24
+ export interface ToRunRecordsOptions {
25
+ /** Selector -> verification ID, from the graph's automated verifications. */
26
+ verifications: ReadonlyMap<string, string>;
27
+ commit: string;
28
+ graphHash: string;
29
+ environment: string;
30
+ scope: RunScope;
31
+ tool: RunTool;
32
+ /** UTC timestamps of the whole run. */
33
+ startedAt: string;
34
+ finishedAt: string;
35
+ /** Where the run's report lives; each record's uri is this plus `#` and the verification ID. */
36
+ uriBase: string;
37
+ }
38
+
39
+ export interface ConvertedRun {
40
+ /** One record per verification at least one test matched, by verification ID. */
41
+ records: RunRecord[];
42
+ /** Selectors no test matched, sorted: their verifications get no record from this run, and read as missing. */
43
+ unmatched: string[];
44
+ }
45
+
46
+ const SEVERITY: Record<RunResult, number> = { pass: 0, skip: 1, error: 2, fail: 3 };
47
+ const WORD = /[A-Za-z0-9_-]/;
48
+
49
+ /**
50
+ * A selector matches a title when it occurs in it as a whole token: the
51
+ * characters either side are not letters, digits, `_` or `-`. So
52
+ * `schedule-review-v1` matches `schedule [schedule-review-v1] accepts` and a
53
+ * title holding the bracketed tag, but `review-v1` does not match it.
54
+ */
55
+ export function selectorMatches(selector: string, title: string): boolean {
56
+ if (selector === "") return false;
57
+ const startsWord = WORD.test(selector[0]);
58
+ const endsWord = WORD.test(selector[selector.length - 1]);
59
+ for (let at = title.indexOf(selector); at !== -1; at = title.indexOf(selector, at + 1)) {
60
+ const before = at === 0 ? "" : title[at - 1];
61
+ const after = title[at + selector.length] ?? "";
62
+ if ((!startsWord || before === "" || !WORD.test(before)) && (!endsWord || after === "" || !WORD.test(after))) {
63
+ return true;
64
+ }
65
+ }
66
+ return false;
67
+ }
68
+
69
+ /** `EV-` and the first 16 hex digits of SHA-256 over the verification ID, commit, graph hash and end time, newline-joined. */
70
+ export function deriveEvidenceId(
71
+ verificationId: string,
72
+ commit: string,
73
+ graphHash: string,
74
+ finishedAt: string,
75
+ ): string {
76
+ return `EV-${sha256Hex([verificationId, commit, graphHash, finishedAt].join("\n")).slice(0, 16)}`;
77
+ }
78
+
79
+ /** The worse of two results: fail, then error, then skip, then pass. */
80
+ export function worst(a: RunResult, b: RunResult): RunResult {
81
+ return SEVERITY[a] >= SEVERITY[b] ? a : b;
82
+ }
83
+
84
+ /**
85
+ * One run record per verification whose selector matched at least one test.
86
+ * Several matching tests make one record with the worst result and a
87
+ * rationale that counts them. Throws when the options would make a malformed
88
+ * record, since that is a caller's mistake rather than evidence.
89
+ */
90
+ export function toRunRecords(parsed: ParsedRun, options: ToRunRecordsOptions): ConvertedRun {
91
+ const matched = new Map<string, { selectors: Set<string>; tests: Set<number> }>();
92
+ const unmatched: string[] = [];
93
+ for (const selector of [...options.verifications.keys()].sort(compareStrings)) {
94
+ const verificationId = options.verifications.get(selector) as string;
95
+ const hits = parsed.tests.flatMap((test, index) => (selectorMatches(selector, test.fullName) ? [index] : []));
96
+ if (hits.length === 0) {
97
+ unmatched.push(selector);
98
+ continue;
99
+ }
100
+ const entry = matched.get(verificationId) ?? { selectors: new Set<string>(), tests: new Set<number>() };
101
+ entry.selectors.add(selector);
102
+ for (const hit of hits) entry.tests.add(hit);
103
+ matched.set(verificationId, entry);
104
+ }
105
+
106
+ const records: RunRecord[] = [];
107
+ for (const verificationId of [...matched.keys()].sort(compareStrings)) {
108
+ const { selectors, tests } = matched.get(verificationId) as { selectors: Set<string>; tests: Set<number> };
109
+ const results = [...tests].sort((a, b) => a - b).map((index) => parsed.tests[index].result);
110
+ const result = results.reduce(worst, "pass" as RunResult);
111
+ const counts = (["pass", "fail", "error", "skip"] as const)
112
+ .map((r) => [r, results.filter((x) => x === r).length] as const)
113
+ .filter(([, n]) => n > 0)
114
+ .map(([r, n]) => `${n} ${r}`)
115
+ .join(", ");
116
+ const names = [...selectors].sort(compareStrings).join(", ");
117
+ records.push({
118
+ evidenceId: deriveEvidenceId(verificationId, options.commit, options.graphHash, options.finishedAt),
119
+ verificationId,
120
+ commit: options.commit,
121
+ graphHash: options.graphHash,
122
+ environment: options.environment,
123
+ scope: { product: options.scope.product, release: options.scope.release },
124
+ tool: { name: options.tool.name, version: options.tool.version },
125
+ reviewer: null,
126
+ startedAt: options.startedAt,
127
+ finishedAt: options.finishedAt,
128
+ result,
129
+ uri: `${options.uriBase}#${encodeURIComponent(verificationId)}`,
130
+ rationale: `${results.length} test${results.length === 1 ? "" : "s"} matched ${names}: ${counts}.`,
131
+ });
132
+ }
133
+
134
+ const check = readRunRecords(records, "adapter output");
135
+ if (check.diagnostics.length > 0) {
136
+ throw new Error(`toRunRecords was given options that make malformed records: ${check.diagnostics[0].message}`);
137
+ }
138
+ return { records, unmatched };
139
+ }