assertledger 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +31 -0
- package/LICENSE +21 -0
- package/README.fr.md +236 -0
- package/README.md +224 -0
- package/SECURITY.md +51 -0
- package/benchmarks/agentic-profile/README.md +15 -0
- package/benchmarks/agentic-profile/public/README.md +5 -0
- package/benchmarks/self-hosted-core/README.md +113 -0
- package/benchmarks/self-hosted-core/adapter.mjs +293 -0
- package/benchmarks/self-hosted-core/builder.ts +193 -0
- package/benchmarks/self-hosted-core/campaign.ts +233 -0
- package/benchmarks/self-hosted-core/existing-tests-builder.ts +217 -0
- package/benchmarks/self-hosted-core/existing-tests.ts +146 -0
- package/benchmarks/self-hosted-core/liveness.test.mjs +8 -0
- package/conformance/v1/bundle.json +104 -0
- package/conformance/v1/expected/canonical-order-a.json +4 -0
- package/conformance/v1/expected/canonical-order-b.json +4 -0
- package/conformance/v1/expected/create-benchmark-v1-measured.json +575 -0
- package/conformance/v1/expected/create-profile-v1-qualified.json +280 -0
- package/conformance/v1/expected/decide-collection-failure-non-kill.json +192 -0
- package/conformance/v1/expected/decide-compile-failure-non-kill.json +192 -0
- package/conformance/v1/expected/decide-infra-error-non-kill.json +192 -0
- package/conformance/v1/expected/decide-no-test-discovered-non-kill.json +192 -0
- package/conformance/v1/expected/decide-process-crash-non-kill.json +192 -0
- package/conformance/v1/expected/decide-timeout-non-kill.json +192 -0
- package/conformance/v1/expected/decide-verified.json +192 -0
- package/conformance/v1/expected/replay-benchmark-v1-resealed-summary-forgery.json +12 -0
- package/conformance/v1/expected/replay-evidence-raw-tamper.json +6 -0
- package/conformance/v1/expected/replay-evidence-resealed-semantic-forgery.json +6 -0
- package/conformance/v1/inputs/canonical-order-a.json +8 -0
- package/conformance/v1/inputs/canonical-order-b.json +8 -0
- package/conformance/v1/inputs/create-benchmark-v1-measured.json +459 -0
- package/conformance/v1/inputs/create-profile-v1-qualified.json +228 -0
- package/conformance/v1/inputs/decide-collection-failure-non-kill.json +143 -0
- package/conformance/v1/inputs/decide-compile-failure-non-kill.json +143 -0
- package/conformance/v1/inputs/decide-infra-error-non-kill.json +143 -0
- package/conformance/v1/inputs/decide-no-test-discovered-non-kill.json +143 -0
- package/conformance/v1/inputs/decide-process-crash-non-kill.json +143 -0
- package/conformance/v1/inputs/decide-timeout-non-kill.json +143 -0
- package/conformance/v1/inputs/decide-verified.json +143 -0
- package/conformance/v1/inputs/replay-benchmark-v1-resealed-summary-forgery.json +575 -0
- package/conformance/v1/inputs/replay-evidence-raw-tamper.json +201 -0
- package/conformance/v1/inputs/replay-evidence-resealed-semantic-forgery.json +192 -0
- package/conformance/v1/schemas/expected-digests.json +175 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +951 -0
- package/dist/cli.js.map +1 -0
- package/dist/contracts/diagnostics.d.ts +18 -0
- package/dist/contracts/diagnostics.d.ts.map +1 -0
- package/dist/contracts/diagnostics.js +13 -0
- package/dist/contracts/diagnostics.js.map +1 -0
- package/dist/contracts/index.d.ts +3908 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +2569 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/runtime-doctor.d.ts +107 -0
- package/dist/contracts/runtime-doctor.d.ts.map +1 -0
- package/dist/contracts/runtime-doctor.js +91 -0
- package/dist/contracts/runtime-doctor.js.map +1 -0
- package/dist/core/index.d.ts +200 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +2587 -0
- package/dist/core/index.js.map +1 -0
- package/dist/diagnostics.d.ts +7 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +252 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/engine/adapters/node-test-profile.d.ts +14 -0
- package/dist/engine/adapters/node-test-profile.d.ts.map +1 -0
- package/dist/engine/adapters/node-test-profile.js +14 -0
- package/dist/engine/adapters/node-test-profile.js.map +1 -0
- package/dist/engine/adapters/node-test-runtime.d.ts +39 -0
- package/dist/engine/adapters/node-test-runtime.d.ts.map +1 -0
- package/dist/engine/adapters/node-test-runtime.js +173 -0
- package/dist/engine/adapters/node-test-runtime.js.map +1 -0
- package/dist/engine/adapters/runtime-facts.d.ts +26 -0
- package/dist/engine/adapters/runtime-facts.d.ts.map +1 -0
- package/dist/engine/adapters/runtime-facts.js +73 -0
- package/dist/engine/adapters/runtime-facts.js.map +1 -0
- package/dist/engine/connection.d.ts +22 -0
- package/dist/engine/connection.d.ts.map +1 -0
- package/dist/engine/connection.js +343 -0
- package/dist/engine/connection.js.map +1 -0
- package/dist/engine/git-regression.d.ts +25 -0
- package/dist/engine/git-regression.d.ts.map +1 -0
- package/dist/engine/git-regression.js +803 -0
- package/dist/engine/git-regression.js.map +1 -0
- package/dist/engine/index.d.ts +55 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +2782 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/node-test-reporter.d.ts +2 -0
- package/dist/engine/node-test-reporter.d.ts.map +1 -0
- package/dist/engine/node-test-reporter.js +70 -0
- package/dist/engine/node-test-reporter.js.map +1 -0
- package/dist/engine/runtime-doctor.d.ts +16 -0
- package/dist/engine/runtime-doctor.d.ts.map +1 -0
- package/dist/engine/runtime-doctor.js +100 -0
- package/dist/engine/runtime-doctor.js.map +1 -0
- package/dist/evaluation/agentic-corpus.d.ts +161 -0
- package/dist/evaluation/agentic-corpus.d.ts.map +1 -0
- package/dist/evaluation/agentic-corpus.js +710 -0
- package/dist/evaluation/agentic-corpus.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +391 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/stdio.d.ts +3 -0
- package/dist/mcp/stdio.d.ts.map +1 -0
- package/dist/mcp/stdio.js +13 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/sdk/index.d.ts +52 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +224 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/docs/adapter-protocol.md +196 -0
- package/docs/agentic-benchmark.md +118 -0
- package/docs/agentic-corpus-experiment-h3.md +89 -0
- package/docs/agentic-corpus-plan.md +105 -0
- package/docs/agentic-corpus-provenance.md +59 -0
- package/docs/agentic-test-profile-pilot.md +57 -0
- package/docs/agentic-test-profile-v2.md +116 -0
- package/docs/agentic-test-profile.md +274 -0
- package/docs/architecture.md +157 -0
- package/docs/ci.md +37 -0
- package/docs/client-connections.md +61 -0
- package/docs/conformance-v1.md +72 -0
- package/docs/decisions/0001-typescript-runtime.md +24 -0
- package/docs/developer-experience.md +55 -0
- package/docs/diagnostics.md +35 -0
- package/docs/distribution.md +40 -0
- package/docs/git-regression.md +39 -0
- package/docs/migration-repository-validation-order.md +35 -0
- package/docs/migration-testforge-to-assertledger.md +64 -0
- package/docs/project-intent.md +173 -0
- package/docs/proof-model.md +116 -0
- package/docs/reference.md +336 -0
- package/docs/release-1.0.md +63 -0
- package/docs/repository-audit.md +52 -0
- package/docs/repository-init.md +60 -0
- package/docs/research-basis.md +27 -0
- package/docs/roadmap.md +74 -0
- package/docs/runtime-doctor.md +65 -0
- package/docs/testexplora-calibration.md +71 -0
- package/examples/agentic-benchmark/benchmark-request.mjs +19 -0
- package/examples/agentic-benchmark/structured-phase-adapter-fixture.mjs +35 -0
- package/examples/agentic-profile/profile-benchmark.mjs +34 -0
- package/examples/agentic-profile/profile-manifest.mjs +28 -0
- package/examples/git-history/README.md +44 -0
- package/examples/git-history/create-demo.mjs +128 -0
- package/examples/git-history/escape-string-regexp/LICENSE +9 -0
- package/examples/git-history/escape-string-regexp/before.cjs.txt +11 -0
- package/examples/git-history/escape-string-regexp/fixed.cjs.txt +13 -0
- package/examples/git-history/escape-string-regexp/provenance.json +28 -0
- package/examples/node-test/repository/package.json +5 -0
- package/examples/node-test/repository/src/is-even.js +3 -0
- package/examples/node-test/repository/tests/base.test.js +6 -0
- package/examples/node-test/request.json +93 -0
- package/integrations/skill/SKILL.md +51 -0
- package/package.json +88 -0
- package/schemas/agentic-benchmark-acquisition-replay-result.v1.json +70 -0
- package/schemas/agentic-benchmark-acquisition-request.v1.json +564 -0
- package/schemas/agentic-benchmark-acquisition-result.v1.json +1409 -0
- package/schemas/agentic-benchmark-artifact.v1.json +1251 -0
- package/schemas/agentic-benchmark-replay-result.v1.json +84 -0
- package/schemas/agentic-benchmark-request.v1.json +1034 -0
- package/schemas/agentic-corpus-allocation-commitment-replay-result.v1.json +58 -0
- package/schemas/agentic-corpus-allocation-commitment.v1.json +141 -0
- package/schemas/agentic-corpus-allocation-replay-result.v1.json +34 -0
- package/schemas/agentic-corpus-allocation-request.v1.json +65 -0
- package/schemas/agentic-corpus-allocation-reveal.v1.json +66 -0
- package/schemas/agentic-corpus-allocation.v1.json +167 -0
- package/schemas/agentic-corpus-experiment-artifact.v1.json +329 -0
- package/schemas/agentic-corpus-experiment-plan-replay-result.v1.json +50 -0
- package/schemas/agentic-corpus-experiment-plan.v1.json +424 -0
- package/schemas/agentic-corpus-experiment-replay-request.v1.json +336 -0
- package/schemas/agentic-corpus-experiment-replay-result.v1.json +106 -0
- package/schemas/agentic-corpus-experiment-request.v1.json +204 -0
- package/schemas/agentic-corpus-provenance.v1.json +143 -0
- package/schemas/agentic-corpus-trust-policy.v1.json +133 -0
- package/schemas/agentic-profile-replay-result.v1.json +56 -0
- package/schemas/agentic-profile-replay-result.v2.json +63 -0
- package/schemas/agentic-profile-report.v1.json +961 -0
- package/schemas/agentic-profile-report.v2.json +1674 -0
- package/schemas/agentic-profile-request.v1.json +671 -0
- package/schemas/agentic-profile-request.v2.json +1338 -0
- package/schemas/evidence-manifest.v1.json +636 -0
- package/schemas/replay-result.v1.json +49 -0
- package/schemas/repository-analysis.v1.json +119 -0
- package/schemas/repository-audit.v1.json +811 -0
- package/schemas/repository-init-config.v1.json +183 -0
- package/schemas/repository-init-lock.v1.json +162 -0
- package/schemas/repository-init-result.v1.json +212 -0
- package/schemas/verification-request.v1.json +389 -0
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
import { createHash, createPublicKey, verify as verifySignature } from "node:crypto";
|
|
2
|
+
import { lstat, readdir, readFile, realpath } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parseAgenticCorpusProvenance, parseAgenticCorpusTrustPolicy, } from "../contracts/index.js";
|
|
5
|
+
import { agenticCorpusAllocationCommitmentSigningBytes, canonicalize, portablePathKey, sha256Canonical, } from "../core/index.js";
|
|
6
|
+
const SCHEMA_VERSION = "1.0.0";
|
|
7
|
+
const CASE_SUFFIX = ".case.json";
|
|
8
|
+
const PORTABLE_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
9
|
+
const SHA256_DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
10
|
+
const HYPOTHESES = ["H1", "H2", "H3", "H4"];
|
|
11
|
+
export class AgenticCorpusError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
constructor(code, options) {
|
|
14
|
+
super(code, options);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.name = "AgenticCorpusError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function invalid(code = "AGENTIC_CORPUS_CASE_INVALID") {
|
|
20
|
+
throw new AgenticCorpusError(code);
|
|
21
|
+
}
|
|
22
|
+
function record(value) {
|
|
23
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
24
|
+
return invalid();
|
|
25
|
+
const prototype = Object.getPrototypeOf(value);
|
|
26
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
27
|
+
return invalid();
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function exactKeys(value, required, optional = []) {
|
|
31
|
+
const allowed = new Set([...required, ...optional]);
|
|
32
|
+
if (required.some((key) => !Object.hasOwn(value, key)))
|
|
33
|
+
invalid();
|
|
34
|
+
if (Object.keys(value).some((key) => !allowed.has(key)))
|
|
35
|
+
invalid();
|
|
36
|
+
}
|
|
37
|
+
function portableIdentifier(value) {
|
|
38
|
+
if (typeof value !== "string" || !PORTABLE_IDENTIFIER.test(value))
|
|
39
|
+
return invalid();
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
function digest(value) {
|
|
43
|
+
if (typeof value !== "string" || !SHA256_DIGEST.test(value))
|
|
44
|
+
return invalid();
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
export function agenticCorpusTrustPolicyDigest(policy) {
|
|
48
|
+
return sha256Canonical(policy);
|
|
49
|
+
}
|
|
50
|
+
export function agenticCorpusProvenanceProjection(value) {
|
|
51
|
+
return {
|
|
52
|
+
schemaVersion: value.schemaVersion,
|
|
53
|
+
domain: value.domain,
|
|
54
|
+
trustPolicyDigest: value.trustPolicyDigest,
|
|
55
|
+
caseId: value.caseId,
|
|
56
|
+
caseDigest: value.caseDigest,
|
|
57
|
+
source: value.source,
|
|
58
|
+
execution: value.execution,
|
|
59
|
+
authorKeyId: value.author.keyId,
|
|
60
|
+
reviewerKeyId: value.reviewer.keyId,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export function agenticCorpusProvenanceSigningBytes(value) {
|
|
64
|
+
return Buffer.from(`TESTFORGE_AGENTIC_CORPUS_PROVENANCE_V1\0${canonicalize(agenticCorpusProvenanceProjection(value))}`, "utf8");
|
|
65
|
+
}
|
|
66
|
+
export function agenticCorpusProvenanceDigest(value) {
|
|
67
|
+
return sha256Canonical({
|
|
68
|
+
projection: agenticCorpusProvenanceProjection(value),
|
|
69
|
+
authorSignature: value.author.signature,
|
|
70
|
+
reviewerSignature: value.reviewer.signature,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function strictBase64Url(value) {
|
|
74
|
+
try {
|
|
75
|
+
const decoded = Buffer.from(value, "base64url");
|
|
76
|
+
return decoded.length > 0 && decoded.toString("base64url") === value ? decoded : null;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function trustedKey(policy, keyId) {
|
|
83
|
+
const entry = policy.keys.find((key) => key.keyId === keyId);
|
|
84
|
+
if (entry === undefined)
|
|
85
|
+
return null;
|
|
86
|
+
const der = strictBase64Url(entry.publicKey.data);
|
|
87
|
+
if (der === null)
|
|
88
|
+
return null;
|
|
89
|
+
try {
|
|
90
|
+
const key = createPublicKey({ key: der, format: "der", type: "spki" });
|
|
91
|
+
const canonicalDer = key.export({ format: "der", type: "spki" });
|
|
92
|
+
if (!Buffer.isBuffer(canonicalDer) || !canonicalDer.equals(der))
|
|
93
|
+
return null;
|
|
94
|
+
const derived = `sha256:${createHash("sha256").update(canonicalDer).digest("hex")}`;
|
|
95
|
+
if (key.asymmetricKeyType !== "ed25519" || derived !== entry.keyId)
|
|
96
|
+
return null;
|
|
97
|
+
return { entry, key, canonicalKeyFingerprint: derived };
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function allPolicyKeysValid(policy) {
|
|
104
|
+
return policy.keys.every((entry) => trustedKey(policy, entry.keyId) !== null);
|
|
105
|
+
}
|
|
106
|
+
export function verifyAgenticCorpusAllocationCommitmentSignatures(commitment, policy, expectedPolicyDigest) {
|
|
107
|
+
const authorSigner = commitment.signers.find((item) => item.role === "AUTHOR");
|
|
108
|
+
const reviewerSigner = commitment.signers.find((item) => item.role === "REVIEWER");
|
|
109
|
+
const author = authorSigner === undefined ? null : trustedKey(policy, authorSigner.keyId);
|
|
110
|
+
const reviewer = reviewerSigner === undefined ? null : trustedKey(policy, reviewerSigner.keyId);
|
|
111
|
+
const principalsDistinct = author !== null &&
|
|
112
|
+
reviewer !== null &&
|
|
113
|
+
author.entry.keyId !== reviewer.entry.keyId &&
|
|
114
|
+
author.entry.subjectId !== reviewer.entry.subjectId &&
|
|
115
|
+
author.canonicalKeyFingerprint !== reviewer.canonicalKeyFingerprint;
|
|
116
|
+
const sourcePolicies = commitment.caseSet.map((subject) => policy.sources.find((source) => source.sourceId === subject.sourceId &&
|
|
117
|
+
source.sourceIdentityDigest === subject.sourceIdentityDigest));
|
|
118
|
+
const authorTrusted = author?.entry.roles.includes("AUTHOR") === true &&
|
|
119
|
+
sourcePolicies.every((source) => source?.authorizedAuthorSubjectIds.includes(author.entry.subjectId) === true);
|
|
120
|
+
const reviewerTrusted = reviewer?.entry.roles.includes("REVIEWER") === true &&
|
|
121
|
+
sourcePolicies.every((source) => source?.authorizedReviewerSubjectIds.includes(reviewer.entry.subjectId) === true);
|
|
122
|
+
const bytes = agenticCorpusAllocationCommitmentSigningBytes(commitment);
|
|
123
|
+
const authorSignature = authorSigner === undefined ? null : strictBase64Url(authorSigner.signature);
|
|
124
|
+
const reviewerSignature = reviewerSigner === undefined ? null : strictBase64Url(reviewerSigner.signature);
|
|
125
|
+
const policyValid = policy.policyDigest === expectedPolicyDigest &&
|
|
126
|
+
policy.policyDigest ===
|
|
127
|
+
agenticCorpusTrustPolicyDigest({
|
|
128
|
+
schemaVersion: policy.schemaVersion,
|
|
129
|
+
policyId: policy.policyId,
|
|
130
|
+
keys: policy.keys,
|
|
131
|
+
sources: policy.sources,
|
|
132
|
+
}) &&
|
|
133
|
+
allPolicyKeysValid(policy);
|
|
134
|
+
return {
|
|
135
|
+
principalsDistinct,
|
|
136
|
+
valid: policyValid &&
|
|
137
|
+
principalsDistinct &&
|
|
138
|
+
authorTrusted &&
|
|
139
|
+
reviewerTrusted &&
|
|
140
|
+
author !== null &&
|
|
141
|
+
reviewer !== null &&
|
|
142
|
+
authorSignature !== null &&
|
|
143
|
+
reviewerSignature !== null &&
|
|
144
|
+
verifySignature(null, bytes, author.key, authorSignature) &&
|
|
145
|
+
verifySignature(null, bytes, reviewer.key, reviewerSignature),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export function replayAgenticCorpusProvenance(caseValue, sidecarValue, policyValue, expectedPolicyDigest) {
|
|
149
|
+
const rails = {
|
|
150
|
+
schemaValid: false,
|
|
151
|
+
canonicalCaseDigestValid: false,
|
|
152
|
+
policyDigestValid: false,
|
|
153
|
+
sourceRegistered: false,
|
|
154
|
+
sourceBindingValid: false,
|
|
155
|
+
executionReceiptDigestValid: false,
|
|
156
|
+
authorTrusted: false,
|
|
157
|
+
reviewerTrusted: false,
|
|
158
|
+
principalsDistinct: false,
|
|
159
|
+
authorSignatureValid: false,
|
|
160
|
+
reviewerSignatureValid: false,
|
|
161
|
+
provenanceDigestValid: false,
|
|
162
|
+
};
|
|
163
|
+
let corpusCase;
|
|
164
|
+
let sidecar;
|
|
165
|
+
let policy;
|
|
166
|
+
try {
|
|
167
|
+
corpusCase = parseAgenticCorpusCase(caseValue);
|
|
168
|
+
sidecar = parseAgenticCorpusProvenance(sidecarValue);
|
|
169
|
+
policy = parseAgenticCorpusTrustPolicy(policyValue);
|
|
170
|
+
rails.schemaValid = true;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return { valid: false, ...rails };
|
|
174
|
+
}
|
|
175
|
+
rails.canonicalCaseDigestValid = sidecar.caseDigest === sha256Canonical(corpusCase);
|
|
176
|
+
rails.policyDigestValid =
|
|
177
|
+
SHA256_DIGEST.test(expectedPolicyDigest) &&
|
|
178
|
+
policy.policyDigest === expectedPolicyDigest &&
|
|
179
|
+
policy.policyDigest ===
|
|
180
|
+
agenticCorpusTrustPolicyDigest({
|
|
181
|
+
schemaVersion: policy.schemaVersion,
|
|
182
|
+
policyId: policy.policyId,
|
|
183
|
+
keys: policy.keys,
|
|
184
|
+
sources: policy.sources,
|
|
185
|
+
}) &&
|
|
186
|
+
allPolicyKeysValid(policy);
|
|
187
|
+
const source = policy.sources.find((entry) => entry.sourceId === sidecar.source.sourceId);
|
|
188
|
+
rails.sourceRegistered = source !== undefined;
|
|
189
|
+
rails.sourceBindingValid =
|
|
190
|
+
source !== undefined &&
|
|
191
|
+
sidecar.trustPolicyDigest === expectedPolicyDigest &&
|
|
192
|
+
sidecar.caseId === corpusCase.caseId &&
|
|
193
|
+
sidecar.source.sourceId === corpusCase.sourceId &&
|
|
194
|
+
sidecar.source.sourceRevision === corpusCase.sourceRevision &&
|
|
195
|
+
sidecar.source.sourceIdentityDigest === source.sourceIdentityDigest;
|
|
196
|
+
rails.executionReceiptDigestValid =
|
|
197
|
+
sidecar.execution.receiptDigest ===
|
|
198
|
+
sha256Canonical({
|
|
199
|
+
protocolId: sidecar.execution.protocolId,
|
|
200
|
+
evidenceDigests: sidecar.execution.evidenceDigests,
|
|
201
|
+
});
|
|
202
|
+
const author = trustedKey(policy, sidecar.author.keyId);
|
|
203
|
+
const reviewer = trustedKey(policy, sidecar.reviewer.keyId);
|
|
204
|
+
rails.authorTrusted =
|
|
205
|
+
author?.entry.roles.includes("AUTHOR") === true &&
|
|
206
|
+
source?.authorizedAuthorSubjectIds.includes(author.entry.subjectId) === true;
|
|
207
|
+
rails.reviewerTrusted =
|
|
208
|
+
reviewer?.entry.roles.includes("REVIEWER") === true &&
|
|
209
|
+
source?.authorizedReviewerSubjectIds.includes(reviewer.entry.subjectId) === true;
|
|
210
|
+
rails.principalsDistinct =
|
|
211
|
+
author !== null &&
|
|
212
|
+
reviewer !== null &&
|
|
213
|
+
author.entry.keyId !== reviewer.entry.keyId &&
|
|
214
|
+
author.entry.subjectId !== reviewer.entry.subjectId &&
|
|
215
|
+
author.canonicalKeyFingerprint !== reviewer.canonicalKeyFingerprint;
|
|
216
|
+
const bytes = agenticCorpusProvenanceSigningBytes(sidecar);
|
|
217
|
+
const authorSignature = strictBase64Url(sidecar.author.signature);
|
|
218
|
+
const reviewerSignature = strictBase64Url(sidecar.reviewer.signature);
|
|
219
|
+
rails.authorSignatureValid =
|
|
220
|
+
author !== null &&
|
|
221
|
+
authorSignature !== null &&
|
|
222
|
+
verifySignature(null, bytes, author.key, authorSignature);
|
|
223
|
+
rails.reviewerSignatureValid =
|
|
224
|
+
reviewer !== null &&
|
|
225
|
+
reviewerSignature !== null &&
|
|
226
|
+
verifySignature(null, bytes, reviewer.key, reviewerSignature);
|
|
227
|
+
rails.provenanceDigestValid = sidecar.provenanceDigest === agenticCorpusProvenanceDigest(sidecar);
|
|
228
|
+
return { valid: Object.values(rails).every(Boolean), ...rails };
|
|
229
|
+
}
|
|
230
|
+
function boolean(value) {
|
|
231
|
+
if (typeof value !== "boolean")
|
|
232
|
+
return invalid();
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
235
|
+
function count(value) {
|
|
236
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
237
|
+
return invalid();
|
|
238
|
+
return value;
|
|
239
|
+
}
|
|
240
|
+
function fraction(detected, total) {
|
|
241
|
+
const validDetected = count(detected);
|
|
242
|
+
const validTotal = count(total);
|
|
243
|
+
if (validTotal === 0 || validDetected > validTotal)
|
|
244
|
+
return invalid();
|
|
245
|
+
return [validDetected, validTotal];
|
|
246
|
+
}
|
|
247
|
+
function parseH1(value) {
|
|
248
|
+
const item = record(value);
|
|
249
|
+
exactKeys(item, ["equalTargetStrength", "baselineWarmP95Us", "profileWarmP95Us"]);
|
|
250
|
+
return {
|
|
251
|
+
equalTargetStrength: boolean(item.equalTargetStrength),
|
|
252
|
+
baselineWarmP95Us: count(item.baselineWarmP95Us),
|
|
253
|
+
profileWarmP95Us: count(item.profileWarmP95Us),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function parseH2(value) {
|
|
257
|
+
const item = record(value);
|
|
258
|
+
exactKeys(item, [
|
|
259
|
+
"baselineNeutralSurvived",
|
|
260
|
+
"baselineNeutralTotal",
|
|
261
|
+
"profileNeutralSurvived",
|
|
262
|
+
"profileNeutralTotal",
|
|
263
|
+
]);
|
|
264
|
+
const [baselineNeutralSurvived, baselineNeutralTotal] = fraction(item.baselineNeutralSurvived, item.baselineNeutralTotal);
|
|
265
|
+
const [profileNeutralSurvived, profileNeutralTotal] = fraction(item.profileNeutralSurvived, item.profileNeutralTotal);
|
|
266
|
+
return {
|
|
267
|
+
baselineNeutralSurvived,
|
|
268
|
+
baselineNeutralTotal,
|
|
269
|
+
profileNeutralSurvived,
|
|
270
|
+
profileNeutralTotal,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function parseH3(value) {
|
|
274
|
+
const item = record(value);
|
|
275
|
+
exactKeys(item, [
|
|
276
|
+
"equalBudget",
|
|
277
|
+
"baselineHeldOutFaultsDetected",
|
|
278
|
+
"baselineHeldOutFaultsTotal",
|
|
279
|
+
"profileHeldOutFaultsDetected",
|
|
280
|
+
"profileHeldOutFaultsTotal",
|
|
281
|
+
]);
|
|
282
|
+
const [baselineHeldOutFaultsDetected, baselineHeldOutFaultsTotal] = fraction(item.baselineHeldOutFaultsDetected, item.baselineHeldOutFaultsTotal);
|
|
283
|
+
const [profileHeldOutFaultsDetected, profileHeldOutFaultsTotal] = fraction(item.profileHeldOutFaultsDetected, item.profileHeldOutFaultsTotal);
|
|
284
|
+
return {
|
|
285
|
+
equalBudget: boolean(item.equalBudget),
|
|
286
|
+
baselineHeldOutFaultsDetected,
|
|
287
|
+
baselineHeldOutFaultsTotal,
|
|
288
|
+
profileHeldOutFaultsDetected,
|
|
289
|
+
profileHeldOutFaultsTotal,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function parseH4(value) {
|
|
293
|
+
const item = record(value);
|
|
294
|
+
exactKeys(item, [
|
|
295
|
+
"reviewedReduction",
|
|
296
|
+
"baselineHistoricalFaultsDetected",
|
|
297
|
+
"baselineHistoricalFaultsTotal",
|
|
298
|
+
"profileHistoricalFaultsDetected",
|
|
299
|
+
"profileHistoricalFaultsTotal",
|
|
300
|
+
"baselineCostUs",
|
|
301
|
+
"profileCostUs",
|
|
302
|
+
"baselineMutantCount",
|
|
303
|
+
"profileMutantCount",
|
|
304
|
+
]);
|
|
305
|
+
const [baselineHistoricalFaultsDetected, baselineHistoricalFaultsTotal] = fraction(item.baselineHistoricalFaultsDetected, item.baselineHistoricalFaultsTotal);
|
|
306
|
+
const [profileHistoricalFaultsDetected, profileHistoricalFaultsTotal] = fraction(item.profileHistoricalFaultsDetected, item.profileHistoricalFaultsTotal);
|
|
307
|
+
return {
|
|
308
|
+
reviewedReduction: boolean(item.reviewedReduction),
|
|
309
|
+
baselineHistoricalFaultsDetected,
|
|
310
|
+
baselineHistoricalFaultsTotal,
|
|
311
|
+
profileHistoricalFaultsDetected,
|
|
312
|
+
profileHistoricalFaultsTotal,
|
|
313
|
+
baselineCostUs: count(item.baselineCostUs),
|
|
314
|
+
profileCostUs: count(item.profileCostUs),
|
|
315
|
+
baselineMutantCount: count(item.baselineMutantCount),
|
|
316
|
+
profileMutantCount: count(item.profileMutantCount),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
export function parseAgenticCorpusCase(value) {
|
|
320
|
+
const item = record(value);
|
|
321
|
+
exactKeys(item, ["schemaVersion", "caseId", "sourceId", "sourceRevision"], ["h1", "h2", "h3", "h4"]);
|
|
322
|
+
if (item.schemaVersion !== SCHEMA_VERSION)
|
|
323
|
+
invalid();
|
|
324
|
+
const parsed = {
|
|
325
|
+
schemaVersion: SCHEMA_VERSION,
|
|
326
|
+
caseId: portableIdentifier(item.caseId),
|
|
327
|
+
sourceId: portableIdentifier(item.sourceId),
|
|
328
|
+
sourceRevision: digest(item.sourceRevision),
|
|
329
|
+
};
|
|
330
|
+
if (item.h1 !== undefined)
|
|
331
|
+
parsed.h1 = parseH1(item.h1);
|
|
332
|
+
if (item.h2 !== undefined)
|
|
333
|
+
parsed.h2 = parseH2(item.h2);
|
|
334
|
+
if (item.h3 !== undefined)
|
|
335
|
+
parsed.h3 = parseH3(item.h3);
|
|
336
|
+
if (item.h4 !== undefined)
|
|
337
|
+
parsed.h4 = parseH4(item.h4);
|
|
338
|
+
return parsed;
|
|
339
|
+
}
|
|
340
|
+
function compareIdentifier(left, right) {
|
|
341
|
+
return left.caseId < right.caseId ? -1 : left.caseId > right.caseId ? 1 : 0;
|
|
342
|
+
}
|
|
343
|
+
async function readSplit(directory, policy, expectedPolicyDigest) {
|
|
344
|
+
let entries;
|
|
345
|
+
try {
|
|
346
|
+
const metadata = await lstat(directory);
|
|
347
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
348
|
+
throw new AgenticCorpusError("SPLIT_DIRECTORY_INVALID");
|
|
349
|
+
}
|
|
350
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
if (error instanceof AgenticCorpusError)
|
|
354
|
+
throw error;
|
|
355
|
+
throw new AgenticCorpusError("SPLIT_DIRECTORY_INVALID", { cause: error });
|
|
356
|
+
}
|
|
357
|
+
const caseNames = new Set(entries.filter((entry) => entry.name.endsWith(CASE_SUFFIX)).map((entry) => entry.name));
|
|
358
|
+
const portableCaseNames = new Set();
|
|
359
|
+
const portableProvenanceNames = new Set();
|
|
360
|
+
for (const entry of entries) {
|
|
361
|
+
const suffix = entry.name.endsWith(CASE_SUFFIX)
|
|
362
|
+
? CASE_SUFFIX
|
|
363
|
+
: entry.name.endsWith(".provenance.json")
|
|
364
|
+
? ".provenance.json"
|
|
365
|
+
: null;
|
|
366
|
+
if (suffix === null)
|
|
367
|
+
continue;
|
|
368
|
+
const key = portablePathKey(entry.name.slice(0, -suffix.length));
|
|
369
|
+
const seen = suffix === CASE_SUFFIX ? portableCaseNames : portableProvenanceNames;
|
|
370
|
+
if (seen.has(key))
|
|
371
|
+
throw new AgenticCorpusError("PORTABLE_CORPUS_FILE_COLLISION");
|
|
372
|
+
seen.add(key);
|
|
373
|
+
}
|
|
374
|
+
if (entries.some((entry) => entry.name.endsWith(".provenance.json") &&
|
|
375
|
+
!caseNames.has(`${entry.name.slice(0, -".provenance.json".length)}.case.json`))) {
|
|
376
|
+
throw new AgenticCorpusError("PROVENANCE_FILE_ORPHANED");
|
|
377
|
+
}
|
|
378
|
+
const cases = [];
|
|
379
|
+
for (const entry of entries.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
|
|
380
|
+
if (!entry.name.endsWith(CASE_SUFFIX))
|
|
381
|
+
continue;
|
|
382
|
+
if (!entry.isFile())
|
|
383
|
+
throw new AgenticCorpusError("CASE_FILE_INVALID");
|
|
384
|
+
let value;
|
|
385
|
+
try {
|
|
386
|
+
value = JSON.parse(await readFile(path.join(directory, entry.name), "utf8"));
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
throw new AgenticCorpusError("CASE_JSON_INVALID", { cause: error });
|
|
390
|
+
}
|
|
391
|
+
const corpusCase = parseAgenticCorpusCase(value);
|
|
392
|
+
const basename = entry.name.slice(0, -CASE_SUFFIX.length);
|
|
393
|
+
const fileBasenameKey = portablePathKey(basename);
|
|
394
|
+
if (fileBasenameKey !== portablePathKey(corpusCase.caseId)) {
|
|
395
|
+
throw new AgenticCorpusError("CASE_FILE_ID_MISMATCH");
|
|
396
|
+
}
|
|
397
|
+
const provenancePath = path.join(directory, `${basename}.provenance.json`);
|
|
398
|
+
let provenanceText;
|
|
399
|
+
let provenanceValue;
|
|
400
|
+
try {
|
|
401
|
+
const metadata = await lstat(provenancePath);
|
|
402
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
403
|
+
throw new AgenticCorpusError("PROVENANCE_FILE_INVALID");
|
|
404
|
+
}
|
|
405
|
+
const [directoryReal, provenanceReal] = await Promise.all([
|
|
406
|
+
realpath(directory),
|
|
407
|
+
realpath(provenancePath),
|
|
408
|
+
]);
|
|
409
|
+
if (path.dirname(provenanceReal) !== directoryReal) {
|
|
410
|
+
throw new AgenticCorpusError("PROVENANCE_FILE_INVALID");
|
|
411
|
+
}
|
|
412
|
+
provenanceText = await readFile(provenancePath, "utf8");
|
|
413
|
+
provenanceValue = JSON.parse(provenanceText);
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
if (error instanceof AgenticCorpusError)
|
|
417
|
+
throw error;
|
|
418
|
+
throw new AgenticCorpusError("PROVENANCE_MISSING_OR_INVALID", { cause: error });
|
|
419
|
+
}
|
|
420
|
+
if (`${canonicalize(provenanceValue)}\n` !== provenanceText) {
|
|
421
|
+
throw new AgenticCorpusError("PROVENANCE_JSON_NONCANONICAL");
|
|
422
|
+
}
|
|
423
|
+
let provenance;
|
|
424
|
+
try {
|
|
425
|
+
provenance = parseAgenticCorpusProvenance(provenanceValue);
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
throw new AgenticCorpusError("PROVENANCE_INVALID", { cause: error });
|
|
429
|
+
}
|
|
430
|
+
const replay = replayAgenticCorpusProvenance(corpusCase, provenance, policy, expectedPolicyDigest);
|
|
431
|
+
if (!replay.valid)
|
|
432
|
+
throw new AgenticCorpusError("PROVENANCE_INVALID");
|
|
433
|
+
cases.push({
|
|
434
|
+
...corpusCase,
|
|
435
|
+
provenanceDigest: provenance.provenanceDigest,
|
|
436
|
+
sourceIdentityDigest: provenance.source.sourceIdentityDigest,
|
|
437
|
+
fileBasenameKey,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
return cases.sort(compareIdentifier);
|
|
441
|
+
}
|
|
442
|
+
function trustedPolicy(options) {
|
|
443
|
+
let policy;
|
|
444
|
+
try {
|
|
445
|
+
policy = parseAgenticCorpusTrustPolicy(options.policy);
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
throw new AgenticCorpusError("PROVENANCE_POLICY_INVALID", { cause: error });
|
|
449
|
+
}
|
|
450
|
+
const computed = agenticCorpusTrustPolicyDigest({
|
|
451
|
+
schemaVersion: policy.schemaVersion,
|
|
452
|
+
policyId: policy.policyId,
|
|
453
|
+
keys: policy.keys,
|
|
454
|
+
sources: policy.sources,
|
|
455
|
+
});
|
|
456
|
+
if (!SHA256_DIGEST.test(options.expectedPolicyDigest) ||
|
|
457
|
+
policy.policyDigest !== options.expectedPolicyDigest ||
|
|
458
|
+
policy.policyDigest !== computed ||
|
|
459
|
+
!allPolicyKeysValid(policy)) {
|
|
460
|
+
throw new AgenticCorpusError("PROVENANCE_POLICY_DIGEST_MISMATCH");
|
|
461
|
+
}
|
|
462
|
+
return policy;
|
|
463
|
+
}
|
|
464
|
+
async function loadCorpus(root, options) {
|
|
465
|
+
const policy = trustedPolicy(options);
|
|
466
|
+
const publicDirectory = path.join(root, "public");
|
|
467
|
+
const privateDirectory = path.join(root, "private");
|
|
468
|
+
try {
|
|
469
|
+
const [rootReal, publicReal, privateReal] = await Promise.all([
|
|
470
|
+
realpath(root),
|
|
471
|
+
realpath(publicDirectory),
|
|
472
|
+
realpath(privateDirectory),
|
|
473
|
+
]);
|
|
474
|
+
if (publicReal === privateReal ||
|
|
475
|
+
path.dirname(publicReal) !== rootReal ||
|
|
476
|
+
path.dirname(privateReal) !== rootReal) {
|
|
477
|
+
throw new AgenticCorpusError("SPLITS_NOT_PHYSICALLY_SEPARATE");
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
if (error instanceof AgenticCorpusError)
|
|
482
|
+
throw error;
|
|
483
|
+
throw new AgenticCorpusError("SPLIT_DIRECTORY_INVALID", { cause: error });
|
|
484
|
+
}
|
|
485
|
+
// Public precedes private so two invalid splits always yield the same operator-visible reason.
|
|
486
|
+
const publicCases = await readSplit(publicDirectory, policy, options.expectedPolicyDigest);
|
|
487
|
+
const holdoutCases = await readSplit(privateDirectory, policy, options.expectedPolicyDigest);
|
|
488
|
+
const identifiers = new Map();
|
|
489
|
+
const basenames = new Set();
|
|
490
|
+
for (const item of [...publicCases, ...holdoutCases]) {
|
|
491
|
+
const identifierKey = portablePathKey(item.caseId);
|
|
492
|
+
const existingIdentifier = identifiers.get(identifierKey);
|
|
493
|
+
if (existingIdentifier !== undefined) {
|
|
494
|
+
throw new AgenticCorpusError(existingIdentifier === item.caseId ? "DUPLICATE_CASE_ID" : "PORTABLE_CASE_ID_COLLISION");
|
|
495
|
+
}
|
|
496
|
+
if (basenames.has(item.fileBasenameKey)) {
|
|
497
|
+
throw new AgenticCorpusError("PORTABLE_CORPUS_FILE_COLLISION");
|
|
498
|
+
}
|
|
499
|
+
identifiers.set(identifierKey, item.caseId);
|
|
500
|
+
basenames.add(item.fileBasenameKey);
|
|
501
|
+
}
|
|
502
|
+
return { publicCases, holdoutCases };
|
|
503
|
+
}
|
|
504
|
+
function compareFractions(leftNumerator, leftDenominator, rightNumerator, rightDenominator) {
|
|
505
|
+
const left = BigInt(leftNumerator) * BigInt(rightDenominator);
|
|
506
|
+
const right = BigInt(rightNumerator) * BigInt(leftDenominator);
|
|
507
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
508
|
+
}
|
|
509
|
+
function insufficient(reasonCode) {
|
|
510
|
+
return { outcome: "INSUFFICIENT", reasonCodes: [reasonCode] };
|
|
511
|
+
}
|
|
512
|
+
function evaluateH1(item) {
|
|
513
|
+
if (item.h1 === undefined)
|
|
514
|
+
return insufficient("HYPOTHESIS_EVIDENCE_MISSING");
|
|
515
|
+
if (!item.h1.equalTargetStrength)
|
|
516
|
+
return insufficient("TARGET_STRENGTH_NOT_EQUAL");
|
|
517
|
+
return item.h1.profileWarmP95Us < item.h1.baselineWarmP95Us
|
|
518
|
+
? { outcome: "SUPPORT", reasonCodes: ["PROFILE_WARM_P95_LOWER"] }
|
|
519
|
+
: { outcome: "OPPOSITION", reasonCodes: ["PROFILE_WARM_P95_NOT_LOWER"] };
|
|
520
|
+
}
|
|
521
|
+
function evaluateH2(item) {
|
|
522
|
+
if (item.h2 === undefined)
|
|
523
|
+
return insufficient("HYPOTHESIS_EVIDENCE_MISSING");
|
|
524
|
+
const comparison = compareFractions(item.h2.profileNeutralSurvived, item.h2.profileNeutralTotal, item.h2.baselineNeutralSurvived, item.h2.baselineNeutralTotal);
|
|
525
|
+
return comparison > 0
|
|
526
|
+
? { outcome: "SUPPORT", reasonCodes: ["PROFILE_NEUTRAL_SURVIVAL_HIGHER"] }
|
|
527
|
+
: { outcome: "OPPOSITION", reasonCodes: ["PROFILE_NEUTRAL_SURVIVAL_NOT_HIGHER"] };
|
|
528
|
+
}
|
|
529
|
+
function evaluateH3(item) {
|
|
530
|
+
if (item.h3 === undefined)
|
|
531
|
+
return insufficient("HYPOTHESIS_EVIDENCE_MISSING");
|
|
532
|
+
if (!item.h3.equalBudget)
|
|
533
|
+
return insufficient("BUDGET_NOT_EQUAL");
|
|
534
|
+
const comparison = compareFractions(item.h3.profileHeldOutFaultsDetected, item.h3.profileHeldOutFaultsTotal, item.h3.baselineHeldOutFaultsDetected, item.h3.baselineHeldOutFaultsTotal);
|
|
535
|
+
return comparison >= 0
|
|
536
|
+
? { outcome: "SUPPORT", reasonCodes: ["PROFILE_HELD_OUT_RECALL_NON_INFERIOR"] }
|
|
537
|
+
: { outcome: "OPPOSITION", reasonCodes: ["PROFILE_HELD_OUT_RECALL_INFERIOR"] };
|
|
538
|
+
}
|
|
539
|
+
function evaluateH4(item) {
|
|
540
|
+
if (item.h4 === undefined)
|
|
541
|
+
return insufficient("HYPOTHESIS_EVIDENCE_MISSING");
|
|
542
|
+
if (!item.h4.reviewedReduction)
|
|
543
|
+
return insufficient("REDUCTION_NOT_REVIEWED");
|
|
544
|
+
const reasons = [];
|
|
545
|
+
const recallComparison = compareFractions(item.h4.profileHistoricalFaultsDetected, item.h4.profileHistoricalFaultsTotal, item.h4.baselineHistoricalFaultsDetected, item.h4.baselineHistoricalFaultsTotal);
|
|
546
|
+
if (recallComparison < 0)
|
|
547
|
+
reasons.push("PROFILE_HISTORICAL_RECALL_INFERIOR");
|
|
548
|
+
if (item.h4.profileCostUs >= item.h4.baselineCostUs)
|
|
549
|
+
reasons.push("PROFILE_COST_NOT_LOWER");
|
|
550
|
+
if (item.h4.profileMutantCount >= item.h4.baselineMutantCount) {
|
|
551
|
+
reasons.push("PROFILE_MUTANT_COUNT_NOT_LOWER");
|
|
552
|
+
}
|
|
553
|
+
return reasons.length === 0
|
|
554
|
+
? { outcome: "SUPPORT", reasonCodes: ["REVIEWED_REDUCTION_SUPPORTS_H4"] }
|
|
555
|
+
: { outcome: "OPPOSITION", reasonCodes: reasons };
|
|
556
|
+
}
|
|
557
|
+
function evaluateCase(item) {
|
|
558
|
+
return { H1: evaluateH1(item), H2: evaluateH2(item), H3: evaluateH3(item), H4: evaluateH4(item) };
|
|
559
|
+
}
|
|
560
|
+
function isEvaluable(item, hypothesis) {
|
|
561
|
+
switch (hypothesis) {
|
|
562
|
+
case "H1":
|
|
563
|
+
return item.h1?.equalTargetStrength === true;
|
|
564
|
+
case "H2":
|
|
565
|
+
return item.h2 !== undefined;
|
|
566
|
+
case "H3":
|
|
567
|
+
return item.h3?.equalBudget === true;
|
|
568
|
+
case "H4":
|
|
569
|
+
return item.h4?.reviewedReduction === true;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function statusFor(corpus) {
|
|
573
|
+
const allCases = [...corpus.publicCases, ...corpus.holdoutCases];
|
|
574
|
+
const evaluable = Object.fromEntries(HYPOTHESES.map((hypothesis) => [
|
|
575
|
+
hypothesis,
|
|
576
|
+
allCases.filter((item) => isEvaluable(item, hypothesis)).length,
|
|
577
|
+
]));
|
|
578
|
+
const counts = {
|
|
579
|
+
totalCases: allCases.length,
|
|
580
|
+
publicCases: corpus.publicCases.length,
|
|
581
|
+
holdoutCases: corpus.holdoutCases.length,
|
|
582
|
+
distinctSources: new Set(allCases.map((item) => item.sourceIdentityDigest)).size,
|
|
583
|
+
evaluable,
|
|
584
|
+
};
|
|
585
|
+
const failedGates = [];
|
|
586
|
+
if (counts.totalCases < 20)
|
|
587
|
+
failedGates.push("MINIMUM_CASES_NOT_MET");
|
|
588
|
+
if (counts.distinctSources < 3)
|
|
589
|
+
failedGates.push("MINIMUM_SOURCES_NOT_MET");
|
|
590
|
+
if (counts.publicCases < 1)
|
|
591
|
+
failedGates.push("PUBLIC_SPLIT_EMPTY");
|
|
592
|
+
if (counts.holdoutCases < 1)
|
|
593
|
+
failedGates.push("HOLDOUT_SPLIT_EMPTY");
|
|
594
|
+
for (const hypothesis of HYPOTHESES) {
|
|
595
|
+
if (evaluable[hypothesis] === 0)
|
|
596
|
+
failedGates.push(`${hypothesis}_EVIDENCE_MISSING`);
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
schemaVersion: SCHEMA_VERSION,
|
|
600
|
+
status: failedGates.length === 0 ? "READY" : "NOT_READY",
|
|
601
|
+
failedGates,
|
|
602
|
+
counts,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function policyRequiredStatus() {
|
|
606
|
+
return {
|
|
607
|
+
schemaVersion: SCHEMA_VERSION,
|
|
608
|
+
status: "NOT_READY",
|
|
609
|
+
failedGates: ["PROVENANCE_POLICY_REQUIRED"],
|
|
610
|
+
counts: {
|
|
611
|
+
totalCases: 0,
|
|
612
|
+
publicCases: 0,
|
|
613
|
+
holdoutCases: 0,
|
|
614
|
+
distinctSources: 0,
|
|
615
|
+
evaluable: { H1: 0, H2: 0, H3: 0, H4: 0 },
|
|
616
|
+
},
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
export async function inspectAgenticCorpus(root, options) {
|
|
620
|
+
if (options === undefined)
|
|
621
|
+
return policyRequiredStatus();
|
|
622
|
+
return statusFor(await loadCorpus(root, options));
|
|
623
|
+
}
|
|
624
|
+
function aggregate(outcomes) {
|
|
625
|
+
return {
|
|
626
|
+
support: outcomes.filter((item) => item.outcome === "SUPPORT").length,
|
|
627
|
+
opposition: outcomes.filter((item) => item.outcome === "OPPOSITION").length,
|
|
628
|
+
insufficient: outcomes.filter((item) => item.outcome === "INSUFFICIENT").length,
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
export async function evaluateAgenticCorpusPublic(root, options) {
|
|
632
|
+
if (options === undefined)
|
|
633
|
+
return policyRequiredStatus();
|
|
634
|
+
const corpus = await loadCorpus(root, options);
|
|
635
|
+
const readiness = statusFor(corpus);
|
|
636
|
+
if (readiness.status === "NOT_READY")
|
|
637
|
+
return readiness;
|
|
638
|
+
const cases = corpus.publicCases.map((item) => {
|
|
639
|
+
const hypotheses = evaluateCase(item);
|
|
640
|
+
const outcomes = Object.values(hypotheses);
|
|
641
|
+
return {
|
|
642
|
+
caseId: item.caseId,
|
|
643
|
+
sourceId: item.sourceId,
|
|
644
|
+
sourceRevision: item.sourceRevision,
|
|
645
|
+
provenanceDigest: item.provenanceDigest,
|
|
646
|
+
hypotheses,
|
|
647
|
+
score: {
|
|
648
|
+
numerator: outcomes.filter((outcome) => outcome.outcome === "SUPPORT").length,
|
|
649
|
+
denominator: outcomes.filter((outcome) => outcome.outcome !== "INSUFFICIENT").length,
|
|
650
|
+
},
|
|
651
|
+
};
|
|
652
|
+
});
|
|
653
|
+
const allOutcomes = cases.flatMap((item) => Object.values(item.hypotheses));
|
|
654
|
+
return {
|
|
655
|
+
schemaVersion: SCHEMA_VERSION,
|
|
656
|
+
status: "EVALUATED",
|
|
657
|
+
split: "PUBLIC",
|
|
658
|
+
score: {
|
|
659
|
+
numerator: allOutcomes.filter((outcome) => outcome.outcome === "SUPPORT").length,
|
|
660
|
+
denominator: allOutcomes.filter((outcome) => outcome.outcome !== "INSUFFICIENT").length,
|
|
661
|
+
},
|
|
662
|
+
hypotheses: Object.fromEntries(HYPOTHESES.map((hypothesis) => [
|
|
663
|
+
hypothesis,
|
|
664
|
+
aggregate(cases.map((item) => item.hypotheses[hypothesis])),
|
|
665
|
+
])),
|
|
666
|
+
cases,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
export async function evaluateAgenticCorpusHoldout(root, options) {
|
|
670
|
+
if (options === undefined) {
|
|
671
|
+
return {
|
|
672
|
+
schemaVersion: SCHEMA_VERSION,
|
|
673
|
+
status: "NOT_READY",
|
|
674
|
+
failedGates: ["CORPUS_NOT_READY"],
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
let corpus;
|
|
678
|
+
try {
|
|
679
|
+
corpus = await loadCorpus(root, options);
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
if (error instanceof AgenticCorpusError) {
|
|
683
|
+
return {
|
|
684
|
+
schemaVersion: SCHEMA_VERSION,
|
|
685
|
+
status: "INVALID_CORPUS",
|
|
686
|
+
reasonCodes: ["CORPUS_INVALID"],
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
throw error;
|
|
690
|
+
}
|
|
691
|
+
const readiness = statusFor(corpus);
|
|
692
|
+
if (readiness.status === "NOT_READY") {
|
|
693
|
+
return {
|
|
694
|
+
schemaVersion: SCHEMA_VERSION,
|
|
695
|
+
status: "NOT_READY",
|
|
696
|
+
failedGates: ["CORPUS_NOT_READY"],
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
const outcomes = corpus.holdoutCases.map(evaluateCase);
|
|
700
|
+
return {
|
|
701
|
+
schemaVersion: SCHEMA_VERSION,
|
|
702
|
+
status: "EVALUATED",
|
|
703
|
+
split: "HOLDOUT",
|
|
704
|
+
hypotheses: Object.fromEntries(HYPOTHESES.map((hypothesis) => [
|
|
705
|
+
hypothesis,
|
|
706
|
+
aggregate(outcomes.map((item) => item[hypothesis])),
|
|
707
|
+
])),
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
//# sourceMappingURL=agentic-corpus.js.map
|