codex-workflow-v2 2.0.0-beta.12.1 → 2.0.0-beta.12.11

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 (61) hide show
  1. package/README.md +65 -5
  2. package/dist/src/alpha6/downstream-proof.d.ts +68 -0
  3. package/dist/src/alpha6/downstream-proof.js +604 -0
  4. package/dist/src/alpha6/downstream-proof.js.map +1 -0
  5. package/dist/src/alpha6/milestone.d.ts +6 -2
  6. package/dist/src/alpha6/milestone.js +164 -10
  7. package/dist/src/alpha6/milestone.js.map +1 -1
  8. package/dist/src/alpha6/plan-risk.js +2 -2
  9. package/dist/src/alpha6/plan-risk.js.map +1 -1
  10. package/dist/src/alpha6/remediation.d.ts +2 -2
  11. package/dist/src/alpha6/remediation.js +18 -2
  12. package/dist/src/alpha6/remediation.js.map +1 -1
  13. package/dist/src/alpha6/review.d.ts +6 -0
  14. package/dist/src/alpha6/review.js +12 -0
  15. package/dist/src/alpha6/review.js.map +1 -1
  16. package/dist/src/alpha7/corrective-recovery.d.ts +8 -1
  17. package/dist/src/alpha7/corrective-recovery.js +406 -26
  18. package/dist/src/alpha7/corrective-recovery.js.map +1 -1
  19. package/dist/src/cli.js +118 -5
  20. package/dist/src/cli.js.map +1 -1
  21. package/dist/src/contracts.d.ts +112 -3
  22. package/dist/src/dependency-provenance.d.ts +25 -2
  23. package/dist/src/dependency-provenance.js +199 -13
  24. package/dist/src/dependency-provenance.js.map +1 -1
  25. package/dist/src/git.d.ts +1 -1
  26. package/dist/src/git.js +6 -2
  27. package/dist/src/git.js.map +1 -1
  28. package/dist/src/historical-step-provenance.d.ts +20 -0
  29. package/dist/src/historical-step-provenance.js +111 -0
  30. package/dist/src/historical-step-provenance.js.map +1 -0
  31. package/dist/src/state/corrective-replan-executor.js +5 -0
  32. package/dist/src/state/corrective-replan-executor.js.map +1 -1
  33. package/dist/src/version.d.ts +1 -1
  34. package/dist/src/version.js +1 -1
  35. package/dist/src/version.js.map +1 -1
  36. package/dist/src/workflow.d.ts +37 -2
  37. package/dist/src/workflow.js +1409 -44
  38. package/dist/src/workflow.js.map +1 -1
  39. package/docs/autonomy-guardrails.md +73 -4
  40. package/docs/decisions.md +14 -0
  41. package/docs/delegated-approval.md +6 -1
  42. package/docs/development-flow.md +60 -5
  43. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  44. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  45. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  46. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +94 -8
  47. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +72 -8
  48. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +145 -32
  49. package/docs/release.md +46 -8
  50. package/docs/updating-existing-project.md +142 -6
  51. package/docs/validation-report.md +109 -95
  52. package/package.json +7 -2
  53. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +1 -1
  54. package/plugins/codex-workflow-gateway/references/protocol.md +55 -1
  55. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +150 -2
  56. package/schemas/authorization-event.schema.json +8 -1
  57. package/schemas/downstream-proof-invalidation-event.schema.json +71 -0
  58. package/schemas/downstream-proof-replan-recovery-event.schema.json +61 -0
  59. package/schemas/milestone-scope-change-event.schema.json +25 -1
  60. package/schemas/task.schema.json +104 -0
  61. package/scripts/generate-pdf-docs.py +13 -2
@@ -2,7 +2,8 @@ import { appendFileSync, existsSync, lstatSync, readFileSync, readdirSync } from
2
2
  import { createHash } from 'node:crypto';
3
3
  import path from 'node:path';
4
4
  import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION, } from './contracts.js';
5
- import { inspectDependencyProvenanceRecovery } from './dependency-provenance.js';
5
+ import { inspectDependencyKnowledgeRefresh, inspectDependencyProvenanceRecovery, } from './dependency-provenance.js';
6
+ import { inspectHistoricalStepProvenanceRecovery, omittedStepEvidenceCommits, } from './historical-step-provenance.js';
6
7
  import { renderBrief, renderPlan, renderResult } from './artifacts.js';
7
8
  import { assertDelegationCanAuthorize, hashDelegationPolicy, prepareDelegationGrantGate, } from './delegation.js';
8
9
  import { assessDiscovery, mergeUnique } from './domain/discovery.js';
@@ -10,17 +11,18 @@ import { pathAllowed, validatePlan } from './domain/validation.js';
10
11
  import { WorkflowError } from './errors.js';
11
12
  import { bindGraphEvidence, createGraphRefreshRequest, fallbackGraphBinding, inspectGraphBinding, validateGraphRequestCurrent, } from './graph.js';
12
13
  import { inspectLocalDependency, inspectWorkflowVersions } from './diagnostics.js';
13
- import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, appendTaskWriterCredentialReplacement, assertTaskClaimAllowed, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, readTaskC1Posture, } from './alpha6/handoff.js';
14
+ import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, appendTaskWriterCredentialReplacement, assertTaskClaimAllowed, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, prepareTaskCorrectiveYield, readTaskC1Posture, } from './alpha6/handoff.js';
15
+ import { assessDownstreamProofCarryover, assessDownstreamProofReplanRecovery, assessDownstreamProofRecovery, downstreamProofInvalidationSidecar, downstreamProofReplanRecoverySidecar, prepareDownstreamProofInvalidation, prepareDownstreamProofReplanRecovery, readDownstreamProofInvalidations, readDownstreamProofReplanRecoveries, hashDirtyWorktree, } from './alpha6/downstream-proof.js';
14
16
  import { applyAdoptionPosture, assertAdoptionBaselinePreserved, buildAdoptionPreparation, initializeProjectRegistrationAdoption, readCurrentAdoptionPosture, } from './alpha6/adoption.js';
15
- import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, readCurrentPlanRiskAudit, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
17
+ import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, readCurrentPlanRiskAudit, readPlanRiskAuditEvents, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
16
18
  import { appendCorrectiveDecisionEvent, appendPlanIntegrityRecoveryDecision, appendRemediationEvent, appendRemediationModeRecovery, appendStopEscalateOverride, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, defaultCorrectiveAuditorActor, findCurrentGuardedRemediationPosture, findRemediationModeRecoveryCandidate, findFailedGuardedStepRequiringCorrectiveDecision, readGuardedRemediationPostureForStep, readCorrectiveDecisionEvents, readRemediationEvents, prepareStopEscalateOverride, stopEscalateOverrideDecisionEventIds, } from './alpha6/remediation.js';
17
19
  import { assessPlanIntegrityRecovery, defaultPlanIntegrityRecoveryActor, } from './alpha6/plan-integrity.js';
18
20
  import { assertMechanicalFeasibilityFresh, evaluateMechanicalFeasibility, hashMechanicalFeasibilityEvidence, } from './alpha6/mechanical-feasibility.js';
19
- import { assessMilestoneInitialAssembly, applyInitialMilestonePlanTransaction, applyMilestoneScopeChangeTransaction, assertMilestoneDependenciesDeclared, assertMilestoneTaskRunnable, assessMilestoneTaskDependencies, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, inspectMilestoneWithIntegrity, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, prepareMilestoneScopeChangeBundle, readMilestoneForScopeChange, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
21
+ import { assessMilestoneInitialAssembly, applyInitialMilestonePlanTransaction, applyMilestoneScopeChangeTransaction, assertMilestoneDependenciesDeclared, assertMilestoneTaskRunnable, assessMilestoneTaskDependencies, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, inspectMilestoneWithIntegrity, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, prepareMilestoneScopeChangeBundle, readMilestoneScopeChangeEvents, readMilestoneForScopeChange, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
20
22
  import { applyProjectTransaction, recoverProjectTransactions, runRollbackProjectTransaction, } from './beta1/project-transaction.js';
21
- import { buildCurrentStrictStepReviewCycle, countUnverifiedStrictStepReviews, ensurePendingStrictStepReview, isStepReviewRequired, latestFailedStepReviewRouting, latestPassedVerifiedStepReviewEvent, recordStrictStepReview, verifiedHistoricalStepReviewCommits, } from './alpha6/review.js';
23
+ import { buildCurrentStrictStepReviewCycle, countUnverifiedStrictStepReviews, ensurePendingStrictStepReview, isStepReviewRequired, latestFailedStepReviewRouting, latestPassedVerifiedStepReviewEvent, recordStrictStepReview, verifiedHistoricalStepReviewCommits, verifiedHistoricalStepReviewBindings, } from './alpha6/review.js';
22
24
  import { applyMilestoneAutonomyContract, assertAutonomousPlanEvolution, prepareMilestoneAutonomyContract, readMilestoneAutonomyEvents, requireActiveMilestoneAutonomy, } from './alpha7/autonomy.js';
23
- import { appendCorrectiveDecisionRecovery, readValidatedCorrectiveDecisionRecoveryForNavigation, requireCorrectiveDecisionRecoveryForRun, } from './alpha7/corrective-recovery.js';
25
+ import { appendCorrectiveDecisionRecovery, readValidatedCorrectiveDecisionRecoveryForNavigation, readValidatedStopOverrideContextRebindIfApplicable, requireCorrectiveDecisionRecoveryForRun, } from './alpha7/corrective-recovery.js';
24
26
  import { applyKnowledgeSelections, hashKnowledgeMap, inspectKnowledgeMap, normalizeRelativePath, reconcileKnowledgeMap, scanProjectKnowledge, selectKnowledgeSources, } from './memory.js';
25
27
  import { canonicalJsonStringify, sha256Hex } from './alpha6/store-sidecars.js';
26
28
  import { ensureDirectory } from './fs-utils.js';
@@ -170,6 +172,202 @@ export class WorkflowService {
170
172
  now: this.now,
171
173
  }).recover({ taskId, transactionId });
172
174
  }
175
+ recoverDownstreamProof(repository, taskId, expectedTaskRevision, actor, writerToken, reason) {
176
+ const context = this.#mutationContext(repository);
177
+ const lease = context.locks.heartbeat(taskId, writerToken);
178
+ const task = this.store.readTask(context.identity.projectId, taskId);
179
+ assertExpectedRevision(task, expectedTaskRevision);
180
+ this.assertTaskMutationAllowedByHandoff(task, actor, lease.token);
181
+ const posture = readTaskC1Posture(this.store, task);
182
+ if (posture.state === 'none' && lease.owner !== actor.trim()) {
183
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream proof recovery actor must own the active writer lease.', {
184
+ taskId,
185
+ requiredActor: lease.owner,
186
+ actualActor: actor.trim(),
187
+ });
188
+ }
189
+ const assessment = assessDownstreamProofRecovery(this.store, context.identity.repositoryRoot, task);
190
+ if (!assessment.applicable || !assessment.eligible || !assessment.activeStepId) {
191
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Task is not eligible for bounded downstream proof recovery.', {
192
+ taskId,
193
+ assessment,
194
+ });
195
+ }
196
+ const invalidatedIds = new Set(assessment.invalidatedSteps.map((step) => step.stepId));
197
+ const retainedHistoricalCommits = assessment.invalidatedSteps.map((step) => step.evidenceCommit);
198
+ const now = this.now();
199
+ const resultTask = {
200
+ ...task,
201
+ revision: task.revision + 1,
202
+ updatedAt: now.toISOString(),
203
+ status: 'needs_fix',
204
+ steps: task.steps.map((step) => {
205
+ if (invalidatedIds.has(step.id))
206
+ return { ...step, status: 'planned', evidence: null };
207
+ if (step.id === assessment.activeStepId)
208
+ return { ...step, status: 'blocked', evidence: null };
209
+ return step;
210
+ }),
211
+ authorizations: task.authorizations.map((authorization) => authorization.kind === 'execution' && authorization.decision === 'approved'
212
+ ? { ...authorization, decision: 'superseded' }
213
+ : authorization),
214
+ review: emptyReview(),
215
+ resultHash: null,
216
+ evidenceHash: null,
217
+ blockReason: `${assessment.activeStepId} exposed changes required in completed predecessor authority; replace the Step Plan before resuming execution.`,
218
+ invalidatedStepCommits: [...new Set([
219
+ ...(task.invalidatedStepCommits ?? []),
220
+ ...retainedHistoricalCommits,
221
+ ])],
222
+ };
223
+ const invalidation = prepareDownstreamProofInvalidation(this.store, resultTask, assessment, {
224
+ previousTaskRevision: task.revision,
225
+ actor,
226
+ reason,
227
+ }, now);
228
+ const preparedYield = posture.state === 'claimed'
229
+ ? prepareTaskCorrectiveYield(this.store, resultTask, {
230
+ actor,
231
+ writerToken,
232
+ reason: `Yield after downstream proof invalidated predecessor completion authority: ${reason}`,
233
+ eventId: `HOF-${sha256Hex([
234
+ 'downstream-proof-recovery-v1',
235
+ task.id,
236
+ String(task.revision),
237
+ invalidation.event.eventHash,
238
+ actor.trim(),
239
+ ].join('\0'))}`,
240
+ }, now)
241
+ : null;
242
+ const projectRoot = this.store.projectRoot(task.projectId);
243
+ const relative = (absolute) => path.relative(projectRoot, absolute).split(path.sep).join('/');
244
+ const taskRoot = this.store.taskRoot(task.projectId, task.id);
245
+ applyProjectTransaction({
246
+ projectRoot,
247
+ projectId: task.projectId,
248
+ transactionId: `tx-${createEntityId('TASK', now.getTime()).toLowerCase()}`,
249
+ kind: 'downstream-proof-predecessor-recovery',
250
+ now,
251
+ targets: [
252
+ {
253
+ path: relative(path.join(taskRoot, downstreamProofInvalidationSidecar())),
254
+ content: invalidation.postimage,
255
+ },
256
+ ...(preparedYield
257
+ ? [{ path: relative(path.join(taskRoot, 'handoffs.jsonl')), content: preparedYield.postimage.toString('utf8') }]
258
+ : []),
259
+ { path: relative(path.join(taskRoot, 'state.json')), content: `${JSON.stringify(resultTask, null, 2)}\n` },
260
+ { path: relative(path.join(this.store.lockRoot(task.projectId), `${task.id}.json`)), content: null },
261
+ ],
262
+ validate: () => {
263
+ const saved = this.store.readTask(task.projectId, task.id);
264
+ const events = readDownstreamProofInvalidations(this.store, saved);
265
+ const readbackDirtyFiles = changedFiles(context.identity.repositoryRoot).sort();
266
+ if (saved.revision !== resultTask.revision
267
+ || saved.status !== 'needs_fix'
268
+ || events.at(-1)?.eventHash !== invalidation.event.eventHash
269
+ || headCommit(context.identity.repositoryRoot) !== assessment.preservedHeadCommit
270
+ || JSON.stringify(readbackDirtyFiles) !== JSON.stringify(assessment.dirtyFiles)
271
+ || context.locks.inspect(task.id) !== null
272
+ || (preparedYield && readTaskC1Posture(this.store, saved).state !== 'none')) {
273
+ throw new WorkflowError('STATE_CORRUPT', 'Downstream proof recovery transaction readback failed.', {
274
+ taskId,
275
+ });
276
+ }
277
+ validateTaskHistory(context.identity.repositoryRoot, saved);
278
+ },
279
+ });
280
+ return {
281
+ task: this.store.readTask(task.projectId, task.id),
282
+ invalidation: invalidation.event,
283
+ };
284
+ }
285
+ recoverDownstreamProofReplan(repository, taskId, expectedTaskRevision, actor, reason) {
286
+ const context = this.#mutationContext(repository);
287
+ const task = this.store.readTask(context.identity.projectId, taskId);
288
+ assertExpectedRevision(task, expectedTaskRevision);
289
+ // This repair intentionally runs before Project Knowledge refresh: the exact
290
+ // dependency-only transport can make the current map stale, while the
291
+ // recovery assessment still binds the stranded Task to its recorded rebind.
292
+ const observation = this.observationSnapshot(repository).observation;
293
+ if (observation.kind !== 'assessment') {
294
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan recovery requires a coherent observation boundary.', {
295
+ observation,
296
+ });
297
+ }
298
+ const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
299
+ const assessment = assessDownstreamProofReplanRecovery(this.store, context.identity.repositoryRoot, task, { currentPlanRiskAuditExists });
300
+ const environmentBlockers = [
301
+ ...(!assessment.dependencyRecoveryCommit
302
+ ? ['The exact beta.12.7 dependency compatibility must be registered before Task recovery.']
303
+ : []),
304
+ ...(observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
305
+ ...(observation.pendingProjectTransactions.length > 0
306
+ || observation.pendingMilestoneTransactions.length > 0
307
+ || observation.pendingTaskTransactions.length > 0
308
+ ? ['No pending Workflow transaction may exist.'] : []),
309
+ ...(observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
310
+ ...(observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
311
+ ];
312
+ if (!assessment.applicable || !assessment.eligible || environmentBlockers.length > 0) {
313
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Task is not eligible for bounded downstream-proof replan recovery.', {
314
+ taskId,
315
+ blockers: [...assessment.blockers, ...environmentBlockers],
316
+ });
317
+ }
318
+ const now = this.now();
319
+ const resultTask = {
320
+ ...task,
321
+ revision: task.revision + 1,
322
+ updatedAt: now.toISOString(),
323
+ status: 'needs_fix',
324
+ authorizations: task.authorizations.map((authorization) => authorization.kind === 'execution' && authorization.decision === 'approved'
325
+ ? { ...authorization, decision: 'superseded' }
326
+ : authorization),
327
+ review: emptyReview(),
328
+ resultHash: null,
329
+ evidenceHash: null,
330
+ blockReason: `${assessment.invalidation.activeStepId} downstream-proof recovery requires replacement of the full unfinished Step Plan before execution can resume.`,
331
+ };
332
+ const recovery = prepareDownstreamProofReplanRecovery(this.store, resultTask, assessment, { previousTaskRevision: task.revision, actor, reason }, now);
333
+ const projectRoot = this.store.projectRoot(task.projectId);
334
+ const relative = (absolute) => path.relative(projectRoot, absolute).split(path.sep).join('/');
335
+ const taskRoot = this.store.taskRoot(task.projectId, task.id);
336
+ applyProjectTransaction({
337
+ projectRoot,
338
+ projectId: task.projectId,
339
+ transactionId: `tx-${createEntityId('TASK', now.getTime()).toLowerCase()}`,
340
+ kind: 'downstream-proof-replan-recovery',
341
+ now,
342
+ targets: [
343
+ {
344
+ path: relative(path.join(taskRoot, downstreamProofReplanRecoverySidecar())),
345
+ content: recovery.postimage,
346
+ },
347
+ { path: relative(path.join(taskRoot, 'state.json')), content: `${JSON.stringify(resultTask, null, 2)}\n` },
348
+ ],
349
+ validate: () => {
350
+ const saved = this.store.readTask(task.projectId, task.id);
351
+ const events = readDownstreamProofReplanRecoveries(this.store, saved);
352
+ if (saved.revision !== resultTask.revision
353
+ || saved.status !== 'needs_fix'
354
+ || saved.planHash !== task.planHash
355
+ || events.at(-1)?.eventHash !== recovery.event.eventHash
356
+ || headCommit(context.identity.repositoryRoot) !== recovery.event.preservedHeadCommit
357
+ || JSON.stringify(changedFiles(context.identity.repositoryRoot).sort())
358
+ !== JSON.stringify(recovery.event.dirtyFiles)) {
359
+ throw new WorkflowError('STATE_CORRUPT', 'Downstream-proof replan recovery transaction readback failed.', {
360
+ taskId,
361
+ });
362
+ }
363
+ validateTaskHistory(context.identity.repositoryRoot, saved);
364
+ },
365
+ });
366
+ return {
367
+ task: this.store.readTask(task.projectId, task.id),
368
+ recovery: recovery.event,
369
+ };
370
+ }
173
371
  correctiveReplanController(repository) {
174
372
  const identity = resolveRepositoryIdentity(repository);
175
373
  return createCorrectiveReplanPublicController({
@@ -403,6 +601,353 @@ export class WorkflowService {
403
601
  const savedDiscovery = this.store.writeDiscovery({ ...discovery, status: 'materialized', materializedEntityId: taskId }, expectedRevision);
404
602
  return { discovery: savedDiscovery, task: savedTask, taskRoot };
405
603
  }
604
+ materializeAuditedRemediationTask(repository, milestoneId, expectedMilestoneRevision, blockedTaskId, expectedBlockedTaskRevision, input) {
605
+ const { identity, locks } = this.#mutationContext(repository);
606
+ const observation = this.observationSnapshot(repository).observation;
607
+ if (observation.kind !== 'assessment'
608
+ || observation.leases.length > 0
609
+ || observation.coreTaskOperations.length > 0
610
+ || observation.pendingProjectTransactions.length > 0
611
+ || observation.pendingMilestoneTransactions.length > 0
612
+ || observation.pendingTaskTransactions.length > 0
613
+ || observation.corruptTaskTransactions.length > 0) {
614
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Audited remediation materialization requires a coherent, idle observation boundary.', { observation });
615
+ }
616
+ if (!isClean(identity.repositoryRoot)) {
617
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Audited remediation materialization requires a clean Milestone base checkout.', { changedFiles: changedFiles(identity.repositoryRoot) });
618
+ }
619
+ const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
620
+ assertExpectedRevision(milestone, expectedMilestoneRevision);
621
+ if (milestone.status !== 'active' || currentBranch(identity.repositoryRoot) !== milestone.baseBranch) {
622
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Audited remediation materialization requires the active Milestone base branch.', {
623
+ milestoneStatus: milestone.status,
624
+ expectedBranch: milestone.baseBranch,
625
+ actualBranch: currentBranch(identity.repositoryRoot),
626
+ });
627
+ }
628
+ this.assertMilestoneAuthorizationFresh(milestone);
629
+ const blockedTask = this.store.readTask(identity.projectId, blockedTaskId);
630
+ assertExpectedRevision(blockedTask, expectedBlockedTaskRevision);
631
+ if (blockedTask.milestoneId !== milestone.id
632
+ || blockedTask.status !== 'awaiting_execution_authorization'
633
+ || !blockedTask.planHash
634
+ || blockedTask.workspaceOwner !== null
635
+ || blockedTask.steps.some((step) => step.status !== 'planned')) {
636
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Audited remediation is available only for an unstarted Milestone Task with a rejected current Plan.', {
637
+ taskId: blockedTask.id,
638
+ taskStatus: blockedTask.status,
639
+ milestoneId: blockedTask.milestoneId,
640
+ workspaceOwner: blockedTask.workspaceOwner,
641
+ });
642
+ }
643
+ const blockedMembership = milestone.memberships.find((membership) => membership.taskId === blockedTask.id);
644
+ if (blockedMembership?.disposition !== 'required') {
645
+ throw new WorkflowError('TRANSITION_BLOCKED', 'The blocked Task must remain a required Milestone member.', { taskId: blockedTask.id, disposition: blockedMembership?.disposition ?? null });
646
+ }
647
+ const audit = readPlanRiskAuditEvents(this.store, blockedTask).at(-1);
648
+ if (!audit
649
+ || audit.decision !== 'stop-escalate'
650
+ || audit.taskRevision !== blockedTask.revision
651
+ || audit.briefHash !== blockedTask.briefHash
652
+ || audit.planHash !== blockedTask.planHash) {
653
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Audited remediation requires the current stop-escalate Plan Risk Audit.', {
654
+ taskId: blockedTask.id,
655
+ currentTaskRevision: blockedTask.revision,
656
+ currentBriefHash: blockedTask.briefHash,
657
+ currentPlanHash: blockedTask.planHash,
658
+ latestAudit: audit ?? null,
659
+ });
660
+ }
661
+ const duplicate = readMilestoneScopeChangeEvents(this.store, identity.projectId, milestone.id)
662
+ .find((event) => event.authorizationMode === 'audited-remediation'
663
+ && event.remediationEvidence?.planRiskAuditEventHash === audit.eventHash);
664
+ if (duplicate) {
665
+ throw new WorkflowError('TRANSITION_BLOCKED', 'This blocking Plan Risk Audit already has a materialized remediation Task.', {
666
+ planRiskAuditEventId: audit.eventId,
667
+ remediationTaskId: duplicate.remediationEvidence?.remediationTaskId ?? null,
668
+ });
669
+ }
670
+ const discovery = this.store.readDiscovery(identity.projectId, input.discoveryId);
671
+ assertExpectedRevision(discovery, input.expectedDiscoveryRevision);
672
+ const readiness = assessDiscovery(discovery);
673
+ if (!readiness.ready || discovery.status !== 'ready_to_materialize') {
674
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Remediation Discovery is not ready to materialize.', {
675
+ discoveryId: discovery.id,
676
+ missing: readiness.missing,
677
+ });
678
+ }
679
+ const title = input.title.trim();
680
+ if (!title)
681
+ throw new WorkflowError('INVALID_ARGUMENT', 'Remediation Task title is required.');
682
+ const predecessorTaskIds = [...new Set(input.predecessorTaskIds.map((taskId) => taskId.trim()))].sort();
683
+ if (predecessorTaskIds.length === 0 || predecessorTaskIds.some((taskId) => !taskId)) {
684
+ throw new WorkflowError('INVALID_ARGUMENT', 'Audited remediation requires at least one explicit merged predecessor Task.');
685
+ }
686
+ const requiredPredecessorTaskIds = [...(blockedMembership.dependsOnTaskIds ?? [])].sort();
687
+ if (canonicalJsonStringify(predecessorTaskIds) !== canonicalJsonStringify(requiredPredecessorTaskIds)) {
688
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Audited remediation must inherit the blocked Task exact direct predecessor frontier.', {
689
+ blockedTaskId: blockedTask.id,
690
+ requiredPredecessorTaskIds,
691
+ actualPredecessorTaskIds: predecessorTaskIds,
692
+ });
693
+ }
694
+ const membershipsByTaskId = new Map(milestone.memberships.map((membership) => [membership.taskId, membership]));
695
+ const ancestorTaskIds = new Set();
696
+ const visitAncestors = (taskId) => {
697
+ const membership = membershipsByTaskId.get(taskId);
698
+ for (const dependencyId of membership?.dependsOnTaskIds ?? []) {
699
+ if (ancestorTaskIds.has(dependencyId))
700
+ continue;
701
+ ancestorTaskIds.add(dependencyId);
702
+ visitAncestors(dependencyId);
703
+ }
704
+ };
705
+ visitAncestors(blockedTask.id);
706
+ for (const predecessorTaskId of predecessorTaskIds) {
707
+ const predecessorMembership = membershipsByTaskId.get(predecessorTaskId);
708
+ const predecessor = this.store.readTask(identity.projectId, predecessorTaskId);
709
+ if (!ancestorTaskIds.has(predecessorTaskId)
710
+ || predecessorMembership?.disposition !== 'required'
711
+ || predecessor.status !== 'merged') {
712
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Every remediation predecessor must be a merged required ancestor of the blocked Task.', {
713
+ predecessorTaskId,
714
+ isAncestor: ancestorTaskIds.has(predecessorTaskId),
715
+ disposition: predecessorMembership?.disposition ?? null,
716
+ status: predecessor.status,
717
+ });
718
+ }
719
+ }
720
+ const now = this.now();
721
+ const timestamp = now.toISOString();
722
+ const actor = `agent:milestone-remediation:${milestone.id}`;
723
+ const storedKnowledgeMap = this.store.readKnowledgeMap(identity.projectId);
724
+ const scannedKnowledgeMap = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, now);
725
+ const inspectedKnowledgeMap = inspectKnowledgeMap(storedKnowledgeMap, scannedKnowledgeMap);
726
+ let dependencyKnowledgeRefresh = null;
727
+ let refreshedKnowledgeMap = null;
728
+ if (inspectedKnowledgeMap.status !== 'active' || !inspectedKnowledgeMap.approval) {
729
+ dependencyKnowledgeRefresh = inspectDependencyKnowledgeRefresh(identity.repositoryRoot, milestone.baseBranch, storedKnowledgeMap, inspectedKnowledgeMap);
730
+ if (!dependencyKnowledgeRefresh.eligible) {
731
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Audited remediation cannot bypass unrelated or unproven Project Knowledge drift.', { dependencyKnowledgeRefresh });
732
+ }
733
+ const reconciled = reconcileKnowledgeMap(storedKnowledgeMap, scannedKnowledgeMap);
734
+ const approved = applyKnowledgeSelections(reconciled, [], actor, now);
735
+ if (!approved.approval) {
736
+ throw new WorkflowError('STATE_CORRUPT', 'Dependency-only Knowledge refresh did not produce approval evidence.');
737
+ }
738
+ refreshedKnowledgeMap = {
739
+ ...approved,
740
+ revision: storedKnowledgeMap.revision + 2,
741
+ createdAt: storedKnowledgeMap.createdAt,
742
+ updatedAt: timestamp,
743
+ approval: {
744
+ ...approved.approval,
745
+ authorizationMode: 'corrective-derived',
746
+ },
747
+ };
748
+ }
749
+ const remediationTaskId = createEntityId('TASK', now.getTime());
750
+ const briefContent = renderBrief(discovery, title);
751
+ const normalizedBriefContent = briefContent.endsWith('\n') ? briefContent : `${briefContent}\n`;
752
+ const briefHash = sha256Hex(normalizedBriefContent);
753
+ const remediationTask = {
754
+ schemaVersion: STATE_SCHEMA_VERSION,
755
+ revision: 1,
756
+ id: remediationTaskId,
757
+ projectId: identity.projectId,
758
+ createdAt: timestamp,
759
+ updatedAt: timestamp,
760
+ kind: 'task',
761
+ status: 'planning',
762
+ title,
763
+ slug: slugify(title),
764
+ discoveryId: discovery.id,
765
+ milestoneId: milestone.id,
766
+ repositoryRoot: identity.repositoryRoot,
767
+ baseBranch: milestone.baseBranch,
768
+ baseCommit: null,
769
+ taskBranch: null,
770
+ workspaceOwner: null,
771
+ briefHash,
772
+ planHash: null,
773
+ planObjective: null,
774
+ resultHash: null,
775
+ evidenceHash: null,
776
+ requirements: discovery.acceptance.map((_, index) => `REQ-${String(index + 1).padStart(3, '0')}`),
777
+ acceptance: discovery.acceptance.map((_, index) => `AC-${String(index + 1).padStart(3, '0')}`),
778
+ risks: discovery.assumptions,
779
+ knowledgeImpact: null,
780
+ knowledgeImpactReason: null,
781
+ knowledgeTargets: [],
782
+ knowledgeMapRevision: null,
783
+ knowledgeMapHash: null,
784
+ graphUse: null,
785
+ steps: [],
786
+ authorizations: [],
787
+ knowledgeRebinds: [],
788
+ review: emptyReview(),
789
+ blockReason: null,
790
+ mergedCommit: null,
791
+ systemCommits: [],
792
+ };
793
+ const consumedDiscovery = {
794
+ ...discovery,
795
+ revision: discovery.revision + 1,
796
+ updatedAt: timestamp,
797
+ status: 'materialized',
798
+ materializedEntityId: remediationTaskId,
799
+ };
800
+ const remediationEvidence = {
801
+ blockedTaskId: blockedTask.id,
802
+ blockedTaskRevision: blockedTask.revision,
803
+ blockedBriefHash: blockedTask.briefHash,
804
+ blockedPlanHash: blockedTask.planHash,
805
+ planRiskAuditEventId: audit.eventId,
806
+ planRiskAuditEventHash: audit.eventHash,
807
+ remediationTaskId,
808
+ predecessorTaskIds,
809
+ };
810
+ const reason = `Audited remediation for ${blockedTask.id} after stop-escalate ${audit.eventId}.`;
811
+ const plan = {
812
+ outcome: milestone.outcome,
813
+ successSignal: milestone.successSignal,
814
+ acceptance: [...milestone.acceptance],
815
+ checks: [...milestone.checks],
816
+ memberships: [
817
+ ...milestone.memberships.map((membership) => membership.taskId === blockedTask.id
818
+ ? {
819
+ ...membership,
820
+ dependsOnTaskIds: [...new Set([...(membership.dependsOnTaskIds ?? []), remediationTaskId])],
821
+ }
822
+ : { ...membership, dependsOnTaskIds: [...(membership.dependsOnTaskIds ?? [])] }),
823
+ {
824
+ taskId: remediationTaskId,
825
+ disposition: 'required',
826
+ reason,
827
+ dependsOnTaskIds: predecessorTaskIds,
828
+ },
829
+ ],
830
+ };
831
+ const readCandidateTask = (taskId) => taskId === remediationTaskId
832
+ ? remediationTask
833
+ : this.store.readTask(identity.projectId, taskId);
834
+ const preparedCandidate = prepareMilestoneScopeChangeCandidate({
835
+ milestone,
836
+ plan,
837
+ readTask: readCandidateTask,
838
+ actor,
839
+ auditedRemediationEvidence: remediationEvidence,
840
+ });
841
+ const sourceAuthorization = [...milestone.authorizations]
842
+ .reverse()
843
+ .find((authorization) => authorization.kind === 'execution' && authorization.decision === 'approved');
844
+ if (!sourceAuthorization) {
845
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Audited remediation has no current Milestone execution authority.');
846
+ }
847
+ const derivedAuthorization = {
848
+ kind: 'execution',
849
+ decision: 'approved',
850
+ actor,
851
+ recordedAt: timestamp,
852
+ briefHash: null,
853
+ planHash: preparedCandidate.candidatePlanHash,
854
+ resultHash: null,
855
+ evidenceHash: sha256Hex(canonicalJsonStringify({
856
+ sourcePlanHash: sourceAuthorization.planHash,
857
+ sourceAuthorizationMode: sourceAuthorization.authorizationMode ?? 'human',
858
+ planRiskAuditEventHash: audit.eventHash,
859
+ remediationEvidence,
860
+ })),
861
+ headCommit: headCommit(identity.repositoryRoot),
862
+ reason: 'Derived from the current Milestone execution authority and exact blocking Plan Risk Audit.',
863
+ authorizationMode: 'corrective-derived',
864
+ };
865
+ const prepared = {
866
+ ...preparedCandidate,
867
+ authorizations: [...preparedCandidate.authorizations, derivedAuthorization],
868
+ };
869
+ const bundle = prepareMilestoneScopeChangeBundle({
870
+ store: this.store,
871
+ milestone,
872
+ prepared,
873
+ now,
874
+ readTask: readCandidateTask,
875
+ });
876
+ const projectRoot = this.store.projectRoot(identity.projectId);
877
+ const relative = (absolute) => path.relative(projectRoot, absolute).split(path.sep).join('/');
878
+ applyProjectTransaction({
879
+ projectRoot,
880
+ projectId: identity.projectId,
881
+ transactionId: `tx-${createEntityId('TASK', now.getTime()).toLowerCase()}`,
882
+ kind: 'audited-remediation-materialization',
883
+ now,
884
+ targets: [
885
+ ...(refreshedKnowledgeMap
886
+ ? [{
887
+ path: relative(path.join(projectRoot, 'knowledge-map.json')),
888
+ content: `${JSON.stringify(refreshedKnowledgeMap, null, 2)}\n`,
889
+ }]
890
+ : []),
891
+ {
892
+ path: relative(path.join(this.store.discoveryRoot(identity.projectId, discovery.id), 'state.json')),
893
+ content: `${JSON.stringify(consumedDiscovery, null, 2)}\n`,
894
+ },
895
+ {
896
+ path: relative(path.join(this.store.taskRoot(identity.projectId, remediationTaskId), 'brief.md')),
897
+ content: normalizedBriefContent,
898
+ },
899
+ {
900
+ path: relative(path.join(this.store.taskRoot(identity.projectId, remediationTaskId), 'state.json')),
901
+ content: `${JSON.stringify(remediationTask, null, 2)}\n`,
902
+ },
903
+ {
904
+ path: relative(path.join(this.store.milestoneRoot(identity.projectId, milestone.id), 'plan.json')),
905
+ content: bundle.planContent,
906
+ },
907
+ {
908
+ path: relative(path.join(this.store.milestoneRoot(identity.projectId, milestone.id), 'scope-change-events.jsonl')),
909
+ content: bundle.sidecarContent,
910
+ },
911
+ {
912
+ path: relative(path.join(this.store.milestoneRoot(identity.projectId, milestone.id), 'state.json')),
913
+ content: bundle.stateContent,
914
+ },
915
+ ],
916
+ validate: () => {
917
+ const savedDiscovery = this.store.readDiscovery(identity.projectId, discovery.id);
918
+ const savedRemediationTask = this.store.readTask(identity.projectId, remediationTaskId);
919
+ const savedMilestone = this.readMilestoneCurrent(identity.projectId, milestone.id);
920
+ const savedBlockedMembership = savedMilestone.memberships
921
+ .find((membership) => membership.taskId === blockedTask.id);
922
+ const savedRemediationMembership = savedMilestone.memberships
923
+ .find((membership) => membership.taskId === remediationTaskId);
924
+ const savedAuthorization = [...savedMilestone.authorizations]
925
+ .reverse()
926
+ .find((authorization) => authorization.kind === 'execution' && authorization.decision === 'approved');
927
+ const savedKnowledgeMap = this.store.readKnowledgeMap(identity.projectId);
928
+ if (savedDiscovery.materializedEntityId !== remediationTaskId
929
+ || savedRemediationTask.milestoneId !== milestone.id
930
+ || !savedBlockedMembership?.dependsOnTaskIds?.includes(remediationTaskId)
931
+ || savedRemediationMembership?.disposition !== 'required'
932
+ || savedAuthorization?.authorizationMode !== 'corrective-derived'
933
+ || savedMilestone.status !== 'active'
934
+ || (refreshedKnowledgeMap !== null
935
+ && (savedKnowledgeMap.revision !== refreshedKnowledgeMap.revision
936
+ || savedKnowledgeMap.status !== 'active'
937
+ || savedKnowledgeMap.approval?.authorizationMode !== 'corrective-derived'))
938
+ || locks.inspect(blockedTask.id) !== null) {
939
+ throw new WorkflowError('STATE_CORRUPT', 'Audited remediation transaction readback failed.');
940
+ }
941
+ },
942
+ });
943
+ return {
944
+ discovery: this.store.readDiscovery(identity.projectId, discovery.id),
945
+ remediationTask: this.store.readTask(identity.projectId, remediationTaskId),
946
+ blockedTask: this.store.readTask(identity.projectId, blockedTask.id),
947
+ milestone: this.readMilestoneCurrent(identity.projectId, milestone.id),
948
+ remediationEvidence,
949
+ };
950
+ }
406
951
  replaceSplitRequiredTask(repository, oldTaskId, expectedOldTaskRevision, discoveryId, expectedDiscoveryRevision, expectedMilestoneRevision, title, actor) {
407
952
  const { identity, locks } = this.#mutationContext(repository);
408
953
  const oldTask = this.store.readTask(identity.projectId, oldTaskId);
@@ -1066,7 +1611,10 @@ export class WorkflowService {
1066
1611
  const overriddenStopDecisionEventIds = stopEscalateOverrideDecisionEventIds(this.store, task);
1067
1612
  const currentHardBlockingDecisions = currentPlanCorrectiveDecisions.filter((event) => event.decision === 'split-required'
1068
1613
  || (event.decision === 'stop-escalate' && !overriddenStopDecisionEventIds.has(event.eventId)));
1069
- const currentPlanRiskAudit = task.status === 'needs_fix' || currentReplanDecisions.length > 0
1614
+ const downstreamReplanRecovered = readDownstreamProofReplanRecoveries(this.store, task)
1615
+ .some((event) => event.resultTaskRevision === task.revision && event.reboundPlanHash === task.planHash);
1616
+ const currentPlanRiskAudit = (task.status === 'needs_fix' || currentReplanDecisions.length > 0)
1617
+ && !downstreamReplanRecovered
1070
1618
  ? readCurrentPlanRiskAudit(this.store, task)
1071
1619
  : null;
1072
1620
  const classifiedCorrectivePosture = currentReplanDecisions.length > 0 && currentPlanRiskAudit
@@ -1199,6 +1747,10 @@ export class WorkflowService {
1199
1747
  status: dependenciesComplete(step, completedIds) ? 'planned' : 'blocked',
1200
1748
  evidence: null,
1201
1749
  }));
1750
+ const invalidatedStepCommits = [...new Set([
1751
+ ...(task.invalidatedStepCommits ?? []),
1752
+ ...omittedStepEvidenceCommits(task, plannedSteps),
1753
+ ])];
1202
1754
  if (task.status === 'needs_fix' &&
1203
1755
  task.review.status === 'failed' &&
1204
1756
  plannedSteps.every((step) => step.status === 'completed')) {
@@ -1237,6 +1789,7 @@ export class WorkflowService {
1237
1789
  knowledgeMapHash: plan.knowledgeMapHash,
1238
1790
  graphUse: plan.graphUse,
1239
1791
  steps: plannedSteps,
1792
+ invalidatedStepCommits,
1240
1793
  authorizations,
1241
1794
  review: emptyReview(),
1242
1795
  resultHash: null,
@@ -1472,7 +2025,12 @@ export class WorkflowService {
1472
2025
  requiredAction: 'task plan-set',
1473
2026
  });
1474
2027
  }
1475
- const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'task.execution_authorize', actor, task);
2028
+ const correctiveAuthority = delegationGrantId === null
2029
+ ? this.currentCorrectiveDerivedTaskAuthority(identity.projectId, task, actor)
2030
+ : null;
2031
+ const delegation = correctiveAuthority
2032
+ ? null
2033
+ : this.delegatedAuthorization(identity.projectId, delegationGrantId, 'task.execution_authorize', actor, task);
1476
2034
  const event = {
1477
2035
  kind: 'execution',
1478
2036
  decision: 'approved',
@@ -1484,7 +2042,11 @@ export class WorkflowService {
1484
2042
  evidenceHash: hashMechanicalFeasibilityEvidence(mechanicalFeasibility),
1485
2043
  headCommit: mechanicalFeasibility.evaluatedHead,
1486
2044
  reason,
1487
- authorizationMode: delegation ? 'delegated' : 'human',
2045
+ authorizationMode: correctiveAuthority
2046
+ ? 'corrective-derived'
2047
+ : delegation
2048
+ ? 'delegated'
2049
+ : 'human',
1488
2050
  ...(delegation ? { delegation } : {}),
1489
2051
  mechanicalFeasibility,
1490
2052
  };
@@ -1827,14 +2389,26 @@ export class WorkflowService {
1827
2389
  const recovery = staleDecision?.decision === 'continue-fix'
1828
2390
  ? requireCorrectiveDecisionRecoveryForRun(this.store, task, stepId, actor, writerToken)
1829
2391
  : null;
1830
- const gate = assertGuardedRemediationAttemptAllowed(this.store, task, stepId, planRiskAudit, recovery?.correctiveDecisionEventId ?? null);
2392
+ const stopOverrideContextRebind = staleDecision?.decision === 'stop-escalate'
2393
+ ? readValidatedStopOverrideContextRebindIfApplicable(this.store, task, stepId, context.identity.repositoryRoot)
2394
+ : null;
2395
+ const gate = assertGuardedRemediationAttemptAllowed(this.store, task, stepId, planRiskAudit, recovery?.correctiveDecisionEventId ?? null, stopOverrideContextRebind?.stopDecision.eventId ?? null);
1831
2396
  resumesCorrectiveReplan = gate.posture === 'replan-ready';
1832
2397
  }
1833
2398
  const knowledgeMap = this.assertTaskKnowledgeBinding(context.identity.repositoryRoot, task);
1834
2399
  const dirty = changedFiles(context.identity.repositoryRoot);
1835
- if (step.status === 'planned' && dirty.length > 0 && !resumesCorrectiveReplan) {
2400
+ const downstreamProofCarryover = step.status === 'planned' && dirty.length > 0
2401
+ ? assessDownstreamProofCarryover(this.store, context.identity.repositoryRoot, task, stepId)
2402
+ : null;
2403
+ if (step.status === 'planned'
2404
+ && dirty.length > 0
2405
+ && !resumesCorrectiveReplan
2406
+ && !downstreamProofCarryover?.eligible) {
1836
2407
  throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} must start from a clean checkout.`, {
1837
2408
  changedFiles: dirty,
2409
+ ...(downstreamProofCarryover?.applicable
2410
+ ? { downstreamProofCarryoverBlockers: downstreamProofCarryover.blockers }
2411
+ : {}),
1838
2412
  });
1839
2413
  }
1840
2414
  const dirtyOutsideStep = dirty.filter((file) => !pathAllowed(file, step.allowedWrites));
@@ -2065,7 +2639,7 @@ export class WorkflowService {
2065
2639
  checksHash: sha256Hex(canonicalJsonStringify(checks)),
2066
2640
  reviewEventHash: null,
2067
2641
  reviewerAttestationHash: null,
2068
- }, planRiskAudit, this.now());
2642
+ }, planRiskAudit, this.now(), this.stopOverrideContextRebindDecisionId(task, stepId, context.identity.repositoryRoot));
2069
2643
  }
2070
2644
  const steps = task.steps.map((candidate) => candidate.id === stepId ? { ...candidate, status: 'failed' } : candidate);
2071
2645
  return this.store.writeTask({ ...task, status: 'needs_fix', steps, blockReason: `${stepId} checks failed.` }, expectedRevision);
@@ -2185,7 +2759,7 @@ export class WorkflowService {
2185
2759
  checksHash: currentCycle.reviewRecordedEvent.checksHash,
2186
2760
  reviewEventHash: currentCycle.reviewRecordedEvent.eventHash,
2187
2761
  reviewerAttestationHash: currentCycle.reviewerAttestationEvent.eventHash,
2188
- }, planRiskAudit, this.now());
2762
+ }, planRiskAudit, this.now(), this.stopOverrideContextRebindDecisionId(task, stepId, context.identity.repositoryRoot));
2189
2763
  }
2190
2764
  return this.store.writeTask(applyStrictStepReviewResolution(task, stepId, currentCycle.resolution), expectedRevision);
2191
2765
  }
@@ -2213,7 +2787,7 @@ export class WorkflowService {
2213
2787
  checksHash: reviewRecordedEvent.checksHash,
2214
2788
  reviewEventHash: reviewRecordedEvent.eventHash,
2215
2789
  reviewerAttestationHash: reviewerAttestationEvent.eventHash,
2216
- }, planRiskAudit, this.now());
2790
+ }, planRiskAudit, this.now(), this.stopOverrideContextRebindDecisionId(task, stepId, context.identity.repositoryRoot));
2217
2791
  }
2218
2792
  return this.store.writeTask(applyStrictStepReviewResolution(task, stepId, recorded.resolution, launch.review.summary), expectedRevision);
2219
2793
  }
@@ -2285,7 +2859,7 @@ export class WorkflowService {
2285
2859
  checksHash: recorded.reviewRecordedEvent.checksHash,
2286
2860
  reviewEventHash: recorded.reviewRecordedEvent.eventHash,
2287
2861
  reviewerAttestationHash: recorded.reviewerAttestationEvent.eventHash,
2288
- }, readCurrentPlanRiskAudit(this.store, task), this.now());
2862
+ }, readCurrentPlanRiskAudit(this.store, task), this.now(), this.stopOverrideContextRebindDecisionId(task, stepId, context.identity.repositoryRoot));
2289
2863
  }
2290
2864
  const saved = this.store.writeTask(applyStrictStepReviewResolution(task, stepId, recorded.resolution, review.summary), expectedRevision);
2291
2865
  return {
@@ -2521,16 +3095,23 @@ export class WorkflowService {
2521
3095
  assertCorrectiveAuditorIndependence(this.store, task, stepId, correctiveAudit.auditor);
2522
3096
  return appendCorrectiveDecisionEvent(this.store, task, stepId, correctiveAudit, planRiskAudit, task.planHash, this.now());
2523
3097
  }
2524
- setTaskResult(repository, taskId, expectedRevision, summary, limitations) {
2525
- const { identity } = this.#mutationContext(repository);
3098
+ setTaskResult(repository, taskId, expectedRevision, summary, limitations, writerToken, actor) {
3099
+ const context = this.#mutationContext(repository);
3100
+ const { identity } = context;
2526
3101
  const task = this.store.readTask(identity.projectId, taskId);
2527
3102
  assertExpectedRevision(task, expectedRevision);
2528
3103
  if (task.status !== 'validating' || task.review.status !== 'passed') {
2529
3104
  throw new WorkflowError('TRANSITION_BLOCKED', 'Result requires a passing independent review.');
2530
3105
  }
2531
3106
  this.assertReviewFresh(identity.repositoryRoot, task);
3107
+ const lease = context.locks.heartbeat(taskId, writerToken);
3108
+ this.assertTaskMutationAllowedByHandoff(task, actor ?? null, lease.token);
2532
3109
  const root = this.store.taskRoot(task.projectId, task.id);
2533
3110
  const stagedResult = this.store.stageArtifact(root, 'result.md', renderResult(task, summary, limitations));
3111
+ // Human final acceptance is an intentionally unbounded pause. Release the
3112
+ // execution lease before entering that gate so elapsed human time cannot
3113
+ // turn a successful Task into a repository-wide stale-lock repair route.
3114
+ context.locks.release(taskId, lease.token);
2534
3115
  const saved = this.store.writeTask({ ...task, status: 'awaiting_final_acceptance', resultHash: stagedResult.hash, blockReason: null }, expectedRevision);
2535
3116
  this.store.publishArtifact(stagedResult);
2536
3117
  return saved;
@@ -2550,7 +3131,12 @@ export class WorkflowService {
2550
3131
  this.assertReviewFresh(identity.repositoryRoot, task);
2551
3132
  const resultHash = this.store.hashArtifact(root, 'result.md');
2552
3133
  const evidenceHash = this.store.hashArtifact(root, 'evidence.json');
2553
- const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'task.final_accept', actor, task);
3134
+ const correctiveAuthority = delegationGrantId === null
3135
+ ? this.currentCorrectiveDerivedTaskAuthority(identity.projectId, task, actor)
3136
+ : null;
3137
+ const delegation = correctiveAuthority
3138
+ ? null
3139
+ : this.delegatedAuthorization(identity.projectId, delegationGrantId, 'task.final_accept', actor, task);
2554
3140
  const event = {
2555
3141
  kind: 'final_acceptance',
2556
3142
  decision: 'approved',
@@ -2562,7 +3148,11 @@ export class WorkflowService {
2562
3148
  evidenceHash,
2563
3149
  headCommit: headCommit(identity.repositoryRoot),
2564
3150
  reason: '',
2565
- authorizationMode: delegation ? 'delegated' : 'human',
3151
+ authorizationMode: correctiveAuthority
3152
+ ? 'corrective-derived'
3153
+ : delegation
3154
+ ? 'delegated'
3155
+ : 'human',
2566
3156
  ...(delegation ? { delegation } : {}),
2567
3157
  };
2568
3158
  return this.store.writeTask({
@@ -2793,7 +3383,288 @@ export class WorkflowService {
2793
3383
  throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
2794
3384
  return this.inspectDependencyProvenanceCandidate(snapshot, task);
2795
3385
  }
3386
+ historicalStepProvenanceRecoveryPreflight(repository, taskId) {
3387
+ const snapshot = this.observationSnapshot(repository);
3388
+ if (snapshot.observation.kind !== 'assessment') {
3389
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Historical Step provenance recovery requires a coherent observation boundary.', {
3390
+ observation: snapshot.observation,
3391
+ });
3392
+ }
3393
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
3394
+ if (!task)
3395
+ throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
3396
+ return this.inspectHistoricalStepProvenanceCandidate(snapshot, task);
3397
+ }
3398
+ historicalStepDependencyRecoveryPreflight(repository, taskId) {
3399
+ const snapshot = this.observationSnapshot(repository);
3400
+ if (snapshot.observation.kind !== 'assessment') {
3401
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Historical Step dependency recovery requires a coherent observation boundary.', {
3402
+ observation: snapshot.observation,
3403
+ });
3404
+ }
3405
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
3406
+ if (!task)
3407
+ throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
3408
+ let candidateParent = null;
3409
+ try {
3410
+ candidateParent = runGit(snapshot.identity.repositoryRoot, ['rev-parse', 'HEAD^']);
3411
+ }
3412
+ catch {
3413
+ candidateParent = null;
3414
+ }
3415
+ const assessment = candidateParent
3416
+ ? this.inspectHistoricalStepProvenanceCandidate(snapshot, task, {
3417
+ historyBoundary: 'HEAD^',
3418
+ preservedHeadCommit: candidateParent,
3419
+ })
3420
+ : null;
3421
+ if (!assessment?.eligible
3422
+ || !assessment.activeStepId
3423
+ || !assessment.commitSha
3424
+ || !task.planHash) {
3425
+ const ordinary = this.inspectDependencyProvenanceCandidate(snapshot, task);
3426
+ return {
3427
+ ...ordinary,
3428
+ eligible: false,
3429
+ action: null,
3430
+ historicalStepProvenance: null,
3431
+ blockers: [...new Set([
3432
+ ...ordinary.blockers,
3433
+ ...(assessment?.blockers ?? []),
3434
+ 'The dependency candidate parent must contain exactly one accepted and strictly reviewed historical Step commit omitted by the current corrective Plan.',
3435
+ ])],
3436
+ };
3437
+ }
3438
+ let dirtyWorktreeHash;
3439
+ try {
3440
+ dirtyWorktreeHash = hashDirtyWorktree(snapshot.identity.repositoryRoot, assessment.dirtyFiles);
3441
+ }
3442
+ catch (error) {
3443
+ const ordinary = this.inspectDependencyProvenanceCandidate(snapshot, task);
3444
+ return {
3445
+ ...ordinary,
3446
+ eligible: false,
3447
+ action: null,
3448
+ historicalStepProvenance: null,
3449
+ blockers: [...new Set([
3450
+ ...ordinary.blockers,
3451
+ error instanceof Error ? error.message : String(error),
3452
+ ])],
3453
+ };
3454
+ }
3455
+ return this.inspectDependencyProvenanceCandidate(snapshot, task, null, null, {
3456
+ activeStepId: assessment.activeStepId,
3457
+ planHash: task.planHash,
3458
+ dirtyFiles: assessment.dirtyFiles,
3459
+ dirtyWorktreeHash,
3460
+ retainedCommitSha: assessment.commitSha,
3461
+ });
3462
+ }
3463
+ recoverHistoricalStepProvenance(repository, taskId, expectedRevision, commitSha, actor, reason) {
3464
+ const normalizedActor = actor.trim();
3465
+ const normalizedReason = reason.trim();
3466
+ if (!normalizedActor)
3467
+ throw new WorkflowError('INVALID_ARGUMENT', 'Historical Step provenance recovery requires an actor.');
3468
+ if (!normalizedReason)
3469
+ throw new WorkflowError('INVALID_ARGUMENT', 'Historical Step provenance recovery requires a reason.');
3470
+ const context = this.#mutationContext(repository);
3471
+ const current = context.store.readTask(context.identity.projectId, taskId);
3472
+ const prior = current.historicalStepProvenanceRecoveries?.find((record) => record.commitSha === commitSha);
3473
+ if (prior && (current.invalidatedStepCommits ?? []).includes(commitSha)) {
3474
+ validateTaskHistory(context.identity.repositoryRoot, current);
3475
+ return current;
3476
+ }
3477
+ assertExpectedRevision(current, expectedRevision);
3478
+ const preflight = this.historicalStepProvenanceRecoveryPreflight(repository, taskId);
3479
+ if (!preflight.eligible || preflight.commitSha !== commitSha || !preflight.sourceFinalAcceptanceRecordedAt) {
3480
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'The bounded historical Step provenance recovery preflight failed.', {
3481
+ taskId,
3482
+ requestedCommit: commitSha,
3483
+ candidateCommit: preflight.commitSha,
3484
+ blockers: preflight.blockers,
3485
+ });
3486
+ }
3487
+ const sourceAcceptance = current.authorizations.find((authorization) => authorization.kind === 'final_acceptance'
3488
+ && ['approved', 'superseded'].includes(authorization.decision)
3489
+ && authorization.headCommit === commitSha
3490
+ && authorization.recordedAt === preflight.sourceFinalAcceptanceRecordedAt
3491
+ && authorization.planHash
3492
+ && authorization.resultHash
3493
+ && authorization.evidenceHash);
3494
+ if (!sourceAcceptance?.planHash || !sourceAcceptance.resultHash || !sourceAcceptance.evidenceHash) {
3495
+ throw new WorkflowError('STATE_CONFLICT', 'The source final acceptance changed after historical provenance preflight.');
3496
+ }
3497
+ const saved = context.store.writeTask({
3498
+ ...current,
3499
+ invalidatedStepCommits: [...new Set([...(current.invalidatedStepCommits ?? []), commitSha])],
3500
+ historicalStepProvenanceRecoveries: [
3501
+ ...(current.historicalStepProvenanceRecoveries ?? []),
3502
+ {
3503
+ commitSha,
3504
+ actor: normalizedActor,
3505
+ reason: normalizedReason,
3506
+ recordedAt: this.now().toISOString(),
3507
+ sourceFinalAcceptanceRecordedAt: sourceAcceptance.recordedAt,
3508
+ sourcePlanHash: sourceAcceptance.planHash,
3509
+ sourceResultHash: sourceAcceptance.resultHash,
3510
+ sourceEvidenceHash: sourceAcceptance.evidenceHash,
3511
+ },
3512
+ ],
3513
+ }, expectedRevision);
3514
+ validateTaskHistory(context.identity.repositoryRoot, saved);
3515
+ return saved;
3516
+ }
3517
+ activeDownstreamProofDependencyRecoveryPreflight(repository, taskId) {
3518
+ const snapshot = this.observationSnapshot(repository);
3519
+ if (snapshot.observation.kind !== 'assessment') {
3520
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Active downstream-proof dependency recovery requires a coherent observation boundary.', {
3521
+ observation: snapshot.observation,
3522
+ });
3523
+ }
3524
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
3525
+ if (!task)
3526
+ throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
3527
+ const assessment = assessDownstreamProofRecovery(this.store, snapshot.identity.repositoryRoot, task, { historyBoundary: 'HEAD^', preservedHeadCommit: headCommit(snapshot.identity.repositoryRoot) });
3528
+ if (!assessment.applicable
3529
+ || !assessment.eligible
3530
+ || !assessment.activeStepId
3531
+ || !assessment.dirtyWorktreeHash
3532
+ || !task.planHash) {
3533
+ const ordinary = this.inspectDependencyProvenanceCandidate(snapshot, task);
3534
+ return {
3535
+ ...ordinary,
3536
+ eligible: false,
3537
+ action: null,
3538
+ activeDownstreamProof: null,
3539
+ blockers: [...new Set([
3540
+ ...ordinary.blockers,
3541
+ ...assessment.blockers,
3542
+ 'The Task must have one eligible active downstream-proof predecessor invalidation at the candidate parent boundary.',
3543
+ ])],
3544
+ };
3545
+ }
3546
+ return this.inspectDependencyProvenanceCandidate(snapshot, task, {
3547
+ activeStepId: assessment.activeStepId,
3548
+ planHash: task.planHash,
3549
+ dirtyFiles: assessment.dirtyFiles,
3550
+ dirtyWorktreeHash: assessment.dirtyWorktreeHash,
3551
+ invalidatedStepIds: assessment.invalidatedSteps.map((step) => step.stepId),
3552
+ });
3553
+ }
3554
+ downstreamProofReplanUpdatePreflight(repository, taskId) {
3555
+ const snapshot = this.observationSnapshot(repository);
3556
+ if (snapshot.observation.kind !== 'assessment') {
3557
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan update preflight requires a coherent observation boundary.', {
3558
+ observation: snapshot.observation,
3559
+ });
3560
+ }
3561
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
3562
+ if (!task)
3563
+ throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
3564
+ const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
3565
+ const assessment = assessDownstreamProofReplanRecovery(this.store, snapshot.identity.repositoryRoot, task, { currentPlanRiskAuditExists });
3566
+ const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
3567
+ const sourceVersion = '2.0.0-beta.12.6';
3568
+ const versionSurfaces = {
3569
+ declared: versions.declared,
3570
+ locked: versions.locked,
3571
+ installed: versions.installed,
3572
+ currentBranch: versions.currentBranch,
3573
+ baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
3574
+ };
3575
+ const blockers = [
3576
+ ...assessment.blockers,
3577
+ ...(String(PACKAGE_VERSION) === sourceVersion ? ['Recovery runner must be newer than the beta.12.6 source package.'] : []),
3578
+ ...Object.entries(versionSurfaces)
3579
+ .filter(([, version]) => version !== sourceVersion)
3580
+ .map(([surface]) => `${surface} Workflow version must equal the stranded source ${sourceVersion}.`),
3581
+ ...(snapshot.observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
3582
+ ...(snapshot.observation.pendingProjectTransactions.length > 0
3583
+ || snapshot.observation.pendingMilestoneTransactions.length > 0
3584
+ || snapshot.observation.pendingTaskTransactions.length > 0
3585
+ ? ['No pending Workflow transaction may exist.'] : []),
3586
+ ...(snapshot.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
3587
+ ...(snapshot.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
3588
+ ];
3589
+ return {
3590
+ readOnly: true,
3591
+ eligible: assessment.applicable && assessment.eligible && blockers.length === 0,
3592
+ action: assessment.applicable && assessment.eligible && blockers.length === 0
3593
+ ? 'update downstream-proof-replan-transport'
3594
+ : null,
3595
+ projectId: task.projectId,
3596
+ taskId: task.id,
3597
+ taskRevision: task.revision,
3598
+ sourceVersion,
3599
+ targetVersion: PACKAGE_VERSION,
3600
+ sourceHeadCommit: assessment.invalidation?.preservedHeadCommit ?? null,
3601
+ taskBranch: task.taskBranch,
3602
+ baseBranch: task.baseBranch,
3603
+ dirtyFiles: assessment.compatibility?.dirtyFiles ?? [],
3604
+ dirtyWorktreeHash: assessment.compatibility?.dirtyWorktreeHash ?? null,
3605
+ requiredTransport: [
3606
+ 'Commit only package.json and package-lock.json on the active Milestone base.',
3607
+ 'Commit only the same dependency files on the Task branch while preserving all product bytes.',
3608
+ 'Install the exact target package, then follow fresh next to dependency registration.',
3609
+ ],
3610
+ blockers,
3611
+ };
3612
+ }
3613
+ downstreamProofReplanDependencyRecoveryPreflight(repository, taskId) {
3614
+ const snapshot = this.observationSnapshot(repository);
3615
+ if (snapshot.observation.kind !== 'assessment') {
3616
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan dependency recovery requires a coherent observation boundary.', {
3617
+ observation: snapshot.observation,
3618
+ });
3619
+ }
3620
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
3621
+ if (!task)
3622
+ throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
3623
+ let candidateParent = null;
3624
+ try {
3625
+ candidateParent = runGit(snapshot.identity.repositoryRoot, ['rev-parse', 'HEAD^']);
3626
+ }
3627
+ catch {
3628
+ candidateParent = null;
3629
+ }
3630
+ const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
3631
+ const assessment = candidateParent
3632
+ ? assessDownstreamProofReplanRecovery(this.store, snapshot.identity.repositoryRoot, task, {
3633
+ historyBoundary: 'HEAD^',
3634
+ preservedHeadCommit: candidateParent,
3635
+ currentPlanRiskAuditExists,
3636
+ })
3637
+ : null;
3638
+ if (!assessment?.applicable || !assessment.eligible || !assessment.compatibility) {
3639
+ const ordinary = this.inspectDependencyProvenanceCandidate(snapshot, task);
3640
+ return {
3641
+ ...ordinary,
3642
+ eligible: false,
3643
+ action: null,
3644
+ activeDownstreamProof: null,
3645
+ downstreamProofReplan: null,
3646
+ blockers: [...new Set([
3647
+ ...ordinary.blockers,
3648
+ ...(assessment?.blockers ?? []),
3649
+ 'The Task must preserve the exact beta.12.6 downstream-proof invalidation and accidental knowledge-rebind boundary.',
3650
+ ])],
3651
+ };
3652
+ }
3653
+ return this.inspectDependencyProvenanceCandidate(snapshot, task, null, assessment.compatibility);
3654
+ }
2796
3655
  recoverDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
3656
+ return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, false);
3657
+ }
3658
+ recoverActiveDownstreamProofDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
3659
+ return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, true);
3660
+ }
3661
+ recoverDownstreamProofReplanDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
3662
+ return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, 'downstream-replan');
3663
+ }
3664
+ recoverHistoricalStepDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
3665
+ return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, 'historical-step');
3666
+ }
3667
+ recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, recoveryMode) {
2797
3668
  const normalizedActor = actor.trim();
2798
3669
  const normalizedReason = reason.trim();
2799
3670
  if (!normalizedActor)
@@ -2803,7 +3674,13 @@ export class WorkflowService {
2803
3674
  const context = this.#mutationContext(repository);
2804
3675
  const current = context.store.readTask(context.identity.projectId, taskId);
2805
3676
  const currentHead = headCommit(context.identity.repositoryRoot);
2806
- const prior = current.dependencyProvenanceRecoveries?.find((record) => record.commitSha === currentHead && record.packageVersion === PACKAGE_VERSION);
3677
+ const prior = current.dependencyProvenanceRecoveries?.find((record) => record.commitSha === currentHead
3678
+ && record.packageVersion === PACKAGE_VERSION
3679
+ && (recoveryMode === 'downstream-replan'
3680
+ ? Boolean(record.downstreamProofReplan)
3681
+ : recoveryMode === 'historical-step'
3682
+ ? Boolean(record.historicalStepProvenance)
3683
+ : Boolean(record.activeDownstreamProof) === recoveryMode));
2807
3684
  if (prior && current.systemCommits.includes(currentHead))
2808
3685
  return current;
2809
3686
  assertExpectedRevision(current, expectedRevision);
@@ -2811,7 +3688,13 @@ export class WorkflowService {
2811
3688
  const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
2812
3689
  if (!task)
2813
3690
  throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
2814
- const preflight = this.inspectDependencyProvenanceCandidate(snapshot, task);
3691
+ const preflight = recoveryMode === 'downstream-replan'
3692
+ ? this.downstreamProofReplanDependencyRecoveryPreflight(repository, taskId)
3693
+ : recoveryMode === 'historical-step'
3694
+ ? this.historicalStepDependencyRecoveryPreflight(repository, taskId)
3695
+ : recoveryMode
3696
+ ? this.activeDownstreamProofDependencyRecoveryPreflight(repository, taskId)
3697
+ : this.inspectDependencyProvenanceCandidate(snapshot, task);
2815
3698
  if (!preflight.eligible || !preflight.commitSha || !preflight.parentCommitSha) {
2816
3699
  throw new WorkflowError('GIT_PRECONDITION_FAILED', 'The bounded dependency provenance recovery preflight failed.', {
2817
3700
  taskId,
@@ -2833,6 +3716,15 @@ export class WorkflowService {
2833
3716
  reason: normalizedReason,
2834
3717
  recordedAt: this.now().toISOString(),
2835
3718
  retainedHistoricalCommits: preflight.retainedHistoricalCommits,
3719
+ ...(preflight.activeDownstreamProof
3720
+ ? { activeDownstreamProof: preflight.activeDownstreamProof }
3721
+ : {}),
3722
+ ...(preflight.downstreamProofReplan
3723
+ ? { downstreamProofReplan: preflight.downstreamProofReplan }
3724
+ : {}),
3725
+ ...(preflight.historicalStepProvenance
3726
+ ? { historicalStepProvenance: preflight.historicalStepProvenance }
3727
+ : {}),
2836
3728
  },
2837
3729
  ],
2838
3730
  }, expectedRevision);
@@ -3232,9 +4124,53 @@ export class WorkflowService {
3232
4124
  ...(dependency ? { dependency } : {}),
3233
4125
  });
3234
4126
  }
4127
+ const prioritizedRemediationTask = status.milestones.flatMap((milestone) => {
4128
+ if (milestone.status !== 'active')
4129
+ return [];
4130
+ const event = [...readMilestoneScopeChangeEvents(this.store, status.projectId, milestone.id)]
4131
+ .reverse()
4132
+ .find((candidate) => candidate.authorizationMode === 'audited-remediation'
4133
+ && candidate.candidatePlanHash === milestone.planHash
4134
+ && candidate.remediationEvidence?.remediationTaskId);
4135
+ if (!event?.remediationEvidence)
4136
+ return [];
4137
+ const task = tasksById.get(event.remediationEvidence.remediationTaskId);
4138
+ return task
4139
+ && taskIsActionableFromRepositoryNext(task, milestonesById, tasksById)
4140
+ ? [task]
4141
+ : [];
4142
+ }).at(0) ?? null;
3235
4143
  const activeTask = requestedTask
4144
+ ?? prioritizedRemediationTask
3236
4145
  ?? status.tasks.find((task) => taskIsActionableFromRepositoryNext(task, milestonesById, tasksById));
3237
4146
  if (activeTask) {
4147
+ const historicalStepProvenanceRecovery = this.historicalStepProvenanceRecoveryPreflight(repositoryRoot, activeTask.id);
4148
+ if (historicalStepProvenanceRecovery.eligible) {
4149
+ return withAdoption({
4150
+ scope: 'task',
4151
+ id: activeTask.id,
4152
+ status: activeTask.status,
4153
+ action: 'task historical-step-provenance-recover',
4154
+ revision: activeTask.revision,
4155
+ historicalStepProvenanceRecovery,
4156
+ });
4157
+ }
4158
+ const historicalStepDependencyRecovery = this.historicalStepDependencyRecoveryPreflight(repositoryRoot, activeTask.id);
4159
+ if (historicalStepDependencyRecovery.eligible) {
4160
+ return withAdoption({
4161
+ scope: 'task',
4162
+ id: activeTask.id,
4163
+ status: activeTask.status,
4164
+ action: 'update historical-step-dependency-recover',
4165
+ revision: activeTask.revision,
4166
+ dependencyProvenanceRecovery: historicalStepDependencyRecovery,
4167
+ compatibilityBoundary: {
4168
+ preservesWorktree: true,
4169
+ prerequisite: 'The active Milestone base and Task branch contain only the exact target dependency commits; the candidate parent still binds the reviewed historical Step commit.',
4170
+ continuation: 'Register the dependency commit and historical provenance atomically, then follow fresh next.',
4171
+ },
4172
+ });
4173
+ }
3238
4174
  const dependencyRecovery = this.dependencyProvenanceRecoveryPreflight(repositoryRoot, activeTask.id);
3239
4175
  if (dependencyRecovery.eligible) {
3240
4176
  return withAdoption({
@@ -3246,6 +4182,38 @@ export class WorkflowService {
3246
4182
  dependencyProvenanceRecovery: dependencyRecovery,
3247
4183
  });
3248
4184
  }
4185
+ const activeDownstreamProofDependencyRecovery = this.activeDownstreamProofDependencyRecoveryPreflight(repositoryRoot, activeTask.id);
4186
+ if (activeDownstreamProofDependencyRecovery.eligible) {
4187
+ return withAdoption({
4188
+ scope: 'task',
4189
+ id: activeTask.id,
4190
+ status: activeTask.status,
4191
+ action: 'update downstream-proof-dependency-recover',
4192
+ revision: activeTask.revision,
4193
+ dependencyProvenanceRecovery: activeDownstreamProofDependencyRecovery,
4194
+ compatibilityBoundary: {
4195
+ preservesWorktree: true,
4196
+ prerequisite: 'The active Milestone base and Task branch already contain only the exact dependency update commits.',
4197
+ continuation: 'Run task downstream-proof-recover after this provenance registration.',
4198
+ },
4199
+ });
4200
+ }
4201
+ const downstreamProofReplanDependencyRecovery = this.downstreamProofReplanDependencyRecoveryPreflight(repositoryRoot, activeTask.id);
4202
+ if (downstreamProofReplanDependencyRecovery.eligible) {
4203
+ return withAdoption({
4204
+ scope: 'task',
4205
+ id: activeTask.id,
4206
+ status: activeTask.status,
4207
+ action: 'update downstream-proof-replan-dependency-recover',
4208
+ revision: activeTask.revision,
4209
+ dependencyProvenanceRecovery: downstreamProofReplanDependencyRecovery,
4210
+ compatibilityBoundary: {
4211
+ preservesWorktree: true,
4212
+ prerequisite: 'A newer package preflight approved the exact beta.12.6 stranded replan transport before dependency-only commits were created.',
4213
+ continuation: 'Register this dependency commit, then run task downstream-proof-replan-recover.',
4214
+ },
4215
+ });
4216
+ }
3249
4217
  const activeTaskMilestone = activeTask.milestoneId
3250
4218
  ? milestonesById.get(activeTask.milestoneId) ?? null
3251
4219
  : null;
@@ -3269,36 +4237,231 @@ export class WorkflowService {
3269
4237
  },
3270
4238
  }
3271
4239
  : value);
4240
+ const currentPlanRiskAuditExists = activeTask.planHash
4241
+ ? readPlanRiskAuditEvents(this.store, activeTask).some((event) => event.taskRevision === activeTask.revision && event.planHash === activeTask.planHash)
4242
+ : false;
4243
+ const downstreamReplanRecovery = assessDownstreamProofReplanRecovery(this.store, repositoryRoot, activeTask, { currentPlanRiskAuditExists });
4244
+ if (downstreamReplanRecovery.applicable) {
4245
+ const environmentBlockers = [
4246
+ ...(!downstreamReplanRecovery.dependencyRecoveryCommit
4247
+ ? ['The exact beta.12.7 dependency compatibility must be registered before Task recovery.']
4248
+ : []),
4249
+ ...(status.observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
4250
+ ...(status.observation.pendingProjectTransactions.length > 0
4251
+ || status.observation.pendingMilestoneTransactions.length > 0
4252
+ || status.observation.pendingTaskTransactions.length > 0
4253
+ ? ['No pending Workflow transaction may exist.'] : []),
4254
+ ...(status.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
4255
+ ...(status.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
4256
+ ];
4257
+ return withAdoption(withMilestoneCancellation(downstreamReplanRecovery.eligible && environmentBlockers.length === 0
4258
+ ? {
4259
+ scope: 'task',
4260
+ id: activeTask.id,
4261
+ status: activeTask.status,
4262
+ action: 'task downstream-proof-replan-recover',
4263
+ revision: activeTask.revision,
4264
+ downstreamProofReplanRecovery: {
4265
+ state: 'eligible',
4266
+ invalidationEventId: downstreamReplanRecovery.invalidation.eventId,
4267
+ sourcePlanHash: downstreamReplanRecovery.invalidation.planHash,
4268
+ reboundPlanHash: activeTask.planHash,
4269
+ dirtyFiles: downstreamReplanRecovery.compatibility.dirtyFiles,
4270
+ dirtyWorktreeHash: downstreamReplanRecovery.compatibility.dirtyWorktreeHash,
4271
+ dependencyRecoveryCommit: downstreamReplanRecovery.dependencyRecoveryCommit,
4272
+ boundedEffect: 'restore-needs-fix-and-return-same-task-to-plan-set',
4273
+ },
4274
+ }
4275
+ : {
4276
+ scope: 'task',
4277
+ id: activeTask.id,
4278
+ status: activeTask.status,
4279
+ action: 'doctor',
4280
+ blockedAction: 'task downstream-proof-replan-recover',
4281
+ revision: activeTask.revision,
4282
+ downstreamProofReplanRecovery: {
4283
+ state: 'blocked',
4284
+ blockers: [...downstreamReplanRecovery.blockers, ...environmentBlockers],
4285
+ },
4286
+ }));
4287
+ }
3272
4288
  let activePlanRiskAudit = null;
3273
4289
  let planRiskAuditRecovery = null;
3274
- if (activeTask.planHash) {
4290
+ const requiresReplacementPlan = activeTask.status === 'needs_fix'
4291
+ && !activeTask.steps.some((step) => step.status === 'failed');
4292
+ if (activeTask.planHash && !requiresReplacementPlan) {
3275
4293
  try {
3276
4294
  activePlanRiskAudit = readCurrentPlanRiskAudit(this.store, activeTask);
3277
4295
  }
3278
4296
  catch (error) {
3279
4297
  if (!(error instanceof WorkflowError) || error.code !== 'TRANSITION_BLOCKED')
3280
4298
  throw error;
3281
- planRiskAuditRecovery = {
3282
- scope: 'task',
3283
- id: activeTask.id,
3284
- status: activeTask.status,
3285
- action: 'task plan-risk-audit',
3286
- revision: activeTask.revision,
3287
- planRiskAuditRecovery: {
3288
- state: 'required',
3289
- reason: error.message,
3290
- currentBriefHash: activeTask.briefHash,
3291
- currentPlanHash: activeTask.planHash,
3292
- knowledgeMapRevision: activeTask.knowledgeMapRevision,
3293
- knowledgeMapHash: activeTask.knowledgeMapHash,
3294
- },
4299
+ const blockingDecision = error.details.decision;
4300
+ const blockingAudit = readPlanRiskAuditEvents(this.store, activeTask).at(-1) ?? null;
4301
+ const existingRemediationEvent = blockingAudit && activeTask.milestoneId
4302
+ ? [...readMilestoneScopeChangeEvents(this.store, status.projectId, activeTask.milestoneId)]
4303
+ .reverse()
4304
+ .find((event) => event.authorizationMode === 'audited-remediation'
4305
+ && event.remediationEvidence?.planRiskAuditEventHash === blockingAudit.eventHash) ?? null
4306
+ : null;
4307
+ const existingRemediationTask = existingRemediationEvent?.remediationEvidence
4308
+ ? tasksById.get(existingRemediationEvent.remediationEvidence.remediationTaskId) ?? null
4309
+ : null;
4310
+ const remediationDiscovery = status.discoveries.find((discovery) => !['materialized', 'abandoned'].includes(discovery.status)) ?? null;
4311
+ const remediationMilestone = activeTask.milestoneId
4312
+ ? milestonesById.get(activeTask.milestoneId) ?? null
4313
+ : null;
4314
+ const remediationMemberships = remediationMilestone
4315
+ ? new Map(remediationMilestone.memberships.map((membership) => [membership.taskId, membership]))
4316
+ : new Map();
4317
+ const remediationMembership = remediationMemberships.get(activeTask.id) ?? null;
4318
+ const auditedRemediationEligible = activeTask.status === 'awaiting_execution_authorization'
4319
+ && remediationMilestone?.status === 'active'
4320
+ && remediationMembership?.disposition === 'required';
4321
+ const remediationAncestorIds = new Set();
4322
+ const visitRemediationAncestors = (candidateTaskId) => {
4323
+ for (const predecessorTaskId of remediationMemberships.get(candidateTaskId)?.dependsOnTaskIds ?? []) {
4324
+ if (remediationAncestorIds.has(predecessorTaskId))
4325
+ continue;
4326
+ remediationAncestorIds.add(predecessorTaskId);
4327
+ visitRemediationAncestors(predecessorTaskId);
4328
+ }
3295
4329
  };
4330
+ visitRemediationAncestors(activeTask.id);
4331
+ const eligibleRemediationPredecessorTaskIds = [...remediationAncestorIds]
4332
+ .filter((candidateTaskId) => tasksById.get(candidateTaskId)?.status === 'merged')
4333
+ .sort();
4334
+ planRiskAuditRecovery = blockingDecision === 'stop-escalate' && auditedRemediationEligible
4335
+ ? {
4336
+ scope: 'task',
4337
+ id: activeTask.id,
4338
+ status: activeTask.status,
4339
+ action: existingRemediationTask?.status === 'merged'
4340
+ ? 'task plan-set'
4341
+ : existingRemediationTask
4342
+ ? 'doctor'
4343
+ : remediationDiscovery?.status === 'ready_to_materialize'
4344
+ ? 'milestone remediation-materialize'
4345
+ : remediationDiscovery
4346
+ ? 'discovery update'
4347
+ : 'discovery start',
4348
+ blockedAction: 'task authorize',
4349
+ revision: activeTask.revision,
4350
+ planRiskAuditDecision: {
4351
+ state: 'recorded-blocking',
4352
+ decision: blockingDecision,
4353
+ reason: error.message,
4354
+ requiredAction: 'Materialize one audited additive remediation Task, merge it, then replace the rejected Plan.',
4355
+ },
4356
+ auditedRemediation: {
4357
+ state: existingRemediationTask?.status === 'merged'
4358
+ ? 'merged-replan-required'
4359
+ : existingRemediationTask
4360
+ ? 'materialized-not-merged'
4361
+ : remediationDiscovery?.status === 'ready_to_materialize'
4362
+ ? 'ready-to-materialize'
4363
+ : 'discovery-required',
4364
+ humanApprovalRequired: false,
4365
+ blockedTaskId: activeTask.id,
4366
+ blockedTaskRevision: activeTask.revision,
4367
+ milestoneId: remediationMilestone.id,
4368
+ milestoneRevision: remediationMilestone.revision,
4369
+ planRiskAuditEventId: blockingAudit?.eventId ?? null,
4370
+ planRiskAuditEventHash: blockingAudit?.eventHash ?? null,
4371
+ discoveryId: remediationDiscovery?.id ?? null,
4372
+ discoveryRevision: remediationDiscovery?.revision ?? null,
4373
+ remediationTaskId: existingRemediationTask?.id ?? null,
4374
+ remediationTaskStatus: existingRemediationTask?.status ?? null,
4375
+ requiredPredecessorTaskIds: [...(remediationMembership.dependsOnTaskIds ?? [])],
4376
+ eligibleMergedAncestorTaskIds: eligibleRemediationPredecessorTaskIds,
4377
+ command: existingRemediationTask?.status === 'merged'
4378
+ ? 'task plan-set'
4379
+ : existingRemediationTask
4380
+ ? 'doctor'
4381
+ : remediationDiscovery?.status === 'ready_to_materialize'
4382
+ ? 'milestone remediation-materialize'
4383
+ : remediationDiscovery
4384
+ ? 'discovery update'
4385
+ : 'discovery start',
4386
+ boundedEffect: 'append-one-required-remediation-task-and-add-one-blocked-task-dependency',
4387
+ preservesMilestoneSemantics: true,
4388
+ },
4389
+ taskPlanContract: this.taskPlanContract(status.projectId, activeTask),
4390
+ }
4391
+ : blockingDecision === 'stop-escalate' || blockingDecision === 'split-required'
4392
+ ? {
4393
+ scope: 'task',
4394
+ id: activeTask.id,
4395
+ status: activeTask.status,
4396
+ action: blockingDecision === 'stop-escalate' ? 'task plan-set' : 'doctor',
4397
+ blockedAction: 'task authorize',
4398
+ revision: activeTask.revision,
4399
+ planRiskAuditDecision: {
4400
+ state: 'recorded-blocking',
4401
+ decision: blockingDecision,
4402
+ reason: error.message,
4403
+ requiredAction: blockingDecision === 'stop-escalate'
4404
+ ? 'Replace the rejected Step Plan before requesting fresh authorization.'
4405
+ : 'Resolve the required cross-Task split through an explicit Milestone topology decision.',
4406
+ },
4407
+ ...(blockingDecision === 'stop-escalate'
4408
+ ? { taskPlanContract: this.taskPlanContract(status.projectId, activeTask) }
4409
+ : {}),
4410
+ }
4411
+ : {
4412
+ scope: 'task',
4413
+ id: activeTask.id,
4414
+ status: activeTask.status,
4415
+ action: 'task plan-risk-audit',
4416
+ revision: activeTask.revision,
4417
+ planRiskAuditRecovery: {
4418
+ state: 'required',
4419
+ reason: error.message,
4420
+ currentBriefHash: activeTask.briefHash,
4421
+ currentPlanHash: activeTask.planHash,
4422
+ knowledgeMapRevision: activeTask.knowledgeMapRevision,
4423
+ knowledgeMapHash: activeTask.knowledgeMapHash,
4424
+ },
4425
+ };
3296
4426
  }
3297
4427
  }
3298
4428
  const taskNext = activeTask.status === 'planning'
3299
4429
  ? null
3300
- : planRiskAuditRecovery
3301
- ?? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, activePlanRiskAudit);
4430
+ : requiresReplacementPlan
4431
+ ? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, null)
4432
+ : planRiskAuditRecovery
4433
+ ?? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, activePlanRiskAudit);
4434
+ const auditedRemediationNavigation = taskNext?.auditedRemediation;
4435
+ let auditedDependencyKnowledgeRefreshBlocked = null;
4436
+ if (auditedRemediationNavigation
4437
+ && activeTaskMilestone
4438
+ && ['discovery start', 'discovery update', 'milestone remediation-materialize'].includes(String(taskNext?.action))) {
4439
+ try {
4440
+ const storedKnowledgeMap = this.store.readKnowledgeMap(status.projectId);
4441
+ const inspectedKnowledgeMap = inspectCurrentKnowledgeMap();
4442
+ if (inspectedKnowledgeMap.status !== 'active' || !inspectedKnowledgeMap.approval) {
4443
+ const dependencyKnowledgeRefresh = inspectDependencyKnowledgeRefresh(repositoryRoot, activeTaskMilestone.baseBranch, storedKnowledgeMap, inspectedKnowledgeMap);
4444
+ if (dependencyKnowledgeRefresh.eligible) {
4445
+ return withAdoption(withMilestoneCancellation({
4446
+ ...taskNext,
4447
+ auditedRemediation: {
4448
+ ...auditedRemediationNavigation,
4449
+ dependencyKnowledgeRefresh: {
4450
+ ...dependencyKnowledgeRefresh,
4451
+ humanApprovalRequired: false,
4452
+ application: 'atomic-with-remediation-materialization',
4453
+ },
4454
+ },
4455
+ }));
4456
+ }
4457
+ auditedDependencyKnowledgeRefreshBlocked = dependencyKnowledgeRefresh;
4458
+ }
4459
+ }
4460
+ catch (error) {
4461
+ if (!(error instanceof WorkflowError) || error.code !== 'NOT_FOUND')
4462
+ throw error;
4463
+ }
4464
+ }
3302
4465
  // Active Step work must outrank Project Knowledge refresh/rebind. Both completion and
3303
4466
  // strict review are executable while context refresh correctly remains forbidden.
3304
4467
  const hardStop = taskNext?.action === 'task corrective-decision'
@@ -3306,6 +4469,7 @@ export class WorkflowService {
3306
4469
  const terminalCorrectiveDecision = taskNext?.correctiveDecisionGate?.decision;
3307
4470
  if (taskNext?.action === 'task step-complete'
3308
4471
  || taskNext?.action === 'task step-review'
4472
+ || taskNext?.action === 'task downstream-proof-recover'
3309
4473
  || taskNext?.action === 'task remediation-mode-recover'
3310
4474
  || taskNext?.action === 'task plan-integrity-recover'
3311
4475
  || taskNext?.blockedAction === 'task plan-integrity-recover'
@@ -3345,7 +4509,9 @@ export class WorkflowService {
3345
4509
  ? this.store.hashArtifact(this.store.taskRoot(status.projectId, activeTask.id), 'plan.md')
3346
4510
  : null)
3347
4511
  : null;
3348
- const contextRefreshOptions = action === 'project-memory reconcile' && refreshAssessment?.eligible
4512
+ const contextRefreshOptions = action === 'project-memory reconcile'
4513
+ && taskNext?.action !== 'task plan-set'
4514
+ && refreshAssessment?.eligible
3349
4515
  ? this.delegatedContextRefreshOptions(status.projectId, activeTask, knowledgeMap, refreshAssessment.mode)
3350
4516
  : [];
3351
4517
  if (requestedTask && action === 'project-memory reconcile' && contextRefreshOptions.length === 0) {
@@ -3390,9 +4556,21 @@ export class WorkflowService {
3390
4556
  },
3391
4557
  }
3392
4558
  : {}),
4559
+ ...(auditedDependencyKnowledgeRefreshBlocked
4560
+ ? {
4561
+ auditedDependencyKnowledgeRefreshBlocked: {
4562
+ ...auditedDependencyKnowledgeRefreshBlocked,
4563
+ humanApprovalRequired: true,
4564
+ requiredAction: 'Resolve or explicitly approve the non-dependency Project Knowledge drift.',
4565
+ },
4566
+ }
4567
+ : {}),
3393
4568
  }));
3394
4569
  }
3395
4570
  const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
4571
+ if (taskNext?.action === 'task plan-set') {
4572
+ return withAdoption(withMilestoneCancellation(taskNext));
4573
+ }
3396
4574
  if (taskCanRebindKnowledge(activeTask) &&
3397
4575
  activeTask.planHash &&
3398
4576
  (activeTask.knowledgeMapRevision !== knowledgeMap.revision ||
@@ -3693,6 +4871,32 @@ export class WorkflowService {
3693
4871
  milestonePlanHash: milestone.planHash,
3694
4872
  };
3695
4873
  }
4874
+ currentCorrectiveDerivedTaskAuthority(projectId, task, actor) {
4875
+ if (!task.milestoneId || actor.trim() !== `agent:milestone-remediation:${task.milestoneId}`)
4876
+ return null;
4877
+ const milestone = this.readMilestoneCurrent(projectId, task.milestoneId);
4878
+ if (milestone.status !== 'active' || !milestone.planHash)
4879
+ return null;
4880
+ const membership = milestone.memberships.find((candidate) => candidate.taskId === task.id);
4881
+ if (membership?.disposition !== 'required')
4882
+ return null;
4883
+ const authorization = [...milestone.authorizations]
4884
+ .reverse()
4885
+ .find((candidate) => candidate.kind === 'execution' && candidate.decision === 'approved');
4886
+ if (authorization?.authorizationMode !== 'corrective-derived'
4887
+ || authorization.planHash !== milestone.planHash
4888
+ || authorization.actor !== actor.trim())
4889
+ return null;
4890
+ const scopeChange = [...readMilestoneScopeChangeEvents(this.store, projectId, milestone.id)]
4891
+ .reverse()
4892
+ .find((candidate) => candidate.authorizationMode === 'audited-remediation'
4893
+ && candidate.candidatePlanHash === milestone.planHash
4894
+ && candidate.remediationEvidence !== null
4895
+ && candidate.remediationEvidence !== undefined);
4896
+ if (!scopeChange)
4897
+ return null;
4898
+ return { milestone, authorization, scopeChange };
4899
+ }
3696
4900
  nextForTaskWithDelegation(projectId, repositoryRoot, task, planRiskAudit = null) {
3697
4901
  const transition = task.status === 'awaiting_execution_authorization'
3698
4902
  ? 'task.execution_authorize'
@@ -3702,6 +4906,12 @@ export class WorkflowService {
3702
4906
  const delegatedApprovalOptions = transition
3703
4907
  ? this.delegatedApprovalOptions(projectId, transition, task)
3704
4908
  : [];
4909
+ const correctiveDerivedActor = task.milestoneId
4910
+ ? `agent:milestone-remediation:${task.milestoneId}`
4911
+ : null;
4912
+ const correctiveDerivedAuthority = transition && correctiveDerivedActor
4913
+ ? this.currentCorrectiveDerivedTaskAuthority(projectId, task, correctiveDerivedActor)
4914
+ : null;
3705
4915
  let remediationModeRecovery = null;
3706
4916
  let guardedCorrectivePosture = null;
3707
4917
  if (planRiskAudit) {
@@ -3728,6 +4938,12 @@ export class WorkflowService {
3728
4938
  }
3729
4939
  }
3730
4940
  const next = this.taskGitNavigationOverride(task) ?? nextForTask(task);
4941
+ const nextStepId = typeof next.stepId === 'string' ? next.stepId : null;
4942
+ const downstreamProofCarryover = next.action === 'task run'
4943
+ && nextStepId
4944
+ && changedFiles(repositoryRoot).length > 0
4945
+ ? assessDownstreamProofCarryover(this.store, repositoryRoot, task, nextStepId)
4946
+ : null;
3731
4947
  let mechanicalFeasibilityOverlay = {};
3732
4948
  if (task.status === 'awaiting_execution_authorization' && task.planHash) {
3733
4949
  try {
@@ -3784,6 +5000,7 @@ export class WorkflowService {
3784
5000
  const planIntegrityAssessment = !remediationModeRecovery && planRiskAudit && failedStep
3785
5001
  ? assessPlanIntegrityRecovery(this.store, repositoryRoot, task, failedStep.id)
3786
5002
  : null;
5003
+ const downstreamProofAssessment = assessDownstreamProofRecovery(this.store, repositoryRoot, task);
3787
5004
  const strictStepReviewStep = planRiskAudit
3788
5005
  ? task.steps.find((step) => {
3789
5006
  if (step.status !== 'in_progress' || !step.evidence || !planRiskAudit.reviewRequiredStepIds.includes(step.id)) {
@@ -3956,6 +5173,39 @@ export class WorkflowService {
3956
5173
  case 'decision-required':
3957
5174
  if (guardedCorrectivePosture.correctiveDecision
3958
5175
  && guardedCorrectivePosture.correctiveDecision.planHash !== task.planHash) {
5176
+ if (guardedCorrectivePosture.correctiveDecision.decision === 'stop-escalate') {
5177
+ try {
5178
+ const assessment = readValidatedStopOverrideContextRebindIfApplicable(this.store, task, guardedCorrectivePosture.stepId, repositoryRoot);
5179
+ if (!assessment) {
5180
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Stale stop decision has no validated stop-override context-rebind continuation.');
5181
+ }
5182
+ guardedCorrectiveOverlay = {
5183
+ action: 'task run',
5184
+ stepId: guardedCorrectivePosture.stepId,
5185
+ stopOverrideContextRebind: {
5186
+ state: 'validated',
5187
+ overrideEventId: assessment.override.eventId,
5188
+ stopDecisionEventId: assessment.stopDecision.eventId,
5189
+ sourcePlanHash: assessment.override.planHash,
5190
+ reboundPlanHash: task.planHash,
5191
+ semanticPlanHash: assessment.semanticPlanHash,
5192
+ appendOnly: true,
5193
+ },
5194
+ };
5195
+ }
5196
+ catch (error) {
5197
+ guardedCorrectiveOverlay = {
5198
+ action: 'doctor',
5199
+ stepId: guardedCorrectivePosture.stepId,
5200
+ blockedAction: 'task run',
5201
+ stopOverrideContextRebind: {
5202
+ state: 'blocked',
5203
+ reason: error instanceof Error ? error.message : String(error),
5204
+ },
5205
+ };
5206
+ }
5207
+ break;
5208
+ }
3959
5209
  try {
3960
5210
  const navigation = readValidatedCorrectiveDecisionRecoveryForNavigation(this.store, task, guardedCorrectivePosture.stepId);
3961
5211
  guardedCorrectiveOverlay = navigation.recovery
@@ -4118,6 +5368,36 @@ export class WorkflowService {
4118
5368
  break;
4119
5369
  }
4120
5370
  }
5371
+ const downstreamProofOverlay = downstreamProofAssessment.applicable
5372
+ ? downstreamProofAssessment.eligible
5373
+ ? {
5374
+ action: 'task downstream-proof-recover',
5375
+ blockedAction: 'task step-complete',
5376
+ stepId: downstreamProofAssessment.activeStepId,
5377
+ downstreamProofRecovery: {
5378
+ state: 'eligible',
5379
+ expectedRevision: task.revision,
5380
+ preservesWorktree: true,
5381
+ preservedHeadCommit: downstreamProofAssessment.preservedHeadCommit,
5382
+ dirtyFiles: downstreamProofAssessment.dirtyFiles,
5383
+ outsideActiveStepFiles: downstreamProofAssessment.outsideActiveStepFiles,
5384
+ invalidatedSteps: downstreamProofAssessment.invalidatedSteps,
5385
+ boundedEffect: 'invalidate-predecessor-completion-and-return-to-plan-set',
5386
+ },
5387
+ }
5388
+ : {
5389
+ action: 'doctor',
5390
+ blockedAction: 'task downstream-proof-recover',
5391
+ stepId: downstreamProofAssessment.activeStepId,
5392
+ downstreamProofRecovery: {
5393
+ state: 'blocked',
5394
+ expectedRevision: task.revision,
5395
+ blockers: downstreamProofAssessment.blockers,
5396
+ dirtyFiles: downstreamProofAssessment.dirtyFiles,
5397
+ outsideActiveStepFiles: downstreamProofAssessment.outsideActiveStepFiles,
5398
+ },
5399
+ }
5400
+ : {};
4121
5401
  const result = {
4122
5402
  ...next,
4123
5403
  ...handoffOverlay,
@@ -4171,6 +5451,7 @@ export class WorkflowService {
4171
5451
  }
4172
5452
  : {}),
4173
5453
  ...guardedCorrectiveOverlay,
5454
+ ...downstreamProofOverlay,
4174
5455
  ...(failedStepReviewRouting?.route === 'replan' && next.action === 'task run'
4175
5456
  ? {
4176
5457
  action: 'task plan-set',
@@ -4185,7 +5466,44 @@ export class WorkflowService {
4185
5466
  }
4186
5467
  : {}),
4187
5468
  ...mechanicalFeasibilityOverlay,
5469
+ ...(downstreamProofCarryover?.applicable
5470
+ ? downstreamProofCarryover.eligible
5471
+ ? {
5472
+ downstreamProofCarryover: {
5473
+ state: 'validated',
5474
+ invalidationEventId: downstreamProofCarryover.invalidationEventId,
5475
+ stepId: nextStepId,
5476
+ dirtyFiles: downstreamProofCarryover.dirtyFiles,
5477
+ dirtyWorktreeHash: downstreamProofCarryover.dirtyWorktreeHash,
5478
+ meaning: 'The replacement Step may adopt exactly these preserved uncommitted bytes on task run.',
5479
+ },
5480
+ }
5481
+ : {
5482
+ action: 'doctor',
5483
+ blockedAction: 'task run',
5484
+ downstreamProofCarryover: {
5485
+ state: 'blocked',
5486
+ invalidationEventId: downstreamProofCarryover.invalidationEventId,
5487
+ stepId: nextStepId,
5488
+ blockers: downstreamProofCarryover.blockers,
5489
+ },
5490
+ }
5491
+ : {}),
4188
5492
  ...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
5493
+ ...(correctiveDerivedAuthority && correctiveDerivedActor
5494
+ ? {
5495
+ correctiveDerivedApproval: {
5496
+ action: transition === 'task.execution_authorize' ? 'task authorize' : 'task accept',
5497
+ actor: correctiveDerivedActor,
5498
+ humanApprovalRequired: false,
5499
+ milestoneId: correctiveDerivedAuthority.milestone.id,
5500
+ milestoneRevision: correctiveDerivedAuthority.milestone.revision,
5501
+ milestonePlanHash: correctiveDerivedAuthority.milestone.planHash,
5502
+ planRiskAuditEventId: correctiveDerivedAuthority.scopeChange.remediationEvidence?.planRiskAuditEventId ?? null,
5503
+ authority: 'current-corrective-derived-milestone-execution-authorization',
5504
+ },
5505
+ }
5506
+ : {}),
4189
5507
  };
4190
5508
  if (handoffPosture.state === 'pending') {
4191
5509
  return this.withTaskNavigationContracts(task, {
@@ -4221,6 +5539,12 @@ export class WorkflowService {
4221
5539
  };
4222
5540
  }
4223
5541
  assertTaskContextChangeAllowed(task) {
5542
+ if (task.status === 'needs_fix' && !task.steps.some((step) => step.status === 'failed')) {
5543
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} requires a replacement Step Plan; Project Knowledge cannot rebind or refresh the stale Plan.`, {
5544
+ taskId: task.id,
5545
+ requiredAction: 'Run task plan-set with the full unfinished remainder before requesting authorization.',
5546
+ });
5547
+ }
4224
5548
  const posture = this.taskTerminalCorrectivePosture(task);
4225
5549
  if (!posture)
4226
5550
  return;
@@ -4234,8 +5558,24 @@ export class WorkflowService {
4234
5558
  : 'Escalate outside this Task; stop-escalate cannot be resumed or rebound.',
4235
5559
  });
4236
5560
  }
5561
+ stopOverrideContextRebindDecisionId(task, stepId, repositoryRoot) {
5562
+ return readValidatedStopOverrideContextRebindIfApplicable(this.store, task, stepId, repositoryRoot)?.stopDecision.eventId ?? null;
5563
+ }
4237
5564
  taskHasTerminalCorrectiveDecision(task) {
4238
- return this.taskTerminalCorrectivePosture(task) !== null;
5565
+ try {
5566
+ return this.taskTerminalCorrectivePosture(task) !== null;
5567
+ }
5568
+ catch (error) {
5569
+ if (error instanceof WorkflowError
5570
+ && error.code === 'TRANSITION_BLOCKED'
5571
+ && (error.details.decision === 'stop-escalate' || error.details.decision === 'split-required')) {
5572
+ // A blocking Plan Risk Audit is not a guarded-Step terminal decision, but it
5573
+ // equally cannot own an atomic delegated context refresh. Treat it as
5574
+ // ineligible here so standalone Knowledge reconciliation remains executable.
5575
+ return true;
5576
+ }
5577
+ throw error;
5578
+ }
4239
5579
  }
4240
5580
  taskTerminalCorrectivePosture(task) {
4241
5581
  if (!task.planHash)
@@ -4339,6 +5679,7 @@ export class WorkflowService {
4339
5679
  if (!taskIsActionableFromRepositoryNext(task, milestonesById, tasksById)
4340
5680
  || !task.planHash
4341
5681
  || !taskCanRebindKnowledge(task)
5682
+ || (task.status === 'needs_fix' && !task.steps.some((step) => step.status === 'failed'))
4342
5683
  || this.taskHasTerminalCorrectiveDecision(task))
4343
5684
  continue;
4344
5685
  const assessment = assessDelegatedKnowledgeRefresh(approved, inspected, task, this.store.hashArtifact(this.store.taskRoot(projectId, task.id), 'plan.md'));
@@ -4397,6 +5738,10 @@ export class WorkflowService {
4397
5738
  statePlanHash: task.planHash,
4398
5739
  });
4399
5740
  }
5741
+ if (authorization.authorizationMode === 'corrective-derived'
5742
+ && !this.currentCorrectiveDerivedTaskAuthority(task.projectId, task, authorization.actor)) {
5743
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Corrective-derived Task authorization is no longer backed by the current Milestone authority.', { taskId: task.id, milestoneId: task.milestoneId, actor: authorization.actor });
5744
+ }
4400
5745
  const mechanicalFeasibility = authorization.mechanicalFeasibility;
4401
5746
  if (!mechanicalFeasibility)
4402
5747
  return authorization;
@@ -4468,7 +5813,7 @@ export class WorkflowService {
4468
5813
  },
4469
5814
  };
4470
5815
  }
4471
- inspectDependencyProvenanceCandidate(snapshot, task) {
5816
+ inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null, downstreamProofReplan = null, historicalStepProvenance = null) {
4472
5817
  const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
4473
5818
  const now = this.now().getTime();
4474
5819
  return inspectDependencyProvenanceRecovery(snapshot.identity.repositoryRoot, task, {
@@ -4488,7 +5833,20 @@ export class WorkflowService {
4488
5833
  currentBranch: versions.currentBranch,
4489
5834
  baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
4490
5835
  },
4491
- });
5836
+ }, activeDownstreamProof, downstreamProofReplan, historicalStepProvenance);
5837
+ }
5838
+ inspectHistoricalStepProvenanceCandidate(snapshot, task, options = {}) {
5839
+ const now = this.now().getTime();
5840
+ return inspectHistoricalStepProvenanceRecovery(snapshot.identity.repositoryRoot, task, {
5841
+ activeLeaseCount: snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) > now).length,
5842
+ staleLeaseCount: snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) <= now).length,
5843
+ pendingTransactionCount: snapshot.observation.pendingProjectTransactions.length
5844
+ + snapshot.observation.pendingMilestoneTransactions.length
5845
+ + snapshot.observation.pendingTaskTransactions.length,
5846
+ corruptTransactionCount: snapshot.observation.corruptTaskTransactions.length,
5847
+ coreOperationCount: snapshot.observation.coreTaskOperations.length,
5848
+ verifiedHistoricalStepBindings: verifiedHistoricalStepReviewBindings(this.store, task),
5849
+ }, options);
4492
5850
  }
4493
5851
  withTaskNavigationContracts(task, next) {
4494
5852
  const action = typeof next.action === 'string' ? next.action : null;
@@ -4526,7 +5884,7 @@ export class WorkflowService {
4526
5884
  return activeLeaseOwner;
4527
5885
  const authorizationKind = ['task merge', 'task merge-confirm'].includes(action)
4528
5886
  ? 'final_acceptance'
4529
- : ['task step-complete', 'task submit'].includes(action)
5887
+ : ['task step-complete', 'task downstream-proof-recover', 'task submit', 'task result-set'].includes(action)
4530
5888
  ? 'execution'
4531
5889
  : null;
4532
5890
  if (!authorizationKind)
@@ -5310,7 +6668,14 @@ function gitMutationPolicyForAction(task, action) {
5310
6668
  }
5311
6669
  }
5312
6670
  function writerTokenContractForAction(action, claimedC1Posture = false) {
5313
- const alwaysLeaseBound = ['task step-complete', 'task submit', 'task merge', 'task merge-confirm'];
6671
+ const alwaysLeaseBound = [
6672
+ 'task step-complete',
6673
+ 'task downstream-proof-recover',
6674
+ 'task submit',
6675
+ 'task result-set',
6676
+ 'task merge',
6677
+ 'task merge-confirm',
6678
+ ];
5314
6679
  const claimedC1LeaseBound = [
5315
6680
  'task run',
5316
6681
  'task step-review',