codex-workflow-v2 2.0.0-beta.13.5 → 2.0.0-beta.13.7
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/README.md +6 -1
- package/dist/reviewer-runtime-build.json +13 -9
- package/dist/src/alpha6/root-cause-replan-carryover.d.ts +18 -0
- package/dist/src/alpha6/root-cause-replan-carryover.js +396 -0
- package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -0
- package/dist/src/cli-actions.d.ts +1 -1
- package/dist/src/cli-actions.js +1 -0
- package/dist/src/cli-actions.js.map +1 -1
- package/dist/src/cli.js +10 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +35 -0
- package/dist/src/dependency-provenance.d.ts +3 -2
- package/dist/src/dependency-provenance.js +33 -2
- package/dist/src/dependency-provenance.js.map +1 -1
- package/dist/src/gateway-handshake.js +1 -0
- package/dist/src/gateway-handshake.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/workflow.d.ts +6 -0
- package/dist/src/workflow.js +480 -10
- package/dist/src/workflow.js.map +1 -1
- package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
- package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
- package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
- package/docs/release.md +61 -0
- package/package.json +2 -1
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +71 -0
- package/schemas/task.schema.json +55 -0
package/dist/src/workflow.js
CHANGED
|
@@ -3,7 +3,7 @@ import { appendFileSync, existsSync, lstatSync, readFileSync, readdirSync } from
|
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION, } from './contracts.js';
|
|
6
|
-
import { inspectBoundedWorkflowDependencyCommit, inspectDependencyKnowledgeRefresh, inspectDependencyProvenanceRecovery, } from './dependency-provenance.js';
|
|
6
|
+
import { inspectBoundedWorkflowDependencyCommit, inspectDependencyKnowledgeRefresh, inspectDependencyProvenanceRecovery, workflowDependencyVersionAt, } from './dependency-provenance.js';
|
|
7
7
|
import { inspectHistoricalStepProvenanceRecovery, omittedStepEvidenceCommits, } from './historical-step-provenance.js';
|
|
8
8
|
import { renderBrief, renderPlan, renderResult } from './artifacts.js';
|
|
9
9
|
import { assertDelegationCanAuthorize, hashDelegationPolicy, prepareDelegationGrantGate, } from './delegation.js';
|
|
@@ -15,11 +15,12 @@ import { WorkflowError } from './errors.js';
|
|
|
15
15
|
import { withObservedRouteMetadata } from './observed-routes.js';
|
|
16
16
|
import { bindGraphEvidence, createGraphRefreshRequest, fallbackGraphBinding, inspectGraphBinding, validateGraphRequestCurrent, } from './graph.js';
|
|
17
17
|
import { inspectLocalDependency, inspectWorkflowVersions } from './diagnostics.js';
|
|
18
|
-
import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, appendTaskWriterCredentialReplacement, assertTaskClaimAllowed, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, prepareTaskCorrectiveYield, readTaskC1Posture, } from './alpha6/handoff.js';
|
|
18
|
+
import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, appendTaskWriterCredentialReplacement, assertTaskClaimAllowed, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, prepareTaskCorrectiveYield, readTaskC1Posture, readTaskHandoffEvents, } from './alpha6/handoff.js';
|
|
19
19
|
import { assessDownstreamProofCarryover, assessDownstreamProofReplanRecovery, assessDownstreamProofRecovery, downstreamProofInvalidationSidecar, downstreamProofReplanRecoverySidecar, prepareDownstreamProofInvalidation, prepareDownstreamProofReplanRecovery, readDownstreamProofInvalidations, readDownstreamProofReplanRecoveries, hashDirtyWorktree, } from './alpha6/downstream-proof.js';
|
|
20
20
|
import { applyAdoptionPosture, assertAdoptionBaselinePreserved, buildAdoptionPreparation, initializeProjectRegistrationAdoption, readCurrentAdoptionPosture, } from './alpha6/adoption.js';
|
|
21
21
|
import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, buildCandidatePlanRiskAuditEvent, buildPlanRiskAuditEvent, readCurrentPlanRiskAudit, readPlanRiskAuditEvents, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
|
|
22
22
|
import { hashCompletedSteps, hashExecutionAuthorization, preparePreExecutionReplanEvent, preExecutionReplanSidecar, readCurrentPreExecutionReplan, } from './alpha6/preexecution-replan.js';
|
|
23
|
+
import { assessRootCauseReplanDirtyCarryover, matchesTerminalCorrectiveYield, } from './alpha6/root-cause-replan-carryover.js';
|
|
23
24
|
import { appendCorrectiveDecisionEvent, appendPlanIntegrityRecoveryDecision, appendRemediationModeRecovery, appendStopEscalateOverride, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, defaultCorrectiveAuditorActor, effectiveStepAllowedWrites, findCurrentGuardedRemediationPosture, findRemediationModeRecoveryCandidate, findFailedGuardedStepRequiringCorrectiveDecision, readGuardedRemediationPostureForStep, readCorrectiveDecisionEvents, readRemediationEvents, deriveRollingCauseDecision, deriveCurrentCauseDecisionForTask, prepareRemediationEvent, prepareStopEscalateOverride, stopEscalateOverrideDecisionEventIds, } from './alpha6/remediation.js';
|
|
24
25
|
import { assessPlanIntegrityRecovery, defaultPlanIntegrityRecoveryActor, } from './alpha6/plan-integrity.js';
|
|
25
26
|
import { mechanicalCauseBindings, normalizeMechanicalFailures, } from './alpha6/remediation-cause.js';
|
|
@@ -2756,11 +2757,15 @@ export class WorkflowService {
|
|
|
2756
2757
|
const planIntegrityDirtyCarryover = step.status === 'planned' && dirty.length > 0
|
|
2757
2758
|
? this.assessPostRebindCheckSupportDirtyCarryover(context.identity.repositoryRoot, task, stepId)
|
|
2758
2759
|
: null;
|
|
2760
|
+
const rootCauseReplanDirtyCarryover = step.status === 'planned' && dirty.length > 0
|
|
2761
|
+
? this.assessCurrentRootCauseReplanDirtyCarryover(context.identity.repositoryRoot, task, stepId)
|
|
2762
|
+
: null;
|
|
2759
2763
|
if (step.status === 'planned'
|
|
2760
2764
|
&& dirty.length > 0
|
|
2761
2765
|
&& !resumesCorrectiveReplan
|
|
2762
2766
|
&& !downstreamProofCarryover?.eligible
|
|
2763
|
-
&& !planIntegrityDirtyCarryover?.eligible
|
|
2767
|
+
&& !planIntegrityDirtyCarryover?.eligible
|
|
2768
|
+
&& !rootCauseReplanDirtyCarryover?.eligible) {
|
|
2764
2769
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} must start from a clean checkout.`, {
|
|
2765
2770
|
changedFiles: dirty,
|
|
2766
2771
|
...(downstreamProofCarryover?.applicable
|
|
@@ -2769,6 +2774,9 @@ export class WorkflowService {
|
|
|
2769
2774
|
...(planIntegrityDirtyCarryover?.applicable
|
|
2770
2775
|
? { planIntegrityDirtyCarryoverBlockers: planIntegrityDirtyCarryover.blockers }
|
|
2771
2776
|
: {}),
|
|
2777
|
+
...(rootCauseReplanDirtyCarryover?.applicable
|
|
2778
|
+
? { rootCauseReplanDirtyCarryoverBlockers: rootCauseReplanDirtyCarryover.blockers }
|
|
2779
|
+
: {}),
|
|
2772
2780
|
});
|
|
2773
2781
|
}
|
|
2774
2782
|
const dirtyOutsideStep = dirty.filter((file) => !pathAllowed(file, allowedWrites));
|
|
@@ -4174,7 +4182,299 @@ export class WorkflowService {
|
|
|
4174
4182
|
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
4175
4183
|
if (!task)
|
|
4176
4184
|
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
4177
|
-
|
|
4185
|
+
const candidate = inspectBoundedWorkflowDependencyCommit(snapshot.identity.repositoryRoot, headCommit(snapshot.identity.repositoryRoot));
|
|
4186
|
+
const carryover = this.assessCurrentRootCauseReplanDirtyCarryover(snapshot.identity.repositoryRoot, task, undefined, candidate?.parentCommitSha);
|
|
4187
|
+
const preflight = this.inspectDependencyProvenanceCandidate(snapshot, task, null, null, null, null, carryover.eligible ? carryover.compatibility : null);
|
|
4188
|
+
if (!preflight.rootCauseReplan)
|
|
4189
|
+
return preflight;
|
|
4190
|
+
let baseTip = null;
|
|
4191
|
+
let baseDependency = null;
|
|
4192
|
+
try {
|
|
4193
|
+
baseTip = runGit(snapshot.identity.repositoryRoot, ['rev-parse', task.baseBranch]);
|
|
4194
|
+
baseDependency = inspectBoundedWorkflowDependencyCommit(snapshot.identity.repositoryRoot, baseTip);
|
|
4195
|
+
}
|
|
4196
|
+
catch {
|
|
4197
|
+
// The blocker below keeps a missing or unreadable base reference fail closed.
|
|
4198
|
+
}
|
|
4199
|
+
const bridgeBlockers = [
|
|
4200
|
+
...(preflight.dependencyCommits.length !== 1
|
|
4201
|
+
? ['Corrective carryover recovery requires exactly one Task dependency-only commit.'] : []),
|
|
4202
|
+
...(preflight.parentCommitSha !== preflight.rootCauseReplan.sourceHeadCommit
|
|
4203
|
+
? ['Corrective carryover dependency parent must equal the bound source HEAD.'] : []),
|
|
4204
|
+
...(preflight.parentCommitSha
|
|
4205
|
+
&& workflowDependencyVersionAt(snapshot.identity.repositoryRoot, preflight.parentCommitSha) !== '2.0.0-beta.13.6'
|
|
4206
|
+
? ['Corrective carryover dependency parent must declare exact source beta.13.6.'] : []),
|
|
4207
|
+
...(preflight.dependencyCommits.at(-1)?.packageVersion !== PACKAGE_VERSION
|
|
4208
|
+
? [`Corrective carryover dependency commit must declare exact target ${PACKAGE_VERSION}.`] : []),
|
|
4209
|
+
...(!baseDependency
|
|
4210
|
+
|| baseDependency.commitSha !== baseTip
|
|
4211
|
+
|| baseDependency.packageVersion !== PACKAGE_VERSION
|
|
4212
|
+
|| workflowDependencyVersionAt(snapshot.identity.repositoryRoot, baseDependency.parentCommitSha)
|
|
4213
|
+
!== workflowDependencyVersionAt(snapshot.identity.repositoryRoot, preflight.parentCommitSha ?? '')
|
|
4214
|
+
? ['Corrective carryover Milestone-base tip must be one dependency-only commit with the same exact source and runtime target.'] : []),
|
|
4215
|
+
];
|
|
4216
|
+
return bridgeBlockers.length === 0
|
|
4217
|
+
? preflight
|
|
4218
|
+
: {
|
|
4219
|
+
...preflight,
|
|
4220
|
+
eligible: false,
|
|
4221
|
+
action: null,
|
|
4222
|
+
commitSha: null,
|
|
4223
|
+
parentCommitSha: null,
|
|
4224
|
+
blockers: [...new Set([...preflight.blockers, ...bridgeBlockers])],
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
4227
|
+
correctiveCarryoverUpdatePreflight(repository, taskId, candidate = null) {
|
|
4228
|
+
const snapshot = this.observationSnapshot(repository);
|
|
4229
|
+
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
4230
|
+
if (!task)
|
|
4231
|
+
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
4232
|
+
const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
|
|
4233
|
+
const sourceVersion = '2.0.0-beta.13.6';
|
|
4234
|
+
const targetVersion = '2.0.0-beta.13.7';
|
|
4235
|
+
const sourceSurfaces = {
|
|
4236
|
+
declared: versions.declared,
|
|
4237
|
+
locked: versions.locked,
|
|
4238
|
+
installed: versions.installed,
|
|
4239
|
+
currentBranch: versions.currentBranch,
|
|
4240
|
+
baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
|
|
4241
|
+
};
|
|
4242
|
+
const c1 = readTaskC1Posture(this.store, task);
|
|
4243
|
+
if (c1.state !== 'claimed') {
|
|
4244
|
+
return this.consumedCarryoverSourceRecoveryPreflight(snapshot, task, sourceSurfaces, candidate);
|
|
4245
|
+
}
|
|
4246
|
+
const assessment = this.assessCurrentRootCauseReplanDirtyCarryover(snapshot.identity.repositoryRoot, task);
|
|
4247
|
+
const now = this.now().getTime();
|
|
4248
|
+
const activeLeases = snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) > now);
|
|
4249
|
+
const staleLeases = snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) <= now);
|
|
4250
|
+
const transactionCount = snapshot.observation.pendingProjectTransactions.length
|
|
4251
|
+
+ snapshot.observation.pendingMilestoneTransactions.length
|
|
4252
|
+
+ snapshot.observation.pendingTaskTransactions.length;
|
|
4253
|
+
const authorizationBlockers = [];
|
|
4254
|
+
if (assessment.eligible) {
|
|
4255
|
+
try {
|
|
4256
|
+
this.assertExecutionAuthorizationFresh(task, snapshot.identity.repositoryRoot);
|
|
4257
|
+
}
|
|
4258
|
+
catch (error) {
|
|
4259
|
+
authorizationBlockers.push(error instanceof Error ? error.message : String(error));
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
const baseBlockers = [
|
|
4263
|
+
...assessment.blockers,
|
|
4264
|
+
...authorizationBlockers,
|
|
4265
|
+
...(PACKAGE_VERSION !== targetVersion ? [`Runner must be the exact ${targetVersion} compatibility target.`] : []),
|
|
4266
|
+
...Object.entries(sourceSurfaces)
|
|
4267
|
+
.filter(([, version]) => version !== sourceVersion)
|
|
4268
|
+
.map(([surface]) => `${surface} Workflow version must equal the exact ${sourceVersion} source.`),
|
|
4269
|
+
...observationPreflightBlockers(snapshot.observation),
|
|
4270
|
+
...(transactionCount > 0 ? ['No pending Workflow transaction may exist.'] : []),
|
|
4271
|
+
...(snapshot.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
|
|
4272
|
+
...(snapshot.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
|
|
4273
|
+
...(c1.state !== 'claimed' || assessment.compatibility?.claimant !== c1.claimant
|
|
4274
|
+
? ['The exact original Worker C1 claim must remain current.'] : []),
|
|
4275
|
+
...(activeLeases.length > 0 ? ['No live writer lease may exist during corrective carryover transport.'] : []),
|
|
4276
|
+
...(staleLeases.some((lease) => lease.entityId !== task.id
|
|
4277
|
+
|| c1.state !== 'claimed' || lease.owner !== c1.claimant
|
|
4278
|
+
|| createHash('sha256').update(lease.token).digest('hex') !== c1.writerLeaseTokenHash)
|
|
4279
|
+
? ['No unrelated or differently owned stale writer lease may exist.'] : []),
|
|
4280
|
+
...(staleLeases.length > 1 ? ['At most the exact corrective Task stale lease may exist.'] : []),
|
|
4281
|
+
];
|
|
4282
|
+
const repairableStale = baseBlockers.length === 0 && staleLeases.length === 1
|
|
4283
|
+
? staleLeases[0]
|
|
4284
|
+
: null;
|
|
4285
|
+
const blockers = [
|
|
4286
|
+
...baseBlockers,
|
|
4287
|
+
...(repairableStale
|
|
4288
|
+
? ['Repair the exact expired claimant lease with source locks repair, then run status, next, and this preflight again.']
|
|
4289
|
+
: []),
|
|
4290
|
+
];
|
|
4291
|
+
const compatibility = assessment.compatibility;
|
|
4292
|
+
return {
|
|
4293
|
+
readOnly: true,
|
|
4294
|
+
eligible: Boolean(assessment.eligible && compatibility && blockers.length === 0 && staleLeases.length === 0),
|
|
4295
|
+
action: assessment.eligible && compatibility && blockers.length === 0 && staleLeases.length === 0
|
|
4296
|
+
? 'update corrective-carryover-transport'
|
|
4297
|
+
: null,
|
|
4298
|
+
projectId: task.projectId,
|
|
4299
|
+
taskId: task.id,
|
|
4300
|
+
taskRevision: task.revision,
|
|
4301
|
+
stepId: compatibility?.stepId ?? null,
|
|
4302
|
+
sourceVersion,
|
|
4303
|
+
targetVersion,
|
|
4304
|
+
sourceHeadCommit: compatibility?.sourceHeadCommit ?? null,
|
|
4305
|
+
taskBranch: task.taskBranch,
|
|
4306
|
+
baseBranch: task.baseBranch,
|
|
4307
|
+
dirtyFiles: compatibility?.dirtyFiles ?? [],
|
|
4308
|
+
dirtyWorktreeHash: compatibility?.dirtyWorktreeHash ?? null,
|
|
4309
|
+
compatibilityHash: compatibility?.compatibilityHash ?? null,
|
|
4310
|
+
c1Posture: c1.state === 'claimed' ? {
|
|
4311
|
+
state: 'claimed',
|
|
4312
|
+
claimant: c1.claimant,
|
|
4313
|
+
claimEventId: compatibility?.c1ClaimEventId ?? null,
|
|
4314
|
+
claimEventHash: compatibility?.c1ClaimEventHash ?? null,
|
|
4315
|
+
} : null,
|
|
4316
|
+
staleLeaseRepair: repairableStale ? [{
|
|
4317
|
+
action: 'locks repair',
|
|
4318
|
+
entityId: task.id,
|
|
4319
|
+
owner: repairableStale.owner,
|
|
4320
|
+
expiresAt: repairableStale.expiresAt,
|
|
4321
|
+
}] : [],
|
|
4322
|
+
requiredTransport: [
|
|
4323
|
+
'Commit only package.json and package-lock.json on the active Milestone base.',
|
|
4324
|
+
'Commit only the same dependency files on the Task branch while preserving the bound dirty bytes.',
|
|
4325
|
+
'Install the exact target package, register dependency provenance, refresh context, replace the same Worker credential, then follow fresh next.',
|
|
4326
|
+
],
|
|
4327
|
+
blockers,
|
|
4328
|
+
};
|
|
4329
|
+
}
|
|
4330
|
+
consumedCarryoverSourceRecoveryPreflight(snapshot, task, sourceSurfaces, candidate) {
|
|
4331
|
+
const repositoryRoot = snapshot.identity.repositoryRoot;
|
|
4332
|
+
const sourceVersion = '2.0.0-beta.13.6';
|
|
4333
|
+
const targetVersion = '2.0.0-beta.13.7';
|
|
4334
|
+
const c1 = readTaskC1Posture(this.store, task);
|
|
4335
|
+
const recorded = [...(task.dependencyProvenanceRecoveries ?? [])].reverse().find((record) => record.rootCauseReplan && task.systemCommits.includes(record.commitSha))?.rootCauseReplan;
|
|
4336
|
+
const currentHead = headCommit(repositoryRoot);
|
|
4337
|
+
const dirtyFiles = changedFiles(repositoryRoot).sort();
|
|
4338
|
+
const dirtyWorktreeHash = hashDirtyWorktree(repositoryRoot, dirtyFiles);
|
|
4339
|
+
const navigation = this.next(repositoryRoot, task.id);
|
|
4340
|
+
const action = String(navigation.action);
|
|
4341
|
+
const stepId = typeof navigation.stepId === 'string' ? navigation.stepId : null;
|
|
4342
|
+
const step = task.steps.find((candidate) => candidate.id === stepId) ?? null;
|
|
4343
|
+
const terminal = readRemediationEvents(this.store, task)
|
|
4344
|
+
.filter((event) => event.stepId === stepId && event.failureKind === 'checks-failed').at(-1);
|
|
4345
|
+
const matchingYields = readTaskHandoffEvents(this.store, task).filter((event) => terminal && matchesTerminalCorrectiveYield(event, terminal, task.id));
|
|
4346
|
+
const yielded = matchingYields.length === 1 ? matchingYields[0] : null;
|
|
4347
|
+
const assessment = this.assessCurrentRootCauseReplanDirtyCarryover(repositoryRoot, task, stepId ?? undefined);
|
|
4348
|
+
const missingClaimOnly = assessment.blockers.length === 1
|
|
4349
|
+
&& assessment.blockers[0].startsWith('Corrective carryover requires the retained claimed C1');
|
|
4350
|
+
const rollingWave = navigation.rollingWave;
|
|
4351
|
+
const sourceActionAllowed = (action === 'task plan-set' && step?.status === 'failed'
|
|
4352
|
+
&& rollingWave?.state === 'root-cause-replan-required' && !assessment.applicable && c1.state === 'none')
|
|
4353
|
+
|| (action === 'task handoff-prepare' && step?.status === 'planned' && missingClaimOnly && c1.state === 'none')
|
|
4354
|
+
|| (action === 'task claim' && step?.status === 'planned' && missingClaimOnly
|
|
4355
|
+
&& c1.state === 'pending' && c1.targetActor === yielded?.actor);
|
|
4356
|
+
const blockers = [
|
|
4357
|
+
...(PACKAGE_VERSION !== targetVersion ? [`Runner must be the exact ${targetVersion} compatibility target.`] : []),
|
|
4358
|
+
...Object.entries(sourceSurfaces).filter(([, version]) => version !== sourceVersion)
|
|
4359
|
+
.map(([surface]) => `${surface} Workflow version must equal the exact ${sourceVersion} source.`),
|
|
4360
|
+
...observationPreflightBlockers(snapshot.observation),
|
|
4361
|
+
...(snapshot.leases.length > 0 ? ['No active or stale writer lease may exist before source recovery.'] : []),
|
|
4362
|
+
...(snapshot.observation.pendingProjectTransactions.length
|
|
4363
|
+
+ snapshot.observation.pendingMilestoneTransactions.length
|
|
4364
|
+
+ snapshot.observation.pendingTaskTransactions.length
|
|
4365
|
+
+ snapshot.observation.corruptTaskTransactions.length
|
|
4366
|
+
+ snapshot.observation.coreTaskOperations.length > 0 ? ['No pending or corrupt transaction or Core operation may exist.'] : []),
|
|
4367
|
+
...(!recorded || recorded.stepId === stepId
|
|
4368
|
+
|| task.steps.find((candidate) => candidate.id === recorded.stepId)?.status !== 'completed'
|
|
4369
|
+
? ['A different, verified completed Step must own the historical carryover binding.'] : []),
|
|
4370
|
+
...(!sourceActionAllowed ? ['No exact source Plan replacement or original-Worker handoff action is eligible.'] : []),
|
|
4371
|
+
...(!(task.status === 'ready' || (action === 'task plan-set' && task.status === 'needs_fix'))
|
|
4372
|
+
|| task.workspaceOwner !== 'local'
|
|
4373
|
+
|| currentBranch(repositoryRoot) !== task.taskBranch
|
|
4374
|
+
|| task.steps.some((candidate) => candidate.status === 'in_progress')
|
|
4375
|
+
? ['The Task must retain its local, no-running-Step source recovery boundary.'] : []),
|
|
4376
|
+
...(!step || dirtyFiles.length === 0
|
|
4377
|
+
|| dirtyFiles.some((file) => !pathAllowed(file, step.allowedWrites) || pathAllowed(file, step.forbiddenScope))
|
|
4378
|
+
? ['Retained dirty files must remain inside the exact current Step scope.'] : []),
|
|
4379
|
+
...(!terminal || terminal.failureEvidence.completionCommit !== currentHead
|
|
4380
|
+
|| terminal.failureEvidence.dirtyWorktreeHash !== dirtyWorktreeHash
|
|
4381
|
+
? ['The terminal repeated failure HEAD and dirty-byte binding must remain unchanged.'] : []),
|
|
4382
|
+
...(!yielded || !terminal || yielded.planHash === null
|
|
4383
|
+
|| !Number.isFinite(Date.parse(yielded.recordedAt))
|
|
4384
|
+
|| Date.parse(yielded.recordedAt) < Date.parse(terminal.recordedAt)
|
|
4385
|
+
? ['The terminal failure must retain its exact completed original-Worker corrective yield.'] : []),
|
|
4386
|
+
];
|
|
4387
|
+
let candidateBinding = null;
|
|
4388
|
+
if (action === 'task plan-set') {
|
|
4389
|
+
if (!candidate) {
|
|
4390
|
+
blockers.push('Source Plan replacement requires candidate Plan and independent Plan Risk Audit inputs.');
|
|
4391
|
+
}
|
|
4392
|
+
else {
|
|
4393
|
+
try {
|
|
4394
|
+
validatePlan(candidate.plan);
|
|
4395
|
+
const retained = deriveCompletedStepCarryover(task, candidate.plan.steps);
|
|
4396
|
+
if (task.steps.some((completed) => completed.status === 'completed' && !retained.has(completed.id))) {
|
|
4397
|
+
throw new Error('Candidate must preserve every completed Step definition and its execution authority exactly.');
|
|
4398
|
+
}
|
|
4399
|
+
const correctiveStep = candidate.plan.steps.find((candidateStep) => candidateStep.id === stepId);
|
|
4400
|
+
if (!correctiveStep || dirtyFiles.some((file) => !pathAllowed(file, correctiveStep.allowedWrites) || pathAllowed(file, correctiveStep.forbiddenScope))) {
|
|
4401
|
+
throw new Error('Candidate must retain the exact failed Step and confine every dirty path to its replacement scope.');
|
|
4402
|
+
}
|
|
4403
|
+
if (correctiveStep.dependencies.some((dependency) => !retained.has(dependency))) {
|
|
4404
|
+
throw new Error('Candidate corrective Step dependencies must retain completed execution authority.');
|
|
4405
|
+
}
|
|
4406
|
+
if (candidate.plan.objective !== task.planObjective
|
|
4407
|
+
|| canonicalJsonStringify(candidate.plan.requirements) !== canonicalJsonStringify(task.requirements)
|
|
4408
|
+
|| canonicalJsonStringify(candidate.plan.acceptance) !== canonicalJsonStringify(task.acceptance)) {
|
|
4409
|
+
throw new Error('Candidate cannot change Task objective, requirements or acceptance.');
|
|
4410
|
+
}
|
|
4411
|
+
const map = this.assertKnowledgeMapBinding(repositoryRoot, task.projectId, candidate.plan.knowledgeMapRevision, candidate.plan.knowledgeMapHash);
|
|
4412
|
+
this.validateKnowledgeTargets(map, candidate.plan);
|
|
4413
|
+
const prepared = prepareCorrectiveReplanCandidate(task, candidate.plan);
|
|
4414
|
+
if (!task.planHash || normalizePlanKnowledgeBinding(prepared.planBytes, 'source recovery candidate').semanticHash
|
|
4415
|
+
=== normalizePlanKnowledgeBinding(readVerifiedPlanArtifact(this.store.taskRoot(task.projectId, task.id), task.planHash, task.planHash), 'current failed Plan').semanticHash) {
|
|
4416
|
+
throw new Error('Candidate must change the failed Plan semantics before source recovery.');
|
|
4417
|
+
}
|
|
4418
|
+
if (!['approved', 'approved-with-rationale'].includes(candidate.planRiskAudit.decision)) {
|
|
4419
|
+
throw new Error('Source recovery candidate requires a positive independent Plan Risk Audit.');
|
|
4420
|
+
}
|
|
4421
|
+
const auditTask = {
|
|
4422
|
+
...task, status: 'awaiting_execution_authorization', planHash: prepared.planHash,
|
|
4423
|
+
knowledgeMapRevision: candidate.plan.knowledgeMapRevision,
|
|
4424
|
+
knowledgeMapHash: candidate.plan.knowledgeMapHash,
|
|
4425
|
+
steps: candidate.plan.steps.map((candidateStep) => retained.get(candidateStep.id) ?? {
|
|
4426
|
+
...candidateStep, status: 'planned', evidence: null, failurePause: null,
|
|
4427
|
+
}),
|
|
4428
|
+
};
|
|
4429
|
+
validatePlanRiskAuditCandidate(this.store, auditTask, candidate.planRiskAudit);
|
|
4430
|
+
candidateBinding = {
|
|
4431
|
+
candidatePlanHash: prepared.planHash,
|
|
4432
|
+
candidateInputHash: sha256Hex(canonicalJsonStringify(candidate.plan)),
|
|
4433
|
+
riskAuditInputHash: sha256Hex(canonicalJsonStringify(candidate.planRiskAudit)),
|
|
4434
|
+
};
|
|
4435
|
+
}
|
|
4436
|
+
catch (error) {
|
|
4437
|
+
blockers.push(error instanceof Error ? error.message : String(error));
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4441
|
+
try {
|
|
4442
|
+
validateTaskHistory(repositoryRoot, task);
|
|
4443
|
+
this.assertTaskKnowledgeBinding(repositoryRoot, task);
|
|
4444
|
+
const sourceAudit = [...readPlanRiskAuditEvents(this.store, task)].reverse().find((event) => event.taskRevision <= (terminal?.failureEvidence.executionTaskRevision ?? -1));
|
|
4445
|
+
if (!sourceAudit || !yielded?.planHash || !task.planHash
|
|
4446
|
+
|| normalizePlanKnowledgeBinding(readVerifiedPlanArtifact(this.store.taskRoot(task.projectId, task.id), sourceAudit.planHash, task.planHash), 'terminal source').semanticHash
|
|
4447
|
+
!== normalizePlanKnowledgeBinding(readVerifiedPlanArtifact(this.store.taskRoot(task.projectId, task.id), yielded.planHash, task.planHash), 'terminal corrective yield').semanticHash) {
|
|
4448
|
+
throw new Error('Terminal failure and exact corrective yield must share a verified source Plan.');
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
catch (error) {
|
|
4452
|
+
blockers.push(error instanceof Error ? error.message : String(error));
|
|
4453
|
+
}
|
|
4454
|
+
const binding = {
|
|
4455
|
+
taskId: task.id, taskRevision: task.revision, stepId, briefHash: task.briefHash,
|
|
4456
|
+
planHash: task.planHash, planRiskAuditEventHash: readCurrentPlanRiskAudit(this.store, task)?.audit.eventHash ?? null,
|
|
4457
|
+
sourceHeadCommit: currentHead, dirtyFiles, dirtyWorktreeHash,
|
|
4458
|
+
completedStepsHash: hashCompletedSteps(task), historicalCarryoverHash: recorded?.compatibilityHash ?? null,
|
|
4459
|
+
terminalRemediationEventHash: terminal?.eventHash ?? null,
|
|
4460
|
+
correctiveYieldEventHash: yielded?.eventHash ?? null, originalWorker: yielded?.actor ?? null,
|
|
4461
|
+
c1Posture: c1.state,
|
|
4462
|
+
candidateBinding,
|
|
4463
|
+
};
|
|
4464
|
+
return {
|
|
4465
|
+
readOnly: true, eligible: false, sourceVersion, targetVersion, ...binding,
|
|
4466
|
+
action: null, transportAllowed: false,
|
|
4467
|
+
...(action === 'task plan-set' ? { taskPlanContract: navigation.taskPlanContract } : {}),
|
|
4468
|
+
compatibilityHash: sha256Hex(canonicalJsonStringify(binding)),
|
|
4469
|
+
sourceRecovery: blockers.length === 0 ? {
|
|
4470
|
+
eligible: true, action,
|
|
4471
|
+
...(action === 'task plan-set' ? { ...candidateBinding } : {}),
|
|
4472
|
+
...(action === 'task handoff-prepare' ? { targetActor: yielded.actor } : {}),
|
|
4473
|
+
...(action === 'task claim' ? { actor: yielded.actor } : {}),
|
|
4474
|
+
instruction: 'Execute only this exact action with the installed source package; run source status then next and repeat this read-only preflight. Ordinary Plan Risk Audit and execution authorization retain their normal gates.',
|
|
4475
|
+
} : null,
|
|
4476
|
+
blockers,
|
|
4477
|
+
};
|
|
4178
4478
|
}
|
|
4179
4479
|
historicalStepProvenanceRecoveryPreflight(repository, taskId) {
|
|
4180
4480
|
const snapshot = this.observationSnapshot(repository);
|
|
@@ -4645,7 +4945,9 @@ export class WorkflowService {
|
|
|
4645
4945
|
? Boolean(record.historicalStepProvenance)
|
|
4646
4946
|
: recoveryMode === 'plan-integrity'
|
|
4647
4947
|
? Boolean(record.planIntegrity)
|
|
4648
|
-
:
|
|
4948
|
+
: recoveryMode === false && record.rootCauseReplan
|
|
4949
|
+
? true
|
|
4950
|
+
: Boolean(record.activeDownstreamProof) === recoveryMode));
|
|
4649
4951
|
if (prior && current.systemCommits.includes(currentHead))
|
|
4650
4952
|
return current;
|
|
4651
4953
|
assertExpectedRevision(current, expectedRevision);
|
|
@@ -4661,13 +4963,26 @@ export class WorkflowService {
|
|
|
4661
4963
|
? this.planIntegrityDependencyRecoveryPreflight(repository, taskId)
|
|
4662
4964
|
: recoveryMode
|
|
4663
4965
|
? this.activeDownstreamProofDependencyRecoveryPreflight(repository, taskId)
|
|
4664
|
-
: this.
|
|
4966
|
+
: this.dependencyProvenanceRecoveryPreflight(repository, taskId);
|
|
4665
4967
|
if (!preflight.eligible || !preflight.commitSha || !preflight.parentCommitSha) {
|
|
4666
4968
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'The bounded dependency provenance recovery preflight failed.', {
|
|
4667
4969
|
taskId,
|
|
4668
4970
|
blockers: preflight.blockers,
|
|
4669
4971
|
});
|
|
4670
4972
|
}
|
|
4973
|
+
if (preflight.rootCauseReplan) {
|
|
4974
|
+
const posture = readTaskC1Posture(this.store, task);
|
|
4975
|
+
if (posture.state !== 'claimed'
|
|
4976
|
+
|| posture.claimant !== preflight.rootCauseReplan.claimant
|
|
4977
|
+
|| normalizedActor !== posture.claimant) {
|
|
4978
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Corrective carryover dependency recovery requires the exact retained C1 claimant actor.', {
|
|
4979
|
+
taskId,
|
|
4980
|
+
actor: normalizedActor,
|
|
4981
|
+
requiredActor: preflight.rootCauseReplan.claimant,
|
|
4982
|
+
posture: posture.state,
|
|
4983
|
+
});
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4671
4986
|
const now = this.now();
|
|
4672
4987
|
const recoveredRecords = preflight.dependencyCommits.map((dependencyCommit, index) => {
|
|
4673
4988
|
const isTerminalCommit = index === preflight.dependencyCommits.length - 1;
|
|
@@ -4693,6 +5008,9 @@ export class WorkflowService {
|
|
|
4693
5008
|
...(isTerminalCommit && preflight.planIntegrity
|
|
4694
5009
|
? { planIntegrity: preflight.planIntegrity }
|
|
4695
5010
|
: {}),
|
|
5011
|
+
...(isTerminalCommit && preflight.rootCauseReplan
|
|
5012
|
+
? { rootCauseReplan: preflight.rootCauseReplan }
|
|
5013
|
+
: {}),
|
|
4696
5014
|
};
|
|
4697
5015
|
});
|
|
4698
5016
|
const nextTask = {
|
|
@@ -6039,6 +6357,9 @@ export class WorkflowService {
|
|
|
6039
6357
|
}
|
|
6040
6358
|
const next = this.taskGitNavigationOverride(task) ?? nextForTask(task);
|
|
6041
6359
|
const nextStepId = typeof next.stepId === 'string' ? next.stepId : null;
|
|
6360
|
+
const rootCauseStepId = nextStepId
|
|
6361
|
+
?? task.steps.find((candidate) => candidate.status === 'planned')?.id
|
|
6362
|
+
?? null;
|
|
6042
6363
|
const downstreamProofCarryover = next.action === 'task run'
|
|
6043
6364
|
&& nextStepId
|
|
6044
6365
|
&& changedFiles(repositoryRoot).length > 0
|
|
@@ -6049,6 +6370,24 @@ export class WorkflowService {
|
|
|
6049
6370
|
&& changedFiles(repositoryRoot).length > 0
|
|
6050
6371
|
? this.assessPostRebindCheckSupportDirtyCarryover(repositoryRoot, task, nextStepId)
|
|
6051
6372
|
: null;
|
|
6373
|
+
let rootCauseReplanDirtyCarryover = ['task run', 'task handoff-prepare', 'task claim'].includes(String(next.action))
|
|
6374
|
+
&& rootCauseStepId
|
|
6375
|
+
&& changedFiles(repositoryRoot).length > 0
|
|
6376
|
+
? this.assessCurrentRootCauseReplanDirtyCarryover(repositoryRoot, task, rootCauseStepId)
|
|
6377
|
+
: null;
|
|
6378
|
+
if (rootCauseReplanDirtyCarryover?.eligible) {
|
|
6379
|
+
try {
|
|
6380
|
+
this.assertExecutionAuthorizationFresh(task, repositoryRoot);
|
|
6381
|
+
}
|
|
6382
|
+
catch (error) {
|
|
6383
|
+
rootCauseReplanDirtyCarryover = {
|
|
6384
|
+
applicable: true,
|
|
6385
|
+
eligible: false,
|
|
6386
|
+
compatibility: null,
|
|
6387
|
+
blockers: [error instanceof Error ? error.message : String(error)],
|
|
6388
|
+
};
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6052
6391
|
let mechanicalFeasibilityOverlay = {};
|
|
6053
6392
|
if (task.status === 'awaiting_execution_authorization' && task.planHash) {
|
|
6054
6393
|
try {
|
|
@@ -6678,7 +7017,7 @@ export class WorkflowService {
|
|
|
6678
7017
|
downstreamProofCarryover: {
|
|
6679
7018
|
state: 'validated',
|
|
6680
7019
|
invalidationEventId: downstreamProofCarryover.invalidationEventId,
|
|
6681
|
-
stepId:
|
|
7020
|
+
stepId: rootCauseStepId,
|
|
6682
7021
|
dirtyFiles: downstreamProofCarryover.dirtyFiles,
|
|
6683
7022
|
dirtyWorktreeHash: downstreamProofCarryover.dirtyWorktreeHash,
|
|
6684
7023
|
meaning: 'The replacement Step may adopt exactly these preserved uncommitted bytes on task run.',
|
|
@@ -6718,6 +7057,52 @@ export class WorkflowService {
|
|
|
6718
7057
|
},
|
|
6719
7058
|
}
|
|
6720
7059
|
: {}),
|
|
7060
|
+
...(rootCauseReplanDirtyCarryover?.applicable
|
|
7061
|
+
? rootCauseReplanDirtyCarryover.eligible && rootCauseReplanDirtyCarryover.compatibility
|
|
7062
|
+
? {
|
|
7063
|
+
action: 'task run',
|
|
7064
|
+
rootCauseReplanDirtyCarryover: {
|
|
7065
|
+
state: 'validated',
|
|
7066
|
+
stepId: nextStepId,
|
|
7067
|
+
compatibilityHash: rootCauseReplanDirtyCarryover.compatibility.compatibilityHash,
|
|
7068
|
+
dirtyFiles: rootCauseReplanDirtyCarryover.compatibility.dirtyFiles,
|
|
7069
|
+
dirtyWorktreeHash: rootCauseReplanDirtyCarryover.compatibility.dirtyWorktreeHash,
|
|
7070
|
+
meaning: 'The replacement Step may adopt the exact retained root-cause failure bytes.',
|
|
7071
|
+
},
|
|
7072
|
+
}
|
|
7073
|
+
: rootCauseReplanDirtyCarryover.blockers.length === 1
|
|
7074
|
+
&& rootCauseReplanDirtyCarryover.blockers[0]?.startsWith('Corrective carryover requires the retained claimed C1')
|
|
7075
|
+
? handoffPosture.state === 'none'
|
|
7076
|
+
? {
|
|
7077
|
+
action: 'task handoff-prepare',
|
|
7078
|
+
blockedAction: 'task run',
|
|
7079
|
+
rootCauseReplanDirtyCarryover: {
|
|
7080
|
+
state: 'awaiting-c1',
|
|
7081
|
+
stepId: rootCauseStepId,
|
|
7082
|
+
requiredActor: defaultTaskWorkerActor(task.id),
|
|
7083
|
+
},
|
|
7084
|
+
}
|
|
7085
|
+
: handoffPosture.state === 'pending'
|
|
7086
|
+
? {}
|
|
7087
|
+
: {
|
|
7088
|
+
action: 'doctor',
|
|
7089
|
+
blockedAction: 'task run',
|
|
7090
|
+
rootCauseReplanDirtyCarryover: {
|
|
7091
|
+
state: 'blocked',
|
|
7092
|
+
stepId: rootCauseStepId,
|
|
7093
|
+
blockers: rootCauseReplanDirtyCarryover.blockers,
|
|
7094
|
+
},
|
|
7095
|
+
}
|
|
7096
|
+
: {
|
|
7097
|
+
action: 'doctor',
|
|
7098
|
+
blockedAction: 'task run',
|
|
7099
|
+
rootCauseReplanDirtyCarryover: {
|
|
7100
|
+
state: 'blocked',
|
|
7101
|
+
stepId: rootCauseStepId,
|
|
7102
|
+
blockers: rootCauseReplanDirtyCarryover.blockers,
|
|
7103
|
+
},
|
|
7104
|
+
}
|
|
7105
|
+
: {}),
|
|
6721
7106
|
...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
|
|
6722
7107
|
...(correctiveDerivedAuthority && correctiveDerivedActor
|
|
6723
7108
|
? {
|
|
@@ -6734,7 +7119,10 @@ export class WorkflowService {
|
|
|
6734
7119
|
}
|
|
6735
7120
|
: {}),
|
|
6736
7121
|
};
|
|
6737
|
-
|
|
7122
|
+
const rootCauseAllowsPendingClaim = !rootCauseReplanDirtyCarryover?.applicable
|
|
7123
|
+
|| (rootCauseReplanDirtyCarryover.blockers.length === 1
|
|
7124
|
+
&& rootCauseReplanDirtyCarryover.blockers[0]?.startsWith('Corrective carryover requires the retained claimed C1'));
|
|
7125
|
+
if (handoffPosture.state === 'pending' && rootCauseAllowsPendingClaim) {
|
|
6738
7126
|
return this.withTaskNavigationContracts(task, {
|
|
6739
7127
|
...result,
|
|
6740
7128
|
action: 'task claim',
|
|
@@ -7256,7 +7644,7 @@ export class WorkflowService {
|
|
|
7256
7644
|
blockers,
|
|
7257
7645
|
};
|
|
7258
7646
|
}
|
|
7259
|
-
inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null, downstreamProofReplan = null, historicalStepProvenance = null, planIntegrity = null) {
|
|
7647
|
+
inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null, downstreamProofReplan = null, historicalStepProvenance = null, planIntegrity = null, rootCauseReplan = null) {
|
|
7260
7648
|
const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
|
|
7261
7649
|
const now = this.now().getTime();
|
|
7262
7650
|
return inspectDependencyProvenanceRecovery(snapshot.identity.repositoryRoot, task, {
|
|
@@ -7276,7 +7664,89 @@ export class WorkflowService {
|
|
|
7276
7664
|
currentBranch: versions.currentBranch,
|
|
7277
7665
|
baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
|
|
7278
7666
|
},
|
|
7279
|
-
}, activeDownstreamProof, downstreamProofReplan, historicalStepProvenance, planIntegrity);
|
|
7667
|
+
}, activeDownstreamProof, downstreamProofReplan, historicalStepProvenance, planIntegrity, rootCauseReplan);
|
|
7668
|
+
}
|
|
7669
|
+
assessCurrentRootCauseReplanDirtyCarryover(repositoryRoot, task, stepId, historyHead) {
|
|
7670
|
+
const recordedRecovery = [...(task.dependencyProvenanceRecoveries ?? [])].reverse().find((record) => record.rootCauseReplan
|
|
7671
|
+
&& task.systemCommits.includes(record.commitSha)) ?? null;
|
|
7672
|
+
const recorded = recordedRecovery?.rootCauseReplan ?? null;
|
|
7673
|
+
if (recorded) {
|
|
7674
|
+
const recordedStep = task.steps.find((candidate) => candidate.id === recorded.stepId) ?? null;
|
|
7675
|
+
if (!recordedStep) {
|
|
7676
|
+
return {
|
|
7677
|
+
applicable: true,
|
|
7678
|
+
eligible: false,
|
|
7679
|
+
compatibility: null,
|
|
7680
|
+
blockers: [`Recorded corrective carryover Step ${recorded.stepId} is missing from the current Plan.`],
|
|
7681
|
+
};
|
|
7682
|
+
}
|
|
7683
|
+
if (recordedStep.status === 'completed') {
|
|
7684
|
+
const blockers = [];
|
|
7685
|
+
if (!recordedStep.evidence) {
|
|
7686
|
+
blockers.push(`Completed corrective carryover Step ${recorded.stepId} has no execution evidence.`);
|
|
7687
|
+
}
|
|
7688
|
+
try {
|
|
7689
|
+
validateTaskHistory(repositoryRoot, task, historyHead);
|
|
7690
|
+
const sourceAudit = readPlanRiskAuditEvents(this.store, task).find((event) => event.eventId === recorded.replacementPlanRiskAuditEventId
|
|
7691
|
+
&& event.eventHash === recorded.replacementPlanRiskAuditEventHash) ?? null;
|
|
7692
|
+
const classification = sourceAudit?.stepClassifications.find((candidate) => candidate.stepId === recorded.stepId) ?? null;
|
|
7693
|
+
if (!sourceAudit || !classification) {
|
|
7694
|
+
blockers.push('Completed corrective carryover Step lost its exact replacement Plan Risk Audit binding.');
|
|
7695
|
+
}
|
|
7696
|
+
else if (classification.reviewRequired
|
|
7697
|
+
&& recordedStep.evidence
|
|
7698
|
+
&& !verifiedHistoricalStepReviewBindings(this.store, task).some((binding) => binding.stepId === recorded.stepId
|
|
7699
|
+
&& binding.commitSha === recordedStep.evidence.commitSha)) {
|
|
7700
|
+
blockers.push(`Completed corrective carryover Step ${recorded.stepId} lacks verified strict-review proof.`);
|
|
7701
|
+
}
|
|
7702
|
+
}
|
|
7703
|
+
catch (error) {
|
|
7704
|
+
blockers.push(error instanceof Error ? error.message : String(error));
|
|
7705
|
+
}
|
|
7706
|
+
if (blockers.length > 0) {
|
|
7707
|
+
return {
|
|
7708
|
+
applicable: true,
|
|
7709
|
+
eligible: false,
|
|
7710
|
+
compatibility: null,
|
|
7711
|
+
blockers: [...new Set(blockers)],
|
|
7712
|
+
};
|
|
7713
|
+
}
|
|
7714
|
+
// A reviewed, history-valid completed Step consumed this carryover. Keep its
|
|
7715
|
+
// immutable recovery record, but let later Steps use their own lifecycle route.
|
|
7716
|
+
}
|
|
7717
|
+
else {
|
|
7718
|
+
if (stepId && stepId !== recorded.stepId) {
|
|
7719
|
+
return {
|
|
7720
|
+
applicable: true,
|
|
7721
|
+
eligible: false,
|
|
7722
|
+
compatibility: null,
|
|
7723
|
+
blockers: [`Recorded corrective carryover is bound to ${recorded.stepId}, not requested ${stepId}.`],
|
|
7724
|
+
};
|
|
7725
|
+
}
|
|
7726
|
+
return assessRootCauseReplanDirtyCarryover(this.store, repositoryRoot, task, recorded.stepId, {
|
|
7727
|
+
binding: recorded,
|
|
7728
|
+
});
|
|
7729
|
+
}
|
|
7730
|
+
}
|
|
7731
|
+
const candidates = task.steps
|
|
7732
|
+
.filter((step) => step.status === 'planned' && (!stepId || step.id === stepId))
|
|
7733
|
+
.flatMap((step) => [...new Set(readRemediationEvents(this.store, task)
|
|
7734
|
+
.filter((event) => event.stepId === step.id && event.failureKind === 'checks-failed')
|
|
7735
|
+
.map((event) => event.failureEvidence.completionCommit))]
|
|
7736
|
+
.map((historyHead) => assessRootCauseReplanDirtyCarryover(this.store, repositoryRoot, task, step.id, { historyHead })))
|
|
7737
|
+
.filter((assessment) => assessment.applicable);
|
|
7738
|
+
const eligible = candidates.filter((assessment) => assessment.eligible && assessment.compatibility);
|
|
7739
|
+
if (eligible.length === 1)
|
|
7740
|
+
return eligible[0];
|
|
7741
|
+
return {
|
|
7742
|
+
applicable: candidates.length > 0,
|
|
7743
|
+
eligible: false,
|
|
7744
|
+
compatibility: null,
|
|
7745
|
+
blockers: [...new Set([
|
|
7746
|
+
...candidates.flatMap((assessment) => assessment.blockers),
|
|
7747
|
+
...(eligible.length > 1 ? ['Corrective carryover history resolved to more than one eligible boundary.'] : []),
|
|
7748
|
+
])],
|
|
7749
|
+
};
|
|
7280
7750
|
}
|
|
7281
7751
|
inspectHistoricalStepProvenanceCandidate(snapshot, task, options = {}) {
|
|
7282
7752
|
const now = this.now().getTime();
|