@mcoda/mswarm 0.1.90 → 0.1.91
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/dist/codali-executor.d.ts +107 -6
- package/dist/codali-executor.d.ts.map +1 -1
- package/dist/codali-executor.js +500 -26
- package/dist/codali-executor.js.map +1 -1
- package/dist/runtime.d.ts +4 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +18 -5
- package/dist/runtime.js.map +1 -1
- package/dist/vendor/codali/cli/DatasetCommand.d.ts +38 -0
- package/dist/vendor/codali/cli/DatasetCommand.d.ts.map +1 -0
- package/dist/vendor/codali/cli/DatasetCommand.js +959 -0
- package/dist/vendor/codali/cli/EvalCommand.d.ts +3 -0
- package/dist/vendor/codali/cli/EvalCommand.d.ts.map +1 -1
- package/dist/vendor/codali/cli/EvalCommand.js +55 -1
- package/dist/vendor/codali/cli/ImprovementCommand.d.ts +97 -0
- package/dist/vendor/codali/cli/ImprovementCommand.d.ts.map +1 -0
- package/dist/vendor/codali/cli/ImprovementCommand.js +1879 -0
- package/dist/vendor/codali/cli.d.ts.map +1 -1
- package/dist/vendor/codali/cli.js +15 -0
- package/dist/vendor/codali/dataset-cli.d.ts +3 -0
- package/dist/vendor/codali/dataset-cli.d.ts.map +1 -0
- package/dist/vendor/codali/dataset-cli.js +30 -0
- package/dist/vendor/codali/eval/CodaliGatewayLiveHarness.d.ts +90 -1
- package/dist/vendor/codali/eval/CodaliGatewayLiveHarness.d.ts.map +1 -1
- package/dist/vendor/codali/eval/CodaliGatewayLiveHarness.js +443 -9
- package/dist/vendor/codali/eval/GatewayDatasetEval.d.ts +70 -0
- package/dist/vendor/codali/eval/GatewayDatasetEval.d.ts.map +1 -0
- package/dist/vendor/codali/eval/GatewayDatasetEval.js +477 -0
- package/dist/vendor/codali/eval/GatewayEvalSuite.d.ts +52 -0
- package/dist/vendor/codali/eval/GatewayEvalSuite.d.ts.map +1 -1
- package/dist/vendor/codali/eval/GatewayEvalSuite.js +135 -1
- package/dist/vendor/codali/gateway/CodaliGateway.d.ts +4 -0
- package/dist/vendor/codali/gateway/CodaliGateway.d.ts.map +1 -1
- package/dist/vendor/codali/gateway/CodaliGateway.js +37 -1
- package/dist/vendor/codali/gateway/CodaliGatewaySchemas.d.ts.map +1 -1
- package/dist/vendor/codali/gateway/CodaliGatewaySchemas.js +0 -4
- package/dist/vendor/codali/gateway/CodaliGatewayStore.d.ts.map +1 -1
- package/dist/vendor/codali/gateway/CodaliGatewayStore.js +1 -0
- package/dist/vendor/codali/gateway/GatewayPlanner.d.ts.map +1 -1
- package/dist/vendor/codali/gateway/GatewayPlanner.js +355 -41
- package/dist/vendor/codali/gateway/GatewayStateMachine.d.ts +1 -0
- package/dist/vendor/codali/gateway/GatewayStateMachine.d.ts.map +1 -1
- package/dist/vendor/codali/gateway/GatewayStateMachine.js +221 -5
- package/dist/vendor/codali/improvement/CandidateReleaseBuilder.d.ts +201 -0
- package/dist/vendor/codali/improvement/CandidateReleaseBuilder.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/CandidateReleaseBuilder.js +1175 -0
- package/dist/vendor/codali/improvement/DatasetEligibilityGate.d.ts +75 -0
- package/dist/vendor/codali/improvement/DatasetEligibilityGate.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/DatasetEligibilityGate.js +497 -0
- package/dist/vendor/codali/improvement/DatasetExportManifestReader.d.ts +88 -0
- package/dist/vendor/codali/improvement/DatasetExportManifestReader.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/DatasetExportManifestReader.js +333 -0
- package/dist/vendor/codali/improvement/DocdexRetrievalCandidateBuilder.d.ts +192 -0
- package/dist/vendor/codali/improvement/DocdexRetrievalCandidateBuilder.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/DocdexRetrievalCandidateBuilder.js +625 -0
- package/dist/vendor/codali/improvement/EvalReplayCandidateBuilder.d.ts +173 -0
- package/dist/vendor/codali/improvement/EvalReplayCandidateBuilder.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/EvalReplayCandidateBuilder.js +363 -0
- package/dist/vendor/codali/improvement/FineTuneJobPlanner.d.ts +281 -0
- package/dist/vendor/codali/improvement/FineTuneJobPlanner.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/FineTuneJobPlanner.js +823 -0
- package/dist/vendor/codali/improvement/ImprovementEvalRunner.d.ts +50 -0
- package/dist/vendor/codali/improvement/ImprovementEvalRunner.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/ImprovementEvalRunner.js +648 -0
- package/dist/vendor/codali/improvement/ImprovementPolicy.d.ts +272 -0
- package/dist/vendor/codali/improvement/ImprovementPolicy.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/ImprovementPolicy.js +1450 -0
- package/dist/vendor/codali/improvement/ModelRouterCandidateBuilder.d.ts +186 -0
- package/dist/vendor/codali/improvement/ModelRouterCandidateBuilder.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/ModelRouterCandidateBuilder.js +880 -0
- package/dist/vendor/codali/improvement/OperatorInspector.d.ts +182 -0
- package/dist/vendor/codali/improvement/OperatorInspector.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/OperatorInspector.js +604 -0
- package/dist/vendor/codali/improvement/ProductionGovernance.d.ts +91 -0
- package/dist/vendor/codali/improvement/ProductionGovernance.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/ProductionGovernance.js +329 -0
- package/dist/vendor/codali/improvement/PromptSchemaToolMetadataCandidateBuilder.d.ts +152 -0
- package/dist/vendor/codali/improvement/PromptSchemaToolMetadataCandidateBuilder.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/PromptSchemaToolMetadataCandidateBuilder.js +480 -0
- package/dist/vendor/codali/improvement/PublishOrchestrator.d.ts +113 -0
- package/dist/vendor/codali/improvement/PublishOrchestrator.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/PublishOrchestrator.js +590 -0
- package/dist/vendor/codali/improvement/ReleaseOutcomeReporter.d.ts +133 -0
- package/dist/vendor/codali/improvement/ReleaseOutcomeReporter.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/ReleaseOutcomeReporter.js +501 -0
- package/dist/vendor/codali/improvement/StorageServiceImprovementClient.d.ts +71 -0
- package/dist/vendor/codali/improvement/StorageServiceImprovementClient.d.ts.map +1 -0
- package/dist/vendor/codali/improvement/StorageServiceImprovementClient.js +245 -0
- package/dist/vendor/codali/index.d.ts +43 -0
- package/dist/vendor/codali/index.d.ts.map +1 -1
- package/dist/vendor/codali/index.js +21 -0
- package/dist/vendor/codali/runtime/CodaliRuntime.d.ts +0 -2
- package/dist/vendor/codali/runtime/CodaliRuntime.d.ts.map +1 -1
- package/dist/vendor/codali/runtime/CodaliRuntime.js +0 -5
- package/dist/vendor/codali/storage/CodaliDatasetPrivacyEngine.d.ts +138 -0
- package/dist/vendor/codali/storage/CodaliDatasetPrivacyEngine.d.ts.map +1 -0
- package/dist/vendor/codali/storage/CodaliDatasetPrivacyEngine.js +417 -0
- package/dist/vendor/codali/storage/CodaliFeedbackReviewIngestion.d.ts +50 -0
- package/dist/vendor/codali/storage/CodaliFeedbackReviewIngestion.d.ts.map +1 -0
- package/dist/vendor/codali/storage/CodaliFeedbackReviewIngestion.js +141 -0
- package/dist/vendor/codali/storage/CodaliStorageContracts.d.ts +337 -0
- package/dist/vendor/codali/storage/CodaliStorageContracts.d.ts.map +1 -0
- package/dist/vendor/codali/storage/CodaliStorageContracts.js +1555 -0
- package/dist/vendor/codali/storage/DatasetExportJob.d.ts +51 -0
- package/dist/vendor/codali/storage/DatasetExportJob.d.ts.map +1 -0
- package/dist/vendor/codali/storage/DatasetExportJob.js +358 -0
- package/dist/vendor/codali/storage/DatasetReviewQueue.d.ts +98 -0
- package/dist/vendor/codali/storage/DatasetReviewQueue.d.ts.map +1 -0
- package/dist/vendor/codali/storage/DatasetReviewQueue.js +426 -0
- package/dist/vendor/codali/storage/GatewayDatasetStore.d.ts +233 -0
- package/dist/vendor/codali/storage/GatewayDatasetStore.d.ts.map +1 -0
- package/dist/vendor/codali/storage/GatewayDatasetStore.js +2070 -0
- package/package.json +3 -3
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION, validateCodaliImprovementRelease, validateCodaliImprovementScorecard, } from "./ImprovementPolicy.js";
|
|
6
|
+
import { DEFAULT_CODALI_CANDIDATE_RELEASE_APPROVED_PATHS } from "./CandidateReleaseBuilder.js";
|
|
7
|
+
import { validateCodaliStorageExportManifest, } from "../storage/CodaliStorageContracts.js";
|
|
8
|
+
export const CODALI_IMPROVEMENT_EVAL_RUNNER_SCHEMA_VERSION = "codali.improvement.eval_runner.v1";
|
|
9
|
+
export const CODALI_IMPROVEMENT_EVAL_GATE_IDS = [
|
|
10
|
+
"deterministic_tests",
|
|
11
|
+
"replay_fixtures",
|
|
12
|
+
"privacy_metadata",
|
|
13
|
+
"deletion_groups",
|
|
14
|
+
"tenant_scope",
|
|
15
|
+
"object_checksums",
|
|
16
|
+
"tool_policy",
|
|
17
|
+
"no_shell_write_destructive_tools",
|
|
18
|
+
"no_cross_tenant_replay",
|
|
19
|
+
"lineage_validity",
|
|
20
|
+
"approved_file_paths",
|
|
21
|
+
];
|
|
22
|
+
const DEFAULT_CANDIDATE_DIRECTORIES = [
|
|
23
|
+
path.resolve(".codali", "improvement", "candidates"),
|
|
24
|
+
path.resolve(".codali", "dataset", "exports", "objects"),
|
|
25
|
+
path.resolve(".codali", "dataset", "exports"),
|
|
26
|
+
];
|
|
27
|
+
const RISKY_TOOL_PATTERN = /(^|[_:/.-])(shell|exec|write|delete|remove|rm|destructive)([_:/.-]|$)/iu;
|
|
28
|
+
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
29
|
+
const stableJson = (value) => {
|
|
30
|
+
if (Array.isArray(value))
|
|
31
|
+
return `[${value.map((item) => stableJson(item)).join(",")}]`;
|
|
32
|
+
if (isRecord(value)) {
|
|
33
|
+
return `{${Object.entries(value)
|
|
34
|
+
.filter(([, item]) => item !== undefined)
|
|
35
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
36
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`)
|
|
37
|
+
.join(",")}}`;
|
|
38
|
+
}
|
|
39
|
+
return JSON.stringify(value);
|
|
40
|
+
};
|
|
41
|
+
const sha256Hex = (input) => createHash("sha256").update(input).digest("hex");
|
|
42
|
+
const hashId = (prefix, value) => `${prefix}-${sha256Hex(stableJson(value)).slice(0, 16)}`;
|
|
43
|
+
const uniqueStrings = (values) => Array.from(new Set(values.filter((value) => typeof value === "string" && value.trim().length > 0))).sort();
|
|
44
|
+
const pathExists = async (value) => {
|
|
45
|
+
try {
|
|
46
|
+
await stat(value);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const readJsonFile = async (filePath) => {
|
|
54
|
+
const raw = await readFile(filePath, "utf8");
|
|
55
|
+
return JSON.parse(raw);
|
|
56
|
+
};
|
|
57
|
+
const candidateIdForManifest = (manifest) => hashId("candidate", {
|
|
58
|
+
exportId: manifest.manifestId,
|
|
59
|
+
checksum: manifest.checksum,
|
|
60
|
+
sourceRecordIds: uniqueStrings(manifest.lineage.sourceRecordIds),
|
|
61
|
+
});
|
|
62
|
+
const validateManifest = (input) => {
|
|
63
|
+
const result = validateCodaliStorageExportManifest(input);
|
|
64
|
+
return result.ok ? result.value : undefined;
|
|
65
|
+
};
|
|
66
|
+
const validateRelease = (input) => {
|
|
67
|
+
const result = validateCodaliImprovementRelease(input);
|
|
68
|
+
return result.ok ? result.value : undefined;
|
|
69
|
+
};
|
|
70
|
+
const directCandidatePath = async (candidateId, explicitPath) => {
|
|
71
|
+
const value = explicitPath ?? candidateId;
|
|
72
|
+
if (!value.trim())
|
|
73
|
+
return undefined;
|
|
74
|
+
const resolved = value.startsWith("file://")
|
|
75
|
+
? fileURLToPath(value)
|
|
76
|
+
: path.resolve(value);
|
|
77
|
+
return (await pathExists(resolved)) ? resolved : undefined;
|
|
78
|
+
};
|
|
79
|
+
const findCandidateFile = async (candidateId, directories) => {
|
|
80
|
+
let inspected = 0;
|
|
81
|
+
for (const directory of directories) {
|
|
82
|
+
const root = path.resolve(directory);
|
|
83
|
+
if (!(await pathExists(root)))
|
|
84
|
+
continue;
|
|
85
|
+
const stack = [root];
|
|
86
|
+
while (stack.length > 0) {
|
|
87
|
+
const current = stack.pop();
|
|
88
|
+
if (!current)
|
|
89
|
+
continue;
|
|
90
|
+
const entries = (await readdir(current, { withFileTypes: true }))
|
|
91
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
const entryPath = path.join(current, entry.name);
|
|
94
|
+
if (entry.isDirectory()) {
|
|
95
|
+
stack.push(entryPath);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!entry.isFile() || !entry.name.endsWith(".json"))
|
|
99
|
+
continue;
|
|
100
|
+
inspected += 1;
|
|
101
|
+
if (inspected > 5000) {
|
|
102
|
+
throw new Error("CODALI_IMPROVEMENT_EVAL_CANDIDATE_SEARCH_LIMIT");
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const parsed = await readJsonFile(entryPath);
|
|
106
|
+
if (candidateMatches(candidateId, parsed)) {
|
|
107
|
+
return { path: entryPath, candidate: parsed };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Ignore non-candidate JSON payloads under object stores.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
};
|
|
118
|
+
const candidateMatches = (candidateId, value) => {
|
|
119
|
+
if (!isRecord(value))
|
|
120
|
+
return false;
|
|
121
|
+
if (value.candidateId === candidateId || value.candidate_id === candidateId)
|
|
122
|
+
return true;
|
|
123
|
+
if (isRecord(value.release) && value.release.candidateId === candidateId)
|
|
124
|
+
return true;
|
|
125
|
+
const manifest = validateManifest(value) ?? validateManifest(value.manifest);
|
|
126
|
+
if (manifest) {
|
|
127
|
+
return manifest.manifestId === candidateId || candidateIdForManifest(manifest) === candidateId;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
};
|
|
131
|
+
const stringArray = (value) => Array.isArray(value)
|
|
132
|
+
? value.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
133
|
+
: undefined;
|
|
134
|
+
const nestedRecord = (record, pathSegments) => {
|
|
135
|
+
let current = record;
|
|
136
|
+
for (const segment of pathSegments) {
|
|
137
|
+
if (!isRecord(current))
|
|
138
|
+
return undefined;
|
|
139
|
+
current = current[segment];
|
|
140
|
+
}
|
|
141
|
+
return isRecord(current) ? current : undefined;
|
|
142
|
+
};
|
|
143
|
+
const collectStringsUnderKeys = (value, keyPattern, output = []) => {
|
|
144
|
+
if (Array.isArray(value)) {
|
|
145
|
+
for (const item of value)
|
|
146
|
+
collectStringsUnderKeys(item, keyPattern, output);
|
|
147
|
+
return output;
|
|
148
|
+
}
|
|
149
|
+
if (!isRecord(value))
|
|
150
|
+
return output;
|
|
151
|
+
for (const [key, item] of Object.entries(value)) {
|
|
152
|
+
if (keyPattern.test(key)) {
|
|
153
|
+
if (typeof item === "string")
|
|
154
|
+
output.push(item);
|
|
155
|
+
if (Array.isArray(item)) {
|
|
156
|
+
output.push(...item.filter((entry) => typeof entry === "string"));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
collectStringsUnderKeys(item, keyPattern, output);
|
|
160
|
+
}
|
|
161
|
+
return output;
|
|
162
|
+
};
|
|
163
|
+
const collectChangedFilePaths = (record) => {
|
|
164
|
+
const paths = [
|
|
165
|
+
...collectStringsUnderKeys(record, /^(relativePath|path|filePath|targetPath)$/u),
|
|
166
|
+
...(stringArray(record?.changedFilePaths) ?? []),
|
|
167
|
+
...(stringArray(record?.approvedFilePaths) ?? []),
|
|
168
|
+
];
|
|
169
|
+
const metadata = nestedRecord(record, ["release", "metadata"]) ?? nestedRecord(record, ["metadata"]);
|
|
170
|
+
const writePlanTargets = nestedRecord(metadata, ["writePlan"])?.targets;
|
|
171
|
+
if (Array.isArray(writePlanTargets)) {
|
|
172
|
+
for (const target of writePlanTargets) {
|
|
173
|
+
if (isRecord(target) && typeof target.relativePath === "string") {
|
|
174
|
+
paths.push(target.relativePath);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const generatedArtifacts = record?.generatedArtifacts ??
|
|
179
|
+
metadata?.generatedArtifacts ??
|
|
180
|
+
nestedRecord(record, ["release", "metadata"])?.generatedArtifacts;
|
|
181
|
+
if (Array.isArray(generatedArtifacts)) {
|
|
182
|
+
for (const artifact of generatedArtifacts) {
|
|
183
|
+
if (isRecord(artifact) && typeof artifact.relativePath === "string") {
|
|
184
|
+
paths.push(artifact.relativePath);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return uniqueStrings(paths)
|
|
189
|
+
.filter((item) => !item.startsWith("file://"))
|
|
190
|
+
.filter((item) => item !== "." && !item.endsWith(".jsonl"));
|
|
191
|
+
};
|
|
192
|
+
const collectApprovedPaths = (record, inputApprovedPaths) => {
|
|
193
|
+
const metadata = nestedRecord(record, ["release", "metadata"]) ?? nestedRecord(record, ["metadata"]);
|
|
194
|
+
const writePlan = nestedRecord(metadata, ["writePlan"]);
|
|
195
|
+
const workspace = nestedRecord(metadata, ["candidateWorkspace"]);
|
|
196
|
+
return uniqueStrings([
|
|
197
|
+
...(inputApprovedPaths ?? []),
|
|
198
|
+
...(stringArray(record?.approvedPaths) ?? []),
|
|
199
|
+
...(stringArray(writePlan?.approvedPaths) ?? []),
|
|
200
|
+
...(stringArray(workspace?.approvedPaths) ?? []),
|
|
201
|
+
...DEFAULT_CODALI_CANDIDATE_RELEASE_APPROVED_PATHS,
|
|
202
|
+
]);
|
|
203
|
+
};
|
|
204
|
+
const collectChecks = (record, keys) => {
|
|
205
|
+
const checks = [];
|
|
206
|
+
for (const key of keys) {
|
|
207
|
+
const value = record?.[key] ?? nestedRecord(record, ["validation"])?.[key];
|
|
208
|
+
if (!Array.isArray(value))
|
|
209
|
+
continue;
|
|
210
|
+
for (const item of value) {
|
|
211
|
+
if (!isRecord(item))
|
|
212
|
+
continue;
|
|
213
|
+
const status = typeof item.status === "string" ? item.status : undefined;
|
|
214
|
+
if (status !== "passed" &&
|
|
215
|
+
status !== "failed" &&
|
|
216
|
+
status !== "skipped" &&
|
|
217
|
+
status !== "warning") {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
checks.push({
|
|
221
|
+
name: typeof item.name === "string" ? item.name : key,
|
|
222
|
+
status,
|
|
223
|
+
reason: typeof item.reason === "string" ? item.reason : undefined,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return checks;
|
|
228
|
+
};
|
|
229
|
+
const normalizeCandidateEvidence = async (input) => {
|
|
230
|
+
if (input.candidate) {
|
|
231
|
+
return candidateEvidenceFromRecord(input.candidateId, input.candidate, "provided", undefined, input.approvedPaths);
|
|
232
|
+
}
|
|
233
|
+
const directPath = await directCandidatePath(input.candidateId, input.candidatePath);
|
|
234
|
+
if (directPath) {
|
|
235
|
+
const parsed = await readJsonFile(directPath);
|
|
236
|
+
return candidateEvidenceFromRecord(input.candidateId, parsed, "candidate_file", directPath, input.approvedPaths);
|
|
237
|
+
}
|
|
238
|
+
const found = await findCandidateFile(input.candidateId, input.candidateDirectories?.length
|
|
239
|
+
? input.candidateDirectories
|
|
240
|
+
: DEFAULT_CANDIDATE_DIRECTORIES);
|
|
241
|
+
if (found) {
|
|
242
|
+
const source = validateManifest(found.candidate) ? "export_manifest" : "candidate_file";
|
|
243
|
+
return candidateEvidenceFromRecord(input.candidateId, found.candidate, source, found.path, input.approvedPaths);
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
candidateId: input.candidateId,
|
|
247
|
+
source: "missing",
|
|
248
|
+
changedFilePaths: [],
|
|
249
|
+
approvedPaths: collectApprovedPaths(undefined, input.approvedPaths),
|
|
250
|
+
deterministicChecks: [],
|
|
251
|
+
privacyChecks: [],
|
|
252
|
+
policyChecks: [],
|
|
253
|
+
replayChecks: [],
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
const candidateEvidenceFromRecord = (candidateId, value, source, filePath, inputApprovedPaths) => {
|
|
257
|
+
const record = isRecord(value) ? value : undefined;
|
|
258
|
+
const manifest = validateManifest(value) ??
|
|
259
|
+
validateManifest(record?.manifest) ??
|
|
260
|
+
validateManifest(record?.sourceManifest);
|
|
261
|
+
const release = validateRelease(value) ?? validateRelease(record?.release);
|
|
262
|
+
const toolPolicy = isRecord(record?.toolPolicy)
|
|
263
|
+
? record.toolPolicy
|
|
264
|
+
: nestedRecord(record, ["policy"]) ?? nestedRecord(record, ["metadata", "toolPolicy"]);
|
|
265
|
+
const allowedTools = uniqueStrings([
|
|
266
|
+
...(stringArray(toolPolicy?.allowedTools) ?? []),
|
|
267
|
+
...(stringArray(toolPolicy?.allowed_tools) ?? []),
|
|
268
|
+
...collectStringsUnderKeys(record, /^(allowedTools|allowed_tools|runtimeTools|runtime_tools)$/u),
|
|
269
|
+
]);
|
|
270
|
+
const deniedTools = uniqueStrings([
|
|
271
|
+
...(stringArray(toolPolicy?.deniedTools) ?? []),
|
|
272
|
+
...(stringArray(toolPolicy?.denied_tools) ?? []),
|
|
273
|
+
]);
|
|
274
|
+
const destructiveToolsAllowed = toolPolicy?.destructiveToolsAllowed === true ||
|
|
275
|
+
toolPolicy?.allowDestructiveTools === true ||
|
|
276
|
+
toolPolicy?.allow_destructive_tools === true;
|
|
277
|
+
return {
|
|
278
|
+
candidateId: release?.candidateId ?? record?.candidateId ?? candidateId,
|
|
279
|
+
source,
|
|
280
|
+
path: filePath,
|
|
281
|
+
raw: record,
|
|
282
|
+
release,
|
|
283
|
+
manifest,
|
|
284
|
+
changedFilePaths: collectChangedFilePaths(record),
|
|
285
|
+
approvedPaths: collectApprovedPaths(record, inputApprovedPaths),
|
|
286
|
+
deterministicChecks: collectChecks(record, ["deterministicTests", "deterministic_tests", "tests"]),
|
|
287
|
+
privacyChecks: collectChecks(record, ["privacyChecks", "privacy_checks"]),
|
|
288
|
+
policyChecks: collectChecks(record, ["policyChecks", "policy_checks"]),
|
|
289
|
+
replayChecks: collectChecks(record, ["replayChecks", "replay_checks", "replayFixtures", "replay_fixtures"]),
|
|
290
|
+
allowedTools,
|
|
291
|
+
deniedTools,
|
|
292
|
+
destructiveToolsAllowed,
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
const gate = (gateId, gateType, severity, status, reasons, metadata) => ({
|
|
296
|
+
gateId,
|
|
297
|
+
gateType,
|
|
298
|
+
severity,
|
|
299
|
+
status,
|
|
300
|
+
passed: status === "passed" || status === "warning",
|
|
301
|
+
score: status === "passed" ? 1 : status === "warning" ? 0.5 : 0,
|
|
302
|
+
reasons,
|
|
303
|
+
...(metadata ? { metadata } : {}),
|
|
304
|
+
});
|
|
305
|
+
const evaluateCheckGate = (gateId, gateType, checks, missingReason) => {
|
|
306
|
+
if (checks.length === 0)
|
|
307
|
+
return gate(gateId, gateType, "hard", "skipped", [missingReason]);
|
|
308
|
+
const failed = checks.filter((check) => check.status === "failed");
|
|
309
|
+
if (failed.length) {
|
|
310
|
+
return gate(gateId, gateType, "hard", "failed", failed.map((check) => check.reason ?? `${check.name}_failed`), {
|
|
311
|
+
checks,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
const skipped = checks.filter((check) => check.status === "skipped");
|
|
315
|
+
if (skipped.length) {
|
|
316
|
+
return gate(gateId, gateType, "hard", "skipped", skipped.map((check) => check.reason ?? `${check.name}_skipped`), {
|
|
317
|
+
checks,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const warnings = checks.filter((check) => check.status === "warning");
|
|
321
|
+
if (warnings.length) {
|
|
322
|
+
return gate(gateId, gateType, "hard", "warning", warnings.map((check) => check.reason ?? `${check.name}_warning`), {
|
|
323
|
+
checks,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return gate(gateId, gateType, "hard", "passed", [], { checks });
|
|
327
|
+
};
|
|
328
|
+
const allManifestRefs = (manifest) => {
|
|
329
|
+
if (!manifest)
|
|
330
|
+
return [];
|
|
331
|
+
return [
|
|
332
|
+
...manifest.artifactRefs,
|
|
333
|
+
...manifest.records.flatMap((record) => record.objectRef ? [record.objectRef] : []),
|
|
334
|
+
];
|
|
335
|
+
};
|
|
336
|
+
const ownerScopeKey = (ref) => `${ref.ownerScope.tenantHash}:${ref.ownerScope.productId}:${ref.ownerScope.deploymentId ?? ""}`;
|
|
337
|
+
const evaluatePrivacyMetadataGate = (evidence) => {
|
|
338
|
+
const summary = evidence.manifest?.privacySummary;
|
|
339
|
+
if (!summary)
|
|
340
|
+
return gate("privacy_metadata", "privacy", "hard", "skipped", ["privacy_metadata_not_available"]);
|
|
341
|
+
const missing = [
|
|
342
|
+
typeof summary.recordCount === "number" ? undefined : "privacy_record_count_missing",
|
|
343
|
+
typeof summary.containsSecrets === "boolean" ? undefined : "privacy_secret_flag_missing",
|
|
344
|
+
typeof summary.containsCustomerData === "boolean" ? undefined : "privacy_customer_data_flag_missing",
|
|
345
|
+
typeof summary.exportAllowedCount === "number" ? undefined : "privacy_export_count_missing",
|
|
346
|
+
].filter((item) => Boolean(item));
|
|
347
|
+
if (missing.length)
|
|
348
|
+
return gate("privacy_metadata", "privacy", "hard", "failed", missing);
|
|
349
|
+
if (summary.containsSecrets)
|
|
350
|
+
return gate("privacy_metadata", "privacy", "hard", "failed", ["privacy_summary_contains_secrets"]);
|
|
351
|
+
return gate("privacy_metadata", "privacy", "hard", "passed", [], {
|
|
352
|
+
recordCount: summary.recordCount,
|
|
353
|
+
exportAllowedCount: summary.exportAllowedCount,
|
|
354
|
+
trainingAllowedCount: summary.trainingAllowedCount,
|
|
355
|
+
});
|
|
356
|
+
};
|
|
357
|
+
const evaluateDeletionGroupsGate = (evidence) => {
|
|
358
|
+
const snapshot = evidence.manifest?.deletionGroupSnapshot;
|
|
359
|
+
if (!snapshot)
|
|
360
|
+
return gate("deletion_groups", "privacy", "hard", "skipped", ["deletion_group_snapshot_not_available"]);
|
|
361
|
+
const missingRecordIds = (evidence.manifest?.lineage.sourceRecordIds ?? [])
|
|
362
|
+
.filter((recordId) => !(snapshot.byRecordId[recordId]?.length));
|
|
363
|
+
if (!snapshot.deletionGroupIds.length || missingRecordIds.length) {
|
|
364
|
+
return gate("deletion_groups", "privacy", "hard", "failed", [
|
|
365
|
+
...(!snapshot.deletionGroupIds.length ? ["deletion_group_ids_empty"] : []),
|
|
366
|
+
...missingRecordIds.map((recordId) => `deletion_group_missing_for_record:${recordId}`),
|
|
367
|
+
]);
|
|
368
|
+
}
|
|
369
|
+
return gate("deletion_groups", "privacy", "hard", "passed", [], {
|
|
370
|
+
deletionGroupCount: snapshot.deletionGroupIds.length,
|
|
371
|
+
});
|
|
372
|
+
};
|
|
373
|
+
const evaluateTenantScopeGate = (evidence) => {
|
|
374
|
+
const refs = allManifestRefs(evidence.manifest);
|
|
375
|
+
if (!refs.length)
|
|
376
|
+
return gate("tenant_scope", "privacy", "hard", "skipped", ["tenant_scope_refs_not_available"]);
|
|
377
|
+
const scopeKeys = uniqueStrings(refs.map(ownerScopeKey));
|
|
378
|
+
const releaseScope = evidence.release?.scope;
|
|
379
|
+
const releaseMismatch = releaseScope
|
|
380
|
+
? refs.some((ref) => ref.ownerScope.tenantHash !== releaseScope.tenantHash ||
|
|
381
|
+
ref.ownerScope.productId !== releaseScope.productId)
|
|
382
|
+
: false;
|
|
383
|
+
if (scopeKeys.length !== 1 || releaseMismatch) {
|
|
384
|
+
return gate("tenant_scope", "privacy", "hard", "failed", [
|
|
385
|
+
...(scopeKeys.length !== 1 ? ["multiple_tenant_scopes_detected"] : []),
|
|
386
|
+
...(releaseMismatch ? ["release_scope_mismatches_manifest_scope"] : []),
|
|
387
|
+
], { scopeKeys });
|
|
388
|
+
}
|
|
389
|
+
return gate("tenant_scope", "privacy", "hard", "passed", [], { scopeKey: scopeKeys[0] });
|
|
390
|
+
};
|
|
391
|
+
const verifyFileRef = async (ref) => {
|
|
392
|
+
if (!ref.uri?.startsWith("file://"))
|
|
393
|
+
return undefined;
|
|
394
|
+
const filePath = fileURLToPath(ref.uri);
|
|
395
|
+
const raw = await readFile(filePath);
|
|
396
|
+
const actualHash = `sha256:${sha256Hex(raw)}`;
|
|
397
|
+
if (actualHash !== ref.contentHash)
|
|
398
|
+
return `object_checksum_mismatch:${ref.refId}`;
|
|
399
|
+
if (raw.byteLength !== ref.byteSize)
|
|
400
|
+
return `object_size_mismatch:${ref.refId}`;
|
|
401
|
+
return undefined;
|
|
402
|
+
};
|
|
403
|
+
const evaluateObjectChecksumsGate = async (evidence) => {
|
|
404
|
+
const refs = allManifestRefs(evidence.manifest);
|
|
405
|
+
if (!evidence.manifest || !refs.length) {
|
|
406
|
+
return gate("object_checksums", "privacy", "hard", "skipped", ["object_refs_not_available"]);
|
|
407
|
+
}
|
|
408
|
+
const reasons = [];
|
|
409
|
+
const checksumRef = evidence.manifest.artifactRefs.find((ref) => ref.contentHash === evidence.manifest?.checksum);
|
|
410
|
+
if (!checksumRef)
|
|
411
|
+
reasons.push("manifest_checksum_artifact_missing");
|
|
412
|
+
for (const ref of refs) {
|
|
413
|
+
if (!ref.contentHash.startsWith("sha256:"))
|
|
414
|
+
reasons.push(`object_hash_not_sha256:${ref.refId}`);
|
|
415
|
+
if (!Number.isInteger(ref.byteSize) || ref.byteSize <= 0)
|
|
416
|
+
reasons.push(`object_byte_size_invalid:${ref.refId}`);
|
|
417
|
+
try {
|
|
418
|
+
const fileReason = await verifyFileRef(ref);
|
|
419
|
+
if (fileReason)
|
|
420
|
+
reasons.push(fileReason);
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
reasons.push(`object_file_unreadable:${ref.refId}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return reasons.length
|
|
427
|
+
? gate("object_checksums", "privacy", "hard", "failed", uniqueStrings(reasons))
|
|
428
|
+
: gate("object_checksums", "privacy", "hard", "passed", [], { objectRefCount: refs.length });
|
|
429
|
+
};
|
|
430
|
+
const evaluateToolPolicyGate = (evidence) => {
|
|
431
|
+
if (!evidence.allowedTools?.length && !evidence.deniedTools?.length && evidence.destructiveToolsAllowed === undefined) {
|
|
432
|
+
return gate("tool_policy", "policy", "hard", "warning", ["explicit_tool_policy_not_present"]);
|
|
433
|
+
}
|
|
434
|
+
if (evidence.destructiveToolsAllowed) {
|
|
435
|
+
return gate("tool_policy", "policy", "hard", "failed", ["destructive_tools_allowed_by_policy"]);
|
|
436
|
+
}
|
|
437
|
+
return gate("tool_policy", "policy", "hard", "passed", [], {
|
|
438
|
+
allowedTools: evidence.allowedTools,
|
|
439
|
+
deniedTools: evidence.deniedTools,
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
const evaluateNoRiskyToolsGate = (evidence) => {
|
|
443
|
+
const tools = uniqueStrings([...(evidence.allowedTools ?? [])]);
|
|
444
|
+
if (!tools.length) {
|
|
445
|
+
return gate("no_shell_write_destructive_tools", "policy", "hard", "skipped", ["no_runtime_tool_list_present"]);
|
|
446
|
+
}
|
|
447
|
+
const riskyTools = tools.filter((toolName) => RISKY_TOOL_PATTERN.test(toolName));
|
|
448
|
+
return riskyTools.length
|
|
449
|
+
? gate("no_shell_write_destructive_tools", "policy", "hard", "failed", riskyTools.map((toolName) => `risky_tool_allowed:${toolName}`))
|
|
450
|
+
: gate("no_shell_write_destructive_tools", "policy", "hard", "passed", [], { allowedTools: tools });
|
|
451
|
+
};
|
|
452
|
+
const evaluateNoCrossTenantReplayGate = (evidence) => {
|
|
453
|
+
const replayRefs = evidence.manifest?.artifactRefs
|
|
454
|
+
.filter((ref) => isRecord(ref.metadata) && ref.metadata.artifactType === "replay_fixture") ?? [];
|
|
455
|
+
if (!replayRefs.length && !evidence.replayChecks.length) {
|
|
456
|
+
return gate("no_cross_tenant_replay", "replay", "hard", "skipped", ["replay_fixture_not_available"]);
|
|
457
|
+
}
|
|
458
|
+
const replayScopeKeys = uniqueStrings(replayRefs.map(ownerScopeKey));
|
|
459
|
+
if (replayScopeKeys.length > 1) {
|
|
460
|
+
return gate("no_cross_tenant_replay", "replay", "hard", "failed", ["cross_tenant_replay_scope_detected"], {
|
|
461
|
+
replayScopeKeys,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return gate("no_cross_tenant_replay", "replay", "hard", "passed", [], {
|
|
465
|
+
replayFixtureCount: replayRefs.length || evidence.replayChecks.length,
|
|
466
|
+
});
|
|
467
|
+
};
|
|
468
|
+
const evaluateLineageGate = (evidence) => {
|
|
469
|
+
const lineage = evidence.manifest?.lineage;
|
|
470
|
+
if (!lineage)
|
|
471
|
+
return gate("lineage_validity", "release", "hard", "skipped", ["lineage_not_available"]);
|
|
472
|
+
const reasons = [
|
|
473
|
+
!lineage.sourceRecordIds.length ? "lineage_source_records_empty" : undefined,
|
|
474
|
+
!lineage.sourceObjectHashes.length ? "lineage_source_object_hashes_empty" : undefined,
|
|
475
|
+
evidence.manifest && lineage.sourceRecordIds.length !== evidence.manifest.recordCount
|
|
476
|
+
? "lineage_record_count_mismatch"
|
|
477
|
+
: undefined,
|
|
478
|
+
].filter((item) => Boolean(item));
|
|
479
|
+
return reasons.length
|
|
480
|
+
? gate("lineage_validity", "release", "hard", "failed", reasons)
|
|
481
|
+
: gate("lineage_validity", "release", "hard", "passed", [], {
|
|
482
|
+
sourceRecordCount: lineage.sourceRecordIds.length,
|
|
483
|
+
sourceObjectHashCount: lineage.sourceObjectHashes.length,
|
|
484
|
+
});
|
|
485
|
+
};
|
|
486
|
+
const normalizeRelativePath = (value) => value.split(path.sep).join("/").replace(/^\.\//u, "");
|
|
487
|
+
const isApprovedPath = (relativePath, approvedPaths) => {
|
|
488
|
+
const normalized = normalizeRelativePath(relativePath);
|
|
489
|
+
return approvedPaths.some((approvedPath) => {
|
|
490
|
+
const approved = normalizeRelativePath(approvedPath);
|
|
491
|
+
return approved.endsWith("/")
|
|
492
|
+
? normalized.startsWith(approved)
|
|
493
|
+
: normalized === approved;
|
|
494
|
+
});
|
|
495
|
+
};
|
|
496
|
+
const evaluateApprovedFilePathsGate = (evidence) => {
|
|
497
|
+
if (!evidence.changedFilePaths.length) {
|
|
498
|
+
return gate("approved_file_paths", "release", "hard", "skipped", ["candidate_write_targets_not_available"]);
|
|
499
|
+
}
|
|
500
|
+
const unapproved = evidence.changedFilePaths
|
|
501
|
+
.filter((targetPath) => !isApprovedPath(targetPath, evidence.approvedPaths));
|
|
502
|
+
return unapproved.length
|
|
503
|
+
? gate("approved_file_paths", "release", "hard", "failed", unapproved.map((targetPath) => `target_path_not_approved:${targetPath}`), {
|
|
504
|
+
approvedPaths: evidence.approvedPaths,
|
|
505
|
+
})
|
|
506
|
+
: gate("approved_file_paths", "release", "hard", "passed", [], {
|
|
507
|
+
approvedPaths: evidence.approvedPaths,
|
|
508
|
+
changedFilePaths: evidence.changedFilePaths,
|
|
509
|
+
});
|
|
510
|
+
};
|
|
511
|
+
const toContractGate = (candidateId, createdAt, result) => ({
|
|
512
|
+
schemaVersion: CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION,
|
|
513
|
+
gateId: `gate-${result.gateId}`,
|
|
514
|
+
candidateId,
|
|
515
|
+
gateType: result.gateType,
|
|
516
|
+
status: result.status,
|
|
517
|
+
required: result.severity === "hard",
|
|
518
|
+
passed: result.passed,
|
|
519
|
+
createdAt,
|
|
520
|
+
score: result.score,
|
|
521
|
+
...(result.reasons.length ? { reasons: result.reasons } : {}),
|
|
522
|
+
metadata: {
|
|
523
|
+
schemaVersion: CODALI_IMPROVEMENT_EVAL_RUNNER_SCHEMA_VERSION,
|
|
524
|
+
gateId: result.gateId,
|
|
525
|
+
severity: result.severity,
|
|
526
|
+
status: result.status,
|
|
527
|
+
...(result.metadata ?? {}),
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
const scorecardStatusFor = (gates) => {
|
|
531
|
+
if (gates.some((item) => item.severity === "hard" && (item.status === "failed" || item.status === "skipped"))) {
|
|
532
|
+
return "blocked";
|
|
533
|
+
}
|
|
534
|
+
if (gates.some((item) => item.status === "failed"))
|
|
535
|
+
return "failed";
|
|
536
|
+
if (gates.some((item) => item.status === "warning" || item.status === "skipped"))
|
|
537
|
+
return "degraded";
|
|
538
|
+
return "passed";
|
|
539
|
+
};
|
|
540
|
+
const releaseLevelFor = (release) => release?.releaseLevel ?? 0;
|
|
541
|
+
const releaseScopeFor = (release) => ({
|
|
542
|
+
tenantHash: release?.scope.tenantHash ?? "local_tenant",
|
|
543
|
+
productId: release?.scope.productId ?? "local_product",
|
|
544
|
+
...(release?.scope.deploymentId ? { deploymentId: release.scope.deploymentId } : {}),
|
|
545
|
+
});
|
|
546
|
+
const buildReleaseApproval = (candidateId, release, gates) => {
|
|
547
|
+
const failedHard = gates.filter((item) => item.severity === "hard" && item.status === "failed");
|
|
548
|
+
const skippedHard = gates.filter((item) => item.severity === "hard" && item.status === "skipped");
|
|
549
|
+
const warningGates = gates.filter((item) => item.status === "warning");
|
|
550
|
+
const releaseLevel = releaseLevelFor(release);
|
|
551
|
+
const hardBlocked = [...failedHard, ...skippedHard];
|
|
552
|
+
const blockedReasons = uniqueStrings(hardBlocked.flatMap((item) => item.reasons.length
|
|
553
|
+
? item.reasons.map((reason) => `${item.gateId}:${reason}`)
|
|
554
|
+
: [`${item.gateId}:${item.status}`]));
|
|
555
|
+
const warningReasons = uniqueStrings(warningGates.flatMap((item) => item.reasons.length
|
|
556
|
+
? item.reasons.map((reason) => `${item.gateId}:${reason}`)
|
|
557
|
+
: [`${item.gateId}:warning`]));
|
|
558
|
+
const releasable = hardBlocked.length === 0 && warningGates.length === 0;
|
|
559
|
+
return {
|
|
560
|
+
candidateId,
|
|
561
|
+
releaseLevel,
|
|
562
|
+
tagAllowed: releasable && releaseLevel >= 3,
|
|
563
|
+
publishAllowed: releasable && releaseLevel >= 4,
|
|
564
|
+
failedHardGateIds: failedHard.map((item) => item.gateId),
|
|
565
|
+
skippedHardGateIds: skippedHard.map((item) => item.gateId),
|
|
566
|
+
warningGateIds: warningGates.map((item) => item.gateId),
|
|
567
|
+
blockedReasons,
|
|
568
|
+
warningReasons,
|
|
569
|
+
requiresManualReview: skippedHard.length > 0 || warningGates.length > 0,
|
|
570
|
+
};
|
|
571
|
+
};
|
|
572
|
+
export const buildCodaliImprovementEvalScorecard = async (input) => {
|
|
573
|
+
const now = input.now ?? (() => new Date());
|
|
574
|
+
const createdAt = now().toISOString();
|
|
575
|
+
const evidence = await normalizeCandidateEvidence(input);
|
|
576
|
+
const gates = [
|
|
577
|
+
evaluateCheckGate("deterministic_tests", "eval", evidence.deterministicChecks, "deterministic_test_results_not_available"),
|
|
578
|
+
evaluateCheckGate("replay_fixtures", "replay", evidence.replayChecks.length
|
|
579
|
+
? evidence.replayChecks
|
|
580
|
+
: evidence.manifest?.artifactRefs.some((ref) => isRecord(ref.metadata) && ref.metadata.artifactType === "replay_fixture")
|
|
581
|
+
? [{ name: "replay_fixture_present", status: "passed" }]
|
|
582
|
+
: [], "replay_fixture_results_not_available"),
|
|
583
|
+
evaluatePrivacyMetadataGate(evidence),
|
|
584
|
+
evaluateDeletionGroupsGate(evidence),
|
|
585
|
+
evaluateTenantScopeGate(evidence),
|
|
586
|
+
await evaluateObjectChecksumsGate(evidence),
|
|
587
|
+
evaluateToolPolicyGate(evidence),
|
|
588
|
+
evaluateNoRiskyToolsGate(evidence),
|
|
589
|
+
evaluateNoCrossTenantReplayGate(evidence),
|
|
590
|
+
evaluateLineageGate(evidence),
|
|
591
|
+
evaluateApprovedFilePathsGate(evidence),
|
|
592
|
+
];
|
|
593
|
+
const releaseApproval = buildReleaseApproval(evidence.candidateId, evidence.release, gates);
|
|
594
|
+
const scores = Object.fromEntries(gates.map((item) => [item.gateId, item.score]));
|
|
595
|
+
const scorecard = {
|
|
596
|
+
schemaVersion: CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION,
|
|
597
|
+
scorecardId: hashId("scorecard", {
|
|
598
|
+
candidateId: evidence.candidateId,
|
|
599
|
+
gates: gates.map((item) => [item.gateId, item.status, item.reasons]),
|
|
600
|
+
}),
|
|
601
|
+
candidateId: evidence.candidateId,
|
|
602
|
+
status: scorecardStatusFor(gates),
|
|
603
|
+
gates: gates.map((item) => toContractGate(evidence.candidateId, createdAt, item)),
|
|
604
|
+
scores,
|
|
605
|
+
createdAt,
|
|
606
|
+
summary: releaseApproval.blockedReasons.length
|
|
607
|
+
? "Release approval is blocked by hard gate evidence."
|
|
608
|
+
: releaseApproval.warningReasons.length
|
|
609
|
+
? "Release approval requires manual review for warnings."
|
|
610
|
+
: "All evaluated release gates passed.",
|
|
611
|
+
metadata: {
|
|
612
|
+
schemaVersion: CODALI_IMPROVEMENT_EVAL_RUNNER_SCHEMA_VERSION,
|
|
613
|
+
candidateSource: evidence.source,
|
|
614
|
+
...(evidence.path ? { candidatePath: evidence.path } : {}),
|
|
615
|
+
releaseApproval,
|
|
616
|
+
manifestId: evidence.manifest?.manifestId,
|
|
617
|
+
releaseId: evidence.release?.releaseId,
|
|
618
|
+
approvedPaths: evidence.approvedPaths,
|
|
619
|
+
changedFilePaths: evidence.changedFilePaths,
|
|
620
|
+
},
|
|
621
|
+
};
|
|
622
|
+
const validation = validateCodaliImprovementScorecard(scorecard);
|
|
623
|
+
if (!validation.ok) {
|
|
624
|
+
throw new Error(`CODALI_IMPROVEMENT_EVAL_SCORECARD_INVALID: ${validation.issues
|
|
625
|
+
.map((issue) => `${issue.path}:${issue.code}`)
|
|
626
|
+
.join("; ")}`);
|
|
627
|
+
}
|
|
628
|
+
const storagePayload = {
|
|
629
|
+
schemaVersion: CODALI_IMPROVEMENT_EVAL_RUNNER_SCHEMA_VERSION,
|
|
630
|
+
scorecard,
|
|
631
|
+
releaseApproval,
|
|
632
|
+
blockedReasons: releaseApproval.blockedReasons,
|
|
633
|
+
warnings: releaseApproval.warningReasons,
|
|
634
|
+
releaseScope: releaseScopeFor(evidence.release),
|
|
635
|
+
};
|
|
636
|
+
return {
|
|
637
|
+
schemaVersion: CODALI_IMPROVEMENT_EVAL_RUNNER_SCHEMA_VERSION,
|
|
638
|
+
candidateId: evidence.candidateId,
|
|
639
|
+
candidateSource: evidence.source,
|
|
640
|
+
...(evidence.path ? { candidatePath: evidence.path } : {}),
|
|
641
|
+
scorecard: validation.value,
|
|
642
|
+
gates,
|
|
643
|
+
releaseApproval,
|
|
644
|
+
blockedReasons: releaseApproval.blockedReasons,
|
|
645
|
+
warnings: releaseApproval.warningReasons,
|
|
646
|
+
storagePayload,
|
|
647
|
+
};
|
|
648
|
+
};
|