@resolveio/server-lib 22.3.155 → 22.3.157
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/package.json +1 -1
- package/util/ai-run-evidence-adapters.js +225 -41
- package/util/ai-run-evidence-adapters.js.map +1 -1
- package/util/ai-run-evidence.d.ts +1 -1
- package/util/ai-run-evidence.js.map +1 -1
- package/util/ai-runner-manager-policy.d.ts +87 -0
- package/util/ai-runner-manager-policy.js +313 -0
- package/util/ai-runner-manager-policy.js.map +1 -1
|
@@ -47,6 +47,9 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
47
47
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
48
48
|
};
|
|
49
49
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.normalizeResolveIOAIManagerHotfixEvidence = normalizeResolveIOAIManagerHotfixEvidence;
|
|
51
|
+
exports.validateResolveIOAIManagerHotfixEvidence = validateResolveIOAIManagerHotfixEvidence;
|
|
52
|
+
exports.decideResolveIOAIManagerHotfixContinuation = decideResolveIOAIManagerHotfixContinuation;
|
|
50
53
|
exports.buildResolveIOAIManagerHotfixFirstReleasePolicy = buildResolveIOAIManagerHotfixFirstReleasePolicy;
|
|
51
54
|
exports.isResolveIOAIManagerSafeAutoDispatch = isResolveIOAIManagerSafeAutoDispatch;
|
|
52
55
|
exports.normalizeResolveIOAIManagerFailureClass = normalizeResolveIOAIManagerFailureClass;
|
|
@@ -96,6 +99,316 @@ function cleanList(values, limit, max) {
|
|
|
96
99
|
}
|
|
97
100
|
return result;
|
|
98
101
|
}
|
|
102
|
+
var HOTFIX_CHANNELS = new Set([
|
|
103
|
+
'static_ui',
|
|
104
|
+
'backend_js',
|
|
105
|
+
'config',
|
|
106
|
+
'seed_data',
|
|
107
|
+
'cache_invalidation',
|
|
108
|
+
'service_restart',
|
|
109
|
+
'release_artifact',
|
|
110
|
+
'force_deploy_review',
|
|
111
|
+
'new_artifact_deploy',
|
|
112
|
+
'artifact_build'
|
|
113
|
+
]);
|
|
114
|
+
function cleanObject(value) {
|
|
115
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
116
|
+
}
|
|
117
|
+
function normalizeHotfixChannel(value) {
|
|
118
|
+
var normalized = cleanText(value, 120).toLowerCase().replace(/[\s-]+/g, '_');
|
|
119
|
+
return HOTFIX_CHANNELS.has(normalized) ? normalized : '';
|
|
120
|
+
}
|
|
121
|
+
function normalizeHotfixStatus(value) {
|
|
122
|
+
var normalized = cleanText(value, 120).toLowerCase().replace(/[\s-]+/g, '_');
|
|
123
|
+
if (/^(pass|passed|success|succeeded|complete|completed|accepted)$/.test(normalized)) {
|
|
124
|
+
return 'passed';
|
|
125
|
+
}
|
|
126
|
+
if (/^(block|blocked|failed|fail|error|denied)$/.test(normalized)) {
|
|
127
|
+
return 'blocked';
|
|
128
|
+
}
|
|
129
|
+
if (/^(missing|none)$/.test(normalized)) {
|
|
130
|
+
return 'missing';
|
|
131
|
+
}
|
|
132
|
+
return 'incomplete';
|
|
133
|
+
}
|
|
134
|
+
function hotfixStatusPassed(value) {
|
|
135
|
+
return normalizeHotfixStatus(value) === 'passed';
|
|
136
|
+
}
|
|
137
|
+
function normalizeHotfixEvidenceTarget(value) {
|
|
138
|
+
var source = cleanObject(value);
|
|
139
|
+
return {
|
|
140
|
+
surface: cleanText(source.surface, 160),
|
|
141
|
+
host: cleanText(source.host || source.hostname || source.domain, 240),
|
|
142
|
+
path: cleanText(source.path || source.remotePath || source.remote_path, 500),
|
|
143
|
+
artifactPath: cleanText(source.artifactPath || source.artifact_path || source.localPath || source.local_path, 500),
|
|
144
|
+
bucket: cleanText(source.bucket || source.s3Bucket || source.s3_bucket, 240),
|
|
145
|
+
distributionId: cleanText(source.distributionId || source.distribution_id || source.cloudfrontDistributionId || source.cloudfront_distribution_id, 240),
|
|
146
|
+
processName: cleanText(source.processName || source.process_name || source.service || source.serviceName, 160),
|
|
147
|
+
configKey: cleanText(source.configKey || source.config_key || source.key, 240),
|
|
148
|
+
cacheKey: cleanText(source.cacheKey || source.cache_key, 240),
|
|
149
|
+
seedKey: cleanText(source.seedKey || source.seed_key || source.fixture, 240)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function normalizeResolveIOAIManagerHotfixEvidence(value, policy) {
|
|
153
|
+
var _a;
|
|
154
|
+
var source = cleanObject(value);
|
|
155
|
+
if (!Object.keys(source).length) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
var channel = normalizeHotfixChannel(source.channel || source.hotfixChannel || source.hotfix_channel || ((_a = policy === null || policy === void 0 ? void 0 : policy.hotfixPlan) === null || _a === void 0 ? void 0 : _a.recommendedChannel));
|
|
159
|
+
if (!channel) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
var target = normalizeHotfixEvidenceTarget(source.target || source);
|
|
163
|
+
return {
|
|
164
|
+
channel: channel,
|
|
165
|
+
status: normalizeHotfixStatus(source.status),
|
|
166
|
+
target: target,
|
|
167
|
+
compiledArtifactPath: cleanText(source.compiledArtifactPath || source.compiled_artifact_path || target.artifactPath, 500),
|
|
168
|
+
builtDistPath: cleanText(source.builtDistPath || source.built_dist_path || target.artifactPath, 500),
|
|
169
|
+
remoteChecksumBefore: cleanText(source.remoteChecksumBefore || source.remote_checksum_before || source.checksumBefore || source.checksum_before, 160),
|
|
170
|
+
remoteChecksumAfter: cleanText(source.remoteChecksumAfter || source.remote_checksum_after || source.checksumAfter || source.checksum_after, 160),
|
|
171
|
+
remoteChecksum: cleanText(source.remoteChecksum || source.remote_checksum || source.checksum, 160),
|
|
172
|
+
restartEvidence: cleanText(source.restartEvidence || source.restart_evidence, 1000),
|
|
173
|
+
healthCheckStatus: cleanText(source.healthCheckStatus || source.health_check_status || source.health, 160),
|
|
174
|
+
selfTestStatus: cleanText(source.selfTestStatus || source.self_test_status || source.selfTest || source.self_test, 160),
|
|
175
|
+
releaseGateStatus: cleanText(source.releaseGateStatus || source.release_gate_status || source.releaseGate || source.release_gate, 160),
|
|
176
|
+
s3UploadResult: cleanText(source.s3UploadResult || source.s3_upload_result || source.uploadResult || source.upload_result, 1000),
|
|
177
|
+
cloudfrontInvalidationId: cleanText(source.cloudfrontInvalidationId || source.cloudfront_invalidation_id || source.invalidationId || source.invalidation_id, 240),
|
|
178
|
+
publicProof: cleanText(source.publicProof || source.public_proof || source.publicUrl || source.public_url, 1000),
|
|
179
|
+
configChangeEvidence: cleanText(source.configChangeEvidence || source.config_change_evidence, 1000),
|
|
180
|
+
seedDataEvidence: cleanText(source.seedDataEvidence || source.seed_data_evidence, 1000),
|
|
181
|
+
cacheInvalidationEvidence: cleanText(source.cacheInvalidationEvidence || source.cache_invalidation_evidence, 1000),
|
|
182
|
+
serviceRestartEvidence: cleanText(source.serviceRestartEvidence || source.service_restart_evidence, 1000),
|
|
183
|
+
releaseArtifactFingerprint: cleanText(source.releaseArtifactFingerprint || source.release_artifact_fingerprint || source.artifactFingerprint || source.artifact_fingerprint, 240),
|
|
184
|
+
lastReleaseArtifactFingerprint: cleanText(source.lastReleaseArtifactFingerprint || source.last_release_artifact_fingerprint || source.lastArtifactFingerprint || source.last_artifact_fingerprint, 240),
|
|
185
|
+
forceDeployReason: cleanText(source.forceDeployReason || source.force_deploy_reason, 1000),
|
|
186
|
+
hotfixCannotResolveReason: cleanText(source.hotfixCannotResolveReason || source.hotfix_cannot_resolve_reason, 1000),
|
|
187
|
+
fullDeployRequested: source.fullDeployRequested === true || source.full_deploy_requested === true,
|
|
188
|
+
recordedAt: source.recordedAt || source.recorded_at
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function matchingHotfixPlanStep(policy, channel) {
|
|
192
|
+
var _a;
|
|
193
|
+
return (((_a = policy === null || policy === void 0 ? void 0 : policy.hotfixPlan) === null || _a === void 0 ? void 0 : _a.steps) || []).find(function (step) { return step.channel === channel; });
|
|
194
|
+
}
|
|
195
|
+
function pushMissing(blockers, condition, message) {
|
|
196
|
+
if (!condition) {
|
|
197
|
+
blockers.push(message);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function validateResolveIOAIManagerHotfixEvidence(value, options) {
|
|
201
|
+
var _a, _b, _c, _d;
|
|
202
|
+
if (options === void 0) { options = {}; }
|
|
203
|
+
var normalized = normalizeResolveIOAIManagerHotfixEvidence(value, options.policy);
|
|
204
|
+
var policy = options.policy;
|
|
205
|
+
if (!normalized) {
|
|
206
|
+
return {
|
|
207
|
+
valid: false,
|
|
208
|
+
status: 'missing',
|
|
209
|
+
channel: '',
|
|
210
|
+
blockers: ['Hotfix evidence is missing or channel is unsupported.'],
|
|
211
|
+
warnings: [],
|
|
212
|
+
fullDeployAllowed: false,
|
|
213
|
+
fullDeployBlocked: true,
|
|
214
|
+
hotfixSatisfied: false,
|
|
215
|
+
requiredEvidence: ((_b = (_a = policy === null || policy === void 0 ? void 0 : policy.hotfixPlan) === null || _a === void 0 ? void 0 : _a.steps) === null || _b === void 0 ? void 0 : _b.flatMap(function (step) { return step.requiredEvidence; })) || [],
|
|
216
|
+
successEvidence: ((_d = (_c = policy === null || policy === void 0 ? void 0 : policy.hotfixPlan) === null || _c === void 0 ? void 0 : _c.steps) === null || _d === void 0 ? void 0 : _d.flatMap(function (step) { return step.successEvidence; })) || [],
|
|
217
|
+
policy: policy,
|
|
218
|
+
nextAction: 'record_hotfix_evidence'
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
var blockers = [];
|
|
222
|
+
var warnings = [];
|
|
223
|
+
var target = normalized.target || {};
|
|
224
|
+
var channel = normalized.channel;
|
|
225
|
+
var step = matchingHotfixPlanStep(policy, channel);
|
|
226
|
+
var requiredEvidence = (step === null || step === void 0 ? void 0 : step.requiredEvidence) || [];
|
|
227
|
+
var successEvidence = (step === null || step === void 0 ? void 0 : step.successEvidence) || [];
|
|
228
|
+
var policyAllowsFullDeploy = (policy === null || policy === void 0 ? void 0 : policy.fullDeployAllowed) === true;
|
|
229
|
+
var policyBlocksDuplicate = (policy === null || policy === void 0 ? void 0 : policy.duplicateDeployBlocked) === true || (policy === null || policy === void 0 ? void 0 : policy.duplicatePublishBlocked) === true;
|
|
230
|
+
if (normalized.fullDeployRequested && !policyAllowsFullDeploy && channel !== 'force_deploy_review' && channel !== 'new_artifact_deploy') {
|
|
231
|
+
blockers.push('Full deploy requested but hotfix-first policy does not allow a full deploy for this release state.');
|
|
232
|
+
}
|
|
233
|
+
if (normalized.fullDeployRequested && policyBlocksDuplicate && !normalized.forceDeployReason && channel !== 'force_deploy_review') {
|
|
234
|
+
blockers.push('Duplicate deploy/publish fingerprint requires force deploy/publish evidence before any full deploy.');
|
|
235
|
+
}
|
|
236
|
+
if (channel === 'backend_js') {
|
|
237
|
+
pushMissing(blockers, !!normalized.compiledArtifactPath, 'Backend JS hotfix requires compiledArtifactPath.');
|
|
238
|
+
pushMissing(blockers, !!target.host, 'Backend JS hotfix requires target.host.');
|
|
239
|
+
pushMissing(blockers, !!target.path, 'Backend JS hotfix requires target.path.');
|
|
240
|
+
pushMissing(blockers, !!(normalized.remoteChecksumAfter || normalized.remoteChecksum), 'Backend JS hotfix requires remote checksum after replacement.');
|
|
241
|
+
pushMissing(blockers, !!(normalized.restartEvidence || normalized.serviceRestartEvidence), 'Backend JS hotfix requires restart evidence.');
|
|
242
|
+
pushMissing(blockers, hotfixStatusPassed(normalized.healthCheckStatus), 'Backend JS hotfix requires passed healthCheckStatus.');
|
|
243
|
+
pushMissing(blockers, hotfixStatusPassed(normalized.selfTestStatus), 'Backend JS hotfix requires passed selfTestStatus.');
|
|
244
|
+
if (normalized.remoteChecksumBefore && normalized.remoteChecksumAfter && normalized.remoteChecksumBefore === normalized.remoteChecksumAfter) {
|
|
245
|
+
blockers.push('Backend JS hotfix checksum did not change; record force/no-op evidence or do not claim a hotfix.');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (channel === 'static_ui') {
|
|
249
|
+
pushMissing(blockers, !!normalized.builtDistPath, 'Static UI hotfix requires builtDistPath.');
|
|
250
|
+
pushMissing(blockers, !!target.bucket, 'Static UI hotfix requires target.bucket.');
|
|
251
|
+
pushMissing(blockers, !!target.distributionId, 'Static UI hotfix requires target.distributionId.');
|
|
252
|
+
pushMissing(blockers, !!normalized.s3UploadResult, 'Static UI hotfix requires s3UploadResult.');
|
|
253
|
+
pushMissing(blockers, !!normalized.cloudfrontInvalidationId, 'Static UI hotfix requires cloudfrontInvalidationId.');
|
|
254
|
+
pushMissing(blockers, !!normalized.publicProof, 'Static UI hotfix requires publicProof.');
|
|
255
|
+
}
|
|
256
|
+
else if (channel === 'config') {
|
|
257
|
+
pushMissing(blockers, !!target.configKey, 'Config hotfix requires target.configKey.');
|
|
258
|
+
pushMissing(blockers, !!normalized.configChangeEvidence, 'Config hotfix requires configChangeEvidence.');
|
|
259
|
+
pushMissing(blockers, hotfixStatusPassed(normalized.releaseGateStatus), 'Config hotfix requires passed releaseGateStatus.');
|
|
260
|
+
}
|
|
261
|
+
else if (channel === 'seed_data') {
|
|
262
|
+
pushMissing(blockers, !!target.seedKey, 'Seed-data hotfix requires target.seedKey.');
|
|
263
|
+
pushMissing(blockers, !!normalized.seedDataEvidence, 'Seed-data hotfix requires seedDataEvidence.');
|
|
264
|
+
pushMissing(blockers, hotfixStatusPassed(normalized.releaseGateStatus), 'Seed-data hotfix requires passed releaseGateStatus.');
|
|
265
|
+
}
|
|
266
|
+
else if (channel === 'cache_invalidation') {
|
|
267
|
+
pushMissing(blockers, !!target.cacheKey, 'Cache invalidation hotfix requires target.cacheKey.');
|
|
268
|
+
pushMissing(blockers, !!normalized.cacheInvalidationEvidence, 'Cache invalidation hotfix requires cacheInvalidationEvidence.');
|
|
269
|
+
pushMissing(blockers, hotfixStatusPassed(normalized.releaseGateStatus) || hotfixStatusPassed(normalized.healthCheckStatus), 'Cache invalidation hotfix requires passed releaseGateStatus or healthCheckStatus.');
|
|
270
|
+
}
|
|
271
|
+
else if (channel === 'service_restart') {
|
|
272
|
+
pushMissing(blockers, !!target.processName, 'Service restart hotfix requires target.processName.');
|
|
273
|
+
pushMissing(blockers, !!(normalized.serviceRestartEvidence || normalized.restartEvidence), 'Service restart hotfix requires restart evidence.');
|
|
274
|
+
pushMissing(blockers, hotfixStatusPassed(normalized.healthCheckStatus), 'Service restart hotfix requires passed healthCheckStatus.');
|
|
275
|
+
pushMissing(blockers, hotfixStatusPassed(normalized.selfTestStatus), 'Service restart hotfix requires passed selfTestStatus.');
|
|
276
|
+
}
|
|
277
|
+
else if (channel === 'release_artifact') {
|
|
278
|
+
pushMissing(blockers, !!normalized.releaseArtifactFingerprint, 'Release artifact hotfix requires releaseArtifactFingerprint.');
|
|
279
|
+
pushMissing(blockers, hotfixStatusPassed(normalized.releaseGateStatus), 'Release artifact hotfix requires passed releaseGateStatus.');
|
|
280
|
+
}
|
|
281
|
+
else if (channel === 'force_deploy_review') {
|
|
282
|
+
pushMissing(blockers, !!normalized.forceDeployReason, 'Force deploy review requires forceDeployReason.');
|
|
283
|
+
pushMissing(blockers, !!normalized.hotfixCannotResolveReason, 'Force deploy review requires hotfixCannotResolveReason.');
|
|
284
|
+
}
|
|
285
|
+
else if (channel === 'new_artifact_deploy') {
|
|
286
|
+
pushMissing(blockers, !!normalized.releaseArtifactFingerprint, 'New artifact deploy requires releaseArtifactFingerprint.');
|
|
287
|
+
pushMissing(blockers, !!normalized.lastReleaseArtifactFingerprint, 'New artifact deploy requires lastReleaseArtifactFingerprint.');
|
|
288
|
+
if (normalized.releaseArtifactFingerprint && normalized.lastReleaseArtifactFingerprint && normalized.releaseArtifactFingerprint === normalized.lastReleaseArtifactFingerprint) {
|
|
289
|
+
blockers.push('New artifact deploy requires a fingerprint different from the last deployed artifact.');
|
|
290
|
+
}
|
|
291
|
+
pushMissing(blockers, hotfixStatusPassed(normalized.releaseGateStatus), 'New artifact deploy requires passed releaseGateStatus.');
|
|
292
|
+
}
|
|
293
|
+
else if (channel === 'artifact_build') {
|
|
294
|
+
pushMissing(blockers, !!normalized.releaseArtifactFingerprint, 'Artifact build requires releaseArtifactFingerprint.');
|
|
295
|
+
if (normalized.fullDeployRequested) {
|
|
296
|
+
blockers.push('Artifact build evidence cannot request a full deploy in the same step.');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (!step && policy) {
|
|
300
|
+
warnings.push("Hotfix channel ".concat(channel, " is not the recommended channel in the current release policy."));
|
|
301
|
+
}
|
|
302
|
+
var forceDeployReviewValid = channel === 'force_deploy_review' && blockers.length === 0;
|
|
303
|
+
var newArtifactDeployValid = channel === 'new_artifact_deploy' && blockers.length === 0;
|
|
304
|
+
var fullDeployAllowed = forceDeployReviewValid || newArtifactDeployValid || (policyAllowsFullDeploy && blockers.length === 0 && normalized.fullDeployRequested === true);
|
|
305
|
+
var hotfixSatisfied = blockers.length === 0 && !fullDeployAllowed;
|
|
306
|
+
var status = blockers.length
|
|
307
|
+
? (normalizeHotfixStatus(normalized.status) === 'blocked' ? 'blocked' : 'incomplete')
|
|
308
|
+
: 'passed';
|
|
309
|
+
var fullDeployBlocked = !fullDeployAllowed && (normalized.fullDeployRequested === true || (policy === null || policy === void 0 ? void 0 : policy.fullDeployAllowed) !== true);
|
|
310
|
+
var nextAction = blockers.some(function (blocker) { return /force deploy/i.test(blocker); })
|
|
311
|
+
? 'request_force_deploy_reason'
|
|
312
|
+
: fullDeployAllowed
|
|
313
|
+
? 'allow_one_full_deploy'
|
|
314
|
+
: hotfixSatisfied
|
|
315
|
+
? 'rerun_release_gate'
|
|
316
|
+
: blockers.length
|
|
317
|
+
? 'record_hotfix_evidence'
|
|
318
|
+
: 'park_manual';
|
|
319
|
+
normalized.status = status;
|
|
320
|
+
return {
|
|
321
|
+
valid: blockers.length === 0,
|
|
322
|
+
status: status,
|
|
323
|
+
channel: channel,
|
|
324
|
+
blockers: blockers,
|
|
325
|
+
warnings: warnings,
|
|
326
|
+
fullDeployAllowed: fullDeployAllowed,
|
|
327
|
+
fullDeployBlocked: fullDeployBlocked,
|
|
328
|
+
hotfixSatisfied: hotfixSatisfied,
|
|
329
|
+
requiredEvidence: requiredEvidence,
|
|
330
|
+
successEvidence: successEvidence,
|
|
331
|
+
normalized: normalized,
|
|
332
|
+
policy: policy,
|
|
333
|
+
nextAction: nextAction
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function decideResolveIOAIManagerHotfixContinuation(input) {
|
|
337
|
+
var _a, _b;
|
|
338
|
+
if (input === void 0) { input = {}; }
|
|
339
|
+
var validation = validateResolveIOAIManagerHotfixEvidence(input.evidence, { policy: input.policy });
|
|
340
|
+
var failureClass = cleanText(input.failureClass, 120) || 'release';
|
|
341
|
+
var blockerText = cleanText(input.blocker || validation.blockers.join('; ') || ((_a = validation.normalized) === null || _a === void 0 ? void 0 : _a.hotfixCannotResolveReason), 1000);
|
|
342
|
+
var blockerFingerprint = fingerprintResolveIOAIManagerBlocker(blockerText || "".concat(validation.channel, ":").concat(validation.status, ":").concat(validation.nextAction));
|
|
343
|
+
var baseBlockers = __spreadArray([], __read(validation.blockers), false);
|
|
344
|
+
var repeatedFailure = input.repeatedFailure === true;
|
|
345
|
+
var releaseGatePassed = input.releaseGatePassed === true
|
|
346
|
+
|| hotfixStatusPassed((_b = validation.normalized) === null || _b === void 0 ? void 0 : _b.releaseGateStatus);
|
|
347
|
+
var action = validation.nextAction;
|
|
348
|
+
var canContinueRun = false;
|
|
349
|
+
var canRunFullDeploy = validation.fullDeployAllowed;
|
|
350
|
+
var shouldRerunReleaseGate = false;
|
|
351
|
+
var shouldPark = false;
|
|
352
|
+
var reason = '';
|
|
353
|
+
var nextCommands = [];
|
|
354
|
+
if (!validation.valid || validation.status === 'missing' || validation.status === 'incomplete') {
|
|
355
|
+
action = validation.nextAction === 'request_force_deploy_reason'
|
|
356
|
+
? 'request_force_deploy_reason'
|
|
357
|
+
: 'record_hotfix_evidence';
|
|
358
|
+
reason = validation.blockers.length
|
|
359
|
+
? "Hotfix evidence is not complete: ".concat(validation.blockers.join('; '))
|
|
360
|
+
: 'Hotfix evidence is missing; record the hotfix target, checksum, restart, health, and self-test proof before continuing.';
|
|
361
|
+
nextCommands.push('record_hotfix_evidence', 'rerun_hotfix_evidence_validator');
|
|
362
|
+
}
|
|
363
|
+
else if (validation.fullDeployAllowed) {
|
|
364
|
+
action = 'allow_one_full_deploy';
|
|
365
|
+
canRunFullDeploy = true;
|
|
366
|
+
reason = 'Hotfix evidence proves the duplicate release cannot be resolved by a hotfix, or a new artifact was verified, so exactly one full deploy can proceed.';
|
|
367
|
+
nextCommands.push('run_release_gate_once', 'execute_one_full_deploy_with_force_evidence', 'record_deploy_result');
|
|
368
|
+
}
|
|
369
|
+
else if (repeatedFailure) {
|
|
370
|
+
action = 'park_manual';
|
|
371
|
+
shouldPark = true;
|
|
372
|
+
reason = 'The same release failure repeated after valid hotfix evidence; park for manual review instead of looping or redeploying blindly.';
|
|
373
|
+
nextCommands.push('park_manual_with_hotfix_evidence', 'attach_release_gate_logs');
|
|
374
|
+
}
|
|
375
|
+
else if (validation.hotfixSatisfied && releaseGatePassed) {
|
|
376
|
+
action = 'continue_runner';
|
|
377
|
+
canContinueRun = true;
|
|
378
|
+
reason = 'Hotfix evidence and the release gate both passed; continue the current runner without a full deploy.';
|
|
379
|
+
nextCommands.push('continue_current_runner', 'record_hotfix_continuation');
|
|
380
|
+
}
|
|
381
|
+
else if (validation.hotfixSatisfied) {
|
|
382
|
+
action = 'rerun_release_gate';
|
|
383
|
+
shouldRerunReleaseGate = true;
|
|
384
|
+
reason = 'Hotfix evidence passed; rerun the smallest release gate before continuing the runner.';
|
|
385
|
+
nextCommands.push('rerun_release_gate_once', 'record_release_gate_status');
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
action = 'park_manual';
|
|
389
|
+
shouldPark = true;
|
|
390
|
+
reason = 'Hotfix evidence reached an unsupported state; park instead of starting another broad deploy or model loop.';
|
|
391
|
+
baseBlockers.push('Unsupported hotfix continuation state.');
|
|
392
|
+
nextCommands.push('park_manual_with_hotfix_evidence');
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
action: action,
|
|
396
|
+
canContinueRun: canContinueRun,
|
|
397
|
+
canRunFullDeploy: canRunFullDeploy,
|
|
398
|
+
shouldRerunReleaseGate: shouldRerunReleaseGate,
|
|
399
|
+
shouldPark: shouldPark,
|
|
400
|
+
reason: reason,
|
|
401
|
+
blockers: baseBlockers,
|
|
402
|
+
warnings: validation.warnings,
|
|
403
|
+
requiredEvidence: validation.requiredEvidence,
|
|
404
|
+
successEvidence: validation.successEvidence,
|
|
405
|
+
nextCommands: nextCommands,
|
|
406
|
+
failureClass: failureClass,
|
|
407
|
+
blockerFingerprint: blockerFingerprint,
|
|
408
|
+
validation: validation,
|
|
409
|
+
recordedAt: isoNow(input.now)
|
|
410
|
+
};
|
|
411
|
+
}
|
|
99
412
|
function releaseStatusIsBlocked(value) {
|
|
100
413
|
return /fail|error|blocked|missing|empty|not ready|not_ready|denied|invalid|timeout|stale/i.test(value);
|
|
101
414
|
}
|