codex-workflow-v2 2.0.0-beta.13 → 2.0.0-beta.13.10

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.
Files changed (86) hide show
  1. package/README.md +6 -1
  2. package/dist/reviewer-runtime-build.json +44 -20
  3. package/dist/src/alpha6/failed-step-planning-recovery.d.ts +10 -0
  4. package/dist/src/alpha6/failed-step-planning-recovery.js +93 -0
  5. package/dist/src/alpha6/failed-step-planning-recovery.js.map +1 -0
  6. package/dist/src/alpha6/plan-risk.d.ts +4 -0
  7. package/dist/src/alpha6/plan-risk.js +50 -13
  8. package/dist/src/alpha6/plan-risk.js.map +1 -1
  9. package/dist/src/alpha6/preexecution-replan.d.ts +38 -0
  10. package/dist/src/alpha6/preexecution-replan.js +130 -0
  11. package/dist/src/alpha6/preexecution-replan.js.map +1 -0
  12. package/dist/src/alpha6/remediation.d.ts +4 -2
  13. package/dist/src/alpha6/remediation.js +155 -20
  14. package/dist/src/alpha6/remediation.js.map +1 -1
  15. package/dist/src/alpha6/root-cause-replan-carryover.d.ts +18 -0
  16. package/dist/src/alpha6/root-cause-replan-carryover.js +396 -0
  17. package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -0
  18. package/dist/src/alpha7/autonomy.d.ts +3 -0
  19. package/dist/src/alpha7/autonomy.js +89 -42
  20. package/dist/src/alpha7/autonomy.js.map +1 -1
  21. package/dist/src/change-explanation.d.ts +64 -0
  22. package/dist/src/change-explanation.js +150 -0
  23. package/dist/src/change-explanation.js.map +1 -0
  24. package/dist/src/cli-actions.d.ts +3 -2
  25. package/dist/src/cli-actions.js +6 -0
  26. package/dist/src/cli-actions.js.map +1 -1
  27. package/dist/src/cli.js +73 -1
  28. package/dist/src/cli.js.map +1 -1
  29. package/dist/src/contracts.d.ts +38 -0
  30. package/dist/src/dependency-provenance.d.ts +3 -2
  31. package/dist/src/dependency-provenance.js +33 -2
  32. package/dist/src/dependency-provenance.js.map +1 -1
  33. package/dist/src/domain/plan-semantics.d.ts +11 -0
  34. package/dist/src/domain/plan-semantics.js +49 -0
  35. package/dist/src/domain/plan-semantics.js.map +1 -0
  36. package/dist/src/gateway-handshake.js +2 -0
  37. package/dist/src/gateway-handshake.js.map +1 -1
  38. package/dist/src/lifecycle/corrective-replan.js +3 -3
  39. package/dist/src/lifecycle/corrective-replan.js.map +1 -1
  40. package/dist/src/observed-routes.js +1 -0
  41. package/dist/src/observed-routes.js.map +1 -1
  42. package/dist/src/pending-review-update.d.ts +49 -0
  43. package/dist/src/pending-review-update.js +132 -0
  44. package/dist/src/pending-review-update.js.map +1 -0
  45. package/dist/src/repository.js +16 -4
  46. package/dist/src/repository.js.map +1 -1
  47. package/dist/src/reviewer.js +4 -1
  48. package/dist/src/reviewer.js.map +1 -1
  49. package/dist/src/state/corrective-replan-executor.js +3 -1
  50. package/dist/src/state/corrective-replan-executor.js.map +1 -1
  51. package/dist/src/state/corrective-replan-public.js +5 -4
  52. package/dist/src/state/corrective-replan-public.js.map +1 -1
  53. package/dist/src/state/corrective-yield-executor.js +12 -3
  54. package/dist/src/state/corrective-yield-executor.js.map +1 -1
  55. package/dist/src/state/store.d.ts +1 -1
  56. package/dist/src/state/store.js +17 -3
  57. package/dist/src/state/store.js.map +1 -1
  58. package/dist/src/version.d.ts +1 -1
  59. package/dist/src/version.js +1 -1
  60. package/dist/src/version.js.map +1 -1
  61. package/dist/src/workflow.d.ts +84 -1
  62. package/dist/src/workflow.js +1616 -355
  63. package/dist/src/workflow.js.map +1 -1
  64. package/docs/beta13.2-signal-review-recovery.md +38 -0
  65. package/docs/change-model.md +118 -0
  66. package/docs/delegated-approval.md +26 -0
  67. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  68. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  69. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  70. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +9 -1
  71. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +9 -5
  72. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +18 -3
  73. package/docs/pending-review-update.md +15 -0
  74. package/docs/release.md +267 -2
  75. package/docs/updating-existing-project.md +36 -6
  76. package/docs/validation-report.md +30 -9
  77. package/package.json +2 -1
  78. package/plugins/codex-workflow-gateway/references/chat-dispatch.md +204 -0
  79. package/plugins/codex-workflow-gateway/references/protocol.md +11 -0
  80. package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +298 -0
  81. package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +43 -0
  82. package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +55 -3
  83. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +218 -21
  84. package/schemas/plan-risk-audit-event.schema.json +1 -0
  85. package/schemas/preexecution-replan-event.schema.json +45 -0
  86. package/schemas/task.schema.json +55 -0
@@ -1,10 +1,12 @@
1
1
  import { closeSync, existsSync, openSync, readFileSync, unlinkSync } from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { normalizePlanKnowledgeBinding, readVerifiedPlanArtifact } from '../domain/plan-semantics.js';
3
4
  import { pathAllowed } from '../domain/validation.js';
4
5
  import { WorkflowError } from '../errors.js';
5
6
  import { hashCanonical } from '../lifecycle/fingerprint.js';
6
7
  import { createUlid } from '../ulid.js';
7
8
  import { PACKAGE_VERSION } from '../version.js';
9
+ import { readPlanRiskAuditEvents } from './plan-risk.js';
8
10
  import { assertHash64, canonicalJsonStringify, prepareSidecarAppend, sha256Hex, } from './store-sidecars.js';
9
11
  import { readTaskC1Posture } from './handoff.js';
10
12
  import { buildCurrentStrictStepReviewCycle, latestPassedVerifiedStepReviewEvent, readReviewerAttestations, readStepReviewEvents, } from './review.js';
@@ -83,7 +85,7 @@ function validateRemediationHistory(store, task) {
83
85
  validateRemediationReviewReferences(store, task, remediationEvents);
84
86
  validateCorrectiveDecisionCoverage(task, correctiveDecisionEvents, remediationEvents);
85
87
  const recoveredEventIds = validateRemediationModeRecoveries(store, task, remediationEvents, correctiveDecisionEvents, modeRecoveries);
86
- validateHistoricalRemediationModesForAllSteps(task, remediationEvents, correctiveDecisionEvents, recoveredEventIds, overriddenStopDecisionEventIds);
88
+ validateHistoricalRemediationModesForAllSteps(store, task, remediationEvents, correctiveDecisionEvents, recoveredEventIds, overriddenStopDecisionEventIds);
87
89
  return {
88
90
  remediationEvents,
89
91
  correctiveDecisionEvents,
@@ -161,6 +163,7 @@ export function prepareRemediationEvent(store, task, stepId, failureKind, failur
161
163
  const recoveredDecisionEventId = findCorrectiveRecoveryDecisionIdForFailure(store, task, stepId);
162
164
  const allExisting = readRemediationEvents(store, task);
163
165
  const existing = allExisting.filter((event) => event.stepId === stepId);
166
+ const correctiveDecisions = readCorrectiveDecisionEvents(store, task);
164
167
  const matching = existing.filter((event) => remediationEventMatchesFailure(event, failureKind, failureEvidence));
165
168
  if (matching.length > 1) {
166
169
  throw new WorkflowError('STATE_CORRUPT', 'Multiple remediation events match the same failure identity.', {
@@ -181,10 +184,13 @@ export function prepareRemediationEvent(store, task, stepId, failureKind, failur
181
184
  });
182
185
  }
183
186
  const priorEvents = existing.filter((event) => event.attemptOrdinal < reused.attemptOrdinal);
184
- const gate = deriveAttemptGateFromHistory(task, stepId, planRiskAudit, priorEvents, readCorrectiveDecisionEvents(store, task), {
187
+ const cycle = selectCurrentPlanRemediationCycle(store, task, priorEvents, correctiveDecisions);
188
+ const gate = deriveAttemptGateFromHistory(task, stepId, planRiskAudit, cycle.remediationEvents, cycle.correctiveDecisions, {
185
189
  recoveredDecisionEventId,
186
190
  reboundStopOverrideDecisionEventId,
187
191
  overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
192
+ priorRemediationEvents: cycle.priorRemediationEvents,
193
+ nextAttemptOrdinal: reused.attemptOrdinal,
188
194
  });
189
195
  if (reused.attemptOrdinal !== gate.nextAttemptOrdinal || reused.mode !== gate.mode || reused.guardedCategory !== gate.guardedCategory) {
190
196
  throw new WorkflowError('STATE_CORRUPT', 'Existing remediation event conflicts with the current guarded retry binding.', {
@@ -199,10 +205,13 @@ export function prepareRemediationEvent(store, task, stepId, failureKind, failur
199
205
  }
200
206
  return { event: reused, postimage: null };
201
207
  }
202
- const gate = deriveAttemptGateFromHistory(task, stepId, planRiskAudit, existing, readCorrectiveDecisionEvents(store, task), {
208
+ const cycle = selectCurrentPlanRemediationCycle(store, task, existing, correctiveDecisions);
209
+ const gate = deriveAttemptGateFromHistory(task, stepId, planRiskAudit, cycle.remediationEvents, cycle.correctiveDecisions, {
203
210
  recoveredDecisionEventId,
204
211
  reboundStopOverrideDecisionEventId,
205
212
  overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
213
+ priorRemediationEvents: cycle.priorRemediationEvents,
214
+ nextAttemptOrdinal: existing.length + 1,
206
215
  });
207
216
  const prepared = prepareSidecarAppend(allExisting, {
208
217
  eventId: `RME-${createUlid(now.getTime())}`,
@@ -832,6 +841,8 @@ export function assertGuardedRemediationAttemptAllowed(store, task, stepId, plan
832
841
  recoveredDecisionEventId,
833
842
  reboundStopOverrideDecisionEventId,
834
843
  overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
844
+ priorRemediationEvents: history.priorRemediationEvents,
845
+ nextAttemptOrdinal: history.nextAttemptOrdinal,
835
846
  });
836
847
  }
837
848
  export function readGuardedRemediationPostureForStep(store, task, stepId, planRiskAudit) {
@@ -849,11 +860,16 @@ export function readGuardedRemediationPostureForStep(store, task, stepId, planRi
849
860
  const earlyPlanIntegrityDecision = remediationEvents.length === 1
850
861
  ? findCurrentCorrectiveDecision(task, stepId, 2, history.correctiveDecisions)
851
862
  : null;
852
- if (remediationEvents.length < 2 && earlyPlanIntegrityDecision?.recoveryBasis !== 'plan-integrity')
863
+ const currentDecision = findCurrentCorrectiveDecision(task, stepId, history.nextAttemptOrdinal, history.correctiveDecisions);
864
+ if (remediationEvents.length < 2 && earlyPlanIntegrityDecision?.recoveryBasis !== 'plan-integrity'
865
+ && currentDecision === null
866
+ && deriveCurrentCauseDecision(remediationEvents, history.priorRemediationEvents) !== 'root-cause-replan')
853
867
  return null;
854
868
  if (step.status === 'failed' || step.status === 'planned') {
855
869
  return deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEvents, history.correctiveDecisions, {
856
870
  overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
871
+ priorRemediationEvents: history.priorRemediationEvents,
872
+ nextAttemptOrdinal: history.nextAttemptOrdinal,
857
873
  });
858
874
  }
859
875
  if (task.status !== 'blocked' || step.status !== 'in_progress' || !step.evidence)
@@ -863,19 +879,99 @@ export function readGuardedRemediationPostureForStep(store, task, stepId, planRi
863
879
  return null;
864
880
  return deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEvents, history.correctiveDecisions, {
865
881
  overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
882
+ priorRemediationEvents: history.priorRemediationEvents,
883
+ nextAttemptOrdinal: history.nextAttemptOrdinal,
866
884
  });
867
885
  }
868
886
  function readOpenGuardedRemediationHistory(store, task, stepId) {
869
- const remediationEvents = readRemediationEvents(store, task).filter((event) => event.stepId === stepId);
870
- const correctiveDecisions = readCorrectiveDecisionEvents(store, task).filter((event) => event.stepId === stepId);
887
+ const allStepRemediations = readRemediationEvents(store, task).filter((event) => event.stepId === stepId);
888
+ const allStepDecisions = readCorrectiveDecisionEvents(store, task).filter((event) => event.stepId === stepId);
871
889
  const passedBoundary = latestPassedVerifiedStepReviewEvent(store, task, stepId);
872
- if (!passedBoundary)
873
- return { remediationEvents, correctiveDecisions };
890
+ const remediationEvents = passedBoundary
891
+ ? allStepRemediations.filter((event) => event.recordedAt > passedBoundary.recordedAt)
892
+ : allStepRemediations;
893
+ const correctiveDecisions = passedBoundary
894
+ ? allStepDecisions.filter((event) => event.recordedAt > passedBoundary.recordedAt)
895
+ : allStepDecisions;
896
+ return {
897
+ ...selectCurrentPlanRemediationCycle(store, task, remediationEvents, correctiveDecisions),
898
+ nextAttemptOrdinal: allStepRemediations.length + 1,
899
+ };
900
+ }
901
+ function selectCurrentPlanRemediationCycle(store, task, remediationEvents, correctiveDecisions) {
902
+ const unchanged = () => ({
903
+ remediationEvents: [...remediationEvents],
904
+ priorRemediationEvents: [],
905
+ correctiveDecisions: [...correctiveDecisions],
906
+ });
907
+ if (remediationEvents.length === 0 || !task.planHash)
908
+ return unchanged();
909
+ const audits = readPlanRiskAuditEvents(store, task);
910
+ const currentAudit = [...audits].reverse().find((event) => event.planHash === task.planHash
911
+ && event.taskRevision <= task.revision
912
+ && event.decision !== 'split-required'
913
+ && event.decision !== 'stop-escalate') ?? null;
914
+ if (!currentAudit)
915
+ return unchanged();
916
+ const taskRoot = store.taskRoot(task.projectId, task.id);
917
+ let currentSemanticHash;
918
+ try {
919
+ currentSemanticHash = normalizePlanKnowledgeBinding(readVerifiedPlanArtifact(taskRoot, task.planHash, task.planHash), 'current').semanticHash;
920
+ }
921
+ catch {
922
+ return unchanged();
923
+ }
924
+ const reviews = readStepReviewEvents(store, task);
925
+ const bindings = remediationEvents.map((event) => resolveRemediationPlanCycleBinding(taskRoot, task.planHash, event, reviews, audits));
926
+ if (bindings.some((binding) => binding === null))
927
+ return unchanged();
928
+ let cycleStart = remediationEvents.length;
929
+ while (cycleStart > 0 && bindings[cycleStart - 1].semanticHash === currentSemanticHash)
930
+ cycleStart -= 1;
931
+ if (cycleStart === remediationEvents.length
932
+ && currentAudit.taskRevision <= bindings.at(-1).sourceTaskRevision)
933
+ return unchanged();
874
934
  return {
875
- remediationEvents: remediationEvents.filter((event) => event.recordedAt > passedBoundary.recordedAt),
876
- correctiveDecisions: correctiveDecisions.filter((event) => event.recordedAt > passedBoundary.recordedAt),
935
+ remediationEvents: remediationEvents.slice(cycleStart),
936
+ priorRemediationEvents: remediationEvents.slice(0, cycleStart),
937
+ // Corrective decisions remain globally authoritative. The semantic cycle scopes
938
+ // cause counting only; filtering decisions could discard stop/split authority.
939
+ correctiveDecisions: [...correctiveDecisions],
877
940
  };
878
941
  }
942
+ function resolveRemediationPlanCycleBinding(taskRoot, currentPlanHash, event, reviews, audits) {
943
+ let planHash = null;
944
+ let sourceTaskRevision = null;
945
+ if (event.failureEvidence.reviewEventHash) {
946
+ const review = reviews.find((candidate) => candidate.eventHash === event.failureEvidence.reviewEventHash) ?? null;
947
+ if (!review)
948
+ return null;
949
+ planHash = review.planHash;
950
+ sourceTaskRevision = review.taskRevision;
951
+ }
952
+ else if (event.failureEvidence.executionTaskRevision !== undefined) {
953
+ sourceTaskRevision = event.failureEvidence.executionTaskRevision;
954
+ planHash = [...audits].reverse().find((audit) => audit.taskRevision <= sourceTaskRevision)?.planHash ?? null;
955
+ }
956
+ if (!planHash || sourceTaskRevision === null
957
+ || !audits.some((audit) => audit.planHash === planHash && audit.taskRevision <= sourceTaskRevision))
958
+ return null;
959
+ try {
960
+ return {
961
+ semanticHash: normalizePlanKnowledgeBinding(readVerifiedPlanArtifact(taskRoot, planHash, currentPlanHash), 'remediation ' + event.eventId).semanticHash,
962
+ planHash,
963
+ sourceTaskRevision,
964
+ };
965
+ }
966
+ catch {
967
+ return null;
968
+ }
969
+ }
970
+ export function deriveCurrentCauseDecisionForTask(store, task, stepId, remediationEvents) {
971
+ const decisions = readCorrectiveDecisionEvents(store, task).filter((event) => event.stepId === stepId);
972
+ const cycle = selectCurrentPlanRemediationCycle(store, task, remediationEvents, decisions);
973
+ return deriveCurrentCauseDecision(cycle.remediationEvents, cycle.priorRemediationEvents);
974
+ }
879
975
  export function findCurrentGuardedRemediationPosture(store, task, planRiskAudit) {
880
976
  const matches = planRiskAudit.reviewRequiredStepIds
881
977
  .map((stepId) => readGuardedRemediationPostureForStep(store, task, stepId, planRiskAudit))
@@ -899,10 +995,10 @@ export function findFailedGuardedStepRequiringCorrectiveDecision(store, task, pl
899
995
  : null;
900
996
  }
901
997
  function deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEvents, correctiveDecisions, options = {}) {
902
- const attemptCount = remediationEvents.length;
903
- const nextAttemptOrdinal = attemptCount + 1;
998
+ const attemptCount = remediationEvents.length + (options.priorRemediationEvents?.length ?? 0);
999
+ const nextAttemptOrdinal = options.nextAttemptOrdinal ?? attemptCount + 1;
904
1000
  const guardedCategory = requireGuardedCategory(planRiskAudit, stepId);
905
- const rollingCauseDecision = deriveRollingCauseDecision(remediationEvents);
1001
+ const rollingCauseDecision = deriveCurrentCauseDecision(remediationEvents, options.priorRemediationEvents ?? []);
906
1002
  const currentCorrectiveDecision = findCurrentCorrectiveDecision(task, stepId, nextAttemptOrdinal, correctiveDecisions);
907
1003
  const planIntegrityDecision = currentCorrectiveDecision;
908
1004
  if (planIntegrityDecision?.recoveryBasis === 'plan-integrity'
@@ -954,7 +1050,7 @@ function deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEv
954
1050
  if (rollingCauseDecision !== null
955
1051
  && !(rollingCauseDecision === 'same-task-fix' && currentCorrectiveDecision)) {
956
1052
  if (rollingCauseDecision === 'root-cause-replan' && options.enforceRuntimeGate) {
957
- throw new WorkflowError('TRANSITION_BLOCKED', `Guarded Step ${stepId} repeated the same authoritative cause after a claimed fix and requires root-cause replan.`, { taskId: task.id, stepId, attemptCount, requiredAction: 'task plan-set' });
1053
+ throw new WorkflowError('TRANSITION_BLOCKED', `Guarded Step ${stepId} has an explicit Plan conflict or repeated authoritative cause and requires replan.`, { taskId: task.id, stepId, attemptCount, requiredAction: 'task plan-set' });
958
1054
  }
959
1055
  return {
960
1056
  stepId,
@@ -1071,12 +1167,25 @@ function deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEv
1071
1167
  posture: correctiveDecision.decision === 'continue-fix' ? 'continue-fix' : correctiveDecision.decision,
1072
1168
  };
1073
1169
  }
1074
- export function deriveRollingCauseDecision(remediationEvents) {
1170
+ // Current execution must respect a proved Plan obstruction immediately. Keep the
1171
+ // historical rolling classifier unchanged: older valid histories may have been
1172
+ // recorded before this gate existed and must not be reclassified as corrupt.
1173
+ export function deriveCurrentCauseDecision(remediationEvents, priorRemediationEvents = []) {
1174
+ // A verified replacement clears the active cause, not the global attempt
1175
+ // count or an explicit corrective decision's dirty-work continuation authority.
1176
+ if (remediationEvents.length === 0 && priorRemediationEvents.length > 0
1177
+ && priorRemediationEvents.every((event) => event.causeBindings !== undefined))
1178
+ return 'same-task-fix';
1179
+ if (remediationEvents.at(-1)?.causeBindings?.some(cause => cause.authority === 'external-reviewer' && cause.planConflict))
1180
+ return 'root-cause-replan';
1181
+ return deriveRollingCauseDecision(remediationEvents, priorRemediationEvents);
1182
+ }
1183
+ export function deriveRollingCauseDecision(remediationEvents, priorRemediationEvents = []) {
1075
1184
  if (remediationEvents.length === 0 || remediationEvents.some((event) => event.causeBindings === undefined))
1076
1185
  return null;
1077
1186
  const latest = remediationEvents.at(-1);
1078
- const priorFingerprints = new Set(remediationEvents.slice(0, -1)
1079
- .flatMap((event) => event.causeBindings.map((cause) => cause.causeFingerprint)));
1187
+ const priorFingerprints = new Set([...priorRemediationEvents, ...remediationEvents.slice(0, -1)]
1188
+ .flatMap((event) => event.causeBindings?.map((cause) => cause.causeFingerprint) ?? []));
1080
1189
  const authoritativeRepeat = latest.causeBindings.some((cause) => priorFingerprints.has(cause.causeFingerprint)
1081
1190
  && (cause.authority === 'mechanical' || (cause.authority === 'external-reviewer' && cause.planConflict)));
1082
1191
  return authoritativeRepeat ? 'root-cause-replan' : 'same-task-fix';
@@ -1986,10 +2095,36 @@ function validateCorrectiveDecisionCoverage(task, decisions, remediationEvents)
1986
2095
  }
1987
2096
  }
1988
2097
  }
1989
- function validateHistoricalRemediationModes(task, stepId, remediationEvents, correctiveDecisions, recoveredEventIds, overriddenStopDecisionEventIds) {
2098
+ function validateHistoricalRemediationModes(store, task, stepId, remediationEvents, correctiveDecisions, recoveredEventIds, overriddenStopDecisionEventIds) {
1990
2099
  const causeAwareHistory = remediationEvents.length > 0
1991
2100
  && remediationEvents.every((event) => event.causeBindings !== undefined);
1992
2101
  if (causeAwareHistory) {
2102
+ const audits = readPlanRiskAuditEvents(store, task);
2103
+ const reviews = readStepReviewEvents(store, task);
2104
+ const taskRoot = store.taskRoot(task.projectId, task.id);
2105
+ const cycleBindings = task.planHash
2106
+ ? remediationEvents.map((event) => resolveRemediationPlanCycleBinding(taskRoot, task.planHash, event, reviews, audits))
2107
+ : remediationEvents.map(() => null);
2108
+ if (cycleBindings.every((binding) => binding !== null)) {
2109
+ let cycleStart = 0;
2110
+ for (let index = 0; index < remediationEvents.length; index += 1) {
2111
+ if (index > 0 && cycleBindings[index].semanticHash !== cycleBindings[index - 1].semanticHash) {
2112
+ cycleStart = index;
2113
+ }
2114
+ const cyclePrefix = remediationEvents.slice(cycleStart, index + 1);
2115
+ const priorCycles = remediationEvents.slice(0, cycleStart);
2116
+ if (deriveRollingCauseDecision(cyclePrefix, priorCycles) === 'root-cause-replan'
2117
+ && index < remediationEvents.length - 1
2118
+ && cycleBindings[index + 1].semanticHash === cycleBindings[index].semanticHash) {
2119
+ throw new WorkflowError('STATE_CORRUPT', 'Remediation continued after an authoritative cause required semantic replan.', {
2120
+ taskId: task.id,
2121
+ stepId,
2122
+ eventId: remediationEvents[index].eventId,
2123
+ });
2124
+ }
2125
+ }
2126
+ return;
2127
+ }
1993
2128
  for (let index = 0; index < remediationEvents.length; index += 1) {
1994
2129
  const prefix = remediationEvents.slice(0, index + 1);
1995
2130
  if (deriveRollingCauseDecision(prefix) === 'root-cause-replan' && index < remediationEvents.length - 1) {
@@ -2044,13 +2179,13 @@ function validateHistoricalRemediationModes(task, stepId, remediationEvents, cor
2044
2179
  }
2045
2180
  }
2046
2181
  }
2047
- function validateHistoricalRemediationModesForAllSteps(task, remediationEvents, correctiveDecisions, recoveredEventIds, overriddenStopDecisionEventIds) {
2182
+ function validateHistoricalRemediationModesForAllSteps(store, task, remediationEvents, correctiveDecisions, recoveredEventIds, overriddenStopDecisionEventIds) {
2048
2183
  const stepIds = new Set(remediationEvents.map((event) => event.stepId));
2049
2184
  for (const stepId of stepIds) {
2050
2185
  const stepEvents = remediationEvents
2051
2186
  .filter((event) => event.stepId === stepId)
2052
2187
  .sort((left, right) => left.attemptOrdinal - right.attemptOrdinal);
2053
- validateHistoricalRemediationModes(task, stepId, stepEvents, correctiveDecisions, recoveredEventIds, overriddenStopDecisionEventIds);
2188
+ validateHistoricalRemediationModes(store, task, stepId, stepEvents, correctiveDecisions, recoveredEventIds, overriddenStopDecisionEventIds);
2054
2189
  }
2055
2190
  }
2056
2191
  function validateCorrectiveDecisionAudit(audit) {