@agentplat/mesh-sim 0.3.0-beta.2 → 0.3.0-beta.5
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 +447 -2
- package/dist/collective-closed-loop-action.d.ts +80 -0
- package/dist/collective-closed-loop-action.d.ts.map +1 -0
- package/dist/collective-closed-loop-action.js +471 -0
- package/dist/collective-closed-loop-action.js.map +1 -0
- package/dist/collective-closed-loop-contracts.d.ts +75 -0
- package/dist/collective-closed-loop-contracts.d.ts.map +1 -0
- package/dist/collective-closed-loop-contracts.js +420 -0
- package/dist/collective-closed-loop-contracts.js.map +1 -0
- package/dist/collective-closed-loop-fault-matrix.d.ts +101 -0
- package/dist/collective-closed-loop-fault-matrix.d.ts.map +1 -0
- package/dist/collective-closed-loop-fault-matrix.js +253 -0
- package/dist/collective-closed-loop-fault-matrix.js.map +1 -0
- package/dist/collective-closed-loop-recovery.d.ts +70 -0
- package/dist/collective-closed-loop-recovery.d.ts.map +1 -0
- package/dist/collective-closed-loop-recovery.js +823 -0
- package/dist/collective-closed-loop-recovery.js.map +1 -0
- package/dist/collective-closed-loop-reference.d.ts +54 -0
- package/dist/collective-closed-loop-reference.d.ts.map +1 -0
- package/dist/collective-closed-loop-reference.js +1696 -0
- package/dist/collective-closed-loop-reference.js.map +1 -0
- package/dist/collective-closed-loop-replanning.d.ts +27 -0
- package/dist/collective-closed-loop-replanning.d.ts.map +1 -0
- package/dist/collective-closed-loop-replanning.js +151 -0
- package/dist/collective-closed-loop-replanning.js.map +1 -0
- package/dist/collective-closed-loop-resilience-campaign.d.ts +22 -0
- package/dist/collective-closed-loop-resilience-campaign.d.ts.map +1 -0
- package/dist/collective-closed-loop-resilience-campaign.js +196 -0
- package/dist/collective-closed-loop-resilience-campaign.js.map +1 -0
- package/dist/collective-closed-loop-resilience-contracts.d.ts +138 -0
- package/dist/collective-closed-loop-resilience-contracts.d.ts.map +1 -0
- package/dist/collective-closed-loop-resilience-contracts.js +677 -0
- package/dist/collective-closed-loop-resilience-contracts.js.map +1 -0
- package/dist/collective-closed-loop-runner.d.ts +133 -0
- package/dist/collective-closed-loop-runner.d.ts.map +1 -0
- package/dist/collective-closed-loop-runner.js +1429 -0
- package/dist/collective-closed-loop-runner.js.map +1 -0
- package/dist/collective-closed-loop-runtime.d.ts +95 -0
- package/dist/collective-closed-loop-runtime.d.ts.map +1 -0
- package/dist/collective-closed-loop-runtime.js +1192 -0
- package/dist/collective-closed-loop-runtime.js.map +1 -0
- package/dist/collective-effect-provenance.d.ts +5 -0
- package/dist/collective-effect-provenance.d.ts.map +1 -0
- package/dist/collective-effect-provenance.js +32 -0
- package/dist/collective-effect-provenance.js.map +1 -0
- package/dist/collective-environment.d.ts +49 -0
- package/dist/collective-environment.d.ts.map +1 -0
- package/dist/collective-environment.js +907 -0
- package/dist/collective-environment.js.map +1 -0
- package/dist/collective-progressive-scale-overlay.d.ts +39 -0
- package/dist/collective-progressive-scale-overlay.d.ts.map +1 -0
- package/dist/collective-progressive-scale-overlay.js +147 -0
- package/dist/collective-progressive-scale-overlay.js.map +1 -0
- package/dist/collective-progressive-scale.d.ts +202 -0
- package/dist/collective-progressive-scale.d.ts.map +1 -0
- package/dist/collective-progressive-scale.js +755 -0
- package/dist/collective-progressive-scale.js.map +1 -0
- package/dist/collective-statistical-campaign-aggregation.d.ts +97 -0
- package/dist/collective-statistical-campaign-aggregation.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-aggregation.js +446 -0
- package/dist/collective-statistical-campaign-aggregation.js.map +1 -0
- package/dist/collective-statistical-campaign-artifact-stream.d.ts +66 -0
- package/dist/collective-statistical-campaign-artifact-stream.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-artifact-stream.js +207 -0
- package/dist/collective-statistical-campaign-artifact-stream.js.map +1 -0
- package/dist/collective-statistical-campaign-bundle.d.ts +151 -0
- package/dist/collective-statistical-campaign-bundle.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-bundle.js +885 -0
- package/dist/collective-statistical-campaign-bundle.js.map +1 -0
- package/dist/collective-statistical-campaign-executor.d.ts +131 -0
- package/dist/collective-statistical-campaign-executor.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-executor.js +856 -0
- package/dist/collective-statistical-campaign-executor.js.map +1 -0
- package/dist/collective-statistical-campaign-normative-analysis.d.ts +85 -0
- package/dist/collective-statistical-campaign-normative-analysis.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-normative-analysis.js +483 -0
- package/dist/collective-statistical-campaign-normative-analysis.js.map +1 -0
- package/dist/collective-statistical-campaign-normative-operation.d.ts +89 -0
- package/dist/collective-statistical-campaign-normative-operation.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-normative-operation.js +266 -0
- package/dist/collective-statistical-campaign-normative-operation.js.map +1 -0
- package/dist/collective-statistical-campaign-normative-registry.d.ts +24 -0
- package/dist/collective-statistical-campaign-normative-registry.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-normative-registry.js +163 -0
- package/dist/collective-statistical-campaign-normative-registry.js.map +1 -0
- package/dist/collective-statistical-campaign-registered-adapter.d.ts +16 -0
- package/dist/collective-statistical-campaign-registered-adapter.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-registered-adapter.js +518 -0
- package/dist/collective-statistical-campaign-registered-adapter.js.map +1 -0
- package/dist/collective-statistical-campaign-scale.d.ts +85 -0
- package/dist/collective-statistical-campaign-scale.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-scale.js +349 -0
- package/dist/collective-statistical-campaign-scale.js.map +1 -0
- package/dist/collective-trace-journal.d.ts +20 -0
- package/dist/collective-trace-journal.d.ts.map +1 -0
- package/dist/collective-trace-journal.js +105 -0
- package/dist/collective-trace-journal.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/dist/multi-domain-environment-contracts.d.ts +122 -0
- package/dist/multi-domain-environment-contracts.d.ts.map +1 -0
- package/dist/multi-domain-environment-contracts.js +14 -0
- package/dist/multi-domain-environment-contracts.js.map +1 -0
- package/dist/multi-domain-environment-runtime.d.ts +25 -0
- package/dist/multi-domain-environment-runtime.d.ts.map +1 -0
- package/dist/multi-domain-environment-runtime.js +493 -0
- package/dist/multi-domain-environment-runtime.js.map +1 -0
- package/dist/multi-domain-environment-validation.d.ts +33 -0
- package/dist/multi-domain-environment-validation.d.ts.map +1 -0
- package/dist/multi-domain-environment-validation.js +415 -0
- package/dist/multi-domain-environment-validation.js.map +1 -0
- package/dist/multi-domain-environment.d.ts +4 -0
- package/dist/multi-domain-environment.d.ts.map +1 -0
- package/dist/multi-domain-environment.js +4 -0
- package/dist/multi-domain-environment.js.map +1 -0
- package/dist/reducer-scenario.d.ts.map +1 -1
- package/dist/reducer-scenario.js +67 -19
- package/dist/reducer-scenario.js.map +1 -1
- package/dist/scalable-evaluation-contracts.d.ts +835 -0
- package/dist/scalable-evaluation-contracts.d.ts.map +1 -0
- package/dist/scalable-evaluation-contracts.js +22 -0
- package/dist/scalable-evaluation-contracts.js.map +1 -0
- package/dist/scalable-evaluation-durability.d.ts +16 -0
- package/dist/scalable-evaluation-durability.d.ts.map +1 -0
- package/dist/scalable-evaluation-durability.js +339 -0
- package/dist/scalable-evaluation-durability.js.map +1 -0
- package/dist/scalable-evaluation-evidence.d.ts +93 -0
- package/dist/scalable-evaluation-evidence.d.ts.map +1 -0
- package/dist/scalable-evaluation-evidence.js +470 -0
- package/dist/scalable-evaluation-evidence.js.map +1 -0
- package/dist/scalable-evaluation-reference-integrated.d.ts +87 -0
- package/dist/scalable-evaluation-reference-integrated.d.ts.map +1 -0
- package/dist/scalable-evaluation-reference-integrated.js +1318 -0
- package/dist/scalable-evaluation-reference-integrated.js.map +1 -0
- package/dist/scalable-evaluation-runner.d.ts +8 -0
- package/dist/scalable-evaluation-runner.d.ts.map +1 -0
- package/dist/scalable-evaluation-runner.js +1810 -0
- package/dist/scalable-evaluation-runner.js.map +1 -0
- package/dist/scalable-evaluation-runtime.d.ts +86 -0
- package/dist/scalable-evaluation-runtime.d.ts.map +1 -0
- package/dist/scalable-evaluation-runtime.js +1310 -0
- package/dist/scalable-evaluation-runtime.js.map +1 -0
- package/dist/scalable-evaluation-validation.d.ts +59 -0
- package/dist/scalable-evaluation-validation.d.ts.map +1 -0
- package/dist/scalable-evaluation-validation.js +377 -0
- package/dist/scalable-evaluation-validation.js.map +1 -0
- package/dist/scalable-evaluation.d.ts +8 -0
- package/dist/scalable-evaluation.d.ts.map +1 -0
- package/dist/scalable-evaluation.js +8 -0
- package/dist/scalable-evaluation.js.map +1 -0
- package/dist/sharded-simulation-contracts.d.ts +240 -0
- package/dist/sharded-simulation-contracts.d.ts.map +1 -0
- package/dist/sharded-simulation-contracts.js +199 -0
- package/dist/sharded-simulation-contracts.js.map +1 -0
- package/dist/sharded-simulation-runtime.d.ts +64 -0
- package/dist/sharded-simulation-runtime.d.ts.map +1 -0
- package/dist/sharded-simulation-runtime.js +755 -0
- package/dist/sharded-simulation-runtime.js.map +1 -0
- package/dist/sharded-simulation-validation.d.ts +10 -0
- package/dist/sharded-simulation-validation.d.ts.map +1 -0
- package/dist/sharded-simulation-validation.js +250 -0
- package/dist/sharded-simulation-validation.js.map +1 -0
- package/dist/sharded-simulation.d.ts +46 -0
- package/dist/sharded-simulation.d.ts.map +1 -0
- package/dist/sharded-simulation.js +32 -0
- package/dist/sharded-simulation.js.map +1 -0
- package/dist/verified-benchmark-contracts.d.ts +132 -0
- package/dist/verified-benchmark-contracts.d.ts.map +1 -0
- package/dist/verified-benchmark-contracts.js +2 -0
- package/dist/verified-benchmark-contracts.js.map +1 -0
- package/dist/verified-benchmark-runtime.d.ts +20 -0
- package/dist/verified-benchmark-runtime.d.ts.map +1 -0
- package/dist/verified-benchmark-runtime.js +199 -0
- package/dist/verified-benchmark-runtime.js.map +1 -0
- package/dist/verified-benchmark-validation.d.ts +64 -0
- package/dist/verified-benchmark-validation.d.ts.map +1 -0
- package/dist/verified-benchmark-validation.js +481 -0
- package/dist/verified-benchmark-validation.js.map +1 -0
- package/dist/verified-benchmark.d.ts +4 -0
- package/dist/verified-benchmark.d.ts.map +1 -0
- package/dist/verified-benchmark.js +4 -0
- package/dist/verified-benchmark.js.map +1 -0
- package/package.json +18 -8
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
import { canonicalizePlanningJsonV1, digestPlanningJsonV1, } from "@agentplat/collective-planning";
|
|
2
|
+
import { validateCollectiveEvaluationCampaignManifestV1, validateCollectiveEvaluationCampaignRegistrationV1, } from "@agentplat/collective-planning/evaluation";
|
|
3
|
+
export const COLLECTIVE_STATISTICAL_CAMPAIGN_MAXIMUM_ARTIFACT_BYTES_V1 = 16 * 1024 * 1024;
|
|
4
|
+
export const COLLECTIVE_STATISTICAL_CAMPAIGN_MAXIMUM_BUNDLE_BYTES_V1 = 256 * 1024 * 1024;
|
|
5
|
+
const encoder = new TextEncoder();
|
|
6
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
7
|
+
const MAXIMUM_SNAPSHOT_DEPTH = 64;
|
|
8
|
+
const MAXIMUM_SNAPSHOT_NODES = 1_000_000;
|
|
9
|
+
const MAXIMUM_SNAPSHOT_FIELDS = 16_384;
|
|
10
|
+
const ARTIFACT_JSON_LIMITS = Object.freeze({
|
|
11
|
+
maximumBytes: COLLECTIVE_STATISTICAL_CAMPAIGN_MAXIMUM_ARTIFACT_BYTES_V1,
|
|
12
|
+
maximumDepth: MAXIMUM_SNAPSHOT_DEPTH,
|
|
13
|
+
maximumNodes: MAXIMUM_SNAPSHOT_NODES,
|
|
14
|
+
maximumKeysPerObject: 4_096,
|
|
15
|
+
maximumItemsPerArray: MAXIMUM_SNAPSHOT_FIELDS,
|
|
16
|
+
});
|
|
17
|
+
const digestPattern = /^sha256:[0-9a-f]{64}$/u;
|
|
18
|
+
const hexPattern = /^[0-9a-f]{64}$/u;
|
|
19
|
+
const artifactKinds = new Set([
|
|
20
|
+
"source-lock",
|
|
21
|
+
"package-lock",
|
|
22
|
+
"fixture",
|
|
23
|
+
"policy",
|
|
24
|
+
"environment",
|
|
25
|
+
"observation-policy",
|
|
26
|
+
"monitor",
|
|
27
|
+
"hidden-canary",
|
|
28
|
+
"registration",
|
|
29
|
+
"manifest",
|
|
30
|
+
"sample",
|
|
31
|
+
"trace",
|
|
32
|
+
"ledger",
|
|
33
|
+
"metric-projection",
|
|
34
|
+
"evidence",
|
|
35
|
+
"comparison",
|
|
36
|
+
"summary",
|
|
37
|
+
]);
|
|
38
|
+
export function digestCollectiveStatisticalCampaignArtifactV1(kind, value) {
|
|
39
|
+
kindValue(kind);
|
|
40
|
+
return digestArtifact({ schemaVersion: 1, kind, value });
|
|
41
|
+
}
|
|
42
|
+
export function digestCollectiveStatisticalCampaignSampleV1(value) {
|
|
43
|
+
const snapshot = snapshotJson(value, "sample digest input");
|
|
44
|
+
const { attempt: _attempt, traceArtifactId: _trace, ledgerArtifactId: _ledger, evidenceArtifactId: _evidence, ...replayStable } = snapshot;
|
|
45
|
+
return digestArtifact({
|
|
46
|
+
schemaVersion: 1,
|
|
47
|
+
kind: "sample-result",
|
|
48
|
+
value: replayStable,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function digestCollectiveStatisticalCampaignTraceV1(value) {
|
|
52
|
+
return digestArtifact({ schemaVersion: 1, kind: "trace", value });
|
|
53
|
+
}
|
|
54
|
+
export function digestCollectiveStatisticalCampaignLedgerV1(value) {
|
|
55
|
+
return digestArtifact({ schemaVersion: 1, kind: "ledger", value });
|
|
56
|
+
}
|
|
57
|
+
export function digestCollectiveStatisticalCampaignEvidenceV1(value) {
|
|
58
|
+
return digestArtifact({ schemaVersion: 1, kind: "evidence", value });
|
|
59
|
+
}
|
|
60
|
+
export function digestCollectiveStatisticalCampaignComparisonInputV1(input) {
|
|
61
|
+
return digestArtifact({
|
|
62
|
+
schemaVersion: 1,
|
|
63
|
+
kind: "comparison-input",
|
|
64
|
+
value: input,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export function digestCollectiveStatisticalCampaignComparisonV1(value) {
|
|
68
|
+
return digestArtifact({ schemaVersion: 1, kind: "comparison", value });
|
|
69
|
+
}
|
|
70
|
+
export function digestCollectiveStatisticalCampaignSummaryV1(value) {
|
|
71
|
+
return digestArtifact({ schemaVersion: 1, kind: "summary", value });
|
|
72
|
+
}
|
|
73
|
+
export function digestCollectiveStatisticalCampaignBundleV1(value) {
|
|
74
|
+
const snapshot = snapshotJson(value, "bundle digest input");
|
|
75
|
+
return digestPlanningJsonV1("evaluation-campaign-bundle-v1", snapshot);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Verifies a fully supplied in-memory bundle. Paths remain logical: this
|
|
79
|
+
* function performs no file-system or network access and trusts no summary.
|
|
80
|
+
*/
|
|
81
|
+
export async function verifyCollectiveStatisticalCampaignBundleV1(bundleInput, suppliedBytesInput, hooksInput) {
|
|
82
|
+
const parsedBundle = validateBundle(snapshotJson(bundleInput, "bundle"));
|
|
83
|
+
const byteMap = snapshotByteMap(suppliedBytesInput);
|
|
84
|
+
const hooks = snapshotHooks(hooksInput);
|
|
85
|
+
const expected = new Map(parsedBundle.expectedArtifacts.map((value) => [value.artifactId, value]));
|
|
86
|
+
const indexed = new Map(parsedBundle.artifacts.map((value) => [value.artifactId, value]));
|
|
87
|
+
if (expected.size !== indexed.size ||
|
|
88
|
+
[...expected.keys()].some((id) => !indexed.has(id)))
|
|
89
|
+
fail("artifact index is not an exact closure of expected artifacts");
|
|
90
|
+
for (const [artifactId, expectation] of expected) {
|
|
91
|
+
if (indexed.get(artifactId).kind !== expectation.kind)
|
|
92
|
+
fail(`artifact kind does not match expectation: ${artifactId}`);
|
|
93
|
+
assertExpectedArtifactScope(expectation);
|
|
94
|
+
}
|
|
95
|
+
if (Object.keys(byteMap).length !== indexed.size ||
|
|
96
|
+
[...indexed.keys()].some((id) => !(id in byteMap)))
|
|
97
|
+
fail("supplied artifact bytes are not an exact closure of the index");
|
|
98
|
+
const values = new Map();
|
|
99
|
+
for (const [artifactId, entry] of indexed) {
|
|
100
|
+
const raw = byteMap[artifactId];
|
|
101
|
+
if (raw.byteLength !== entry.byteLength ||
|
|
102
|
+
(await sha256(raw)) !== entry.sha256)
|
|
103
|
+
fail(`artifact bytes do not match index: ${artifactId}`);
|
|
104
|
+
const value = parseJson(raw, artifactId);
|
|
105
|
+
if (digestCollectiveStatisticalCampaignArtifactV1(entry.kind, value) !==
|
|
106
|
+
entry.canonicalDigest)
|
|
107
|
+
fail(`artifact canonical digest is invalid: ${artifactId}`);
|
|
108
|
+
hooks.validateArtifact?.(entry.kind, value, {
|
|
109
|
+
artifactId,
|
|
110
|
+
path: entry.path,
|
|
111
|
+
});
|
|
112
|
+
values.set(artifactId, value);
|
|
113
|
+
}
|
|
114
|
+
assertGlobalArtifact(parsedBundle.sourceLockArtifactId, "source-lock", expected);
|
|
115
|
+
assertGlobalArtifact(parsedBundle.registrationArtifactId, "registration", expected);
|
|
116
|
+
assertGlobalArtifact(parsedBundle.manifestArtifactId, "manifest", expected);
|
|
117
|
+
assertGlobalArtifact(parsedBundle.summaryArtifactId, "summary", expected);
|
|
118
|
+
const sourceLock = validateSourceLock(values.get(parsedBundle.sourceLockArtifactId));
|
|
119
|
+
if (sourceLock.sourceCommit !== hooks.expectedSourceLock.sourceCommit ||
|
|
120
|
+
sourceLock.sourceTreeDigest !== hooks.expectedSourceLock.sourceTreeDigest)
|
|
121
|
+
fail("source lock does not match trusted provenance");
|
|
122
|
+
const registration = validateRegistration(values.get(parsedBundle.registrationArtifactId));
|
|
123
|
+
const manifest = validateManifest(values.get(parsedBundle.manifestArtifactId), registration);
|
|
124
|
+
if (registration.campaignId !== parsedBundle.campaignId)
|
|
125
|
+
fail("bundle campaignId does not match registration");
|
|
126
|
+
assertRegistrationProvenance(registration, indexed);
|
|
127
|
+
const registeredCells = new Map(registration.cells.map((cell) => [cell.cellId, cell]));
|
|
128
|
+
if (parsedBundle.cells.length !== registration.cells.length ||
|
|
129
|
+
parsedBundle.cells.some((cell, index) => cell.cellId !== registration.cells[index].cellId))
|
|
130
|
+
fail("bundle cells do not exactly match the registered schedule");
|
|
131
|
+
const manifestEntries = new Map(manifest.entries.map((entry) => [entry.cellId, entry]));
|
|
132
|
+
const comparisons = new Map();
|
|
133
|
+
const samples = new Map();
|
|
134
|
+
const traces = new Map();
|
|
135
|
+
const ledgers = new Map();
|
|
136
|
+
const evidence = new Map();
|
|
137
|
+
for (const [artifactId, entry] of indexed) {
|
|
138
|
+
const value = values.get(artifactId);
|
|
139
|
+
if (entry.kind === "sample")
|
|
140
|
+
samples.set(artifactId, validateSample(value));
|
|
141
|
+
if (entry.kind === "trace")
|
|
142
|
+
traces.set(artifactId, validateTrace(value));
|
|
143
|
+
if (entry.kind === "ledger")
|
|
144
|
+
ledgers.set(artifactId, validateLedger(value));
|
|
145
|
+
if (entry.kind === "evidence")
|
|
146
|
+
evidence.set(artifactId, validateEvidence(value));
|
|
147
|
+
if (entry.kind === "comparison")
|
|
148
|
+
comparisons.set(artifactId, validateComparison(value));
|
|
149
|
+
}
|
|
150
|
+
const summary = validateSummary(values.get(parsedBundle.summaryArtifactId));
|
|
151
|
+
assertArtifactReferenceClosure(samples, traces, ledgers, evidence);
|
|
152
|
+
for (const [artifactId, sample] of samples) {
|
|
153
|
+
const expectation = expected.get(artifactId);
|
|
154
|
+
const registeredCell = registeredCells.get(sample.cellId);
|
|
155
|
+
if (!expectation ||
|
|
156
|
+
expectation.kind !== "sample" ||
|
|
157
|
+
!registeredCell ||
|
|
158
|
+
sample.seed !== registeredCell.seed ||
|
|
159
|
+
expectation.cellId !== sample.cellId ||
|
|
160
|
+
expectation.seed !== sample.seed ||
|
|
161
|
+
expectation.runner !== sample.runner ||
|
|
162
|
+
expectation.attempt !== sample.attempt)
|
|
163
|
+
fail(`sample is not registered: ${artifactId}`);
|
|
164
|
+
assertExecutionArtifactBinding(artifactId, sample, expectation, expected, traces, ledgers, evidence);
|
|
165
|
+
}
|
|
166
|
+
for (const expectation of expected.values()) {
|
|
167
|
+
if (expectation.kind === "sample" && !samples.has(expectation.artifactId))
|
|
168
|
+
fail(`expected sample is absent: ${expectation.artifactId}`);
|
|
169
|
+
}
|
|
170
|
+
assertExactAttempts(registration, samples, traces, ledgers, evidence, expected);
|
|
171
|
+
const orderedComparisons = [];
|
|
172
|
+
for (const cell of parsedBundle.cells) {
|
|
173
|
+
const registeredCell = registeredCells.get(cell.cellId);
|
|
174
|
+
const manifestEntry = manifestEntries.get(cell.cellId);
|
|
175
|
+
const comparisonExpectation = expected.get(cell.comparisonArtifactId);
|
|
176
|
+
const comparison = comparisons.get(cell.comparisonArtifactId);
|
|
177
|
+
if (comparisonExpectation?.kind !== "comparison" ||
|
|
178
|
+
comparisonExpectation.cellId !== cell.cellId ||
|
|
179
|
+
!comparison ||
|
|
180
|
+
comparison.cellId !== cell.cellId)
|
|
181
|
+
fail(`comparison is missing or substituted: ${cell.cellId}`);
|
|
182
|
+
const adaptive = comparison.adaptiveSampleArtifactIds.map((id) => sampleFor(id, samples, registeredCell.seed, cell.cellId, "adaptive_collective"));
|
|
183
|
+
const centralized = comparison.centralizedSampleArtifactIds.map((id) => sampleFor(id, samples, registeredCell.seed, cell.cellId, "centralized_planner"));
|
|
184
|
+
if (adaptive.length !== 1 || centralized.length !== 1)
|
|
185
|
+
fail(`comparison is not an exact paired cell: ${cell.cellId}`);
|
|
186
|
+
const inputDigest = digestCollectiveStatisticalCampaignComparisonInputV1({
|
|
187
|
+
cellId: comparison.cellId,
|
|
188
|
+
adaptiveSampleDigests: adaptive.map((sample) => sample.sampleDigest),
|
|
189
|
+
centralizedSampleDigests: centralized.map((sample) => sample.sampleDigest),
|
|
190
|
+
bootstrap: comparison.bootstrap,
|
|
191
|
+
});
|
|
192
|
+
if (comparison.inputDigest !== inputDigest)
|
|
193
|
+
fail(`comparison input digest is invalid: ${cell.cellId}`);
|
|
194
|
+
if (!sameJson(hooks.recomputeComparisonStatistics(comparison, adaptive, centralized), comparison.statistics))
|
|
195
|
+
fail(`comparison statistics are not independently recomputable: ${cell.cellId}`);
|
|
196
|
+
assertManifestEntry(manifestEntry, comparison, adaptive[0], centralized[0], samples, traces, ledgers, evidence, expected);
|
|
197
|
+
orderedComparisons.push(comparison);
|
|
198
|
+
}
|
|
199
|
+
if (comparisons.size !== parsedBundle.cells.length)
|
|
200
|
+
fail("comparison artifacts are not an exact closure of cells");
|
|
201
|
+
if (!sameStrings(summary.comparisonArtifactIds, parsedBundle.cells.map((cell) => cell.comparisonArtifactId)))
|
|
202
|
+
fail("summary does not close over all comparisons");
|
|
203
|
+
if (!sameJson(hooks.recomputeSummaryStatistics(summary, orderedComparisons), summary.statistics))
|
|
204
|
+
fail("summary statistics are not independently recomputable");
|
|
205
|
+
const anyFailure = manifest.entries.some((entry) => entry.status === "failure") ||
|
|
206
|
+
[...samples.values()].some((sample) => sample.status === "failed");
|
|
207
|
+
if ((summary.status === "passed") === anyFailure)
|
|
208
|
+
fail("summary status does not reflect retained campaign failures");
|
|
209
|
+
return Object.freeze({
|
|
210
|
+
schemaVersion: 1,
|
|
211
|
+
campaignId: parsedBundle.campaignId,
|
|
212
|
+
registrationDigest: registration.registrationDigest,
|
|
213
|
+
manifestDigest: manifest.manifestDigest,
|
|
214
|
+
artifactCount: indexed.size,
|
|
215
|
+
sampleCount: samples.size,
|
|
216
|
+
comparisonCount: comparisons.size,
|
|
217
|
+
status: summary.status,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function validateBundle(value) {
|
|
221
|
+
exact(value, [
|
|
222
|
+
"schemaVersion",
|
|
223
|
+
"campaignId",
|
|
224
|
+
"sourceLockArtifactId",
|
|
225
|
+
"registrationArtifactId",
|
|
226
|
+
"manifestArtifactId",
|
|
227
|
+
"cells",
|
|
228
|
+
"expectedArtifacts",
|
|
229
|
+
"artifacts",
|
|
230
|
+
"summaryArtifactId",
|
|
231
|
+
"bundleDigest",
|
|
232
|
+
], "bundle");
|
|
233
|
+
const body = value;
|
|
234
|
+
if (body.schemaVersion !== 1)
|
|
235
|
+
fail("bundle schema is invalid");
|
|
236
|
+
token(body.campaignId, "campaignId");
|
|
237
|
+
for (const name of [
|
|
238
|
+
"sourceLockArtifactId",
|
|
239
|
+
"registrationArtifactId",
|
|
240
|
+
"manifestArtifactId",
|
|
241
|
+
"summaryArtifactId",
|
|
242
|
+
])
|
|
243
|
+
token(body[name], name);
|
|
244
|
+
digestValue(body.bundleDigest, "bundleDigest");
|
|
245
|
+
const cells = dense(body.cells, "cells", 1, 240).map(validateCell);
|
|
246
|
+
unique(cells.map((cell) => cell.cellId), "cell ids");
|
|
247
|
+
unique(cells.map((cell) => cell.comparisonArtifactId), "comparison ids");
|
|
248
|
+
const expectedArtifacts = dense(body.expectedArtifacts, "expectedArtifacts", 1, 16_384).map(validateExpectedArtifact);
|
|
249
|
+
unique(expectedArtifacts.map((item) => item.artifactId), "expected artifact ids");
|
|
250
|
+
unique(expectedArtifacts.map((item) => [item.kind, item.cellId, item.seed, item.runner, item.attempt].join("\u0000")), "expected artifact scopes");
|
|
251
|
+
const artifacts = dense(body.artifacts, "artifacts", 1, 16_384).map(validateIndexEntry);
|
|
252
|
+
unique(artifacts.map((item) => item.artifactId), "artifact ids");
|
|
253
|
+
unique(artifacts.map((item) => item.path), "artifact paths");
|
|
254
|
+
const totalBytes = artifacts.reduce((total, item) => total + item.byteLength, 0);
|
|
255
|
+
if (totalBytes > COLLECTIVE_STATISTICAL_CAMPAIGN_MAXIMUM_BUNDLE_BYTES_V1)
|
|
256
|
+
fail("bundle byte limit is exceeded");
|
|
257
|
+
const { bundleDigest: _digest, ...digestBody } = body;
|
|
258
|
+
if (digestCollectiveStatisticalCampaignBundleV1(digestBody) !== body.bundleDigest)
|
|
259
|
+
fail("bundle digest is invalid");
|
|
260
|
+
return body;
|
|
261
|
+
}
|
|
262
|
+
function validateCell(value) {
|
|
263
|
+
exact(value, ["schemaVersion", "cellId", "comparisonArtifactId"], "cell");
|
|
264
|
+
const body = value;
|
|
265
|
+
if (body.schemaVersion !== 1)
|
|
266
|
+
fail("cell schema is invalid");
|
|
267
|
+
token(body.cellId, "cellId");
|
|
268
|
+
token(body.comparisonArtifactId, "comparisonArtifactId");
|
|
269
|
+
return body;
|
|
270
|
+
}
|
|
271
|
+
function validateExpectedArtifact(value) {
|
|
272
|
+
exact(value, [
|
|
273
|
+
"schemaVersion",
|
|
274
|
+
"artifactId",
|
|
275
|
+
"kind",
|
|
276
|
+
"cellId",
|
|
277
|
+
"seed",
|
|
278
|
+
"runner",
|
|
279
|
+
"attempt",
|
|
280
|
+
], "expected artifact");
|
|
281
|
+
const body = value;
|
|
282
|
+
if (body.schemaVersion !== 1)
|
|
283
|
+
fail("expected artifact schema is invalid");
|
|
284
|
+
token(body.artifactId, "expected artifactId");
|
|
285
|
+
kindValue(body.kind);
|
|
286
|
+
nullableToken(body.cellId, "expected cellId");
|
|
287
|
+
nullableInteger(body.seed, "expected seed");
|
|
288
|
+
nullableRunner(body.runner);
|
|
289
|
+
nullableAttempt(body.attempt);
|
|
290
|
+
return body;
|
|
291
|
+
}
|
|
292
|
+
function validateIndexEntry(value) {
|
|
293
|
+
exact(value, [
|
|
294
|
+
"schemaVersion",
|
|
295
|
+
"artifactId",
|
|
296
|
+
"kind",
|
|
297
|
+
"path",
|
|
298
|
+
"byteLength",
|
|
299
|
+
"sha256",
|
|
300
|
+
"canonicalDigest",
|
|
301
|
+
], "artifact index entry");
|
|
302
|
+
const body = value;
|
|
303
|
+
if (body.schemaVersion !== 1)
|
|
304
|
+
fail("index schema is invalid");
|
|
305
|
+
token(body.artifactId, "artifactId");
|
|
306
|
+
kindValue(body.kind);
|
|
307
|
+
safePath(body.path);
|
|
308
|
+
integer(body.byteLength, "byteLength", 0);
|
|
309
|
+
if (body.byteLength >
|
|
310
|
+
COLLECTIVE_STATISTICAL_CAMPAIGN_MAXIMUM_ARTIFACT_BYTES_V1)
|
|
311
|
+
fail("artifact byte limit is exceeded");
|
|
312
|
+
hex(body.sha256, "sha256");
|
|
313
|
+
digestValue(body.canonicalDigest, "canonicalDigest");
|
|
314
|
+
return body;
|
|
315
|
+
}
|
|
316
|
+
function validateSourceLock(value) {
|
|
317
|
+
exact(value, ["schemaVersion", "sourceCommit", "sourceTreeDigest", "dirtyWorktree"], "source lock");
|
|
318
|
+
const body = value;
|
|
319
|
+
if (body.schemaVersion !== 1 ||
|
|
320
|
+
typeof body.sourceCommit !== "string" ||
|
|
321
|
+
!/^[0-9a-f]{40}$/u.test(body.sourceCommit) ||
|
|
322
|
+
body.dirtyWorktree !== false)
|
|
323
|
+
fail("source lock is invalid");
|
|
324
|
+
digestValue(body.sourceTreeDigest, "sourceTreeDigest");
|
|
325
|
+
return body;
|
|
326
|
+
}
|
|
327
|
+
function validateRegistration(value) {
|
|
328
|
+
try {
|
|
329
|
+
return validateCollectiveEvaluationCampaignRegistrationV1(value);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
fail(`registration contract is invalid: ${errorMessage(error)}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function validateManifest(value, registration) {
|
|
336
|
+
try {
|
|
337
|
+
return validateCollectiveEvaluationCampaignManifestV1(value, registration);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
fail(`manifest contract is invalid: ${errorMessage(error)}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function validateSample(value) {
|
|
344
|
+
exact(value, [
|
|
345
|
+
"schemaVersion", "cellId", "seed", "runner", "attempt", "status",
|
|
346
|
+
"traceArtifactId", "ledgerArtifactId", "evidenceArtifactId", "outcome",
|
|
347
|
+
"sampleDigest",
|
|
348
|
+
], "sample");
|
|
349
|
+
const body = value;
|
|
350
|
+
if (body.schemaVersion !== 1)
|
|
351
|
+
fail("sample schema is invalid");
|
|
352
|
+
token(body.cellId, "sample cellId");
|
|
353
|
+
integer(body.seed, "sample seed", 0);
|
|
354
|
+
runner(body.runner);
|
|
355
|
+
attempt(body.attempt);
|
|
356
|
+
if (body.status !== "passed" && body.status !== "failed")
|
|
357
|
+
fail("sample status is invalid");
|
|
358
|
+
token(body.traceArtifactId, "traceArtifactId");
|
|
359
|
+
token(body.ledgerArtifactId, "ledgerArtifactId");
|
|
360
|
+
token(body.evidenceArtifactId, "evidenceArtifactId");
|
|
361
|
+
safeJson(body.outcome);
|
|
362
|
+
digestValue(body.sampleDigest, "sampleDigest");
|
|
363
|
+
const { sampleDigest: _digest, ...digestBody } = body;
|
|
364
|
+
if (digestCollectiveStatisticalCampaignSampleV1(digestBody) !== body.sampleDigest)
|
|
365
|
+
fail("sample digest is invalid");
|
|
366
|
+
return body;
|
|
367
|
+
}
|
|
368
|
+
function validateTrace(value) {
|
|
369
|
+
exact(value, ["schemaVersion", "sampleArtifactId", "records", "traceDigest"], "trace");
|
|
370
|
+
const body = value;
|
|
371
|
+
if (body.schemaVersion !== 1)
|
|
372
|
+
fail("trace schema is invalid");
|
|
373
|
+
token(body.sampleArtifactId, "trace sampleArtifactId");
|
|
374
|
+
dense(body.records, "trace records", 0, 16_384).forEach(safeJson);
|
|
375
|
+
digestValue(body.traceDigest, "traceDigest");
|
|
376
|
+
const { traceDigest: _digest, ...digestBody } = body;
|
|
377
|
+
if (digestCollectiveStatisticalCampaignTraceV1(digestBody) !== body.traceDigest)
|
|
378
|
+
fail("trace digest is invalid");
|
|
379
|
+
return body;
|
|
380
|
+
}
|
|
381
|
+
function validateLedger(value) {
|
|
382
|
+
exact(value, ["schemaVersion", "sampleArtifactId", "records", "ledgerDigest"], "ledger");
|
|
383
|
+
const body = value;
|
|
384
|
+
if (body.schemaVersion !== 1)
|
|
385
|
+
fail("ledger schema is invalid");
|
|
386
|
+
token(body.sampleArtifactId, "ledger sampleArtifactId");
|
|
387
|
+
dense(body.records, "ledger records", 0, 16_384).forEach(safeJson);
|
|
388
|
+
digestValue(body.ledgerDigest, "ledgerDigest");
|
|
389
|
+
const { ledgerDigest: _digest, ...digestBody } = body;
|
|
390
|
+
if (digestCollectiveStatisticalCampaignLedgerV1(digestBody) !== body.ledgerDigest)
|
|
391
|
+
fail("ledger digest is invalid");
|
|
392
|
+
return body;
|
|
393
|
+
}
|
|
394
|
+
function validateEvidence(value) {
|
|
395
|
+
exact(value, ["schemaVersion", "sampleArtifactId", "sampleDigest", "observations", "evidenceDigest"], "evidence");
|
|
396
|
+
const body = value;
|
|
397
|
+
if (body.schemaVersion !== 1)
|
|
398
|
+
fail("evidence schema is invalid");
|
|
399
|
+
token(body.sampleArtifactId, "evidence sampleArtifactId");
|
|
400
|
+
digestValue(body.sampleDigest, "evidence sampleDigest");
|
|
401
|
+
dense(body.observations, "evidence observations", 0, 16_384).forEach(safeJson);
|
|
402
|
+
digestValue(body.evidenceDigest, "evidenceDigest");
|
|
403
|
+
const { evidenceDigest: _digest, ...digestBody } = body;
|
|
404
|
+
if (digestCollectiveStatisticalCampaignEvidenceV1(digestBody) !== body.evidenceDigest)
|
|
405
|
+
fail("evidence digest is invalid");
|
|
406
|
+
return body;
|
|
407
|
+
}
|
|
408
|
+
function validateComparison(value) {
|
|
409
|
+
exact(value, [
|
|
410
|
+
"schemaVersion", "cellId", "adaptiveSampleArtifactIds",
|
|
411
|
+
"centralizedSampleArtifactIds", "bootstrap", "statistics", "inputDigest",
|
|
412
|
+
"comparisonDigest",
|
|
413
|
+
], "comparison");
|
|
414
|
+
const body = value;
|
|
415
|
+
if (body.schemaVersion !== 1)
|
|
416
|
+
fail("comparison schema is invalid");
|
|
417
|
+
token(body.cellId, "comparison cellId");
|
|
418
|
+
const adaptive = strings(body.adaptiveSampleArtifactIds, "adaptive samples", 1);
|
|
419
|
+
const centralized = strings(body.centralizedSampleArtifactIds, "centralized samples", 1);
|
|
420
|
+
unique(adaptive, "adaptive samples");
|
|
421
|
+
unique(centralized, "centralized samples");
|
|
422
|
+
exact(body.bootstrap, ["schemaVersion", "seed", "resamples"], "bootstrap");
|
|
423
|
+
const bootstrap = body.bootstrap;
|
|
424
|
+
if (bootstrap.schemaVersion !== 1)
|
|
425
|
+
fail("bootstrap schema is invalid");
|
|
426
|
+
integer(bootstrap.seed, "bootstrap seed", 0);
|
|
427
|
+
integer(bootstrap.resamples, "bootstrap resamples", 10_000);
|
|
428
|
+
safeJson(body.statistics);
|
|
429
|
+
digestValue(body.inputDigest, "inputDigest");
|
|
430
|
+
digestValue(body.comparisonDigest, "comparisonDigest");
|
|
431
|
+
const { comparisonDigest: _digest, ...digestBody } = body;
|
|
432
|
+
if (digestCollectiveStatisticalCampaignComparisonV1(digestBody) !== body.comparisonDigest)
|
|
433
|
+
fail("comparison digest is invalid");
|
|
434
|
+
return body;
|
|
435
|
+
}
|
|
436
|
+
function validateSummary(value) {
|
|
437
|
+
exact(value, ["schemaVersion", "comparisonArtifactIds", "status", "statistics", "summaryDigest"], "summary");
|
|
438
|
+
const body = value;
|
|
439
|
+
if (body.schemaVersion !== 1)
|
|
440
|
+
fail("summary schema is invalid");
|
|
441
|
+
const comparisons = strings(body.comparisonArtifactIds, "summary comparisons", 1);
|
|
442
|
+
unique(comparisons, "summary comparisons");
|
|
443
|
+
if (body.status !== "passed" && body.status !== "failed")
|
|
444
|
+
fail("summary status is invalid");
|
|
445
|
+
safeJson(body.statistics);
|
|
446
|
+
digestValue(body.summaryDigest, "summaryDigest");
|
|
447
|
+
const { summaryDigest: _digest, ...digestBody } = body;
|
|
448
|
+
if (digestCollectiveStatisticalCampaignSummaryV1(digestBody) !== body.summaryDigest)
|
|
449
|
+
fail("summary digest is invalid");
|
|
450
|
+
return body;
|
|
451
|
+
}
|
|
452
|
+
function assertRegistrationProvenance(registration, indexed) {
|
|
453
|
+
const bindings = [
|
|
454
|
+
["source-lock", registration.sourceDigest],
|
|
455
|
+
["package-lock", registration.packageDigest],
|
|
456
|
+
["fixture", registration.fixtureManifestDigest],
|
|
457
|
+
["policy", registration.policyDigest],
|
|
458
|
+
["environment", registration.environmentDigest],
|
|
459
|
+
["observation-policy", registration.observationPolicyDigest],
|
|
460
|
+
["monitor", registration.monitorDigest],
|
|
461
|
+
["hidden-canary", registration.hiddenCanaryDigest],
|
|
462
|
+
];
|
|
463
|
+
for (const [kind, digest] of bindings) {
|
|
464
|
+
const artifact = singleArtifactOfKind(indexed, kind);
|
|
465
|
+
if (artifact.canonicalDigest !== digest)
|
|
466
|
+
fail(`registration provenance does not bind ${kind}`);
|
|
467
|
+
}
|
|
468
|
+
singleArtifactOfKind(indexed, "registration");
|
|
469
|
+
singleArtifactOfKind(indexed, "manifest");
|
|
470
|
+
singleArtifactOfKind(indexed, "summary");
|
|
471
|
+
}
|
|
472
|
+
function assertArtifactReferenceClosure(samples, traces, ledgers, evidence) {
|
|
473
|
+
const closures = [
|
|
474
|
+
[new Set([...samples.values()].map((item) => item.traceArtifactId)), traces, "trace"],
|
|
475
|
+
[new Set([...samples.values()].map((item) => item.ledgerArtifactId)), ledgers, "ledger"],
|
|
476
|
+
[new Set([...samples.values()].map((item) => item.evidenceArtifactId)), evidence, "evidence"],
|
|
477
|
+
];
|
|
478
|
+
for (const [references, artifacts, label] of closures) {
|
|
479
|
+
if (references.size !== artifacts.size ||
|
|
480
|
+
[...artifacts.keys()].some((id) => !references.has(id)))
|
|
481
|
+
fail(`${label} artifacts are not an exact closure of samples`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function assertExecutionArtifactBinding(sampleArtifactId, sample, sampleExpectation, expected, traces, ledgers, evidence) {
|
|
485
|
+
const bindings = [
|
|
486
|
+
[sample.traceArtifactId, "trace", traces.get(sample.traceArtifactId)],
|
|
487
|
+
[sample.ledgerArtifactId, "ledger", ledgers.get(sample.ledgerArtifactId)],
|
|
488
|
+
[sample.evidenceArtifactId, "evidence", evidence.get(sample.evidenceArtifactId)],
|
|
489
|
+
];
|
|
490
|
+
for (const [artifactId, kind, artifact] of bindings) {
|
|
491
|
+
const expectation = expected.get(artifactId);
|
|
492
|
+
if (!expectation || !sameExecutionScope(expectation, sampleExpectation, kind))
|
|
493
|
+
fail(`sample ${kind} expectation is not bound: ${sampleArtifactId}`);
|
|
494
|
+
if (!artifact || artifact.sampleArtifactId !== sampleArtifactId)
|
|
495
|
+
fail(`sample ${kind} is not bound: ${sampleArtifactId}`);
|
|
496
|
+
}
|
|
497
|
+
const evidenceValue = evidence.get(sample.evidenceArtifactId);
|
|
498
|
+
if (evidenceValue.sampleDigest !== sample.sampleDigest)
|
|
499
|
+
fail(`sample evidence digest is not bound: ${sampleArtifactId}`);
|
|
500
|
+
}
|
|
501
|
+
function assertExactAttempts(registration, samples, traces, ledgers, evidence, expected) {
|
|
502
|
+
for (const cell of registration.cells) {
|
|
503
|
+
for (const runnerValue of ["adaptive_collective", "centralized_planner"]) {
|
|
504
|
+
const firstExpectation = findSampleExpectation(expected, cell.cellId, cell.seed, runnerValue, "first");
|
|
505
|
+
const replayExpectation = findSampleExpectation(expected, cell.cellId, cell.seed, runnerValue, "replay");
|
|
506
|
+
if (!firstExpectation || !replayExpectation)
|
|
507
|
+
fail(`expected replay pair is absent: ${cell.cellId}/${runnerValue}`);
|
|
508
|
+
const first = samples.get(firstExpectation.artifactId);
|
|
509
|
+
const replay = samples.get(replayExpectation.artifactId);
|
|
510
|
+
if (!first || !replay || first.sampleDigest !== replay.sampleDigest)
|
|
511
|
+
fail(`sample replay diverged: ${cell.cellId}/${runnerValue}`);
|
|
512
|
+
if (!sameJson(traces.get(first.traceArtifactId).records, traces.get(replay.traceArtifactId).records) ||
|
|
513
|
+
!sameJson(ledgers.get(first.ledgerArtifactId).records, ledgers.get(replay.ledgerArtifactId).records) ||
|
|
514
|
+
!sameJson(evidence.get(first.evidenceArtifactId).observations, evidence.get(replay.evidenceArtifactId).observations))
|
|
515
|
+
fail(`replay artifacts diverged: ${cell.cellId}/${runnerValue}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function assertManifestEntry(entry, comparison, adaptive, centralized, samples, traces, ledgers, evidence, expected) {
|
|
520
|
+
const executions = [...expected.values()]
|
|
521
|
+
.filter((item) => item.kind === "sample" && item.cellId === entry.cellId)
|
|
522
|
+
.map((item) => samples.get(item.artifactId));
|
|
523
|
+
if (entry.status === "failure") {
|
|
524
|
+
if (!executions.some((sample) => sample.status === "failed"))
|
|
525
|
+
fail(`failed manifest entry omits a failed execution: ${entry.cellId}`);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (executions.some((sample) => sample.status !== "passed"))
|
|
529
|
+
fail(`successful manifest entry contains a failed execution: ${entry.cellId}`);
|
|
530
|
+
const expectedBindings = {
|
|
531
|
+
adaptiveResultDigest: adaptive.sampleDigest,
|
|
532
|
+
centralizedResultDigest: centralized.sampleDigest,
|
|
533
|
+
adaptiveTraceDigest: traces.get(adaptive.traceArtifactId).traceDigest,
|
|
534
|
+
centralizedTraceDigest: traces.get(centralized.traceArtifactId).traceDigest,
|
|
535
|
+
adaptiveLedgerDigest: ledgers.get(adaptive.ledgerArtifactId).ledgerDigest,
|
|
536
|
+
centralizedLedgerDigest: ledgers.get(centralized.ledgerArtifactId).ledgerDigest,
|
|
537
|
+
fairnessDigest: comparison.comparisonDigest,
|
|
538
|
+
adaptiveCampaignEvidenceDigest: evidence.get(adaptive.evidenceArtifactId).evidenceDigest,
|
|
539
|
+
centralizedCampaignEvidenceDigest: evidence.get(centralized.evidenceArtifactId).evidenceDigest,
|
|
540
|
+
};
|
|
541
|
+
for (const [name, digest] of Object.entries(expectedBindings)) {
|
|
542
|
+
if (entry[name] !== digest)
|
|
543
|
+
fail(`manifest ${name} does not bind cell: ${entry.cellId}`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function sampleFor(id, samples, seed, cellId, runnerValue) {
|
|
547
|
+
const sample = samples.get(id);
|
|
548
|
+
if (!sample ||
|
|
549
|
+
sample.cellId !== cellId ||
|
|
550
|
+
sample.seed !== seed ||
|
|
551
|
+
sample.runner !== runnerValue ||
|
|
552
|
+
sample.attempt !== "first")
|
|
553
|
+
fail(`comparison sample is invalid: ${id}`);
|
|
554
|
+
return sample;
|
|
555
|
+
}
|
|
556
|
+
function snapshotByteMap(value) {
|
|
557
|
+
const descriptors = dataRecordDescriptors(value, "artifact bytes");
|
|
558
|
+
const result = Object.create(null);
|
|
559
|
+
let total = 0;
|
|
560
|
+
for (const [id, descriptor] of descriptors) {
|
|
561
|
+
token(id, "artifact bytes id");
|
|
562
|
+
const raw = descriptor.value;
|
|
563
|
+
let bytes;
|
|
564
|
+
if (typeof raw === "string") {
|
|
565
|
+
if (raw.length > COLLECTIVE_STATISTICAL_CAMPAIGN_MAXIMUM_ARTIFACT_BYTES_V1)
|
|
566
|
+
fail(`artifact byte limit is exceeded: ${id}`);
|
|
567
|
+
bytes = encoder.encode(raw);
|
|
568
|
+
}
|
|
569
|
+
else if (raw instanceof Uint8Array) {
|
|
570
|
+
if (raw.byteLength > COLLECTIVE_STATISTICAL_CAMPAIGN_MAXIMUM_ARTIFACT_BYTES_V1)
|
|
571
|
+
fail(`artifact byte limit is exceeded: ${id}`);
|
|
572
|
+
bytes = new Uint8Array(raw);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
fail(`artifact bytes are invalid: ${id}`);
|
|
576
|
+
}
|
|
577
|
+
if (bytes.byteLength > COLLECTIVE_STATISTICAL_CAMPAIGN_MAXIMUM_ARTIFACT_BYTES_V1)
|
|
578
|
+
fail(`artifact byte limit is exceeded: ${id}`);
|
|
579
|
+
total += bytes.byteLength;
|
|
580
|
+
if (total > COLLECTIVE_STATISTICAL_CAMPAIGN_MAXIMUM_BUNDLE_BYTES_V1)
|
|
581
|
+
fail("bundle byte limit is exceeded");
|
|
582
|
+
result[id] = bytes;
|
|
583
|
+
}
|
|
584
|
+
return Object.freeze(result);
|
|
585
|
+
}
|
|
586
|
+
function snapshotHooks(value) {
|
|
587
|
+
const descriptors = dataRecordDescriptors(value, "verification hooks");
|
|
588
|
+
const permitted = new Set([
|
|
589
|
+
"expectedSourceLock",
|
|
590
|
+
"validateArtifact",
|
|
591
|
+
"recomputeComparisonStatistics",
|
|
592
|
+
"recomputeSummaryStatistics",
|
|
593
|
+
]);
|
|
594
|
+
const fields = Object.fromEntries(descriptors.map(([name, descriptor]) => {
|
|
595
|
+
if (!permitted.has(name))
|
|
596
|
+
fail(`hook is invalid: ${name}`);
|
|
597
|
+
return [name, descriptor.value];
|
|
598
|
+
}));
|
|
599
|
+
for (const name of [
|
|
600
|
+
"recomputeComparisonStatistics",
|
|
601
|
+
"recomputeSummaryStatistics",
|
|
602
|
+
])
|
|
603
|
+
if (typeof fields[name] !== "function")
|
|
604
|
+
fail(`required hook is invalid: ${name}`);
|
|
605
|
+
if (fields.validateArtifact !== undefined && typeof fields.validateArtifact !== "function")
|
|
606
|
+
fail("hook is invalid: validateArtifact");
|
|
607
|
+
exact(fields.expectedSourceLock, ["sourceCommit", "sourceTreeDigest"], "expected source lock");
|
|
608
|
+
const source = fields.expectedSourceLock;
|
|
609
|
+
if (typeof source.sourceCommit !== "string" || !/^[0-9a-f]{40}$/u.test(source.sourceCommit))
|
|
610
|
+
fail("expected source commit is invalid");
|
|
611
|
+
digestValue(source.sourceTreeDigest, "expected sourceTreeDigest");
|
|
612
|
+
return Object.freeze({
|
|
613
|
+
expectedSourceLock: Object.freeze({
|
|
614
|
+
sourceCommit: source.sourceCommit,
|
|
615
|
+
sourceTreeDigest: source.sourceTreeDigest,
|
|
616
|
+
}),
|
|
617
|
+
...(fields.validateArtifact === undefined
|
|
618
|
+
? {}
|
|
619
|
+
: { validateArtifact: fields.validateArtifact }),
|
|
620
|
+
recomputeComparisonStatistics: fields.recomputeComparisonStatistics,
|
|
621
|
+
recomputeSummaryStatistics: fields.recomputeSummaryStatistics,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
function snapshotJson(value, label, budget = {
|
|
625
|
+
nodes: 0,
|
|
626
|
+
stringCodeUnits: 0,
|
|
627
|
+
}, depth = 0, active = new WeakSet()) {
|
|
628
|
+
if (depth > MAXIMUM_SNAPSHOT_DEPTH)
|
|
629
|
+
fail(`${label} exceeds snapshot depth`);
|
|
630
|
+
budget.nodes += 1;
|
|
631
|
+
if (budget.nodes > MAXIMUM_SNAPSHOT_NODES)
|
|
632
|
+
fail(`${label} exceeds snapshot node limit`);
|
|
633
|
+
if (value === null ||
|
|
634
|
+
typeof value === "boolean")
|
|
635
|
+
return value;
|
|
636
|
+
if (typeof value === "string") {
|
|
637
|
+
budget.stringCodeUnits += value.length;
|
|
638
|
+
if (budget.stringCodeUnits >
|
|
639
|
+
COLLECTIVE_STATISTICAL_CAMPAIGN_MAXIMUM_BUNDLE_BYTES_V1)
|
|
640
|
+
fail(`${label} exceeds snapshot string limit`);
|
|
641
|
+
return value;
|
|
642
|
+
}
|
|
643
|
+
if (typeof value === "number") {
|
|
644
|
+
if (!Number.isFinite(value) || Object.is(value, -0))
|
|
645
|
+
fail(`${label} is not canonical JSON`);
|
|
646
|
+
return value;
|
|
647
|
+
}
|
|
648
|
+
if (typeof value !== "object")
|
|
649
|
+
fail(`${label} is not JSON data`);
|
|
650
|
+
if (active.has(value))
|
|
651
|
+
fail(`${label} is cyclic`);
|
|
652
|
+
active.add(value);
|
|
653
|
+
try {
|
|
654
|
+
if (Array.isArray(value)) {
|
|
655
|
+
if (Object.getPrototypeOf(value) !== Array.prototype ||
|
|
656
|
+
Object.getOwnPropertySymbols(value).length !== 0)
|
|
657
|
+
fail(`${label} array is invalid`);
|
|
658
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
659
|
+
if (!lengthDescriptor || !("value" in lengthDescriptor))
|
|
660
|
+
fail(`${label} array length is invalid`);
|
|
661
|
+
const length = lengthDescriptor.value;
|
|
662
|
+
if (!Number.isSafeInteger(length) ||
|
|
663
|
+
length < 0 ||
|
|
664
|
+
length > MAXIMUM_SNAPSHOT_FIELDS)
|
|
665
|
+
fail(`${label} array exceeds snapshot item limit`);
|
|
666
|
+
const names = Object.getOwnPropertyNames(value);
|
|
667
|
+
if (names.length !== length + 1)
|
|
668
|
+
fail(`${label} array has holes or properties`);
|
|
669
|
+
const result = [];
|
|
670
|
+
for (let index = 0; index < length; index += 1) {
|
|
671
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
672
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor))
|
|
673
|
+
fail(`${label} array has accessor or hole`);
|
|
674
|
+
result.push(snapshotJson(descriptor.value, `${label}[${index}]`, budget, depth + 1, active));
|
|
675
|
+
}
|
|
676
|
+
return Object.freeze(result);
|
|
677
|
+
}
|
|
678
|
+
const descriptors = dataRecordDescriptors(value, label);
|
|
679
|
+
const result = Object.create(null);
|
|
680
|
+
for (const [name, descriptor] of descriptors)
|
|
681
|
+
result[name] = snapshotJson(descriptor.value, `${label}.${name}`, budget, depth + 1, active);
|
|
682
|
+
return Object.freeze(result);
|
|
683
|
+
}
|
|
684
|
+
finally {
|
|
685
|
+
active.delete(value);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function dataRecordDescriptors(value, label, maximumFields = MAXIMUM_SNAPSHOT_FIELDS) {
|
|
689
|
+
if (value === null ||
|
|
690
|
+
typeof value !== "object" ||
|
|
691
|
+
Array.isArray(value) ||
|
|
692
|
+
(Object.getPrototypeOf(value) !== Object.prototype &&
|
|
693
|
+
Object.getPrototypeOf(value) !== null) ||
|
|
694
|
+
Object.getOwnPropertySymbols(value).length !== 0)
|
|
695
|
+
fail(`${label} must be a plain data object`);
|
|
696
|
+
const names = Object.getOwnPropertyNames(value);
|
|
697
|
+
if (names.length > maximumFields)
|
|
698
|
+
fail(`${label} exceeds snapshot field limit`);
|
|
699
|
+
return Object.freeze(names.map((name) => {
|
|
700
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, name);
|
|
701
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor))
|
|
702
|
+
fail(`${label} must not have accessors`);
|
|
703
|
+
return Object.freeze([name, descriptor]);
|
|
704
|
+
}));
|
|
705
|
+
}
|
|
706
|
+
function parseJson(bytes, artifactId) {
|
|
707
|
+
try {
|
|
708
|
+
return snapshotJson(JSON.parse(decoder.decode(bytes)), `artifact ${artifactId}`);
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
if (error instanceof TypeError && error.message.startsWith("collective_statistical"))
|
|
712
|
+
throw error;
|
|
713
|
+
fail(`artifact is not valid UTF-8 JSON: ${artifactId}`);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
async function sha256(bytes) {
|
|
717
|
+
const result = await crypto.subtle.digest("SHA-256", bytes);
|
|
718
|
+
return [...new Uint8Array(result)]
|
|
719
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
720
|
+
.join("");
|
|
721
|
+
}
|
|
722
|
+
function digestArtifact(value) {
|
|
723
|
+
const snapshot = snapshotJson(value, "artifact digest input");
|
|
724
|
+
return digestPlanningJsonV1("evaluation-campaign-artifact-v1", snapshot, ARTIFACT_JSON_LIMITS);
|
|
725
|
+
}
|
|
726
|
+
function exact(value, keys, label) {
|
|
727
|
+
const descriptors = dataRecordDescriptors(value, label);
|
|
728
|
+
const names = descriptors.map(([name]) => name).sort(compareAscii);
|
|
729
|
+
const expected = [...keys].sort(compareAscii);
|
|
730
|
+
if (names.length !== expected.length ||
|
|
731
|
+
names.some((name, index) => name !== expected[index]))
|
|
732
|
+
fail(`${label} has an invalid shape`);
|
|
733
|
+
if (descriptors.some(([, descriptor]) => descriptor.value === undefined))
|
|
734
|
+
fail(`${label} has undefined property`);
|
|
735
|
+
}
|
|
736
|
+
function dense(value, label, minimum, maximum) {
|
|
737
|
+
if (!Array.isArray(value) ||
|
|
738
|
+
Object.getPrototypeOf(value) !== Array.prototype ||
|
|
739
|
+
Object.getOwnPropertySymbols(value).length !== 0 ||
|
|
740
|
+
value.length < minimum ||
|
|
741
|
+
value.length > maximum ||
|
|
742
|
+
Object.getOwnPropertyNames(value).length !== value.length + 1)
|
|
743
|
+
fail(`${label} is invalid`);
|
|
744
|
+
const result = [];
|
|
745
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
746
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
747
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor))
|
|
748
|
+
fail(`${label} has accessor or hole`);
|
|
749
|
+
result.push(descriptor.value);
|
|
750
|
+
}
|
|
751
|
+
return result;
|
|
752
|
+
}
|
|
753
|
+
function safeJson(value) {
|
|
754
|
+
canonicalizePlanningJsonV1(value, ARTIFACT_JSON_LIMITS);
|
|
755
|
+
}
|
|
756
|
+
function assertGlobalArtifact(artifactId, kind, expected) {
|
|
757
|
+
const value = expected.get(artifactId);
|
|
758
|
+
if (!value || value.kind !== kind || !isGlobalScope(value))
|
|
759
|
+
fail(`${kind} expectation is invalid`);
|
|
760
|
+
}
|
|
761
|
+
function assertExpectedArtifactScope(value) {
|
|
762
|
+
const execution = ["sample", "trace", "ledger", "evidence"].includes(value.kind);
|
|
763
|
+
const cellOnly = value.kind === "comparison";
|
|
764
|
+
if (execution) {
|
|
765
|
+
if (value.cellId === null ||
|
|
766
|
+
value.seed === null ||
|
|
767
|
+
value.runner === null ||
|
|
768
|
+
value.attempt === null)
|
|
769
|
+
fail(`execution artifact expectation is incomplete: ${value.artifactId}`);
|
|
770
|
+
}
|
|
771
|
+
else if (cellOnly) {
|
|
772
|
+
if (value.cellId === null ||
|
|
773
|
+
value.seed !== null ||
|
|
774
|
+
value.runner !== null ||
|
|
775
|
+
value.attempt !== null)
|
|
776
|
+
fail(`cell artifact expectation is invalid: ${value.artifactId}`);
|
|
777
|
+
}
|
|
778
|
+
else if (!isGlobalScope(value)) {
|
|
779
|
+
fail(`global artifact expectation is invalid: ${value.artifactId}`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function isGlobalScope(value) {
|
|
783
|
+
return value.cellId === null && value.seed === null && value.runner === null && value.attempt === null;
|
|
784
|
+
}
|
|
785
|
+
function sameExecutionScope(value, sample, kind) {
|
|
786
|
+
return (value.kind === kind &&
|
|
787
|
+
value.cellId === sample.cellId &&
|
|
788
|
+
value.seed === sample.seed &&
|
|
789
|
+
value.runner === sample.runner &&
|
|
790
|
+
value.attempt === sample.attempt);
|
|
791
|
+
}
|
|
792
|
+
function findSampleExpectation(expected, cellId, seed, runnerValue, attemptValue) {
|
|
793
|
+
return [...expected.values()].find((item) => item.kind === "sample" &&
|
|
794
|
+
item.cellId === cellId &&
|
|
795
|
+
item.seed === seed &&
|
|
796
|
+
item.runner === runnerValue &&
|
|
797
|
+
item.attempt === attemptValue);
|
|
798
|
+
}
|
|
799
|
+
function singleArtifactOfKind(indexed, kind) {
|
|
800
|
+
const values = [...indexed.values()].filter((value) => value.kind === kind);
|
|
801
|
+
if (values.length !== 1)
|
|
802
|
+
fail(`bundle must contain exactly one ${kind} artifact`);
|
|
803
|
+
return values[0];
|
|
804
|
+
}
|
|
805
|
+
function token(value, label) {
|
|
806
|
+
if (typeof value !== "string" ||
|
|
807
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:@/-]*$/u.test(value))
|
|
808
|
+
fail(`${label} is invalid`);
|
|
809
|
+
}
|
|
810
|
+
function nullableToken(value, label) {
|
|
811
|
+
if (value !== null)
|
|
812
|
+
token(value, label);
|
|
813
|
+
}
|
|
814
|
+
function integer(value, label, minimum) {
|
|
815
|
+
if (!Number.isSafeInteger(value) || value < minimum)
|
|
816
|
+
fail(`${label} is invalid`);
|
|
817
|
+
}
|
|
818
|
+
function nullableInteger(value, label) {
|
|
819
|
+
if (value !== null)
|
|
820
|
+
integer(value, label, 0);
|
|
821
|
+
}
|
|
822
|
+
function strings(value, label, minimum) {
|
|
823
|
+
return dense(value, label, minimum, 256).map((item) => {
|
|
824
|
+
token(item, label);
|
|
825
|
+
return item;
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
function runner(value) {
|
|
829
|
+
if (value !== "adaptive_collective" && value !== "centralized_planner")
|
|
830
|
+
fail("runner is invalid");
|
|
831
|
+
}
|
|
832
|
+
function nullableRunner(value) {
|
|
833
|
+
if (value !== null)
|
|
834
|
+
runner(value);
|
|
835
|
+
}
|
|
836
|
+
function attempt(value) {
|
|
837
|
+
if (value !== "first" && value !== "replay")
|
|
838
|
+
fail("attempt is invalid");
|
|
839
|
+
}
|
|
840
|
+
function nullableAttempt(value) {
|
|
841
|
+
if (value !== null)
|
|
842
|
+
attempt(value);
|
|
843
|
+
}
|
|
844
|
+
function kindValue(value) {
|
|
845
|
+
if (typeof value !== "string" ||
|
|
846
|
+
!artifactKinds.has(value))
|
|
847
|
+
fail("artifact kind is invalid");
|
|
848
|
+
}
|
|
849
|
+
function digestValue(value, label) {
|
|
850
|
+
if (typeof value !== "string" || !digestPattern.test(value))
|
|
851
|
+
fail(`${label} is invalid`);
|
|
852
|
+
}
|
|
853
|
+
function hex(value, label) {
|
|
854
|
+
if (typeof value !== "string" || !hexPattern.test(value))
|
|
855
|
+
fail(`${label} is invalid`);
|
|
856
|
+
}
|
|
857
|
+
function safePath(value) {
|
|
858
|
+
if (typeof value !== "string" ||
|
|
859
|
+
value.length === 0 ||
|
|
860
|
+
value.startsWith("/") ||
|
|
861
|
+
value.includes("\\") ||
|
|
862
|
+
value.split("/").some((part) => part === "" || part === "." || part === ".."))
|
|
863
|
+
fail("artifact path is not a safe relative path");
|
|
864
|
+
}
|
|
865
|
+
function unique(values, label) {
|
|
866
|
+
if (new Set(values).size !== values.length)
|
|
867
|
+
fail(`${label} are not unique`);
|
|
868
|
+
}
|
|
869
|
+
function sameStrings(actual, expected) {
|
|
870
|
+
return actual.length === expected.length && actual.every((value, index) => value === expected[index]);
|
|
871
|
+
}
|
|
872
|
+
function sameJson(left, right) {
|
|
873
|
+
return (canonicalizePlanningJsonV1(left, ARTIFACT_JSON_LIMITS) ===
|
|
874
|
+
canonicalizePlanningJsonV1(right, ARTIFACT_JSON_LIMITS));
|
|
875
|
+
}
|
|
876
|
+
function compareAscii(left, right) {
|
|
877
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
878
|
+
}
|
|
879
|
+
function errorMessage(error) {
|
|
880
|
+
return error instanceof Error ? error.message : "unknown error";
|
|
881
|
+
}
|
|
882
|
+
function fail(message) {
|
|
883
|
+
throw new TypeError(`collective_statistical_campaign_bundle_invalid: ${message}`);
|
|
884
|
+
}
|
|
885
|
+
//# sourceMappingURL=collective-statistical-campaign-bundle.js.map
|