codex-workflow-v2 2.0.0-beta.13.5 → 2.0.0-beta.13.6

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.
@@ -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';
@@ -20,6 +20,7 @@ import { assessDownstreamProofCarryover, assessDownstreamProofReplanRecovery, as
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, } 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,145 @@ 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
- return this.inspectDependencyProvenanceCandidate(snapshot, task);
4185
+ const carryover = this.assessCurrentRootCauseReplanDirtyCarryover(snapshot.identity.repositoryRoot, task);
4186
+ const preflight = this.inspectDependencyProvenanceCandidate(snapshot, task, null, null, null, null, carryover.eligible ? carryover.compatibility : null);
4187
+ if (!preflight.rootCauseReplan)
4188
+ return preflight;
4189
+ let baseTip = null;
4190
+ let baseDependency = null;
4191
+ try {
4192
+ baseTip = runGit(snapshot.identity.repositoryRoot, ['rev-parse', task.baseBranch]);
4193
+ baseDependency = inspectBoundedWorkflowDependencyCommit(snapshot.identity.repositoryRoot, baseTip);
4194
+ }
4195
+ catch {
4196
+ // The blocker below keeps a missing or unreadable base reference fail closed.
4197
+ }
4198
+ const bridgeBlockers = [
4199
+ ...(preflight.dependencyCommits.length !== 1
4200
+ ? ['Corrective carryover recovery requires exactly one Task dependency-only commit.'] : []),
4201
+ ...(preflight.parentCommitSha !== preflight.rootCauseReplan.sourceHeadCommit
4202
+ ? ['Corrective carryover dependency parent must equal the bound source HEAD.'] : []),
4203
+ ...(preflight.parentCommitSha
4204
+ && workflowDependencyVersionAt(snapshot.identity.repositoryRoot, preflight.parentCommitSha) !== '2.0.0-beta.13.5'
4205
+ ? ['Corrective carryover dependency parent must declare exact source 2.0.0-beta.13.5.'] : []),
4206
+ ...(preflight.dependencyCommits.at(-1)?.packageVersion !== '2.0.0-beta.13.6'
4207
+ ? ['Corrective carryover dependency commit must declare exact target 2.0.0-beta.13.6.'] : []),
4208
+ ...(!baseDependency
4209
+ || baseDependency.commitSha !== baseTip
4210
+ || baseDependency.packageVersion !== '2.0.0-beta.13.6'
4211
+ || workflowDependencyVersionAt(snapshot.identity.repositoryRoot, baseDependency.parentCommitSha) !== '2.0.0-beta.13.5'
4212
+ ? ['Corrective carryover Milestone-base tip must be one exact 2.0.0-beta.13.5 to 2.0.0-beta.13.6 dependency-only commit.'] : []),
4213
+ ];
4214
+ return bridgeBlockers.length === 0
4215
+ ? preflight
4216
+ : {
4217
+ ...preflight,
4218
+ eligible: false,
4219
+ action: null,
4220
+ commitSha: null,
4221
+ parentCommitSha: null,
4222
+ blockers: [...new Set([...preflight.blockers, ...bridgeBlockers])],
4223
+ };
4224
+ }
4225
+ correctiveCarryoverUpdatePreflight(repository, taskId) {
4226
+ const snapshot = this.observationSnapshot(repository);
4227
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
4228
+ if (!task)
4229
+ throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
4230
+ const sourceVersion = '2.0.0-beta.13.5';
4231
+ const targetVersion = '2.0.0-beta.13.6';
4232
+ const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
4233
+ const sourceSurfaces = {
4234
+ declared: versions.declared,
4235
+ locked: versions.locked,
4236
+ installed: versions.installed,
4237
+ currentBranch: versions.currentBranch,
4238
+ baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
4239
+ };
4240
+ const assessment = this.assessCurrentRootCauseReplanDirtyCarryover(snapshot.identity.repositoryRoot, task);
4241
+ const c1 = readTaskC1Posture(this.store, task);
4242
+ const now = this.now().getTime();
4243
+ const activeLeases = snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) > now);
4244
+ const staleLeases = snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) <= now);
4245
+ const transactionCount = snapshot.observation.pendingProjectTransactions.length
4246
+ + snapshot.observation.pendingMilestoneTransactions.length
4247
+ + snapshot.observation.pendingTaskTransactions.length;
4248
+ const authorizationBlockers = [];
4249
+ if (assessment.eligible) {
4250
+ try {
4251
+ this.assertExecutionAuthorizationFresh(task, snapshot.identity.repositoryRoot);
4252
+ }
4253
+ catch (error) {
4254
+ authorizationBlockers.push(error instanceof Error ? error.message : String(error));
4255
+ }
4256
+ }
4257
+ const baseBlockers = [
4258
+ ...assessment.blockers,
4259
+ ...authorizationBlockers,
4260
+ ...(PACKAGE_VERSION !== targetVersion ? [`Runner must be the exact ${targetVersion} compatibility target.`] : []),
4261
+ ...Object.entries(sourceSurfaces)
4262
+ .filter(([, version]) => version !== sourceVersion)
4263
+ .map(([surface]) => `${surface} Workflow version must equal the exact ${sourceVersion} source.`),
4264
+ ...observationPreflightBlockers(snapshot.observation),
4265
+ ...(transactionCount > 0 ? ['No pending Workflow transaction may exist.'] : []),
4266
+ ...(snapshot.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
4267
+ ...(snapshot.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
4268
+ ...(c1.state !== 'claimed' || assessment.compatibility?.claimant !== c1.claimant
4269
+ ? ['The exact original Worker C1 claim must remain current.'] : []),
4270
+ ...(activeLeases.length > 0 ? ['No live writer lease may exist during corrective carryover transport.'] : []),
4271
+ ...(staleLeases.some((lease) => lease.entityId !== task.id
4272
+ || c1.state !== 'claimed' || lease.owner !== c1.claimant
4273
+ || createHash('sha256').update(lease.token).digest('hex') !== c1.writerLeaseTokenHash)
4274
+ ? ['No unrelated or differently owned stale writer lease may exist.'] : []),
4275
+ ...(staleLeases.length > 1 ? ['At most the exact corrective Task stale lease may exist.'] : []),
4276
+ ];
4277
+ const repairableStale = baseBlockers.length === 0 && staleLeases.length === 1
4278
+ ? staleLeases[0]
4279
+ : null;
4280
+ const blockers = [
4281
+ ...baseBlockers,
4282
+ ...(repairableStale
4283
+ ? ['Repair the exact expired claimant lease with source locks repair, then run status, next, and this preflight again.']
4284
+ : []),
4285
+ ];
4286
+ const compatibility = assessment.compatibility;
4287
+ return {
4288
+ readOnly: true,
4289
+ eligible: Boolean(assessment.eligible && compatibility && blockers.length === 0 && staleLeases.length === 0),
4290
+ action: assessment.eligible && compatibility && blockers.length === 0 && staleLeases.length === 0
4291
+ ? 'update corrective-carryover-transport'
4292
+ : null,
4293
+ projectId: task.projectId,
4294
+ taskId: task.id,
4295
+ taskRevision: task.revision,
4296
+ stepId: compatibility?.stepId ?? null,
4297
+ sourceVersion,
4298
+ targetVersion,
4299
+ sourceHeadCommit: compatibility?.sourceHeadCommit ?? null,
4300
+ taskBranch: task.taskBranch,
4301
+ baseBranch: task.baseBranch,
4302
+ dirtyFiles: compatibility?.dirtyFiles ?? [],
4303
+ dirtyWorktreeHash: compatibility?.dirtyWorktreeHash ?? null,
4304
+ compatibilityHash: compatibility?.compatibilityHash ?? null,
4305
+ c1Posture: c1.state === 'claimed' ? {
4306
+ state: 'claimed',
4307
+ claimant: c1.claimant,
4308
+ claimEventId: compatibility?.c1ClaimEventId ?? null,
4309
+ claimEventHash: compatibility?.c1ClaimEventHash ?? null,
4310
+ } : null,
4311
+ staleLeaseRepair: repairableStale ? [{
4312
+ action: 'locks repair',
4313
+ entityId: task.id,
4314
+ owner: repairableStale.owner,
4315
+ expiresAt: repairableStale.expiresAt,
4316
+ }] : [],
4317
+ requiredTransport: [
4318
+ 'Commit only package.json and package-lock.json on the active Milestone base.',
4319
+ 'Commit only the same dependency files on the Task branch while preserving the bound dirty bytes.',
4320
+ 'Install the exact target package, register dependency provenance, refresh context, replace the same Worker credential, then follow fresh next.',
4321
+ ],
4322
+ blockers,
4323
+ };
4178
4324
  }
4179
4325
  historicalStepProvenanceRecoveryPreflight(repository, taskId) {
4180
4326
  const snapshot = this.observationSnapshot(repository);
@@ -4645,7 +4791,9 @@ export class WorkflowService {
4645
4791
  ? Boolean(record.historicalStepProvenance)
4646
4792
  : recoveryMode === 'plan-integrity'
4647
4793
  ? Boolean(record.planIntegrity)
4648
- : Boolean(record.activeDownstreamProof) === recoveryMode));
4794
+ : recoveryMode === false && record.rootCauseReplan
4795
+ ? true
4796
+ : Boolean(record.activeDownstreamProof) === recoveryMode));
4649
4797
  if (prior && current.systemCommits.includes(currentHead))
4650
4798
  return current;
4651
4799
  assertExpectedRevision(current, expectedRevision);
@@ -4661,13 +4809,26 @@ export class WorkflowService {
4661
4809
  ? this.planIntegrityDependencyRecoveryPreflight(repository, taskId)
4662
4810
  : recoveryMode
4663
4811
  ? this.activeDownstreamProofDependencyRecoveryPreflight(repository, taskId)
4664
- : this.inspectDependencyProvenanceCandidate(snapshot, task);
4812
+ : this.dependencyProvenanceRecoveryPreflight(repository, taskId);
4665
4813
  if (!preflight.eligible || !preflight.commitSha || !preflight.parentCommitSha) {
4666
4814
  throw new WorkflowError('GIT_PRECONDITION_FAILED', 'The bounded dependency provenance recovery preflight failed.', {
4667
4815
  taskId,
4668
4816
  blockers: preflight.blockers,
4669
4817
  });
4670
4818
  }
4819
+ if (preflight.rootCauseReplan) {
4820
+ const posture = readTaskC1Posture(this.store, task);
4821
+ if (posture.state !== 'claimed'
4822
+ || posture.claimant !== preflight.rootCauseReplan.claimant
4823
+ || normalizedActor !== posture.claimant) {
4824
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Corrective carryover dependency recovery requires the exact retained C1 claimant actor.', {
4825
+ taskId,
4826
+ actor: normalizedActor,
4827
+ requiredActor: preflight.rootCauseReplan.claimant,
4828
+ posture: posture.state,
4829
+ });
4830
+ }
4831
+ }
4671
4832
  const now = this.now();
4672
4833
  const recoveredRecords = preflight.dependencyCommits.map((dependencyCommit, index) => {
4673
4834
  const isTerminalCommit = index === preflight.dependencyCommits.length - 1;
@@ -4693,6 +4854,9 @@ export class WorkflowService {
4693
4854
  ...(isTerminalCommit && preflight.planIntegrity
4694
4855
  ? { planIntegrity: preflight.planIntegrity }
4695
4856
  : {}),
4857
+ ...(isTerminalCommit && preflight.rootCauseReplan
4858
+ ? { rootCauseReplan: preflight.rootCauseReplan }
4859
+ : {}),
4696
4860
  };
4697
4861
  });
4698
4862
  const nextTask = {
@@ -6039,6 +6203,9 @@ export class WorkflowService {
6039
6203
  }
6040
6204
  const next = this.taskGitNavigationOverride(task) ?? nextForTask(task);
6041
6205
  const nextStepId = typeof next.stepId === 'string' ? next.stepId : null;
6206
+ const rootCauseStepId = nextStepId
6207
+ ?? task.steps.find((candidate) => candidate.status === 'planned')?.id
6208
+ ?? null;
6042
6209
  const downstreamProofCarryover = next.action === 'task run'
6043
6210
  && nextStepId
6044
6211
  && changedFiles(repositoryRoot).length > 0
@@ -6049,6 +6216,24 @@ export class WorkflowService {
6049
6216
  && changedFiles(repositoryRoot).length > 0
6050
6217
  ? this.assessPostRebindCheckSupportDirtyCarryover(repositoryRoot, task, nextStepId)
6051
6218
  : null;
6219
+ let rootCauseReplanDirtyCarryover = ['task run', 'task handoff-prepare', 'task claim'].includes(String(next.action))
6220
+ && rootCauseStepId
6221
+ && changedFiles(repositoryRoot).length > 0
6222
+ ? this.assessCurrentRootCauseReplanDirtyCarryover(repositoryRoot, task, rootCauseStepId)
6223
+ : null;
6224
+ if (rootCauseReplanDirtyCarryover?.eligible) {
6225
+ try {
6226
+ this.assertExecutionAuthorizationFresh(task, repositoryRoot);
6227
+ }
6228
+ catch (error) {
6229
+ rootCauseReplanDirtyCarryover = {
6230
+ applicable: true,
6231
+ eligible: false,
6232
+ compatibility: null,
6233
+ blockers: [error instanceof Error ? error.message : String(error)],
6234
+ };
6235
+ }
6236
+ }
6052
6237
  let mechanicalFeasibilityOverlay = {};
6053
6238
  if (task.status === 'awaiting_execution_authorization' && task.planHash) {
6054
6239
  try {
@@ -6678,7 +6863,7 @@ export class WorkflowService {
6678
6863
  downstreamProofCarryover: {
6679
6864
  state: 'validated',
6680
6865
  invalidationEventId: downstreamProofCarryover.invalidationEventId,
6681
- stepId: nextStepId,
6866
+ stepId: rootCauseStepId,
6682
6867
  dirtyFiles: downstreamProofCarryover.dirtyFiles,
6683
6868
  dirtyWorktreeHash: downstreamProofCarryover.dirtyWorktreeHash,
6684
6869
  meaning: 'The replacement Step may adopt exactly these preserved uncommitted bytes on task run.',
@@ -6718,6 +6903,52 @@ export class WorkflowService {
6718
6903
  },
6719
6904
  }
6720
6905
  : {}),
6906
+ ...(rootCauseReplanDirtyCarryover?.applicable
6907
+ ? rootCauseReplanDirtyCarryover.eligible && rootCauseReplanDirtyCarryover.compatibility
6908
+ ? {
6909
+ action: 'task run',
6910
+ rootCauseReplanDirtyCarryover: {
6911
+ state: 'validated',
6912
+ stepId: nextStepId,
6913
+ compatibilityHash: rootCauseReplanDirtyCarryover.compatibility.compatibilityHash,
6914
+ dirtyFiles: rootCauseReplanDirtyCarryover.compatibility.dirtyFiles,
6915
+ dirtyWorktreeHash: rootCauseReplanDirtyCarryover.compatibility.dirtyWorktreeHash,
6916
+ meaning: 'The replacement Step may adopt the exact retained root-cause failure bytes.',
6917
+ },
6918
+ }
6919
+ : rootCauseReplanDirtyCarryover.blockers.length === 1
6920
+ && rootCauseReplanDirtyCarryover.blockers[0]?.startsWith('Corrective carryover requires the retained claimed C1')
6921
+ ? handoffPosture.state === 'none'
6922
+ ? {
6923
+ action: 'task handoff-prepare',
6924
+ blockedAction: 'task run',
6925
+ rootCauseReplanDirtyCarryover: {
6926
+ state: 'awaiting-c1',
6927
+ stepId: rootCauseStepId,
6928
+ requiredActor: defaultTaskWorkerActor(task.id),
6929
+ },
6930
+ }
6931
+ : handoffPosture.state === 'pending'
6932
+ ? {}
6933
+ : {
6934
+ action: 'doctor',
6935
+ blockedAction: 'task run',
6936
+ rootCauseReplanDirtyCarryover: {
6937
+ state: 'blocked',
6938
+ stepId: rootCauseStepId,
6939
+ blockers: rootCauseReplanDirtyCarryover.blockers,
6940
+ },
6941
+ }
6942
+ : {
6943
+ action: 'doctor',
6944
+ blockedAction: 'task run',
6945
+ rootCauseReplanDirtyCarryover: {
6946
+ state: 'blocked',
6947
+ stepId: rootCauseStepId,
6948
+ blockers: rootCauseReplanDirtyCarryover.blockers,
6949
+ },
6950
+ }
6951
+ : {}),
6721
6952
  ...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
6722
6953
  ...(correctiveDerivedAuthority && correctiveDerivedActor
6723
6954
  ? {
@@ -6734,7 +6965,10 @@ export class WorkflowService {
6734
6965
  }
6735
6966
  : {}),
6736
6967
  };
6737
- if (handoffPosture.state === 'pending') {
6968
+ const rootCauseAllowsPendingClaim = !rootCauseReplanDirtyCarryover?.applicable
6969
+ || (rootCauseReplanDirtyCarryover.blockers.length === 1
6970
+ && rootCauseReplanDirtyCarryover.blockers[0]?.startsWith('Corrective carryover requires the retained claimed C1'));
6971
+ if (handoffPosture.state === 'pending' && rootCauseAllowsPendingClaim) {
6738
6972
  return this.withTaskNavigationContracts(task, {
6739
6973
  ...result,
6740
6974
  action: 'task claim',
@@ -7256,7 +7490,7 @@ export class WorkflowService {
7256
7490
  blockers,
7257
7491
  };
7258
7492
  }
7259
- inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null, downstreamProofReplan = null, historicalStepProvenance = null, planIntegrity = null) {
7493
+ inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null, downstreamProofReplan = null, historicalStepProvenance = null, planIntegrity = null, rootCauseReplan = null) {
7260
7494
  const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
7261
7495
  const now = this.now().getTime();
7262
7496
  return inspectDependencyProvenanceRecovery(snapshot.identity.repositoryRoot, task, {
@@ -7276,7 +7510,43 @@ export class WorkflowService {
7276
7510
  currentBranch: versions.currentBranch,
7277
7511
  baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
7278
7512
  },
7279
- }, activeDownstreamProof, downstreamProofReplan, historicalStepProvenance, planIntegrity);
7513
+ }, activeDownstreamProof, downstreamProofReplan, historicalStepProvenance, planIntegrity, rootCauseReplan);
7514
+ }
7515
+ assessCurrentRootCauseReplanDirtyCarryover(repositoryRoot, task, stepId) {
7516
+ const recorded = [...(task.dependencyProvenanceRecoveries ?? [])].reverse().find((record) => record.rootCauseReplan
7517
+ && task.systemCommits.includes(record.commitSha))?.rootCauseReplan ?? null;
7518
+ if (recorded) {
7519
+ if (stepId && stepId !== recorded.stepId) {
7520
+ return {
7521
+ applicable: true,
7522
+ eligible: false,
7523
+ compatibility: null,
7524
+ blockers: [`Recorded corrective carryover is bound to ${recorded.stepId}, not requested ${stepId}.`],
7525
+ };
7526
+ }
7527
+ return assessRootCauseReplanDirtyCarryover(this.store, repositoryRoot, task, recorded.stepId, {
7528
+ binding: recorded,
7529
+ });
7530
+ }
7531
+ const candidates = task.steps
7532
+ .filter((step) => step.status === 'planned' && (!stepId || step.id === stepId))
7533
+ .flatMap((step) => [...new Set(readRemediationEvents(this.store, task)
7534
+ .filter((event) => event.stepId === step.id && event.failureKind === 'checks-failed')
7535
+ .map((event) => event.failureEvidence.completionCommit))]
7536
+ .map((historyHead) => assessRootCauseReplanDirtyCarryover(this.store, repositoryRoot, task, step.id, { historyHead })))
7537
+ .filter((assessment) => assessment.applicable);
7538
+ const eligible = candidates.filter((assessment) => assessment.eligible && assessment.compatibility);
7539
+ if (eligible.length === 1)
7540
+ return eligible[0];
7541
+ return {
7542
+ applicable: candidates.length > 0,
7543
+ eligible: false,
7544
+ compatibility: null,
7545
+ blockers: [...new Set([
7546
+ ...candidates.flatMap((assessment) => assessment.blockers),
7547
+ ...(eligible.length > 1 ? ['Corrective carryover history resolved to more than one eligible boundary.'] : []),
7548
+ ])],
7549
+ };
7280
7550
  }
7281
7551
  inspectHistoricalStepProvenanceCandidate(snapshot, task, options = {}) {
7282
7552
  const now = this.now().getTime();