@actuarial-ts/compliance 0.6.0 → 0.6.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/README.md +15 -9
- package/dist/bundle.d.ts +1 -1
- package/dist/bundle.d.ts.map +1 -1
- package/dist/bundle.js +163 -21
- package/dist/bundle.js.map +1 -1
- package/dist/diagnosticRun.d.ts +52 -29
- package/dist/diagnosticRun.d.ts.map +1 -1
- package/dist/diagnosticRun.js +691 -109
- package/dist/diagnosticRun.js.map +1 -1
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +47 -6
- package/dist/ledger.js.map +1 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +27 -3
- package/dist/metadata.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -5
- package/src/bundle.ts +272 -32
- package/src/diagnosticRun.ts +1396 -93
- package/src/ledger.ts +59 -8
- package/src/metadata.ts +48 -7
- package/src/version.ts +1 -1
package/dist/diagnosticRun.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { CORE_PACKAGE_VERSION, canonicalJson, fnv1a64, getMetricDiagnosticsResultIdentity, getPreparedDiagnosticDataIdentity, runMetricDiagnostics, verifyPreparedDiagnosticDataIntegrity, } from "@actuarial-ts/core";
|
|
2
|
-
import { DATA_PACKAGE_VERSION, assertCompletedValidatedMetricDiagnosticsRun, reviewPreparedDiagnosticData, } from "@actuarial-ts/data";
|
|
1
|
+
import { CORE_PACKAGE_VERSION, DiagnosticValidationError, canonicalJson, compileDiagnosticDefinition, fnv1a64, getMetricDiagnosticsResultIdentity, getPreparedDiagnosticDataIdentity, runMetricDiagnostics, verifyPreparedDiagnosticDataIntegrity, } from "@actuarial-ts/core";
|
|
2
|
+
import { DATA_PACKAGE_VERSION, assertCompletedValidatedMetricDiagnosticsRun, reviewPreparedDiagnosticData, runValidatedMetricDiagnostics, validateDiagnosticRunInput, } from "@actuarial-ts/data";
|
|
3
3
|
import { ComplianceError } from "./errors.js";
|
|
4
4
|
import { COMPLIANCE_PACKAGE_VERSION } from "./version.js";
|
|
5
5
|
const verified = new WeakMap();
|
|
6
6
|
const token = (value, path) => {
|
|
7
|
-
if (value.length === 0 ||
|
|
7
|
+
if (value.length === 0 ||
|
|
8
|
+
/^[\t-\r ]|[\t-\r ]$/.test(value) ||
|
|
9
|
+
value.includes("\0"))
|
|
8
10
|
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `${path} must be a nonempty token`, path);
|
|
9
11
|
for (let index = 0; index < value.length; index++) {
|
|
10
12
|
const unit = value.charCodeAt(index);
|
|
@@ -18,53 +20,168 @@ const token = (value, path) => {
|
|
|
18
20
|
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `${path} contains malformed Unicode`, path);
|
|
19
21
|
}
|
|
20
22
|
};
|
|
21
|
-
function freeze(value, seen = new WeakSet()) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
function freeze(value, seen = new WeakSet()) {
|
|
24
|
+
if (value === null || typeof value !== "object" || seen.has(value))
|
|
25
|
+
return value;
|
|
26
|
+
seen.add(value);
|
|
27
|
+
for (const child of Object.values(value))
|
|
28
|
+
freeze(child, seen);
|
|
29
|
+
return Object.freeze(value);
|
|
30
|
+
}
|
|
31
|
+
function tag(kind, key, value) {
|
|
32
|
+
return `fnv1a64-jcs-v1:${fnv1a64(canonicalJson({ identityVersion: 1, kind, [key]: value }))}`;
|
|
33
|
+
}
|
|
34
|
+
function manifestIdentity(manifest) {
|
|
35
|
+
return {
|
|
36
|
+
...manifest,
|
|
37
|
+
executionPolicy: {
|
|
38
|
+
gate: manifest.executionPolicy.gate,
|
|
39
|
+
review: {
|
|
40
|
+
body: manifest.executionPolicy.review.identityBody,
|
|
41
|
+
reportFingerprint: manifest.executionPolicy.review.reportFingerprint,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function bindingTag(runFingerprint, resultFingerprint) {
|
|
47
|
+
return `fnv1a64-jcs-v1:${fnv1a64(canonicalJson({ identityVersion: 1, kind: "diagnostic-run-result", runFingerprint, resultFingerprint }))}`;
|
|
48
|
+
}
|
|
49
|
+
function plain(value) {
|
|
50
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
51
|
+
return false;
|
|
52
|
+
const prototype = Object.getPrototypeOf(value);
|
|
53
|
+
return prototype === Object.prototype || prototype === null;
|
|
54
|
+
}
|
|
55
|
+
function exactKeys(value, allowed, path) {
|
|
56
|
+
for (const key of Object.keys(value))
|
|
57
|
+
if (!allowed.includes(key))
|
|
58
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Unknown field ${key}`, `${path}.${key}`);
|
|
59
|
+
}
|
|
60
|
+
function snapshotArtifacts(evidence, scope, path) {
|
|
61
|
+
if (!Array.isArray(evidence))
|
|
62
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `${path} must be an array`, path);
|
|
26
63
|
const seen = new Set();
|
|
27
64
|
const result = [];
|
|
28
65
|
for (const [index, item] of evidence.entries()) {
|
|
29
|
-
|
|
30
|
-
|
|
66
|
+
const itemPath = `${path}[${index}]`;
|
|
67
|
+
if (!plain(item))
|
|
68
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Artifact evidence must be a plain object", itemPath);
|
|
69
|
+
if (typeof item.id !== "string")
|
|
70
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Artifact id must be a string", `${itemPath}.id`);
|
|
71
|
+
token(item.id, `${itemPath}.id`);
|
|
72
|
+
if (item.scope !== scope)
|
|
73
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Artifact scope must be ${scope}`, `${itemPath}.scope`);
|
|
31
74
|
if (seen.has(item.id))
|
|
32
|
-
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Duplicate artifact ID ${item.id}`,
|
|
75
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Duplicate artifact ID ${item.id}`, `${itemPath}.id`);
|
|
33
76
|
seen.add(item.id);
|
|
34
77
|
if (item.assurance === "sdk-computed") {
|
|
78
|
+
exactKeys(item, ["id", "scope", "assurance", "bytes"], itemPath);
|
|
35
79
|
if (!(item.bytes instanceof Uint8Array))
|
|
36
|
-
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "SDK-computed artifact evidence requires actual Uint8Array bytes",
|
|
80
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "SDK-computed artifact evidence requires actual Uint8Array bytes", `${itemPath}.bytes`);
|
|
37
81
|
const bytes = new Uint8Array(item.bytes.byteLength);
|
|
38
82
|
bytes.set(item.bytes);
|
|
39
|
-
result.push({ id: item.id, scope
|
|
83
|
+
result.push({ id: item.id, scope, assurance: item.assurance, bytes });
|
|
40
84
|
}
|
|
41
|
-
else {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
85
|
+
else if (item.assurance === "caller-declared") {
|
|
86
|
+
exactKeys(item, ["id", "scope", "assurance", "algorithm", "value"], itemPath);
|
|
87
|
+
if (typeof item.algorithm !== "string")
|
|
88
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Artifact algorithm must be a string", `${itemPath}.algorithm`);
|
|
89
|
+
if (typeof item.value !== "string")
|
|
90
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Artifact value must be a string", `${itemPath}.value`);
|
|
91
|
+
token(item.algorithm, `${itemPath}.algorithm`);
|
|
92
|
+
token(item.value, `${itemPath}.value`);
|
|
93
|
+
result.push({
|
|
94
|
+
id: item.id,
|
|
95
|
+
scope,
|
|
96
|
+
assurance: item.assurance,
|
|
97
|
+
algorithm: item.algorithm,
|
|
98
|
+
value: item.value,
|
|
99
|
+
});
|
|
47
100
|
}
|
|
101
|
+
else
|
|
102
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Unknown artifact assurance", `${itemPath}.assurance`);
|
|
48
103
|
}
|
|
49
|
-
return result.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
104
|
+
return result.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
50
105
|
}
|
|
51
|
-
async function digestArtifacts(snapshot) {
|
|
52
|
-
if (snapshot.some((item) => "bytes" in item) &&
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
106
|
+
async function digestArtifacts(snapshot, path) {
|
|
107
|
+
if (snapshot.some((item) => "bytes" in item) &&
|
|
108
|
+
globalThis.crypto?.subtle === undefined)
|
|
109
|
+
throw new ComplianceError("CRYPTO_UNAVAILABLE", "Web Crypto SHA-256 is unavailable", path);
|
|
110
|
+
return Promise.all(snapshot.map(async (item) => {
|
|
111
|
+
if (!("bytes" in item))
|
|
112
|
+
return item;
|
|
113
|
+
const hash = await globalThis.crypto.subtle.digest("SHA-256", item.bytes);
|
|
114
|
+
return {
|
|
115
|
+
id: item.id,
|
|
116
|
+
scope: item.scope,
|
|
117
|
+
assurance: item.assurance,
|
|
118
|
+
algorithm: "sha256",
|
|
119
|
+
value: [...new Uint8Array(hash)]
|
|
120
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
121
|
+
.join(""),
|
|
122
|
+
byteLength: item.bytes.byteLength,
|
|
123
|
+
};
|
|
124
|
+
}));
|
|
56
125
|
}
|
|
57
126
|
function snapshotLineage(lineage) {
|
|
58
|
-
|
|
59
|
-
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "
|
|
60
|
-
|
|
127
|
+
if (!Array.isArray(lineage))
|
|
128
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "$.preparationLineage must be an array", "$.preparationLineage");
|
|
129
|
+
return lineage
|
|
130
|
+
.map((raw, index) => {
|
|
131
|
+
const path = `$.preparationLineage[${index}]`;
|
|
132
|
+
if (!plain(raw))
|
|
133
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Lineage edge must be a plain object", path);
|
|
134
|
+
exactKeys(raw, ["outputArtifactId", "inputArtifactIds", "transformationArtifactIds"], path);
|
|
135
|
+
if (typeof raw.outputArtifactId !== "string")
|
|
136
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Lineage output must be a string", `${path}.outputArtifactId`);
|
|
137
|
+
token(raw.outputArtifactId, `${path}.outputArtifactId`);
|
|
138
|
+
const readIds = (value, key) => {
|
|
139
|
+
if (!Array.isArray(value))
|
|
140
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Lineage ${key} must be an array`, `${path}.${key}`);
|
|
141
|
+
const result = value.map((id, inputIndex) => {
|
|
142
|
+
if (typeof id !== "string")
|
|
143
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Lineage artifact id must be a string", `${path}.${key}[${inputIndex}]`);
|
|
144
|
+
token(id, `${path}.${key}[${inputIndex}]`);
|
|
145
|
+
return id;
|
|
146
|
+
});
|
|
147
|
+
if (new Set(result).size !== result.length)
|
|
148
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Lineage ${key} must be unique`, `${path}.${key}`);
|
|
149
|
+
return [...result].sort();
|
|
150
|
+
};
|
|
151
|
+
const inputs = readIds(raw.inputArtifactIds, "inputArtifactIds"), transformations = readIds(raw.transformationArtifactIds, "transformationArtifactIds");
|
|
152
|
+
if (inputs.length + transformations.length === 0)
|
|
153
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Lineage edge must have at least one upstream artifact", path);
|
|
154
|
+
return {
|
|
155
|
+
outputArtifactId: raw.outputArtifactId,
|
|
156
|
+
inputArtifactIds: inputs,
|
|
157
|
+
transformationArtifactIds: transformations,
|
|
158
|
+
};
|
|
159
|
+
})
|
|
160
|
+
.sort((a, b) => a.outputArtifactId < b.outputArtifactId
|
|
161
|
+
? -1
|
|
162
|
+
: a.outputArtifactId > b.outputArtifactId
|
|
163
|
+
? 1
|
|
164
|
+
: 0);
|
|
61
165
|
}
|
|
62
|
-
function validateArtifactGraph(run,
|
|
63
|
-
const byId = new Map(
|
|
166
|
+
function validateArtifactGraph(run, inputArtifacts, preparationArtifacts, lineage) {
|
|
167
|
+
const byId = new Map();
|
|
168
|
+
for (const [index, item] of [
|
|
169
|
+
...inputArtifacts,
|
|
170
|
+
...preparationArtifacts,
|
|
171
|
+
].entries()) {
|
|
172
|
+
if (byId.has(item.id))
|
|
173
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Duplicate artifact ID ${item.id}`, `$.${index < inputArtifacts.length ? "inputArtifacts" : "preparationArtifacts"}[${index < inputArtifacts.length ? index : index - inputArtifacts.length}].id`);
|
|
174
|
+
byId.set(item.id, item);
|
|
175
|
+
}
|
|
64
176
|
const referenced = new Set();
|
|
65
|
-
const requireArtifact = (id, scope, path) => {
|
|
66
|
-
|
|
67
|
-
|
|
177
|
+
const requireArtifact = (id, scope, path) => {
|
|
178
|
+
const artifact = byId.get(id);
|
|
179
|
+
if (!artifact)
|
|
180
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Artifact reference ${id} is unresolved`, path);
|
|
181
|
+
if (artifact.scope !== scope)
|
|
182
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Artifact ${id} must have ${scope} scope`, path);
|
|
183
|
+
referenced.add(id);
|
|
184
|
+
};
|
|
68
185
|
let unsourced = false;
|
|
69
186
|
for (const [index, item] of run.prepared.inputAudit.entries()) {
|
|
70
187
|
const source = item.record.source;
|
|
@@ -92,125 +209,590 @@ function validateArtifactGraph(run, artifacts, lineage) {
|
|
|
92
209
|
if (run.datasetArtifactId === null)
|
|
93
210
|
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Unsourced diagnostic input requires datasetArtifactId", "$.completedRun.datasetArtifactId");
|
|
94
211
|
const fallback = byId.get(run.datasetArtifactId);
|
|
95
|
-
if (!fallback ||
|
|
212
|
+
if (!fallback ||
|
|
213
|
+
fallback.scope !== "input" ||
|
|
214
|
+
fallback.assurance !== "sdk-computed")
|
|
96
215
|
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "datasetArtifactId must resolve to SDK-computed input evidence", "$.completedRun.datasetArtifactId");
|
|
97
216
|
referenced.add(run.datasetArtifactId);
|
|
98
217
|
}
|
|
99
|
-
else if (run.datasetArtifactId !== null)
|
|
218
|
+
else if (run.datasetArtifactId !== null) {
|
|
100
219
|
requireArtifact(run.datasetArtifactId, "input", "$.completedRun.datasetArtifactId");
|
|
101
|
-
|
|
220
|
+
if (byId.get(run.datasetArtifactId).assurance !== "sdk-computed")
|
|
221
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "datasetArtifactId must resolve to SDK-computed input evidence", "$.completedRun.datasetArtifactId");
|
|
222
|
+
}
|
|
223
|
+
for (const [basisIndex, basis,] of run.prepared.definition.definition.amountBases.entries())
|
|
102
224
|
for (const [componentIndex, component] of basis.components.entries())
|
|
103
|
-
if (component.limitation.kind !== "unlimited" &&
|
|
225
|
+
if (component.limitation.kind !== "unlimited" &&
|
|
226
|
+
component.limitation.kind !== "unknown" &&
|
|
227
|
+
component.limitation.derivation.kind === "external")
|
|
104
228
|
requireArtifact(component.limitation.derivation.transformationRef, "preparation", `$.definition.amountBases[${basisIndex}].components[${componentIndex}].limitation.derivation.transformationRef`);
|
|
105
|
-
for (const [ruleIndex, rule] of run.prepared.definition.definition.reviewRules.entries())
|
|
106
|
-
if (rule.kind === "layer-order" &&
|
|
229
|
+
for (const [ruleIndex, rule,] of run.prepared.definition.definition.reviewRules.entries())
|
|
230
|
+
if (rule.kind === "layer-order" &&
|
|
231
|
+
rule.comparability.kind === "caller-asserted")
|
|
107
232
|
requireArtifact(rule.comparability.rationaleArtifactId, "preparation", `$.definition.reviewRules[${ruleIndex}].comparability.rationaleArtifactId`);
|
|
108
233
|
if (run.gate.rationaleRef !== null)
|
|
109
234
|
requireArtifact(run.gate.rationaleRef, "preparation", "$.completedRun.gate.rationaleRef");
|
|
110
235
|
const edges = new Map();
|
|
111
236
|
for (const [index, edge] of lineage.entries()) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
237
|
+
const path = `$.preparationLineage[${index}]`;
|
|
238
|
+
if (edges.has(edge.outputArtifactId))
|
|
239
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "An artifact may have only one producing lineage edge", `${path}.outputArtifactId`);
|
|
240
|
+
const downstream = byId.get(edge.outputArtifactId);
|
|
115
241
|
if (!downstream)
|
|
116
|
-
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Lineage artifact ${edge.
|
|
242
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Lineage artifact ${edge.outputArtifactId} is unresolved`, `${path}.outputArtifactId`);
|
|
117
243
|
if (downstream.scope !== "input")
|
|
118
|
-
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Lineage
|
|
244
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Lineage output artifact must have input scope", `${path}.outputArtifactId`);
|
|
119
245
|
for (const [inputIndex, id] of edge.inputArtifactIds.entries()) {
|
|
120
|
-
if (id === edge.
|
|
121
|
-
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Lineage may not reference itself",
|
|
122
|
-
|
|
123
|
-
|
|
246
|
+
if (id === edge.outputArtifactId)
|
|
247
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Lineage may not reference itself", `${path}.inputArtifactIds[${inputIndex}]`);
|
|
248
|
+
const artifact = byId.get(id);
|
|
249
|
+
if (!artifact || artifact.scope !== "input")
|
|
250
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Lineage input ${id} must resolve to input evidence`, `${path}.inputArtifactIds[${inputIndex}]`);
|
|
251
|
+
}
|
|
252
|
+
for (const [transformIndex, id,] of edge.transformationArtifactIds.entries()) {
|
|
253
|
+
const artifact = byId.get(id);
|
|
254
|
+
if (!artifact || artifact.scope !== "preparation")
|
|
255
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Lineage transformation ${id} must resolve to preparation evidence`, `${path}.transformationArtifactIds[${transformIndex}]`);
|
|
124
256
|
}
|
|
125
|
-
edges.set(edge.
|
|
257
|
+
edges.set(edge.outputArtifactId, [
|
|
258
|
+
...edge.inputArtifactIds,
|
|
259
|
+
...edge.transformationArtifactIds,
|
|
260
|
+
]);
|
|
126
261
|
}
|
|
127
262
|
const visiting = new Set(), visited = new Set();
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
263
|
+
for (const root of [...referenced]) {
|
|
264
|
+
const stack = [{ id: root, exit: false }];
|
|
265
|
+
while (stack.length > 0) {
|
|
266
|
+
const { id, exit } = stack.pop();
|
|
267
|
+
if (exit) {
|
|
268
|
+
visiting.delete(id);
|
|
269
|
+
visited.add(id);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (visiting.has(id))
|
|
273
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Artifact lineage contains a cycle", "$.preparationLineage");
|
|
274
|
+
if (visited.has(id))
|
|
275
|
+
continue;
|
|
276
|
+
visiting.add(id);
|
|
277
|
+
stack.push({ id, exit: true });
|
|
278
|
+
for (const upstream of [...(edges.get(id) ?? [])].reverse()) {
|
|
279
|
+
referenced.add(upstream);
|
|
280
|
+
stack.push({ id: upstream, exit: false });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
for (const [index, artifact] of inputArtifacts.entries())
|
|
137
285
|
if (!referenced.has(artifact.id))
|
|
138
|
-
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Artifact ${artifact.id} is orphaned`, `$.
|
|
286
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Artifact ${artifact.id} is orphaned`, `$.inputArtifacts[${index}].id`);
|
|
287
|
+
for (const [index, artifact] of preparationArtifacts.entries())
|
|
288
|
+
if (!referenced.has(artifact.id))
|
|
289
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Artifact ${artifact.id} is orphaned`, `$.preparationArtifacts[${index}].id`);
|
|
139
290
|
}
|
|
140
291
|
function rerunAndVerify(run) {
|
|
141
292
|
assertCompletedValidatedMetricDiagnosticsRun(run);
|
|
142
293
|
verifyPreparedDiagnosticDataIntegrity(run.prepared);
|
|
143
|
-
const review = reviewPreparedDiagnosticData({
|
|
144
|
-
|
|
294
|
+
const review = reviewPreparedDiagnosticData({
|
|
295
|
+
prepared: run.prepared,
|
|
296
|
+
evidence: run.review.evidence,
|
|
297
|
+
});
|
|
298
|
+
if (review.reportFingerprint !== run.review.reportFingerprint ||
|
|
299
|
+
canonicalJson(review.identityBody) !==
|
|
300
|
+
canonicalJson(run.review.identityBody))
|
|
145
301
|
throw new ComplianceError("DIAGNOSTIC_MISMATCH", "Stored diagnostic review does not match a regenerated review", "$.review");
|
|
146
|
-
const rerun = runMetricDiagnostics({
|
|
147
|
-
|
|
302
|
+
const rerun = runMetricDiagnostics({
|
|
303
|
+
prepared: run.prepared,
|
|
304
|
+
groupMap: run.groupMap,
|
|
305
|
+
groupDimensions: run.groupDimensions,
|
|
306
|
+
});
|
|
307
|
+
if (canonicalJson(getMetricDiagnosticsResultIdentity(rerun)) !==
|
|
308
|
+
canonicalJson(getMetricDiagnosticsResultIdentity(run.result)))
|
|
148
309
|
throw new ComplianceError("DIAGNOSTIC_MISMATCH", "Stored diagnostic result does not match deterministic replay", "$.result");
|
|
149
|
-
const reviewBlocked = review.report.checks.some((check) => !run.gate.allowedReviewStatuses.includes(check.status))
|
|
150
|
-
|
|
151
|
-
|
|
310
|
+
const reviewBlocked = review.report.checks.some((check) => !run.gate.allowedReviewStatuses.includes(check.status)) ||
|
|
311
|
+
review.evaluations.some((evaluation) => {
|
|
312
|
+
const status = evaluation.expressionOverflows.length > 0
|
|
313
|
+
? "fail"
|
|
314
|
+
: evaluation.status === "triggered"
|
|
315
|
+
? evaluation.severity
|
|
316
|
+
: evaluation.status;
|
|
317
|
+
return !run.gate.allowedReviewStatuses.includes(status);
|
|
318
|
+
});
|
|
319
|
+
const metricBlocked = rerun.findings.some((finding) => finding.category !== "structural" &&
|
|
320
|
+
!run.gate.allowedMetricFindingSeverities.includes(finding.severity));
|
|
321
|
+
if (reviewBlocked ||
|
|
322
|
+
metricBlocked ||
|
|
323
|
+
run.gate.reviewGate !== "passed" ||
|
|
324
|
+
run.gate.metricGate !== "passed")
|
|
152
325
|
throw new ComplianceError("DIAGNOSTIC_MISMATCH", "Diagnostic execution gates do not recompute as passed", "$.gate");
|
|
153
326
|
return { review, result: getMetricDiagnosticsResultIdentity(run.result) };
|
|
154
327
|
}
|
|
328
|
+
function buildManifest(run, review, inputArtifacts, preparationArtifacts, lineage) {
|
|
329
|
+
const preparation = getPreparedDiagnosticDataIdentity(run.prepared);
|
|
330
|
+
return freeze({
|
|
331
|
+
definitionIntegrity: run.prepared.definition.definitionIntegrity,
|
|
332
|
+
preparationFingerprint: run.prepared.preparationFingerprint,
|
|
333
|
+
runPresetId: run.runPresetId,
|
|
334
|
+
datasetArtifactId: run.datasetArtifactId,
|
|
335
|
+
inputArtifacts: [...inputArtifacts].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0),
|
|
336
|
+
preparationArtifacts: [...preparationArtifacts].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0),
|
|
337
|
+
preparationLineage: lineage,
|
|
338
|
+
inputAudit: preparation.inputAudit,
|
|
339
|
+
filter: preparation.filter,
|
|
340
|
+
groupMap: { ...run.groupMap },
|
|
341
|
+
groupDimensions: { ...run.groupDimensions },
|
|
342
|
+
completePeriodCutoffs: preparation.completePeriodCutoffs,
|
|
343
|
+
expectedCellGridFingerprint: preparation.expectedCellsProvided
|
|
344
|
+
? tag("diagnostic-expected-cell-grid", "expectedCells", preparation.expectedCells)
|
|
345
|
+
: null,
|
|
346
|
+
executionPolicy: { review, gate: run.gate },
|
|
347
|
+
engine: {
|
|
348
|
+
packages: {
|
|
349
|
+
core: CORE_PACKAGE_VERSION,
|
|
350
|
+
data: DATA_PACKAGE_VERSION,
|
|
351
|
+
compliance: COMPLIANCE_PACKAGE_VERSION,
|
|
352
|
+
},
|
|
353
|
+
algorithmVersion: "diagnostics-1",
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
}
|
|
155
357
|
export async function createDiagnosticRunIdentity(input) {
|
|
358
|
+
if (!plain(input))
|
|
359
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Diagnostic evidence must be a plain object", "$");
|
|
360
|
+
exactKeys(input, [
|
|
361
|
+
"completedRun",
|
|
362
|
+
"inputArtifacts",
|
|
363
|
+
"preparationArtifacts",
|
|
364
|
+
"preparationLineage",
|
|
365
|
+
], "$");
|
|
156
366
|
const run = input.completedRun;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
367
|
+
let authenticated;
|
|
368
|
+
try {
|
|
369
|
+
authenticated = rerunAndVerify(run);
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
if (error instanceof ComplianceError)
|
|
373
|
+
throw error;
|
|
374
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "completedRun must be an authentic completed diagnostic run", "$.completedRun");
|
|
375
|
+
}
|
|
376
|
+
const inputSnapshot = snapshotArtifacts(input.inputArtifacts, "input", "$.inputArtifacts");
|
|
377
|
+
const preparationSnapshot = snapshotArtifacts(input.preparationArtifacts, "preparation", "$.preparationArtifacts");
|
|
378
|
+
const lineage = snapshotLineage(input.preparationLineage);
|
|
379
|
+
validateArtifactGraph(run, inputSnapshot, preparationSnapshot, lineage);
|
|
380
|
+
const [inputArtifacts, preparationArtifacts] = await Promise.all([
|
|
381
|
+
digestArtifacts(inputSnapshot, "$.inputArtifacts"),
|
|
382
|
+
digestArtifacts(preparationSnapshot, "$.preparationArtifacts"),
|
|
383
|
+
]);
|
|
384
|
+
const manifest = buildManifest(run, authenticated.review, inputArtifacts, preparationArtifacts, lineage);
|
|
385
|
+
const runFingerprint = tag("diagnostic-run", "manifest", manifestIdentity(manifest));
|
|
166
386
|
const resultFingerprint = tag("diagnostic-result", "result", authenticated.result);
|
|
167
|
-
const runResultFingerprint =
|
|
387
|
+
const runResultFingerprint = bindingTag(runFingerprint, resultFingerprint);
|
|
168
388
|
const definition = run.prepared.definition;
|
|
169
|
-
const provenance = freeze({
|
|
389
|
+
const provenance = freeze({
|
|
390
|
+
definition: {
|
|
391
|
+
definition: definition.definition,
|
|
392
|
+
identities: {
|
|
393
|
+
algorithm: "fnv1a64-jcs-v1",
|
|
394
|
+
formulaById: { ...definition.formulaFingerprints },
|
|
395
|
+
calculationByInstanceId: { ...definition.calculationFingerprints },
|
|
396
|
+
definition: definition.definitionIntegrity,
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
manifest,
|
|
400
|
+
review: authenticated.review,
|
|
401
|
+
result: run.result,
|
|
402
|
+
runFingerprint,
|
|
403
|
+
resultFingerprint,
|
|
404
|
+
runResultFingerprint,
|
|
405
|
+
});
|
|
170
406
|
verified.set(provenance, run);
|
|
171
407
|
return provenance;
|
|
172
408
|
}
|
|
173
|
-
export function assertVerifiedDiagnosticRunProvenance(value) {
|
|
174
|
-
|
|
409
|
+
export function assertVerifiedDiagnosticRunProvenance(value) {
|
|
410
|
+
if (value === null || typeof value !== "object" || !verified.has(value))
|
|
411
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Value is not authentic verified diagnostic provenance", "$");
|
|
412
|
+
}
|
|
413
|
+
function readArtifactDigests(value, scope, path) {
|
|
414
|
+
if (!Array.isArray(value))
|
|
415
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Artifact digests must be an array", path);
|
|
416
|
+
return value.map((item, index) => {
|
|
417
|
+
const itemPath = `${path}[${index}]`;
|
|
418
|
+
if (!plain(item))
|
|
419
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Artifact digest must be a plain object", itemPath);
|
|
420
|
+
for (const key of ["id", "algorithm", "value"]) {
|
|
421
|
+
if (typeof item[key] !== "string")
|
|
422
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `${key} must be a token`, `${itemPath}.${key}`);
|
|
423
|
+
token(item[key], `${itemPath}.${key}`);
|
|
424
|
+
}
|
|
425
|
+
if (item.scope !== scope)
|
|
426
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", `Artifact scope must be ${scope}`, `${itemPath}.scope`);
|
|
427
|
+
if (item.assurance === "sdk-computed") {
|
|
428
|
+
exactKeys(item, ["id", "scope", "assurance", "algorithm", "value", "byteLength"], itemPath);
|
|
429
|
+
if (item.algorithm !== "sha256")
|
|
430
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "SDK digests must use sha256", `${itemPath}.algorithm`);
|
|
431
|
+
if (!/^[0-9a-f]{64}$/.test(item.value))
|
|
432
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Invalid SHA-256 digest", `${itemPath}.value`);
|
|
433
|
+
if (!Number.isSafeInteger(item.byteLength) ||
|
|
434
|
+
item.byteLength < 0)
|
|
435
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Invalid byte length", `${itemPath}.byteLength`);
|
|
436
|
+
}
|
|
437
|
+
else if (item.assurance === "caller-declared") {
|
|
438
|
+
exactKeys(item, ["id", "scope", "assurance", "algorithm", "value"], itemPath);
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Unknown artifact assurance", `${itemPath}.assurance`);
|
|
442
|
+
}
|
|
443
|
+
return item;
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
function readRecordedEngine(value) {
|
|
447
|
+
if (!plain(value))
|
|
448
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Invalid diagnostic engine", "$.manifest.engine");
|
|
449
|
+
exactKeys(value, ["packages", "algorithmVersion"], "$.manifest.engine");
|
|
450
|
+
if (value.algorithmVersion !== "diagnostics-1")
|
|
451
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Unsupported diagnostic algorithm", "$.manifest.engine.algorithmVersion");
|
|
452
|
+
if (!plain(value.packages))
|
|
453
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Invalid diagnostic package versions", "$.manifest.engine.packages");
|
|
454
|
+
exactKeys(value.packages, ["core", "data", "compliance"], "$.manifest.engine.packages");
|
|
455
|
+
for (const name of ["core", "data", "compliance"]) {
|
|
456
|
+
if (typeof value.packages[name] !== "string")
|
|
457
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Package version must be a token", `$.manifest.engine.packages.${name}`);
|
|
458
|
+
token(value.packages[name], `$.manifest.engine.packages.${name}`);
|
|
459
|
+
}
|
|
460
|
+
return value;
|
|
461
|
+
}
|
|
462
|
+
function auditedNumber(value, path) {
|
|
463
|
+
if (!plain(value))
|
|
464
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Invalid audited number", path);
|
|
465
|
+
if (value.status === "observed" &&
|
|
466
|
+
typeof value.value === "number" &&
|
|
467
|
+
Number.isFinite(value.value)) {
|
|
468
|
+
exactKeys(value, ["status", "value"], path);
|
|
469
|
+
return value.value;
|
|
470
|
+
}
|
|
471
|
+
if (value.status === "missing" && value.value === null) {
|
|
472
|
+
exactKeys(value, ["status", "value"], path);
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
if (value.status === "non-finite" && value.value === null) {
|
|
476
|
+
exactKeys(value, ["status", "value", "nonFiniteKind"], path);
|
|
477
|
+
if (value.nonFiniteKind === "nan")
|
|
478
|
+
return NaN;
|
|
479
|
+
if (value.nonFiniteKind === "positive-infinity")
|
|
480
|
+
return Infinity;
|
|
481
|
+
if (value.nonFiniteKind === "negative-infinity")
|
|
482
|
+
return -Infinity;
|
|
483
|
+
}
|
|
484
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Invalid audited number", path);
|
|
485
|
+
}
|
|
486
|
+
/** Normalized optional nulls are omitted only when rebuilding authored input. */
|
|
487
|
+
function omitNulls(value) {
|
|
488
|
+
return plain(value)
|
|
489
|
+
? Object.fromEntries(Object.entries(value).filter(([, item]) => item !== null))
|
|
490
|
+
: value;
|
|
491
|
+
}
|
|
492
|
+
/** Human descriptions/details are regenerated, but are not identity-bearing. */
|
|
493
|
+
function reviewIdentityView(value) {
|
|
494
|
+
if (!plain(value) ||
|
|
495
|
+
!plain(value.report) ||
|
|
496
|
+
!Array.isArray(value.report.checks))
|
|
497
|
+
return value;
|
|
498
|
+
return {
|
|
499
|
+
...value,
|
|
500
|
+
report: {
|
|
501
|
+
...value.report,
|
|
502
|
+
checks: value.report.checks.map((check) => {
|
|
503
|
+
if (!plain(check))
|
|
504
|
+
return check;
|
|
505
|
+
const { description: _description, details: _details, ...identity } = check;
|
|
506
|
+
return identity;
|
|
507
|
+
}),
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function provenanceIdentityView(value) {
|
|
512
|
+
if (!plain(value))
|
|
513
|
+
return value;
|
|
514
|
+
const manifest = value.manifest;
|
|
515
|
+
const executionPolicy = plain(manifest) ? manifest.executionPolicy : null;
|
|
516
|
+
return {
|
|
517
|
+
...value,
|
|
518
|
+
review: reviewIdentityView(value.review),
|
|
519
|
+
...(plain(manifest) && plain(executionPolicy)
|
|
520
|
+
? {
|
|
521
|
+
manifest: {
|
|
522
|
+
...manifest,
|
|
523
|
+
executionPolicy: {
|
|
524
|
+
...executionPolicy,
|
|
525
|
+
review: reviewIdentityView(executionPolicy.review),
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
}
|
|
529
|
+
: {}),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function replaySerializedRun(compiled, manifest, review) {
|
|
533
|
+
if (!Array.isArray(manifest.inputAudit))
|
|
534
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Input audit must be an array", "$.manifest.inputAudit");
|
|
535
|
+
const losses = [], exposures = [], expectedCells = [];
|
|
536
|
+
for (const [index, item] of manifest.inputAudit.entries()) {
|
|
537
|
+
const path = `$.manifest.inputAudit[${index}]`;
|
|
538
|
+
if (!plain(item) || !plain(item.record))
|
|
539
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Invalid input audit entry", path);
|
|
540
|
+
exactKeys(item, ["kind", "record", "disposition"], path);
|
|
541
|
+
const record = { ...item.record };
|
|
542
|
+
if (record.source === null)
|
|
543
|
+
delete record.source;
|
|
544
|
+
else if (record.source !== undefined)
|
|
545
|
+
record.source = omitNulls(record.source);
|
|
546
|
+
if (item.kind === "loss") {
|
|
547
|
+
if (!plain(record.measures))
|
|
548
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Invalid audited measures", `${path}.record.measures`);
|
|
549
|
+
record.measures = Object.fromEntries(Object.entries(record.measures).map(([id, value]) => [
|
|
550
|
+
id,
|
|
551
|
+
auditedNumber(value, `${path}.record.measures.${id}`),
|
|
552
|
+
]));
|
|
553
|
+
if (record.claimId === null)
|
|
554
|
+
delete record.claimId;
|
|
555
|
+
losses.push(record);
|
|
556
|
+
}
|
|
557
|
+
else if (item.kind === "exposure") {
|
|
558
|
+
record.value = auditedNumber(record.value, `${path}.record.value`);
|
|
559
|
+
if (record.valuation === null)
|
|
560
|
+
delete record.valuation;
|
|
561
|
+
exposures.push(record);
|
|
562
|
+
}
|
|
563
|
+
else if (item.kind === "expected-cell")
|
|
564
|
+
expectedCells.push(record);
|
|
565
|
+
else
|
|
566
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Unknown input audit kind", `${path}.kind`);
|
|
567
|
+
}
|
|
568
|
+
if (!plain(manifest.executionPolicy) || !plain(manifest.executionPolicy.gate))
|
|
569
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", "Missing execution gate", "$.manifest.executionPolicy.gate");
|
|
570
|
+
const gate = manifest.executionPolicy.gate;
|
|
571
|
+
exactKeys(gate, [
|
|
572
|
+
"allowedReviewStatuses",
|
|
573
|
+
"allowedMetricFindingSeverities",
|
|
574
|
+
"rationaleRef",
|
|
575
|
+
"reviewGate",
|
|
576
|
+
"metricGate",
|
|
577
|
+
], "$.manifest.executionPolicy.gate");
|
|
578
|
+
let outcome;
|
|
579
|
+
try {
|
|
580
|
+
outcome = runValidatedMetricDiagnostics(validateDiagnosticRunInput({
|
|
581
|
+
definition: compiled.definition,
|
|
582
|
+
losses,
|
|
583
|
+
exposures,
|
|
584
|
+
...(manifest.filter === null
|
|
585
|
+
? {}
|
|
586
|
+
: { filter: omitNulls(manifest.filter) }),
|
|
587
|
+
completePeriodCutoffs: manifest.completePeriodCutoffs,
|
|
588
|
+
...(manifest.expectedCellGridFingerprint === null
|
|
589
|
+
? {}
|
|
590
|
+
: { expectedCells }),
|
|
591
|
+
reviewEvidence: review.evidence,
|
|
592
|
+
...(manifest.runPresetId === null
|
|
593
|
+
? {}
|
|
594
|
+
: { runPresetId: manifest.runPresetId }),
|
|
595
|
+
...(manifest.datasetArtifactId === null
|
|
596
|
+
? {}
|
|
597
|
+
: { datasetArtifactId: manifest.datasetArtifactId }),
|
|
598
|
+
groupMap: manifest.groupMap,
|
|
599
|
+
groupDimensions: manifest.groupDimensions,
|
|
600
|
+
policy: {
|
|
601
|
+
allowedReviewStatuses: gate.allowedReviewStatuses,
|
|
602
|
+
allowedMetricFindingSeverities: gate.allowedMetricFindingSeverities,
|
|
603
|
+
...(gate.rationaleRef === null
|
|
604
|
+
? {}
|
|
605
|
+
: { rationaleRef: gate.rationaleRef }),
|
|
606
|
+
},
|
|
607
|
+
}));
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
if (!(error instanceof DiagnosticValidationError))
|
|
611
|
+
throw error;
|
|
612
|
+
const issue = error.issues[0];
|
|
613
|
+
let issuePath = issue?.path ?? "$";
|
|
614
|
+
const recordPath = /^\$\.(losses|exposures|expectedCells)\[(\d+)\](.*)$/.exec(issuePath);
|
|
615
|
+
if (recordPath) {
|
|
616
|
+
const kind = recordPath[1] === "losses"
|
|
617
|
+
? "loss"
|
|
618
|
+
: recordPath[1] === "exposures"
|
|
619
|
+
? "exposure"
|
|
620
|
+
: "expected-cell";
|
|
621
|
+
const auditIndices = manifest.inputAudit.flatMap((item, index) => plain(item) && item.kind === kind ? [index] : []);
|
|
622
|
+
issuePath = `$.manifest.inputAudit[${auditIndices[Number(recordPath[2])]}].record${recordPath[3]}`;
|
|
623
|
+
}
|
|
624
|
+
else if (issuePath.startsWith("$.reviewEvidence"))
|
|
625
|
+
issuePath = issuePath.replace("$.reviewEvidence", "$.review.evidence");
|
|
626
|
+
else if (issuePath.startsWith("$.policy"))
|
|
627
|
+
issuePath = issuePath.replace("$.policy", "$.manifest.executionPolicy.gate");
|
|
628
|
+
else if (issuePath.startsWith("$.definition"))
|
|
629
|
+
issuePath = issuePath.replace("$.definition", "$.definition.definition");
|
|
630
|
+
else
|
|
631
|
+
issuePath = issuePath.replace(/^\$\./, "$.manifest.");
|
|
632
|
+
throw new ComplianceError("BAD_DIAGNOSTIC_RUN", issue?.message ?? "Invalid diagnostic replay input", issuePath);
|
|
633
|
+
}
|
|
634
|
+
if (outcome.status !== "completed")
|
|
635
|
+
throw new ComplianceError("DIAGNOSTIC_MISMATCH", "Stored execution does not pass its declared gates", "$.manifest.executionPolicy.gate");
|
|
636
|
+
return outcome;
|
|
637
|
+
}
|
|
638
|
+
/** Verify serialized evidence by replaying the owning core/data public workflows. */
|
|
639
|
+
export function serializedDiagnosticRunMismatch(value, path = "$.diagnosticRuns[0]") {
|
|
640
|
+
try {
|
|
641
|
+
if (!plain(value))
|
|
642
|
+
return path;
|
|
643
|
+
const fields = [
|
|
644
|
+
"definition",
|
|
645
|
+
"manifest",
|
|
646
|
+
"review",
|
|
647
|
+
"result",
|
|
648
|
+
"runFingerprint",
|
|
649
|
+
"resultFingerprint",
|
|
650
|
+
"runResultFingerprint",
|
|
651
|
+
];
|
|
652
|
+
exactKeys(value, fields, "$");
|
|
653
|
+
for (const key of fields)
|
|
654
|
+
if (!Object.hasOwn(value, key))
|
|
655
|
+
return `${path}.${key}`;
|
|
656
|
+
if (!plain(value.definition))
|
|
657
|
+
return `${path}.definition`;
|
|
658
|
+
exactKeys(value.definition, ["definition", "identities"], "$.definition");
|
|
659
|
+
let compiled;
|
|
660
|
+
try {
|
|
661
|
+
compiled = compileDiagnosticDefinition(value.definition.definition);
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
const issuePath = error instanceof DiagnosticValidationError
|
|
665
|
+
? error.issues[0]?.path
|
|
666
|
+
: undefined;
|
|
667
|
+
return `${path}.definition.definition${issuePath?.startsWith("$") ? issuePath.slice(1) : ""}`;
|
|
668
|
+
}
|
|
669
|
+
const identities = {
|
|
670
|
+
algorithm: "fnv1a64-jcs-v1",
|
|
671
|
+
formulaById: compiled.formulaFingerprints,
|
|
672
|
+
calculationByInstanceId: compiled.calculationFingerprints,
|
|
673
|
+
definition: compiled.definitionIntegrity,
|
|
674
|
+
};
|
|
675
|
+
let mismatch = firstDifference(value.definition.identities, identities, `${path}.definition.identities`);
|
|
676
|
+
if (mismatch !== null)
|
|
677
|
+
return mismatch;
|
|
678
|
+
if (!plain(value.manifest))
|
|
679
|
+
return `${path}.manifest`;
|
|
680
|
+
if (!plain(value.review))
|
|
681
|
+
return `${path}.review`;
|
|
682
|
+
const manifest = value.manifest;
|
|
683
|
+
const engine = readRecordedEngine(manifest.engine);
|
|
684
|
+
const inputArtifacts = readArtifactDigests(manifest.inputArtifacts, "input", "$.manifest.inputArtifacts");
|
|
685
|
+
const preparationArtifacts = readArtifactDigests(manifest.preparationArtifacts, "preparation", "$.manifest.preparationArtifacts");
|
|
686
|
+
const lineage = snapshotLineage(manifest.preparationLineage);
|
|
687
|
+
const run = replaySerializedRun(compiled, manifest, value.review);
|
|
688
|
+
if (manifest.preparationFingerprint !== run.prepared.preparationFingerprint)
|
|
689
|
+
return `${path}.manifest.preparationFingerprint`;
|
|
690
|
+
validateArtifactGraph(run, inputArtifacts, preparationArtifacts, lineage);
|
|
691
|
+
mismatch = firstDifference(reviewIdentityView(value.review), reviewIdentityView(run.review), `${path}.review`);
|
|
692
|
+
if (mismatch !== null)
|
|
693
|
+
return mismatch;
|
|
694
|
+
const expectedManifest = {
|
|
695
|
+
...buildManifest(run, run.review, inputArtifacts, preparationArtifacts, lineage),
|
|
696
|
+
// Historical package versions describe the original run; reproduction
|
|
697
|
+
// checks the supported algorithm and bundle-wide version agreement.
|
|
698
|
+
engine,
|
|
699
|
+
};
|
|
700
|
+
mismatch = firstDifference(provenanceIdentityView({ manifest })
|
|
701
|
+
.manifest, provenanceIdentityView({ manifest: expectedManifest }).manifest, `${path}.manifest`);
|
|
702
|
+
if (mismatch !== null)
|
|
703
|
+
return mismatch;
|
|
704
|
+
const result = getMetricDiagnosticsResultIdentity(run.result);
|
|
705
|
+
let candidateResult;
|
|
706
|
+
try {
|
|
707
|
+
candidateResult = getMetricDiagnosticsResultIdentity(value.result);
|
|
708
|
+
}
|
|
709
|
+
catch (error) {
|
|
710
|
+
const issuePath = error instanceof DiagnosticValidationError
|
|
711
|
+
? error.issues[0]?.path
|
|
712
|
+
: undefined;
|
|
713
|
+
return `${path}.result${issuePath?.startsWith("$") ? issuePath.slice(1) : ""}`;
|
|
714
|
+
}
|
|
715
|
+
mismatch = firstDifference(candidateResult, result, `${path}.result`);
|
|
716
|
+
if (mismatch !== null)
|
|
717
|
+
return mismatch;
|
|
718
|
+
const runFingerprint = tag("diagnostic-run", "manifest", manifestIdentity(expectedManifest));
|
|
719
|
+
const resultFingerprint = tag("diagnostic-result", "result", result);
|
|
720
|
+
if (value.runFingerprint !== runFingerprint)
|
|
721
|
+
return `${path}.runFingerprint`;
|
|
722
|
+
if (value.resultFingerprint !== resultFingerprint)
|
|
723
|
+
return `${path}.resultFingerprint`;
|
|
724
|
+
if (value.runResultFingerprint !==
|
|
725
|
+
bindingTag(runFingerprint, resultFingerprint))
|
|
726
|
+
return `${path}.runResultFingerprint`;
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
if (error instanceof ComplianceError && error.path) {
|
|
731
|
+
const relative = error.path
|
|
732
|
+
.replace(/^\$\.completedRun\.prepared/, "$.manifest")
|
|
733
|
+
.replace(/^\$\.completedRun\.review/, "$.review")
|
|
734
|
+
.replace(/^\$\.completedRun\.gate/, "$.manifest.executionPolicy.gate")
|
|
735
|
+
.replace(/^\$\.completedRun\./, "$.manifest.")
|
|
736
|
+
.replace(/^\$\.(inputArtifacts|preparationArtifacts|preparationLineage)/, "$.manifest.$1");
|
|
737
|
+
return `${path}${relative.slice(1)}`;
|
|
738
|
+
}
|
|
739
|
+
return path;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
175
742
|
/** @internal Bundle authoring uses owner state rather than trusting the public snapshot. */
|
|
176
|
-
export function verifiedDefinitionForBundle(value) {
|
|
743
|
+
export function verifiedDefinitionForBundle(value) {
|
|
744
|
+
assertVerifiedDiagnosticRunProvenance(value);
|
|
745
|
+
return verified.get(value).prepared.definition;
|
|
746
|
+
}
|
|
177
747
|
export async function verifyDiagnosticRunIdentity(candidate, input) {
|
|
178
|
-
const regenerated = await createDiagnosticRunIdentity(input);
|
|
179
748
|
let left;
|
|
749
|
+
let snapshot;
|
|
180
750
|
try {
|
|
181
751
|
left = canonicalJson(candidate);
|
|
752
|
+
snapshot = JSON.parse(left);
|
|
182
753
|
}
|
|
183
754
|
catch {
|
|
184
755
|
throw new ComplianceError("DIAGNOSTIC_MISMATCH", "Stored diagnostic provenance is not canonical JSON", "$");
|
|
185
756
|
}
|
|
186
|
-
|
|
187
|
-
|
|
757
|
+
const regenerated = await createDiagnosticRunIdentity(input);
|
|
758
|
+
const comparedSnapshot = provenanceIdentityView(snapshot);
|
|
759
|
+
const comparedRegenerated = provenanceIdentityView(regenerated);
|
|
760
|
+
if (canonicalJson(comparedSnapshot) !== canonicalJson(comparedRegenerated))
|
|
761
|
+
throw new ComplianceError("DIAGNOSTIC_MISMATCH", "Stored diagnostic provenance differs from regenerated provenance", firstDifference(comparedSnapshot, comparedRegenerated, "$") ?? "$");
|
|
188
762
|
return regenerated;
|
|
189
763
|
}
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
764
|
+
function firstDifference(left, right, path) {
|
|
765
|
+
if (plain(left) && plain(right)) {
|
|
766
|
+
const keys = [
|
|
767
|
+
...new Set([...Object.keys(left), ...Object.keys(right)]),
|
|
768
|
+
].sort();
|
|
769
|
+
for (const key of keys) {
|
|
770
|
+
const childPath = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
|
|
771
|
+
? `${path}.${key}`
|
|
772
|
+
: `${path}[${JSON.stringify(key)}]`;
|
|
773
|
+
if (!Object.prototype.hasOwnProperty.call(left, key) ||
|
|
774
|
+
!Object.prototype.hasOwnProperty.call(right, key))
|
|
775
|
+
return childPath;
|
|
776
|
+
const found = firstDifference(left[key], right[key], childPath);
|
|
777
|
+
if (found)
|
|
778
|
+
return found;
|
|
779
|
+
}
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
782
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
783
|
+
const shared = Math.min(left.length, right.length);
|
|
784
|
+
for (let index = 0; index < shared; index++) {
|
|
785
|
+
const found = firstDifference(left[index], right[index], `${path}[${index}]`);
|
|
786
|
+
if (found)
|
|
787
|
+
return found;
|
|
788
|
+
}
|
|
789
|
+
return left.length === right.length ? null : `${path}[${shared}]`;
|
|
790
|
+
}
|
|
791
|
+
try {
|
|
792
|
+
return canonicalJson(left) === canonicalJson(right) ? null : path;
|
|
793
|
+
}
|
|
794
|
+
catch {
|
|
795
|
+
return path;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
216
798
|
//# sourceMappingURL=diagnosticRun.js.map
|