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.
- package/README.md +6 -1
- package/dist/reviewer-runtime-build.json +44 -20
- package/dist/src/alpha6/failed-step-planning-recovery.d.ts +10 -0
- package/dist/src/alpha6/failed-step-planning-recovery.js +93 -0
- package/dist/src/alpha6/failed-step-planning-recovery.js.map +1 -0
- package/dist/src/alpha6/plan-risk.d.ts +4 -0
- package/dist/src/alpha6/plan-risk.js +50 -13
- package/dist/src/alpha6/plan-risk.js.map +1 -1
- package/dist/src/alpha6/preexecution-replan.d.ts +38 -0
- package/dist/src/alpha6/preexecution-replan.js +130 -0
- package/dist/src/alpha6/preexecution-replan.js.map +1 -0
- package/dist/src/alpha6/remediation.d.ts +4 -2
- package/dist/src/alpha6/remediation.js +155 -20
- package/dist/src/alpha6/remediation.js.map +1 -1
- 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/alpha7/autonomy.d.ts +3 -0
- package/dist/src/alpha7/autonomy.js +89 -42
- package/dist/src/alpha7/autonomy.js.map +1 -1
- package/dist/src/change-explanation.d.ts +64 -0
- package/dist/src/change-explanation.js +150 -0
- package/dist/src/change-explanation.js.map +1 -0
- package/dist/src/cli-actions.d.ts +3 -2
- package/dist/src/cli-actions.js +6 -0
- package/dist/src/cli-actions.js.map +1 -1
- package/dist/src/cli.js +73 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +38 -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/domain/plan-semantics.d.ts +11 -0
- package/dist/src/domain/plan-semantics.js +49 -0
- package/dist/src/domain/plan-semantics.js.map +1 -0
- package/dist/src/gateway-handshake.js +2 -0
- package/dist/src/gateway-handshake.js.map +1 -1
- package/dist/src/lifecycle/corrective-replan.js +3 -3
- package/dist/src/lifecycle/corrective-replan.js.map +1 -1
- package/dist/src/observed-routes.js +1 -0
- package/dist/src/observed-routes.js.map +1 -1
- package/dist/src/pending-review-update.d.ts +49 -0
- package/dist/src/pending-review-update.js +132 -0
- package/dist/src/pending-review-update.js.map +1 -0
- package/dist/src/repository.js +16 -4
- package/dist/src/repository.js.map +1 -1
- package/dist/src/reviewer.js +4 -1
- package/dist/src/reviewer.js.map +1 -1
- package/dist/src/state/corrective-replan-executor.js +3 -1
- package/dist/src/state/corrective-replan-executor.js.map +1 -1
- package/dist/src/state/corrective-replan-public.js +5 -4
- package/dist/src/state/corrective-replan-public.js.map +1 -1
- package/dist/src/state/corrective-yield-executor.js +12 -3
- package/dist/src/state/corrective-yield-executor.js.map +1 -1
- package/dist/src/state/store.d.ts +1 -1
- package/dist/src/state/store.js +17 -3
- package/dist/src/state/store.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/src/workflow.d.ts +84 -1
- package/dist/src/workflow.js +1616 -355
- package/dist/src/workflow.js.map +1 -1
- package/docs/beta13.2-signal-review-recovery.md +38 -0
- package/docs/change-model.md +118 -0
- package/docs/delegated-approval.md +26 -0
- 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/pdf/sources/codex-workflow-v2-architecture-ru.md +9 -1
- package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +9 -5
- package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +18 -3
- package/docs/pending-review-update.md +15 -0
- package/docs/release.md +267 -2
- package/docs/updating-existing-project.md +36 -6
- package/docs/validation-report.md +30 -9
- package/package.json +2 -1
- package/plugins/codex-workflow-gateway/references/chat-dispatch.md +204 -0
- package/plugins/codex-workflow-gateway/references/protocol.md +11 -0
- package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +298 -0
- package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +43 -0
- package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +55 -3
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +218 -21
- package/schemas/plan-risk-audit-event.schema.json +1 -0
- package/schemas/preexecution-replan-event.schema.json +45 -0
- 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
|
|
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
|
|
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
|
-
|
|
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
|
|
870
|
-
const
|
|
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
|
-
|
|
873
|
-
|
|
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.
|
|
876
|
-
|
|
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 =
|
|
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}
|
|
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
|
-
|
|
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
|
|
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) {
|