@dailephd/my-dev-kit-lab 0.9.2 → 0.9.4
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/CHANGELOG.md +17 -1
- package/README.md +9 -5
- package/dist/src/agents/adapters/codexAdapter.js +4 -1
- package/dist/src/cli/help.js +38 -2
- package/dist/src/cli/runLabCli.js +9 -1
- package/dist/src/commands/buildGalleryCommand.js +16 -3
- package/dist/src/commands/runComparePromptsCommand.js +109 -0
- package/dist/src/commands/runExperimentRunCommand.js +47 -2
- package/dist/src/core/processTree.js +41 -17
- package/dist/src/core/runMeasuredCommand.js +14 -8
- package/dist/src/evaluation/affectedNeighborhood.js +140 -32
- package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpointStore.js +23 -1
- package/dist/src/experiments/plugins/agentSuccessRate/campaignExecution.js +7 -2
- package/dist/src/experiments/plugins/agentSuccessRate/campaignIdentity.js +29 -4
- package/dist/src/experiments/plugins/agentSuccessRate/campaignResumableExecution.js +1 -1
- package/dist/src/experiments/plugins/agentSuccessRate/config.js +47 -2
- package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +4 -2
- package/dist/src/experiments/plugins/agentSuccessRate/promptContract.js +121 -0
- package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +32 -2
- package/dist/src/experiments/plugins/agentSuccessRate/realAgentPrompt.js +105 -11
- package/dist/src/experiments/plugins/agentSuccessRate/strictDiffResponse.js +312 -0
- package/dist/src/gallery/galleryPathSafety.js +111 -0
- package/dist/src/gallery/readGallerySources.js +542 -0
- package/dist/src/gallery/writeGalleryManifest.js +57 -3
- package/dist/src/report/experiments/buildPluginExperimentReport.js +4 -16
- package/dist/src/report/experiments/htmlSafety.js +32 -0
- package/dist/src/report/experiments/metricEvidence.js +389 -0
- package/dist/src/report/experiments/metricGlossary.js +227 -0
- package/dist/src/report/experiments/metricGlossaryCatalog.js +199 -0
- package/dist/src/report/experiments/promptTemplateComparisonEvidence.js +377 -0
- package/dist/src/report/experiments/promptTemplateComparisonModel.js +436 -0
- package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +158 -64
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +22 -677
- package/dist/src/report/experiments/renderPromptTemplateComparison.js +192 -0
- package/dist/src/report/experiments/renderSpecializedTextSections.js +633 -0
- package/dist/src/report/experiments/reportSectionRegistry.js +79 -0
- package/dist/src/report/experiments/specializedReportSections.js +142 -0
- package/dist/src/report/experiments/writePluginExperimentReports.js +4 -2
- package/dist/src/securityValidation/attackScenarios/scenarios/secretLeakageScenario.js +146 -133
- package/dist/src/securityValidation/attackScenarios/scenarios/subprocessInjectionScenario.js +54 -36
- package/dist/src/securityValidation/commandRunner.js +13 -6
- package/dist/src/securityValidation/packageChecks/parseNpmPackDryRun.js +46 -21
- package/docs/METRICS.md +11 -1
- package/package.json +3 -3
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// Read-only evidence loader for the prompt-template comparison. It reads persisted agent-success-rate artifacts
|
|
2
|
+
// from one run root and verifies them with the campaign owners' own validators: the complete checkpoint store
|
|
3
|
+
// read (every generation, history, artifact integrity, V2 authorization state) against trusted task definitions
|
|
4
|
+
// that are authenticated by the persisted campaign identity's task digests. Nothing is written, no provider is
|
|
5
|
+
// invoked, and every read is bounded, physically contained and change-detected.
|
|
6
|
+
import { statSync } from "node:fs";
|
|
7
|
+
import { open, readdir, realpath, stat } from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { isSafeRelativeArtifactPath } from "../../gallery/galleryPathSafety.js";
|
|
11
|
+
import { assertReadAgentSuccessCorpus } from "../../evaluation/agentSuccess/index.js";
|
|
12
|
+
import { AGENT_SUCCESS_RATE_ANALYSIS_ARTIFACT_FILE, AGENT_SUCCESS_RATE_ANALYSIS_SCHEMA_VERSION, validateAgentSuccessRateAnalysisArtifact, validateAgentSuccessRateArtifactFamily } from "../../experiments/plugins/agentSuccessRate/analysisArtifact.js";
|
|
13
|
+
import { attemptEvidenceOf } from "../../experiments/plugins/agentSuccessRate/attemptEvidence.js";
|
|
14
|
+
import { AGENT_SUCCESS_RATE_EXECUTION_ARTIFACT_FILE, AGENT_SUCCESS_RATE_EXECUTION_SCHEMA_VERSION, validateAgentSuccessRateExecutionArtifact } from "../../experiments/plugins/agentSuccessRate/executionArtifact.js";
|
|
15
|
+
import { CAMPAIGN_CHECKPOINT_MAX_BYTES, CAMPAIGN_CHECKPOINT_SCHEMA } from "../../experiments/plugins/agentSuccessRate/campaignCheckpoint.js";
|
|
16
|
+
import { readCampaignCheckpoint, readCampaignCheckpointV2 } from "../../experiments/plugins/agentSuccessRate/campaignCheckpointStore.js";
|
|
17
|
+
import { CAMPAIGN_CHECKPOINT_SCHEMA_V2 } from "../../experiments/plugins/agentSuccessRate/campaignRecovery.js";
|
|
18
|
+
import { CAMPAIGN_IDENTITY_VERSION_V2, validateCampaignIdentity } from "../../experiments/plugins/agentSuccessRate/campaignIdentity.js";
|
|
19
|
+
import { AGENT_SUCCESS_RATE_PLUGIN_ID, AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE } from "../../experiments/plugins/agentSuccessRate/metadata.js";
|
|
20
|
+
import { PROMPT_CONTRACT_ARTIFACT_FILE, assertPromptContractShape, promptContractDigest, renderPromptContractArtifact } from "../../experiments/plugins/agentSuccessRate/promptContract.js";
|
|
21
|
+
const MAX_ARTIFACT_BYTES = 32 * 1024 * 1024;
|
|
22
|
+
const MAX_PROMPT_CONTRACT_BYTES = 64 * 1024;
|
|
23
|
+
const MAX_CASES = 1000;
|
|
24
|
+
const MAX_REASON_LENGTH = 240;
|
|
25
|
+
const STORES = ["campaign-checkpoints-v2", "campaign-checkpoints"];
|
|
26
|
+
const GENERATION = /^generation-(\d{10})\.json$/;
|
|
27
|
+
const CORPUS_CATALOG = "benchmarks/contracts/agent-success-rate-tasks.json";
|
|
28
|
+
class EvidenceError extends Error {
|
|
29
|
+
}
|
|
30
|
+
function reason(text) {
|
|
31
|
+
return text.length > MAX_REASON_LENGTH ? `${text.slice(0, MAX_REASON_LENGTH)}...` : text;
|
|
32
|
+
}
|
|
33
|
+
function within(root, candidate) {
|
|
34
|
+
const relative = path.relative(root, candidate);
|
|
35
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
36
|
+
}
|
|
37
|
+
/** Bounded read of one regular file physically inside `realRoot`; fails when the file changes during the read. */
|
|
38
|
+
async function readContained(realRoot, relative, maxBytes) {
|
|
39
|
+
if (!isSafeRelativeArtifactPath(relative))
|
|
40
|
+
throw new EvidenceError(`unsafe artifact path`);
|
|
41
|
+
const target = path.resolve(realRoot, ...relative.split("/"));
|
|
42
|
+
const real = await realpath(target).catch(() => {
|
|
43
|
+
throw new EvidenceError(`${path.basename(relative)} is missing or unreadable`);
|
|
44
|
+
});
|
|
45
|
+
if (!within(realRoot, real))
|
|
46
|
+
throw new EvidenceError(`${path.basename(relative)} resolves outside the run root`);
|
|
47
|
+
const before = await stat(real);
|
|
48
|
+
if (!before.isFile())
|
|
49
|
+
throw new EvidenceError(`${path.basename(relative)} is not an ordinary file`);
|
|
50
|
+
if (before.size > maxBytes)
|
|
51
|
+
throw new EvidenceError(`${path.basename(relative)} exceeds the ${maxBytes}-byte limit`);
|
|
52
|
+
const handle = await open(real, "r");
|
|
53
|
+
try {
|
|
54
|
+
const buffer = Buffer.alloc(before.size);
|
|
55
|
+
let offset = 0;
|
|
56
|
+
while (offset < before.size) {
|
|
57
|
+
const { bytesRead } = await handle.read(buffer, offset, before.size - offset, offset);
|
|
58
|
+
if (bytesRead === 0)
|
|
59
|
+
break;
|
|
60
|
+
offset += bytesRead;
|
|
61
|
+
}
|
|
62
|
+
const after = await handle.stat();
|
|
63
|
+
if (offset !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs) {
|
|
64
|
+
throw new EvidenceError(`${path.basename(relative)} changed while it was being read`);
|
|
65
|
+
}
|
|
66
|
+
return buffer;
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
await handle.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function readJson(realRoot, relative, maxBytes) {
|
|
73
|
+
const bytes = await readContained(realRoot, relative, maxBytes);
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(bytes.toString("utf8"));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new EvidenceError(`${path.basename(relative)} is not valid JSON`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function defaultToolRoot() {
|
|
82
|
+
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
83
|
+
for (const up of ["../../../", "../../../../"]) {
|
|
84
|
+
const candidate = path.resolve(here, up);
|
|
85
|
+
try {
|
|
86
|
+
if (statSyncIsFile(path.join(candidate, ...CORPUS_CATALOG.split("/"))))
|
|
87
|
+
return candidate;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// try the next layout
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return path.resolve(here, "../../../");
|
|
94
|
+
}
|
|
95
|
+
function statSyncIsFile(file) {
|
|
96
|
+
return statSync(file).isFile();
|
|
97
|
+
}
|
|
98
|
+
async function defaultTrustedTasks(identity, toolRoot) {
|
|
99
|
+
let corpusTasks;
|
|
100
|
+
try {
|
|
101
|
+
corpusTasks = assertReadAgentSuccessCorpus(toolRoot).tasks;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return { ok: false, reason: "the trusted task corpus is unavailable for this installation" };
|
|
105
|
+
}
|
|
106
|
+
const byId = new Map(corpusTasks.map((task) => [task.id, task]));
|
|
107
|
+
const tasks = [];
|
|
108
|
+
for (const selected of identity.selected) {
|
|
109
|
+
const task = byId.get(selected.caseId);
|
|
110
|
+
if (!task)
|
|
111
|
+
return { ok: false, reason: `the trusted task corpus does not contain case ${selected.caseId}` };
|
|
112
|
+
tasks.push(task);
|
|
113
|
+
}
|
|
114
|
+
return { ok: true, tasks };
|
|
115
|
+
}
|
|
116
|
+
async function discoverIdentity(realRoot, execution) {
|
|
117
|
+
const found = [];
|
|
118
|
+
for (const store of STORES) {
|
|
119
|
+
let identityDirs;
|
|
120
|
+
try {
|
|
121
|
+
identityDirs = (await readdir(path.join(realRoot, store))).filter((name) => /^[a-f0-9]{64}$/.test(name)).sort();
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
for (const dir of identityDirs) {
|
|
127
|
+
let generations;
|
|
128
|
+
try {
|
|
129
|
+
generations = (await readdir(path.join(realRoot, store, dir))).filter((name) => GENERATION.test(name)).sort();
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const latest = generations[generations.length - 1];
|
|
135
|
+
if (!latest)
|
|
136
|
+
continue;
|
|
137
|
+
const value = (await readJson(realRoot, `${store}/${dir}/${latest}`, CAMPAIGN_CHECKPOINT_MAX_BYTES));
|
|
138
|
+
const schema = value.schemaVersion;
|
|
139
|
+
if (schema !== CAMPAIGN_CHECKPOINT_SCHEMA && schema !== CAMPAIGN_CHECKPOINT_SCHEMA_V2)
|
|
140
|
+
throw new EvidenceError("checkpoint schema is not supported");
|
|
141
|
+
try {
|
|
142
|
+
validateCampaignIdentity(value.identity);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
throw new EvidenceError(`campaign identity is invalid (${error instanceof Error ? error.message : "unknown"})`);
|
|
146
|
+
}
|
|
147
|
+
const identity = value.identity;
|
|
148
|
+
if (identity.digest !== dir)
|
|
149
|
+
throw new EvidenceError("checkpoint directory does not match its campaign identity digest");
|
|
150
|
+
found.push({ identity, v2: schema === CAMPAIGN_CHECKPOINT_SCHEMA_V2, runId: schema === CAMPAIGN_CHECKPOINT_SCHEMA_V2 && typeof value.runId === "string" ? value.runId : null });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const byRun = found.filter((entry) => entry.runId === execution.runId);
|
|
154
|
+
if (found.some((entry) => entry.runId !== null && entry.runId !== execution.runId) && byRun.length === 0) {
|
|
155
|
+
throw new EvidenceError("checkpoint run id does not match the execution run");
|
|
156
|
+
}
|
|
157
|
+
const candidates = byRun.length > 0 ? byRun : found.filter((entry) => entry.runId === null);
|
|
158
|
+
if (candidates.length === 0)
|
|
159
|
+
throw new EvidenceError("no campaign identity checkpoint matches the execution run");
|
|
160
|
+
if (candidates.length > 1)
|
|
161
|
+
throw new EvidenceError("more than one campaign identity checkpoint matches the execution run");
|
|
162
|
+
return { identity: candidates[0].identity, v2: candidates[0].v2 };
|
|
163
|
+
}
|
|
164
|
+
async function verifyCheckpoint(realRoot, execution, options) {
|
|
165
|
+
const { identity, v2 } = await discoverIdentity(realRoot, execution);
|
|
166
|
+
const toolRoot = options.toolRoot ?? defaultToolRoot();
|
|
167
|
+
const trusted = options.loadTrustedTasks ? await options.loadTrustedTasks(identity) : await defaultTrustedTasks(identity, toolRoot);
|
|
168
|
+
if (!trusted.ok)
|
|
169
|
+
throw new EvidenceError(`complete checkpoint validation needs the trusted task corpus: ${trusted.reason}`);
|
|
170
|
+
const boundary = { outputRoot: realRoot, toolRoot };
|
|
171
|
+
let stored;
|
|
172
|
+
try {
|
|
173
|
+
stored = v2 ? await readCampaignCheckpointV2(boundary, identity, trusted.tasks) : await readCampaignCheckpoint(boundary, identity, trusted.tasks);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
throw new EvidenceError(`checkpoint validation failed (${error instanceof Error ? error.message : "unknown"})`);
|
|
177
|
+
}
|
|
178
|
+
if (stored.checkpoint === null || typeof stored.checkpoint !== "object")
|
|
179
|
+
throw new EvidenceError("checkpoint store holds no committed checkpoint for the campaign identity");
|
|
180
|
+
const checkpoint = stored.checkpoint;
|
|
181
|
+
return {
|
|
182
|
+
identity,
|
|
183
|
+
units: checkpoint.units.map((unit) => ({
|
|
184
|
+
caseId: unit.caseId,
|
|
185
|
+
treatmentId: unit.treatmentId,
|
|
186
|
+
state: unit.state,
|
|
187
|
+
taskSuccess: unit.completion ? unit.completion.taskSuccess : null,
|
|
188
|
+
patchOutcome: unit.completion ? unit.completion.evidence.patch.outcome : null,
|
|
189
|
+
patchCode: unit.completion ? unit.completion.evidence.patch.code : null
|
|
190
|
+
})),
|
|
191
|
+
finalized: v2 ? checkpoint.finalization !== null : null
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// Side loader
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
/** Loads and verifies one side. Verification failures become `unverifiable` reasons, never an exception. */
|
|
198
|
+
export async function loadPromptComparisonSide(rootPath, role, options = {}) {
|
|
199
|
+
const side = {
|
|
200
|
+
role,
|
|
201
|
+
identity: null,
|
|
202
|
+
promptBasis: "unverified",
|
|
203
|
+
promptContract: null,
|
|
204
|
+
execution: null,
|
|
205
|
+
unverifiable: [],
|
|
206
|
+
unverifiedUnits: new Map(),
|
|
207
|
+
warnings: []
|
|
208
|
+
};
|
|
209
|
+
let realRoot;
|
|
210
|
+
try {
|
|
211
|
+
realRoot = await realpath(rootPath);
|
|
212
|
+
if (!(await stat(realRoot)).isDirectory())
|
|
213
|
+
throw new Error("not a directory");
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
side.unverifiable.push("run root does not exist or is not a readable directory");
|
|
217
|
+
return side;
|
|
218
|
+
}
|
|
219
|
+
let execution;
|
|
220
|
+
try {
|
|
221
|
+
const raw = (await readJson(realRoot, AGENT_SUCCESS_RATE_EXECUTION_ARTIFACT_FILE, MAX_ARTIFACT_BYTES));
|
|
222
|
+
if (typeof raw !== "object" || raw === null || raw.schemaVersion !== AGENT_SUCCESS_RATE_EXECUTION_SCHEMA_VERSION)
|
|
223
|
+
throw new EvidenceError("execution artifact schema is not supported");
|
|
224
|
+
if (raw.pluginId !== AGENT_SUCCESS_RATE_PLUGIN_ID)
|
|
225
|
+
throw new EvidenceError("execution artifact plugin identity does not match");
|
|
226
|
+
if (typeof raw.runId !== "string" || raw.runId.length === 0 || raw.runId.length > 200)
|
|
227
|
+
throw new EvidenceError("execution artifact run identity is missing");
|
|
228
|
+
if (!Array.isArray(raw.cases) || raw.cases.length > MAX_CASES)
|
|
229
|
+
throw new EvidenceError("execution artifact case list is missing or too large");
|
|
230
|
+
const problems = validateAgentSuccessRateExecutionArtifact(raw);
|
|
231
|
+
if (problems.length > 0)
|
|
232
|
+
throw new EvidenceError(`execution artifact is inconsistent (${problems.slice(0, 3).join("; ")})`);
|
|
233
|
+
if (raw.executionMode !== AGENT_SUCCESS_RATE_REAL_AGENT_EXECUTION_MODE || !raw.realAgent)
|
|
234
|
+
throw new EvidenceError("execution artifact is not a real-agent run");
|
|
235
|
+
execution = raw;
|
|
236
|
+
side.execution = raw;
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
side.unverifiable.push(reason(`execution evidence: ${error instanceof Error ? error.message : "unreadable"}`));
|
|
240
|
+
return side;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
const analysisRaw = await readJson(realRoot, AGENT_SUCCESS_RATE_ANALYSIS_ARTIFACT_FILE, MAX_ARTIFACT_BYTES).catch((error) => {
|
|
244
|
+
if (error instanceof EvidenceError && /missing or unreadable/.test(error.message))
|
|
245
|
+
return null;
|
|
246
|
+
throw error;
|
|
247
|
+
});
|
|
248
|
+
if (analysisRaw === null) {
|
|
249
|
+
side.warnings.push("analysis artifact is absent; task-success verdicts come from persisted attempt evidence");
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
const analysis = analysisRaw;
|
|
253
|
+
if (analysis.schemaVersion !== AGENT_SUCCESS_RATE_ANALYSIS_SCHEMA_VERSION)
|
|
254
|
+
throw new EvidenceError("analysis artifact schema is not supported");
|
|
255
|
+
const problems = [...validateAgentSuccessRateAnalysisArtifact(analysis), ...validateAgentSuccessRateArtifactFamily(execution, analysis)];
|
|
256
|
+
if (problems.length > 0)
|
|
257
|
+
throw new EvidenceError(`analysis artifact is inconsistent (${problems.slice(0, 3).join("; ")})`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
side.unverifiable.push(reason(`analysis evidence: ${error instanceof Error ? error.message : "unreadable"}`));
|
|
262
|
+
return side;
|
|
263
|
+
}
|
|
264
|
+
let verified;
|
|
265
|
+
try {
|
|
266
|
+
verified = await verifyCheckpoint(realRoot, execution, options);
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
side.unverifiable.push(reason(`campaign checkpoint: ${error instanceof Error ? error.message : "unverifiable"}`));
|
|
270
|
+
return side;
|
|
271
|
+
}
|
|
272
|
+
const identity = verified.identity;
|
|
273
|
+
side.identity = identity;
|
|
274
|
+
const run = execution.realAgent;
|
|
275
|
+
const executedIds = execution.cases.map((c) => c.caseId).sort();
|
|
276
|
+
const selectedIds = identity.selected.map((row) => row.caseId).sort();
|
|
277
|
+
if (identity.config.providerId !== run.providerId || identity.config.timeoutMs !== run.timeoutMs || identity.config.repairAttempts !== (run.repairAttempts ?? 0)) {
|
|
278
|
+
side.unverifiable.push("campaign identity configuration disagrees with the execution run settings");
|
|
279
|
+
}
|
|
280
|
+
if (executedIds.join("\0") !== selectedIds.join("\0"))
|
|
281
|
+
side.unverifiable.push("campaign identity cases differ from the executed cases");
|
|
282
|
+
if (side.unverifiable.length > 0)
|
|
283
|
+
return side;
|
|
284
|
+
// Relationship between persisted execution evidence and the validated checkpoint units.
|
|
285
|
+
const checkpointUnits = new Map(verified.units.map((unit) => [`${unit.caseId}/${unit.treatmentId}`, unit]));
|
|
286
|
+
for (const entry of execution.cases) {
|
|
287
|
+
for (const treatment of entry.treatments) {
|
|
288
|
+
const key = `${entry.caseId}/${treatment.treatmentId}`;
|
|
289
|
+
const unit = checkpointUnits.get(key);
|
|
290
|
+
if (!unit) {
|
|
291
|
+
side.unverifiable.push(reason(`execution unit ${key} is absent from the validated checkpoint`));
|
|
292
|
+
return side;
|
|
293
|
+
}
|
|
294
|
+
if (unit.state !== "completed") {
|
|
295
|
+
side.unverifiedUnits.set(key, `checkpoint-unit-${unit.state}`);
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const attempts = treatment.attempts && treatment.attempts.length > 0 ? treatment.attempts : null;
|
|
299
|
+
const finalTaskSuccess = attempts ? attempts[attempts.length - 1].taskSuccess : null;
|
|
300
|
+
if ((finalTaskSuccess !== null && unit.taskSuccess !== finalTaskSuccess) || (unit.patchOutcome !== null && unit.patchOutcome !== treatment.patch.outcome) || (unit.patchCode !== null && unit.patchCode !== treatment.patch.code)) {
|
|
301
|
+
side.unverifiable.push(reason(`execution evidence for ${key} contradicts the checkpoint completion evidence`));
|
|
302
|
+
return side;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (verified.finalized === false)
|
|
307
|
+
side.warnings.push("the V2 campaign checkpoint is not finalized");
|
|
308
|
+
// Prompt identity: independently established from persisted, integrity-checked prompt-contract artifacts.
|
|
309
|
+
const attemptDirectories = [];
|
|
310
|
+
for (const entry of execution.cases) {
|
|
311
|
+
for (const treatment of entry.treatments) {
|
|
312
|
+
for (const evidence of attemptEvidenceOf(treatment)) {
|
|
313
|
+
const directory = evidence.realAgent?.agentArtifactDirectory ?? null;
|
|
314
|
+
if (directory !== null)
|
|
315
|
+
attemptDirectories.push({ unit: `${entry.caseId}/${treatment.treatmentId}`, directory });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const isV2 = identity.schemaVersion === CAMPAIGN_IDENTITY_VERSION_V2;
|
|
320
|
+
if (identity.config.providerId === "deterministic-fixture") {
|
|
321
|
+
side.unverifiable.push("a deterministic-fixture identity carries no prompt template");
|
|
322
|
+
return side;
|
|
323
|
+
}
|
|
324
|
+
if (!isV2) {
|
|
325
|
+
// V1 remains readable and its checkpoint has already been validated, but the closed V1 identity does not bind a
|
|
326
|
+
// prompt contract. Neither a sidecar digest nor a checkpoint hash for prompt bytes identifies the template.
|
|
327
|
+
side.unverifiable.push("historical V1 prompt identity cannot be proven from independently bound template evidence");
|
|
328
|
+
return side;
|
|
329
|
+
}
|
|
330
|
+
if (attemptDirectories.length === 0) {
|
|
331
|
+
side.unverifiable.push("no provider attempt artifact exists to establish the prompt identity");
|
|
332
|
+
return side;
|
|
333
|
+
}
|
|
334
|
+
const parsed = [];
|
|
335
|
+
for (const item of attemptDirectories) {
|
|
336
|
+
try {
|
|
337
|
+
const bytes = (await readContained(realRoot, `${item.directory}/${PROMPT_CONTRACT_ARTIFACT_FILE}`, MAX_PROMPT_CONTRACT_BYTES)).toString("utf8");
|
|
338
|
+
const document = JSON.parse(bytes);
|
|
339
|
+
assertPromptContractShape(document.promptContract);
|
|
340
|
+
const contract = document.promptContract;
|
|
341
|
+
if (document.promptContractDigest !== promptContractDigest(contract) || bytes !== renderPromptContractArtifact(contract)) {
|
|
342
|
+
parsed.push({ unit: item.unit, contract: null, problem: "prompt-contract artifact is not self-consistent" });
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
parsed.push({ unit: item.unit, contract, problem: null });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
parsed.push({ unit: item.unit, contract: null, problem: error instanceof EvidenceError ? error.message : "prompt-contract artifact is unreadable or malformed" });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
let contract;
|
|
353
|
+
let basis;
|
|
354
|
+
const expected = renderPromptContractArtifact(identity.promptContract);
|
|
355
|
+
for (const item of parsed) {
|
|
356
|
+
if (item.contract !== null && renderPromptContractArtifact(item.contract) !== expected) {
|
|
357
|
+
side.unverifiable.push(reason(`prompt contract artifact for ${item.unit} does not match the campaign identity`));
|
|
358
|
+
return side;
|
|
359
|
+
}
|
|
360
|
+
if (item.contract === null) {
|
|
361
|
+
if (/self-consistent|malformed|outside/.test(item.problem ?? "")) {
|
|
362
|
+
side.unverifiable.push(reason(`prompt contract artifact for ${item.unit} is invalid: ${item.problem}`));
|
|
363
|
+
return side;
|
|
364
|
+
}
|
|
365
|
+
side.unverifiedUnits.set(item.unit, "prompt-contract-unverified");
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (parsed.every((item) => item.contract === null)) {
|
|
369
|
+
side.unverifiable.push("no persisted prompt-contract.json artifact could verify the campaign identity's prompt contract");
|
|
370
|
+
return side;
|
|
371
|
+
}
|
|
372
|
+
contract = identity.promptContract;
|
|
373
|
+
basis = "campaign-identity-v2-verified-contract";
|
|
374
|
+
side.promptContract = contract;
|
|
375
|
+
side.promptBasis = basis;
|
|
376
|
+
return side;
|
|
377
|
+
}
|