@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,1450 @@
|
|
|
1
|
+
export const CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION = "codali.improvement.v1";
|
|
2
|
+
export const CODALI_IMPROVEMENT_CLI_JSON_SCHEMA_VERSION = "codali.improvement.cli.v1";
|
|
3
|
+
export const CODALI_IMPROVEMENT_RELEASE_LEVELS = [0, 1, 2, 3, 4];
|
|
4
|
+
export const CODALI_IMPROVEMENT_ARTIFACT_TYPES = [
|
|
5
|
+
"analysis_report",
|
|
6
|
+
"gate_report",
|
|
7
|
+
"scorecard",
|
|
8
|
+
"eval_replay_fixture",
|
|
9
|
+
"eval_suite",
|
|
10
|
+
"prompt_patch",
|
|
11
|
+
"schema_patch",
|
|
12
|
+
"tool_metadata_patch",
|
|
13
|
+
"branch_patch",
|
|
14
|
+
"prerelease_tag",
|
|
15
|
+
"canary_tag",
|
|
16
|
+
"stable_npm_release",
|
|
17
|
+
"release_notes",
|
|
18
|
+
];
|
|
19
|
+
export const CODALI_IMPROVEMENT_POLICY_ACTIONS = [
|
|
20
|
+
"analyze",
|
|
21
|
+
"add_eval_replay",
|
|
22
|
+
"branch_metadata",
|
|
23
|
+
"create_prerelease_tag",
|
|
24
|
+
"publish_stable",
|
|
25
|
+
"export",
|
|
26
|
+
"training",
|
|
27
|
+
"auto_tag",
|
|
28
|
+
"auto_publish",
|
|
29
|
+
];
|
|
30
|
+
export const CODALI_IMPROVEMENT_CLI_JSON_OUTPUT_TYPES = [
|
|
31
|
+
"improvement.policy",
|
|
32
|
+
"improvement.policy_decision",
|
|
33
|
+
"improvement.release_levels",
|
|
34
|
+
"improvement.run",
|
|
35
|
+
"improvement.candidate",
|
|
36
|
+
"improvement.propose",
|
|
37
|
+
"improvement.artifact",
|
|
38
|
+
"improvement.gate",
|
|
39
|
+
"improvement.scorecard",
|
|
40
|
+
"improvement.release",
|
|
41
|
+
"improvement.outcome",
|
|
42
|
+
"improvement.monitor",
|
|
43
|
+
"improvement.inspect",
|
|
44
|
+
];
|
|
45
|
+
export const CODALI_IMPROVEMENT_RELEASE_LEVEL_CONTRACTS = {
|
|
46
|
+
0: {
|
|
47
|
+
level: 0,
|
|
48
|
+
name: "analysis_only",
|
|
49
|
+
description: "analysis only",
|
|
50
|
+
minAction: "analyze",
|
|
51
|
+
allowedActions: ["analyze"],
|
|
52
|
+
allowedArtifactTypes: ["analysis_report", "gate_report", "scorecard"],
|
|
53
|
+
},
|
|
54
|
+
1: {
|
|
55
|
+
level: 1,
|
|
56
|
+
name: "eval_replay_additions",
|
|
57
|
+
description: "eval/replay additions",
|
|
58
|
+
minAction: "add_eval_replay",
|
|
59
|
+
allowedActions: ["analyze", "add_eval_replay", "export"],
|
|
60
|
+
allowedArtifactTypes: [
|
|
61
|
+
"analysis_report",
|
|
62
|
+
"gate_report",
|
|
63
|
+
"scorecard",
|
|
64
|
+
"eval_replay_fixture",
|
|
65
|
+
"eval_suite",
|
|
66
|
+
"release_notes",
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
2: {
|
|
70
|
+
level: 2,
|
|
71
|
+
name: "metadata_branch",
|
|
72
|
+
description: "prompt/schema/tool metadata branch",
|
|
73
|
+
minAction: "branch_metadata",
|
|
74
|
+
allowedActions: [
|
|
75
|
+
"analyze",
|
|
76
|
+
"add_eval_replay",
|
|
77
|
+
"branch_metadata",
|
|
78
|
+
"export",
|
|
79
|
+
"training",
|
|
80
|
+
],
|
|
81
|
+
allowedArtifactTypes: [
|
|
82
|
+
"analysis_report",
|
|
83
|
+
"gate_report",
|
|
84
|
+
"scorecard",
|
|
85
|
+
"eval_replay_fixture",
|
|
86
|
+
"eval_suite",
|
|
87
|
+
"prompt_patch",
|
|
88
|
+
"schema_patch",
|
|
89
|
+
"tool_metadata_patch",
|
|
90
|
+
"branch_patch",
|
|
91
|
+
"release_notes",
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
3: {
|
|
95
|
+
level: 3,
|
|
96
|
+
name: "prerelease_canary_tag",
|
|
97
|
+
description: "prerelease/canary tag",
|
|
98
|
+
minAction: "create_prerelease_tag",
|
|
99
|
+
allowedActions: [
|
|
100
|
+
"analyze",
|
|
101
|
+
"add_eval_replay",
|
|
102
|
+
"branch_metadata",
|
|
103
|
+
"create_prerelease_tag",
|
|
104
|
+
"export",
|
|
105
|
+
"training",
|
|
106
|
+
"auto_tag",
|
|
107
|
+
],
|
|
108
|
+
allowedArtifactTypes: [
|
|
109
|
+
"analysis_report",
|
|
110
|
+
"gate_report",
|
|
111
|
+
"scorecard",
|
|
112
|
+
"eval_replay_fixture",
|
|
113
|
+
"eval_suite",
|
|
114
|
+
"prompt_patch",
|
|
115
|
+
"schema_patch",
|
|
116
|
+
"tool_metadata_patch",
|
|
117
|
+
"branch_patch",
|
|
118
|
+
"prerelease_tag",
|
|
119
|
+
"canary_tag",
|
|
120
|
+
"release_notes",
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
4: {
|
|
124
|
+
level: 4,
|
|
125
|
+
name: "stable_npm_release",
|
|
126
|
+
description: "stable npm release",
|
|
127
|
+
minAction: "publish_stable",
|
|
128
|
+
allowedActions: [
|
|
129
|
+
"analyze",
|
|
130
|
+
"add_eval_replay",
|
|
131
|
+
"branch_metadata",
|
|
132
|
+
"create_prerelease_tag",
|
|
133
|
+
"publish_stable",
|
|
134
|
+
"export",
|
|
135
|
+
"training",
|
|
136
|
+
"auto_tag",
|
|
137
|
+
"auto_publish",
|
|
138
|
+
],
|
|
139
|
+
allowedArtifactTypes: [
|
|
140
|
+
"analysis_report",
|
|
141
|
+
"gate_report",
|
|
142
|
+
"scorecard",
|
|
143
|
+
"eval_replay_fixture",
|
|
144
|
+
"eval_suite",
|
|
145
|
+
"prompt_patch",
|
|
146
|
+
"schema_patch",
|
|
147
|
+
"tool_metadata_patch",
|
|
148
|
+
"branch_patch",
|
|
149
|
+
"prerelease_tag",
|
|
150
|
+
"canary_tag",
|
|
151
|
+
"stable_npm_release",
|
|
152
|
+
"release_notes",
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
export const DEFAULT_CODALI_IMPROVEMENT_POLICY_LIMITS = {
|
|
157
|
+
releaseLevel: 0,
|
|
158
|
+
allowedArtifactTypes: CODALI_IMPROVEMENT_RELEASE_LEVEL_CONTRACTS[0].allowedArtifactTypes,
|
|
159
|
+
maxExamples: 100,
|
|
160
|
+
maxObjectBytes: 1048576,
|
|
161
|
+
storageMode: "local_only",
|
|
162
|
+
exportEnabled: false,
|
|
163
|
+
trainingEnabled: false,
|
|
164
|
+
autoTagEnabled: false,
|
|
165
|
+
autoPublishEnabled: false,
|
|
166
|
+
};
|
|
167
|
+
const STRING_ARRAY_KEYS = new Set([
|
|
168
|
+
"artifactIds",
|
|
169
|
+
"sourceExportIds",
|
|
170
|
+
"blockedReasons",
|
|
171
|
+
"reasons",
|
|
172
|
+
]);
|
|
173
|
+
const ACTION_REQUIRED_LEVELS = {
|
|
174
|
+
analyze: 0,
|
|
175
|
+
add_eval_replay: 1,
|
|
176
|
+
export: 1,
|
|
177
|
+
branch_metadata: 2,
|
|
178
|
+
training: 2,
|
|
179
|
+
create_prerelease_tag: 3,
|
|
180
|
+
auto_tag: 3,
|
|
181
|
+
publish_stable: 4,
|
|
182
|
+
auto_publish: 4,
|
|
183
|
+
};
|
|
184
|
+
const CODALI_IMPROVEMENT_MONITOR_REPORT_SCHEMA_VERSION = "codali.improvement.release_outcome_report.v1";
|
|
185
|
+
const CODALI_IMPROVEMENT_MONITOR_RUNTIME_PACKAGE_KINDS = [
|
|
186
|
+
"prompt_package",
|
|
187
|
+
"router_policy",
|
|
188
|
+
"retrieval_policy",
|
|
189
|
+
"schema",
|
|
190
|
+
"fine_tune_adapter",
|
|
191
|
+
];
|
|
192
|
+
const CODALI_IMPROVEMENT_MONITOR_ROLLBACK_TRIGGER_CODES = [
|
|
193
|
+
"schema_failures",
|
|
194
|
+
"accepted_answer_rate_drop",
|
|
195
|
+
"verifier_contradictions",
|
|
196
|
+
"tool_failures",
|
|
197
|
+
"latency_increase",
|
|
198
|
+
"cost_increase",
|
|
199
|
+
"privacy_security_warnings",
|
|
200
|
+
];
|
|
201
|
+
export const isCodaliImprovementValidationOk = (result) => result.ok;
|
|
202
|
+
export const createCodaliImprovementPolicy = (input) => ({
|
|
203
|
+
...input,
|
|
204
|
+
schemaVersion: CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION,
|
|
205
|
+
});
|
|
206
|
+
export const getCodaliImprovementReleaseLevelContract = (level) => CODALI_IMPROVEMENT_RELEASE_LEVEL_CONTRACTS[level];
|
|
207
|
+
export const getCodaliImprovementAllowedArtifactTypesForLevel = (level) => CODALI_IMPROVEMENT_RELEASE_LEVEL_CONTRACTS[level].allowedArtifactTypes;
|
|
208
|
+
export const evaluateCodaliImprovementPolicy = (policyInput, request) => {
|
|
209
|
+
const policyResult = validateCodaliImprovementPolicy(policyInput);
|
|
210
|
+
const reasons = [];
|
|
211
|
+
const blockedBy = [];
|
|
212
|
+
const policy = policyResult.ok ? policyResult.value : undefined;
|
|
213
|
+
const releaseLevel = request.releaseLevel ?? policy?.releaseLevel ?? 0;
|
|
214
|
+
const requiredReleaseLevel = ACTION_REQUIRED_LEVELS[request.action] ?? 4;
|
|
215
|
+
if (!policyResult.ok) {
|
|
216
|
+
reasons.push("invalid_policy");
|
|
217
|
+
blockedBy.push("policy_validator");
|
|
218
|
+
}
|
|
219
|
+
if (!isReleaseLevel(releaseLevel)) {
|
|
220
|
+
reasons.push("invalid_release_level");
|
|
221
|
+
blockedBy.push("release_level");
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
if (policy && releaseLevel > policy.releaseLevel) {
|
|
225
|
+
reasons.push("requested_release_level_exceeds_policy");
|
|
226
|
+
blockedBy.push("release_level");
|
|
227
|
+
}
|
|
228
|
+
if (releaseLevel < requiredReleaseLevel) {
|
|
229
|
+
reasons.push("release_level_too_low");
|
|
230
|
+
blockedBy.push("release_level");
|
|
231
|
+
}
|
|
232
|
+
if (!levelAllowsAction(releaseLevel, request.action)) {
|
|
233
|
+
reasons.push("action_not_allowed_at_release_level");
|
|
234
|
+
blockedBy.push("release_level");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (policy) {
|
|
238
|
+
if (request.action === "export" && !policy.exportEnabled) {
|
|
239
|
+
reasons.push("export_disabled");
|
|
240
|
+
blockedBy.push("export_enabled");
|
|
241
|
+
}
|
|
242
|
+
if (request.action === "training" && !policy.trainingEnabled) {
|
|
243
|
+
reasons.push("training_disabled");
|
|
244
|
+
blockedBy.push("training_enabled");
|
|
245
|
+
}
|
|
246
|
+
if (request.action === "auto_tag" && !policy.autoTagEnabled) {
|
|
247
|
+
reasons.push("auto_tag_disabled");
|
|
248
|
+
blockedBy.push("auto_tag_enabled");
|
|
249
|
+
}
|
|
250
|
+
if (request.action === "publish_stable" && !policy.autoPublishEnabled) {
|
|
251
|
+
reasons.push("publish_disabled");
|
|
252
|
+
blockedBy.push("auto_publish_enabled");
|
|
253
|
+
}
|
|
254
|
+
if (request.action === "auto_publish" && !policy.autoPublishEnabled) {
|
|
255
|
+
reasons.push("auto_publish_disabled");
|
|
256
|
+
blockedBy.push("auto_publish_enabled");
|
|
257
|
+
}
|
|
258
|
+
if (request.scope && !sameScope(policy.scope, request.scope)) {
|
|
259
|
+
reasons.push("scope_not_allowed");
|
|
260
|
+
blockedBy.push("tenant_product_scope");
|
|
261
|
+
}
|
|
262
|
+
if (typeof request.exampleCount === "number" &&
|
|
263
|
+
request.exampleCount > policy.maxExamples) {
|
|
264
|
+
reasons.push("max_examples_exceeded");
|
|
265
|
+
blockedBy.push("max_examples");
|
|
266
|
+
}
|
|
267
|
+
if (typeof request.objectBytes === "number" &&
|
|
268
|
+
request.objectBytes > policy.maxObjectBytes) {
|
|
269
|
+
reasons.push("max_object_bytes_exceeded");
|
|
270
|
+
blockedBy.push("max_object_bytes");
|
|
271
|
+
}
|
|
272
|
+
if (request.artifactType &&
|
|
273
|
+
!policy.allowedArtifactTypes.includes(request.artifactType)) {
|
|
274
|
+
reasons.push("artifact_type_not_allowed_by_policy");
|
|
275
|
+
blockedBy.push("allowed_artifact_types");
|
|
276
|
+
}
|
|
277
|
+
if (request.artifactType &&
|
|
278
|
+
isReleaseLevel(releaseLevel) &&
|
|
279
|
+
!getCodaliImprovementAllowedArtifactTypesForLevel(releaseLevel).includes(request.artifactType)) {
|
|
280
|
+
reasons.push("artifact_type_not_allowed_at_release_level");
|
|
281
|
+
blockedBy.push("release_level");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
schemaVersion: CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION,
|
|
286
|
+
policyId: policy?.policyId ?? "invalid-policy",
|
|
287
|
+
action: request.action,
|
|
288
|
+
allowed: reasons.length === 0,
|
|
289
|
+
releaseLevel: isReleaseLevel(releaseLevel) ? releaseLevel : 0,
|
|
290
|
+
requiredReleaseLevel,
|
|
291
|
+
reasons: uniqueStrings(reasons),
|
|
292
|
+
blockedBy: uniqueStrings(blockedBy),
|
|
293
|
+
artifactType: request.artifactType,
|
|
294
|
+
scope: request.scope,
|
|
295
|
+
};
|
|
296
|
+
};
|
|
297
|
+
export const buildCodaliImprovementCliJsonOutput = (input) => withoutUndefined({
|
|
298
|
+
schemaVersion: CODALI_IMPROVEMENT_CLI_JSON_SCHEMA_VERSION,
|
|
299
|
+
command: "improvement",
|
|
300
|
+
outputType: input.outputType,
|
|
301
|
+
status: input.status,
|
|
302
|
+
generatedAt: input.generatedAt ?? new Date().toISOString(),
|
|
303
|
+
policy: input.policy,
|
|
304
|
+
decision: input.decision,
|
|
305
|
+
data: input.data,
|
|
306
|
+
issues: input.issues ?? [],
|
|
307
|
+
});
|
|
308
|
+
export const validateCodaliImprovementPolicy = (input) => {
|
|
309
|
+
const bag = { issues: [] };
|
|
310
|
+
const record = requireRecord(input, "$", bag);
|
|
311
|
+
if (!record)
|
|
312
|
+
return fail(bag);
|
|
313
|
+
assertAllowedKeys(record, "$", [
|
|
314
|
+
"schemaVersion",
|
|
315
|
+
"policyId",
|
|
316
|
+
"releaseLevel",
|
|
317
|
+
"scope",
|
|
318
|
+
"allowedArtifactTypes",
|
|
319
|
+
"maxExamples",
|
|
320
|
+
"maxObjectBytes",
|
|
321
|
+
"storageMode",
|
|
322
|
+
"exportEnabled",
|
|
323
|
+
"trainingEnabled",
|
|
324
|
+
"autoTagEnabled",
|
|
325
|
+
"autoPublishEnabled",
|
|
326
|
+
"metadata",
|
|
327
|
+
], bag);
|
|
328
|
+
const schemaVersion = readConstString(record, "schemaVersion", CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION, "$.schemaVersion", bag);
|
|
329
|
+
const releaseLevel = readReleaseLevel(record, "releaseLevel", "$.releaseLevel", bag);
|
|
330
|
+
const scope = validateScope(record.scope, "$.scope", bag);
|
|
331
|
+
const allowedArtifactTypes = readEnumArray(record, "allowedArtifactTypes", CODALI_IMPROVEMENT_ARTIFACT_TYPES, "$.allowedArtifactTypes", bag);
|
|
332
|
+
const policy = withoutUndefined({
|
|
333
|
+
schemaVersion,
|
|
334
|
+
policyId: readNonEmptyString(record, "policyId", "$.policyId", bag),
|
|
335
|
+
releaseLevel,
|
|
336
|
+
scope: scope ?? fallbackScope(),
|
|
337
|
+
allowedArtifactTypes,
|
|
338
|
+
maxExamples: readNonNegativeInteger(record, "maxExamples", "$.maxExamples", bag),
|
|
339
|
+
maxObjectBytes: readPositiveInteger(record, "maxObjectBytes", "$.maxObjectBytes", bag),
|
|
340
|
+
storageMode: readEnumValue(record, "storageMode", ["local_only", "storage_service", "hybrid"], "$.storageMode", bag),
|
|
341
|
+
exportEnabled: readBoolean(record, "exportEnabled", "$.exportEnabled", bag),
|
|
342
|
+
trainingEnabled: readBoolean(record, "trainingEnabled", "$.trainingEnabled", bag),
|
|
343
|
+
autoTagEnabled: readBoolean(record, "autoTagEnabled", "$.autoTagEnabled", bag),
|
|
344
|
+
autoPublishEnabled: readBoolean(record, "autoPublishEnabled", "$.autoPublishEnabled", bag),
|
|
345
|
+
metadata: readOptionalRecord(record, "metadata", "$.metadata", bag),
|
|
346
|
+
});
|
|
347
|
+
validatePolicyInvariants(policy, "$", bag);
|
|
348
|
+
return bag.issues.length > 0 ? fail(bag) : ok(policy);
|
|
349
|
+
};
|
|
350
|
+
export const validateCodaliImprovementRun = (input) => {
|
|
351
|
+
const bag = { issues: [] };
|
|
352
|
+
const record = requireRecord(input, "$", bag);
|
|
353
|
+
if (!record)
|
|
354
|
+
return fail(bag);
|
|
355
|
+
assertAllowedKeys(record, "$", [
|
|
356
|
+
"schemaVersion",
|
|
357
|
+
"runId",
|
|
358
|
+
"policyId",
|
|
359
|
+
"scope",
|
|
360
|
+
"releaseLevel",
|
|
361
|
+
"status",
|
|
362
|
+
"createdAt",
|
|
363
|
+
"sourceExportIds",
|
|
364
|
+
"maxExamples",
|
|
365
|
+
"metadata",
|
|
366
|
+
], bag);
|
|
367
|
+
const scope = validateScope(record.scope, "$.scope", bag);
|
|
368
|
+
const run = withoutUndefined({
|
|
369
|
+
schemaVersion: readConstString(record, "schemaVersion", CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION, "$.schemaVersion", bag),
|
|
370
|
+
runId: readNonEmptyString(record, "runId", "$.runId", bag),
|
|
371
|
+
policyId: readNonEmptyString(record, "policyId", "$.policyId", bag),
|
|
372
|
+
scope: scope ?? fallbackScope(),
|
|
373
|
+
releaseLevel: readReleaseLevel(record, "releaseLevel", "$.releaseLevel", bag),
|
|
374
|
+
status: readEnumValue(record, "status", ["planned", "running", "blocked", "completed", "failed"], "$.status", bag),
|
|
375
|
+
createdAt: readIsoLikeString(record, "createdAt", "$.createdAt", bag),
|
|
376
|
+
sourceExportIds: readStringArray(record, "sourceExportIds", "$.sourceExportIds", bag),
|
|
377
|
+
maxExamples: readNonNegativeInteger(record, "maxExamples", "$.maxExamples", bag),
|
|
378
|
+
metadata: readOptionalRecord(record, "metadata", "$.metadata", bag),
|
|
379
|
+
});
|
|
380
|
+
return bag.issues.length > 0 ? fail(bag) : ok(run);
|
|
381
|
+
};
|
|
382
|
+
export const validateCodaliImprovementArtifact = (input) => {
|
|
383
|
+
const bag = { issues: [] };
|
|
384
|
+
const record = requireRecord(input, "$", bag);
|
|
385
|
+
if (!record)
|
|
386
|
+
return fail(bag);
|
|
387
|
+
assertAllowedKeys(record, "$", [
|
|
388
|
+
"schemaVersion",
|
|
389
|
+
"artifactId",
|
|
390
|
+
"artifactType",
|
|
391
|
+
"scope",
|
|
392
|
+
"storageMode",
|
|
393
|
+
"byteSize",
|
|
394
|
+
"contentHash",
|
|
395
|
+
"exportAllowed",
|
|
396
|
+
"trainingAllowed",
|
|
397
|
+
"createdAt",
|
|
398
|
+
"uri",
|
|
399
|
+
"metadata",
|
|
400
|
+
], bag);
|
|
401
|
+
const scope = validateScope(record.scope, "$.scope", bag);
|
|
402
|
+
const artifact = withoutUndefined({
|
|
403
|
+
schemaVersion: readConstString(record, "schemaVersion", CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION, "$.schemaVersion", bag),
|
|
404
|
+
artifactId: readNonEmptyString(record, "artifactId", "$.artifactId", bag),
|
|
405
|
+
artifactType: readEnumValue(record, "artifactType", CODALI_IMPROVEMENT_ARTIFACT_TYPES, "$.artifactType", bag),
|
|
406
|
+
scope: scope ?? fallbackScope(),
|
|
407
|
+
storageMode: readEnumValue(record, "storageMode", ["local_only", "storage_service", "hybrid"], "$.storageMode", bag),
|
|
408
|
+
byteSize: readNonNegativeInteger(record, "byteSize", "$.byteSize", bag),
|
|
409
|
+
contentHash: readNonEmptyString(record, "contentHash", "$.contentHash", bag),
|
|
410
|
+
exportAllowed: readBoolean(record, "exportAllowed", "$.exportAllowed", bag),
|
|
411
|
+
trainingAllowed: readBoolean(record, "trainingAllowed", "$.trainingAllowed", bag),
|
|
412
|
+
createdAt: readIsoLikeString(record, "createdAt", "$.createdAt", bag),
|
|
413
|
+
uri: readOptionalString(record, "uri", "$.uri", bag),
|
|
414
|
+
metadata: readOptionalRecord(record, "metadata", "$.metadata", bag),
|
|
415
|
+
});
|
|
416
|
+
return bag.issues.length > 0 ? fail(bag) : ok(artifact);
|
|
417
|
+
};
|
|
418
|
+
export const validateCodaliImprovementCandidate = (input) => {
|
|
419
|
+
const bag = { issues: [] };
|
|
420
|
+
const record = requireRecord(input, "$", bag);
|
|
421
|
+
if (!record)
|
|
422
|
+
return fail(bag);
|
|
423
|
+
assertAllowedKeys(record, "$", [
|
|
424
|
+
"schemaVersion",
|
|
425
|
+
"candidateId",
|
|
426
|
+
"runId",
|
|
427
|
+
"scope",
|
|
428
|
+
"candidateKind",
|
|
429
|
+
"status",
|
|
430
|
+
"artifactIds",
|
|
431
|
+
"sourceExportIds",
|
|
432
|
+
"exampleCount",
|
|
433
|
+
"objectBytes",
|
|
434
|
+
"createdAt",
|
|
435
|
+
"blockedReasons",
|
|
436
|
+
"metadata",
|
|
437
|
+
], bag);
|
|
438
|
+
const scope = validateScope(record.scope, "$.scope", bag);
|
|
439
|
+
const candidate = withoutUndefined({
|
|
440
|
+
schemaVersion: readConstString(record, "schemaVersion", CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION, "$.schemaVersion", bag),
|
|
441
|
+
candidateId: readNonEmptyString(record, "candidateId", "$.candidateId", bag),
|
|
442
|
+
runId: readNonEmptyString(record, "runId", "$.runId", bag),
|
|
443
|
+
scope: scope ?? fallbackScope(),
|
|
444
|
+
candidateKind: readEnumValue(record, "candidateKind", [
|
|
445
|
+
"analysis",
|
|
446
|
+
"eval_replay",
|
|
447
|
+
"model_router",
|
|
448
|
+
"prompt",
|
|
449
|
+
"schema",
|
|
450
|
+
"tool_metadata",
|
|
451
|
+
"release",
|
|
452
|
+
], "$.candidateKind", bag),
|
|
453
|
+
status: readEnumValue(record, "status", ["proposed", "accepted", "blocked", "rejected", "released"], "$.status", bag),
|
|
454
|
+
artifactIds: readStringArray(record, "artifactIds", "$.artifactIds", bag),
|
|
455
|
+
sourceExportIds: readStringArray(record, "sourceExportIds", "$.sourceExportIds", bag),
|
|
456
|
+
exampleCount: readNonNegativeInteger(record, "exampleCount", "$.exampleCount", bag),
|
|
457
|
+
objectBytes: readNonNegativeInteger(record, "objectBytes", "$.objectBytes", bag),
|
|
458
|
+
createdAt: readIsoLikeString(record, "createdAt", "$.createdAt", bag),
|
|
459
|
+
blockedReasons: readOptionalStringArray(record, "blockedReasons", "$.blockedReasons", bag),
|
|
460
|
+
metadata: readOptionalRecord(record, "metadata", "$.metadata", bag),
|
|
461
|
+
});
|
|
462
|
+
return bag.issues.length > 0 ? fail(bag) : ok(candidate);
|
|
463
|
+
};
|
|
464
|
+
export const validateCodaliImprovementGate = (input) => {
|
|
465
|
+
const bag = { issues: [] };
|
|
466
|
+
const gate = validateGateWithBag(input, "$", bag);
|
|
467
|
+
return bag.issues.length > 0 ? fail(bag) : ok(gate ?? fallbackGate());
|
|
468
|
+
};
|
|
469
|
+
export const validateCodaliImprovementScorecard = (input) => {
|
|
470
|
+
const bag = { issues: [] };
|
|
471
|
+
const record = requireRecord(input, "$", bag);
|
|
472
|
+
if (!record)
|
|
473
|
+
return fail(bag);
|
|
474
|
+
assertAllowedKeys(record, "$", [
|
|
475
|
+
"schemaVersion",
|
|
476
|
+
"scorecardId",
|
|
477
|
+
"candidateId",
|
|
478
|
+
"status",
|
|
479
|
+
"gates",
|
|
480
|
+
"scores",
|
|
481
|
+
"createdAt",
|
|
482
|
+
"summary",
|
|
483
|
+
"metadata",
|
|
484
|
+
], bag);
|
|
485
|
+
const gates = readGateArray(record, "gates", "$.gates", bag);
|
|
486
|
+
const scorecard = withoutUndefined({
|
|
487
|
+
schemaVersion: readConstString(record, "schemaVersion", CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION, "$.schemaVersion", bag),
|
|
488
|
+
scorecardId: readNonEmptyString(record, "scorecardId", "$.scorecardId", bag),
|
|
489
|
+
candidateId: readNonEmptyString(record, "candidateId", "$.candidateId", bag),
|
|
490
|
+
status: readEnumValue(record, "status", ["passed", "failed", "degraded", "blocked"], "$.status", bag),
|
|
491
|
+
gates,
|
|
492
|
+
scores: readScores(record, "scores", "$.scores", bag),
|
|
493
|
+
createdAt: readIsoLikeString(record, "createdAt", "$.createdAt", bag),
|
|
494
|
+
summary: readOptionalString(record, "summary", "$.summary", bag),
|
|
495
|
+
metadata: readOptionalRecord(record, "metadata", "$.metadata", bag),
|
|
496
|
+
});
|
|
497
|
+
return bag.issues.length > 0 ? fail(bag) : ok(scorecard);
|
|
498
|
+
};
|
|
499
|
+
export const validateCodaliImprovementRelease = (input) => {
|
|
500
|
+
const bag = { issues: [] };
|
|
501
|
+
const record = requireRecord(input, "$", bag);
|
|
502
|
+
if (!record)
|
|
503
|
+
return fail(bag);
|
|
504
|
+
assertAllowedKeys(record, "$", [
|
|
505
|
+
"schemaVersion",
|
|
506
|
+
"releaseId",
|
|
507
|
+
"candidateId",
|
|
508
|
+
"scope",
|
|
509
|
+
"releaseLevel",
|
|
510
|
+
"status",
|
|
511
|
+
"artifactIds",
|
|
512
|
+
"createdAt",
|
|
513
|
+
"tagName",
|
|
514
|
+
"packageName",
|
|
515
|
+
"version",
|
|
516
|
+
"blockedReasons",
|
|
517
|
+
"metadata",
|
|
518
|
+
], bag);
|
|
519
|
+
const scope = validateScope(record.scope, "$.scope", bag);
|
|
520
|
+
const release = withoutUndefined({
|
|
521
|
+
schemaVersion: readConstString(record, "schemaVersion", CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION, "$.schemaVersion", bag),
|
|
522
|
+
releaseId: readNonEmptyString(record, "releaseId", "$.releaseId", bag),
|
|
523
|
+
candidateId: readNonEmptyString(record, "candidateId", "$.candidateId", bag),
|
|
524
|
+
scope: scope ?? fallbackScope(),
|
|
525
|
+
releaseLevel: readReleaseLevel(record, "releaseLevel", "$.releaseLevel", bag),
|
|
526
|
+
status: readEnumValue(record, "status", ["planned", "blocked", "created", "failed", "published"], "$.status", bag),
|
|
527
|
+
artifactIds: readStringArray(record, "artifactIds", "$.artifactIds", bag),
|
|
528
|
+
createdAt: readIsoLikeString(record, "createdAt", "$.createdAt", bag),
|
|
529
|
+
tagName: readOptionalString(record, "tagName", "$.tagName", bag),
|
|
530
|
+
packageName: readOptionalString(record, "packageName", "$.packageName", bag),
|
|
531
|
+
version: readOptionalString(record, "version", "$.version", bag),
|
|
532
|
+
blockedReasons: readOptionalStringArray(record, "blockedReasons", "$.blockedReasons", bag),
|
|
533
|
+
metadata: readOptionalRecord(record, "metadata", "$.metadata", bag),
|
|
534
|
+
});
|
|
535
|
+
validateReleaseInvariants(release, "$", bag);
|
|
536
|
+
return bag.issues.length > 0 ? fail(bag) : ok(release);
|
|
537
|
+
};
|
|
538
|
+
export const validateCodaliImprovementOutcome = (input) => {
|
|
539
|
+
const bag = { issues: [] };
|
|
540
|
+
const record = requireRecord(input, "$", bag);
|
|
541
|
+
if (!record)
|
|
542
|
+
return fail(bag);
|
|
543
|
+
assertAllowedKeys(record, "$", [
|
|
544
|
+
"schemaVersion",
|
|
545
|
+
"outcomeId",
|
|
546
|
+
"releaseId",
|
|
547
|
+
"scope",
|
|
548
|
+
"status",
|
|
549
|
+
"published",
|
|
550
|
+
"tagged",
|
|
551
|
+
"trainingUsed",
|
|
552
|
+
"exportUsed",
|
|
553
|
+
"createdAt",
|
|
554
|
+
"reasons",
|
|
555
|
+
"telemetry",
|
|
556
|
+
"metadata",
|
|
557
|
+
], bag);
|
|
558
|
+
const scope = validateScope(record.scope, "$.scope", bag);
|
|
559
|
+
const outcome = withoutUndefined({
|
|
560
|
+
schemaVersion: readConstString(record, "schemaVersion", CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION, "$.schemaVersion", bag),
|
|
561
|
+
outcomeId: readNonEmptyString(record, "outcomeId", "$.outcomeId", bag),
|
|
562
|
+
releaseId: readNonEmptyString(record, "releaseId", "$.releaseId", bag),
|
|
563
|
+
scope: scope ?? fallbackScope(),
|
|
564
|
+
status: readEnumValue(record, "status", ["succeeded", "failed", "degraded", "rolled_back", "blocked"], "$.status", bag),
|
|
565
|
+
published: readBoolean(record, "published", "$.published", bag),
|
|
566
|
+
tagged: readBoolean(record, "tagged", "$.tagged", bag),
|
|
567
|
+
trainingUsed: readBoolean(record, "trainingUsed", "$.trainingUsed", bag),
|
|
568
|
+
exportUsed: readBoolean(record, "exportUsed", "$.exportUsed", bag),
|
|
569
|
+
createdAt: readIsoLikeString(record, "createdAt", "$.createdAt", bag),
|
|
570
|
+
reasons: readOptionalStringArray(record, "reasons", "$.reasons", bag),
|
|
571
|
+
telemetry: readOptionalNumberRecord(record, "telemetry", "$.telemetry", bag),
|
|
572
|
+
metadata: readOptionalRecord(record, "metadata", "$.metadata", bag),
|
|
573
|
+
});
|
|
574
|
+
validateOutcomeInvariants(outcome, "$", bag);
|
|
575
|
+
return bag.issues.length > 0 ? fail(bag) : ok(outcome);
|
|
576
|
+
};
|
|
577
|
+
export const validateCodaliImprovementPolicyDecision = (input) => {
|
|
578
|
+
const bag = { issues: [] };
|
|
579
|
+
const record = requireRecord(input, "$", bag);
|
|
580
|
+
if (!record)
|
|
581
|
+
return fail(bag);
|
|
582
|
+
assertAllowedKeys(record, "$", [
|
|
583
|
+
"schemaVersion",
|
|
584
|
+
"policyId",
|
|
585
|
+
"action",
|
|
586
|
+
"allowed",
|
|
587
|
+
"releaseLevel",
|
|
588
|
+
"requiredReleaseLevel",
|
|
589
|
+
"reasons",
|
|
590
|
+
"blockedBy",
|
|
591
|
+
"artifactType",
|
|
592
|
+
"scope",
|
|
593
|
+
], bag);
|
|
594
|
+
const scope = record.scope === undefined
|
|
595
|
+
? undefined
|
|
596
|
+
: validateScope(record.scope, "$.scope", bag);
|
|
597
|
+
const decision = withoutUndefined({
|
|
598
|
+
schemaVersion: readConstString(record, "schemaVersion", CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION, "$.schemaVersion", bag),
|
|
599
|
+
policyId: readNonEmptyString(record, "policyId", "$.policyId", bag),
|
|
600
|
+
action: readEnumValue(record, "action", CODALI_IMPROVEMENT_POLICY_ACTIONS, "$.action", bag),
|
|
601
|
+
allowed: readBoolean(record, "allowed", "$.allowed", bag),
|
|
602
|
+
releaseLevel: readReleaseLevel(record, "releaseLevel", "$.releaseLevel", bag),
|
|
603
|
+
requiredReleaseLevel: readReleaseLevel(record, "requiredReleaseLevel", "$.requiredReleaseLevel", bag),
|
|
604
|
+
reasons: readStringArray(record, "reasons", "$.reasons", bag),
|
|
605
|
+
blockedBy: readStringArray(record, "blockedBy", "$.blockedBy", bag),
|
|
606
|
+
artifactType: readOptionalEnumValue(record, "artifactType", CODALI_IMPROVEMENT_ARTIFACT_TYPES, "$.artifactType", bag),
|
|
607
|
+
scope,
|
|
608
|
+
});
|
|
609
|
+
return bag.issues.length > 0 ? fail(bag) : ok(decision);
|
|
610
|
+
};
|
|
611
|
+
export const validateCodaliImprovementCliJsonOutput = (input) => {
|
|
612
|
+
const bag = { issues: [] };
|
|
613
|
+
const record = requireRecord(input, "$", bag);
|
|
614
|
+
if (!record)
|
|
615
|
+
return fail(bag);
|
|
616
|
+
assertAllowedKeys(record, "$", [
|
|
617
|
+
"schemaVersion",
|
|
618
|
+
"command",
|
|
619
|
+
"outputType",
|
|
620
|
+
"status",
|
|
621
|
+
"generatedAt",
|
|
622
|
+
"policy",
|
|
623
|
+
"decision",
|
|
624
|
+
"data",
|
|
625
|
+
"issues",
|
|
626
|
+
], bag);
|
|
627
|
+
const policy = record.policy === undefined
|
|
628
|
+
? undefined
|
|
629
|
+
: readNestedValidation(validateCodaliImprovementPolicy(record.policy), "$.policy", bag);
|
|
630
|
+
const decision = record.decision === undefined
|
|
631
|
+
? undefined
|
|
632
|
+
: readNestedValidation(validateCodaliImprovementPolicyDecision(record.decision), "$.decision", bag);
|
|
633
|
+
const output = withoutUndefined({
|
|
634
|
+
schemaVersion: readConstString(record, "schemaVersion", CODALI_IMPROVEMENT_CLI_JSON_SCHEMA_VERSION, "$.schemaVersion", bag),
|
|
635
|
+
command: readConstString(record, "command", "improvement", "$.command", bag),
|
|
636
|
+
outputType: readEnumValue(record, "outputType", CODALI_IMPROVEMENT_CLI_JSON_OUTPUT_TYPES, "$.outputType", bag),
|
|
637
|
+
status: readEnumValue(record, "status", ["ok", "blocked", "error"], "$.status", bag),
|
|
638
|
+
generatedAt: readIsoLikeString(record, "generatedAt", "$.generatedAt", bag),
|
|
639
|
+
policy,
|
|
640
|
+
decision,
|
|
641
|
+
data: record.data,
|
|
642
|
+
issues: readIssueArray(record, "issues", "$.issues", bag),
|
|
643
|
+
});
|
|
644
|
+
validateCliJsonOutputInvariants(record, output, "$", bag);
|
|
645
|
+
return bag.issues.length > 0 ? fail(bag) : ok(output);
|
|
646
|
+
};
|
|
647
|
+
const validateCliJsonOutputInvariants = (record, output, path, bag) => {
|
|
648
|
+
if (output.status === "ok" && output.issues.length > 0) {
|
|
649
|
+
pushIssue(bag, `${path}.status`, "ok_output_cannot_have_issues", "OK improvement CLI output cannot include validation issues.");
|
|
650
|
+
}
|
|
651
|
+
if (output.status === "blocked" &&
|
|
652
|
+
output.outputType === "improvement.policy_decision" &&
|
|
653
|
+
output.decision?.allowed !== false) {
|
|
654
|
+
pushIssue(bag, `${path}.decision`, "blocked_output_requires_blocking_decision", "Blocked improvement CLI output requires a decision with allowed=false.");
|
|
655
|
+
}
|
|
656
|
+
if (output.status === "error" && output.issues.length === 0) {
|
|
657
|
+
pushIssue(bag, `${path}.issues`, "error_output_requires_issues", "Error improvement CLI output must include validation issues.");
|
|
658
|
+
}
|
|
659
|
+
switch (output.outputType) {
|
|
660
|
+
case "improvement.policy":
|
|
661
|
+
requireCliPolicy(output, path, bag);
|
|
662
|
+
rejectCliDecision(output, path, bag);
|
|
663
|
+
rejectCliData(record, path, bag);
|
|
664
|
+
return;
|
|
665
|
+
case "improvement.policy_decision":
|
|
666
|
+
requireCliPolicy(output, path, bag);
|
|
667
|
+
requireCliDecision(output, path, bag);
|
|
668
|
+
rejectCliData(record, path, bag);
|
|
669
|
+
return;
|
|
670
|
+
case "improvement.release_levels":
|
|
671
|
+
rejectCliPolicy(output, path, bag);
|
|
672
|
+
rejectCliDecision(output, path, bag);
|
|
673
|
+
validateReleaseLevelContractArray(record.data, `${path}.data`, bag);
|
|
674
|
+
return;
|
|
675
|
+
case "improvement.run":
|
|
676
|
+
validateRequiredCliData(record, `${path}.data`, validateCodaliImprovementRun, bag);
|
|
677
|
+
return;
|
|
678
|
+
case "improvement.candidate":
|
|
679
|
+
validateRequiredCliData(record, `${path}.data`, validateCodaliImprovementCandidate, bag);
|
|
680
|
+
return;
|
|
681
|
+
case "improvement.artifact":
|
|
682
|
+
validateRequiredCliData(record, `${path}.data`, validateCodaliImprovementArtifact, bag);
|
|
683
|
+
return;
|
|
684
|
+
case "improvement.gate":
|
|
685
|
+
validateRequiredCliData(record, `${path}.data`, validateCodaliImprovementGate, bag);
|
|
686
|
+
return;
|
|
687
|
+
case "improvement.scorecard":
|
|
688
|
+
validateRequiredCliData(record, `${path}.data`, validateCodaliImprovementScorecard, bag);
|
|
689
|
+
return;
|
|
690
|
+
case "improvement.release":
|
|
691
|
+
validateRequiredCliData(record, `${path}.data`, validateCodaliImprovementRelease, bag);
|
|
692
|
+
return;
|
|
693
|
+
case "improvement.outcome":
|
|
694
|
+
validateRequiredCliData(record, `${path}.data`, validateCodaliImprovementOutcome, bag);
|
|
695
|
+
return;
|
|
696
|
+
case "improvement.monitor":
|
|
697
|
+
validateRequiredCliData(record, `${path}.data`, validateMonitorReportData, bag);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
const requireCliPolicy = (output, path, bag) => {
|
|
702
|
+
if (!output.policy) {
|
|
703
|
+
pushIssue(bag, `${path}.policy`, "missing_policy", "This improvement CLI output type requires a policy payload.");
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
const rejectCliPolicy = (output, path, bag) => {
|
|
707
|
+
if (output.policy) {
|
|
708
|
+
pushIssue(bag, `${path}.policy`, "unexpected_policy", "This improvement CLI output type must carry its payload in data.");
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
const requireCliDecision = (output, path, bag) => {
|
|
712
|
+
if (!output.decision) {
|
|
713
|
+
pushIssue(bag, `${path}.decision`, "missing_decision", "This improvement CLI output type requires a policy decision payload.");
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
const rejectCliDecision = (output, path, bag) => {
|
|
717
|
+
if (output.decision) {
|
|
718
|
+
pushIssue(bag, `${path}.decision`, "unexpected_decision", "This improvement CLI output type does not allow a policy decision payload.");
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
const rejectCliData = (record, path, bag) => {
|
|
722
|
+
if (record.data !== undefined) {
|
|
723
|
+
pushIssue(bag, `${path}.data`, "unexpected_data", "This improvement CLI output type does not allow a data payload.");
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
const validateRequiredCliData = (record, path, validator, bag) => {
|
|
727
|
+
if (record.data === undefined) {
|
|
728
|
+
pushIssue(bag, path, "missing_data", "This improvement CLI output type requires a data payload.");
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
readNestedValidation(validator(record.data), path, bag);
|
|
732
|
+
};
|
|
733
|
+
const validateReleaseLevelContractArray = (input, path, bag) => {
|
|
734
|
+
if (!Array.isArray(input)) {
|
|
735
|
+
pushIssue(bag, path, "required_array", "Release level CLI data must be an array.");
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const seenLevels = new Set();
|
|
739
|
+
input.forEach((entry, index) => {
|
|
740
|
+
const entryPath = `${path}[${index}]`;
|
|
741
|
+
const record = requireRecord(entry, entryPath, bag);
|
|
742
|
+
if (!record)
|
|
743
|
+
return;
|
|
744
|
+
assertAllowedKeys(record, entryPath, [
|
|
745
|
+
"level",
|
|
746
|
+
"name",
|
|
747
|
+
"description",
|
|
748
|
+
"minAction",
|
|
749
|
+
"allowedActions",
|
|
750
|
+
"allowedArtifactTypes",
|
|
751
|
+
], bag);
|
|
752
|
+
const rawLevel = record.level;
|
|
753
|
+
const level = readReleaseLevel(record, "level", `${entryPath}.level`, bag);
|
|
754
|
+
const name = readNonEmptyString(record, "name", `${entryPath}.name`, bag);
|
|
755
|
+
const description = readNonEmptyString(record, "description", `${entryPath}.description`, bag);
|
|
756
|
+
const minAction = readEnumValue(record, "minAction", CODALI_IMPROVEMENT_POLICY_ACTIONS, `${entryPath}.minAction`, bag);
|
|
757
|
+
const allowedActions = readEnumArray(record, "allowedActions", CODALI_IMPROVEMENT_POLICY_ACTIONS, `${entryPath}.allowedActions`, bag);
|
|
758
|
+
const allowedArtifactTypes = readEnumArray(record, "allowedArtifactTypes", CODALI_IMPROVEMENT_ARTIFACT_TYPES, `${entryPath}.allowedArtifactTypes`, bag);
|
|
759
|
+
if (!isReleaseLevel(rawLevel))
|
|
760
|
+
return;
|
|
761
|
+
if (seenLevels.has(level)) {
|
|
762
|
+
pushIssue(bag, `${entryPath}.level`, "duplicate_release_level_contract", `Release level ${level} appears more than once.`);
|
|
763
|
+
}
|
|
764
|
+
seenLevels.add(level);
|
|
765
|
+
const expected = CODALI_IMPROVEMENT_RELEASE_LEVEL_CONTRACTS[level];
|
|
766
|
+
if (name !== expected.name ||
|
|
767
|
+
description !== expected.description ||
|
|
768
|
+
minAction !== expected.minAction ||
|
|
769
|
+
!sameStringSequence(allowedActions, expected.allowedActions) ||
|
|
770
|
+
!sameStringSequence(allowedArtifactTypes, expected.allowedArtifactTypes)) {
|
|
771
|
+
pushIssue(bag, entryPath, "release_level_contract_mismatch", `Release level ${level} must match the canonical improvement contract.`);
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
for (const level of CODALI_IMPROVEMENT_RELEASE_LEVELS) {
|
|
775
|
+
if (!seenLevels.has(level)) {
|
|
776
|
+
pushIssue(bag, path, "missing_release_level_contract", `Release level ${level} is missing from CLI data.`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
const validateMonitorReportData = (input) => {
|
|
781
|
+
const bag = { issues: [] };
|
|
782
|
+
const record = requireRecord(input, "$", bag);
|
|
783
|
+
if (!record)
|
|
784
|
+
return fail(bag);
|
|
785
|
+
assertAllowedKeys(record, "$", [
|
|
786
|
+
"schemaVersion",
|
|
787
|
+
"releaseId",
|
|
788
|
+
"status",
|
|
789
|
+
"generatedAt",
|
|
790
|
+
"monitorWindow",
|
|
791
|
+
"thresholds",
|
|
792
|
+
"metrics",
|
|
793
|
+
"runtimeFlags",
|
|
794
|
+
"shadowTraffic",
|
|
795
|
+
"rollbackTriggers",
|
|
796
|
+
"rolloutEvents",
|
|
797
|
+
"rollbackEvents",
|
|
798
|
+
"outcome",
|
|
799
|
+
"improvementCycleFeedback",
|
|
800
|
+
"storageWrites",
|
|
801
|
+
], bag);
|
|
802
|
+
readConstString(record, "schemaVersion", CODALI_IMPROVEMENT_MONITOR_REPORT_SCHEMA_VERSION, "$.schemaVersion", bag);
|
|
803
|
+
readNonEmptyString(record, "releaseId", "$.releaseId", bag);
|
|
804
|
+
readEnumValue(record, "status", ["healthy", "watch", "rollback_required", "rolled_back"], "$.status", bag);
|
|
805
|
+
readIsoLikeString(record, "generatedAt", "$.generatedAt", bag);
|
|
806
|
+
validateMonitorWindow(record.monitorWindow, "$.monitorWindow", bag);
|
|
807
|
+
validateMonitorThresholds(record.thresholds, "$.thresholds", bag);
|
|
808
|
+
validateMonitorMetrics(record.metrics, "$.metrics", bag);
|
|
809
|
+
validateMonitorRuntimeFlags(record.runtimeFlags, "$.runtimeFlags", bag);
|
|
810
|
+
validateMonitorShadowTraffic(record.shadowTraffic, "$.shadowTraffic", bag);
|
|
811
|
+
validateMonitorRollbackTriggers(record.rollbackTriggers, "$.rollbackTriggers", bag);
|
|
812
|
+
validateMonitorRolloutEvents(record.rolloutEvents, "$.rolloutEvents", bag);
|
|
813
|
+
validateMonitorRollbackEvents(record.rollbackEvents, "$.rollbackEvents", bag);
|
|
814
|
+
readNestedValidation(validateCodaliImprovementOutcome(record.outcome), "$.outcome", bag);
|
|
815
|
+
validateMonitorFeedback(record.improvementCycleFeedback, "$.improvementCycleFeedback", bag);
|
|
816
|
+
validateMonitorStorageWrites(record.storageWrites, "$.storageWrites", bag);
|
|
817
|
+
return bag.issues.length > 0 ? fail(bag) : ok(record);
|
|
818
|
+
};
|
|
819
|
+
const validateMonitorWindow = (input, path, bag) => {
|
|
820
|
+
const record = requireRecord(input, path, bag);
|
|
821
|
+
if (!record)
|
|
822
|
+
return;
|
|
823
|
+
assertAllowedKeys(record, path, ["startedAt", "endedAt", "durationMinutes"], bag);
|
|
824
|
+
readIsoLikeString(record, "startedAt", `${path}.startedAt`, bag);
|
|
825
|
+
readIsoLikeString(record, "endedAt", `${path}.endedAt`, bag);
|
|
826
|
+
readPositiveInteger(record, "durationMinutes", `${path}.durationMinutes`, bag);
|
|
827
|
+
};
|
|
828
|
+
const validateMonitorThresholds = (input, path, bag) => {
|
|
829
|
+
const record = requireRecord(input, path, bag);
|
|
830
|
+
if (!record)
|
|
831
|
+
return;
|
|
832
|
+
assertAllowedKeys(record, path, [
|
|
833
|
+
"maxSchemaFailures",
|
|
834
|
+
"minAcceptedAnswerRate",
|
|
835
|
+
"maxAcceptedAnswerRateDrop",
|
|
836
|
+
"maxVerifierContradictions",
|
|
837
|
+
"maxToolFailures",
|
|
838
|
+
"maxP95LatencyIncreaseRatio",
|
|
839
|
+
"maxCostIncreaseRatio",
|
|
840
|
+
"maxPrivacySecurityWarnings",
|
|
841
|
+
], bag);
|
|
842
|
+
readNonNegativeInteger(record, "maxSchemaFailures", `${path}.maxSchemaFailures`, bag);
|
|
843
|
+
readNonNegativeNumber(record, "minAcceptedAnswerRate", `${path}.minAcceptedAnswerRate`, bag);
|
|
844
|
+
readNonNegativeNumber(record, "maxAcceptedAnswerRateDrop", `${path}.maxAcceptedAnswerRateDrop`, bag);
|
|
845
|
+
readNonNegativeInteger(record, "maxVerifierContradictions", `${path}.maxVerifierContradictions`, bag);
|
|
846
|
+
readNonNegativeInteger(record, "maxToolFailures", `${path}.maxToolFailures`, bag);
|
|
847
|
+
readNonNegativeNumber(record, "maxP95LatencyIncreaseRatio", `${path}.maxP95LatencyIncreaseRatio`, bag);
|
|
848
|
+
readNonNegativeNumber(record, "maxCostIncreaseRatio", `${path}.maxCostIncreaseRatio`, bag);
|
|
849
|
+
readNonNegativeInteger(record, "maxPrivacySecurityWarnings", `${path}.maxPrivacySecurityWarnings`, bag);
|
|
850
|
+
};
|
|
851
|
+
const validateMonitorMetrics = (input, path, bag) => {
|
|
852
|
+
const record = requireRecord(input, path, bag);
|
|
853
|
+
if (!record)
|
|
854
|
+
return;
|
|
855
|
+
assertAllowedKeys(record, path, [
|
|
856
|
+
"eligibleRequestCount",
|
|
857
|
+
"shadowRequestCount",
|
|
858
|
+
"schemaFailures",
|
|
859
|
+
"acceptedAnswerRate",
|
|
860
|
+
"baselineAcceptedAnswerRate",
|
|
861
|
+
"verifierContradictions",
|
|
862
|
+
"toolFailures",
|
|
863
|
+
"p95LatencyMs",
|
|
864
|
+
"baselineP95LatencyMs",
|
|
865
|
+
"costUsd",
|
|
866
|
+
"baselineCostUsd",
|
|
867
|
+
"privacySecurityWarnings",
|
|
868
|
+
], bag);
|
|
869
|
+
readNonNegativeInteger(record, "eligibleRequestCount", `${path}.eligibleRequestCount`, bag);
|
|
870
|
+
readNonNegativeInteger(record, "shadowRequestCount", `${path}.shadowRequestCount`, bag);
|
|
871
|
+
readNonNegativeInteger(record, "schemaFailures", `${path}.schemaFailures`, bag);
|
|
872
|
+
readOptionalRate(record, "acceptedAnswerRate", `${path}.acceptedAnswerRate`, bag);
|
|
873
|
+
readOptionalRate(record, "baselineAcceptedAnswerRate", `${path}.baselineAcceptedAnswerRate`, bag);
|
|
874
|
+
readNonNegativeInteger(record, "verifierContradictions", `${path}.verifierContradictions`, bag);
|
|
875
|
+
readNonNegativeInteger(record, "toolFailures", `${path}.toolFailures`, bag);
|
|
876
|
+
readOptionalNonNegativeNumber(record, "p95LatencyMs", `${path}.p95LatencyMs`, bag);
|
|
877
|
+
readOptionalNonNegativeNumber(record, "baselineP95LatencyMs", `${path}.baselineP95LatencyMs`, bag);
|
|
878
|
+
readOptionalNonNegativeNumber(record, "costUsd", `${path}.costUsd`, bag);
|
|
879
|
+
readOptionalNonNegativeNumber(record, "baselineCostUsd", `${path}.baselineCostUsd`, bag);
|
|
880
|
+
readNonNegativeInteger(record, "privacySecurityWarnings", `${path}.privacySecurityWarnings`, bag);
|
|
881
|
+
};
|
|
882
|
+
const validateMonitorRuntimeFlags = (input, path, bag) => {
|
|
883
|
+
if (!Array.isArray(input)) {
|
|
884
|
+
pushIssue(bag, path, "required_array", "runtimeFlags must be an array.");
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
const seen = new Set();
|
|
888
|
+
input.forEach((entry, index) => {
|
|
889
|
+
const entryPath = `${path}[${index}]`;
|
|
890
|
+
const record = requireRecord(entry, entryPath, bag);
|
|
891
|
+
if (!record)
|
|
892
|
+
return;
|
|
893
|
+
assertAllowedKeys(record, entryPath, [
|
|
894
|
+
"packageKind",
|
|
895
|
+
"version",
|
|
896
|
+
"enabled",
|
|
897
|
+
"disableOnRollback",
|
|
898
|
+
"rollbackDisabled",
|
|
899
|
+
"reason",
|
|
900
|
+
], bag);
|
|
901
|
+
const packageKind = readEnumValue(record, "packageKind", CODALI_IMPROVEMENT_MONITOR_RUNTIME_PACKAGE_KINDS, `${entryPath}.packageKind`, bag);
|
|
902
|
+
if (seen.has(packageKind)) {
|
|
903
|
+
pushIssue(bag, `${entryPath}.packageKind`, "duplicate_runtime_package_flag", `${packageKind} appears more than once.`);
|
|
904
|
+
}
|
|
905
|
+
seen.add(packageKind);
|
|
906
|
+
readNonEmptyString(record, "version", `${entryPath}.version`, bag);
|
|
907
|
+
readBoolean(record, "enabled", `${entryPath}.enabled`, bag);
|
|
908
|
+
readBoolean(record, "disableOnRollback", `${entryPath}.disableOnRollback`, bag);
|
|
909
|
+
readBoolean(record, "rollbackDisabled", `${entryPath}.rollbackDisabled`, bag);
|
|
910
|
+
readOptionalString(record, "reason", `${entryPath}.reason`, bag);
|
|
911
|
+
});
|
|
912
|
+
for (const packageKind of CODALI_IMPROVEMENT_MONITOR_RUNTIME_PACKAGE_KINDS) {
|
|
913
|
+
if (!seen.has(packageKind)) {
|
|
914
|
+
pushIssue(bag, path, "missing_runtime_package_flag", `${packageKind} must be represented in runtimeFlags.`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
const validateMonitorShadowTraffic = (input, path, bag) => {
|
|
919
|
+
const record = requireRecord(input, path, bag);
|
|
920
|
+
if (!record)
|
|
921
|
+
return;
|
|
922
|
+
assertAllowedKeys(record, path, [
|
|
923
|
+
"enabled",
|
|
924
|
+
"nonBlocking",
|
|
925
|
+
"eligibleRequestCount",
|
|
926
|
+
"shadowRequestCount",
|
|
927
|
+
"coverageRate",
|
|
928
|
+
"status",
|
|
929
|
+
], bag);
|
|
930
|
+
readBoolean(record, "enabled", `${path}.enabled`, bag);
|
|
931
|
+
if (record.nonBlocking !== true) {
|
|
932
|
+
pushIssue(bag, `${path}.nonBlocking`, "non_blocking_shadow_required", "Shadow traffic must be non-blocking.");
|
|
933
|
+
}
|
|
934
|
+
readNonNegativeInteger(record, "eligibleRequestCount", `${path}.eligibleRequestCount`, bag);
|
|
935
|
+
readNonNegativeInteger(record, "shadowRequestCount", `${path}.shadowRequestCount`, bag);
|
|
936
|
+
readNonNegativeNumber(record, "coverageRate", `${path}.coverageRate`, bag);
|
|
937
|
+
readEnumValue(record, "status", ["not_eligible", "skipped", "partial", "completed"], `${path}.status`, bag);
|
|
938
|
+
};
|
|
939
|
+
const validateMonitorRollbackTriggers = (input, path, bag) => {
|
|
940
|
+
if (!Array.isArray(input)) {
|
|
941
|
+
pushIssue(bag, path, "required_array", "rollbackTriggers must be an array.");
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const seen = new Set();
|
|
945
|
+
input.forEach((entry, index) => {
|
|
946
|
+
const entryPath = `${path}[${index}]`;
|
|
947
|
+
const record = requireRecord(entry, entryPath, bag);
|
|
948
|
+
if (!record)
|
|
949
|
+
return;
|
|
950
|
+
assertAllowedKeys(record, entryPath, [
|
|
951
|
+
"code",
|
|
952
|
+
"triggered",
|
|
953
|
+
"observed",
|
|
954
|
+
"threshold",
|
|
955
|
+
"message",
|
|
956
|
+
], bag);
|
|
957
|
+
const code = readEnumValue(record, "code", CODALI_IMPROVEMENT_MONITOR_ROLLBACK_TRIGGER_CODES, `${entryPath}.code`, bag);
|
|
958
|
+
if (seen.has(code)) {
|
|
959
|
+
pushIssue(bag, `${entryPath}.code`, "duplicate_rollback_trigger", `${code} appears more than once.`);
|
|
960
|
+
}
|
|
961
|
+
seen.add(code);
|
|
962
|
+
readBoolean(record, "triggered", `${entryPath}.triggered`, bag);
|
|
963
|
+
readNonNegativeNumber(record, "observed", `${entryPath}.observed`, bag);
|
|
964
|
+
readNonNegativeNumber(record, "threshold", `${entryPath}.threshold`, bag);
|
|
965
|
+
readNonEmptyString(record, "message", `${entryPath}.message`, bag);
|
|
966
|
+
});
|
|
967
|
+
for (const code of CODALI_IMPROVEMENT_MONITOR_ROLLBACK_TRIGGER_CODES) {
|
|
968
|
+
if (!seen.has(code)) {
|
|
969
|
+
pushIssue(bag, path, "missing_rollback_trigger", `${code} must be represented in rollbackTriggers.`);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
const validateMonitorRolloutEvents = (input, path, bag) => {
|
|
974
|
+
if (!Array.isArray(input)) {
|
|
975
|
+
pushIssue(bag, path, "required_array", "rolloutEvents must be an array.");
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
input.forEach((entry, index) => {
|
|
979
|
+
const entryPath = `${path}[${index}]`;
|
|
980
|
+
const record = requireRecord(entry, entryPath, bag);
|
|
981
|
+
if (!record)
|
|
982
|
+
return;
|
|
983
|
+
assertAllowedKeys(record, entryPath, [
|
|
984
|
+
"eventId",
|
|
985
|
+
"releaseId",
|
|
986
|
+
"eventType",
|
|
987
|
+
"createdAt",
|
|
988
|
+
"metadata",
|
|
989
|
+
], bag);
|
|
990
|
+
readNonEmptyString(record, "eventId", `${entryPath}.eventId`, bag);
|
|
991
|
+
readNonEmptyString(record, "releaseId", `${entryPath}.releaseId`, bag);
|
|
992
|
+
readEnumValue(record, "eventType", [
|
|
993
|
+
"monitor_started",
|
|
994
|
+
"runtime_flags_applied",
|
|
995
|
+
"shadow_traffic_started",
|
|
996
|
+
"shadow_traffic_completed",
|
|
997
|
+
], `${entryPath}.eventType`, bag);
|
|
998
|
+
readIsoLikeString(record, "createdAt", `${entryPath}.createdAt`, bag);
|
|
999
|
+
requireRecord(record.metadata, `${entryPath}.metadata`, bag);
|
|
1000
|
+
});
|
|
1001
|
+
};
|
|
1002
|
+
const validateMonitorRollbackEvents = (input, path, bag) => {
|
|
1003
|
+
if (!Array.isArray(input)) {
|
|
1004
|
+
pushIssue(bag, path, "required_array", "rollbackEvents must be an array.");
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
input.forEach((entry, index) => {
|
|
1008
|
+
const entryPath = `${path}[${index}]`;
|
|
1009
|
+
const record = requireRecord(entry, entryPath, bag);
|
|
1010
|
+
if (!record)
|
|
1011
|
+
return;
|
|
1012
|
+
assertAllowedKeys(record, entryPath, [
|
|
1013
|
+
"eventId",
|
|
1014
|
+
"releaseId",
|
|
1015
|
+
"eventType",
|
|
1016
|
+
"createdAt",
|
|
1017
|
+
"triggerCodes",
|
|
1018
|
+
"runtimePackageKind",
|
|
1019
|
+
"runtimePackageVersion",
|
|
1020
|
+
"unpublishNpm",
|
|
1021
|
+
"metadata",
|
|
1022
|
+
], bag);
|
|
1023
|
+
readNonEmptyString(record, "eventId", `${entryPath}.eventId`, bag);
|
|
1024
|
+
readNonEmptyString(record, "releaseId", `${entryPath}.releaseId`, bag);
|
|
1025
|
+
readEnumValue(record, "eventType", ["rollback_triggered", "runtime_package_disabled", "rollback_applied"], `${entryPath}.eventType`, bag);
|
|
1026
|
+
readIsoLikeString(record, "createdAt", `${entryPath}.createdAt`, bag);
|
|
1027
|
+
readEnumArray(record, "triggerCodes", CODALI_IMPROVEMENT_MONITOR_ROLLBACK_TRIGGER_CODES, `${entryPath}.triggerCodes`, bag);
|
|
1028
|
+
readOptionalEnumValue(record, "runtimePackageKind", CODALI_IMPROVEMENT_MONITOR_RUNTIME_PACKAGE_KINDS, `${entryPath}.runtimePackageKind`, bag);
|
|
1029
|
+
readOptionalString(record, "runtimePackageVersion", `${entryPath}.runtimePackageVersion`, bag);
|
|
1030
|
+
if (record.unpublishNpm !== false) {
|
|
1031
|
+
pushIssue(bag, `${entryPath}.unpublishNpm`, "rollback_must_not_unpublish_npm", "Rollback events must disable runtime packages without unpublishing npm.");
|
|
1032
|
+
}
|
|
1033
|
+
requireRecord(record.metadata, `${entryPath}.metadata`, bag);
|
|
1034
|
+
});
|
|
1035
|
+
};
|
|
1036
|
+
const validateMonitorFeedback = (input, path, bag) => {
|
|
1037
|
+
const record = requireRecord(input, path, bag);
|
|
1038
|
+
if (!record)
|
|
1039
|
+
return;
|
|
1040
|
+
assertAllowedKeys(record, path, [
|
|
1041
|
+
"status",
|
|
1042
|
+
"releaseId",
|
|
1043
|
+
"nextCycleReasons",
|
|
1044
|
+
"recommendedArtifactTypes",
|
|
1045
|
+
"source",
|
|
1046
|
+
], bag);
|
|
1047
|
+
readEnumValue(record, "status", ["recorded", "queued"], `${path}.status`, bag);
|
|
1048
|
+
readNonEmptyString(record, "releaseId", `${path}.releaseId`, bag);
|
|
1049
|
+
readStringArray(record, "nextCycleReasons", `${path}.nextCycleReasons`, bag);
|
|
1050
|
+
readStringArray(record, "recommendedArtifactTypes", `${path}.recommendedArtifactTypes`, bag);
|
|
1051
|
+
readConstString(record, "source", "release_monitor", `${path}.source`, bag);
|
|
1052
|
+
};
|
|
1053
|
+
const validateMonitorStorageWrites = (input, path, bag) => {
|
|
1054
|
+
if (!Array.isArray(input)) {
|
|
1055
|
+
pushIssue(bag, path, "required_array", "storageWrites must be an array.");
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
input.forEach((entry, index) => {
|
|
1059
|
+
const entryPath = `${path}[${index}]`;
|
|
1060
|
+
const record = requireRecord(entry, entryPath, bag);
|
|
1061
|
+
if (!record)
|
|
1062
|
+
return;
|
|
1063
|
+
assertAllowedKeys(record, entryPath, ["accepted", "status", "scope"], bag);
|
|
1064
|
+
readBoolean(record, "accepted", `${entryPath}.accepted`, bag);
|
|
1065
|
+
readNonNegativeInteger(record, "status", `${entryPath}.status`, bag);
|
|
1066
|
+
validateMonitorStorageScope(record.scope, `${entryPath}.scope`, bag);
|
|
1067
|
+
});
|
|
1068
|
+
};
|
|
1069
|
+
const validateMonitorStorageScope = (input, path, bag) => {
|
|
1070
|
+
const record = requireRecord(input, path, bag);
|
|
1071
|
+
if (!record)
|
|
1072
|
+
return;
|
|
1073
|
+
assertAllowedKeys(record, path, ["tenantId", "productId", "deploymentId", "runId"], bag);
|
|
1074
|
+
readNonEmptyString(record, "tenantId", `${path}.tenantId`, bag);
|
|
1075
|
+
readNonEmptyString(record, "productId", `${path}.productId`, bag);
|
|
1076
|
+
readNonEmptyString(record, "deploymentId", `${path}.deploymentId`, bag);
|
|
1077
|
+
readNonEmptyString(record, "runId", `${path}.runId`, bag);
|
|
1078
|
+
};
|
|
1079
|
+
const validatePolicyInvariants = (policy, path, bag) => {
|
|
1080
|
+
const allowedForLevel = getCodaliImprovementAllowedArtifactTypesForLevel(policy.releaseLevel);
|
|
1081
|
+
for (const artifactType of policy.allowedArtifactTypes) {
|
|
1082
|
+
if (!allowedForLevel.includes(artifactType)) {
|
|
1083
|
+
pushIssue(bag, `${path}.allowedArtifactTypes`, "artifact_type_exceeds_release_level", `${artifactType} is not allowed for release level ${policy.releaseLevel}`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (policy.autoTagEnabled && policy.releaseLevel < 3) {
|
|
1087
|
+
pushIssue(bag, `${path}.autoTagEnabled`, "auto_tag_requires_level_3", "auto-tagging requires release level 3 or higher.");
|
|
1088
|
+
}
|
|
1089
|
+
if (policy.autoPublishEnabled && policy.releaseLevel < 4) {
|
|
1090
|
+
pushIssue(bag, `${path}.autoPublishEnabled`, "auto_publish_requires_level_4", "auto-publish requires release level 4.");
|
|
1091
|
+
}
|
|
1092
|
+
if (policy.trainingEnabled && policy.maxExamples <= 0) {
|
|
1093
|
+
pushIssue(bag, `${path}.trainingEnabled`, "training_requires_examples", "Training cannot be enabled when maxExamples is zero.");
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
const validateReleaseInvariants = (release, path, bag) => {
|
|
1097
|
+
if (release.releaseLevel >= 3 && !release.tagName) {
|
|
1098
|
+
pushIssue(bag, `${path}.tagName`, "release_level_requires_tag", "Release levels 3 and 4 require an explicit tagName.");
|
|
1099
|
+
}
|
|
1100
|
+
if (release.releaseLevel === 4 && (!release.packageName || !release.version)) {
|
|
1101
|
+
pushIssue(bag, path, "stable_release_requires_package_version", "Release level 4 requires packageName and version.");
|
|
1102
|
+
}
|
|
1103
|
+
if ((release.status === "blocked" || release.status === "failed") &&
|
|
1104
|
+
(!release.blockedReasons || release.blockedReasons.length === 0)) {
|
|
1105
|
+
pushIssue(bag, `${path}.blockedReasons`, "blocked_release_requires_reason", "Blocked or failed releases must retain at least one reason.");
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
const validateOutcomeInvariants = (outcome, path, bag) => {
|
|
1109
|
+
if ((outcome.status === "failed" ||
|
|
1110
|
+
outcome.status === "degraded" ||
|
|
1111
|
+
outcome.status === "blocked") &&
|
|
1112
|
+
(!outcome.reasons || outcome.reasons.length === 0)) {
|
|
1113
|
+
pushIssue(bag, `${path}.reasons`, "non_success_outcome_requires_reason", "Failed, degraded, or blocked outcomes must retain at least one reason.");
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
const levelAllowsAction = (level, action) => {
|
|
1117
|
+
const allowedActions = CODALI_IMPROVEMENT_RELEASE_LEVEL_CONTRACTS[level].allowedActions;
|
|
1118
|
+
return allowedActions.includes(action);
|
|
1119
|
+
};
|
|
1120
|
+
const sameScope = (left, right) => left.tenantHash === right.tenantHash &&
|
|
1121
|
+
left.productId === right.productId &&
|
|
1122
|
+
(left.deploymentId ?? "") === (right.deploymentId ?? "");
|
|
1123
|
+
const isReleaseLevel = (value) => typeof value === "number" &&
|
|
1124
|
+
CODALI_IMPROVEMENT_RELEASE_LEVELS.includes(value);
|
|
1125
|
+
const validateScope = (input, path, bag) => {
|
|
1126
|
+
const record = requireRecord(input, path, bag);
|
|
1127
|
+
if (!record)
|
|
1128
|
+
return undefined;
|
|
1129
|
+
assertAllowedKeys(record, path, ["tenantHash", "productId", "deploymentId"], bag);
|
|
1130
|
+
return withoutUndefined({
|
|
1131
|
+
tenantHash: readNonEmptyString(record, "tenantHash", `${path}.tenantHash`, bag),
|
|
1132
|
+
productId: readNonEmptyString(record, "productId", `${path}.productId`, bag),
|
|
1133
|
+
deploymentId: readOptionalString(record, "deploymentId", `${path}.deploymentId`, bag),
|
|
1134
|
+
});
|
|
1135
|
+
};
|
|
1136
|
+
const validateGateWithBag = (input, path, bag) => {
|
|
1137
|
+
const record = requireRecord(input, path, bag);
|
|
1138
|
+
if (!record)
|
|
1139
|
+
return undefined;
|
|
1140
|
+
assertAllowedKeys(record, path, [
|
|
1141
|
+
"schemaVersion",
|
|
1142
|
+
"gateId",
|
|
1143
|
+
"candidateId",
|
|
1144
|
+
"gateType",
|
|
1145
|
+
"status",
|
|
1146
|
+
"required",
|
|
1147
|
+
"passed",
|
|
1148
|
+
"createdAt",
|
|
1149
|
+
"score",
|
|
1150
|
+
"reasons",
|
|
1151
|
+
"metadata",
|
|
1152
|
+
], bag);
|
|
1153
|
+
const status = readEnumValue(record, "status", ["passed", "failed", "blocked", "skipped", "warning"], `${path}.status`, bag);
|
|
1154
|
+
const reasons = readOptionalStringArray(record, "reasons", `${path}.reasons`, bag);
|
|
1155
|
+
if (status === "skipped" && (!reasons || reasons.length === 0)) {
|
|
1156
|
+
pushIssue(bag, `${path}.reasons`, "skipped_gate_reasons_required", "Skipped gates must include at least one exact reason.");
|
|
1157
|
+
}
|
|
1158
|
+
return withoutUndefined({
|
|
1159
|
+
schemaVersion: readConstString(record, "schemaVersion", CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION, `${path}.schemaVersion`, bag),
|
|
1160
|
+
gateId: readNonEmptyString(record, "gateId", `${path}.gateId`, bag),
|
|
1161
|
+
candidateId: readNonEmptyString(record, "candidateId", `${path}.candidateId`, bag),
|
|
1162
|
+
gateType: readEnumValue(record, "gateType", ["policy", "privacy", "eval", "replay", "release", "manual_review"], `${path}.gateType`, bag),
|
|
1163
|
+
status,
|
|
1164
|
+
required: readBoolean(record, "required", `${path}.required`, bag),
|
|
1165
|
+
passed: readBoolean(record, "passed", `${path}.passed`, bag),
|
|
1166
|
+
createdAt: readIsoLikeString(record, "createdAt", `${path}.createdAt`, bag),
|
|
1167
|
+
score: readOptionalNumber(record, "score", `${path}.score`, bag),
|
|
1168
|
+
reasons,
|
|
1169
|
+
metadata: readOptionalRecord(record, "metadata", `${path}.metadata`, bag),
|
|
1170
|
+
});
|
|
1171
|
+
};
|
|
1172
|
+
const readGateArray = (record, key, path, bag) => {
|
|
1173
|
+
const value = record[key];
|
|
1174
|
+
if (!Array.isArray(value)) {
|
|
1175
|
+
pushIssue(bag, path, "required_array", `${key} must be an array.`);
|
|
1176
|
+
return [];
|
|
1177
|
+
}
|
|
1178
|
+
return value
|
|
1179
|
+
.map((entry, index) => validateGateWithBag(entry, `${path}[${index}]`, bag))
|
|
1180
|
+
.filter((entry) => Boolean(entry));
|
|
1181
|
+
};
|
|
1182
|
+
const readScores = (record, key, path, bag) => {
|
|
1183
|
+
const value = record[key];
|
|
1184
|
+
if (!isRecord(value)) {
|
|
1185
|
+
pushIssue(bag, path, "required_object", `${key} must be an object.`);
|
|
1186
|
+
return {};
|
|
1187
|
+
}
|
|
1188
|
+
const scores = {};
|
|
1189
|
+
for (const [scoreKey, scoreValue] of Object.entries(value)) {
|
|
1190
|
+
if (typeof scoreValue !== "number" || !Number.isFinite(scoreValue)) {
|
|
1191
|
+
pushIssue(bag, `${path}.${scoreKey}`, "invalid_number", "Score values must be finite numbers.");
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
scores[scoreKey] = scoreValue;
|
|
1195
|
+
}
|
|
1196
|
+
return scores;
|
|
1197
|
+
};
|
|
1198
|
+
const readIssueArray = (record, key, path, bag) => {
|
|
1199
|
+
const value = record[key];
|
|
1200
|
+
if (!Array.isArray(value)) {
|
|
1201
|
+
pushIssue(bag, path, "required_array", `${key} must be an array.`);
|
|
1202
|
+
return [];
|
|
1203
|
+
}
|
|
1204
|
+
const issues = [];
|
|
1205
|
+
value.forEach((entry, index) => {
|
|
1206
|
+
const entryPath = `${path}[${index}]`;
|
|
1207
|
+
const issueRecord = requireRecord(entry, entryPath, bag);
|
|
1208
|
+
if (!issueRecord)
|
|
1209
|
+
return;
|
|
1210
|
+
assertAllowedKeys(issueRecord, entryPath, ["path", "code", "message"], bag);
|
|
1211
|
+
issues.push({
|
|
1212
|
+
path: readNonEmptyString(issueRecord, "path", `${entryPath}.path`, bag),
|
|
1213
|
+
code: readNonEmptyString(issueRecord, "code", `${entryPath}.code`, bag),
|
|
1214
|
+
message: readNonEmptyString(issueRecord, "message", `${entryPath}.message`, bag),
|
|
1215
|
+
});
|
|
1216
|
+
});
|
|
1217
|
+
return issues;
|
|
1218
|
+
};
|
|
1219
|
+
const readNestedValidation = (result, path, bag) => {
|
|
1220
|
+
if (result.ok)
|
|
1221
|
+
return result.value;
|
|
1222
|
+
for (const issue of result.issues) {
|
|
1223
|
+
pushIssue(bag, `${path}${issue.path === "$" ? "" : issue.path.slice(1)}`, issue.code, issue.message);
|
|
1224
|
+
}
|
|
1225
|
+
return undefined;
|
|
1226
|
+
};
|
|
1227
|
+
const readReleaseLevel = (record, key, path, bag) => {
|
|
1228
|
+
const value = record[key];
|
|
1229
|
+
if (!isReleaseLevel(value)) {
|
|
1230
|
+
pushIssue(bag, path, "invalid_release_level", `${key} must be one of ${CODALI_IMPROVEMENT_RELEASE_LEVELS.join(", ")}.`);
|
|
1231
|
+
return 0;
|
|
1232
|
+
}
|
|
1233
|
+
return value;
|
|
1234
|
+
};
|
|
1235
|
+
const readEnumArray = (record, key, allowed, path, bag) => {
|
|
1236
|
+
const value = record[key];
|
|
1237
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
1238
|
+
pushIssue(bag, path, "required_array", `${key} must be a non-empty array.`);
|
|
1239
|
+
return [];
|
|
1240
|
+
}
|
|
1241
|
+
const seen = new Set();
|
|
1242
|
+
const output = [];
|
|
1243
|
+
value.forEach((entry, index) => {
|
|
1244
|
+
if (typeof entry !== "string" || !allowed.includes(entry)) {
|
|
1245
|
+
pushIssue(bag, `${path}[${index}]`, "invalid_enum", `${key} contains an unsupported value.`);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
if (seen.has(entry)) {
|
|
1249
|
+
pushIssue(bag, `${path}[${index}]`, "duplicate_value", `${key} cannot contain duplicate values.`);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
seen.add(entry);
|
|
1253
|
+
output.push(entry);
|
|
1254
|
+
});
|
|
1255
|
+
return output;
|
|
1256
|
+
};
|
|
1257
|
+
const readStringArray = (record, key, path, bag) => {
|
|
1258
|
+
const value = record[key];
|
|
1259
|
+
if (!Array.isArray(value)) {
|
|
1260
|
+
pushIssue(bag, path, "required_array", `${key} must be an array.`);
|
|
1261
|
+
return [];
|
|
1262
|
+
}
|
|
1263
|
+
return normalizeStringArray(value, key, path, bag);
|
|
1264
|
+
};
|
|
1265
|
+
const readOptionalStringArray = (record, key, path, bag) => {
|
|
1266
|
+
if (record[key] === undefined)
|
|
1267
|
+
return undefined;
|
|
1268
|
+
return readStringArray(record, key, path, bag);
|
|
1269
|
+
};
|
|
1270
|
+
const normalizeStringArray = (value, key, path, bag) => {
|
|
1271
|
+
const output = [];
|
|
1272
|
+
value.forEach((entry, index) => {
|
|
1273
|
+
if (typeof entry !== "string" || entry.trim().length === 0) {
|
|
1274
|
+
pushIssue(bag, `${path}[${index}]`, "invalid_string", `${key} must contain only non-empty strings.`);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
output.push(entry);
|
|
1278
|
+
});
|
|
1279
|
+
return output;
|
|
1280
|
+
};
|
|
1281
|
+
const readEnumValue = (record, key, allowed, path, bag) => {
|
|
1282
|
+
const value = record[key];
|
|
1283
|
+
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
1284
|
+
pushIssue(bag, path, "invalid_enum", `${key} contains an unsupported value.`);
|
|
1285
|
+
return allowed[0];
|
|
1286
|
+
}
|
|
1287
|
+
return value;
|
|
1288
|
+
};
|
|
1289
|
+
const readOptionalEnumValue = (record, key, allowed, path, bag) => {
|
|
1290
|
+
if (record[key] === undefined)
|
|
1291
|
+
return undefined;
|
|
1292
|
+
return readEnumValue(record, key, allowed, path, bag);
|
|
1293
|
+
};
|
|
1294
|
+
const readConstString = (record, key, expected, path, bag) => {
|
|
1295
|
+
if (record[key] !== expected) {
|
|
1296
|
+
pushIssue(bag, path, "invalid_const", `${key} must be ${expected}.`);
|
|
1297
|
+
}
|
|
1298
|
+
return expected;
|
|
1299
|
+
};
|
|
1300
|
+
const readNonEmptyString = (record, key, path, bag) => {
|
|
1301
|
+
const value = record[key];
|
|
1302
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
1303
|
+
pushIssue(bag, path, "required_string", `${key} must be a non-empty string.`);
|
|
1304
|
+
return "";
|
|
1305
|
+
}
|
|
1306
|
+
return value;
|
|
1307
|
+
};
|
|
1308
|
+
const readOptionalString = (record, key, path, bag) => {
|
|
1309
|
+
if (record[key] === undefined)
|
|
1310
|
+
return undefined;
|
|
1311
|
+
return readNonEmptyString(record, key, path, bag);
|
|
1312
|
+
};
|
|
1313
|
+
const readIsoLikeString = (record, key, path, bag) => {
|
|
1314
|
+
const value = readNonEmptyString(record, key, path, bag);
|
|
1315
|
+
if (value && Number.isNaN(Date.parse(value))) {
|
|
1316
|
+
pushIssue(bag, path, "invalid_timestamp", `${key} must be parseable as a timestamp.`);
|
|
1317
|
+
}
|
|
1318
|
+
return value;
|
|
1319
|
+
};
|
|
1320
|
+
const readBoolean = (record, key, path, bag) => {
|
|
1321
|
+
const value = record[key];
|
|
1322
|
+
if (typeof value !== "boolean") {
|
|
1323
|
+
pushIssue(bag, path, "required_boolean", `${key} must be a boolean.`);
|
|
1324
|
+
return false;
|
|
1325
|
+
}
|
|
1326
|
+
return value;
|
|
1327
|
+
};
|
|
1328
|
+
const readNonNegativeInteger = (record, key, path, bag) => {
|
|
1329
|
+
const value = record[key];
|
|
1330
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
1331
|
+
pushIssue(bag, path, "required_non_negative_integer", `${key} must be a non-negative integer.`);
|
|
1332
|
+
return 0;
|
|
1333
|
+
}
|
|
1334
|
+
return value;
|
|
1335
|
+
};
|
|
1336
|
+
const readPositiveInteger = (record, key, path, bag) => {
|
|
1337
|
+
const value = record[key];
|
|
1338
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
1339
|
+
pushIssue(bag, path, "required_positive_integer", `${key} must be a positive integer.`);
|
|
1340
|
+
return 1;
|
|
1341
|
+
}
|
|
1342
|
+
return value;
|
|
1343
|
+
};
|
|
1344
|
+
const readNonNegativeNumber = (record, key, path, bag) => {
|
|
1345
|
+
const value = record[key];
|
|
1346
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
1347
|
+
pushIssue(bag, path, "required_non_negative_number", `${key} must be a finite non-negative number.`);
|
|
1348
|
+
return 0;
|
|
1349
|
+
}
|
|
1350
|
+
return value;
|
|
1351
|
+
};
|
|
1352
|
+
const readOptionalNumber = (record, key, path, bag) => {
|
|
1353
|
+
if (record[key] === undefined)
|
|
1354
|
+
return undefined;
|
|
1355
|
+
const value = record[key];
|
|
1356
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1357
|
+
pushIssue(bag, path, "invalid_number", `${key} must be a finite number.`);
|
|
1358
|
+
return undefined;
|
|
1359
|
+
}
|
|
1360
|
+
return value;
|
|
1361
|
+
};
|
|
1362
|
+
const readOptionalNonNegativeNumber = (record, key, path, bag) => {
|
|
1363
|
+
if (record[key] === undefined)
|
|
1364
|
+
return undefined;
|
|
1365
|
+
const value = readOptionalNumber(record, key, path, bag);
|
|
1366
|
+
if (value !== undefined && value < 0) {
|
|
1367
|
+
pushIssue(bag, path, "invalid_non_negative_number", `${key} must be non-negative.`);
|
|
1368
|
+
return undefined;
|
|
1369
|
+
}
|
|
1370
|
+
return value;
|
|
1371
|
+
};
|
|
1372
|
+
const readOptionalRate = (record, key, path, bag) => {
|
|
1373
|
+
const value = readOptionalNonNegativeNumber(record, key, path, bag);
|
|
1374
|
+
if (value !== undefined && value > 1) {
|
|
1375
|
+
pushIssue(bag, path, "invalid_rate", `${key} must be between 0 and 1.`);
|
|
1376
|
+
return undefined;
|
|
1377
|
+
}
|
|
1378
|
+
return value;
|
|
1379
|
+
};
|
|
1380
|
+
const readOptionalRecord = (record, key, path, bag) => {
|
|
1381
|
+
const value = record[key];
|
|
1382
|
+
if (value === undefined)
|
|
1383
|
+
return undefined;
|
|
1384
|
+
if (!isRecord(value)) {
|
|
1385
|
+
pushIssue(bag, path, "invalid_object", `${key} must be an object.`);
|
|
1386
|
+
return undefined;
|
|
1387
|
+
}
|
|
1388
|
+
return value;
|
|
1389
|
+
};
|
|
1390
|
+
const readOptionalNumberRecord = (record, key, path, bag) => {
|
|
1391
|
+
if (record[key] === undefined)
|
|
1392
|
+
return undefined;
|
|
1393
|
+
return readScores(record, key, path, bag);
|
|
1394
|
+
};
|
|
1395
|
+
const requireRecord = (input, path, bag) => {
|
|
1396
|
+
if (!isRecord(input)) {
|
|
1397
|
+
pushIssue(bag, path, "required_object", "Value must be an object.");
|
|
1398
|
+
return undefined;
|
|
1399
|
+
}
|
|
1400
|
+
return input;
|
|
1401
|
+
};
|
|
1402
|
+
const assertAllowedKeys = (record, path, allowedKeys, bag) => {
|
|
1403
|
+
const allowed = new Set(allowedKeys);
|
|
1404
|
+
for (const key of Object.keys(record)) {
|
|
1405
|
+
if (!allowed.has(key)) {
|
|
1406
|
+
pushIssue(bag, `${path}.${key}`, "unknown_field", `${key} is not part of the strict improvement contract.`);
|
|
1407
|
+
}
|
|
1408
|
+
if (record[key] === undefined &&
|
|
1409
|
+
!STRING_ARRAY_KEYS.has(key)) {
|
|
1410
|
+
pushIssue(bag, `${path}.${key}`, "undefined_field", `${key} must be omitted instead of set to undefined.`);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
1414
|
+
const isRecord = (input) => typeof input === "object" && input !== null && !Array.isArray(input);
|
|
1415
|
+
const ok = (value) => ({
|
|
1416
|
+
ok: true,
|
|
1417
|
+
value,
|
|
1418
|
+
issues: [],
|
|
1419
|
+
});
|
|
1420
|
+
const fail = (bag) => ({
|
|
1421
|
+
ok: false,
|
|
1422
|
+
issues: bag.issues,
|
|
1423
|
+
});
|
|
1424
|
+
const pushIssue = (bag, path, code, message) => {
|
|
1425
|
+
bag.issues.push({ path, code, message });
|
|
1426
|
+
};
|
|
1427
|
+
const uniqueStrings = (values) => Array.from(new Set(values));
|
|
1428
|
+
const sameStringSequence = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1429
|
+
const fallbackScope = () => ({
|
|
1430
|
+
tenantHash: "",
|
|
1431
|
+
productId: "",
|
|
1432
|
+
});
|
|
1433
|
+
const fallbackGate = () => ({
|
|
1434
|
+
schemaVersion: CODALI_IMPROVEMENT_CONTRACT_SCHEMA_VERSION,
|
|
1435
|
+
gateId: "",
|
|
1436
|
+
candidateId: "",
|
|
1437
|
+
gateType: "policy",
|
|
1438
|
+
status: "blocked",
|
|
1439
|
+
required: true,
|
|
1440
|
+
passed: false,
|
|
1441
|
+
createdAt: new Date(0).toISOString(),
|
|
1442
|
+
});
|
|
1443
|
+
const withoutUndefined = (input) => {
|
|
1444
|
+
const output = {};
|
|
1445
|
+
for (const [key, value] of Object.entries(input)) {
|
|
1446
|
+
if (value !== undefined)
|
|
1447
|
+
output[key] = value;
|
|
1448
|
+
}
|
|
1449
|
+
return output;
|
|
1450
|
+
};
|