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

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 (34) hide show
  1. package/README.md +1 -1
  2. package/dist/reviewer-runtime-build.json +17 -9
  3. package/dist/src/alpha6/plan-risk.d.ts +1 -0
  4. package/dist/src/alpha6/plan-risk.js +9 -6
  5. package/dist/src/alpha6/plan-risk.js.map +1 -1
  6. package/dist/src/alpha6/preexecution-replan.d.ts +38 -0
  7. package/dist/src/alpha6/preexecution-replan.js +130 -0
  8. package/dist/src/alpha6/preexecution-replan.js.map +1 -0
  9. package/dist/src/alpha6/remediation.d.ts +4 -3
  10. package/dist/src/alpha6/remediation.js +147 -21
  11. package/dist/src/alpha6/remediation.js.map +1 -1
  12. package/dist/src/cli-actions.d.ts +1 -1
  13. package/dist/src/cli-actions.js +1 -0
  14. package/dist/src/cli-actions.js.map +1 -1
  15. package/dist/src/cli.js +44 -0
  16. package/dist/src/cli.js.map +1 -1
  17. package/dist/src/domain/plan-semantics.d.ts +11 -0
  18. package/dist/src/domain/plan-semantics.js +49 -0
  19. package/dist/src/domain/plan-semantics.js.map +1 -0
  20. package/dist/src/version.d.ts +1 -1
  21. package/dist/src/version.js +1 -1
  22. package/dist/src/workflow.d.ts +19 -1
  23. package/dist/src/workflow.js +514 -80
  24. package/dist/src/workflow.js.map +1 -1
  25. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  26. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  27. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  28. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +1 -1
  29. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +2 -2
  30. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +2 -2
  31. package/docs/release.md +43 -0
  32. package/package.json +1 -1
  33. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +35 -2
  34. package/schemas/preexecution-replan-event.schema.json +45 -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,12 +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;
863
+ const currentDecision = findCurrentCorrectiveDecision(task, stepId, history.nextAttemptOrdinal, history.correctiveDecisions);
852
864
  if (remediationEvents.length < 2 && earlyPlanIntegrityDecision?.recoveryBasis !== 'plan-integrity'
853
- && deriveCurrentCauseDecision(remediationEvents) !== 'root-cause-replan')
865
+ && currentDecision === null
866
+ && deriveCurrentCauseDecision(remediationEvents, history.priorRemediationEvents) !== 'root-cause-replan')
854
867
  return null;
855
868
  if (step.status === 'failed' || step.status === 'planned') {
856
869
  return deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEvents, history.correctiveDecisions, {
857
870
  overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
871
+ priorRemediationEvents: history.priorRemediationEvents,
872
+ nextAttemptOrdinal: history.nextAttemptOrdinal,
858
873
  });
859
874
  }
860
875
  if (task.status !== 'blocked' || step.status !== 'in_progress' || !step.evidence)
@@ -864,19 +879,99 @@ export function readGuardedRemediationPostureForStep(store, task, stepId, planRi
864
879
  return null;
865
880
  return deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEvents, history.correctiveDecisions, {
866
881
  overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
882
+ priorRemediationEvents: history.priorRemediationEvents,
883
+ nextAttemptOrdinal: history.nextAttemptOrdinal,
867
884
  });
868
885
  }
869
886
  function readOpenGuardedRemediationHistory(store, task, stepId) {
870
- const remediationEvents = readRemediationEvents(store, task).filter((event) => event.stepId === stepId);
871
- 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);
872
889
  const passedBoundary = latestPassedVerifiedStepReviewEvent(store, task, stepId);
873
- if (!passedBoundary)
874
- 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;
875
896
  return {
876
- remediationEvents: remediationEvents.filter((event) => event.recordedAt > passedBoundary.recordedAt),
877
- correctiveDecisions: correctiveDecisions.filter((event) => event.recordedAt > passedBoundary.recordedAt),
897
+ ...selectCurrentPlanRemediationCycle(store, task, remediationEvents, correctiveDecisions),
898
+ nextAttemptOrdinal: allStepRemediations.length + 1,
878
899
  };
879
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();
934
+ return {
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],
940
+ };
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
+ }
880
975
  export function findCurrentGuardedRemediationPosture(store, task, planRiskAudit) {
881
976
  const matches = planRiskAudit.reviewRequiredStepIds
882
977
  .map((stepId) => readGuardedRemediationPostureForStep(store, task, stepId, planRiskAudit))
@@ -900,10 +995,10 @@ export function findFailedGuardedStepRequiringCorrectiveDecision(store, task, pl
900
995
  : null;
901
996
  }
902
997
  function deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEvents, correctiveDecisions, options = {}) {
903
- const attemptCount = remediationEvents.length;
904
- const nextAttemptOrdinal = attemptCount + 1;
998
+ const attemptCount = remediationEvents.length + (options.priorRemediationEvents?.length ?? 0);
999
+ const nextAttemptOrdinal = options.nextAttemptOrdinal ?? attemptCount + 1;
905
1000
  const guardedCategory = requireGuardedCategory(planRiskAudit, stepId);
906
- const rollingCauseDecision = deriveCurrentCauseDecision(remediationEvents);
1001
+ const rollingCauseDecision = deriveCurrentCauseDecision(remediationEvents, options.priorRemediationEvents ?? []);
907
1002
  const currentCorrectiveDecision = findCurrentCorrectiveDecision(task, stepId, nextAttemptOrdinal, correctiveDecisions);
908
1003
  const planIntegrityDecision = currentCorrectiveDecision;
909
1004
  if (planIntegrityDecision?.recoveryBasis === 'plan-integrity'
@@ -1075,17 +1170,22 @@ function deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEv
1075
1170
  // Current execution must respect a proved Plan obstruction immediately. Keep the
1076
1171
  // historical rolling classifier unchanged: older valid histories may have been
1077
1172
  // recorded before this gate existed and must not be reclassified as corrupt.
1078
- export function deriveCurrentCauseDecision(remediationEvents) {
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';
1079
1179
  if (remediationEvents.at(-1)?.causeBindings?.some(cause => cause.authority === 'external-reviewer' && cause.planConflict))
1080
1180
  return 'root-cause-replan';
1081
- return deriveRollingCauseDecision(remediationEvents);
1181
+ return deriveRollingCauseDecision(remediationEvents, priorRemediationEvents);
1082
1182
  }
1083
- export function deriveRollingCauseDecision(remediationEvents) {
1183
+ export function deriveRollingCauseDecision(remediationEvents, priorRemediationEvents = []) {
1084
1184
  if (remediationEvents.length === 0 || remediationEvents.some((event) => event.causeBindings === undefined))
1085
1185
  return null;
1086
1186
  const latest = remediationEvents.at(-1);
1087
- const priorFingerprints = new Set(remediationEvents.slice(0, -1)
1088
- .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) ?? []));
1089
1189
  const authoritativeRepeat = latest.causeBindings.some((cause) => priorFingerprints.has(cause.causeFingerprint)
1090
1190
  && (cause.authority === 'mechanical' || (cause.authority === 'external-reviewer' && cause.planConflict)));
1091
1191
  return authoritativeRepeat ? 'root-cause-replan' : 'same-task-fix';
@@ -1995,10 +2095,36 @@ function validateCorrectiveDecisionCoverage(task, decisions, remediationEvents)
1995
2095
  }
1996
2096
  }
1997
2097
  }
1998
- function validateHistoricalRemediationModes(task, stepId, remediationEvents, correctiveDecisions, recoveredEventIds, overriddenStopDecisionEventIds) {
2098
+ function validateHistoricalRemediationModes(store, task, stepId, remediationEvents, correctiveDecisions, recoveredEventIds, overriddenStopDecisionEventIds) {
1999
2099
  const causeAwareHistory = remediationEvents.length > 0
2000
2100
  && remediationEvents.every((event) => event.causeBindings !== undefined);
2001
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
+ }
2002
2128
  for (let index = 0; index < remediationEvents.length; index += 1) {
2003
2129
  const prefix = remediationEvents.slice(0, index + 1);
2004
2130
  if (deriveRollingCauseDecision(prefix) === 'root-cause-replan' && index < remediationEvents.length - 1) {
@@ -2053,13 +2179,13 @@ function validateHistoricalRemediationModes(task, stepId, remediationEvents, cor
2053
2179
  }
2054
2180
  }
2055
2181
  }
2056
- function validateHistoricalRemediationModesForAllSteps(task, remediationEvents, correctiveDecisions, recoveredEventIds, overriddenStopDecisionEventIds) {
2182
+ function validateHistoricalRemediationModesForAllSteps(store, task, remediationEvents, correctiveDecisions, recoveredEventIds, overriddenStopDecisionEventIds) {
2057
2183
  const stepIds = new Set(remediationEvents.map((event) => event.stepId));
2058
2184
  for (const stepId of stepIds) {
2059
2185
  const stepEvents = remediationEvents
2060
2186
  .filter((event) => event.stepId === stepId)
2061
2187
  .sort((left, right) => left.attemptOrdinal - right.attemptOrdinal);
2062
- validateHistoricalRemediationModes(task, stepId, stepEvents, correctiveDecisions, recoveredEventIds, overriddenStopDecisionEventIds);
2188
+ validateHistoricalRemediationModes(store, task, stepId, stepEvents, correctiveDecisions, recoveredEventIds, overriddenStopDecisionEventIds);
2063
2189
  }
2064
2190
  }
2065
2191
  function validateCorrectiveDecisionAudit(audit) {