codex-workflow-v2 2.0.0-beta.13.8 → 2.0.0-beta.14
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 +27 -388
- package/dist/reviewer-runtime-build.json +96 -32
- package/dist/src/alpha6/adoption.d.ts +2 -0
- package/dist/src/alpha6/adoption.js +22 -0
- package/dist/src/alpha6/adoption.js.map +1 -1
- package/dist/src/alpha6/captured-check-evidence.d.ts +51 -0
- package/dist/src/alpha6/captured-check-evidence.js +152 -0
- package/dist/src/alpha6/captured-check-evidence.js.map +1 -0
- 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/corrective-decision-boundary.d.ts +4 -0
- package/dist/src/alpha6/corrective-decision-boundary.js +47 -0
- package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -0
- package/dist/src/alpha6/downstream-proof.d.ts +1 -25
- package/dist/src/alpha6/downstream-proof.js +0 -171
- package/dist/src/alpha6/downstream-proof.js.map +1 -1
- 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/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/milestone.d.ts +2 -0
- package/dist/src/alpha6/milestone.js +10 -1
- package/dist/src/alpha6/milestone.js.map +1 -1
- package/dist/src/alpha6/navigation-dirty-carryover.d.ts +11 -0
- package/dist/src/alpha6/navigation-dirty-carryover.js +130 -0
- package/dist/src/alpha6/navigation-dirty-carryover.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/plan-risk.d.ts +2 -0
- package/dist/src/alpha6/plan-risk.js +10 -3
- package/dist/src/alpha6/plan-risk.js.map +1 -1
- package/dist/src/alpha6/remediation.d.ts +6 -21
- package/dist/src/alpha6/remediation.js +161 -476
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/alpha6/root-cause-replan-carryover.d.ts +4 -1
- package/dist/src/alpha6/root-cause-replan-carryover.js +29 -5
- package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -1
- package/dist/src/beta1/project-transaction.d.ts +3 -1
- package/dist/src/beta1/project-transaction.js +6 -2
- package/dist/src/beta1/project-transaction.js.map +1 -1
- package/dist/src/checks/runner.d.ts +26 -0
- package/dist/src/checks/runner.js +223 -0
- package/dist/src/checks/runner.js.map +1 -0
- package/dist/src/checks/task-sync.d.ts +10 -0
- package/dist/src/checks/task-sync.js +41 -0
- package/dist/src/checks/task-sync.js.map +1 -0
- package/dist/src/checks/worker.d.ts +1 -0
- package/dist/src/checks/worker.js +154 -0
- package/dist/src/checks/worker.js.map +1 -0
- package/dist/src/cli-actions.d.ts +2 -2
- package/dist/src/cli-actions.js +3 -15
- package/dist/src/cli-actions.js.map +1 -1
- package/dist/src/cli.js +14 -98
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +35 -32
- package/dist/src/dependency-provenance.d.ts +2 -2
- package/dist/src/dependency-provenance.js +12 -69
- package/dist/src/dependency-provenance.js.map +1 -1
- package/dist/src/domain/base-sync-conflict.d.ts +13 -0
- package/dist/src/domain/base-sync-conflict.js +32 -0
- package/dist/src/domain/base-sync-conflict.js.map +1 -0
- package/dist/src/domain/step-start-admission.d.ts +4 -0
- package/dist/src/domain/step-start-admission.js +9 -0
- package/dist/src/domain/step-start-admission.js.map +1 -0
- package/dist/src/domain/virgin-registration.d.ts +3 -0
- package/dist/src/domain/virgin-registration.js +65 -0
- package/dist/src/domain/virgin-registration.js.map +1 -0
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/gateway-handshake.js +0 -8
- package/dist/src/gateway-handshake.js.map +1 -1
- package/dist/src/git.d.ts +3 -1
- package/dist/src/git.js +41 -14
- package/dist/src/git.js.map +1 -1
- package/dist/src/graph.js +25 -3
- package/dist/src/graph.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/lifecycle/corrective-replan.js +3 -3
- package/dist/src/lifecycle/corrective-replan.js.map +1 -1
- 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/navigation-update-artifact.d.ts +5 -0
- package/dist/src/navigation-update-artifact.js +227 -0
- package/dist/src/navigation-update-artifact.js.map +1 -0
- package/dist/src/navigation-update.d.ts +39 -0
- package/dist/src/navigation-update.js +82 -0
- package/dist/src/navigation-update.js.map +1 -0
- package/dist/src/observation.js +52 -29
- package/dist/src/observation.js.map +1 -1
- package/dist/src/observed-routes.js +5 -6
- package/dist/src/observed-routes.js.map +1 -1
- package/dist/src/pending-review-update.d.ts +0 -13
- package/dist/src/pending-review-update.js +1 -6
- package/dist/src/pending-review-update.js.map +1 -1
- package/dist/src/reviewer.d.ts +1 -1
- package/dist/src/reviewer.js +20 -17
- package/dist/src/reviewer.js.map +1 -1
- package/dist/src/runtime.d.ts +3 -0
- package/dist/src/runtime.js +6 -0
- package/dist/src/runtime.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/state/corrective-replan-public-schema.js +15 -2
- package/dist/src/state/corrective-replan-public-schema.js.map +1 -1
- package/dist/src/state/corrective-replan-public.js +6 -5
- package/dist/src/state/corrective-replan-public.js.map +1 -1
- package/dist/src/state/corrective-replan-transaction.d.ts +1 -0
- package/dist/src/state/corrective-replan-transaction.js +12 -11
- package/dist/src/state/corrective-replan-transaction.js.map +1 -1
- package/dist/src/state/corrective-yield-executor.js +2 -2
- package/dist/src/state/corrective-yield-executor.js.map +1 -1
- package/dist/src/state/corrective-yield-transaction.d.ts +1 -0
- package/dist/src/state/corrective-yield-transaction.js +11 -10
- package/dist/src/state/corrective-yield-transaction.js.map +1 -1
- package/dist/src/state/lock.d.ts +4 -0
- package/dist/src/state/lock.js +21 -0
- package/dist/src/state/lock.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-blocker-route.d.ts +8 -0
- package/dist/src/workflow-blocker-route.js +106 -0
- package/dist/src/workflow-blocker-route.js.map +1 -0
- package/dist/src/workflow.d.ts +48 -63
- package/dist/src/workflow.js +698 -1470
- package/dist/src/workflow.js.map +1 -1
- package/docs/autonomy-guardrails.md +26 -304
- package/docs/decisions.md +14 -112
- package/docs/development-flow.md +35 -238
- package/docs/project-memory.md +31 -50
- package/docs/release-app-evidence.md +190 -0
- package/docs/release.md +95 -363
- package/docs/updating-existing-project.md +19 -717
- package/package.json +11 -13
- package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +2 -2
- package/plugins/codex-workflow-gateway/references/chat-dispatch.md +73 -197
- package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
- package/plugins/codex-workflow-gateway/references/protocol.md +42 -445
- package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +7 -1
- package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +10 -8
- package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +66 -740
- package/references/state-machine.md +4 -4
- package/roles/technical-planner.md +1 -1
- package/schemas/corrective-decision-event.schema.json +3 -1
- package/schemas/project-knowledge-map.schema.json +3 -1
- package/schemas/remediation-event.schema.json +10 -1
- package/schemas/task.schema.json +46 -2
- package/schemas/transition-payloads.schema.json +16 -1
- package/src/alpha6/adoption.ts +1123 -0
- package/src/alpha6/captured-check-evidence.ts +132 -0
- package/src/alpha6/check-support-anchor.ts +128 -0
- package/src/alpha6/component-owner.ts +168 -0
- package/src/alpha6/corrective-decision-boundary.ts +39 -0
- package/src/alpha6/downstream-proof.ts +520 -0
- package/src/alpha6/failed-step-planning-recovery.ts +88 -0
- package/src/alpha6/handoff.ts +1443 -0
- package/src/alpha6/journal.ts +473 -0
- package/src/alpha6/literal-test-invocation.ts +84 -0
- package/src/alpha6/mechanical-feasibility.ts +488 -0
- package/src/alpha6/milestone.ts +2192 -0
- package/src/alpha6/navigation-dirty-carryover.ts +154 -0
- package/src/alpha6/npm-check-contract.ts +47 -0
- package/src/alpha6/plan-integrity.ts +298 -0
- package/src/alpha6/plan-risk.ts +1480 -0
- package/src/alpha6/preexecution-replan.ts +187 -0
- package/src/alpha6/remediation-cause.ts +98 -0
- package/src/alpha6/remediation.ts +2438 -0
- package/src/alpha6/review.ts +1198 -0
- package/src/alpha6/root-cause-replan-carryover.ts +491 -0
- package/src/alpha6/store-sidecars.ts +335 -0
- package/src/alpha7/autonomy.ts +411 -0
- package/src/alpha7/corrective-recovery.ts +1332 -0
- package/src/artifacts.ts +130 -0
- package/src/beta1/project-transaction.ts +355 -0
- package/src/change-explanation.ts +153 -0
- package/src/checks/runner.ts +245 -0
- package/src/checks/task-sync.ts +41 -0
- package/src/checks/worker.ts +149 -0
- package/src/cli-actions.ts +107 -0
- package/src/cli.ts +1457 -0
- package/src/contracts.ts +1494 -0
- package/src/credential-output.ts +89 -0
- package/src/credential-transport.ts +215 -0
- package/src/delegation.ts +190 -0
- package/src/dependency-provenance.ts +472 -0
- package/src/diagnostics.ts +93 -0
- package/src/domain/base-sync-conflict.ts +35 -0
- package/src/domain/completed-step-carryover.ts +76 -0
- package/src/domain/discovery.ts +27 -0
- package/src/domain/plan-semantics.ts +58 -0
- package/src/domain/step-start-admission.ts +10 -0
- package/src/domain/validation.ts +177 -0
- package/src/domain/virgin-registration.ts +46 -0
- package/src/errors.ts +24 -0
- package/src/fs-utils.ts +61 -0
- package/src/gateway-handshake.ts +95 -0
- package/src/git.ts +183 -0
- package/src/graph.ts +342 -0
- package/src/historical-step-provenance.ts +136 -0
- package/src/index.ts +23 -0
- package/src/lifecycle/canonical-hash.ts +28 -0
- package/src/lifecycle/catalog.ts +202 -0
- package/src/lifecycle/compiler-inspection.ts +29 -0
- package/src/lifecycle/core-static-readiness.ts +132 -0
- package/src/lifecycle/corrective-replan-authority.ts +136 -0
- package/src/lifecycle/corrective-replan-binding-manifest.ts +51 -0
- package/src/lifecycle/corrective-replan-credential-core.ts +408 -0
- package/src/lifecycle/corrective-replan-credential-schema.ts +54 -0
- package/src/lifecycle/corrective-replan-credentials.ts +48 -0
- package/src/lifecycle/corrective-replan.ts +843 -0
- package/src/lifecycle/evaluator.ts +48 -0
- package/src/lifecycle/fingerprint.ts +488 -0
- package/src/lifecycle/immutable.ts +8 -0
- package/src/lifecycle/implementation-table.ts +118 -0
- package/src/lifecycle/index.ts +8 -0
- package/src/lifecycle/schema-artifact.ts +263 -0
- package/src/lifecycle/semantic-registry.ts +572 -0
- package/src/lifecycle/types.ts +838 -0
- package/src/memory.ts +273 -0
- package/src/migration.ts +161 -0
- package/src/navigation-actions.ts +70 -0
- package/src/navigation-update-artifact.ts +198 -0
- package/src/navigation-update.ts +121 -0
- package/src/observation.ts +225 -0
- package/src/observed-routes.ts +660 -0
- package/src/operational-contract.ts +125 -0
- package/src/pending-review-update.ts +175 -0
- package/src/repository.ts +99 -0
- package/src/reviewer.ts +1879 -0
- package/src/runtime.ts +6 -0
- package/src/state/corrective-replan-executor.ts +818 -0
- package/src/state/corrective-replan-public-schema.ts +83 -0
- package/src/state/corrective-replan-public.ts +908 -0
- package/src/state/corrective-replan-transaction.ts +949 -0
- package/src/state/corrective-yield-executor.ts +327 -0
- package/src/state/corrective-yield-transaction.ts +730 -0
- package/src/state/lock.ts +902 -0
- package/src/state/store.ts +567 -0
- package/src/transition-core.ts +330 -0
- package/src/ulid.ts +24 -0
- package/src/version.ts +2 -0
- package/src/workflow-blocker-route.ts +109 -0
- package/src/workflow.ts +10172 -0
- package/docs/alpha7.1-implementation-brief.md +0 -268
- package/docs/alpha7.2-corrective-context-refresh-brief.md +0 -484
- package/docs/alpha7.2.1-remediation-recovery-brief.md +0 -86
- package/docs/beta1-stabilization-brief.md +0 -165
- package/docs/beta11-plan-integrity-recovery-brief.md +0 -38
- package/docs/beta13.2-signal-review-recovery.md +0 -38
- package/docs/beta2-initial-assembly-navigation-brief.md +0 -616
- package/docs/change-model.md +0 -118
- package/docs/delegated-approval.md +0 -254
- package/docs/lifecycle/state-machine-stabilization.md +0 -641
- package/docs/pdf/README.md +0 -24
- 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/requirements.txt +0 -1
- package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +0 -478
- package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +0 -508
- package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +0 -778
- package/docs/pending-review-update.md +0 -15
- package/docs/problem-briefs/01-pre-implementation-integrity.md +0 -482
- package/docs/problem-briefs/02-minimal-step-integrity.md +0 -411
- package/docs/problem-briefs/03-minimal-agent-context-integrity.md +0 -358
- package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +0 -573
- package/docs/problem-briefs/BRIEF-TEMPLATE.md +0 -56
- package/docs/problem-briefs/README.md +0 -120
- package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +0 -90
- package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +0 -246
- package/docs/split-required-recovery.md +0 -47
- package/docs/stable-release-defect-register.md +0 -730
- package/docs/validation-report.md +0 -182
- package/scripts/generate-pdf-docs.py +0 -524
- package/scripts/run-pdf-docs.mjs +0 -62
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } 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';
|
|
7
7
|
import { createUlid } from '../ulid.js';
|
|
8
|
-
import { PACKAGE_VERSION } from '../version.js';
|
|
9
8
|
import { readPlanRiskAuditEvents } from './plan-risk.js';
|
|
10
9
|
import { assertHash64, canonicalJsonStringify, prepareSidecarAppend, sha256Hex, } from './store-sidecars.js';
|
|
11
|
-
import { readTaskC1Posture } from './handoff.js';
|
|
12
10
|
import { buildCurrentStrictStepReviewCycle, latestPassedVerifiedStepReviewEvent, readReviewerAttestations, readStepReviewEvents, } from './review.js';
|
|
13
|
-
import {
|
|
11
|
+
import { correctiveReplanBoundaryMatchesStep, readCompletedCorrectiveReplanHistory } from '../state/corrective-replan-executor.js';
|
|
12
|
+
import { semanticBoundaryHash, validLiteralTestInvocationEvidence } from './component-owner.js';
|
|
14
13
|
import { reviewerCauseFingerprint } from './remediation-cause.js';
|
|
15
14
|
export const REMEDIATION_EVENTS_SIDECAR = 'remediation-events.jsonl';
|
|
16
15
|
export const CORRECTIVE_DECISIONS_SIDECAR = 'corrective-decisions.jsonl';
|
|
17
16
|
export const CORRECTIVE_DECISION_RECOVERIES_SIDECAR = 'corrective-decision-recoveries.jsonl';
|
|
18
17
|
export const REMEDIATION_MODE_RECOVERIES_SIDECAR = 'remediation-mode-recoveries.jsonl';
|
|
19
18
|
export const STOP_ESCALATE_OVERRIDES_SIDECAR = 'stop-escalate-overrides.jsonl';
|
|
20
|
-
const REMEDIATION_MODE_RECOVERY_LOCK = '.remediation-mode-recovery.lock';
|
|
21
19
|
const STOP_ESCALATE_OVERRIDE_LOCK = '.stop-escalate-override.lock';
|
|
22
20
|
const LEGACY_STOP_OVERRIDE_POLICY = 'beta11-attempt4-stop-compatibility-v1';
|
|
23
21
|
const COMMIT_SHA_PATTERN = /^[a-f0-9]{40}$/;
|
|
@@ -83,7 +81,7 @@ function validateRemediationHistory(store, task) {
|
|
|
83
81
|
const overriddenStopDecisionEventIds = validateStopEscalateOverrideHistory(task, remediationEvents, correctiveDecisionEvents, readRawStopEscalateOverrideEvents(store, task));
|
|
84
82
|
validateRemediationAttemptOrdinals(remediationEvents, task);
|
|
85
83
|
validateRemediationReviewReferences(store, task, remediationEvents);
|
|
86
|
-
validateCorrectiveDecisionCoverage(task, correctiveDecisionEvents, remediationEvents);
|
|
84
|
+
validateCorrectiveDecisionCoverage(store, task, correctiveDecisionEvents, remediationEvents);
|
|
87
85
|
const recoveredEventIds = validateRemediationModeRecoveries(store, task, remediationEvents, correctiveDecisionEvents, modeRecoveries);
|
|
88
86
|
validateHistoricalRemediationModesForAllSteps(store, task, remediationEvents, correctiveDecisionEvents, recoveredEventIds, overriddenStopDecisionEventIds);
|
|
89
87
|
return {
|
|
@@ -139,7 +137,7 @@ export function readStopEscalateOverrideEvents(store, task) {
|
|
|
139
137
|
const decisions = readRawCorrectiveDecisionEvents(store, task);
|
|
140
138
|
const overrides = readRawStopEscalateOverrideEvents(store, task);
|
|
141
139
|
validateRemediationAttemptOrdinals(remediations, task);
|
|
142
|
-
validateCorrectiveDecisionCoverage(task, decisions, remediations);
|
|
140
|
+
validateCorrectiveDecisionCoverage(store, task, decisions, remediations);
|
|
143
141
|
validateStopEscalateOverrideHistory(task, remediations, decisions, overrides);
|
|
144
142
|
return overrides;
|
|
145
143
|
}
|
|
@@ -148,7 +146,7 @@ export function stopEscalateOverrideDecisionEventIds(store, task) {
|
|
|
148
146
|
const decisions = readRawCorrectiveDecisionEvents(store, task);
|
|
149
147
|
const overrides = readRawStopEscalateOverrideEvents(store, task);
|
|
150
148
|
validateRemediationAttemptOrdinals(remediations, task);
|
|
151
|
-
validateCorrectiveDecisionCoverage(task, decisions, remediations);
|
|
149
|
+
validateCorrectiveDecisionCoverage(store, task, decisions, remediations);
|
|
152
150
|
return validateStopEscalateOverrideHistory(task, remediations, decisions, overrides);
|
|
153
151
|
}
|
|
154
152
|
export function appendRemediationEvent(store, task, stepId, failureKind, failureEvidence, planRiskAudit, now, reboundStopOverrideDecisionEventId = null, causeBindings = []) {
|
|
@@ -185,7 +183,7 @@ export function prepareRemediationEvent(store, task, stepId, failureKind, failur
|
|
|
185
183
|
}
|
|
186
184
|
const priorEvents = existing.filter((event) => event.attemptOrdinal < reused.attemptOrdinal);
|
|
187
185
|
const cycle = selectCurrentPlanRemediationCycle(store, task, priorEvents, correctiveDecisions);
|
|
188
|
-
const gate = deriveAttemptGateFromHistory(task, stepId, planRiskAudit, cycle.remediationEvents, cycle.correctiveDecisions, {
|
|
186
|
+
const gate = deriveAttemptGateFromHistory(store, task, stepId, planRiskAudit, cycle.remediationEvents, cycle.correctiveDecisions, {
|
|
189
187
|
recoveredDecisionEventId,
|
|
190
188
|
reboundStopOverrideDecisionEventId,
|
|
191
189
|
overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
|
|
@@ -206,7 +204,7 @@ export function prepareRemediationEvent(store, task, stepId, failureKind, failur
|
|
|
206
204
|
return { event: reused, postimage: null };
|
|
207
205
|
}
|
|
208
206
|
const cycle = selectCurrentPlanRemediationCycle(store, task, existing, correctiveDecisions);
|
|
209
|
-
const gate = deriveAttemptGateFromHistory(task, stepId, planRiskAudit, cycle.remediationEvents, cycle.correctiveDecisions, {
|
|
207
|
+
const gate = deriveAttemptGateFromHistory(store, task, stepId, planRiskAudit, cycle.remediationEvents, cycle.correctiveDecisions, {
|
|
210
208
|
recoveredDecisionEventId,
|
|
211
209
|
reboundStopOverrideDecisionEventId,
|
|
212
210
|
overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
|
|
@@ -253,276 +251,6 @@ function findCorrectiveRecoveryDecisionIdForFailure(store, task, stepId) {
|
|
|
253
251
|
}
|
|
254
252
|
return recoveries[0]?.correctiveDecisionEventId ?? null;
|
|
255
253
|
}
|
|
256
|
-
export function findRemediationModeRecoveryCandidate(store, task, options = {}) {
|
|
257
|
-
const raw = readRawRemediationEvents(store, task);
|
|
258
|
-
const candidates = raw.filter((event) => event.attemptOrdinal > 2 && event.mode === 'ordinary');
|
|
259
|
-
if (candidates.length === 0)
|
|
260
|
-
return null;
|
|
261
|
-
if (candidates.length !== 1) {
|
|
262
|
-
throw new WorkflowError('STATE_CORRUPT', 'Multiple malformed remediation modes are not recoverable.', {
|
|
263
|
-
taskId: task.id,
|
|
264
|
-
eventIds: candidates.map((event) => event.eventId),
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
const posture = readTaskC1Posture(store, task);
|
|
268
|
-
const lease = readWriterLeaseForModeRecovery(store, task);
|
|
269
|
-
const actor = posture.state === 'claimed' ? posture.claimant : lease?.owner ?? 'worker';
|
|
270
|
-
return assessRemediationModeRecovery(store, task, candidates[0].stepId, actor, lease?.token ?? null, options);
|
|
271
|
-
}
|
|
272
|
-
export function assessRemediationModeRecovery(store, task, stepId, actor, writerToken, options = {}) {
|
|
273
|
-
const normalizedActor = actor.trim();
|
|
274
|
-
if (!normalizedActor)
|
|
275
|
-
throw new WorkflowError('INVALID_ARGUMENT', 'Remediation mode recovery actor is required.');
|
|
276
|
-
const step = task.steps.find((candidate) => candidate.id === stepId) ?? null;
|
|
277
|
-
if (!step)
|
|
278
|
-
throw new WorkflowError('NOT_FOUND', `Step not found: ${stepId}`);
|
|
279
|
-
if (task.status !== 'needs_fix' || step.status !== 'failed') {
|
|
280
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Remediation mode recovery requires the exact failed Task/Step posture.', {
|
|
281
|
-
taskId: task.id,
|
|
282
|
-
taskStatus: task.status,
|
|
283
|
-
stepId,
|
|
284
|
-
stepStatus: step.status,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
if (!task.planHash)
|
|
288
|
-
throw new WorkflowError('STATE_CORRUPT', `Task ${task.id} is missing its Plan hash.`);
|
|
289
|
-
const remediations = readRawRemediationEvents(store, task);
|
|
290
|
-
const decisions = readRawCorrectiveDecisionEvents(store, task);
|
|
291
|
-
validateRemediationAttemptOrdinals(remediations, task);
|
|
292
|
-
validateRemediationReviewReferences(store, task, remediations);
|
|
293
|
-
validateCorrectiveDecisionCoverage(task, decisions, remediations);
|
|
294
|
-
const malformed = remediations.filter((event) => event.attemptOrdinal > 2 && event.mode === 'ordinary');
|
|
295
|
-
if (malformed.length !== 1 || malformed[0].stepId !== stepId || malformed[0].attemptOrdinal !== 3) {
|
|
296
|
-
throw new WorkflowError('STATE_CORRUPT', 'State does not contain the one exact alpha.7.2 remediation mode omission.', {
|
|
297
|
-
taskId: task.id,
|
|
298
|
-
stepId,
|
|
299
|
-
malformedEventIds: malformed.map((event) => event.eventId),
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
const stepRemediations = remediations.filter((event) => event.stepId === stepId);
|
|
303
|
-
if (stepRemediations.length !== 3
|
|
304
|
-
|| stepRemediations[0].mode !== 'ordinary'
|
|
305
|
-
|| stepRemediations[1].mode !== 'ordinary'
|
|
306
|
-
|| stepRemediations[2].eventId !== malformed[0].eventId) {
|
|
307
|
-
throw new WorkflowError('STATE_CORRUPT', 'Remediation mode recovery requires exactly two ordinary attempts and one malformed third attempt.', {
|
|
308
|
-
taskId: task.id,
|
|
309
|
-
stepId,
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
const decisionMatches = decisions.filter((event) => event.stepId === stepId
|
|
313
|
-
&& event.triggeringAttemptCount === 3
|
|
314
|
-
&& event.decision === 'continue-fix'
|
|
315
|
-
&& sameStringArray(event.coveredEventIds, stepRemediations.slice(0, 2).map((event) => event.eventId)));
|
|
316
|
-
if (decisionMatches.length !== 1) {
|
|
317
|
-
throw new WorkflowError('STATE_CORRUPT', 'Remediation mode recovery requires one exact continue-fix decision.', {
|
|
318
|
-
taskId: task.id,
|
|
319
|
-
stepId,
|
|
320
|
-
decisionEventIds: decisionMatches.map((event) => event.eventId),
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
const decision = decisionMatches[0];
|
|
324
|
-
const correctiveRecoveries = readRawCorrectiveDecisionRecoveries(store, task).filter((event) => event.stepId === stepId
|
|
325
|
-
&& event.triggeringAttemptCount === 3
|
|
326
|
-
&& event.correctiveDecisionEventId === decision.eventId
|
|
327
|
-
&& event.correctiveDecisionEventHash === decision.eventHash
|
|
328
|
-
&& event.reboundPlanHash === task.planHash);
|
|
329
|
-
if (correctiveRecoveries.length !== 1) {
|
|
330
|
-
throw new WorkflowError('STATE_CORRUPT', 'Remediation mode recovery requires one exact corrective-decision recovery.', {
|
|
331
|
-
taskId: task.id,
|
|
332
|
-
stepId,
|
|
333
|
-
recoveryEventIds: correctiveRecoveries.map((event) => event.eventId),
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
const correctiveRecovery = correctiveRecoveries[0];
|
|
337
|
-
const authority = options.authorityMode === 'observe'
|
|
338
|
-
? observeRemediationModeRecoveryAuthority(store, task)
|
|
339
|
-
: requireRemediationModeRecoveryAuthority(store, task, normalizedActor, writerToken);
|
|
340
|
-
const existing = readRemediationModeRecoveryEvents(store, task).filter((event) => event.remediationEventId === malformed[0].eventId);
|
|
341
|
-
if (existing.length > 1) {
|
|
342
|
-
throw new WorkflowError('STATE_CORRUPT', 'Multiple remediation mode recoveries target one event.', {
|
|
343
|
-
taskId: task.id,
|
|
344
|
-
remediationEventId: malformed[0].eventId,
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
if (existing[0]) {
|
|
348
|
-
validateRemediationModeRecoveryBinding(existing[0], malformed[0], decision, correctiveRecovery);
|
|
349
|
-
const retryMismatches = [
|
|
350
|
-
existing[0].taskRevision !== task.revision ? 'taskRevision' : null,
|
|
351
|
-
existing[0].planHash !== task.planHash ? 'planHash' : null,
|
|
352
|
-
existing[0].actor !== normalizedActor ? 'actor' : null,
|
|
353
|
-
existing[0].c1ClaimEventHash !== authority.c1ClaimEventHash ? 'c1ClaimEventHash' : null,
|
|
354
|
-
existing[0].writerLeaseTokenHash !== authority.writerLeaseTokenHash ? 'writerLeaseTokenHash' : null,
|
|
355
|
-
].filter((value) => value !== null);
|
|
356
|
-
if (retryMismatches.length > 0) {
|
|
357
|
-
throw new WorkflowError('STATE_CONFLICT', 'Existing remediation mode recovery conflicts with the requested retry binding.', {
|
|
358
|
-
taskId: task.id,
|
|
359
|
-
stepId,
|
|
360
|
-
recoveryEventId: existing[0].eventId,
|
|
361
|
-
mismatches: retryMismatches,
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
const evidenceFingerprint = sha256Hex(canonicalJsonStringify({
|
|
366
|
-
taskId: task.id,
|
|
367
|
-
taskRevision: task.revision,
|
|
368
|
-
stepId,
|
|
369
|
-
planHash: task.planHash,
|
|
370
|
-
remediationEventHash: malformed[0].eventHash,
|
|
371
|
-
correctiveDecisionEventHash: decision.eventHash,
|
|
372
|
-
correctiveDecisionRecoveryEventHash: correctiveRecovery.eventHash,
|
|
373
|
-
actor: normalizedActor,
|
|
374
|
-
c1ClaimEventHash: authority.c1ClaimEventHash,
|
|
375
|
-
writerLeaseTokenHash: authority.writerLeaseTokenHash,
|
|
376
|
-
}));
|
|
377
|
-
return {
|
|
378
|
-
task,
|
|
379
|
-
remediation: malformed[0],
|
|
380
|
-
decision,
|
|
381
|
-
correctiveRecovery,
|
|
382
|
-
existingRecovery: existing[0] ?? null,
|
|
383
|
-
actor: normalizedActor,
|
|
384
|
-
c1ClaimEventHash: authority.c1ClaimEventHash,
|
|
385
|
-
writerLeaseTokenHash: authority.writerLeaseTokenHash,
|
|
386
|
-
evidenceFingerprint,
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
function observeRemediationModeRecoveryAuthority(store, task) {
|
|
390
|
-
const posture = readTaskC1Posture(store, task);
|
|
391
|
-
const lease = readWriterLeaseForModeRecovery(store, task);
|
|
392
|
-
return {
|
|
393
|
-
c1ClaimEventHash: posture.state === 'none' ? null : posture.latestEvent.eventHash,
|
|
394
|
-
writerLeaseTokenHash: lease ? sha256Hex(lease.token) : null,
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
export function appendRemediationModeRecovery(store, task, stepId, expectedRevision, actor, writerToken, now) {
|
|
398
|
-
if (task.revision !== expectedRevision) {
|
|
399
|
-
throw new WorkflowError('STATE_CONFLICT', `State revision conflict for ${task.id}`, {
|
|
400
|
-
expectedRevision,
|
|
401
|
-
actualRevision: task.revision,
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
const preflight = assessRemediationModeRecovery(store, task, stepId, actor, writerToken);
|
|
405
|
-
const root = store.taskRoot(task.projectId, task.id);
|
|
406
|
-
const lockFile = path.join(root, REMEDIATION_MODE_RECOVERY_LOCK);
|
|
407
|
-
let descriptor;
|
|
408
|
-
try {
|
|
409
|
-
descriptor = openSync(lockFile, 'wx', 0o600);
|
|
410
|
-
closeSync(descriptor);
|
|
411
|
-
}
|
|
412
|
-
catch (error) {
|
|
413
|
-
if (existsSync(lockFile)) {
|
|
414
|
-
throw new WorkflowError('LOCKED', `Remediation mode recovery for ${task.id} is already being serialized.`, {
|
|
415
|
-
taskId: task.id,
|
|
416
|
-
stepId,
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
throw error;
|
|
420
|
-
}
|
|
421
|
-
try {
|
|
422
|
-
const currentTask = store.readTask(task.projectId, task.id);
|
|
423
|
-
if (currentTask.revision !== expectedRevision) {
|
|
424
|
-
throw new WorkflowError('STATE_CONFLICT', `State revision conflict for ${task.id}`, {
|
|
425
|
-
expectedRevision,
|
|
426
|
-
actualRevision: currentTask.revision,
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
let underLock;
|
|
430
|
-
try {
|
|
431
|
-
underLock = assessRemediationModeRecovery(store, currentTask, stepId, actor, writerToken);
|
|
432
|
-
}
|
|
433
|
-
catch (error) {
|
|
434
|
-
throw new WorkflowError('STATE_CONFLICT', 'Remediation mode recovery evidence changed after preflight.', {
|
|
435
|
-
taskId: task.id,
|
|
436
|
-
stepId,
|
|
437
|
-
cause: error instanceof Error ? error.message : String(error),
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
if (underLock.existingRecovery)
|
|
441
|
-
return underLock.existingRecovery;
|
|
442
|
-
if (underLock.evidenceFingerprint !== preflight.evidenceFingerprint) {
|
|
443
|
-
throw new WorkflowError('STATE_CONFLICT', 'Remediation mode recovery evidence changed after preflight.', {
|
|
444
|
-
taskId: task.id,
|
|
445
|
-
stepId,
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
return store.appendSidecarEvent(root, REMEDIATION_MODE_RECOVERIES_SIDECAR, {
|
|
449
|
-
eventId: `RMR-${createUlid(now.getTime())}`,
|
|
450
|
-
recordedAt: now.toISOString(),
|
|
451
|
-
taskId: task.id,
|
|
452
|
-
stepId,
|
|
453
|
-
taskRevision: expectedRevision,
|
|
454
|
-
remediationEventId: underLock.remediation.eventId,
|
|
455
|
-
remediationEventHash: underLock.remediation.eventHash,
|
|
456
|
-
correctiveDecisionEventId: underLock.decision.eventId,
|
|
457
|
-
correctiveDecisionEventHash: underLock.decision.eventHash,
|
|
458
|
-
correctiveDecisionRecoveryEventId: underLock.correctiveRecovery.eventId,
|
|
459
|
-
correctiveDecisionRecoveryEventHash: underLock.correctiveRecovery.eventHash,
|
|
460
|
-
attemptOrdinal: 3,
|
|
461
|
-
planHash: currentTask.planHash,
|
|
462
|
-
actor: underLock.actor,
|
|
463
|
-
c1ClaimEventHash: underLock.c1ClaimEventHash,
|
|
464
|
-
writerLeaseTokenHash: underLock.writerLeaseTokenHash,
|
|
465
|
-
fromMode: 'ordinary',
|
|
466
|
-
toMode: 'corrective',
|
|
467
|
-
reason: 'alpha7.2-recovered-continue-fix-mode-omission',
|
|
468
|
-
}, {
|
|
469
|
-
validateEvent: (event, context) => validateStoredRemediationModeRecovery(event, currentTask, context.file, context.line),
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
finally {
|
|
473
|
-
if (existsSync(lockFile))
|
|
474
|
-
unlinkSync(lockFile);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
function requireRemediationModeRecoveryAuthority(store, task, actor, writerToken) {
|
|
478
|
-
const posture = readTaskC1Posture(store, task);
|
|
479
|
-
const lease = readWriterLeaseForModeRecovery(store, task);
|
|
480
|
-
if (posture.state === 'pending') {
|
|
481
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Pending C1 handoff must be claimed before remediation mode recovery.');
|
|
482
|
-
}
|
|
483
|
-
if (posture.state === 'claimed') {
|
|
484
|
-
const tokenHash = writerToken ? sha256Hex(writerToken) : null;
|
|
485
|
-
if (!lease
|
|
486
|
-
|| actor !== posture.claimant
|
|
487
|
-
|| lease.owner !== posture.claimant
|
|
488
|
-
|| !writerToken
|
|
489
|
-
|| lease.token !== writerToken
|
|
490
|
-
|| tokenHash !== posture.writerLeaseTokenHash) {
|
|
491
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Remediation mode recovery requires the claimed C1 actor and exact writer token.', {
|
|
492
|
-
taskId: task.id,
|
|
493
|
-
claimant: posture.claimant,
|
|
494
|
-
leaseOwner: lease?.owner ?? null,
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
return { c1ClaimEventHash: posture.latestEvent.eventHash, writerLeaseTokenHash: tokenHash };
|
|
498
|
-
}
|
|
499
|
-
if (lease && (lease.owner !== actor || !writerToken || lease.token !== writerToken)) {
|
|
500
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Existing writer lease is not owned by the remediation recovery actor/token.');
|
|
501
|
-
}
|
|
502
|
-
if (!lease && writerToken) {
|
|
503
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'A writer token was provided without an existing writer lease.');
|
|
504
|
-
}
|
|
505
|
-
return {
|
|
506
|
-
c1ClaimEventHash: posture.latestEvent?.eventHash ?? null,
|
|
507
|
-
writerLeaseTokenHash: lease ? sha256Hex(lease.token) : null,
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
function readWriterLeaseForModeRecovery(store, task) {
|
|
511
|
-
const file = path.join(store.lockRoot(task.projectId), `${task.id}.json`);
|
|
512
|
-
if (!existsSync(file))
|
|
513
|
-
return null;
|
|
514
|
-
let lease;
|
|
515
|
-
try {
|
|
516
|
-
lease = JSON.parse(readFileSync(file, 'utf8'));
|
|
517
|
-
}
|
|
518
|
-
catch {
|
|
519
|
-
throw new WorkflowError('STATE_CORRUPT', `Writer lease is unreadable: ${file}`);
|
|
520
|
-
}
|
|
521
|
-
if (lease.entityId !== task.id || typeof lease.owner !== 'string' || typeof lease.token !== 'string') {
|
|
522
|
-
throw new WorkflowError('STATE_CORRUPT', `Writer lease does not match Task ${task.id}.`);
|
|
523
|
-
}
|
|
524
|
-
return lease;
|
|
525
|
-
}
|
|
526
254
|
export function appendCorrectiveDecisionEvent(store, task, stepId, correctiveAudit, planRiskAudit, planHash, now) {
|
|
527
255
|
validateCorrectiveDecisionAudit(correctiveAudit);
|
|
528
256
|
const remediationEvents = readRemediationEvents(store, task).filter((event) => event.stepId === stepId);
|
|
@@ -589,176 +317,6 @@ export function appendCorrectiveDecisionEvent(store, task, stepId, correctiveAud
|
|
|
589
317
|
validateEvent: (event, context) => validateStoredCorrectiveDecisionEvent(event, task, context.file, context.line),
|
|
590
318
|
});
|
|
591
319
|
}
|
|
592
|
-
export function prepareStopEscalateOverride(store, task, stepId, expectedRevision, actor, reason, headCommit) {
|
|
593
|
-
if (task.revision !== expectedRevision) {
|
|
594
|
-
throw new WorkflowError('STATE_CONFLICT', `State revision conflict for ${task.id}`, {
|
|
595
|
-
expectedRevision,
|
|
596
|
-
actualRevision: task.revision,
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
const normalizedActor = actor.trim();
|
|
600
|
-
const normalizedReason = reason.trim();
|
|
601
|
-
if (!normalizedActor)
|
|
602
|
-
throw new WorkflowError('INVALID_ARGUMENT', 'Stop override requires a human actor.');
|
|
603
|
-
if (!normalizedReason)
|
|
604
|
-
throw new WorkflowError('INVALID_ARGUMENT', 'Stop override requires a reason.');
|
|
605
|
-
if (!COMMIT_SHA_PATTERN.test(headCommit)) {
|
|
606
|
-
throw new WorkflowError('INVALID_ARGUMENT', 'Stop override requires the exact current Git HEAD.');
|
|
607
|
-
}
|
|
608
|
-
if (task.status !== 'needs_fix') {
|
|
609
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Stop override requires a needs_fix Task.', {
|
|
610
|
-
taskId: task.id,
|
|
611
|
-
taskStatus: task.status,
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
const step = task.steps.find((candidate) => candidate.id === stepId) ?? null;
|
|
615
|
-
if (!step)
|
|
616
|
-
throw new WorkflowError('NOT_FOUND', `Step not found: ${stepId}`);
|
|
617
|
-
if (step.status !== 'failed') {
|
|
618
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Stop override requires the exact failed Step.', {
|
|
619
|
-
taskId: task.id,
|
|
620
|
-
stepId,
|
|
621
|
-
stepStatus: step.status,
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
if (!task.planHash)
|
|
625
|
-
throw new WorkflowError('STATE_CORRUPT', `Task ${task.id} is missing its Plan hash.`);
|
|
626
|
-
const history = readOpenGuardedRemediationHistory(store, task, stepId);
|
|
627
|
-
const remediations = history.remediationEvents;
|
|
628
|
-
if (remediations.length !== 3
|
|
629
|
-
|| remediations[0]?.attemptOrdinal !== 1
|
|
630
|
-
|| remediations[0]?.mode !== 'ordinary'
|
|
631
|
-
|| remediations[1]?.attemptOrdinal !== 2
|
|
632
|
-
|| remediations[1]?.mode !== 'ordinary'
|
|
633
|
-
|| remediations[2]?.attemptOrdinal !== 3
|
|
634
|
-
|| remediations[2]?.mode !== 'corrective') {
|
|
635
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Stop override is limited to the exact beta.11 three-failure history.', {
|
|
636
|
-
taskId: task.id,
|
|
637
|
-
stepId,
|
|
638
|
-
attempts: remediations.map((event) => ({ eventId: event.eventId, ordinal: event.attemptOrdinal, mode: event.mode })),
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
const priorContinueMatches = history.correctiveDecisions.filter((event) => event.triggeringAttemptCount === 3
|
|
642
|
-
&& event.decision === 'continue-fix'
|
|
643
|
-
&& event.planHash === task.planHash
|
|
644
|
-
&& sameStringArray(event.coveredEventIds, remediations.slice(0, 2).map((candidate) => candidate.eventId)));
|
|
645
|
-
const stopMatches = history.correctiveDecisions.filter((event) => event.triggeringAttemptCount === 4
|
|
646
|
-
&& event.decision === 'stop-escalate'
|
|
647
|
-
&& event.planHash === task.planHash
|
|
648
|
-
&& sameStringArray(event.coveredEventIds, remediations.map((candidate) => candidate.eventId)));
|
|
649
|
-
if (priorContinueMatches.length !== 1 || stopMatches.length !== 1) {
|
|
650
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Stop override requires one exact attempt-3 continue decision and one exact attempt-4 stop decision.', {
|
|
651
|
-
taskId: task.id,
|
|
652
|
-
stepId,
|
|
653
|
-
priorContinueDecisionEventIds: priorContinueMatches.map((event) => event.eventId),
|
|
654
|
-
stopDecisionEventIds: stopMatches.map((event) => event.eventId),
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
const priorContinue = priorContinueMatches[0];
|
|
658
|
-
const stopDecision = stopMatches[0];
|
|
659
|
-
const binding = {
|
|
660
|
-
taskId: task.id,
|
|
661
|
-
taskRevision: task.revision,
|
|
662
|
-
stepId,
|
|
663
|
-
stopDecisionEventId: stopDecision.eventId,
|
|
664
|
-
stopDecisionEventHash: stopDecision.eventHash,
|
|
665
|
-
priorContinueDecisionEventId: priorContinue.eventId,
|
|
666
|
-
priorContinueDecisionEventHash: priorContinue.eventHash,
|
|
667
|
-
triggeringAttemptCount: 4,
|
|
668
|
-
coveredEventIds: remediations.map((event) => event.eventId),
|
|
669
|
-
planHash: task.planHash,
|
|
670
|
-
headCommit,
|
|
671
|
-
actor: normalizedActor,
|
|
672
|
-
reason: normalizedReason,
|
|
673
|
-
packageVersion: PACKAGE_VERSION,
|
|
674
|
-
policy: LEGACY_STOP_OVERRIDE_POLICY,
|
|
675
|
-
};
|
|
676
|
-
const confirmationCodeBindingHash = stopEscalateOverrideBindingHash(binding);
|
|
677
|
-
const existing = readStopEscalateOverrideEvents(store, task).find((event) => event.stopDecisionEventId === stopDecision.eventId) ?? null;
|
|
678
|
-
if (existing && existing.confirmationCodeBindingHash !== confirmationCodeBindingHash) {
|
|
679
|
-
throw new WorkflowError('STATE_CONFLICT', 'The stop decision already has an override with a different human binding.', {
|
|
680
|
-
taskId: task.id,
|
|
681
|
-
stepId,
|
|
682
|
-
overrideEventId: existing.eventId,
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
return {
|
|
686
|
-
eligible: true,
|
|
687
|
-
action: 'task stop-override-apply',
|
|
688
|
-
...binding,
|
|
689
|
-
confirmationCodeBindingHash,
|
|
690
|
-
confirmationCode: `SOO-${confirmationCodeBindingHash.slice(0, 24).toUpperCase()}`,
|
|
691
|
-
appendOnly: true,
|
|
692
|
-
preservesOriginalDecision: true,
|
|
693
|
-
delegatedApprovalAllowed: false,
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
export function appendStopEscalateOverride(store, task, stepId, expectedRevision, actor, reason, headCommit, confirmationCode, now) {
|
|
697
|
-
const prepared = prepareStopEscalateOverride(store, task, stepId, expectedRevision, actor, reason, headCommit);
|
|
698
|
-
if (confirmationCode.trim() !== prepared.confirmationCode) {
|
|
699
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Stop override confirmation code does not match the current state binding.', {
|
|
700
|
-
taskId: task.id,
|
|
701
|
-
stepId,
|
|
702
|
-
confirmationCodeBindingHash: prepared.confirmationCodeBindingHash,
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
const root = store.taskRoot(task.projectId, task.id);
|
|
706
|
-
const existing = readStopEscalateOverrideEvents(store, task).find((event) => event.stopDecisionEventId === prepared.stopDecisionEventId) ?? null;
|
|
707
|
-
if (existing)
|
|
708
|
-
return existing;
|
|
709
|
-
const lockFile = path.join(root, STOP_ESCALATE_OVERRIDE_LOCK);
|
|
710
|
-
let descriptor;
|
|
711
|
-
try {
|
|
712
|
-
descriptor = openSync(lockFile, 'wx', 0o600);
|
|
713
|
-
closeSync(descriptor);
|
|
714
|
-
}
|
|
715
|
-
catch (error) {
|
|
716
|
-
if (existsSync(lockFile)) {
|
|
717
|
-
throw new WorkflowError('LOCKED', `Stop override for ${task.id} is already being serialized.`, {
|
|
718
|
-
taskId: task.id,
|
|
719
|
-
stepId,
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
throw error;
|
|
723
|
-
}
|
|
724
|
-
try {
|
|
725
|
-
const currentTask = store.readTask(task.projectId, task.id);
|
|
726
|
-
const underLock = prepareStopEscalateOverride(store, currentTask, stepId, expectedRevision, actor, reason, headCommit);
|
|
727
|
-
if (underLock.confirmationCode !== prepared.confirmationCode
|
|
728
|
-
|| underLock.confirmationCodeBindingHash !== prepared.confirmationCodeBindingHash) {
|
|
729
|
-
throw new WorkflowError('STATE_CONFLICT', 'Stop override evidence changed after preparation.', {
|
|
730
|
-
taskId: task.id,
|
|
731
|
-
stepId,
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
|
-
return store.appendSidecarEvent(root, STOP_ESCALATE_OVERRIDES_SIDECAR, {
|
|
735
|
-
eventId: `SOO-${createUlid(now.getTime())}`,
|
|
736
|
-
recordedAt: now.toISOString(),
|
|
737
|
-
taskId: underLock.taskId,
|
|
738
|
-
taskRevision: underLock.taskRevision,
|
|
739
|
-
stepId: underLock.stepId,
|
|
740
|
-
stopDecisionEventId: underLock.stopDecisionEventId,
|
|
741
|
-
stopDecisionEventHash: underLock.stopDecisionEventHash,
|
|
742
|
-
priorContinueDecisionEventId: underLock.priorContinueDecisionEventId,
|
|
743
|
-
priorContinueDecisionEventHash: underLock.priorContinueDecisionEventHash,
|
|
744
|
-
triggeringAttemptCount: 4,
|
|
745
|
-
coveredEventIds: underLock.coveredEventIds,
|
|
746
|
-
planHash: underLock.planHash,
|
|
747
|
-
headCommit: underLock.headCommit,
|
|
748
|
-
actor: underLock.actor,
|
|
749
|
-
reason: underLock.reason,
|
|
750
|
-
packageVersion: underLock.packageVersion,
|
|
751
|
-
confirmationCodeBindingHash: underLock.confirmationCodeBindingHash,
|
|
752
|
-
policy: LEGACY_STOP_OVERRIDE_POLICY,
|
|
753
|
-
}, {
|
|
754
|
-
validateEvent: (event, context) => validateStoredStopEscalateOverride(event, currentTask, context.file, context.line),
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
finally {
|
|
758
|
-
if (existsSync(lockFile))
|
|
759
|
-
unlinkSync(lockFile);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
320
|
export function appendPlanIntegrityRecoveryDecision(store, task, stepId, auditor, evidence, now) {
|
|
763
321
|
if (!task.planHash) {
|
|
764
322
|
throw new WorkflowError('STATE_CORRUPT', `Task ${task.id} has no Plan hash for Plan-integrity recovery.`);
|
|
@@ -766,7 +324,11 @@ export function appendPlanIntegrityRecoveryDecision(store, task, stepId, auditor
|
|
|
766
324
|
if (!auditor.trim()) {
|
|
767
325
|
throw new WorkflowError('INVALID_ARGUMENT', 'Plan-integrity recovery requires a derived auditor identity.');
|
|
768
326
|
}
|
|
769
|
-
const
|
|
327
|
+
const allRemediationEvents = readRemediationEvents(store, task).filter(event => event.stepId === stepId);
|
|
328
|
+
const support = evidence.kind === 'check-support-anchor-outside-allowed-writes' || evidence.kind === 'failed-check-component-owner-outside-allowed-writes';
|
|
329
|
+
const remediationEvents = support ? selectPlanIntegrityRemediations(store, task, allRemediationEvents, readCorrectiveDecisionEvents(store, task).filter(event => event.stepId === stepId)) : allRemediationEvents;
|
|
330
|
+
if (!remediationEvents)
|
|
331
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Mechanical recovery lacks a verified current Plan cycle.');
|
|
770
332
|
const expectedBindings = evidence.kind === 'missing-npm-script-outside-allowed-writes'
|
|
771
333
|
? [{
|
|
772
334
|
eventId: evidence.remediationEventId,
|
|
@@ -777,8 +339,7 @@ export function appendPlanIntegrityRecoveryDecision(store, task, stepId, auditor
|
|
|
777
339
|
if (remediationEvents.length !== expectedBindings.length
|
|
778
340
|
|| remediationEvents.some((event, index) => {
|
|
779
341
|
const binding = expectedBindings[index];
|
|
780
|
-
return event.
|
|
781
|
-
|| event.failureKind !== 'checks-failed'
|
|
342
|
+
return event.failureKind !== 'checks-failed'
|
|
782
343
|
|| event.eventId !== binding?.eventId
|
|
783
344
|
|| event.eventHash !== binding.eventHash
|
|
784
345
|
|| event.attemptOrdinal !== binding.attemptOrdinal;
|
|
@@ -789,7 +350,7 @@ export function appendPlanIntegrityRecoveryDecision(store, task, stepId, auditor
|
|
|
789
350
|
remediationEventIds: remediationEvents.map((event) => event.eventId),
|
|
790
351
|
});
|
|
791
352
|
}
|
|
792
|
-
const triggeringAttemptCount =
|
|
353
|
+
const triggeringAttemptCount = allRemediationEvents.length + 1;
|
|
793
354
|
const recoveryDecision = evidence.kind === 'check-support-anchor-outside-allowed-writes'
|
|
794
355
|
|| evidence.kind === 'failed-check-component-owner-outside-allowed-writes'
|
|
795
356
|
? 'continue-fix'
|
|
@@ -826,7 +387,7 @@ export function appendPlanIntegrityRecoveryDecision(store, task, stepId, auditor
|
|
|
826
387
|
decision: recoveryDecision,
|
|
827
388
|
auditor: auditor.trim(),
|
|
828
389
|
coveredFindingIds: [findingId],
|
|
829
|
-
coveredEventIds:
|
|
390
|
+
coveredEventIds: allRemediationEvents.map((event) => event.eventId),
|
|
830
391
|
planHash: task.planHash,
|
|
831
392
|
recoveryBasis: 'plan-integrity',
|
|
832
393
|
planIntegrityEvidence: evidence,
|
|
@@ -836,7 +397,7 @@ export function appendPlanIntegrityRecoveryDecision(store, task, stepId, auditor
|
|
|
836
397
|
}
|
|
837
398
|
export function assertGuardedRemediationAttemptAllowed(store, task, stepId, planRiskAudit, recoveredDecisionEventId = null, reboundStopOverrideDecisionEventId = null) {
|
|
838
399
|
const history = readOpenGuardedRemediationHistory(store, task, stepId);
|
|
839
|
-
return deriveAttemptGateFromHistory(task, stepId, planRiskAudit, history.remediationEvents, history.correctiveDecisions, {
|
|
400
|
+
return deriveAttemptGateFromHistory(store, task, stepId, planRiskAudit, history.remediationEvents, history.correctiveDecisions, {
|
|
840
401
|
enforceRuntimeGate: true,
|
|
841
402
|
recoveredDecisionEventId,
|
|
842
403
|
reboundStopOverrideDecisionEventId,
|
|
@@ -866,7 +427,7 @@ export function readGuardedRemediationPostureForStep(store, task, stepId, planRi
|
|
|
866
427
|
&& deriveCurrentCauseDecision(remediationEvents, history.priorRemediationEvents) !== 'root-cause-replan')
|
|
867
428
|
return null;
|
|
868
429
|
if (step.status === 'failed' || step.status === 'planned') {
|
|
869
|
-
return deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEvents, history.correctiveDecisions, {
|
|
430
|
+
return deriveAttemptGateFromHistory(store, task, stepId, planRiskAudit, remediationEvents, history.correctiveDecisions, {
|
|
870
431
|
overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
|
|
871
432
|
priorRemediationEvents: history.priorRemediationEvents,
|
|
872
433
|
nextAttemptOrdinal: history.nextAttemptOrdinal,
|
|
@@ -877,7 +438,7 @@ export function readGuardedRemediationPostureForStep(store, task, stepId, planRi
|
|
|
877
438
|
const currentCycle = buildCurrentStrictStepReviewCycle(store, task, stepId);
|
|
878
439
|
if (currentCycle.resolution !== 'unverified')
|
|
879
440
|
return null;
|
|
880
|
-
return deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEvents, history.correctiveDecisions, {
|
|
441
|
+
return deriveAttemptGateFromHistory(store, task, stepId, planRiskAudit, remediationEvents, history.correctiveDecisions, {
|
|
881
442
|
overriddenStopDecisionEventIds: stopEscalateOverrideDecisionEventIds(store, task),
|
|
882
443
|
priorRemediationEvents: history.priorRemediationEvents,
|
|
883
444
|
nextAttemptOrdinal: history.nextAttemptOrdinal,
|
|
@@ -898,7 +459,7 @@ function readOpenGuardedRemediationHistory(store, task, stepId) {
|
|
|
898
459
|
nextAttemptOrdinal: allStepRemediations.length + 1,
|
|
899
460
|
};
|
|
900
461
|
}
|
|
901
|
-
function selectCurrentPlanRemediationCycle(store, task, remediationEvents, correctiveDecisions) {
|
|
462
|
+
function selectCurrentPlanRemediationCycle(store, task, remediationEvents, correctiveDecisions, canonicalPlanHash = task.planHash) {
|
|
902
463
|
const unchanged = () => ({
|
|
903
464
|
remediationEvents: [...remediationEvents],
|
|
904
465
|
priorRemediationEvents: [],
|
|
@@ -916,13 +477,13 @@ function selectCurrentPlanRemediationCycle(store, task, remediationEvents, corre
|
|
|
916
477
|
const taskRoot = store.taskRoot(task.projectId, task.id);
|
|
917
478
|
let currentSemanticHash;
|
|
918
479
|
try {
|
|
919
|
-
currentSemanticHash = normalizePlanKnowledgeBinding(readVerifiedPlanArtifact(taskRoot, task.planHash,
|
|
480
|
+
currentSemanticHash = normalizePlanKnowledgeBinding(readVerifiedPlanArtifact(taskRoot, task.planHash, canonicalPlanHash), 'current').semanticHash;
|
|
920
481
|
}
|
|
921
482
|
catch {
|
|
922
483
|
return unchanged();
|
|
923
484
|
}
|
|
924
485
|
const reviews = readStepReviewEvents(store, task);
|
|
925
|
-
const bindings = remediationEvents.map((event) => resolveRemediationPlanCycleBinding(taskRoot,
|
|
486
|
+
const bindings = remediationEvents.map((event) => resolveRemediationPlanCycleBinding(taskRoot, canonicalPlanHash, event, reviews, audits));
|
|
926
487
|
if (bindings.some((binding) => binding === null))
|
|
927
488
|
return unchanged();
|
|
928
489
|
let cycleStart = remediationEvents.length;
|
|
@@ -939,6 +500,71 @@ function selectCurrentPlanRemediationCycle(store, task, remediationEvents, corre
|
|
|
939
500
|
correctiveDecisions: [...correctiveDecisions],
|
|
940
501
|
};
|
|
941
502
|
}
|
|
503
|
+
/** Scope mechanical support evidence only; global corrective authority is never discarded. */
|
|
504
|
+
export function selectPlanIntegrityRemediations(store, task, events, decisions, canonicalPlanHash = task.planHash) {
|
|
505
|
+
if (events.length === 0)
|
|
506
|
+
return null;
|
|
507
|
+
const stepId = events[0].stepId;
|
|
508
|
+
if (events.some(event => event.stepId !== stepId) || decisions.some(event => event.stepId !== stepId))
|
|
509
|
+
return null;
|
|
510
|
+
const cycle = selectCurrentPlanRemediationCycle(store, task, events, decisions, canonicalPlanHash);
|
|
511
|
+
if (cycle.priorRemediationEvents.length === 0)
|
|
512
|
+
return decisions.length === 0 ? [...events] : null;
|
|
513
|
+
if (!task.planHash || cycle.remediationEvents.length === 0
|
|
514
|
+
|| decisions.some(d => d.decision === 'stop-escalate' || d.decision === 'split-required'))
|
|
515
|
+
return null;
|
|
516
|
+
const root = store.taskRoot(task.projectId, task.id);
|
|
517
|
+
const history = readCompletedCorrectiveReplanHistory(store, task);
|
|
518
|
+
const semantic = (hash) => {
|
|
519
|
+
const saved = history.flatMap(boundary => [boundary.beforePlan, boundary.afterPlan]).find(bytes => sha256Hex(bytes) === hash);
|
|
520
|
+
return normalizePlanKnowledgeBinding(saved ?? readVerifiedPlanArtifact(root, hash, canonicalPlanHash), 'cycle').semanticHash;
|
|
521
|
+
};
|
|
522
|
+
const currentSemantic = semantic(task.planHash);
|
|
523
|
+
const audits = readPlanRiskAuditEvents(store, task), reviews = readStepReviewEvents(store, task);
|
|
524
|
+
const priorBindings = cycle.priorRemediationEvents.map(event => resolveRemediationPlanCycleBinding(root, canonicalPlanHash, event, reviews, audits));
|
|
525
|
+
const currentBindings = cycle.remediationEvents.map(event => resolveRemediationPlanCycleBinding(root, canonicalPlanHash, event, reviews, audits));
|
|
526
|
+
if (priorBindings.some(b => !b) || currentBindings.some(b => !b))
|
|
527
|
+
return null;
|
|
528
|
+
const boundaries = history.filter(boundary => correctiveReplanBoundaryMatchesStep(boundary.before, stepId)
|
|
529
|
+
&& boundary.after.planHash && boundary.before.planHash
|
|
530
|
+
&& semantic(boundary.after.planHash) === currentSemantic && semantic(boundary.before.planHash) !== currentSemantic
|
|
531
|
+
&& boundary.after.revision <= task.revision
|
|
532
|
+
&& priorBindings.every(b => b.sourceTaskRevision < boundary.after.revision)
|
|
533
|
+
&& currentBindings.every(b => b.sourceTaskRevision > boundary.after.revision));
|
|
534
|
+
if (boundaries.length !== 1)
|
|
535
|
+
return null;
|
|
536
|
+
const boundary = boundaries[0];
|
|
537
|
+
let cursor = task.planHash;
|
|
538
|
+
const visited = new Set();
|
|
539
|
+
const planBytes = (hash) => history.flatMap(b => [b.beforePlan, b.afterPlan]).find(bytes => sha256Hex(bytes) === hash)
|
|
540
|
+
?? readVerifiedPlanArtifact(root, hash, canonicalPlanHash);
|
|
541
|
+
while (cursor !== boundary.after.planHash) {
|
|
542
|
+
if (visited.has(cursor))
|
|
543
|
+
return null;
|
|
544
|
+
visited.add(cursor);
|
|
545
|
+
const links = task.knowledgeRebinds.filter(link => link.reboundPlanHash === cursor);
|
|
546
|
+
if (links.length !== 1 || !links[0].previousPlanHash)
|
|
547
|
+
return null;
|
|
548
|
+
const link = links[0];
|
|
549
|
+
assertKnowledgeBindingOnlyPlanChange(planBytes(link.previousPlanHash), planBytes(cursor));
|
|
550
|
+
if (!planBytes(link.previousPlanHash).includes(`- Knowledge map: revision ${link.fromKnowledgeMapRevision}, hash ${link.fromKnowledgeMapHash}\n`)
|
|
551
|
+
|| !planBytes(cursor).includes(`- Knowledge map: revision ${link.toKnowledgeMapRevision}, hash ${link.toKnowledgeMapHash}\n`))
|
|
552
|
+
return null;
|
|
553
|
+
cursor = link.previousPlanHash;
|
|
554
|
+
}
|
|
555
|
+
if (!task.authorizations.some(a => a.kind === 'execution' && ['approved', 'superseded'].includes(a.decision)
|
|
556
|
+
&& a.planHash === task.planHash && a.briefHash === task.briefHash
|
|
557
|
+
&& a.recordedAt >= boundary.admittedAt))
|
|
558
|
+
return null;
|
|
559
|
+
if (decisions.length === 0 || decisions.some(d => d.recordedAt > boundary.admittedAt
|
|
560
|
+
|| semantic(d.planHash) === currentSemantic))
|
|
561
|
+
return null;
|
|
562
|
+
const last = decisions.at(-1);
|
|
563
|
+
if (last.decision !== 'replan-required' || last.triggeringAttemptCount !== cycle.priorRemediationEvents.length + 1
|
|
564
|
+
|| semantic(last.planHash) !== semantic(boundary.before.planHash))
|
|
565
|
+
return null;
|
|
566
|
+
return cycle.remediationEvents;
|
|
567
|
+
}
|
|
942
568
|
function resolveRemediationPlanCycleBinding(taskRoot, currentPlanHash, event, reviews, audits) {
|
|
943
569
|
let planHash = null;
|
|
944
570
|
let sourceTaskRevision = null;
|
|
@@ -994,7 +620,7 @@ export function findFailedGuardedStepRequiringCorrectiveDecision(store, task, pl
|
|
|
994
620
|
? posture
|
|
995
621
|
: null;
|
|
996
622
|
}
|
|
997
|
-
function deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEvents, correctiveDecisions, options = {}) {
|
|
623
|
+
function deriveAttemptGateFromHistory(store, task, stepId, planRiskAudit, remediationEvents, correctiveDecisions, options = {}) {
|
|
998
624
|
const attemptCount = remediationEvents.length + (options.priorRemediationEvents?.length ?? 0);
|
|
999
625
|
const nextAttemptOrdinal = options.nextAttemptOrdinal ?? attemptCount + 1;
|
|
1000
626
|
const guardedCategory = requireGuardedCategory(planRiskAudit, stepId);
|
|
@@ -1002,8 +628,9 @@ function deriveAttemptGateFromHistory(task, stepId, planRiskAudit, remediationEv
|
|
|
1002
628
|
const currentCorrectiveDecision = findCurrentCorrectiveDecision(task, stepId, nextAttemptOrdinal, correctiveDecisions);
|
|
1003
629
|
const planIntegrityDecision = currentCorrectiveDecision;
|
|
1004
630
|
if (planIntegrityDecision?.recoveryBasis === 'plan-integrity'
|
|
1005
|
-
&& (planIntegrityDecision.planHash === task.planHash || planIntegrityDecision.decision === 'replan-required'
|
|
1006
|
-
|
|
631
|
+
&& (planIntegrityDecision.planHash === task.planHash || planIntegrityDecision.decision === 'replan-required'
|
|
632
|
+
|| isKnowledgeReboundSupportDecision(store, task, planIntegrityDecision))) {
|
|
633
|
+
if (planIntegrityDecision.planHash !== task.planHash && planIntegrityDecision.decision === 'replan-required') {
|
|
1007
634
|
return {
|
|
1008
635
|
stepId,
|
|
1009
636
|
guardedCategory,
|
|
@@ -1310,6 +937,17 @@ function validateStoredRemediationEvent(event, task, file, line) {
|
|
|
1310
937
|
if (hasDirtyWorktreeHash) {
|
|
1311
938
|
assertHash64(event.failureEvidence.dirtyWorktreeHash, `Remediation dirty-worktree hash at ${file}:${line}`);
|
|
1312
939
|
}
|
|
940
|
+
const boundary = event.failureEvidence.unclaimedExecutionBoundary;
|
|
941
|
+
if (boundary !== undefined) {
|
|
942
|
+
if (!boundary || typeof boundary !== 'object' || Array.isArray(boundary)
|
|
943
|
+
|| Object.keys(boundary).sort().join(',') !== 'actor,kind,priorHandoffEventHash'
|
|
944
|
+
|| boundary.kind !== 'native-unclaimed-check-failure'
|
|
945
|
+
|| typeof boundary.actor !== 'string' || !boundary.actor.trim() || boundary.actor !== boundary.actor.trim()
|
|
946
|
+
|| !(boundary.priorHandoffEventHash === null || typeof boundary.priorHandoffEventHash === 'string' && /^[a-f0-9]{64}$/.test(boundary.priorHandoffEventHash))
|
|
947
|
+
|| event.failureKind !== 'checks-failed' || !hasExecutionRevision || !hasDirtyWorktreeHash) {
|
|
948
|
+
throw new WorkflowError('STATE_CORRUPT', 'Invalid native unclaimed execution boundary.', { file, line, eventId: event.eventId });
|
|
949
|
+
}
|
|
950
|
+
}
|
|
1313
951
|
if (!Number.isInteger(event.attemptOrdinal) || event.attemptOrdinal <= 0) {
|
|
1314
952
|
throw new WorkflowError('STATE_CORRUPT', 'Remediation event attemptOrdinal must be a positive integer.', {
|
|
1315
953
|
file,
|
|
@@ -1638,9 +1276,9 @@ function validatePlanIntegrityDecision(event, task, file, line) {
|
|
|
1638
1276
|
const validateCurrentPlanBinding = event.planHash === task.planHash;
|
|
1639
1277
|
if (event.decision !== 'continue-fix'
|
|
1640
1278
|
|| evidence.remediationEvents.length === 0
|
|
1641
|
-
|| event.triggeringAttemptCount !== evidence.remediationEvents.
|
|
1279
|
+
|| event.triggeringAttemptCount !== evidence.remediationEvents.at(-1).attemptOrdinal + 1
|
|
1642
1280
|
|| evidence.remediationEvents.some((binding, index) => {
|
|
1643
|
-
if (!binding.eventId || binding.attemptOrdinal !==
|
|
1281
|
+
if (!binding.eventId || binding.attemptOrdinal !== evidence.remediationEvents[0].attemptOrdinal + index)
|
|
1644
1282
|
return true;
|
|
1645
1283
|
assertHash64(binding.eventHash, `Plan-integrity remediation event hash at ${file}:${line}`);
|
|
1646
1284
|
return false;
|
|
@@ -1681,9 +1319,9 @@ function validatePlanIntegrityDecision(event, task, file, line) {
|
|
|
1681
1319
|
assertHash64(evidence.failedCheckCauseFingerprint, `Plan-integrity failedCheckCauseFingerprint at ${file}:${line}`);
|
|
1682
1320
|
if (event.decision !== 'continue-fix'
|
|
1683
1321
|
|| evidence.remediationEvents.length === 0
|
|
1684
|
-
|| event.triggeringAttemptCount !== evidence.remediationEvents.
|
|
1322
|
+
|| event.triggeringAttemptCount !== evidence.remediationEvents.at(-1).attemptOrdinal + 1
|
|
1685
1323
|
|| evidence.remediationEvents.some((binding, index) => {
|
|
1686
|
-
if (!binding.eventId || binding.attemptOrdinal !==
|
|
1324
|
+
if (!binding.eventId || binding.attemptOrdinal !== evidence.remediationEvents[0].attemptOrdinal + index)
|
|
1687
1325
|
return true;
|
|
1688
1326
|
assertHash64(binding.eventHash, `Plan-integrity remediation event hash at ${file}:${line}`);
|
|
1689
1327
|
return false;
|
|
@@ -1706,9 +1344,10 @@ function validatePlanIntegrityDecision(event, task, file, line) {
|
|
|
1706
1344
|
|| !conflict.componentRoot.trim()
|
|
1707
1345
|
|| !(conflict.ownerPath === conflict.componentRoot
|
|
1708
1346
|
|| conflict.ownerPath.startsWith(`${conflict.componentRoot}/`))
|
|
1709
|
-
||
|
|
1347
|
+
|| (conflict.testInvocation
|
|
1348
|
+
? !validLiteralTestInvocationEvidence(conflict) || importChain.length !== 1
|
|
1349
|
+
: importChain.length < 2 || importChain.at(-1) !== conflict.ownerPath)
|
|
1710
1350
|
|| importChain[0] !== conflict.runnerPath
|
|
1711
|
-
|| importChain.at(-1) !== conflict.ownerPath
|
|
1712
1351
|
|| !evidence.scopeExtensions.includes(conflict.ownerPath);
|
|
1713
1352
|
})) {
|
|
1714
1353
|
throw new WorkflowError('STATE_CORRUPT', 'Plan-integrity v4 evidence has invalid component-owner bindings.', {
|
|
@@ -1744,13 +1383,54 @@ function validatePlanIntegrityDecision(event, task, file, line) {
|
|
|
1744
1383
|
throw new WorkflowError('STATE_CORRUPT', 'Plan-integrity changedFiles must be normalized.', { file, line });
|
|
1745
1384
|
}
|
|
1746
1385
|
}
|
|
1386
|
+
/** A validated support decision survives only a hash-verified Knowledge-only Plan chain.
|
|
1387
|
+
* This preserves scope evidence, never execution authorization or a semantic replan. */
|
|
1388
|
+
export function isKnowledgeReboundSupportDecision(store, task, event) {
|
|
1389
|
+
const evidence = event.planIntegrityEvidence;
|
|
1390
|
+
if (!task.planHash || event.planHash === task.planHash || event.taskId !== task.id
|
|
1391
|
+
|| event.recoveryBasis !== 'plan-integrity' || event.decision !== 'continue-fix'
|
|
1392
|
+
|| (evidence?.kind !== 'check-support-anchor-outside-allowed-writes'
|
|
1393
|
+
&& evidence?.kind !== 'failed-check-component-owner-outside-allowed-writes'))
|
|
1394
|
+
return false;
|
|
1395
|
+
const step = task.steps.find(s => s.id === event.stepId);
|
|
1396
|
+
if (!step || evidence.stepDefinitionHash !== hashCanonical({
|
|
1397
|
+
id: step.id, title: step.title, objective: step.objective, requirements: step.requirements,
|
|
1398
|
+
dependencies: step.dependencies, expectedOutputs: step.expectedOutputs,
|
|
1399
|
+
allowedWrites: step.allowedWrites, forbiddenScope: step.forbiddenScope, checks: step.checks,
|
|
1400
|
+
}) || evidence.scopeExtensions.some(p => pathAllowed(p, step.allowedWrites) || pathAllowed(p, step.forbiddenScope))
|
|
1401
|
+
|| (evidence.kind === 'failed-check-component-owner-outside-allowed-writes'
|
|
1402
|
+
&& evidence.semanticBoundaryHash !== semanticBoundaryHash(task)))
|
|
1403
|
+
return false;
|
|
1404
|
+
const root = store.taskRoot(task.projectId, task.id);
|
|
1405
|
+
let cursor = task.planHash;
|
|
1406
|
+
const visited = new Set();
|
|
1407
|
+
while (cursor !== event.planHash) {
|
|
1408
|
+
if (visited.has(cursor))
|
|
1409
|
+
return false;
|
|
1410
|
+
visited.add(cursor);
|
|
1411
|
+
const links = task.knowledgeRebinds.filter(link => link.reboundPlanHash === cursor);
|
|
1412
|
+
if (links.length !== 1 || !links[0].previousPlanHash)
|
|
1413
|
+
return false;
|
|
1414
|
+
const link = links[0];
|
|
1415
|
+
const before = readVerifiedPlanArtifact(root, link.previousPlanHash, task.planHash);
|
|
1416
|
+
const after = readVerifiedPlanArtifact(root, cursor, task.planHash);
|
|
1417
|
+
assertKnowledgeBindingOnlyPlanChange(before, after);
|
|
1418
|
+
const from = normalizePlanKnowledgeBinding(before, 'source').binding;
|
|
1419
|
+
const to = normalizePlanKnowledgeBinding(after, 'target').binding;
|
|
1420
|
+
if (from.revision !== link.fromKnowledgeMapRevision || from.hash !== link.fromKnowledgeMapHash
|
|
1421
|
+
|| to.revision !== link.toKnowledgeMapRevision || to.hash !== link.toKnowledgeMapHash)
|
|
1422
|
+
return false;
|
|
1423
|
+
cursor = link.previousPlanHash;
|
|
1424
|
+
}
|
|
1425
|
+
return true;
|
|
1426
|
+
}
|
|
1747
1427
|
export function effectiveStepAllowedWrites(store, task, stepId) {
|
|
1748
1428
|
const step = task.steps.find((candidate) => candidate.id === stepId);
|
|
1749
1429
|
if (!step)
|
|
1750
1430
|
throw new WorkflowError('NOT_FOUND', `Step not found: ${stepId}`);
|
|
1751
1431
|
const extensions = readCorrectiveDecisionEvents(store, task)
|
|
1752
1432
|
.filter((event) => event.stepId === stepId
|
|
1753
|
-
&& event.planHash === task.planHash
|
|
1433
|
+
&& (event.planHash === task.planHash || isKnowledgeReboundSupportDecision(store, task, event))
|
|
1754
1434
|
&& event.decision === 'continue-fix'
|
|
1755
1435
|
&& event.recoveryBasis === 'plan-integrity')
|
|
1756
1436
|
.flatMap((event) => event.planIntegrityEvidence?.kind === 'check-support-anchor-outside-allowed-writes'
|
|
@@ -2020,7 +1700,7 @@ function validateRemediationReviewReferences(store, task, events) {
|
|
|
2020
1700
|
}
|
|
2021
1701
|
}
|
|
2022
1702
|
}
|
|
2023
|
-
function validateCorrectiveDecisionCoverage(task, decisions, remediationEvents) {
|
|
1703
|
+
function validateCorrectiveDecisionCoverage(store, task, decisions, remediationEvents) {
|
|
2024
1704
|
const remediationEventsById = new Map(remediationEvents.map((event) => [event.eventId, event]));
|
|
2025
1705
|
const decisionBindings = new Set();
|
|
2026
1706
|
for (const decision of decisions) {
|
|
@@ -2071,9 +1751,14 @@ function validateCorrectiveDecisionCoverage(task, decisions, remediationEvents)
|
|
|
2071
1751
|
const bindings = evidence?.kind === 'missing-npm-script-outside-allowed-writes'
|
|
2072
1752
|
? [{ eventId: evidence.remediationEventId, eventHash: evidence.remediationEventHash, attemptOrdinal: 1 }]
|
|
2073
1753
|
: evidence?.remediationEvents ?? [];
|
|
2074
|
-
|
|
2075
|
-
||
|
|
2076
|
-
|
|
1754
|
+
const support = evidence?.kind === 'check-support-anchor-outside-allowed-writes'
|
|
1755
|
+
|| evidence?.kind === 'failed-check-component-owner-outside-allowed-writes';
|
|
1756
|
+
const scoped = support && bindings[0]?.attemptOrdinal !== 1
|
|
1757
|
+
? selectPlanIntegrityRemediations(store, { ...task, planHash: decision.planHash, revision: evidence.taskRevision }, priorEvents, decisions.filter(d => d.stepId === decision.stepId && d.triggeringAttemptCount < decision.triggeringAttemptCount), task.planHash)
|
|
1758
|
+
: priorEvents;
|
|
1759
|
+
if (!evidence || !scoped
|
|
1760
|
+
|| bindings.length !== scoped.length
|
|
1761
|
+
|| scoped.some((linked, index) => linked.failureKind !== 'checks-failed'
|
|
2077
1762
|
|| linked.eventId !== bindings[index]?.eventId
|
|
2078
1763
|
|| linked.eventHash !== bindings[index]?.eventHash
|
|
2079
1764
|
|| linked.attemptOrdinal !== bindings[index]?.attemptOrdinal)) {
|