codex-workflow-v2 2.0.0-beta.2 → 2.0.0-beta.5

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 (135) hide show
  1. package/README.md +35 -2
  2. package/dist/src/alpha6/adoption.js +13 -3
  3. package/dist/src/alpha6/adoption.js.map +1 -1
  4. package/dist/src/alpha6/handoff.d.ts +23 -0
  5. package/dist/src/alpha6/handoff.js +221 -28
  6. package/dist/src/alpha6/handoff.js.map +1 -1
  7. package/dist/src/alpha6/milestone.d.ts +2 -0
  8. package/dist/src/alpha6/milestone.js +15 -0
  9. package/dist/src/alpha6/milestone.js.map +1 -1
  10. package/dist/src/alpha6/plan-risk.d.ts +1 -0
  11. package/dist/src/alpha6/plan-risk.js +15 -7
  12. package/dist/src/alpha6/plan-risk.js.map +1 -1
  13. package/dist/src/alpha6/review.d.ts +1 -0
  14. package/dist/src/alpha6/review.js +9 -0
  15. package/dist/src/alpha6/review.js.map +1 -1
  16. package/dist/src/cli.js +253 -67
  17. package/dist/src/cli.js.map +1 -1
  18. package/dist/src/contracts.d.ts +111 -5
  19. package/dist/src/contracts.js +1 -1
  20. package/dist/src/credential-output.d.ts +10 -0
  21. package/dist/src/credential-output.js +73 -0
  22. package/dist/src/credential-output.js.map +1 -0
  23. package/dist/src/errors.d.ts +1 -1
  24. package/dist/src/errors.js.map +1 -1
  25. package/dist/src/index.d.ts +1 -1
  26. package/dist/src/index.js +1 -1
  27. package/dist/src/index.js.map +1 -1
  28. package/dist/src/lifecycle/canonical-hash.d.ts +6 -0
  29. package/dist/src/lifecycle/canonical-hash.js +24 -0
  30. package/dist/src/lifecycle/canonical-hash.js.map +1 -0
  31. package/dist/src/lifecycle/catalog.d.ts +26 -0
  32. package/dist/src/lifecycle/catalog.js +208 -0
  33. package/dist/src/lifecycle/catalog.js.map +1 -0
  34. package/dist/src/lifecycle/compiler-inspection.d.ts +48 -0
  35. package/dist/src/lifecycle/compiler-inspection.js +29 -0
  36. package/dist/src/lifecycle/compiler-inspection.js.map +1 -0
  37. package/dist/src/lifecycle/core-static-readiness.d.ts +6 -0
  38. package/dist/src/lifecycle/core-static-readiness.js +130 -0
  39. package/dist/src/lifecycle/core-static-readiness.js.map +1 -0
  40. package/dist/src/lifecycle/corrective-replan-authority.d.ts +53 -0
  41. package/dist/src/lifecycle/corrective-replan-authority.js +139 -0
  42. package/dist/src/lifecycle/corrective-replan-authority.js.map +1 -0
  43. package/dist/src/lifecycle/corrective-replan-binding-manifest.d.ts +21 -0
  44. package/dist/src/lifecycle/corrective-replan-binding-manifest.js +35 -0
  45. package/dist/src/lifecycle/corrective-replan-binding-manifest.js.map +1 -0
  46. package/dist/src/lifecycle/corrective-replan-credential-core.d.ts +86 -0
  47. package/dist/src/lifecycle/corrective-replan-credential-core.js +563 -0
  48. package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -0
  49. package/dist/src/lifecycle/corrective-replan-credential-schema.d.ts +17 -0
  50. package/dist/src/lifecycle/corrective-replan-credential-schema.js +44 -0
  51. package/dist/src/lifecycle/corrective-replan-credential-schema.js.map +1 -0
  52. package/dist/src/lifecycle/corrective-replan-credentials.d.ts +47 -0
  53. package/dist/src/lifecycle/corrective-replan-credentials.js +103 -0
  54. package/dist/src/lifecycle/corrective-replan-credentials.js.map +1 -0
  55. package/dist/src/lifecycle/corrective-replan.d.ts +64 -0
  56. package/dist/src/lifecycle/corrective-replan.js +548 -0
  57. package/dist/src/lifecycle/corrective-replan.js.map +1 -0
  58. package/dist/src/lifecycle/evaluator.d.ts +2 -0
  59. package/dist/src/lifecycle/evaluator.js +35 -0
  60. package/dist/src/lifecycle/evaluator.js.map +1 -0
  61. package/dist/src/lifecycle/fingerprint.d.ts +31 -0
  62. package/dist/src/lifecycle/fingerprint.js +388 -0
  63. package/dist/src/lifecycle/fingerprint.js.map +1 -0
  64. package/dist/src/lifecycle/immutable.d.ts +1 -0
  65. package/dist/src/lifecycle/immutable.js +10 -0
  66. package/dist/src/lifecycle/immutable.js.map +1 -0
  67. package/dist/src/lifecycle/implementation-table.d.ts +29 -0
  68. package/dist/src/lifecycle/implementation-table.js +87 -0
  69. package/dist/src/lifecycle/implementation-table.js.map +1 -0
  70. package/dist/src/lifecycle/index.d.ts +4 -0
  71. package/dist/src/lifecycle/index.js +5 -0
  72. package/dist/src/lifecycle/index.js.map +1 -0
  73. package/dist/src/lifecycle/schema-artifact.d.ts +12 -0
  74. package/dist/src/lifecycle/schema-artifact.js +214 -0
  75. package/dist/src/lifecycle/schema-artifact.js.map +1 -0
  76. package/dist/src/lifecycle/semantic-registry.d.ts +6 -0
  77. package/dist/src/lifecycle/semantic-registry.js +489 -0
  78. package/dist/src/lifecycle/semantic-registry.js.map +1 -0
  79. package/dist/src/lifecycle/types.d.ts +718 -0
  80. package/dist/src/lifecycle/types.js +4 -0
  81. package/dist/src/lifecycle/types.js.map +1 -0
  82. package/dist/src/observation.d.ts +20 -0
  83. package/dist/src/observation.js +171 -0
  84. package/dist/src/observation.js.map +1 -0
  85. package/dist/src/reviewer.d.ts +39 -1
  86. package/dist/src/reviewer.js +179 -43
  87. package/dist/src/reviewer.js.map +1 -1
  88. package/dist/src/state/corrective-replan-executor.d.ts +64 -0
  89. package/dist/src/state/corrective-replan-executor.js +608 -0
  90. package/dist/src/state/corrective-replan-executor.js.map +1 -0
  91. package/dist/src/state/corrective-replan-public-schema.d.ts +3 -0
  92. package/dist/src/state/corrective-replan-public-schema.js +43 -0
  93. package/dist/src/state/corrective-replan-public-schema.js.map +1 -0
  94. package/dist/src/state/corrective-replan-public.d.ts +95 -0
  95. package/dist/src/state/corrective-replan-public.js +665 -0
  96. package/dist/src/state/corrective-replan-public.js.map +1 -0
  97. package/dist/src/state/corrective-replan-transaction.d.ts +160 -0
  98. package/dist/src/state/corrective-replan-transaction.js +730 -0
  99. package/dist/src/state/corrective-replan-transaction.js.map +1 -0
  100. package/dist/src/state/corrective-yield-executor.d.ts +31 -0
  101. package/dist/src/state/corrective-yield-executor.js +236 -0
  102. package/dist/src/state/corrective-yield-executor.js.map +1 -0
  103. package/dist/src/state/corrective-yield-transaction.d.ts +97 -0
  104. package/dist/src/state/corrective-yield-transaction.js +591 -0
  105. package/dist/src/state/corrective-yield-transaction.js.map +1 -0
  106. package/dist/src/state/lock.d.ts +76 -3
  107. package/dist/src/state/lock.js +611 -30
  108. package/dist/src/state/lock.js.map +1 -1
  109. package/dist/src/state/store.d.ts +3 -0
  110. package/dist/src/state/store.js +65 -12
  111. package/dist/src/state/store.js.map +1 -1
  112. package/dist/src/transition-core.d.ts +67 -0
  113. package/dist/src/transition-core.js +318 -0
  114. package/dist/src/transition-core.js.map +1 -0
  115. package/dist/src/version.d.ts +1 -1
  116. package/dist/src/version.js +1 -1
  117. package/dist/src/workflow.d.ts +62 -25
  118. package/dist/src/workflow.js +892 -183
  119. package/dist/src/workflow.js.map +1 -1
  120. package/docs/lifecycle/state-machine-stabilization.md +641 -0
  121. package/docs/release.md +6 -2
  122. package/docs/updating-existing-project.md +9 -0
  123. package/package.json +4 -1
  124. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +36 -0
  125. package/schemas/adoption-posture-event.schema.json +1 -1
  126. package/schemas/context-envelope.schema.json +1 -1
  127. package/schemas/corrective-replan-credentials.private.schema.json +98 -0
  128. package/schemas/corrective-replan-execution-event.private.schema.json +53 -0
  129. package/schemas/corrective-replan-public.schema.json +142 -0
  130. package/schemas/corrective-replan-task-transaction.private.schema.json +151 -0
  131. package/schemas/corrective-yield-task-transaction.private.schema.json +98 -0
  132. package/schemas/remediation-event.schema.json +11 -1
  133. package/schemas/task-handoff-event.schema.json +1 -1
  134. package/schemas/transition-definition.schema.json +2609 -0
  135. package/schemas/transition-payloads.schema.json +140 -0
@@ -9,24 +9,32 @@ import { pathAllowed, validatePlan } from './domain/validation.js';
9
9
  import { WorkflowError } from './errors.js';
10
10
  import { bindGraphEvidence, createGraphRefreshRequest, fallbackGraphBinding, inspectGraphBinding, validateGraphRequestCurrent, } from './graph.js';
11
11
  import { inspectLocalDependency, inspectWorkflowVersions } from './diagnostics.js';
12
- import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, readTaskC1Posture, } from './alpha6/handoff.js';
12
+ import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, appendTaskWriterCredentialReplacement, assertTaskClaimAllowed, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, readTaskC1Posture, } from './alpha6/handoff.js';
13
13
  import { applyAdoptionPosture, assertAdoptionBaselinePreserved, buildAdoptionPreparation, initializeProjectRegistrationAdoption, readCurrentAdoptionPosture, } from './alpha6/adoption.js';
14
14
  import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, readCurrentPlanRiskAudit, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
15
15
  import { appendCorrectiveDecisionEvent, appendRemediationEvent, appendRemediationModeRecovery, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, defaultCorrectiveAuditorActor, findCurrentGuardedRemediationPosture, findRemediationModeRecoveryCandidate, findFailedGuardedStepRequiringCorrectiveDecision, readGuardedRemediationPostureForStep, readCorrectiveDecisionEvents, readRemediationEvents, } from './alpha6/remediation.js';
16
- import { assessMilestoneInitialAssembly, applyMilestoneScopeChangeTransaction, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, prepareMilestoneScopeChangeBundle, readMilestoneForScopeChange, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
16
+ import { assessMilestoneInitialAssembly, applyMilestoneScopeChangeTransaction, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, inspectMilestoneWithIntegrity, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, prepareMilestoneScopeChangeBundle, readMilestoneForScopeChange, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
17
17
  import { applyProjectTransaction, recoverProjectTransactions, runRollbackProjectTransaction, } from './beta1/project-transaction.js';
18
- import { buildCurrentStrictStepReviewCycle, ensurePendingStrictStepReview, isStepReviewRequired, recordStrictStepReview, } from './alpha6/review.js';
18
+ import { buildCurrentStrictStepReviewCycle, countUnverifiedStrictStepReviews, ensurePendingStrictStepReview, isStepReviewRequired, recordStrictStepReview, } from './alpha6/review.js';
19
19
  import { applyMilestoneAutonomyContract, assertAutonomousPlanEvolution, prepareMilestoneAutonomyContract, readMilestoneAutonomyEvents, requireActiveMilestoneAutonomy, } from './alpha7/autonomy.js';
20
20
  import { appendCorrectiveDecisionRecovery, readValidatedCorrectiveDecisionRecoveryForNavigation, requireCorrectiveDecisionRecoveryForRun, } from './alpha7/corrective-recovery.js';
21
21
  import { applyKnowledgeSelections, hashKnowledgeMap, inspectKnowledgeMap, normalizeRelativePath, reconcileKnowledgeMap, scanProjectKnowledge, selectKnowledgeSources, } from './memory.js';
22
22
  import { canonicalJsonStringify, sha256Hex } from './alpha6/store-sidecars.js';
23
+ import { ensureDirectory } from './fs-utils.js';
23
24
  import { commitStep, mergeTaskBranch, runChecks, startLocalTaskBranch, syncBaseIntoTask, validateTaskHistory, } from './git.js';
24
25
  import { changedFiles, currentBranch, gitIsAncestor, headCommit, isClean, resolveRepositoryIdentity, runGit, } from './repository.js';
25
- import { launchStrictStepReviewer } from './reviewer.js';
26
- import { WriterLockManager } from './state/lock.js';
26
+ import { launchStrictStepReviewer, prepareExternalStrictStepReviewPacket, prepareExternalStrictTaskReviewPacket, strictReviewInputHash, validateExternalStrictStepReviewInput, validateExternalStrictTaskReviewInput, } from './reviewer.js';
27
+ import { listCoreTaskOperationMutexes, inspectWriterLease as inspectRawWriterLease, toPublicWriterLease, WriterLockManager, } from './state/lock.js';
27
28
  import { FileStateStore } from './state/store.js';
29
+ import { inspectCorrectiveReplanTaskTransactions } from './state/corrective-replan-transaction.js';
30
+ import { inspectCorrectiveYieldTaskTransactions } from './state/corrective-yield-transaction.js';
31
+ import { createCorrectiveYieldExecutor } from './state/corrective-yield-executor.js';
32
+ import { createCorrectiveReplanPublicController, } from './state/corrective-replan-public.js';
33
+ import { inspectWorkflowObservationBoundary, } from './observation.js';
28
34
  import { createEntityId } from './ulid.js';
29
35
  import { PACKAGE_VERSION } from './version.js';
36
+ import { correctiveReplanBlockedNavigation, correctiveReplanNavigation, evaluateTaskCorrectiveReplanAvailability, evaluateTaskCorrectiveReplanCandidate, identifyCorrectiveReplanPosture, prepareCorrectiveReplanCandidate, TASK_CORRECTIVE_REPLAN_TRANSITION_ID, } from './lifecycle/corrective-replan.js';
37
+ const MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES = 2;
30
38
  export class WorkflowService {
31
39
  store;
32
40
  now;
@@ -34,13 +42,42 @@ export class WorkflowService {
34
42
  this.store = store;
35
43
  this.now = now;
36
44
  }
37
- context(repository) {
45
+ #mutationContext(repository) {
38
46
  const identity = resolveRepositoryIdentity(repository);
39
47
  const projectRoot = this.store.projectRoot(identity.projectId);
48
+ const replanTransactions = inspectCorrectiveReplanTaskTransactions(projectRoot, this.store.root);
49
+ const yieldTransactions = inspectCorrectiveYieldTaskTransactions(projectRoot, this.store.root);
50
+ const taskTransactions = {
51
+ corrupt: [...replanTransactions.corrupt, ...yieldTransactions.corrupt],
52
+ pending: [...replanTransactions.pending, ...yieldTransactions.pending],
53
+ };
54
+ if (taskTransactions.corrupt.length > 0) {
55
+ throw new WorkflowError('STATE_CORRUPT', 'A Task transaction journal failed integrity validation.', {
56
+ transactions: taskTransactions.corrupt,
57
+ });
58
+ }
59
+ if (taskTransactions.pending.length > 0) {
60
+ throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'A Task transaction requires explicit recovery.', {
61
+ taskIds: [...new Set(taskTransactions.pending.map((transaction) => transaction.taskId))].sort(),
62
+ transactionIds: taskTransactions.pending.map((transaction) => transaction.transactionId).sort(),
63
+ });
64
+ }
65
+ const coreTaskOperations = listCoreTaskOperationMutexes(this.store.lockRoot(identity.projectId), this.store.root);
66
+ if (coreTaskOperations.length > 0) {
67
+ throw new WorkflowError('LOCKED', 'A Core Task operation owns the project mutation boundary.', {
68
+ taskIds: coreTaskOperations.map((operation) => operation.entityId).sort(),
69
+ operationIds: coreTaskOperations.map((operation) => operation.operationId).sort(),
70
+ });
71
+ }
40
72
  recoverProjectTransactions(projectRoot, identity.projectId);
41
73
  const projectFile = path.join(projectRoot, 'project.json');
42
74
  const projectAlreadyRegistered = existsSync(projectFile);
43
75
  if (!projectAlreadyRegistered) {
76
+ // The registration posture is a sidecar under the project root. A completely
77
+ // fresh CODEX_WORKFLOW_STATE_HOME has no project directory yet, so establish
78
+ // only that directory before writing the posture; project.json remains the
79
+ // final registration record written below.
80
+ ensureDirectory(projectRoot);
44
81
  initializeProjectRegistrationAdoption(this.store, {
45
82
  projectId: identity.projectId,
46
83
  repositoryIdentity: identity,
@@ -54,17 +91,103 @@ export class WorkflowService {
54
91
  return {
55
92
  identity,
56
93
  store: this.store,
57
- locks: new WriterLockManager(this.store.lockRoot(identity.projectId), this.now),
94
+ locks: new WriterLockManager(this.store.lockRoot(identity.projectId), this.now, this.store.root),
95
+ };
96
+ }
97
+ inspectWriterLease(repository, entityId) {
98
+ const identity = resolveRepositoryIdentity(repository);
99
+ const lease = inspectRawWriterLease(this.store.lockRoot(identity.projectId), entityId, this.store.root);
100
+ return lease ? this.publicWriterLeaseDiagnostic(lease) : null;
101
+ }
102
+ repairStaleWriterLease(repository, entityId) {
103
+ const context = this.#mutationContext(repository);
104
+ const lease = context.locks.repairStale(entityId);
105
+ return {
106
+ ...this.publicWriterLeaseDiagnostic(lease),
107
+ repaired: true,
108
+ };
109
+ }
110
+ prepareCorrectiveReplan(repository, taskId, candidate) {
111
+ return this.correctiveReplanController(repository).prepare({ taskId, candidate });
112
+ }
113
+ confirmCorrectiveReplan(repository, taskId, candidate, gate, confirmationCode) {
114
+ return this.correctiveReplanController(repository).confirm({ taskId, candidate, gate, confirmationCode });
115
+ }
116
+ executeCorrectiveReplan(repository, taskId, candidate, credential) {
117
+ return this.correctiveReplanController(repository).execute({ taskId, candidate, credential });
118
+ }
119
+ recoverCorrectiveReplan(repository, taskId, transactionId) {
120
+ return this.correctiveReplanController(repository).recover({ taskId, transactionId });
121
+ }
122
+ executeCorrectiveYield(repository, taskId, expectedTaskRevision, actor, writerToken, reason) {
123
+ const identity = resolveRepositoryIdentity(repository);
124
+ return createCorrectiveYieldExecutor({
125
+ store: this.store,
126
+ projectId: identity.projectId,
127
+ now: this.now,
128
+ }).execute({
129
+ projectId: identity.projectId,
130
+ taskId,
131
+ expectedTaskRevision,
132
+ actor,
133
+ writerToken,
134
+ reason,
135
+ });
136
+ }
137
+ recoverCorrectiveYield(repository, taskId, transactionId) {
138
+ const identity = resolveRepositoryIdentity(repository);
139
+ return createCorrectiveYieldExecutor({
140
+ store: this.store,
141
+ projectId: identity.projectId,
142
+ now: this.now,
143
+ }).recover({ taskId, transactionId });
144
+ }
145
+ correctiveReplanController(repository) {
146
+ const identity = resolveRepositoryIdentity(repository);
147
+ return createCorrectiveReplanPublicController({
148
+ store: this.store,
149
+ projectId: identity.projectId,
150
+ repositoryRoot: identity.repositoryRoot,
151
+ now: this.now,
152
+ });
153
+ }
154
+ observationSnapshot(repository) {
155
+ const identity = resolveRepositoryIdentity(repository);
156
+ const boundary = inspectWorkflowObservationBoundary({
157
+ store: this.store,
158
+ identity,
159
+ now: this.now(),
160
+ });
161
+ if (boundary.observation.kind !== 'assessment') {
162
+ return {
163
+ ...boundary,
164
+ discoveries: [],
165
+ tasks: [],
166
+ milestones: [],
167
+ delegations: [],
168
+ adoption: null,
169
+ };
170
+ }
171
+ const tasks = this.store.listTasks(identity.projectId);
172
+ const rawMilestones = this.store.listMilestones(identity.projectId);
173
+ const adoption = this.inspectAdoptionPreparation(identity, tasks, rawMilestones, boundary.leases, boundary.currentAdoptionPosture);
174
+ return {
175
+ ...boundary,
176
+ discoveries: this.store.listDiscoveries(identity.projectId),
177
+ tasks,
178
+ milestones: this.listMilestonesObserved(identity.projectId, rawMilestones, adoption.currentPosture),
179
+ delegations: this.store.listDelegations(identity.projectId),
180
+ adoption,
58
181
  };
59
182
  }
60
183
  prepareDelegation(repository, input) {
61
- const { identity } = this.context(repository);
184
+ const { identity } = this.#mutationContext(repository);
62
185
  const gate = prepareDelegationGrantGate(identity.projectId, input, this.now());
63
186
  this.assertDelegationScopeExists(identity.projectId, gate.policy);
64
187
  return gate;
65
188
  }
66
189
  grantDelegation(repository, input, confirmationCode) {
67
- const { identity } = this.context(repository);
190
+ const { identity } = this.#mutationContext(repository);
68
191
  const gate = prepareDelegationGrantGate(identity.projectId, input, this.now());
69
192
  this.assertDelegationScopeExists(identity.projectId, gate.policy);
70
193
  if (confirmationCode.trim() !== gate.confirmationCode) {
@@ -100,7 +223,7 @@ export class WorkflowService {
100
223
  }, null);
101
224
  }
102
225
  revokeDelegation(repository, grantId, expectedRevision, actor, reason) {
103
- const { identity } = this.context(repository);
226
+ const { identity } = this.#mutationContext(repository);
104
227
  const grant = this.store.readDelegation(identity.projectId, grantId);
105
228
  assertExpectedRevision(grant, expectedRevision);
106
229
  if (grant.status !== 'active') {
@@ -124,7 +247,7 @@ export class WorkflowService {
124
247
  startDiscovery(repository, input) {
125
248
  if (!input.goal.trim())
126
249
  throw new WorkflowError('INVALID_ARGUMENT', 'Discovery requires a substantive goal.');
127
- const { identity } = this.context(repository);
250
+ const { identity } = this.#mutationContext(repository);
128
251
  const timestamp = this.now().toISOString();
129
252
  const initial = {
130
253
  schemaVersion: STATE_SCHEMA_VERSION,
@@ -152,7 +275,7 @@ export class WorkflowService {
152
275
  return this.store.writeDiscovery(initial, null);
153
276
  }
154
277
  updateDiscovery(repository, discoveryId, expectedRevision, patch) {
155
- const { identity } = this.context(repository);
278
+ const { identity } = this.#mutationContext(repository);
156
279
  const current = this.store.readDiscovery(identity.projectId, discoveryId);
157
280
  if (current.status === 'materialized' || current.status === 'abandoned') {
158
281
  throw new WorkflowError('TRANSITION_BLOCKED', `Discovery ${discoveryId} is ${current.status}.`);
@@ -176,7 +299,7 @@ export class WorkflowService {
176
299
  return this.store.writeDiscovery(updated, expectedRevision);
177
300
  }
178
301
  materializeTask(repository, discoveryId, expectedRevision, title, baseBranch = 'main', milestoneId = null) {
179
- const { identity } = this.context(repository);
302
+ const { identity } = this.#mutationContext(repository);
180
303
  const discovery = this.store.readDiscovery(identity.projectId, discoveryId);
181
304
  assertExpectedRevision(discovery, expectedRevision);
182
305
  const readiness = assessDiscovery(discovery);
@@ -253,7 +376,7 @@ export class WorkflowService {
253
376
  return { discovery: savedDiscovery, task: savedTask, taskRoot };
254
377
  }
255
378
  replaceSplitRequiredTask(repository, oldTaskId, expectedOldTaskRevision, discoveryId, expectedDiscoveryRevision, expectedMilestoneRevision, title, actor) {
256
- const { identity, locks } = this.context(repository);
379
+ const { identity, locks } = this.#mutationContext(repository);
257
380
  const oldTask = this.store.readTask(identity.projectId, oldTaskId);
258
381
  assertExpectedRevision(oldTask, expectedOldTaskRevision);
259
382
  if (!oldTask.milestoneId) {
@@ -504,7 +627,7 @@ export class WorkflowService {
504
627
  };
505
628
  }
506
629
  materializeMilestone(repository, discoveryId, expectedRevision, title, baseBranch = 'main') {
507
- const { identity } = this.context(repository);
630
+ const { identity } = this.#mutationContext(repository);
508
631
  const discovery = this.store.readDiscovery(identity.projectId, discoveryId);
509
632
  assertExpectedRevision(discovery, expectedRevision);
510
633
  const readiness = assessDiscovery(discovery);
@@ -552,7 +675,7 @@ export class WorkflowService {
552
675
  };
553
676
  }
554
677
  setMilestonePlan(repository, milestoneId, expectedRevision, plan) {
555
- const { identity } = this.context(repository);
678
+ const { identity } = this.#mutationContext(repository);
556
679
  const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
557
680
  assertExpectedRevision(milestone, expectedRevision);
558
681
  if (!['planning', 'awaiting_execution_authorization', 'active', 'blocked'].includes(milestone.status)) {
@@ -624,7 +747,7 @@ export class WorkflowService {
624
747
  return saved;
625
748
  }
626
749
  prepareMilestoneScopeChange(repository, milestoneId, expectedRevision, plan, actor) {
627
- const { identity } = this.context(repository);
750
+ const { identity } = this.#mutationContext(repository);
628
751
  const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
629
752
  assertExpectedRevision(milestone, expectedRevision);
630
753
  assertMilestoneScopeChangeStatusAllowed(milestone);
@@ -641,7 +764,7 @@ export class WorkflowService {
641
764
  };
642
765
  }
643
766
  applyMilestoneScopeChange(repository, milestoneId, expectedRevision, plan, actor, confirmationCode) {
644
- const { identity } = this.context(repository);
767
+ const { identity } = this.#mutationContext(repository);
645
768
  const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
646
769
  assertExpectedRevision(milestone, expectedRevision);
647
770
  assertMilestoneScopeChangeStatusAllowed(milestone);
@@ -664,7 +787,7 @@ export class WorkflowService {
664
787
  });
665
788
  }
666
789
  prepareMilestoneAutonomy(repository, milestoneId, expectedRevision, principal, delegate, expiresAt) {
667
- const { identity } = this.context(repository);
790
+ const { identity } = this.#mutationContext(repository);
668
791
  const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
669
792
  assertExpectedRevision(milestone, expectedRevision);
670
793
  return prepareMilestoneAutonomyContract({
@@ -677,7 +800,7 @@ export class WorkflowService {
677
800
  });
678
801
  }
679
802
  grantMilestoneAutonomy(repository, milestoneId, expectedRevision, principal, delegate, expiresAt, confirmationCode) {
680
- const { identity } = this.context(repository);
803
+ const { identity } = this.#mutationContext(repository);
681
804
  const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
682
805
  assertExpectedRevision(milestone, expectedRevision);
683
806
  return applyMilestoneAutonomyContract({
@@ -692,7 +815,7 @@ export class WorkflowService {
692
815
  });
693
816
  }
694
817
  evolveMilestonePlanAutonomously(repository, milestoneId, expectedRevision, plan, actor) {
695
- const { identity } = this.context(repository);
818
+ const { identity } = this.#mutationContext(repository);
696
819
  const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
697
820
  assertExpectedRevision(milestone, expectedRevision);
698
821
  assertMilestoneScopeChangeStatusAllowed(milestone);
@@ -722,7 +845,7 @@ export class WorkflowService {
722
845
  });
723
846
  }
724
847
  authorizeMilestone(repository, milestoneId, expectedRevision, actor, reason = '', delegationGrantId = null) {
725
- const { identity } = this.context(repository);
848
+ const { identity } = this.#mutationContext(repository);
726
849
  const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
727
850
  assertExpectedRevision(milestone, expectedRevision);
728
851
  if (milestone.status !== 'awaiting_execution_authorization' || !milestone.planHash) {
@@ -751,7 +874,7 @@ export class WorkflowService {
751
874
  return this.store.writeMilestone({ ...milestone, status: 'active', planHash, authorizations: [...milestone.authorizations, event] }, expectedRevision);
752
875
  }
753
876
  validateMilestone(repository, milestoneId, expectedRevision) {
754
- const { identity } = this.context(repository);
877
+ const { identity } = this.#mutationContext(repository);
755
878
  const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
756
879
  assertExpectedRevision(milestone, expectedRevision);
757
880
  if (!['active', 'awaiting_final_acceptance'].includes(milestone.status)) {
@@ -814,7 +937,7 @@ export class WorkflowService {
814
937
  return saved;
815
938
  }
816
939
  acceptMilestone(repository, milestoneId, expectedRevision, actor, confirmationCode, delegationGrantId = null) {
817
- const { identity } = this.context(repository);
940
+ const { identity } = this.#mutationContext(repository);
818
941
  const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
819
942
  assertExpectedRevision(milestone, expectedRevision);
820
943
  if (milestone.status !== 'awaiting_final_acceptance' || !milestone.resultHash || !milestone.evidenceHash) {
@@ -853,7 +976,7 @@ export class WorkflowService {
853
976
  resultHash,
854
977
  evidenceHash,
855
978
  headCommit: currentHead,
856
- reason: `Confirmed with ${gate.confirmationCode}.`,
979
+ reason: 'Milestone final acceptance confirmed against the current validated state.',
857
980
  authorizationMode: delegation ? 'delegated' : 'human',
858
981
  ...(delegation ? { delegation } : {}),
859
982
  };
@@ -869,7 +992,7 @@ export class WorkflowService {
869
992
  cancelMilestone(repository, milestoneId, expectedRevision, reason) {
870
993
  if (!reason.trim())
871
994
  throw new WorkflowError('INVALID_ARGUMENT', 'Milestone cancellation requires a reason.');
872
- const { identity } = this.context(repository);
995
+ const { identity } = this.#mutationContext(repository);
873
996
  const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
874
997
  assertExpectedRevision(milestone, expectedRevision);
875
998
  if (['accepted', 'cancelled'].includes(milestone.status)) {
@@ -879,21 +1002,54 @@ export class WorkflowService {
879
1002
  }
880
1003
  setTaskPlan(repository, taskId, expectedRevision, plan, correctiveAudit = null, planRiskAudit = null) {
881
1004
  validatePlan(plan);
882
- const { identity } = this.context(repository);
1005
+ const { identity } = this.#mutationContext(repository);
883
1006
  const knowledgeMap = this.assertKnowledgeMapBinding(identity.repositoryRoot, identity.projectId, plan.knowledgeMapRevision, plan.knowledgeMapHash);
884
1007
  this.validateKnowledgeTargets(knowledgeMap, plan);
885
1008
  const task = this.store.readTask(identity.projectId, taskId);
886
1009
  assertExpectedRevision(task, expectedRevision);
887
- if (!['planning', 'awaiting_execution_authorization', 'needs_fix', 'blocked'].includes(task.status)) {
888
- throw new WorkflowError('TRANSITION_BLOCKED', `Cannot update Plan while Task is ${task.status}.`);
889
- }
1010
+ const correctiveDecisionEvents = task.planHash ? readCorrectiveDecisionEvents(this.store, task) : [];
890
1011
  const currentPlanCorrectiveDecisions = task.planHash
891
- ? readCorrectiveDecisionEvents(this.store, task).filter((event) => event.planHash === task.planHash)
1012
+ ? correctiveDecisionEvents.filter((event) => event.planHash === task.planHash)
892
1013
  : [];
893
1014
  const currentReplanDecisions = currentPlanCorrectiveDecisions.filter((event) => event.decision === 'replan-required');
894
1015
  const currentContinueFixDecisions = currentPlanCorrectiveDecisions.filter((event) => event.decision === 'continue-fix');
895
1016
  const currentHardBlockingDecisions = currentPlanCorrectiveDecisions.filter((event) => event.decision === 'split-required' || event.decision === 'stop-escalate');
896
- const currentReplanRequiredStepIds = [...new Set(currentReplanDecisions.map((event) => event.stepId))];
1017
+ const currentPlanRiskAudit = task.status === 'needs_fix' || currentReplanDecisions.length > 0
1018
+ ? readCurrentPlanRiskAudit(this.store, task)
1019
+ : null;
1020
+ const classifiedCorrectivePosture = currentReplanDecisions.length > 0 && currentPlanRiskAudit
1021
+ ? findCurrentGuardedRemediationPosture(this.store, task, currentPlanRiskAudit)
1022
+ : null;
1023
+ const correctiveReplanPosture = classifiedCorrectivePosture?.posture === 'replan-required'
1024
+ ? classifiedCorrectivePosture
1025
+ : currentPlanRiskAudit
1026
+ ? identifyCorrectiveReplanPosture(task, currentPlanRiskAudit.reviewRequiredStepIds, correctiveDecisionEvents, readRemediationEvents(this.store, task))
1027
+ : null;
1028
+ const correctiveReplanAvailability = correctiveReplanPosture?.posture === 'replan-required'
1029
+ ? evaluateTaskCorrectiveReplanAvailability(task, correctiveReplanPosture)
1030
+ : null;
1031
+ if (currentReplanDecisions.length > 0) {
1032
+ if (!correctiveReplanAvailability?.allowed) {
1033
+ throw new WorkflowError('TRANSITION_BLOCKED', `Transition ${TASK_CORRECTIVE_REPLAN_TRANSITION_ID} is blocked for Task ${task.id}.`, {
1034
+ transitionId: TASK_CORRECTIVE_REPLAN_TRANSITION_ID,
1035
+ posture: correctiveReplanPosture?.posture ?? null,
1036
+ evaluationPhase: correctiveReplanAvailability?.phase ?? 'availability',
1037
+ blockingGuardIds: correctiveReplanAvailability?.blockingGuardIds ?? [],
1038
+ pendingGuardIds: correctiveReplanAvailability?.pendingGuardIds ?? [],
1039
+ guardResults: correctiveReplanAvailability?.guardResults ?? [],
1040
+ });
1041
+ }
1042
+ throw new WorkflowError('TRANSITION_BLOCKED', `Legacy task plan-set cannot execute ${TASK_CORRECTIVE_REPLAN_TRANSITION_ID}.`, {
1043
+ taskId: task.id,
1044
+ transitionId: TASK_CORRECTIVE_REPLAN_TRANSITION_ID,
1045
+ requiredAction: readTaskC1Posture(this.store, task).state === 'claimed'
1046
+ ? 'Run task corrective-yield first, then use task corrective-replan-prepare.'
1047
+ : 'Use task corrective-replan-prepare, confirm, and execute.',
1048
+ });
1049
+ }
1050
+ else if (!['planning', 'awaiting_execution_authorization', 'needs_fix', 'blocked'].includes(task.status)) {
1051
+ throw new WorkflowError('TRANSITION_BLOCKED', `Cannot update Plan while Task is ${task.status}.`);
1052
+ }
897
1053
  if (currentHardBlockingDecisions.length > 0) {
898
1054
  throw new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} has current corrective decisions that prohibit normal plan updates.`, {
899
1055
  taskId: task.id,
@@ -923,9 +1079,6 @@ export class WorkflowService {
923
1079
  })),
924
1080
  });
925
1081
  }
926
- const currentPlanRiskAudit = task.status === 'needs_fix'
927
- ? readCurrentPlanRiskAudit(this.store, task)
928
- : null;
929
1082
  const failedReviewAttempts = countFailedReviewAttempts(this.store.taskRoot(identity.projectId, taskId));
930
1083
  if (task.status === 'needs_fix' && task.review.status === 'failed' && failedReviewAttempts >= 2) {
931
1084
  validateCorrectivePlanAudit(task, correctiveAudit, failedReviewAttempts);
@@ -968,7 +1121,7 @@ export class WorkflowService {
968
1121
  }
969
1122
  }
970
1123
  const root = this.store.taskRoot(identity.projectId, taskId);
971
- const stagedPlan = this.store.stageArtifact(root, 'plan.md', renderPlan(task, plan));
1124
+ const preparedPlan = prepareCorrectiveReplanCandidate(task, plan);
972
1125
  const authorizations = task.authorizations.map((authorization) => authorization.kind === 'execution' && authorization.decision === 'approved'
973
1126
  ? { ...authorization, decision: 'superseded' }
974
1127
  : authorization);
@@ -985,12 +1138,17 @@ export class WorkflowService {
985
1138
  plannedSteps.every((step) => step.status === 'completed')) {
986
1139
  throw new WorkflowError('TRANSITION_BLOCKED', 'A failed review requires at least one new or changed remediation Step.');
987
1140
  }
988
- if (currentReplanRequiredStepIds.some((stepId) => !plannedSteps.some((step) => step.id === stepId))) {
989
- throw new WorkflowError('TRANSITION_BLOCKED', `Replacement Plan under replan-required must retain every guarded Step targeted by the current corrective decision; removing or renaming one would reset remediation history.`, {
990
- taskId: task.id,
991
- requiredStepIds: currentReplanRequiredStepIds,
992
- plannedStepIds: plannedSteps.map((step) => step.id),
993
- });
1141
+ if (correctiveReplanPosture?.posture === 'replan-required') {
1142
+ const executionEvaluation = evaluateTaskCorrectiveReplanCandidate(task, correctiveReplanPosture, preparedPlan);
1143
+ if (!executionEvaluation.allowed) {
1144
+ throw new WorkflowError('TRANSITION_BLOCKED', `Transition ${TASK_CORRECTIVE_REPLAN_TRANSITION_ID} rejected the candidate Plan for Task ${task.id}.`, {
1145
+ transitionId: TASK_CORRECTIVE_REPLAN_TRANSITION_ID,
1146
+ evaluationPhase: executionEvaluation.phase,
1147
+ blockingGuardIds: executionEvaluation.blockingGuardIds,
1148
+ pendingGuardIds: executionEvaluation.pendingGuardIds,
1149
+ guardResults: executionEvaluation.guardResults,
1150
+ });
1151
+ }
994
1152
  }
995
1153
  if (guardedRemediationGate && currentReplanDecisions.length === 0 && !plannedSteps.some((step) => step.id === guardedRemediationGate.stepId)) {
996
1154
  throw new WorkflowError('TRANSITION_BLOCKED', `Continue-fix must retain guarded Step ${guardedRemediationGate.stepId}; removing or renaming it would reset the remediation breaker.`, {
@@ -998,14 +1156,10 @@ export class WorkflowService {
998
1156
  stepId: guardedRemediationGate.stepId,
999
1157
  });
1000
1158
  }
1001
- if (correctiveAudit && guardedRemediationGate && currentReplanDecisions.length === 0 && currentPlanRiskAudit) {
1002
- assertCorrectiveAuditorIndependence(this.store, task, guardedRemediationGate.stepId, correctiveAudit.auditor);
1003
- appendCorrectiveDecisionEvent(this.store, task, guardedRemediationGate.stepId, correctiveAudit, currentPlanRiskAudit, stagedPlan.hash, this.now());
1004
- }
1005
1159
  const candidate = {
1006
1160
  ...task,
1007
1161
  status: 'awaiting_execution_authorization',
1008
- planHash: stagedPlan.hash,
1162
+ planHash: preparedPlan.planHash,
1009
1163
  planObjective: plan.objective,
1010
1164
  requirements: plan.requirements,
1011
1165
  acceptance: plan.acceptance,
@@ -1025,6 +1179,18 @@ export class WorkflowService {
1025
1179
  };
1026
1180
  if (planRiskAudit)
1027
1181
  validatePlanRiskAuditCandidate(this.store, candidate, planRiskAudit);
1182
+ const stagedPlan = this.store.stageArtifact(root, 'plan.md', preparedPlan.planBytes);
1183
+ if (stagedPlan.hash !== preparedPlan.planHash) {
1184
+ throw new WorkflowError('STATE_CORRUPT', 'Prepared candidate Plan hash changed during staging.', {
1185
+ taskId: task.id,
1186
+ preparedPlanHash: preparedPlan.planHash,
1187
+ stagedPlanHash: stagedPlan.hash,
1188
+ });
1189
+ }
1190
+ if (correctiveAudit && guardedRemediationGate && currentReplanDecisions.length === 0 && currentPlanRiskAudit) {
1191
+ assertCorrectiveAuditorIndependence(this.store, task, guardedRemediationGate.stepId, correctiveAudit.auditor);
1192
+ appendCorrectiveDecisionEvent(this.store, task, guardedRemediationGate.stepId, correctiveAudit, currentPlanRiskAudit, preparedPlan.planHash, this.now());
1193
+ }
1028
1194
  const saved = this.store.writeTask(candidate, expectedRevision);
1029
1195
  this.store.publishArtifact(stagedPlan);
1030
1196
  if (planRiskAudit)
@@ -1035,7 +1201,7 @@ export class WorkflowService {
1035
1201
  return saved;
1036
1202
  }
1037
1203
  rebindTaskKnowledge(repository, taskId, expectedRevision) {
1038
- const { identity } = this.context(repository);
1204
+ const { identity } = this.#mutationContext(repository);
1039
1205
  const task = this.store.readTask(identity.projectId, taskId);
1040
1206
  assertExpectedRevision(task, expectedRevision);
1041
1207
  this.assertTaskContextChangeAllowed(task);
@@ -1120,7 +1286,7 @@ export class WorkflowService {
1120
1286
  if (!delegationGrantId) {
1121
1287
  throw new WorkflowError('TRANSITION_BLOCKED', 'Task context refresh combines approval transitions and therefore requires an explicit delegated approval grant.');
1122
1288
  }
1123
- const { identity } = this.context(repository);
1289
+ const { identity } = this.#mutationContext(repository);
1124
1290
  const task = this.store.readTask(identity.projectId, taskId);
1125
1291
  const currentMap = this.store.readKnowledgeMap(identity.projectId);
1126
1292
  assertExpectedRevision(task, expectedTaskRevision);
@@ -1211,7 +1377,7 @@ export class WorkflowService {
1211
1377
  });
1212
1378
  }
1213
1379
  authorizeTask(repository, taskId, expectedRevision, actor, reason = '', delegationGrantId = null) {
1214
- const { identity } = this.context(repository);
1380
+ const { identity } = this.#mutationContext(repository);
1215
1381
  this.readCurrentAdoptionPostureStrict(identity.projectId);
1216
1382
  const task = this.store.readTask(identity.projectId, taskId);
1217
1383
  assertExpectedRevision(task, expectedRevision);
@@ -1241,7 +1407,7 @@ export class WorkflowService {
1241
1407
  return this.store.writeTask({ ...task, status: 'ready', briefHash, planHash, authorizations: [...task.authorizations, event] }, expectedRevision);
1242
1408
  }
1243
1409
  recordPlanRiskAudit(repository, taskId, expectedRevision, input) {
1244
- const { identity } = this.context(repository);
1410
+ const { identity } = this.#mutationContext(repository);
1245
1411
  this.readCurrentAdoptionPostureStrict(identity.projectId);
1246
1412
  const task = this.store.readTask(identity.projectId, taskId);
1247
1413
  assertExpectedRevision(task, expectedRevision);
@@ -1249,7 +1415,7 @@ export class WorkflowService {
1249
1415
  return appendCurrentPlanRiskAudit(this.store, task, input, this.now());
1250
1416
  }
1251
1417
  showTaskHandoff(repository, taskId) {
1252
- const { identity } = this.context(repository);
1418
+ const { identity } = this.#mutationContext(repository);
1253
1419
  const task = this.store.readTask(identity.projectId, taskId);
1254
1420
  return {
1255
1421
  task,
@@ -1257,8 +1423,8 @@ export class WorkflowService {
1257
1423
  claimTokenAvailable: false,
1258
1424
  };
1259
1425
  }
1260
- handoffTask(repository, taskId, expectedRevision, actor, targetActor, reason, writerToken = null, grantId = null, expiresAt = null) {
1261
- const context = this.context(repository);
1426
+ handoffTask(repository, taskId, expectedRevision, actor, targetActor, reason, writerToken = null, grantId = null, expiresAt = null, replacePending = false) {
1427
+ const context = this.#mutationContext(repository);
1262
1428
  const task = this.store.readTask(context.identity.projectId, taskId);
1263
1429
  assertExpectedRevision(task, expectedRevision);
1264
1430
  const normalizedActor = actor.trim();
@@ -1316,6 +1482,7 @@ export class WorkflowService {
1316
1482
  expectedNext: String(nextForTask(task).action ?? 'task show'),
1317
1483
  grantId,
1318
1484
  expiresAt: expiresAt ?? new Date(this.now().getTime() + 24 * 60 * 60 * 1000).toISOString(),
1485
+ replacePending,
1319
1486
  }, this.now());
1320
1487
  releaseLease();
1321
1488
  return { task, event: prepared.event, bundle: prepared.bundle };
@@ -1327,15 +1494,15 @@ export class WorkflowService {
1327
1494
  }
1328
1495
  }
1329
1496
  claimTask(repository, taskId, expectedRevision, actor, claimToken, writerToken = null) {
1330
- const context = this.context(repository);
1497
+ const context = this.#mutationContext(repository);
1331
1498
  const task = this.store.readTask(context.identity.projectId, taskId);
1332
1499
  assertExpectedRevision(task, expectedRevision);
1333
1500
  const normalizedActor = actor.trim();
1334
1501
  if (!normalizedActor) {
1335
1502
  throw new WorkflowError('INVALID_ARGUMENT', 'Claim actor is required.');
1336
1503
  }
1337
- const pendingHandoff = readTaskC1Posture(this.store, task);
1338
- if (pendingHandoff.state === 'pending' && pendingHandoff.latestEvent.grantId) {
1504
+ const pendingHandoff = assertTaskClaimAllowed(this.store, task, normalizedActor, claimToken, this.now());
1505
+ if (pendingHandoff.latestEvent.grantId) {
1339
1506
  this.assertHandoffGrant(context.identity.projectId, task, normalizedActor, pendingHandoff.latestEvent.grantId);
1340
1507
  }
1341
1508
  const existingLease = context.locks.inspect(taskId);
@@ -1368,7 +1535,14 @@ export class WorkflowService {
1368
1535
  }
1369
1536
  try {
1370
1537
  const event = appendTaskClaim(this.store, task, actor, claimToken, lease.token, this.now());
1371
- return { task, event, lease };
1538
+ return {
1539
+ task,
1540
+ event,
1541
+ lease: toPublicWriterLease(lease, this.now()),
1542
+ writerCredential: acquiredHere
1543
+ ? { disposition: 'issued', token: lease.token }
1544
+ : { disposition: 'renewed' },
1545
+ };
1372
1546
  }
1373
1547
  catch (error) {
1374
1548
  if (acquiredHere)
@@ -1376,8 +1550,45 @@ export class WorkflowService {
1376
1550
  throw error;
1377
1551
  }
1378
1552
  }
1553
+ replaceClaimedWriterCredential(repository, taskId, expectedRevision, actor) {
1554
+ const context = this.#mutationContext(repository);
1555
+ const task = this.store.readTask(context.identity.projectId, taskId);
1556
+ assertExpectedRevision(task, expectedRevision);
1557
+ const normalizedActor = actor.trim();
1558
+ if (!normalizedActor)
1559
+ throw new WorkflowError('INVALID_ARGUMENT', 'Writer credential replacement actor is required.');
1560
+ const posture = readTaskC1Posture(this.store, task);
1561
+ const navigation = this.taskGitNavigationOverride(task) ?? nextForTask(task);
1562
+ const action = String(navigation.action);
1563
+ const currentLease = context.locks.inspect(taskId);
1564
+ const writerTokenContract = writerTokenContractForAction(action, posture.state === 'claimed');
1565
+ const requiredActor = writerTokenContract
1566
+ ? this.writerCredentialRecoveryActor(task, action, posture.state === 'claimed' ? posture.claimant : null, currentLease?.owner ?? null)
1567
+ : null;
1568
+ if (!requiredActor || requiredActor !== normalizedActor) {
1569
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} writer credential replacement requires the exact advertised recovery actor.`, {
1570
+ taskId,
1571
+ posture: posture.state,
1572
+ claimant: posture.state === 'claimed' ? posture.claimant : null,
1573
+ actor: normalizedActor,
1574
+ requiredActor,
1575
+ nextAction: action,
1576
+ });
1577
+ }
1578
+ const lease = currentLease
1579
+ ? context.locks.replaceCredential(taskId, normalizedActor)
1580
+ : context.locks.acquire(taskId, normalizedActor);
1581
+ if (posture.state === 'claimed') {
1582
+ appendTaskWriterCredentialReplacement(this.store, task, normalizedActor, lease.token, this.now());
1583
+ }
1584
+ return {
1585
+ task,
1586
+ lease: toPublicWriterLease(lease, this.now()),
1587
+ writerCredential: { disposition: 'issued', token: lease.token },
1588
+ };
1589
+ }
1379
1590
  handbackTask(repository, taskId, expectedRevision, actor, reason, writerToken = null, limitations = [], recommendedNext = 'Coordinator reviews the terminal Task result and recorded evidence.') {
1380
- const context = this.context(repository);
1591
+ const context = this.#mutationContext(repository);
1381
1592
  const task = this.store.readTask(context.identity.projectId, taskId);
1382
1593
  assertExpectedRevision(task, expectedRevision);
1383
1594
  const lease = task.status === 'merged' || task.status === 'cancelled'
@@ -1389,7 +1600,7 @@ export class WorkflowService {
1389
1600
  return { task, event };
1390
1601
  }
1391
1602
  startTask(repository, taskId, expectedRevision, workspaceOwner) {
1392
- const { identity } = this.context(repository);
1603
+ const { identity } = this.#mutationContext(repository);
1393
1604
  const task = this.store.readTask(identity.projectId, taskId);
1394
1605
  assertExpectedRevision(task, expectedRevision);
1395
1606
  this.requireExecutionAdoptionPosture(identity, taskId);
@@ -1441,7 +1652,7 @@ export class WorkflowService {
1441
1652
  return this.store.writeTask({ ...task, baseCommit, taskBranch, workspaceOwner, blockReason: null }, expectedRevision);
1442
1653
  }
1443
1654
  runStep(repository, taskId, stepId, expectedRevision, actor, writerToken) {
1444
- const context = this.context(repository);
1655
+ const context = this.#mutationContext(repository);
1445
1656
  const task = this.store.readTask(context.identity.projectId, taskId);
1446
1657
  assertExpectedRevision(task, expectedRevision);
1447
1658
  this.requireExecutionAdoptionPosture(context.identity, taskId);
@@ -1470,6 +1681,7 @@ export class WorkflowService {
1470
1681
  throw new WorkflowError('TRANSITION_BLOCKED', `${stepId} cannot start from ${step.status}.`);
1471
1682
  }
1472
1683
  const planRiskAudit = readCurrentPlanRiskAudit(this.store, task);
1684
+ let resumesCorrectiveReplan = false;
1473
1685
  if (remediationHistoryForStep.length > 0 && (!planRiskAudit || !isStepReviewRequired(task, stepId, planRiskAudit.reviewRequiredStepIds))) {
1474
1686
  throw new WorkflowError('TRANSITION_BLOCKED', `Step ${stepId} has remediation history but is no longer marked for guarded review by the current Plan Risk Audit; execution is blocked until the audit and Step posture are reconciled.`, {
1475
1687
  taskId,
@@ -1484,20 +1696,21 @@ export class WorkflowService {
1484
1696
  && guardedPosture.correctiveDecision.planHash !== task.planHash
1485
1697
  ? guardedPosture.correctiveDecision
1486
1698
  : null;
1487
- const recovery = staleDecision
1699
+ const recovery = staleDecision?.decision === 'continue-fix'
1488
1700
  ? requireCorrectiveDecisionRecoveryForRun(this.store, task, stepId, actor, writerToken)
1489
1701
  : null;
1490
- assertGuardedRemediationAttemptAllowed(this.store, task, stepId, planRiskAudit, recovery?.correctiveDecisionEventId ?? null);
1702
+ const gate = assertGuardedRemediationAttemptAllowed(this.store, task, stepId, planRiskAudit, recovery?.correctiveDecisionEventId ?? null);
1703
+ resumesCorrectiveReplan = gate.posture === 'replan-ready';
1491
1704
  }
1492
1705
  const knowledgeMap = this.assertTaskKnowledgeBinding(context.identity.repositoryRoot, task);
1493
1706
  const dirty = changedFiles(context.identity.repositoryRoot);
1494
- if (step.status === 'planned' && dirty.length > 0) {
1707
+ if (step.status === 'planned' && dirty.length > 0 && !resumesCorrectiveReplan) {
1495
1708
  throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} must start from a clean checkout.`, {
1496
1709
  changedFiles: dirty,
1497
1710
  });
1498
1711
  }
1499
1712
  const dirtyOutsideStep = dirty.filter((file) => !pathAllowed(file, step.allowedWrites));
1500
- if (step.status === 'failed' && dirtyOutsideStep.length > 0) {
1713
+ if ((step.status === 'failed' || resumesCorrectiveReplan) && dirtyOutsideStep.length > 0) {
1501
1714
  throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} retry has changes outside allowedWrites.`, {
1502
1715
  changedFiles: dirtyOutsideStep,
1503
1716
  });
@@ -1555,7 +1768,8 @@ export class WorkflowService {
1555
1768
  }
1556
1769
  return {
1557
1770
  task: saved,
1558
- lease,
1771
+ lease: toPublicWriterLease(lease, this.now()),
1772
+ writerCredential: { disposition: acquiredHere ? 'issued' : 'renewed' },
1559
1773
  envelope: {
1560
1774
  protocolVersion: PROTOCOL_VERSION,
1561
1775
  packageVersion: PACKAGE_VERSION,
@@ -1591,14 +1805,14 @@ export class WorkflowService {
1591
1805
  taskId,
1592
1806
  stepId,
1593
1807
  expectedRevision: saved.revision,
1594
- writerToken: lease.token,
1808
+ writerToken: acquiredHere ? lease.token : null,
1595
1809
  knowledgeMap: knowledgeSummary(knowledgeMap),
1596
1810
  knowledgeSources: selectKnowledgeSources(knowledgeMap, 'worker', task.knowledgeTargets.map((target) => target.path)),
1597
1811
  },
1598
1812
  };
1599
1813
  }
1600
1814
  taskContext(repository, taskId, role) {
1601
- const { identity } = this.context(repository);
1815
+ const { identity } = this.#mutationContext(repository);
1602
1816
  const task = this.store.readTask(identity.projectId, taskId);
1603
1817
  const historicalStrictReview = role === 'independent-reviewer'
1604
1818
  && task.steps.some((step) => step.status === 'in_progress' && step.evidence !== null);
@@ -1677,7 +1891,7 @@ export class WorkflowService {
1677
1891
  };
1678
1892
  }
1679
1893
  completeStep(repository, taskId, stepId, expectedRevision, writerToken, notes, actor) {
1680
- const context = this.context(repository);
1894
+ const context = this.#mutationContext(repository);
1681
1895
  const lease = context.locks.heartbeat(taskId, writerToken);
1682
1896
  this.readCurrentAdoptionPostureStrict(context.identity.projectId);
1683
1897
  const task = this.store.readTask(context.identity.projectId, taskId);
@@ -1763,7 +1977,7 @@ export class WorkflowService {
1763
1977
  return this.store.writeTask({ ...task, status: 'in_progress', steps, blockReason: null }, expectedRevision);
1764
1978
  }
1765
1979
  reviewStep(repository, taskId, stepId, expectedRevision, runner, actor, writerToken = null) {
1766
- const context = this.context(repository);
1980
+ const context = this.#mutationContext(repository);
1767
1981
  this.readCurrentAdoptionPostureStrict(context.identity.projectId);
1768
1982
  const task = this.store.readTask(context.identity.projectId, taskId);
1769
1983
  assertExpectedRevision(task, expectedRevision);
@@ -1819,10 +2033,18 @@ export class WorkflowService {
1819
2033
  resolution: currentCycle.resolution,
1820
2034
  });
1821
2035
  }
1822
- assertGuardedRemediationAttemptAllowed(this.store, task, stepId, planRiskAudit);
2036
+ const infrastructureFailures = countUnverifiedStrictStepReviews(this.store, task, stepId);
2037
+ if (infrastructureFailures >= MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES) {
2038
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Strict Step Review exhausted its bounded infrastructure retries.', {
2039
+ taskId: task.id,
2040
+ stepId,
2041
+ infrastructureFailures,
2042
+ requiredAction: 'Use an external sealed reviewer or run doctor; do not start another local reviewer process.',
2043
+ });
2044
+ }
1823
2045
  }
1824
2046
  else if (currentCycle.resolution !== 'pending') {
1825
- if (currentCycle.resolution === 'failed' || currentCycle.resolution === 'unverified') {
2047
+ if (currentCycle.resolution === 'failed') {
1826
2048
  if (!currentCycle.reviewRecordedEvent || !currentCycle.reviewerAttestationEvent) {
1827
2049
  throw new WorkflowError('STATE_CORRUPT', 'Strict Step Review recovery requires exact review and attestation sidecars.', {
1828
2050
  taskId: task.id,
@@ -1830,7 +2052,7 @@ export class WorkflowService {
1830
2052
  resolution: currentCycle.resolution,
1831
2053
  });
1832
2054
  }
1833
- appendRemediationEvent(this.store, task, stepId, currentCycle.resolution === 'failed' ? 'review-failed' : 'review-unverified', {
2055
+ appendRemediationEvent(this.store, task, stepId, 'review-failed', {
1834
2056
  completionCommit: currentCycle.reviewRecordedEvent.completionCommit,
1835
2057
  checksHash: currentCycle.reviewRecordedEvent.checksHash,
1836
2058
  reviewEventHash: currentCycle.reviewRecordedEvent.eventHash,
@@ -1848,7 +2070,7 @@ export class WorkflowService {
1848
2070
  reviewedCommit: launch.reviewedCommit,
1849
2071
  isolationResult: launch.verifiedIsolation ? 'verified' : 'unverified',
1850
2072
  }, this.now());
1851
- if (recorded.resolution === 'failed' || recorded.resolution === 'unverified') {
2073
+ if (recorded.resolution === 'failed') {
1852
2074
  const reviewRecordedEvent = recorded.reviewRecordedEvent;
1853
2075
  const reviewerAttestationEvent = recorded.reviewerAttestationEvent;
1854
2076
  if (!reviewRecordedEvent || !reviewerAttestationEvent) {
@@ -1858,7 +2080,7 @@ export class WorkflowService {
1858
2080
  resolution: recorded.resolution,
1859
2081
  });
1860
2082
  }
1861
- appendRemediationEvent(this.store, task, stepId, recorded.resolution === 'failed' ? 'review-failed' : 'review-unverified', {
2083
+ appendRemediationEvent(this.store, task, stepId, 'review-failed', {
1862
2084
  completionCommit: reviewRecordedEvent.completionCommit,
1863
2085
  checksHash: reviewRecordedEvent.checksHash,
1864
2086
  reviewEventHash: reviewRecordedEvent.eventHash,
@@ -1867,8 +2089,132 @@ export class WorkflowService {
1867
2089
  }
1868
2090
  return this.store.writeTask(applyStrictStepReviewResolution(task, stepId, recorded.resolution, launch.review.summary), expectedRevision);
1869
2091
  }
2092
+ prepareExternalStepReview(repository, taskId, stepId, expectedRevision) {
2093
+ const identity = resolveRepositoryIdentity(repository);
2094
+ const task = this.store.readTask(identity.projectId, taskId);
2095
+ assertExpectedRevision(task, expectedRevision);
2096
+ if (task.status !== 'in_progress' && task.status !== 'blocked') {
2097
+ throw new WorkflowError('TRANSITION_BLOCKED', `External Strict Step Review cannot run while task ${taskId} is ${task.status}.`);
2098
+ }
2099
+ const planRiskAudit = readCurrentPlanRiskAudit(this.store, task);
2100
+ if (!planRiskAudit || !isStepReviewRequired(task, stepId, planRiskAudit.reviewRequiredStepIds)) {
2101
+ throw new WorkflowError('TRANSITION_BLOCKED', `Step ${stepId} is not marked for strict review by the current Plan Risk Audit.`);
2102
+ }
2103
+ const step = requireStep(task, stepId);
2104
+ if (step.status !== 'in_progress' || !step.evidence) {
2105
+ throw new WorkflowError('TRANSITION_BLOCKED', `External Strict Step Review requires ${stepId} in progress with canonical evidence.`);
2106
+ }
2107
+ validateTaskHistory(identity.repositoryRoot, task);
2108
+ if (!isClean(identity.repositoryRoot) || headCommit(identity.repositoryRoot) !== step.evidence.commitSha) {
2109
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'External Strict Step Review requires a clean checkout at the completion commit.');
2110
+ }
2111
+ const envelope = this.taskContext(repository, taskId, 'independent-reviewer');
2112
+ return prepareExternalStrictStepReviewPacket(identity.repositoryRoot, task, this.store.taskRoot(task.projectId, task.id), envelope, stepId, step.evidence);
2113
+ }
2114
+ recordExternalStepReview(repository, taskId, stepId, expectedRevision, input, actor, writerToken = null) {
2115
+ const context = this.#mutationContext(repository);
2116
+ const task = this.store.readTask(context.identity.projectId, taskId);
2117
+ assertExpectedRevision(task, expectedRevision);
2118
+ this.proveOptionalWriterLeaseToken(context, taskId, writerToken);
2119
+ this.assertTaskMutationAllowedByHandoff(task, actor, writerToken);
2120
+ const packet = this.prepareExternalStepReview(repository, taskId, stepId, expectedRevision);
2121
+ let review;
2122
+ try {
2123
+ review = validateExternalStrictStepReviewInput(input, packet);
2124
+ }
2125
+ catch (error) {
2126
+ throw new WorkflowError('INVALID_ARGUMENT', error instanceof Error ? error.message : 'Invalid external strict review.');
2127
+ }
2128
+ if (review.reviewer.trim() === actor.trim()) {
2129
+ throw new WorkflowError('TRANSITION_BLOCKED', 'The independent Step reviewer must differ from the lifecycle mutation actor.');
2130
+ }
2131
+ const currentCycle = buildCurrentStrictStepReviewCycle(this.store, task, stepId);
2132
+ if (task.status === 'blocked' && currentCycle.resolution !== 'unverified') {
2133
+ throw new WorkflowError('TRANSITION_BLOCKED', 'External sealed review rescue requires an unverified prior review cycle.', {
2134
+ taskId,
2135
+ stepId,
2136
+ resolution: currentCycle.resolution,
2137
+ });
2138
+ }
2139
+ const infrastructureFailures = countUnverifiedStrictStepReviews(this.store, task, stepId);
2140
+ if (infrastructureFailures > MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES) {
2141
+ throw new WorkflowError('TRANSITION_BLOCKED', 'External sealed review rescue exceeded its bounded infrastructure history.', {
2142
+ taskId,
2143
+ stepId,
2144
+ infrastructureFailures,
2145
+ });
2146
+ }
2147
+ ensurePendingStrictStepReview(this.store, task, stepId, this.now());
2148
+ const recorded = recordStrictStepReview(this.store, task, stepId, {
2149
+ review,
2150
+ reviewerOutputHash: strictReviewInputHash(review),
2151
+ reviewedCommit: packet.reviewedCommit,
2152
+ isolationResult: 'verified',
2153
+ }, this.now());
2154
+ if (recorded.resolution === 'failed') {
2155
+ appendRemediationEvent(this.store, task, stepId, 'review-failed', {
2156
+ completionCommit: recorded.reviewRecordedEvent.completionCommit,
2157
+ checksHash: recorded.reviewRecordedEvent.checksHash,
2158
+ reviewEventHash: recorded.reviewRecordedEvent.eventHash,
2159
+ reviewerAttestationHash: recorded.reviewerAttestationEvent.eventHash,
2160
+ }, readCurrentPlanRiskAudit(this.store, task), this.now());
2161
+ }
2162
+ const saved = this.store.writeTask(applyStrictStepReviewResolution(task, stepId, recorded.resolution, review.summary), expectedRevision);
2163
+ return {
2164
+ mode: 'external-sealed',
2165
+ packetHash: packet.packetHash,
2166
+ repositorySealHash: packet.repositorySealHash,
2167
+ reviewerThreadId: input.reviewerThreadId,
2168
+ task: saved,
2169
+ };
2170
+ }
2171
+ prepareExternalTaskReview(repository, taskId, expectedRevision) {
2172
+ const identity = resolveRepositoryIdentity(repository);
2173
+ const task = this.store.readTask(identity.projectId, taskId);
2174
+ assertExpectedRevision(task, expectedRevision);
2175
+ const retryingUnverified = task.status === 'blocked' && task.review.status === 'unverified';
2176
+ if (task.status !== 'validating' && !retryingUnverified) {
2177
+ throw new WorkflowError('TRANSITION_BLOCKED', `External Strict Task Review cannot run while task ${taskId} is ${task.status}.`);
2178
+ }
2179
+ validateTaskHistory(identity.repositoryRoot, task);
2180
+ assertTaskBranch(identity.repositoryRoot, task);
2181
+ if (!isClean(identity.repositoryRoot)) {
2182
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'External Strict Task Review requires a clean Task checkout.');
2183
+ }
2184
+ const taskRoot = this.store.taskRoot(task.projectId, task.id);
2185
+ if (!task.evidenceHash || this.store.hashArtifact(taskRoot, 'evidence.json') !== task.evidenceHash) {
2186
+ throw new WorkflowError('STATE_CORRUPT', 'External Strict Task Review requires the exact submitted evidence artifact.');
2187
+ }
2188
+ return prepareExternalStrictTaskReviewPacket(identity.repositoryRoot, task, taskRoot, this.taskContext(repository, taskId, 'independent-reviewer'));
2189
+ }
2190
+ recordExternalTaskReview(repository, taskId, expectedRevision, input, actor, writerToken = null) {
2191
+ const context = this.#mutationContext(repository);
2192
+ const task = this.store.readTask(context.identity.projectId, taskId);
2193
+ assertExpectedRevision(task, expectedRevision);
2194
+ this.proveOptionalWriterLeaseToken(context, taskId, writerToken);
2195
+ this.assertTaskMutationAllowedByHandoff(task, actor, writerToken);
2196
+ const packet = this.prepareExternalTaskReview(repository, taskId, expectedRevision);
2197
+ let review;
2198
+ try {
2199
+ review = validateExternalStrictTaskReviewInput(input, packet);
2200
+ }
2201
+ catch (error) {
2202
+ throw new WorkflowError('INVALID_ARGUMENT', error instanceof Error ? error.message : 'Invalid external strict Task review.');
2203
+ }
2204
+ if (review.reviewer.trim() === actor.trim()) {
2205
+ throw new WorkflowError('TRANSITION_BLOCKED', 'The independent Task reviewer must differ from the lifecycle mutation actor.');
2206
+ }
2207
+ const saved = this.recordReview(repository, taskId, expectedRevision, review, actor, writerToken);
2208
+ return {
2209
+ mode: 'external-sealed',
2210
+ packetHash: packet.packetHash,
2211
+ repositorySealHash: packet.repositorySealHash,
2212
+ reviewerThreadId: input.reviewerThreadId,
2213
+ task: saved,
2214
+ };
2215
+ }
1870
2216
  submitTask(repository, taskId, expectedRevision, writerToken, actor) {
1871
- const context = this.context(repository);
2217
+ const context = this.#mutationContext(repository);
1872
2218
  const lease = context.locks.heartbeat(taskId, writerToken);
1873
2219
  const task = this.store.readTask(context.identity.projectId, taskId);
1874
2220
  assertExpectedRevision(task, expectedRevision);
@@ -1900,11 +2246,11 @@ export class WorkflowService {
1900
2246
  return saved;
1901
2247
  }
1902
2248
  recordReview(repository, taskId, expectedRevision, review, actor, writerToken = null) {
1903
- const { identity } = this.context(repository);
2249
+ const { identity } = this.#mutationContext(repository);
1904
2250
  this.readCurrentAdoptionPostureStrict(identity.projectId);
1905
2251
  const task = this.store.readTask(identity.projectId, taskId);
1906
2252
  assertExpectedRevision(task, expectedRevision);
1907
- this.proveOptionalWriterLeaseToken(this.context(repository), taskId, writerToken);
2253
+ this.proveOptionalWriterLeaseToken(this.#mutationContext(repository), taskId, writerToken);
1908
2254
  this.assertTaskMutationAllowedByHandoff(task, actor ?? null, writerToken);
1909
2255
  const retryingUnverified = task.status === 'blocked' && task.review.status === 'unverified';
1910
2256
  if (task.status !== 'validating' && !retryingUnverified) {
@@ -1942,7 +2288,7 @@ export class WorkflowService {
1942
2288
  return saved;
1943
2289
  }
1944
2290
  recoverStepCorrectiveDecision(repository, taskId, stepId, expectedRevision, actor, writerToken) {
1945
- const context = this.context(repository);
2291
+ const context = this.#mutationContext(repository);
1946
2292
  const task = this.store.readTask(context.identity.projectId, taskId);
1947
2293
  assertExpectedRevision(task, expectedRevision);
1948
2294
  this.requireExecutionAdoptionPosture(context.identity, taskId);
@@ -1954,18 +2300,18 @@ export class WorkflowService {
1954
2300
  return appendCorrectiveDecisionRecovery(this.store, task, stepId, expectedRevision, actor, writerToken, this.now());
1955
2301
  }
1956
2302
  recoverRemediationMode(repository, taskId, stepId, expectedRevision, actor, writerToken) {
1957
- const context = this.context(repository);
2303
+ const context = this.#mutationContext(repository);
1958
2304
  const task = this.store.readTask(context.identity.projectId, taskId);
1959
2305
  assertExpectedRevision(task, expectedRevision);
1960
2306
  this.requireExecutionAdoptionPosture(context.identity, taskId);
1961
2307
  return appendRemediationModeRecovery(this.store, task, stepId, expectedRevision, actor, writerToken, this.now());
1962
2308
  }
1963
2309
  recordStepCorrectiveDecision(repository, taskId, stepId, expectedRevision, correctiveAudit, actor, writerToken = null) {
1964
- const { identity } = this.context(repository);
2310
+ const { identity } = this.#mutationContext(repository);
1965
2311
  this.readCurrentAdoptionPostureStrict(identity.projectId);
1966
2312
  const task = this.store.readTask(identity.projectId, taskId);
1967
2313
  assertExpectedRevision(task, expectedRevision);
1968
- this.proveOptionalWriterLeaseToken(this.context(repository), taskId, writerToken);
2314
+ this.proveOptionalWriterLeaseToken(this.#mutationContext(repository), taskId, writerToken);
1969
2315
  this.assertTaskMutationAllowedByHandoff(task, actor ?? null, writerToken);
1970
2316
  if (!['ready', 'needs_fix', 'blocked'].includes(task.status)) {
1971
2317
  throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} cannot record a corrective decision while ${task.status}.`);
@@ -2007,7 +2353,7 @@ export class WorkflowService {
2007
2353
  return appendCorrectiveDecisionEvent(this.store, task, stepId, correctiveAudit, planRiskAudit, task.planHash, this.now());
2008
2354
  }
2009
2355
  setTaskResult(repository, taskId, expectedRevision, summary, limitations) {
2010
- const { identity } = this.context(repository);
2356
+ const { identity } = this.#mutationContext(repository);
2011
2357
  const task = this.store.readTask(identity.projectId, taskId);
2012
2358
  assertExpectedRevision(task, expectedRevision);
2013
2359
  if (task.status !== 'validating' || task.review.status !== 'passed') {
@@ -2021,7 +2367,7 @@ export class WorkflowService {
2021
2367
  return saved;
2022
2368
  }
2023
2369
  acceptTask(repository, taskId, expectedRevision, actor, delegationGrantId = null) {
2024
- const { identity } = this.context(repository);
2370
+ const { identity } = this.#mutationContext(repository);
2025
2371
  const task = this.store.readTask(identity.projectId, taskId);
2026
2372
  assertExpectedRevision(task, expectedRevision);
2027
2373
  if (task.status !== 'awaiting_final_acceptance' || !task.resultHash || !task.evidenceHash) {
@@ -2059,7 +2405,7 @@ export class WorkflowService {
2059
2405
  }, expectedRevision);
2060
2406
  }
2061
2407
  syncTaskBase(repository, taskId, expectedRevision) {
2062
- const { identity } = this.context(repository);
2408
+ const { identity } = this.#mutationContext(repository);
2063
2409
  const task = this.store.readTask(identity.projectId, taskId);
2064
2410
  assertExpectedRevision(task, expectedRevision);
2065
2411
  if (task.status !== 'merging' || task.workspaceOwner !== 'local' || !task.taskBranch || !task.baseCommit) {
@@ -2111,7 +2457,7 @@ export class WorkflowService {
2111
2457
  return saved;
2112
2458
  }
2113
2459
  mergeTask(repository, taskId, expectedRevision, writerToken, actor) {
2114
- const context = this.context(repository);
2460
+ const context = this.#mutationContext(repository);
2115
2461
  const task = this.store.readTask(context.identity.projectId, taskId);
2116
2462
  assertExpectedRevision(task, expectedRevision);
2117
2463
  const lease = context.locks.heartbeat(taskId, writerToken);
@@ -2133,7 +2479,7 @@ export class WorkflowService {
2133
2479
  return saved;
2134
2480
  }
2135
2481
  confirmExternalMerge(repository, taskId, expectedRevision, writerToken, actor) {
2136
- const context = this.context(repository);
2482
+ const context = this.#mutationContext(repository);
2137
2483
  const task = this.store.readTask(context.identity.projectId, taskId);
2138
2484
  assertExpectedRevision(task, expectedRevision);
2139
2485
  const lease = context.locks.heartbeat(taskId, writerToken);
@@ -2150,25 +2496,22 @@ export class WorkflowService {
2150
2496
  return saved;
2151
2497
  }
2152
2498
  status(repository) {
2153
- const { identity } = this.context(repository);
2154
- const tasks = this.store.listTasks(identity.projectId);
2155
- const rawMilestones = this.store.listMilestones(identity.projectId);
2156
- const adoption = this.inspectAdoptionPreparation(identity, tasks, rawMilestones);
2157
- const milestones = this.listMilestonesCurrent(identity.projectId, adoption.currentPosture);
2499
+ const snapshot = this.observationSnapshot(repository);
2158
2500
  return {
2159
- projectId: identity.projectId,
2160
- discoveries: this.store.listDiscoveries(identity.projectId),
2161
- tasks,
2162
- milestones,
2163
- milestoneInitialAssembly: milestones
2164
- .map((milestone) => this.describeMilestoneInitialAssembly(identity.projectId, milestone, tasks))
2501
+ projectId: snapshot.identity.projectId,
2502
+ discoveries: snapshot.discoveries,
2503
+ tasks: snapshot.tasks,
2504
+ milestones: snapshot.milestones,
2505
+ milestoneInitialAssembly: snapshot.milestones
2506
+ .map((milestone) => this.describeMilestoneInitialAssembly(snapshot.identity.projectId, milestone, snapshot.tasks))
2165
2507
  .filter((value) => value !== null),
2166
- delegations: this.store.listDelegations(identity.projectId),
2167
- adoption,
2508
+ delegations: snapshot.delegations,
2509
+ adoption: snapshot.adoption,
2510
+ observation: snapshot.observation,
2168
2511
  };
2169
2512
  }
2170
2513
  recoverMilestoneTransactions(repository) {
2171
- const { identity } = this.context(repository);
2514
+ const { identity } = this.#mutationContext(repository);
2172
2515
  const milestones = this.listMilestonesCurrent(identity.projectId, readCurrentAdoptionPosture(this.store, identity.projectId));
2173
2516
  return {
2174
2517
  projectId: identity.projectId,
@@ -2176,11 +2519,11 @@ export class WorkflowService {
2176
2519
  };
2177
2520
  }
2178
2521
  prepareAdoption(repository) {
2179
- const { identity } = this.context(repository);
2522
+ const { identity } = this.#mutationContext(repository);
2180
2523
  return this.inspectAdoptionPreparation(identity, this.store.listTasks(identity.projectId), this.store.listMilestones(identity.projectId));
2181
2524
  }
2182
2525
  applyAdoption(repository, actor, confirmationCode) {
2183
- const context = this.context(repository);
2526
+ const context = this.#mutationContext(repository);
2184
2527
  const tasks = context.store.listTasks(context.identity.projectId);
2185
2528
  const milestones = context.store.listMilestones(context.identity.projectId);
2186
2529
  return applyAdoptionPosture(this.store, {
@@ -2197,19 +2540,19 @@ export class WorkflowService {
2197
2540
  }, actor, confirmationCode);
2198
2541
  }
2199
2542
  updatePreflight(repository) {
2200
- const context = this.context(repository);
2201
- const tasks = context.store.listTasks(context.identity.projectId);
2202
- const milestones = context.store.listMilestones(context.identity.projectId);
2203
- const dirty = changedFiles(context.identity.repositoryRoot);
2543
+ const snapshot = this.observationSnapshot(repository);
2544
+ const tasks = snapshot.tasks;
2545
+ const milestones = snapshot.milestones;
2546
+ const dirty = changedFiles(snapshot.identity.repositoryRoot);
2204
2547
  const runningSteps = tasks.flatMap((task) => task.steps
2205
2548
  .filter((step) => step.status === 'in_progress')
2206
2549
  .map((step) => ({ taskId: task.id, stepId: step.id })));
2207
- const leases = context.locks.list();
2550
+ const leases = snapshot.leases;
2208
2551
  const now = this.now().getTime();
2209
2552
  const activeLeases = leases.filter((lease) => Date.parse(lease.expiresAt) > now);
2210
2553
  const staleLeases = leases.filter((lease) => Date.parse(lease.expiresAt) <= now);
2211
2554
  const activeMilestones = milestones.filter((milestone) => !['accepted', 'cancelled'].includes(milestone.status));
2212
- const inspectedVersions = inspectWorkflowVersions(context.identity.repositoryRoot, activeMilestones.map((milestone) => milestone.baseBranch));
2555
+ const inspectedVersions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, activeMilestones.map((milestone) => milestone.baseBranch));
2213
2556
  const milestoneBases = inspectedVersions.milestoneBases.map((base) => ({
2214
2557
  ...base,
2215
2558
  milestoneIds: activeMilestones.filter((milestone) => milestone.baseBranch === base.branch).map((milestone) => milestone.id),
@@ -2236,31 +2579,20 @@ export class WorkflowService {
2236
2579
  ...(activeLeases.length > 0 ? ['At least one writer lease is active.'] : []),
2237
2580
  ...(staleLeases.length > 0 ? ['At least one stale writer lease requires explicit repair.'] : []),
2238
2581
  ...versionBlockers,
2582
+ ...observationPreflightBlockers(snapshot.observation),
2239
2583
  ];
2240
- const adoption = buildAdoptionPreparation({
2241
- projectId: context.identity.projectId,
2242
- repositoryIdentity: context.identity,
2243
- packageVersion: PACKAGE_VERSION,
2244
- protocolVersion: PROTOCOL_VERSION,
2245
- stateSchemaVersion: STATE_SCHEMA_VERSION,
2246
- tasks,
2247
- milestones,
2248
- sidecarInventory: this.collectAdoptionSidecarInventory(context.identity.projectId, tasks, milestones),
2249
- blockers,
2250
- now: this.now(),
2251
- }, readCurrentAdoptionPosture(this.store, context.identity.projectId));
2252
2584
  return {
2253
2585
  safe: blockers.length === 0,
2254
2586
  packageVersion: PACKAGE_VERSION,
2255
2587
  protocolVersion: PROTOCOL_VERSION,
2256
2588
  stateSchemaVersion: STATE_SCHEMA_VERSION,
2257
- projectId: context.identity.projectId,
2258
- repositoryRoot: context.identity.repositoryRoot,
2589
+ projectId: snapshot.identity.projectId,
2590
+ repositoryRoot: snapshot.identity.repositoryRoot,
2259
2591
  clean: dirty.length === 0,
2260
2592
  changedFiles: dirty,
2261
2593
  runningSteps,
2262
- activeLeases,
2263
- staleLeases,
2594
+ activeLeases: activeLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
2595
+ staleLeases: staleLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
2264
2596
  blockers,
2265
2597
  workflowVersions: {
2266
2598
  runtime: inspectedVersions.runtime,
@@ -2276,11 +2608,12 @@ export class WorkflowService {
2276
2608
  ? null
2277
2609
  : 'Land only package.json/package-lock.json dependency changes on every active Milestone base; do not transport unrelated product commits.',
2278
2610
  },
2279
- adoption,
2611
+ adoption: snapshot.adoption,
2612
+ observation: snapshot.observation,
2280
2613
  };
2281
2614
  }
2282
2615
  alpha6StrictReviewRescuePreflight(repository) {
2283
- const context = this.context(repository);
2616
+ const context = this.#mutationContext(repository);
2284
2617
  const dependency = inspectLocalDependency(context.identity.repositoryRoot);
2285
2618
  const tasks = context.store.listTasks(context.identity.projectId);
2286
2619
  const leases = context.locks.list();
@@ -2375,15 +2708,15 @@ export class WorkflowService {
2375
2708
  projectId: context.identity.projectId,
2376
2709
  repositoryRoot: context.identity.repositoryRoot,
2377
2710
  clean: dirty.length === 0,
2378
- activeLeases,
2379
- staleLeases,
2711
+ activeLeases: activeLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
2712
+ staleLeases: staleLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
2380
2713
  candidate,
2381
2714
  blockers,
2382
2715
  requiredActions,
2383
2716
  };
2384
2717
  }
2385
2718
  scanKnowledgeMap(repository) {
2386
- const { identity } = this.context(repository);
2719
+ const { identity } = this.#mutationContext(repository);
2387
2720
  const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
2388
2721
  try {
2389
2722
  const current = this.store.readKnowledgeMap(identity.projectId);
@@ -2396,11 +2729,11 @@ export class WorkflowService {
2396
2729
  }
2397
2730
  }
2398
2731
  showKnowledgeMap(repository) {
2399
- const { identity } = this.context(repository);
2732
+ const { identity } = this.#mutationContext(repository);
2400
2733
  return this.store.readKnowledgeMap(identity.projectId);
2401
2734
  }
2402
2735
  knowledgeMapStatus(repository) {
2403
- const { identity } = this.context(repository);
2736
+ const { identity } = this.#mutationContext(repository);
2404
2737
  const current = this.store.readKnowledgeMap(identity.projectId);
2405
2738
  const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
2406
2739
  return inspectKnowledgeMap(current, scanned);
@@ -2409,7 +2742,7 @@ export class WorkflowService {
2409
2742
  return this.approveKnowledgeMapForTaskRefresh(repository, expectedRevision, selections, actor, delegationGrantId, null);
2410
2743
  }
2411
2744
  approveKnowledgeMapForTaskRefresh(repository, expectedRevision, selections, actor, delegationGrantId, autonomyTaskId) {
2412
- const { identity } = this.context(repository);
2745
+ const { identity } = this.#mutationContext(repository);
2413
2746
  const current = this.store.readKnowledgeMap(identity.projectId);
2414
2747
  assertExpectedRevision(current, expectedRevision);
2415
2748
  const inspected = inspectKnowledgeMap(current, scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now()));
@@ -2426,43 +2759,35 @@ export class WorkflowService {
2426
2759
  return this.store.writeKnowledgeMap(approved, expectedRevision);
2427
2760
  }
2428
2761
  reconcileKnowledgeMap(repository, expectedRevision, allowAtomicTaskRefresh = false) {
2429
- const { identity } = this.context(repository);
2762
+ const { identity } = this.#mutationContext(repository);
2430
2763
  const current = this.store.readKnowledgeMap(identity.projectId);
2431
2764
  assertExpectedRevision(current, expectedRevision);
2432
2765
  const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
2433
2766
  if (!allowAtomicTaskRefresh && current.status === 'active' && current.approval) {
2434
2767
  const milestonesById = new Map(this.listMilestonesCurrent(identity.projectId).map((milestone) => [milestone.id, milestone]));
2435
- const activeTask = this.store
2436
- .listTasks(identity.projectId)
2437
- .find((task) => taskIsActionableFromRepositoryNext(task, milestonesById));
2438
- if (activeTask?.planHash
2439
- && taskCanRebindKnowledge(activeTask)
2440
- && !this.taskHasTerminalCorrectiveDecision(activeTask)) {
2441
- const inspected = inspectKnowledgeMap(current, scanned);
2442
- const assessment = assessDelegatedKnowledgeRefresh(current, inspected, activeTask, this.store.hashArtifact(this.store.taskRoot(identity.projectId, activeTask.id), 'plan.md'));
2443
- if (assessment.eligible
2444
- && this.delegatedContextRefreshOptions(identity.projectId, activeTask, inspected, assessment.mode).length > 0) {
2445
- throw new WorkflowError('TRANSITION_BLOCKED', 'Standalone Project Knowledge reconcile would strand an authorized Task context refresh.', {
2446
- taskId: activeTask.id,
2447
- requiredAction: 'Run the exact task context-refresh action returned by next.',
2448
- });
2449
- }
2768
+ const inspected = inspectKnowledgeMap(current, scanned);
2769
+ const refreshOwner = this.findDelegatedContextRefreshOwner(identity.projectId, this.store.listTasks(identity.projectId), milestonesById, current, inspected);
2770
+ if (refreshOwner) {
2771
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Standalone Project Knowledge reconcile would strand an authorized Task context refresh.', {
2772
+ taskId: refreshOwner.task.id,
2773
+ requiredAction: 'Run the exact task context-refresh action returned by next.',
2774
+ });
2450
2775
  }
2451
2776
  }
2452
2777
  return this.store.writeKnowledgeMap(reconcileKnowledgeMap(current, scanned), expectedRevision);
2453
2778
  }
2454
2779
  graphRefreshRequest(repository, graphKind, mode = 'moderate') {
2455
- const { identity } = this.context(repository);
2780
+ const { identity } = this.#mutationContext(repository);
2456
2781
  return createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
2457
2782
  }
2458
2783
  graphStatus(repository, graphKind, mode = 'moderate') {
2459
- const { identity } = this.context(repository);
2784
+ const { identity } = this.#mutationContext(repository);
2460
2785
  const request = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
2461
2786
  const binding = this.readGraphBindingOrNull(identity.projectId, graphKind);
2462
2787
  return { request, binding: binding ? inspectGraphBinding(binding, request) : null };
2463
2788
  }
2464
2789
  bindGraph(repository, expectedRevision, request, evidence) {
2465
- const { identity } = this.context(repository);
2790
+ const { identity } = this.#mutationContext(repository);
2466
2791
  const currentRequest = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, request.graphKind, request.mode, this.now());
2467
2792
  validateGraphRequestCurrent(request, currentRequest);
2468
2793
  const existing = this.readGraphBindingOrNull(identity.projectId, request.graphKind);
@@ -2470,13 +2795,78 @@ export class WorkflowService {
2470
2795
  return this.store.writeGraphBinding(binding, expectedRevision);
2471
2796
  }
2472
2797
  recordGraphFallback(repository, graphKind, mode, expectedRevision, reason) {
2473
- const { identity } = this.context(repository);
2798
+ const { identity } = this.#mutationContext(repository);
2474
2799
  const request = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
2475
2800
  const existing = this.readGraphBindingOrNull(identity.projectId, graphKind);
2476
2801
  return this.store.writeGraphBinding(fallbackGraphBinding(identity.projectId, request, reason, existing, this.now()), expectedRevision);
2477
2802
  }
2478
- next(repository) {
2803
+ next(repository, taskId = null) {
2479
2804
  const status = this.status(repository);
2805
+ if (status.observation.kind === 'blocked') {
2806
+ return {
2807
+ scope: 'project',
2808
+ projectId: status.projectId,
2809
+ status: 'observation-blocked',
2810
+ action: 'blocked',
2811
+ observation: status.observation,
2812
+ navigation: {
2813
+ kind: 'blocked',
2814
+ reasonCodes: status.observation.reasonCodes,
2815
+ availableRepairTransitionId: null,
2816
+ },
2817
+ };
2818
+ }
2819
+ if (status.observation.kind === 'repair') {
2820
+ const repair = status.observation.repair;
2821
+ return {
2822
+ scope: 'project',
2823
+ projectId: status.projectId,
2824
+ status: 'repair-required',
2825
+ action: repair?.command ?? 'blocked',
2826
+ observation: status.observation,
2827
+ navigation: {
2828
+ kind: repair ? 'repair' : 'blocked',
2829
+ transitionId: null,
2830
+ availableRepairTransitionId: null,
2831
+ ...(repair ? {
2832
+ command: repair.command,
2833
+ cataloged: repair.cataloged,
2834
+ ...('available' in repair ? { available: repair.available } : {}),
2835
+ bindings: repair.command === 'milestone recover'
2836
+ ? {
2837
+ projectId: repair.projectId,
2838
+ milestoneIds: repair.milestoneIds,
2839
+ transactionIds: repair.transactionIds,
2840
+ }
2841
+ : 'taskIds' in repair
2842
+ ? {
2843
+ projectId: repair.projectId,
2844
+ taskIds: repair.taskIds,
2845
+ transactionIds: repair.transactionIds,
2846
+ }
2847
+ : {
2848
+ projectId: repair.projectId,
2849
+ entityIds: repair.entityIds,
2850
+ },
2851
+ } : {}),
2852
+ reasonCodes: status.observation.reasonCodes,
2853
+ },
2854
+ };
2855
+ }
2856
+ if (!status.adoption) {
2857
+ return {
2858
+ scope: 'project',
2859
+ projectId: status.projectId,
2860
+ status: 'observation-blocked',
2861
+ action: 'blocked',
2862
+ observation: status.observation,
2863
+ navigation: {
2864
+ kind: 'blocked',
2865
+ reasonCodes: status.observation.reasonCodes,
2866
+ availableRepairTransitionId: null,
2867
+ },
2868
+ };
2869
+ }
2480
2870
  const adoptionInfo = {
2481
2871
  status: status.adoption.status,
2482
2872
  safe: status.adoption.safe,
@@ -2494,19 +2884,105 @@ export class WorkflowService {
2494
2884
  status: status.adoption.status,
2495
2885
  action: status.adoption.safe ? 'state adoption-apply' : 'restore safe boundary, then state adoption-prepare',
2496
2886
  adoption: adoptionInfo,
2887
+ observation: status.observation,
2888
+ navigation: status.adoption.safe
2889
+ ? {
2890
+ kind: 'human-gate',
2891
+ transitionId: null,
2892
+ cataloged: false,
2893
+ command: 'state adoption-apply',
2894
+ bindings: {
2895
+ projectId: status.projectId,
2896
+ confirmationCode: status.adoption.confirmationCode,
2897
+ confirmationCodeBindingHash: status.adoption.confirmationCodeBindingHash,
2898
+ actorRequired: true,
2899
+ },
2900
+ }
2901
+ : {
2902
+ kind: 'blocked',
2903
+ transitionId: null,
2904
+ availableRepairTransitionId: null,
2905
+ reasonCodes: ['ADOPTION_MISSING'],
2906
+ blockers: status.adoption.blockers,
2907
+ },
2497
2908
  };
2498
2909
  }
2499
- const withAdoption = (value) => ({ ...value, adoption: adoptionInfo });
2910
+ const withAdoption = (value) => ({
2911
+ ...value,
2912
+ adoption: adoptionInfo,
2913
+ observation: status.observation,
2914
+ });
2500
2915
  const repositoryRoot = resolveRepositoryIdentity(repository).repositoryRoot;
2501
2916
  const milestonesById = new Map(status.milestones.map((milestone) => [milestone.id, milestone]));
2502
- const activeTask = status.tasks.find((task) => taskIsActionableFromRepositoryNext(task, milestonesById));
2917
+ const requestedTask = taskId === null
2918
+ ? null
2919
+ : status.tasks.find((task) => task.id === taskId) ?? null;
2920
+ if (taskId !== null && !requestedTask) {
2921
+ throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
2922
+ }
2923
+ if (requestedTask && !taskIsActionableFromRepositoryNext(requestedTask, milestonesById)) {
2924
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${requestedTask.id} is not actionable from repository next.`, {
2925
+ taskId: requestedTask.id,
2926
+ taskStatus: requestedTask.status,
2927
+ milestoneId: requestedTask.milestoneId,
2928
+ });
2929
+ }
2930
+ const activeTask = requestedTask
2931
+ ?? status.tasks.find((task) => taskIsActionableFromRepositoryNext(task, milestonesById));
2503
2932
  if (activeTask) {
2504
- const activePlanRiskAudit = activeTask.planHash
2505
- ? readCurrentPlanRiskAudit(this.store, activeTask)
2933
+ const activeTaskMilestone = activeTask.milestoneId
2934
+ ? milestonesById.get(activeTask.milestoneId) ?? null
2506
2935
  : null;
2936
+ const milestoneCancellationSafe = Boolean(activeTaskMilestone
2937
+ && !activeTask.steps.some((step) => step.status === 'in_progress')
2938
+ && status.observation.leases.length === 0
2939
+ && status.observation.coreTaskOperations.length === 0
2940
+ && status.observation.pendingProjectTransactions.length === 0
2941
+ && status.observation.pendingMilestoneTransactions.length === 0
2942
+ && status.observation.pendingTaskTransactions.length === 0
2943
+ && status.observation.corruptTaskTransactions.length === 0);
2944
+ const withMilestoneCancellation = (value) => (milestoneCancellationSafe
2945
+ ? {
2946
+ ...value,
2947
+ milestoneCancellationOption: {
2948
+ command: 'milestone cancel',
2949
+ milestoneId: activeTaskMilestone.id,
2950
+ expectedRevision: activeTaskMilestone.revision,
2951
+ reasonRequired: true,
2952
+ taskDisposition: 'retained-historical',
2953
+ },
2954
+ }
2955
+ : value);
2956
+ let activePlanRiskAudit = null;
2957
+ let planRiskAuditRecovery = null;
2958
+ if (activeTask.planHash) {
2959
+ try {
2960
+ activePlanRiskAudit = readCurrentPlanRiskAudit(this.store, activeTask);
2961
+ }
2962
+ catch (error) {
2963
+ if (!(error instanceof WorkflowError) || error.code !== 'TRANSITION_BLOCKED')
2964
+ throw error;
2965
+ planRiskAuditRecovery = {
2966
+ scope: 'task',
2967
+ id: activeTask.id,
2968
+ status: activeTask.status,
2969
+ action: 'task plan-risk-audit',
2970
+ revision: activeTask.revision,
2971
+ planRiskAuditRecovery: {
2972
+ state: 'required',
2973
+ reason: error.message,
2974
+ currentBriefHash: activeTask.briefHash,
2975
+ currentPlanHash: activeTask.planHash,
2976
+ knowledgeMapRevision: activeTask.knowledgeMapRevision,
2977
+ knowledgeMapHash: activeTask.knowledgeMapHash,
2978
+ },
2979
+ };
2980
+ }
2981
+ }
2507
2982
  const taskNext = activeTask.status === 'planning'
2508
2983
  ? null
2509
- : this.nextForTaskWithDelegation(status.projectId, activeTask, activePlanRiskAudit);
2984
+ : planRiskAuditRecovery
2985
+ ?? this.nextForTaskWithDelegation(status.projectId, activeTask, activePlanRiskAudit);
2510
2986
  // Active Step work must outrank Project Knowledge refresh/rebind. Both completion and
2511
2987
  // strict review are executable while context refresh correctly remains forbidden.
2512
2988
  const hardStop = taskNext?.action === 'task corrective-decision'
@@ -2518,12 +2994,27 @@ export class WorkflowService {
2518
2994
  || hardStop
2519
2995
  || terminalCorrectiveDecision === 'split-required'
2520
2996
  || terminalCorrectiveDecision === 'stop-escalate') {
2521
- return withAdoption(taskNext);
2997
+ return withAdoption(withMilestoneCancellation(taskNext));
2522
2998
  }
2523
2999
  if (!taskCanRebindKnowledge(activeTask) && activeTask.status !== 'planning') {
2524
- return withAdoption(taskNext);
3000
+ return withAdoption(withMilestoneCancellation(taskNext));
3001
+ }
3002
+ let knowledgeMap;
3003
+ try {
3004
+ knowledgeMap = this.knowledgeMapStatus(repository);
3005
+ }
3006
+ catch (error) {
3007
+ if (!(error instanceof WorkflowError) || error.code !== 'NOT_FOUND')
3008
+ throw error;
3009
+ return withAdoption(withMilestoneCancellation({
3010
+ scope: 'project-memory',
3011
+ status: 'missing',
3012
+ action: 'project-memory scan',
3013
+ stalenessOriginTaskId: activeTask.id,
3014
+ authorizesTask: false,
3015
+ provenanceNotice: 'This Task identifier is diagnostic provenance only; the Project Knowledge action does not authorize or resume it.',
3016
+ }));
2525
3017
  }
2526
- const knowledgeMap = this.knowledgeMapStatus(repository);
2527
3018
  if (knowledgeMap.status !== 'active' || !knowledgeMap.approval) {
2528
3019
  const action = knowledgeMap.status === 'stale' ? 'project-memory reconcile' : 'project-memory approve';
2529
3020
  const delegatedApprovalOptions = action === 'project-memory approve'
@@ -2539,7 +3030,17 @@ export class WorkflowService {
2539
3030
  const contextRefreshOptions = action === 'project-memory reconcile' && refreshAssessment?.eligible
2540
3031
  ? this.delegatedContextRefreshOptions(status.projectId, activeTask, knowledgeMap, refreshAssessment.mode)
2541
3032
  : [];
2542
- return withAdoption({
3033
+ if (requestedTask && action === 'project-memory reconcile' && contextRefreshOptions.length === 0) {
3034
+ const refreshOwner = this.findDelegatedContextRefreshOwner(status.projectId, status.tasks, milestonesById, storedKnowledgeMap, knowledgeMap);
3035
+ if (refreshOwner && refreshOwner.task.id !== requestedTask.id) {
3036
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${requestedTask.id} cannot be selected while Task ${refreshOwner.task.id} requires an atomic context refresh.`, {
3037
+ taskId: requestedTask.id,
3038
+ blockingTaskId: refreshOwner.task.id,
3039
+ requiredAction: 'Run repository next and its exact task context-refresh action first.',
3040
+ });
3041
+ }
3042
+ }
3043
+ return withAdoption(withMilestoneCancellation({
2543
3044
  scope: contextRefreshOptions.length > 0 ? 'task' : 'project-memory',
2544
3045
  status: knowledgeMap.status,
2545
3046
  action: contextRefreshOptions.length > 0 ? 'task context-refresh' : action,
@@ -2571,14 +3072,14 @@ export class WorkflowService {
2571
3072
  },
2572
3073
  }
2573
3074
  : {}),
2574
- });
3075
+ }));
2575
3076
  }
2576
3077
  const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
2577
3078
  if (taskCanRebindKnowledge(activeTask) &&
2578
3079
  activeTask.planHash &&
2579
3080
  (activeTask.knowledgeMapRevision !== knowledgeMap.revision ||
2580
3081
  activeTask.knowledgeMapHash !== knowledgeMapHash)) {
2581
- return withAdoption({
3082
+ return withAdoption(withMilestoneCancellation({
2582
3083
  scope: 'task',
2583
3084
  id: activeTask.id,
2584
3085
  status: activeTask.status,
@@ -2586,9 +3087,9 @@ export class WorkflowService {
2586
3087
  revision: activeTask.revision,
2587
3088
  knowledgeMapRevision: knowledgeMap.revision,
2588
3089
  knowledgeMapHash,
2589
- });
3090
+ }));
2590
3091
  }
2591
- return withAdoption(taskNext ?? this.nextForTaskWithDelegation(status.projectId, activeTask, null));
3092
+ return withAdoption(withMilestoneCancellation(taskNext ?? this.nextForTaskWithDelegation(status.projectId, activeTask, null)));
2592
3093
  }
2593
3094
  const activeMilestone = status.milestones.find((milestone) => !['accepted', 'cancelled'].includes(milestone.status));
2594
3095
  if (activeMilestone) {
@@ -2667,6 +3168,19 @@ export class WorkflowService {
2667
3168
  status: activeMilestone.status,
2668
3169
  action: nextForMilestone(activeMilestone),
2669
3170
  revision: activeMilestone.revision,
3171
+ ...(activeMilestone.status === 'awaiting_execution_authorization' && activeMilestone.planHash
3172
+ ? {
3173
+ milestoneAuthorizationOptions: [
3174
+ { action: 'milestone authorize', mode: 'direct-human' },
3175
+ {
3176
+ action: 'milestone autonomy-prepare',
3177
+ mode: 'delegated',
3178
+ requiresExactHumanConfirmation: true,
3179
+ maximumDurationHours: 72,
3180
+ },
3181
+ ],
3182
+ }
3183
+ : {}),
2670
3184
  ...(activeMilestone.status === 'active'
2671
3185
  ? {
2672
3186
  completionPolicy: {
@@ -2823,6 +3337,19 @@ export class WorkflowService {
2823
3337
  throw error;
2824
3338
  }
2825
3339
  }
3340
+ let catalogCorrectiveReplanPosture = guardedCorrectivePosture?.posture === 'replan-required'
3341
+ ? guardedCorrectivePosture
3342
+ : null;
3343
+ if (!remediationModeRecovery && planRiskAudit && guardedCorrectivePosture === null) {
3344
+ try {
3345
+ catalogCorrectiveReplanPosture = identifyCorrectiveReplanPosture(task, planRiskAudit.reviewRequiredStepIds, readCorrectiveDecisionEvents(this.store, task), readRemediationEvents(this.store, task));
3346
+ }
3347
+ catch (error) {
3348
+ remediationModeRecovery = findRemediationModeRecoveryCandidate(this.store, task);
3349
+ if (!remediationModeRecovery)
3350
+ throw error;
3351
+ }
3352
+ }
2826
3353
  const next = this.taskGitNavigationOverride(task) ?? nextForTask(task);
2827
3354
  const handoffPosture = readTaskC1Posture(this.store, task);
2828
3355
  const strictStepReviewStep = planRiskAudit
@@ -2835,6 +3362,11 @@ export class WorkflowService {
2835
3362
  return buildCurrentStrictStepReviewCycle(this.store, task, step.id).resolution === 'unverified';
2836
3363
  }) ?? null
2837
3364
  : null;
3365
+ const strictReviewInfrastructureFailures = strictStepReviewStep
3366
+ ? countUnverifiedStrictStepReviews(this.store, task, strictStepReviewStep.id)
3367
+ : 0;
3368
+ const strictTaskReviewRequired = (task.status === 'validating' && task.review.status === 'pending')
3369
+ || (task.status === 'blocked' && task.review.status === 'unverified');
2838
3370
  const handoffOverlay = handoffPosture.state === 'pending'
2839
3371
  ? {
2840
3372
  action: 'task claim',
@@ -2843,6 +3375,15 @@ export class WorkflowService {
2843
3375
  state: 'pending',
2844
3376
  targetActor: handoffPosture.targetActor,
2845
3377
  eventId: handoffPosture.latestEvent.eventId,
3378
+ credentialRecovery: {
3379
+ action: 'task handoff-replace',
3380
+ when: 'claim credential is unavailable',
3381
+ requiredActor: handoffPosture.latestEvent.actor,
3382
+ targetActor: handoffPosture.targetActor,
3383
+ grantId: handoffPosture.latestEvent.grantId,
3384
+ expectedRevision: task.revision,
3385
+ invalidatesPriorCredential: true,
3386
+ },
2846
3387
  },
2847
3388
  }
2848
3389
  : handoffPosture.state === 'claimed'
@@ -2865,6 +3406,63 @@ export class WorkflowService {
2865
3406
  },
2866
3407
  }
2867
3408
  : {};
3409
+ const correctiveReplanOverlay = (posture) => {
3410
+ const evaluation = evaluateTaskCorrectiveReplanAvailability(task, posture);
3411
+ const writerLease = inspectRawWriterLease(this.store.lockRoot(task.projectId), task.id, this.store.root);
3412
+ const command = handoffPosture.state === 'claimed'
3413
+ ? 'task corrective-yield'
3414
+ : writerLease
3415
+ ? 'doctor'
3416
+ : 'task corrective-replan-prepare';
3417
+ if (evaluation.allowed && handoffPosture.state === 'none' && writerLease) {
3418
+ const navigation = correctiveReplanNavigation(evaluation);
3419
+ return {
3420
+ action: command,
3421
+ blockedAction: 'task corrective-replan-prepare',
3422
+ stepId: posture.stepId,
3423
+ navigation: {
3424
+ kind: 'blocked',
3425
+ transitionId: navigation.transitionId,
3426
+ semanticFingerprint: navigation.semanticFingerprint,
3427
+ command: navigation.command,
3428
+ blockingGuardIds: ['task.corrective-replan.no-writer-lease'],
3429
+ pendingInputGuardIds: navigation.pendingInputGuardIds,
3430
+ reasonCode: 'ACTIVE_WRITER_LEASE',
3431
+ },
3432
+ correctiveDecisionGate: {
3433
+ attemptCount: posture.attemptCount,
3434
+ decision: posture.correctiveDecision?.decision ?? null,
3435
+ decisionEventId: posture.correctiveDecision?.eventId ?? null,
3436
+ requiredAction: 'The active non-C1 writer lease must expire and be explicitly repaired before corrective replan preparation.',
3437
+ writerLease: {
3438
+ owner: writerLease.owner,
3439
+ expiresAt: writerLease.expiresAt,
3440
+ },
3441
+ },
3442
+ };
3443
+ }
3444
+ return evaluation.allowed
3445
+ ? {
3446
+ action: command,
3447
+ blockedAction: 'task plan-set',
3448
+ stepId: posture.stepId,
3449
+ navigation: { ...correctiveReplanNavigation(evaluation), command },
3450
+ correctiveDecisionGate: {
3451
+ attemptCount: posture.attemptCount,
3452
+ decision: posture.correctiveDecision?.decision ?? null,
3453
+ decisionEventId: posture.correctiveDecision?.eventId ?? null,
3454
+ requiredAction: handoffPosture.state === 'claimed'
3455
+ ? 'Yield the exact C1 claim and writer lease before preparing replacement authority.'
3456
+ : 'Prepare a state-bound candidate, confirm the Human gate, then execute its exact receipt.',
3457
+ },
3458
+ }
3459
+ : {
3460
+ action: 'doctor',
3461
+ blockedAction: 'task plan-set',
3462
+ stepId: posture.stepId,
3463
+ navigation: correctiveReplanBlockedNavigation(evaluation),
3464
+ };
3465
+ };
2868
3466
  let guardedCorrectiveOverlay = {};
2869
3467
  if (remediationModeRecovery) {
2870
3468
  guardedCorrectiveOverlay = {
@@ -2883,7 +3481,12 @@ export class WorkflowService {
2883
3481
  },
2884
3482
  };
2885
3483
  }
2886
- if (!remediationModeRecovery && !strictStepReviewStep && guardedCorrectivePosture) {
3484
+ if (!remediationModeRecovery && guardedCorrectivePosture === null && catalogCorrectiveReplanPosture) {
3485
+ guardedCorrectiveOverlay = correctiveReplanOverlay(catalogCorrectiveReplanPosture);
3486
+ }
3487
+ if (!remediationModeRecovery
3488
+ && guardedCorrectivePosture
3489
+ && (!strictStepReviewStep || guardedCorrectivePosture.posture === 'replan-required')) {
2887
3490
  switch (guardedCorrectivePosture.posture) {
2888
3491
  case 'decision-required':
2889
3492
  if (guardedCorrectivePosture.correctiveDecision
@@ -2958,16 +3561,7 @@ export class WorkflowService {
2958
3561
  };
2959
3562
  break;
2960
3563
  case 'replan-required':
2961
- guardedCorrectiveOverlay = {
2962
- action: 'task plan-set',
2963
- stepId: guardedCorrectivePosture.stepId,
2964
- correctiveDecisionGate: {
2965
- attemptCount: guardedCorrectivePosture.attemptCount,
2966
- decision: guardedCorrectivePosture.correctiveDecision?.decision ?? null,
2967
- decisionEventId: guardedCorrectivePosture.correctiveDecision?.eventId ?? null,
2968
- requiredAction: 'Record an actual new Plan posture before another remediation run.',
2969
- },
2970
- };
3564
+ guardedCorrectiveOverlay = correctiveReplanOverlay(guardedCorrectivePosture);
2971
3565
  break;
2972
3566
  case 'split-required':
2973
3567
  {
@@ -3051,6 +3645,30 @@ export class WorkflowService {
3051
3645
  strictStepReview: {
3052
3646
  reviewRequired: true,
3053
3647
  completionCommit: strictStepReviewStep.evidence.commitSha,
3648
+ infrastructureFailures: strictReviewInfrastructureFailures,
3649
+ maxLocalInfrastructureFailures: MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES,
3650
+ recommendedMode: 'external-sealed',
3651
+ externalSealedReview: {
3652
+ packetCommand: 'task step-review-packet',
3653
+ recordCommand: 'task step-review-record',
3654
+ reviewerMustUseSeparateChat: true,
3655
+ repositorySealRequired: true,
3656
+ },
3657
+ },
3658
+ }
3659
+ : {}),
3660
+ ...(strictTaskReviewRequired
3661
+ ? {
3662
+ strictTaskReview: {
3663
+ reviewRequired: true,
3664
+ localInfrastructureFailureObserved: task.review.status === 'unverified',
3665
+ recommendedMode: 'external-sealed',
3666
+ externalSealedReview: {
3667
+ packetCommand: 'task review-packet',
3668
+ recordCommand: 'task review-sealed-record',
3669
+ reviewerMustUseSeparateChat: true,
3670
+ repositorySealRequired: true,
3671
+ },
3054
3672
  },
3055
3673
  }
3056
3674
  : {}),
@@ -3104,6 +3722,12 @@ export class WorkflowService {
3104
3722
  exactInclusionRequired: true,
3105
3723
  briefHash: task.briefHash,
3106
3724
  knowledgeMap: knowledgeSummary(map),
3725
+ planRiskAuditContract: {
3726
+ required: true,
3727
+ option: '--risk-audit-file',
3728
+ atomicWithPlanSet: true,
3729
+ plannerAuditorSeparationRequired: true,
3730
+ },
3107
3731
  };
3108
3732
  }
3109
3733
  assertTaskContextChangeAllowed(task) {
@@ -3183,6 +3807,22 @@ export class WorkflowService {
3183
3807
  policyHash: grant.policyHash,
3184
3808
  }));
3185
3809
  }
3810
+ findDelegatedContextRefreshOwner(projectId, tasks, milestonesById, approved, inspected) {
3811
+ for (const task of tasks) {
3812
+ if (!taskIsActionableFromRepositoryNext(task, milestonesById)
3813
+ || !task.planHash
3814
+ || !taskCanRebindKnowledge(task)
3815
+ || this.taskHasTerminalCorrectiveDecision(task))
3816
+ continue;
3817
+ const assessment = assessDelegatedKnowledgeRefresh(approved, inspected, task, this.store.hashArtifact(this.store.taskRoot(projectId, task.id), 'plan.md'));
3818
+ if (!assessment.eligible)
3819
+ continue;
3820
+ const options = this.delegatedContextRefreshOptions(projectId, task, inspected, assessment.mode);
3821
+ if (options.length > 0)
3822
+ return { task, assessment, options };
3823
+ }
3824
+ return null;
3825
+ }
3186
3826
  assertPlanBoundedSupportingSourceGrant(projectId, grantId, actor, task) {
3187
3827
  const grant = this.store.readDelegation(projectId, grantId);
3188
3828
  if (!task.milestoneId || grant.scope.kind !== 'milestone' || grant.scope.id !== task.milestoneId) {
@@ -3252,6 +3892,14 @@ export class WorkflowService {
3252
3892
  ? milestone
3253
3893
  : readMilestoneWithIntegrity(this.store, projectId, milestone.id, (taskId) => this.store.readTask(projectId, taskId), () => this.store.listTasks(projectId))));
3254
3894
  }
3895
+ listMilestonesObserved(projectId, milestones, posture) {
3896
+ if (!posture)
3897
+ return milestones;
3898
+ const grandfatheredMilestones = new Set(posture.terminalMilestones.map((milestone) => `${milestone.milestoneId}:${milestone.revision}:${milestone.status}`));
3899
+ return milestones.map((milestone) => (grandfatheredMilestones.has(`${milestone.id}:${milestone.revision}:${milestone.status}`)
3900
+ ? milestone
3901
+ : inspectMilestoneWithIntegrity(this.store, projectId, milestone.id, (taskId) => this.store.readTask(projectId, taskId), () => this.store.listTasks(projectId))));
3902
+ }
3255
3903
  taskGitNavigationOverride(task) {
3256
3904
  if (task.status !== 'merging' || task.workspaceOwner !== 'local' || !task.baseCommit || !task.taskBranch) {
3257
3905
  return null;
@@ -3277,14 +3925,44 @@ export class WorkflowService {
3277
3925
  if (!action)
3278
3926
  return next;
3279
3927
  const policy = gitMutationPolicyForAction(task, action);
3280
- const claimedC1Posture = readTaskC1Posture(this.store, task).state === 'claimed';
3928
+ const c1Posture = readTaskC1Posture(this.store, task);
3929
+ const claimedC1Posture = c1Posture.state === 'claimed';
3281
3930
  const writerTokenContract = writerTokenContractForAction(action, claimedC1Posture);
3931
+ const activeLease = writerTokenContract
3932
+ ? inspectRawWriterLease(this.store.lockRoot(task.projectId), task.id, this.store.root)
3933
+ : null;
3934
+ const writerCredentialRecoveryActor = this.writerCredentialRecoveryActor(task, action, claimedC1Posture ? c1Posture.claimant : null, activeLease?.owner ?? null);
3935
+ const writerCredentialRecovery = writerTokenContract && writerCredentialRecoveryActor
3936
+ ? {
3937
+ action: 'task writer-credential-replace',
3938
+ when: 'the active writer credential is unavailable',
3939
+ requiredActor: writerCredentialRecoveryActor,
3940
+ expectedRevision: task.revision,
3941
+ invalidatesPriorCredential: true,
3942
+ acquiresLeaseWhenAbsent: true,
3943
+ }
3944
+ : null;
3282
3945
  return {
3283
3946
  ...next,
3284
3947
  ...(policy ? { gitMutationPolicy: policy } : {}),
3285
3948
  ...(writerTokenContract ? { writerTokenContract } : {}),
3949
+ ...(writerCredentialRecovery ? { writerCredentialRecovery } : {}),
3286
3950
  };
3287
3951
  }
3952
+ writerCredentialRecoveryActor(task, action, claimedActor, activeLeaseOwner) {
3953
+ if (claimedActor)
3954
+ return claimedActor;
3955
+ if (activeLeaseOwner)
3956
+ return activeLeaseOwner;
3957
+ const authorizationKind = ['task merge', 'task merge-confirm'].includes(action)
3958
+ ? 'final_acceptance'
3959
+ : ['task step-complete', 'task submit'].includes(action)
3960
+ ? 'execution'
3961
+ : null;
3962
+ if (!authorizationKind)
3963
+ return null;
3964
+ return [...task.authorizations].reverse().find((authorization) => authorization.kind === authorizationKind && authorization.decision === 'approved')?.actor ?? null;
3965
+ }
3288
3966
  describeMilestoneInitialAssembly(projectId, milestone, tasks) {
3289
3967
  const assessment = assessMilestoneInitialAssembly({
3290
3968
  store: this.store,
@@ -3325,6 +4003,12 @@ export class WorkflowService {
3325
4003
  actor,
3326
4004
  });
3327
4005
  }
4006
+ publicWriterLeaseDiagnostic(lease) {
4007
+ return {
4008
+ ...toPublicWriterLease(lease, this.now()),
4009
+ tokenFingerprint: createHash('sha256').update(lease.token).digest('hex').slice(0, 16),
4010
+ };
4011
+ }
3328
4012
  proveWriterLeaseTokenForHandoff(context, taskId, writerToken) {
3329
4013
  const currentLease = context.locks.inspect(taskId);
3330
4014
  if (!currentLease) {
@@ -3414,9 +4098,12 @@ export class WorkflowService {
3414
4098
  storedKnowledgeMapHash: storedHash,
3415
4099
  });
3416
4100
  }
3417
- inspectAdoptionPreparation(identity, tasks, milestones) {
3418
- const locks = new WriterLockManager(this.store.lockRoot(identity.projectId), this.now);
3419
- const currentPosture = readCurrentAdoptionPosture(this.store, identity.projectId);
4101
+ inspectAdoptionPreparation(identity, tasks, milestones, observedLeases, observedPosture) {
4102
+ const leases = observedLeases
4103
+ ?? new WriterLockManager(this.store.lockRoot(identity.projectId), this.now, this.store.root).list();
4104
+ const currentPosture = observedPosture === undefined
4105
+ ? readCurrentAdoptionPosture(this.store, identity.projectId)
4106
+ : observedPosture;
3420
4107
  if (currentPosture) {
3421
4108
  assertAdoptionBaselinePreserved(currentPosture, tasks, milestones);
3422
4109
  }
@@ -3429,7 +4116,7 @@ export class WorkflowService {
3429
4116
  tasks,
3430
4117
  milestones,
3431
4118
  sidecarInventory: this.collectAdoptionSidecarInventory(identity.projectId, tasks, milestones),
3432
- blockers: this.computeAdoptionBoundaryBlockers(identity, tasks, locks.list()),
4119
+ blockers: this.computeAdoptionBoundaryBlockers(identity, tasks, leases),
3433
4120
  now: this.now(),
3434
4121
  }, currentPosture);
3435
4122
  }
@@ -4046,11 +4733,14 @@ function writerTokenContractForAction(action, claimedC1Posture = false) {
4046
4733
  const claimedC1LeaseBound = [
4047
4734
  'task run',
4048
4735
  'task step-review',
4736
+ 'task step-review-record',
4049
4737
  'task review-record',
4050
4738
  'task review-launch',
4739
+ 'task review-sealed-record',
4051
4740
  'task corrective-decision-recover',
4052
4741
  'task remediation-mode-recover',
4053
4742
  'task corrective-decision',
4743
+ 'task corrective-yield',
4054
4744
  'task handoff',
4055
4745
  'task handoff-prepare',
4056
4746
  'task handback',
@@ -4064,7 +4754,26 @@ function writerTokenContractForAction(action, claimedC1Posture = false) {
4064
4754
  option: '--writer-token',
4065
4755
  source: 'active-writer-lease',
4066
4756
  sensitive: true,
4757
+ renewalDisposition: 'reuse-presented-token',
4758
+ tokenReissuedOnRenewal: false,
4759
+ };
4760
+ }
4761
+ function observationPreflightBlockers(observation) {
4762
+ const messages = {
4763
+ PROJECT_REGISTRATION_MISSING: 'Workflow project registration is missing.',
4764
+ PROJECT_REPOSITORY_ROOT_UNBOUND: 'The current repository root is not bound to the Workflow project.',
4765
+ PROJECT_RECOVERY_REQUIRED: 'A project transaction requires explicit recovery.',
4766
+ MILESTONE_RECOVERY_REQUIRED: 'A Milestone transaction requires explicit recovery.',
4767
+ TASK_RECOVERY_REQUIRED: 'A Task corrective-replan transaction requires explicit recovery.',
4768
+ TASK_TRANSACTION_INTEGRITY_FAILED: 'A Task transaction journal failed integrity validation.',
4769
+ TASK_OPERATION_IN_PROGRESS: 'A Core Task operation is in progress.',
4770
+ STALE_TASK_OPERATION: 'A stale Core Task operation requires bounded repair.',
4771
+ ADOPTION_MISSING: 'Workflow lifecycle adoption is missing.',
4067
4772
  };
4773
+ return observation.reasonCodes.flatMap((reasonCode) => {
4774
+ const message = messages[reasonCode];
4775
+ return message ? [message] : [];
4776
+ });
4068
4777
  }
4069
4778
  function knowledgeSummary(map) {
4070
4779
  return {