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.
Files changed (281) hide show
  1. package/README.md +27 -388
  2. package/dist/reviewer-runtime-build.json +96 -32
  3. package/dist/src/alpha6/adoption.d.ts +2 -0
  4. package/dist/src/alpha6/adoption.js +22 -0
  5. package/dist/src/alpha6/adoption.js.map +1 -1
  6. package/dist/src/alpha6/captured-check-evidence.d.ts +51 -0
  7. package/dist/src/alpha6/captured-check-evidence.js +152 -0
  8. package/dist/src/alpha6/captured-check-evidence.js.map +1 -0
  9. package/dist/src/alpha6/component-owner.d.ts +15 -1
  10. package/dist/src/alpha6/component-owner.js +44 -2
  11. package/dist/src/alpha6/component-owner.js.map +1 -1
  12. package/dist/src/alpha6/corrective-decision-boundary.d.ts +4 -0
  13. package/dist/src/alpha6/corrective-decision-boundary.js +47 -0
  14. package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -0
  15. package/dist/src/alpha6/downstream-proof.d.ts +1 -25
  16. package/dist/src/alpha6/downstream-proof.js +0 -171
  17. package/dist/src/alpha6/downstream-proof.js.map +1 -1
  18. package/dist/src/alpha6/failed-step-planning-recovery.d.ts +10 -0
  19. package/dist/src/alpha6/failed-step-planning-recovery.js +93 -0
  20. package/dist/src/alpha6/failed-step-planning-recovery.js.map +1 -0
  21. package/dist/src/alpha6/literal-test-invocation.d.ts +2 -0
  22. package/dist/src/alpha6/literal-test-invocation.js +106 -0
  23. package/dist/src/alpha6/literal-test-invocation.js.map +1 -0
  24. package/dist/src/alpha6/milestone.d.ts +2 -0
  25. package/dist/src/alpha6/milestone.js +10 -1
  26. package/dist/src/alpha6/milestone.js.map +1 -1
  27. package/dist/src/alpha6/navigation-dirty-carryover.d.ts +11 -0
  28. package/dist/src/alpha6/navigation-dirty-carryover.js +130 -0
  29. package/dist/src/alpha6/navigation-dirty-carryover.js.map +1 -0
  30. package/dist/src/alpha6/plan-integrity.js +7 -5
  31. package/dist/src/alpha6/plan-integrity.js.map +1 -1
  32. package/dist/src/alpha6/plan-risk.d.ts +2 -0
  33. package/dist/src/alpha6/plan-risk.js +10 -3
  34. package/dist/src/alpha6/plan-risk.js.map +1 -1
  35. package/dist/src/alpha6/remediation.d.ts +6 -21
  36. package/dist/src/alpha6/remediation.js +161 -476
  37. package/dist/src/alpha6/remediation.js.map +1 -1
  38. package/dist/src/alpha6/root-cause-replan-carryover.d.ts +4 -1
  39. package/dist/src/alpha6/root-cause-replan-carryover.js +29 -5
  40. package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -1
  41. package/dist/src/beta1/project-transaction.d.ts +3 -1
  42. package/dist/src/beta1/project-transaction.js +6 -2
  43. package/dist/src/beta1/project-transaction.js.map +1 -1
  44. package/dist/src/checks/runner.d.ts +26 -0
  45. package/dist/src/checks/runner.js +223 -0
  46. package/dist/src/checks/runner.js.map +1 -0
  47. package/dist/src/checks/task-sync.d.ts +10 -0
  48. package/dist/src/checks/task-sync.js +41 -0
  49. package/dist/src/checks/task-sync.js.map +1 -0
  50. package/dist/src/checks/worker.d.ts +1 -0
  51. package/dist/src/checks/worker.js +154 -0
  52. package/dist/src/checks/worker.js.map +1 -0
  53. package/dist/src/cli-actions.d.ts +2 -2
  54. package/dist/src/cli-actions.js +3 -15
  55. package/dist/src/cli-actions.js.map +1 -1
  56. package/dist/src/cli.js +14 -98
  57. package/dist/src/cli.js.map +1 -1
  58. package/dist/src/contracts.d.ts +35 -32
  59. package/dist/src/dependency-provenance.d.ts +2 -2
  60. package/dist/src/dependency-provenance.js +12 -69
  61. package/dist/src/dependency-provenance.js.map +1 -1
  62. package/dist/src/domain/base-sync-conflict.d.ts +13 -0
  63. package/dist/src/domain/base-sync-conflict.js +32 -0
  64. package/dist/src/domain/base-sync-conflict.js.map +1 -0
  65. package/dist/src/domain/step-start-admission.d.ts +4 -0
  66. package/dist/src/domain/step-start-admission.js +9 -0
  67. package/dist/src/domain/step-start-admission.js.map +1 -0
  68. package/dist/src/domain/virgin-registration.d.ts +3 -0
  69. package/dist/src/domain/virgin-registration.js +65 -0
  70. package/dist/src/domain/virgin-registration.js.map +1 -0
  71. package/dist/src/errors.d.ts +1 -1
  72. package/dist/src/errors.js.map +1 -1
  73. package/dist/src/gateway-handshake.js +0 -8
  74. package/dist/src/gateway-handshake.js.map +1 -1
  75. package/dist/src/git.d.ts +3 -1
  76. package/dist/src/git.js +41 -14
  77. package/dist/src/git.js.map +1 -1
  78. package/dist/src/graph.js +25 -3
  79. package/dist/src/graph.js.map +1 -1
  80. package/dist/src/index.d.ts +1 -0
  81. package/dist/src/lifecycle/corrective-replan.js +3 -3
  82. package/dist/src/lifecycle/corrective-replan.js.map +1 -1
  83. package/dist/src/navigation-actions.d.ts +9 -0
  84. package/dist/src/navigation-actions.js +72 -0
  85. package/dist/src/navigation-actions.js.map +1 -0
  86. package/dist/src/navigation-update-artifact.d.ts +5 -0
  87. package/dist/src/navigation-update-artifact.js +227 -0
  88. package/dist/src/navigation-update-artifact.js.map +1 -0
  89. package/dist/src/navigation-update.d.ts +39 -0
  90. package/dist/src/navigation-update.js +82 -0
  91. package/dist/src/navigation-update.js.map +1 -0
  92. package/dist/src/observation.js +52 -29
  93. package/dist/src/observation.js.map +1 -1
  94. package/dist/src/observed-routes.js +5 -6
  95. package/dist/src/observed-routes.js.map +1 -1
  96. package/dist/src/pending-review-update.d.ts +0 -13
  97. package/dist/src/pending-review-update.js +1 -6
  98. package/dist/src/pending-review-update.js.map +1 -1
  99. package/dist/src/reviewer.d.ts +1 -1
  100. package/dist/src/reviewer.js +20 -17
  101. package/dist/src/reviewer.js.map +1 -1
  102. package/dist/src/runtime.d.ts +3 -0
  103. package/dist/src/runtime.js +6 -0
  104. package/dist/src/runtime.js.map +1 -0
  105. package/dist/src/state/corrective-replan-executor.d.ts +10 -0
  106. package/dist/src/state/corrective-replan-executor.js +37 -1
  107. package/dist/src/state/corrective-replan-executor.js.map +1 -1
  108. package/dist/src/state/corrective-replan-public-schema.js +15 -2
  109. package/dist/src/state/corrective-replan-public-schema.js.map +1 -1
  110. package/dist/src/state/corrective-replan-public.js +6 -5
  111. package/dist/src/state/corrective-replan-public.js.map +1 -1
  112. package/dist/src/state/corrective-replan-transaction.d.ts +1 -0
  113. package/dist/src/state/corrective-replan-transaction.js +12 -11
  114. package/dist/src/state/corrective-replan-transaction.js.map +1 -1
  115. package/dist/src/state/corrective-yield-executor.js +2 -2
  116. package/dist/src/state/corrective-yield-executor.js.map +1 -1
  117. package/dist/src/state/corrective-yield-transaction.d.ts +1 -0
  118. package/dist/src/state/corrective-yield-transaction.js +11 -10
  119. package/dist/src/state/corrective-yield-transaction.js.map +1 -1
  120. package/dist/src/state/lock.d.ts +4 -0
  121. package/dist/src/state/lock.js +21 -0
  122. package/dist/src/state/lock.js.map +1 -1
  123. package/dist/src/version.d.ts +1 -1
  124. package/dist/src/version.js +1 -1
  125. package/dist/src/version.js.map +1 -1
  126. package/dist/src/workflow-blocker-route.d.ts +8 -0
  127. package/dist/src/workflow-blocker-route.js +106 -0
  128. package/dist/src/workflow-blocker-route.js.map +1 -0
  129. package/dist/src/workflow.d.ts +48 -63
  130. package/dist/src/workflow.js +698 -1470
  131. package/dist/src/workflow.js.map +1 -1
  132. package/docs/autonomy-guardrails.md +26 -304
  133. package/docs/decisions.md +14 -112
  134. package/docs/development-flow.md +35 -238
  135. package/docs/project-memory.md +31 -50
  136. package/docs/release-app-evidence.md +190 -0
  137. package/docs/release.md +95 -363
  138. package/docs/updating-existing-project.md +19 -717
  139. package/package.json +11 -13
  140. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +2 -2
  141. package/plugins/codex-workflow-gateway/references/chat-dispatch.md +73 -197
  142. package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
  143. package/plugins/codex-workflow-gateway/references/protocol.md +42 -445
  144. package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +7 -1
  145. package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +10 -8
  146. package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
  147. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +66 -740
  148. package/references/state-machine.md +4 -4
  149. package/roles/technical-planner.md +1 -1
  150. package/schemas/corrective-decision-event.schema.json +3 -1
  151. package/schemas/project-knowledge-map.schema.json +3 -1
  152. package/schemas/remediation-event.schema.json +10 -1
  153. package/schemas/task.schema.json +46 -2
  154. package/schemas/transition-payloads.schema.json +16 -1
  155. package/src/alpha6/adoption.ts +1123 -0
  156. package/src/alpha6/captured-check-evidence.ts +132 -0
  157. package/src/alpha6/check-support-anchor.ts +128 -0
  158. package/src/alpha6/component-owner.ts +168 -0
  159. package/src/alpha6/corrective-decision-boundary.ts +39 -0
  160. package/src/alpha6/downstream-proof.ts +520 -0
  161. package/src/alpha6/failed-step-planning-recovery.ts +88 -0
  162. package/src/alpha6/handoff.ts +1443 -0
  163. package/src/alpha6/journal.ts +473 -0
  164. package/src/alpha6/literal-test-invocation.ts +84 -0
  165. package/src/alpha6/mechanical-feasibility.ts +488 -0
  166. package/src/alpha6/milestone.ts +2192 -0
  167. package/src/alpha6/navigation-dirty-carryover.ts +154 -0
  168. package/src/alpha6/npm-check-contract.ts +47 -0
  169. package/src/alpha6/plan-integrity.ts +298 -0
  170. package/src/alpha6/plan-risk.ts +1480 -0
  171. package/src/alpha6/preexecution-replan.ts +187 -0
  172. package/src/alpha6/remediation-cause.ts +98 -0
  173. package/src/alpha6/remediation.ts +2438 -0
  174. package/src/alpha6/review.ts +1198 -0
  175. package/src/alpha6/root-cause-replan-carryover.ts +491 -0
  176. package/src/alpha6/store-sidecars.ts +335 -0
  177. package/src/alpha7/autonomy.ts +411 -0
  178. package/src/alpha7/corrective-recovery.ts +1332 -0
  179. package/src/artifacts.ts +130 -0
  180. package/src/beta1/project-transaction.ts +355 -0
  181. package/src/change-explanation.ts +153 -0
  182. package/src/checks/runner.ts +245 -0
  183. package/src/checks/task-sync.ts +41 -0
  184. package/src/checks/worker.ts +149 -0
  185. package/src/cli-actions.ts +107 -0
  186. package/src/cli.ts +1457 -0
  187. package/src/contracts.ts +1494 -0
  188. package/src/credential-output.ts +89 -0
  189. package/src/credential-transport.ts +215 -0
  190. package/src/delegation.ts +190 -0
  191. package/src/dependency-provenance.ts +472 -0
  192. package/src/diagnostics.ts +93 -0
  193. package/src/domain/base-sync-conflict.ts +35 -0
  194. package/src/domain/completed-step-carryover.ts +76 -0
  195. package/src/domain/discovery.ts +27 -0
  196. package/src/domain/plan-semantics.ts +58 -0
  197. package/src/domain/step-start-admission.ts +10 -0
  198. package/src/domain/validation.ts +177 -0
  199. package/src/domain/virgin-registration.ts +46 -0
  200. package/src/errors.ts +24 -0
  201. package/src/fs-utils.ts +61 -0
  202. package/src/gateway-handshake.ts +95 -0
  203. package/src/git.ts +183 -0
  204. package/src/graph.ts +342 -0
  205. package/src/historical-step-provenance.ts +136 -0
  206. package/src/index.ts +23 -0
  207. package/src/lifecycle/canonical-hash.ts +28 -0
  208. package/src/lifecycle/catalog.ts +202 -0
  209. package/src/lifecycle/compiler-inspection.ts +29 -0
  210. package/src/lifecycle/core-static-readiness.ts +132 -0
  211. package/src/lifecycle/corrective-replan-authority.ts +136 -0
  212. package/src/lifecycle/corrective-replan-binding-manifest.ts +51 -0
  213. package/src/lifecycle/corrective-replan-credential-core.ts +408 -0
  214. package/src/lifecycle/corrective-replan-credential-schema.ts +54 -0
  215. package/src/lifecycle/corrective-replan-credentials.ts +48 -0
  216. package/src/lifecycle/corrective-replan.ts +843 -0
  217. package/src/lifecycle/evaluator.ts +48 -0
  218. package/src/lifecycle/fingerprint.ts +488 -0
  219. package/src/lifecycle/immutable.ts +8 -0
  220. package/src/lifecycle/implementation-table.ts +118 -0
  221. package/src/lifecycle/index.ts +8 -0
  222. package/src/lifecycle/schema-artifact.ts +263 -0
  223. package/src/lifecycle/semantic-registry.ts +572 -0
  224. package/src/lifecycle/types.ts +838 -0
  225. package/src/memory.ts +273 -0
  226. package/src/migration.ts +161 -0
  227. package/src/navigation-actions.ts +70 -0
  228. package/src/navigation-update-artifact.ts +198 -0
  229. package/src/navigation-update.ts +121 -0
  230. package/src/observation.ts +225 -0
  231. package/src/observed-routes.ts +660 -0
  232. package/src/operational-contract.ts +125 -0
  233. package/src/pending-review-update.ts +175 -0
  234. package/src/repository.ts +99 -0
  235. package/src/reviewer.ts +1879 -0
  236. package/src/runtime.ts +6 -0
  237. package/src/state/corrective-replan-executor.ts +818 -0
  238. package/src/state/corrective-replan-public-schema.ts +83 -0
  239. package/src/state/corrective-replan-public.ts +908 -0
  240. package/src/state/corrective-replan-transaction.ts +949 -0
  241. package/src/state/corrective-yield-executor.ts +327 -0
  242. package/src/state/corrective-yield-transaction.ts +730 -0
  243. package/src/state/lock.ts +902 -0
  244. package/src/state/store.ts +567 -0
  245. package/src/transition-core.ts +330 -0
  246. package/src/ulid.ts +24 -0
  247. package/src/version.ts +2 -0
  248. package/src/workflow-blocker-route.ts +109 -0
  249. package/src/workflow.ts +10172 -0
  250. package/docs/alpha7.1-implementation-brief.md +0 -268
  251. package/docs/alpha7.2-corrective-context-refresh-brief.md +0 -484
  252. package/docs/alpha7.2.1-remediation-recovery-brief.md +0 -86
  253. package/docs/beta1-stabilization-brief.md +0 -165
  254. package/docs/beta11-plan-integrity-recovery-brief.md +0 -38
  255. package/docs/beta13.2-signal-review-recovery.md +0 -38
  256. package/docs/beta2-initial-assembly-navigation-brief.md +0 -616
  257. package/docs/change-model.md +0 -118
  258. package/docs/delegated-approval.md +0 -254
  259. package/docs/lifecycle/state-machine-stabilization.md +0 -641
  260. package/docs/pdf/README.md +0 -24
  261. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  262. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  263. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  264. package/docs/pdf/requirements.txt +0 -1
  265. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +0 -478
  266. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +0 -508
  267. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +0 -778
  268. package/docs/pending-review-update.md +0 -15
  269. package/docs/problem-briefs/01-pre-implementation-integrity.md +0 -482
  270. package/docs/problem-briefs/02-minimal-step-integrity.md +0 -411
  271. package/docs/problem-briefs/03-minimal-agent-context-integrity.md +0 -358
  272. package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +0 -573
  273. package/docs/problem-briefs/BRIEF-TEMPLATE.md +0 -56
  274. package/docs/problem-briefs/README.md +0 -120
  275. package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +0 -90
  276. package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +0 -246
  277. package/docs/split-required-recovery.md +0 -47
  278. package/docs/stable-release-defect-register.md +0 -730
  279. package/docs/validation-report.md +0 -182
  280. package/scripts/generate-pdf-docs.py +0 -524
  281. package/scripts/run-pdf-docs.mjs +0 -62
@@ -1,23 +1,21 @@
1
- import { closeSync, existsSync, openSync, readFileSync, unlinkSync } from 'node:fs';
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 { semanticBoundaryHash } from './component-owner.js';
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 remediationEvents = readRemediationEvents(store, task).filter((event) => event.stepId === stepId);
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.attemptOrdinal !== index + 1
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 = remediationEvents.length + 1;
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: remediationEvents.map((event) => event.eventId),
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, task.planHash), 'current').semanticHash;
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, task.planHash, event, reviews, audits));
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
- if (planIntegrityDecision.planHash !== task.planHash) {
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.length + 1
1279
+ || event.triggeringAttemptCount !== evidence.remediationEvents.at(-1).attemptOrdinal + 1
1642
1280
  || evidence.remediationEvents.some((binding, index) => {
1643
- if (!binding.eventId || binding.attemptOrdinal !== index + 1)
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.length + 1
1322
+ || event.triggeringAttemptCount !== evidence.remediationEvents.at(-1).attemptOrdinal + 1
1685
1323
  || evidence.remediationEvents.some((binding, index) => {
1686
- if (!binding.eventId || binding.attemptOrdinal !== index + 1)
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
- || importChain.length < 2
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
- if (!evidence
2075
- || bindings.length !== priorEvents.length
2076
- || priorEvents.some((linked, index) => linked.failureKind !== 'checks-failed'
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)) {