codex-workflow-v2 2.0.0-beta.13.11 → 2.0.0-beta.13.13
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 +1 -1
- package/dist/reviewer-runtime-build.json +21 -9
- package/dist/src/alpha6/component-owner.d.ts +15 -1
- package/dist/src/alpha6/component-owner.js +44 -2
- package/dist/src/alpha6/component-owner.js.map +1 -1
- package/dist/src/alpha6/literal-test-invocation.d.ts +2 -0
- package/dist/src/alpha6/literal-test-invocation.js +106 -0
- package/dist/src/alpha6/literal-test-invocation.js.map +1 -0
- package/dist/src/alpha6/plan-integrity-update.d.ts +6 -0
- package/dist/src/alpha6/plan-integrity-update.js +17 -0
- package/dist/src/alpha6/plan-integrity-update.js.map +1 -0
- package/dist/src/alpha6/plan-integrity.js +7 -5
- package/dist/src/alpha6/plan-integrity.js.map +1 -1
- package/dist/src/alpha6/remediation.d.ts +5 -0
- package/dist/src/alpha6/remediation.js +150 -33
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/contracts.d.ts +7 -0
- package/dist/src/navigation-actions.d.ts +9 -0
- package/dist/src/navigation-actions.js +72 -0
- package/dist/src/navigation-actions.js.map +1 -0
- package/dist/src/state/corrective-replan-executor.d.ts +10 -0
- package/dist/src/state/corrective-replan-executor.js +37 -1
- package/dist/src/state/corrective-replan-executor.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/workflow.d.ts +3 -0
- package/dist/src/workflow.js +66 -9
- package/dist/src/workflow.js.map +1 -1
- package/docs/lifecycle-capture.md +34 -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 +37 -1
- package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +36 -2
- package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +38 -2
- package/docs/release.md +57 -0
- package/docs/stable-release-defect-register.md +26 -1
- package/docs/updating-existing-project.md +45 -0
- package/package.json +1 -1
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +17 -0
- package/schemas/corrective-decision-event.schema.json +3 -1
|
@@ -1,6 +1,6 @@
|
|
|
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
|
+
import { assertKnowledgeBindingOnlyPlanChange, normalizePlanKnowledgeBinding, readVerifiedPlanArtifact } from '../domain/plan-semantics.js';
|
|
4
4
|
import { pathAllowed } from '../domain/validation.js';
|
|
5
5
|
import { WorkflowError } from '../errors.js';
|
|
6
6
|
import { hashCanonical } from '../lifecycle/fingerprint.js';
|
|
@@ -10,7 +10,8 @@ import { readPlanRiskAuditEvents } from './plan-risk.js';
|
|
|
10
10
|
import { assertHash64, canonicalJsonStringify, prepareSidecarAppend, sha256Hex, } from './store-sidecars.js';
|
|
11
11
|
import { readTaskC1Posture } from './handoff.js';
|
|
12
12
|
import { buildCurrentStrictStepReviewCycle, latestPassedVerifiedStepReviewEvent, readReviewerAttestations, readStepReviewEvents, } from './review.js';
|
|
13
|
-
import {
|
|
13
|
+
import { correctiveReplanBoundaryMatchesStep, readCompletedCorrectiveReplanHistory } from '../state/corrective-replan-executor.js';
|
|
14
|
+
import { semanticBoundaryHash, validLiteralTestInvocationEvidence } from './component-owner.js';
|
|
14
15
|
import { reviewerCauseFingerprint } from './remediation-cause.js';
|
|
15
16
|
export const REMEDIATION_EVENTS_SIDECAR = 'remediation-events.jsonl';
|
|
16
17
|
export const CORRECTIVE_DECISIONS_SIDECAR = 'corrective-decisions.jsonl';
|
|
@@ -83,7 +84,7 @@ function validateRemediationHistory(store, task) {
|
|
|
83
84
|
const overriddenStopDecisionEventIds = validateStopEscalateOverrideHistory(task, remediationEvents, correctiveDecisionEvents, readRawStopEscalateOverrideEvents(store, task));
|
|
84
85
|
validateRemediationAttemptOrdinals(remediationEvents, task);
|
|
85
86
|
validateRemediationReviewReferences(store, task, remediationEvents);
|
|
86
|
-
validateCorrectiveDecisionCoverage(task, correctiveDecisionEvents, remediationEvents);
|
|
87
|
+
validateCorrectiveDecisionCoverage(store, task, correctiveDecisionEvents, remediationEvents);
|
|
87
88
|
const recoveredEventIds = validateRemediationModeRecoveries(store, task, remediationEvents, correctiveDecisionEvents, modeRecoveries);
|
|
88
89
|
validateHistoricalRemediationModesForAllSteps(store, task, remediationEvents, correctiveDecisionEvents, recoveredEventIds, overriddenStopDecisionEventIds);
|
|
89
90
|
return {
|
|
@@ -139,7 +140,7 @@ export function readStopEscalateOverrideEvents(store, task) {
|
|
|
139
140
|
const decisions = readRawCorrectiveDecisionEvents(store, task);
|
|
140
141
|
const overrides = readRawStopEscalateOverrideEvents(store, task);
|
|
141
142
|
validateRemediationAttemptOrdinals(remediations, task);
|
|
142
|
-
validateCorrectiveDecisionCoverage(task, decisions, remediations);
|
|
143
|
+
validateCorrectiveDecisionCoverage(store, task, decisions, remediations);
|
|
143
144
|
validateStopEscalateOverrideHistory(task, remediations, decisions, overrides);
|
|
144
145
|
return overrides;
|
|
145
146
|
}
|
|
@@ -148,7 +149,7 @@ export function stopEscalateOverrideDecisionEventIds(store, task) {
|
|
|
148
149
|
const decisions = readRawCorrectiveDecisionEvents(store, task);
|
|
149
150
|
const overrides = readRawStopEscalateOverrideEvents(store, task);
|
|
150
151
|
validateRemediationAttemptOrdinals(remediations, task);
|
|
151
|
-
validateCorrectiveDecisionCoverage(task, decisions, remediations);
|
|
152
|
+
validateCorrectiveDecisionCoverage(store, task, decisions, remediations);
|
|
152
153
|
return validateStopEscalateOverrideHistory(task, remediations, decisions, overrides);
|
|
153
154
|
}
|
|
154
155
|
export function appendRemediationEvent(store, task, stepId, failureKind, failureEvidence, planRiskAudit, now, reboundStopOverrideDecisionEventId = null, causeBindings = []) {
|
|
@@ -185,7 +186,7 @@ export function prepareRemediationEvent(store, task, stepId, failureKind, failur
|
|
|
185
186
|
}
|
|
186
187
|
const priorEvents = existing.filter((event) => event.attemptOrdinal < reused.attemptOrdinal);
|
|
187
188
|
const cycle = selectCurrentPlanRemediationCycle(store, task, priorEvents, correctiveDecisions);
|
|
188
|
-
const gate = deriveAttemptGateFromHistory(task, stepId, planRiskAudit, cycle.remediationEvents, cycle.correctiveDecisions, {
|
|
189
|
+
const gate = deriveAttemptGateFromHistory(store, task, stepId, planRiskAudit, cycle.remediationEvents, cycle.correctiveDecisions, {
|
|
189
190
|
recoveredDecisionEventId,
|
|
190
191
|
reboundStopOverrideDecisionEventId,
|
|
191
192
|
overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
|
|
@@ -206,7 +207,7 @@ export function prepareRemediationEvent(store, task, stepId, failureKind, failur
|
|
|
206
207
|
return { event: reused, postimage: null };
|
|
207
208
|
}
|
|
208
209
|
const cycle = selectCurrentPlanRemediationCycle(store, task, existing, correctiveDecisions);
|
|
209
|
-
const gate = deriveAttemptGateFromHistory(task, stepId, planRiskAudit, cycle.remediationEvents, cycle.correctiveDecisions, {
|
|
210
|
+
const gate = deriveAttemptGateFromHistory(store, task, stepId, planRiskAudit, cycle.remediationEvents, cycle.correctiveDecisions, {
|
|
210
211
|
recoveredDecisionEventId,
|
|
211
212
|
reboundStopOverrideDecisionEventId,
|
|
212
213
|
overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
|
|
@@ -290,7 +291,7 @@ export function assessRemediationModeRecovery(store, task, stepId, actor, writer
|
|
|
290
291
|
const decisions = readRawCorrectiveDecisionEvents(store, task);
|
|
291
292
|
validateRemediationAttemptOrdinals(remediations, task);
|
|
292
293
|
validateRemediationReviewReferences(store, task, remediations);
|
|
293
|
-
validateCorrectiveDecisionCoverage(task, decisions, remediations);
|
|
294
|
+
validateCorrectiveDecisionCoverage(store, task, decisions, remediations);
|
|
294
295
|
const malformed = remediations.filter((event) => event.attemptOrdinal > 2 && event.mode === 'ordinary');
|
|
295
296
|
if (malformed.length !== 1 || malformed[0].stepId !== stepId || malformed[0].attemptOrdinal !== 3) {
|
|
296
297
|
throw new WorkflowError('STATE_CORRUPT', 'State does not contain the one exact alpha.7.2 remediation mode omission.', {
|
|
@@ -766,7 +767,11 @@ export function appendPlanIntegrityRecoveryDecision(store, task, stepId, auditor
|
|
|
766
767
|
if (!auditor.trim()) {
|
|
767
768
|
throw new WorkflowError('INVALID_ARGUMENT', 'Plan-integrity recovery requires a derived auditor identity.');
|
|
768
769
|
}
|
|
769
|
-
const
|
|
770
|
+
const allRemediationEvents = readRemediationEvents(store, task).filter(event => event.stepId === stepId);
|
|
771
|
+
const support = evidence.kind === 'check-support-anchor-outside-allowed-writes' || evidence.kind === 'failed-check-component-owner-outside-allowed-writes';
|
|
772
|
+
const remediationEvents = support ? selectPlanIntegrityRemediations(store, task, allRemediationEvents, readCorrectiveDecisionEvents(store, task).filter(event => event.stepId === stepId)) : allRemediationEvents;
|
|
773
|
+
if (!remediationEvents)
|
|
774
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Mechanical recovery lacks a verified current Plan cycle.');
|
|
770
775
|
const expectedBindings = evidence.kind === 'missing-npm-script-outside-allowed-writes'
|
|
771
776
|
? [{
|
|
772
777
|
eventId: evidence.remediationEventId,
|
|
@@ -777,8 +782,7 @@ export function appendPlanIntegrityRecoveryDecision(store, task, stepId, auditor
|
|
|
777
782
|
if (remediationEvents.length !== expectedBindings.length
|
|
778
783
|
|| remediationEvents.some((event, index) => {
|
|
779
784
|
const binding = expectedBindings[index];
|
|
780
|
-
return event.
|
|
781
|
-
|| event.failureKind !== 'checks-failed'
|
|
785
|
+
return event.failureKind !== 'checks-failed'
|
|
782
786
|
|| event.eventId !== binding?.eventId
|
|
783
787
|
|| event.eventHash !== binding.eventHash
|
|
784
788
|
|| event.attemptOrdinal !== binding.attemptOrdinal;
|
|
@@ -789,7 +793,7 @@ export function appendPlanIntegrityRecoveryDecision(store, task, stepId, auditor
|
|
|
789
793
|
remediationEventIds: remediationEvents.map((event) => event.eventId),
|
|
790
794
|
});
|
|
791
795
|
}
|
|
792
|
-
const triggeringAttemptCount =
|
|
796
|
+
const triggeringAttemptCount = allRemediationEvents.length + 1;
|
|
793
797
|
const recoveryDecision = evidence.kind === 'check-support-anchor-outside-allowed-writes'
|
|
794
798
|
|| evidence.kind === 'failed-check-component-owner-outside-allowed-writes'
|
|
795
799
|
? 'continue-fix'
|
|
@@ -826,7 +830,7 @@ export function appendPlanIntegrityRecoveryDecision(store, task, stepId, auditor
|
|
|
826
830
|
decision: recoveryDecision,
|
|
827
831
|
auditor: auditor.trim(),
|
|
828
832
|
coveredFindingIds: [findingId],
|
|
829
|
-
coveredEventIds:
|
|
833
|
+
coveredEventIds: allRemediationEvents.map((event) => event.eventId),
|
|
830
834
|
planHash: task.planHash,
|
|
831
835
|
recoveryBasis: 'plan-integrity',
|
|
832
836
|
planIntegrityEvidence: evidence,
|
|
@@ -836,7 +840,7 @@ export function appendPlanIntegrityRecoveryDecision(store, task, stepId, auditor
|
|
|
836
840
|
}
|
|
837
841
|
export function assertGuardedRemediationAttemptAllowed(store, task, stepId, planRiskAudit, recoveredDecisionEventId = null, reboundStopOverrideDecisionEventId = null) {
|
|
838
842
|
const history = readOpenGuardedRemediationHistory(store, task, stepId);
|
|
839
|
-
return deriveAttemptGateFromHistory(task, stepId, planRiskAudit, history.remediationEvents, history.correctiveDecisions, {
|
|
843
|
+
return deriveAttemptGateFromHistory(store, task, stepId, planRiskAudit, history.remediationEvents, history.correctiveDecisions, {
|
|
840
844
|
enforceRuntimeGate: true,
|
|
841
845
|
recoveredDecisionEventId,
|
|
842
846
|
reboundStopOverrideDecisionEventId,
|
|
@@ -866,7 +870,7 @@ export function readGuardedRemediationPostureForStep(store, task, stepId, planRi
|
|
|
866
870
|
&& deriveCurrentCauseDecision(remediationEvents, history.priorRemediationEvents) !== 'root-cause-replan')
|
|
867
871
|
return null;
|
|
868
872
|
if (step.status === 'failed' || step.status === 'planned') {
|
|
869
|
-
return deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEvents, history.correctiveDecisions, {
|
|
873
|
+
return deriveAttemptGateFromHistory(store, task, stepId, planRiskAudit, remediationEvents, history.correctiveDecisions, {
|
|
870
874
|
overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
|
|
871
875
|
priorRemediationEvents: history.priorRemediationEvents,
|
|
872
876
|
nextAttemptOrdinal: history.nextAttemptOrdinal,
|
|
@@ -877,7 +881,7 @@ export function readGuardedRemediationPostureForStep(store, task, stepId, planRi
|
|
|
877
881
|
const currentCycle = buildCurrentStrictStepReviewCycle(store, task, stepId);
|
|
878
882
|
if (currentCycle.resolution !== 'unverified')
|
|
879
883
|
return null;
|
|
880
|
-
return deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEvents, history.correctiveDecisions, {
|
|
884
|
+
return deriveAttemptGateFromHistory(store, task, stepId, planRiskAudit, remediationEvents, history.correctiveDecisions, {
|
|
881
885
|
overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
|
|
882
886
|
priorRemediationEvents: history.priorRemediationEvents,
|
|
883
887
|
nextAttemptOrdinal: history.nextAttemptOrdinal,
|
|
@@ -898,7 +902,7 @@ function readOpenGuardedRemediationHistory(store, task, stepId) {
|
|
|
898
902
|
nextAttemptOrdinal: allStepRemediations.length + 1,
|
|
899
903
|
};
|
|
900
904
|
}
|
|
901
|
-
function selectCurrentPlanRemediationCycle(store, task, remediationEvents, correctiveDecisions) {
|
|
905
|
+
function selectCurrentPlanRemediationCycle(store, task, remediationEvents, correctiveDecisions, canonicalPlanHash = task.planHash) {
|
|
902
906
|
const unchanged = () => ({
|
|
903
907
|
remediationEvents: [...remediationEvents],
|
|
904
908
|
priorRemediationEvents: [],
|
|
@@ -916,13 +920,13 @@ function selectCurrentPlanRemediationCycle(store, task, remediationEvents, corre
|
|
|
916
920
|
const taskRoot = store.taskRoot(task.projectId, task.id);
|
|
917
921
|
let currentSemanticHash;
|
|
918
922
|
try {
|
|
919
|
-
currentSemanticHash = normalizePlanKnowledgeBinding(readVerifiedPlanArtifact(taskRoot, task.planHash,
|
|
923
|
+
currentSemanticHash = normalizePlanKnowledgeBinding(readVerifiedPlanArtifact(taskRoot, task.planHash, canonicalPlanHash), 'current').semanticHash;
|
|
920
924
|
}
|
|
921
925
|
catch {
|
|
922
926
|
return unchanged();
|
|
923
927
|
}
|
|
924
928
|
const reviews = readStepReviewEvents(store, task);
|
|
925
|
-
const bindings = remediationEvents.map((event) => resolveRemediationPlanCycleBinding(taskRoot,
|
|
929
|
+
const bindings = remediationEvents.map((event) => resolveRemediationPlanCycleBinding(taskRoot, canonicalPlanHash, event, reviews, audits));
|
|
926
930
|
if (bindings.some((binding) => binding === null))
|
|
927
931
|
return unchanged();
|
|
928
932
|
let cycleStart = remediationEvents.length;
|
|
@@ -939,6 +943,71 @@ function selectCurrentPlanRemediationCycle(store, task, remediationEvents, corre
|
|
|
939
943
|
correctiveDecisions: [...correctiveDecisions],
|
|
940
944
|
};
|
|
941
945
|
}
|
|
946
|
+
/** Scope mechanical support evidence only; global corrective authority is never discarded. */
|
|
947
|
+
export function selectPlanIntegrityRemediations(store, task, events, decisions, canonicalPlanHash = task.planHash) {
|
|
948
|
+
if (events.length === 0)
|
|
949
|
+
return null;
|
|
950
|
+
const stepId = events[0].stepId;
|
|
951
|
+
if (events.some(event => event.stepId !== stepId) || decisions.some(event => event.stepId !== stepId))
|
|
952
|
+
return null;
|
|
953
|
+
const cycle = selectCurrentPlanRemediationCycle(store, task, events, decisions, canonicalPlanHash);
|
|
954
|
+
if (cycle.priorRemediationEvents.length === 0)
|
|
955
|
+
return decisions.length === 0 ? [...events] : null;
|
|
956
|
+
if (!task.planHash || cycle.remediationEvents.length === 0
|
|
957
|
+
|| decisions.some(d => d.decision === 'stop-escalate' || d.decision === 'split-required'))
|
|
958
|
+
return null;
|
|
959
|
+
const root = store.taskRoot(task.projectId, task.id);
|
|
960
|
+
const history = readCompletedCorrectiveReplanHistory(store, task);
|
|
961
|
+
const semantic = (hash) => {
|
|
962
|
+
const saved = history.flatMap(boundary => [boundary.beforePlan, boundary.afterPlan]).find(bytes => sha256Hex(bytes) === hash);
|
|
963
|
+
return normalizePlanKnowledgeBinding(saved ?? readVerifiedPlanArtifact(root, hash, canonicalPlanHash), 'cycle').semanticHash;
|
|
964
|
+
};
|
|
965
|
+
const currentSemantic = semantic(task.planHash);
|
|
966
|
+
const audits = readPlanRiskAuditEvents(store, task), reviews = readStepReviewEvents(store, task);
|
|
967
|
+
const priorBindings = cycle.priorRemediationEvents.map(event => resolveRemediationPlanCycleBinding(root, canonicalPlanHash, event, reviews, audits));
|
|
968
|
+
const currentBindings = cycle.remediationEvents.map(event => resolveRemediationPlanCycleBinding(root, canonicalPlanHash, event, reviews, audits));
|
|
969
|
+
if (priorBindings.some(b => !b) || currentBindings.some(b => !b))
|
|
970
|
+
return null;
|
|
971
|
+
const boundaries = history.filter(boundary => correctiveReplanBoundaryMatchesStep(boundary.before, stepId)
|
|
972
|
+
&& boundary.after.planHash && boundary.before.planHash
|
|
973
|
+
&& semantic(boundary.after.planHash) === currentSemantic && semantic(boundary.before.planHash) !== currentSemantic
|
|
974
|
+
&& boundary.after.revision <= task.revision
|
|
975
|
+
&& priorBindings.every(b => b.sourceTaskRevision < boundary.after.revision)
|
|
976
|
+
&& currentBindings.every(b => b.sourceTaskRevision > boundary.after.revision));
|
|
977
|
+
if (boundaries.length !== 1)
|
|
978
|
+
return null;
|
|
979
|
+
const boundary = boundaries[0];
|
|
980
|
+
let cursor = task.planHash;
|
|
981
|
+
const visited = new Set();
|
|
982
|
+
const planBytes = (hash) => history.flatMap(b => [b.beforePlan, b.afterPlan]).find(bytes => sha256Hex(bytes) === hash)
|
|
983
|
+
?? readVerifiedPlanArtifact(root, hash, canonicalPlanHash);
|
|
984
|
+
while (cursor !== boundary.after.planHash) {
|
|
985
|
+
if (visited.has(cursor))
|
|
986
|
+
return null;
|
|
987
|
+
visited.add(cursor);
|
|
988
|
+
const links = task.knowledgeRebinds.filter(link => link.reboundPlanHash === cursor);
|
|
989
|
+
if (links.length !== 1 || !links[0].previousPlanHash)
|
|
990
|
+
return null;
|
|
991
|
+
const link = links[0];
|
|
992
|
+
assertKnowledgeBindingOnlyPlanChange(planBytes(link.previousPlanHash), planBytes(cursor));
|
|
993
|
+
if (!planBytes(link.previousPlanHash).includes(`- Knowledge map: revision ${link.fromKnowledgeMapRevision}, hash ${link.fromKnowledgeMapHash}\n`)
|
|
994
|
+
|| !planBytes(cursor).includes(`- Knowledge map: revision ${link.toKnowledgeMapRevision}, hash ${link.toKnowledgeMapHash}\n`))
|
|
995
|
+
return null;
|
|
996
|
+
cursor = link.previousPlanHash;
|
|
997
|
+
}
|
|
998
|
+
if (!task.authorizations.some(a => a.kind === 'execution' && ['approved', 'superseded'].includes(a.decision)
|
|
999
|
+
&& a.planHash === task.planHash && a.briefHash === task.briefHash
|
|
1000
|
+
&& a.recordedAt >= boundary.admittedAt))
|
|
1001
|
+
return null;
|
|
1002
|
+
if (decisions.length === 0 || decisions.some(d => d.recordedAt > boundary.admittedAt
|
|
1003
|
+
|| semantic(d.planHash) === currentSemantic))
|
|
1004
|
+
return null;
|
|
1005
|
+
const last = decisions.at(-1);
|
|
1006
|
+
if (last.decision !== 'replan-required' || last.triggeringAttemptCount !== cycle.priorRemediationEvents.length + 1
|
|
1007
|
+
|| semantic(last.planHash) !== semantic(boundary.before.planHash))
|
|
1008
|
+
return null;
|
|
1009
|
+
return cycle.remediationEvents;
|
|
1010
|
+
}
|
|
942
1011
|
function resolveRemediationPlanCycleBinding(taskRoot, currentPlanHash, event, reviews, audits) {
|
|
943
1012
|
let planHash = null;
|
|
944
1013
|
let sourceTaskRevision = null;
|
|
@@ -994,7 +1063,7 @@ export function findFailedGuardedStepRequiringCorrectiveDecision(store, task, pl
|
|
|
994
1063
|
? posture
|
|
995
1064
|
: null;
|
|
996
1065
|
}
|
|
997
|
-
function deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEvents, correctiveDecisions, options = {}) {
|
|
1066
|
+
function deriveAttemptGateFromHistory(store, task, stepId, planRiskAudit, remediationEvents, correctiveDecisions, options = {}) {
|
|
998
1067
|
const attemptCount = remediationEvents.length + (options.priorRemediationEvents?.length ?? 0);
|
|
999
1068
|
const nextAttemptOrdinal = options.nextAttemptOrdinal ?? attemptCount + 1;
|
|
1000
1069
|
const guardedCategory = requireGuardedCategory(planRiskAudit, stepId);
|
|
@@ -1002,8 +1071,9 @@ function deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEv
|
|
|
1002
1071
|
const currentCorrectiveDecision = findCurrentCorrectiveDecision(task, stepId, nextAttemptOrdinal, correctiveDecisions);
|
|
1003
1072
|
const planIntegrityDecision = currentCorrectiveDecision;
|
|
1004
1073
|
if (planIntegrityDecision?.recoveryBasis === 'plan-integrity'
|
|
1005
|
-
&& (planIntegrityDecision.planHash === task.planHash || planIntegrityDecision.decision === 'replan-required'
|
|
1006
|
-
|
|
1074
|
+
&& (planIntegrityDecision.planHash === task.planHash || planIntegrityDecision.decision === 'replan-required'
|
|
1075
|
+
|| isKnowledgeReboundSupportDecision(store, task, planIntegrityDecision))) {
|
|
1076
|
+
if (planIntegrityDecision.planHash !== task.planHash && planIntegrityDecision.decision === 'replan-required') {
|
|
1007
1077
|
return {
|
|
1008
1078
|
stepId,
|
|
1009
1079
|
guardedCategory,
|
|
@@ -1638,9 +1708,9 @@ function validatePlanIntegrityDecision(event, task, file, line) {
|
|
|
1638
1708
|
const validateCurrentPlanBinding = event.planHash === task.planHash;
|
|
1639
1709
|
if (event.decision !== 'continue-fix'
|
|
1640
1710
|
|| evidence.remediationEvents.length === 0
|
|
1641
|
-
|| event.triggeringAttemptCount !== evidence.remediationEvents.
|
|
1711
|
+
|| event.triggeringAttemptCount !== evidence.remediationEvents.at(-1).attemptOrdinal + 1
|
|
1642
1712
|
|| evidence.remediationEvents.some((binding, index) => {
|
|
1643
|
-
if (!binding.eventId || binding.attemptOrdinal !==
|
|
1713
|
+
if (!binding.eventId || binding.attemptOrdinal !== evidence.remediationEvents[0].attemptOrdinal + index)
|
|
1644
1714
|
return true;
|
|
1645
1715
|
assertHash64(binding.eventHash, `Plan-integrity remediation event hash at ${file}:${line}`);
|
|
1646
1716
|
return false;
|
|
@@ -1681,9 +1751,9 @@ function validatePlanIntegrityDecision(event, task, file, line) {
|
|
|
1681
1751
|
assertHash64(evidence.failedCheckCauseFingerprint, `Plan-integrity failedCheckCauseFingerprint at ${file}:${line}`);
|
|
1682
1752
|
if (event.decision !== 'continue-fix'
|
|
1683
1753
|
|| evidence.remediationEvents.length === 0
|
|
1684
|
-
|| event.triggeringAttemptCount !== evidence.remediationEvents.
|
|
1754
|
+
|| event.triggeringAttemptCount !== evidence.remediationEvents.at(-1).attemptOrdinal + 1
|
|
1685
1755
|
|| evidence.remediationEvents.some((binding, index) => {
|
|
1686
|
-
if (!binding.eventId || binding.attemptOrdinal !==
|
|
1756
|
+
if (!binding.eventId || binding.attemptOrdinal !== evidence.remediationEvents[0].attemptOrdinal + index)
|
|
1687
1757
|
return true;
|
|
1688
1758
|
assertHash64(binding.eventHash, `Plan-integrity remediation event hash at ${file}:${line}`);
|
|
1689
1759
|
return false;
|
|
@@ -1706,9 +1776,10 @@ function validatePlanIntegrityDecision(event, task, file, line) {
|
|
|
1706
1776
|
|| !conflict.componentRoot.trim()
|
|
1707
1777
|
|| !(conflict.ownerPath === conflict.componentRoot
|
|
1708
1778
|
|| conflict.ownerPath.startsWith(`${conflict.componentRoot}/`))
|
|
1709
|
-
||
|
|
1779
|
+
|| (conflict.testInvocation
|
|
1780
|
+
? !validLiteralTestInvocationEvidence(conflict) || importChain.length !== 1
|
|
1781
|
+
: importChain.length < 2 || importChain.at(-1) !== conflict.ownerPath)
|
|
1710
1782
|
|| importChain[0] !== conflict.runnerPath
|
|
1711
|
-
|| importChain.at(-1) !== conflict.ownerPath
|
|
1712
1783
|
|| !evidence.scopeExtensions.includes(conflict.ownerPath);
|
|
1713
1784
|
})) {
|
|
1714
1785
|
throw new WorkflowError('STATE_CORRUPT', 'Plan-integrity v4 evidence has invalid component-owner bindings.', {
|
|
@@ -1744,13 +1815,54 @@ function validatePlanIntegrityDecision(event, task, file, line) {
|
|
|
1744
1815
|
throw new WorkflowError('STATE_CORRUPT', 'Plan-integrity changedFiles must be normalized.', { file, line });
|
|
1745
1816
|
}
|
|
1746
1817
|
}
|
|
1818
|
+
/** A validated support decision survives only a hash-verified Knowledge-only Plan chain.
|
|
1819
|
+
* This preserves scope evidence, never execution authorization or a semantic replan. */
|
|
1820
|
+
export function isKnowledgeReboundSupportDecision(store, task, event) {
|
|
1821
|
+
const evidence = event.planIntegrityEvidence;
|
|
1822
|
+
if (!task.planHash || event.planHash === task.planHash || event.taskId !== task.id
|
|
1823
|
+
|| event.recoveryBasis !== 'plan-integrity' || event.decision !== 'continue-fix'
|
|
1824
|
+
|| (evidence?.kind !== 'check-support-anchor-outside-allowed-writes'
|
|
1825
|
+
&& evidence?.kind !== 'failed-check-component-owner-outside-allowed-writes'))
|
|
1826
|
+
return false;
|
|
1827
|
+
const step = task.steps.find(s => s.id === event.stepId);
|
|
1828
|
+
if (!step || evidence.stepDefinitionHash !== hashCanonical({
|
|
1829
|
+
id: step.id, title: step.title, objective: step.objective, requirements: step.requirements,
|
|
1830
|
+
dependencies: step.dependencies, expectedOutputs: step.expectedOutputs,
|
|
1831
|
+
allowedWrites: step.allowedWrites, forbiddenScope: step.forbiddenScope, checks: step.checks,
|
|
1832
|
+
}) || evidence.scopeExtensions.some(p => pathAllowed(p, step.allowedWrites) || pathAllowed(p, step.forbiddenScope))
|
|
1833
|
+
|| (evidence.kind === 'failed-check-component-owner-outside-allowed-writes'
|
|
1834
|
+
&& evidence.semanticBoundaryHash !== semanticBoundaryHash(task)))
|
|
1835
|
+
return false;
|
|
1836
|
+
const root = store.taskRoot(task.projectId, task.id);
|
|
1837
|
+
let cursor = task.planHash;
|
|
1838
|
+
const visited = new Set();
|
|
1839
|
+
while (cursor !== event.planHash) {
|
|
1840
|
+
if (visited.has(cursor))
|
|
1841
|
+
return false;
|
|
1842
|
+
visited.add(cursor);
|
|
1843
|
+
const links = task.knowledgeRebinds.filter(link => link.reboundPlanHash === cursor);
|
|
1844
|
+
if (links.length !== 1 || !links[0].previousPlanHash)
|
|
1845
|
+
return false;
|
|
1846
|
+
const link = links[0];
|
|
1847
|
+
const before = readVerifiedPlanArtifact(root, link.previousPlanHash, task.planHash);
|
|
1848
|
+
const after = readVerifiedPlanArtifact(root, cursor, task.planHash);
|
|
1849
|
+
assertKnowledgeBindingOnlyPlanChange(before, after);
|
|
1850
|
+
const from = normalizePlanKnowledgeBinding(before, 'source').binding;
|
|
1851
|
+
const to = normalizePlanKnowledgeBinding(after, 'target').binding;
|
|
1852
|
+
if (from.revision !== link.fromKnowledgeMapRevision || from.hash !== link.fromKnowledgeMapHash
|
|
1853
|
+
|| to.revision !== link.toKnowledgeMapRevision || to.hash !== link.toKnowledgeMapHash)
|
|
1854
|
+
return false;
|
|
1855
|
+
cursor = link.previousPlanHash;
|
|
1856
|
+
}
|
|
1857
|
+
return true;
|
|
1858
|
+
}
|
|
1747
1859
|
export function effectiveStepAllowedWrites(store, task, stepId) {
|
|
1748
1860
|
const step = task.steps.find((candidate) => candidate.id === stepId);
|
|
1749
1861
|
if (!step)
|
|
1750
1862
|
throw new WorkflowError('NOT_FOUND', `Step not found: ${stepId}`);
|
|
1751
1863
|
const extensions = readCorrectiveDecisionEvents(store, task)
|
|
1752
1864
|
.filter((event) => event.stepId === stepId
|
|
1753
|
-
&& event.planHash === task.planHash
|
|
1865
|
+
&& (event.planHash === task.planHash || isKnowledgeReboundSupportDecision(store, task, event))
|
|
1754
1866
|
&& event.decision === 'continue-fix'
|
|
1755
1867
|
&& event.recoveryBasis === 'plan-integrity')
|
|
1756
1868
|
.flatMap((event) => event.planIntegrityEvidence?.kind === 'check-support-anchor-outside-allowed-writes'
|
|
@@ -2020,7 +2132,7 @@ function validateRemediationReviewReferences(store, task, events) {
|
|
|
2020
2132
|
}
|
|
2021
2133
|
}
|
|
2022
2134
|
}
|
|
2023
|
-
function validateCorrectiveDecisionCoverage(task, decisions, remediationEvents) {
|
|
2135
|
+
function validateCorrectiveDecisionCoverage(store, task, decisions, remediationEvents) {
|
|
2024
2136
|
const remediationEventsById = new Map(remediationEvents.map((event) => [event.eventId, event]));
|
|
2025
2137
|
const decisionBindings = new Set();
|
|
2026
2138
|
for (const decision of decisions) {
|
|
@@ -2071,9 +2183,14 @@ function validateCorrectiveDecisionCoverage(task, decisions, remediationEvents)
|
|
|
2071
2183
|
const bindings = evidence?.kind === 'missing-npm-script-outside-allowed-writes'
|
|
2072
2184
|
? [{ eventId: evidence.remediationEventId, eventHash: evidence.remediationEventHash, attemptOrdinal: 1 }]
|
|
2073
2185
|
: evidence?.remediationEvents ?? [];
|
|
2074
|
-
|
|
2075
|
-
||
|
|
2076
|
-
|
|
2186
|
+
const support = evidence?.kind === 'check-support-anchor-outside-allowed-writes'
|
|
2187
|
+
|| evidence?.kind === 'failed-check-component-owner-outside-allowed-writes';
|
|
2188
|
+
const scoped = support && bindings[0]?.attemptOrdinal !== 1
|
|
2189
|
+
? selectPlanIntegrityRemediations(store, { ...task, planHash: decision.planHash, revision: evidence.taskRevision }, priorEvents, decisions.filter(d => d.stepId === decision.stepId && d.triggeringAttemptCount < decision.triggeringAttemptCount), task.planHash)
|
|
2190
|
+
: priorEvents;
|
|
2191
|
+
if (!evidence || !scoped
|
|
2192
|
+
|| bindings.length !== scoped.length
|
|
2193
|
+
|| scoped.some((linked, index) => linked.failureKind !== 'checks-failed'
|
|
2077
2194
|
|| linked.eventId !== bindings[index]?.eventId
|
|
2078
2195
|
|| linked.eventHash !== bindings[index]?.eventHash
|
|
2079
2196
|
|| linked.attemptOrdinal !== bindings[index]?.attemptOrdinal)) {
|