codex-workflow-v2 2.0.0-alpha.6 → 2.0.0-alpha.7

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 (42) hide show
  1. package/README.md +43 -15
  2. package/dist/src/alpha6/handoff.d.ts +1 -0
  3. package/dist/src/alpha6/handoff.js +6 -0
  4. package/dist/src/alpha6/handoff.js.map +1 -1
  5. package/dist/src/alpha6/milestone.d.ts +3 -0
  6. package/dist/src/alpha6/milestone.js +57 -3
  7. package/dist/src/alpha6/milestone.js.map +1 -1
  8. package/dist/src/alpha6/plan-risk.js +61 -0
  9. package/dist/src/alpha6/plan-risk.js.map +1 -1
  10. package/dist/src/alpha6/remediation.d.ts +1 -0
  11. package/dist/src/alpha6/remediation.js +6 -0
  12. package/dist/src/alpha6/remediation.js.map +1 -1
  13. package/dist/src/alpha7/autonomy.d.ts +61 -0
  14. package/dist/src/alpha7/autonomy.js +256 -0
  15. package/dist/src/alpha7/autonomy.js.map +1 -0
  16. package/dist/src/cli.js +24 -3
  17. package/dist/src/cli.js.map +1 -1
  18. package/dist/src/contracts.d.ts +9 -0
  19. package/dist/src/index.d.ts +3 -0
  20. package/dist/src/index.js +3 -0
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/src/version.d.ts +1 -1
  23. package/dist/src/version.js +1 -1
  24. package/dist/src/workflow.d.ts +35 -0
  25. package/dist/src/workflow.js +342 -23
  26. package/dist/src/workflow.js.map +1 -1
  27. package/docs/autonomy-guardrails.md +32 -11
  28. package/docs/decisions.md +16 -0
  29. package/docs/delegated-approval.md +12 -5
  30. package/docs/development-flow.md +12 -7
  31. package/docs/release.md +4 -2
  32. package/docs/updating-existing-project.md +10 -0
  33. package/docs/validation-report.md +51 -46
  34. package/package.json +1 -1
  35. package/plugins/codex-workflow-gateway/references/protocol.md +42 -10
  36. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +19 -0
  37. package/references/state-machine.md +17 -10
  38. package/roles/delivery-coordinator.md +10 -1
  39. package/roles/technical-planner.md +3 -0
  40. package/schemas/milestone-autonomy-event.schema.json +45 -0
  41. package/schemas/milestone-scope-change-event.schema.json +3 -1
  42. package/schemas/plan-risk-audit-event.schema.json +21 -4
@@ -3,17 +3,19 @@ import { createHash } from 'node:crypto';
3
3
  import path from 'node:path';
4
4
  import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION, } from './contracts.js';
5
5
  import { renderBrief, renderPlan, renderResult } from './artifacts.js';
6
- import { assertDelegationCanAuthorize, delegationCanAuthorize, hashDelegationPolicy, prepareDelegationGrantGate, } from './delegation.js';
6
+ import { assertDelegationCanAuthorize, hashDelegationPolicy, prepareDelegationGrantGate, } from './delegation.js';
7
7
  import { assessDiscovery, mergeUnique } from './domain/discovery.js';
8
8
  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
- import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, assertTaskMutationAllowedByC1, readTaskC1Posture, } from './alpha6/handoff.js';
11
+ import { inspectLocalDependency } from './diagnostics.js';
12
+ import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, readTaskC1Posture, } from './alpha6/handoff.js';
12
13
  import { applyAdoptionPosture, assertAdoptionBaselinePreserved, buildAdoptionPreparation, initializeProjectRegistrationAdoption, readCurrentAdoptionPosture, } from './alpha6/adoption.js';
13
14
  import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, readCurrentPlanRiskAudit, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
14
- import { appendCorrectiveDecisionEvent, appendRemediationEvent, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, findFailedGuardedStepRequiringCorrectiveDecision, readCorrectiveDecisionEvents, readRemediationEvents, } from './alpha6/remediation.js';
15
+ import { appendCorrectiveDecisionEvent, appendRemediationEvent, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, defaultCorrectiveAuditorActor, findFailedGuardedStepRequiringCorrectiveDecision, readCorrectiveDecisionEvents, readRemediationEvents, } from './alpha6/remediation.js';
15
16
  import { applyMilestoneScopeChangeTransaction, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, readMilestoneForScopeChange, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
16
17
  import { buildCurrentStrictStepReviewCycle, ensurePendingStrictStepReview, isStepReviewRequired, recordStrictStepReview, } from './alpha6/review.js';
18
+ import { applyMilestoneAutonomyContract, assertAutonomousPlanEvolution, prepareMilestoneAutonomyContract, readMilestoneAutonomyEvents, requireActiveMilestoneAutonomy, } from './alpha7/autonomy.js';
17
19
  import { applyKnowledgeSelections, hashKnowledgeMap, inspectKnowledgeMap, normalizeRelativePath, reconcileKnowledgeMap, scanProjectKnowledge, selectKnowledgeSources, } from './memory.js';
18
20
  import { canonicalJsonStringify, sha256Hex } from './alpha6/store-sidecars.js';
19
21
  import { commitStep, mergeTaskBranch, runChecks, startLocalTaskBranch, syncBaseIntoTask, validateTaskHistory, } from './git.js';
@@ -182,7 +184,12 @@ export class WorkflowService {
182
184
  if (!title.trim())
183
185
  throw new WorkflowError('INVALID_ARGUMENT', 'Task title is required.');
184
186
  if (milestoneId) {
185
- const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
187
+ // A Milestone is assembled from independently materialized Task discoveries. During
188
+ // that bounded assembly window the reverse-membership set is intentionally incomplete;
189
+ // execution remains unavailable until one complete initial Milestone Plan classifies
190
+ // every linked Task. The scope-change reader preserves every other integrity check while
191
+ // allowing exactly that repairable reverse gap.
192
+ const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
186
193
  if (milestone.status === 'accepted' || milestone.status === 'cancelled') {
187
194
  throw new WorkflowError('TRANSITION_BLOCKED', `Milestone ${milestoneId} is ${milestone.status}.`);
188
195
  }
@@ -401,6 +408,64 @@ export class WorkflowService {
401
408
  readTask: (taskId) => this.store.readTask(identity.projectId, taskId),
402
409
  });
403
410
  }
411
+ prepareMilestoneAutonomy(repository, milestoneId, expectedRevision, principal, delegate, expiresAt) {
412
+ const { identity } = this.context(repository);
413
+ const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
414
+ assertExpectedRevision(milestone, expectedRevision);
415
+ return prepareMilestoneAutonomyContract({
416
+ projectId: identity.projectId,
417
+ milestone,
418
+ principal,
419
+ delegate,
420
+ expiresAt,
421
+ now: this.now(),
422
+ });
423
+ }
424
+ grantMilestoneAutonomy(repository, milestoneId, expectedRevision, principal, delegate, expiresAt, confirmationCode) {
425
+ const { identity } = this.context(repository);
426
+ const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
427
+ assertExpectedRevision(milestone, expectedRevision);
428
+ return applyMilestoneAutonomyContract({
429
+ store: this.store,
430
+ projectId: identity.projectId,
431
+ milestone,
432
+ principal,
433
+ delegate,
434
+ expiresAt,
435
+ confirmationCode,
436
+ now: this.now(),
437
+ });
438
+ }
439
+ evolveMilestonePlanAutonomously(repository, milestoneId, expectedRevision, plan, actor) {
440
+ const { identity } = this.context(repository);
441
+ const milestone = this.readMilestoneScopeChangeCurrent(identity.projectId, milestoneId);
442
+ assertExpectedRevision(milestone, expectedRevision);
443
+ assertMilestoneScopeChangeStatusAllowed(milestone);
444
+ const autonomy = requireActiveMilestoneAutonomy({
445
+ store: this.store,
446
+ projectId: identity.projectId,
447
+ milestone,
448
+ actor,
449
+ now: this.now(),
450
+ });
451
+ validateMilestonePlan(plan);
452
+ assertAutonomousPlanEvolution(milestone, plan, autonomy.contract);
453
+ const prepared = prepareMilestoneScopeChangeCandidate({
454
+ milestone,
455
+ plan,
456
+ readTask: (taskId) => this.store.readTask(identity.projectId, taskId),
457
+ actor,
458
+ autonomyContractEventHash: autonomy.contract.eventHash,
459
+ });
460
+ return applyMilestoneScopeChangeTransaction({
461
+ store: this.store,
462
+ milestone,
463
+ expectedRevision,
464
+ prepared,
465
+ now: this.now(),
466
+ readTask: (taskId) => this.store.readTask(identity.projectId, taskId),
467
+ });
468
+ }
404
469
  authorizeMilestone(repository, milestoneId, expectedRevision, actor, reason = '', delegationGrantId = null) {
405
470
  const { identity } = this.context(repository);
406
471
  const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
@@ -823,10 +888,10 @@ export class WorkflowService {
823
888
  throw new WorkflowError('TRANSITION_BLOCKED', 'Automatic Task context refresh requires a previously approved map with content-only drift.', { currentStatus: currentMap.status, inspectedStatus: inspectedMap.status });
824
889
  }
825
890
  assertContentOnlyKnowledgeRefresh(currentMap, inspectedMap);
826
- this.delegatedAuthorization(identity.projectId, delegationGrantId, 'project_memory.approve', actor, inspectedMap);
891
+ this.delegatedAuthorization(identity.projectId, delegationGrantId, 'project_memory.approve', actor, inspectedMap, task.id);
827
892
  knowledgeMap = this.reconcileKnowledgeMap(repository, currentMap.revision);
828
893
  transitions.push('project-memory reconcile');
829
- knowledgeMap = this.approveKnowledgeMap(repository, knowledgeMap.revision, [], actor, delegationGrantId);
894
+ knowledgeMap = this.approveKnowledgeMapForTaskRefresh(repository, knowledgeMap.revision, [], actor, delegationGrantId, task.id);
830
895
  transitions.push('project-memory approve');
831
896
  }
832
897
  const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
@@ -1230,9 +1295,13 @@ export class WorkflowService {
1230
1295
  taskContext(repository, taskId, role) {
1231
1296
  const { identity } = this.context(repository);
1232
1297
  const task = this.store.readTask(identity.projectId, taskId);
1298
+ const historicalStrictReview = role === 'independent-reviewer'
1299
+ && task.steps.some((step) => step.status === 'in_progress' && step.evidence !== null);
1233
1300
  const knowledgeMap = task.knowledgeMapRevision === null
1234
1301
  ? this.requireActiveKnowledgeMap(identity.repositoryRoot, identity.projectId)
1235
- : this.assertTaskKnowledgeBinding(identity.repositoryRoot, task);
1302
+ : historicalStrictReview
1303
+ ? this.strictReviewKnowledgeMap(identity.repositoryRoot, task)
1304
+ : this.assertTaskKnowledgeBinding(identity.repositoryRoot, task);
1236
1305
  const root = this.store.taskRoot(task.projectId, task.id);
1237
1306
  const planRiskAudit = task.planHash && !['planning', 'merged', 'cancelled'].includes(task.status)
1238
1307
  ? readCurrentPlanRiskAudit(this.store, task)
@@ -1263,7 +1332,7 @@ export class WorkflowService {
1263
1332
  if (role === 'technical-planner') {
1264
1333
  return {
1265
1334
  ...common,
1266
- objective: `Produce an execution-ready Plan for ${task.title}. Perform an explicit risk review before plan-set: migrations, concurrency, restart/replay, crash windows, and external-provider behavior require failure-specific checks in the original Plan.${planRiskReviewScope}`,
1335
+ objective: `Produce an execution-ready Plan for ${task.title}. Perform an explicit risk review before plan-set: migrations, concurrency, restart/replay, crash windows, and external-provider behavior require failure-specific checks and executable proof obligations in the original Plan.${planRiskReviewScope}`,
1267
1336
  resultSchema: {
1268
1337
  type: 'object',
1269
1338
  required: ['objective', 'requirements', 'acceptance', 'risks', 'steps'],
@@ -1274,6 +1343,7 @@ export class WorkflowService {
1274
1343
  'Concurrency or cursor/state ownership is planned without deterministic race checks.',
1275
1344
  'Restart, replay, or crash recovery is claimed without restart/crash-window evidence.',
1276
1345
  'An external provider is used without failure-injection and hidden-default verification.',
1346
+ 'A guarded failure mode lacks a hostile counterexample, expected safe result, or exact executable evidence command.',
1277
1347
  'The safe risk slice crosses persistence, recovery orchestration, and executable composition and should be split.',
1278
1348
  ],
1279
1349
  };
@@ -1398,7 +1468,6 @@ export class WorkflowService {
1398
1468
  throw new WorkflowError('TRANSITION_BLOCKED', `Strict Step Review cannot run while task ${taskId} is ${task.status}.`);
1399
1469
  }
1400
1470
  assertTaskBranch(context.identity.repositoryRoot, task);
1401
- this.assertTaskKnowledgeBinding(context.identity.repositoryRoot, task);
1402
1471
  const planRiskAudit = readCurrentPlanRiskAudit(this.store, task);
1403
1472
  if (!planRiskAudit || !isStepReviewRequired(task, stepId, planRiskAudit.reviewRequiredStepIds)) {
1404
1473
  throw new WorkflowError('TRANSITION_BLOCKED', `Step ${stepId} is not marked for strict review by the current Plan Risk Audit.`, {
@@ -1414,6 +1483,13 @@ export class WorkflowService {
1414
1483
  stepStatus: step.status,
1415
1484
  });
1416
1485
  }
1486
+ // A strict review is bound to the immutable completion commit, Task Plan, and the
1487
+ // Knowledge Map revision that authorized the Step. Project knowledge commonly becomes
1488
+ // stale because the completion commit itself changes a classified source. Requiring the
1489
+ // *current* map here creates a deadlock: review cannot run before rebind, while rebind is
1490
+ // intentionally forbidden until the in-progress Step becomes terminal. The pending
1491
+ // strict-review binding below validates the historical Task context; a current-map rebind
1492
+ // remains mandatory after the review finalizes the Step and before another Step can run.
1417
1493
  validateTaskHistory(context.identity.repositoryRoot, task);
1418
1494
  if (!isClean(context.identity.repositoryRoot)) {
1419
1495
  throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Strict Step Review requires a clean Task checkout.', {
@@ -1845,6 +1921,109 @@ export class WorkflowService {
1845
1921
  adoption,
1846
1922
  };
1847
1923
  }
1924
+ alpha6StrictReviewRescuePreflight(repository) {
1925
+ const context = this.context(repository);
1926
+ const dependency = inspectLocalDependency(context.identity.repositoryRoot);
1927
+ const tasks = context.store.listTasks(context.identity.projectId);
1928
+ const leases = context.locks.list();
1929
+ const now = this.now().getTime();
1930
+ const activeLeases = leases.filter((lease) => Date.parse(lease.expiresAt) > now);
1931
+ const staleLeases = leases.filter((lease) => Date.parse(lease.expiresAt) <= now);
1932
+ const dirty = changedFiles(context.identity.repositoryRoot);
1933
+ const currentHead = headCommit(context.identity.repositoryRoot);
1934
+ const pendingCandidates = [];
1935
+ const malformedCandidates = [];
1936
+ for (const task of tasks) {
1937
+ if (task.status !== 'in_progress')
1938
+ continue;
1939
+ const planRiskAudit = task.planHash ? readCurrentPlanRiskAudit(this.store, task) : null;
1940
+ if (!planRiskAudit)
1941
+ continue;
1942
+ for (const step of task.steps) {
1943
+ if (step.status !== 'in_progress' || !step.evidence)
1944
+ continue;
1945
+ if (!isStepReviewRequired(task, step.id, planRiskAudit.reviewRequiredStepIds))
1946
+ continue;
1947
+ try {
1948
+ const cycle = buildCurrentStrictStepReviewCycle(this.store, task, step.id);
1949
+ if (cycle.resolution !== 'pending')
1950
+ continue;
1951
+ validateTaskHistory(context.identity.repositoryRoot, task);
1952
+ let knowledgeBindingStale = false;
1953
+ try {
1954
+ const storedKnowledgeMap = this.store.readKnowledgeMap(context.identity.projectId);
1955
+ const knowledgeMap = inspectKnowledgeMap(storedKnowledgeMap, scanProjectKnowledge(context.identity.repositoryRoot, context.identity.projectId, this.now()));
1956
+ knowledgeBindingStale = knowledgeMap.status !== 'active'
1957
+ || task.knowledgeMapRevision !== knowledgeMap.revision
1958
+ || task.knowledgeMapHash !== hashKnowledgeMap(knowledgeMap);
1959
+ }
1960
+ catch {
1961
+ knowledgeBindingStale = true;
1962
+ }
1963
+ pendingCandidates.push({
1964
+ taskId: task.id,
1965
+ taskRevision: task.revision,
1966
+ stepId: step.id,
1967
+ completionCommit: step.evidence.commitSha,
1968
+ currentHead,
1969
+ knowledgeBindingStale,
1970
+ });
1971
+ }
1972
+ catch (error) {
1973
+ malformedCandidates.push(error instanceof Error ? error.message : String(error));
1974
+ }
1975
+ }
1976
+ }
1977
+ const candidate = pendingCandidates.length === 1 ? pendingCandidates[0] : null;
1978
+ const candidateLeaseIds = new Set(candidate ? [candidate.taskId] : []);
1979
+ const blockers = [
1980
+ ...(dependency.declared !== '2.0.0-alpha.6'
1981
+ ? ['Project package.json must declare codex-workflow-v2 exactly at 2.0.0-alpha.6.']
1982
+ : []),
1983
+ ...(dirty.length > 0 ? ['Repository checkout is not clean.'] : []),
1984
+ ...(activeLeases.length > 0 ? ['At least one writer lease is active.'] : []),
1985
+ ...(pendingCandidates.length !== 1
1986
+ ? [`Expected exactly one pending alpha.6 strict review; found ${pendingCandidates.length}.`]
1987
+ : []),
1988
+ ...(candidate && candidate.currentHead !== candidate.completionCommit
1989
+ ? ['HEAD does not match the pending strict review completion commit.']
1990
+ : []),
1991
+ ...(staleLeases.some((lease) => !candidateLeaseIds.has(lease.entityId))
1992
+ ? ['A stale writer lease exists outside the rescue Task.']
1993
+ : []),
1994
+ ...malformedCandidates.map((message) => `Pending strict review state is invalid: ${message}`),
1995
+ ];
1996
+ const eligible = blockers.length === 0 && candidate !== null;
1997
+ const requiredActions = eligible
1998
+ ? [
1999
+ ...staleLeases.map((lease) => ({
2000
+ action: 'locks repair',
2001
+ entityId: lease.entityId,
2002
+ })),
2003
+ {
2004
+ action: 'task step-review',
2005
+ taskId: candidate.taskId,
2006
+ stepId: candidate.stepId,
2007
+ expectedRevision: candidate.taskRevision,
2008
+ },
2009
+ ]
2010
+ : [];
2011
+ return {
2012
+ eligible,
2013
+ mutationFree: true,
2014
+ runnerPackageVersion: PACKAGE_VERSION,
2015
+ requiredProjectPackageVersion: '2.0.0-alpha.6',
2016
+ declaredProjectPackageVersion: dependency.declared,
2017
+ projectId: context.identity.projectId,
2018
+ repositoryRoot: context.identity.repositoryRoot,
2019
+ clean: dirty.length === 0,
2020
+ activeLeases,
2021
+ staleLeases,
2022
+ candidate,
2023
+ blockers,
2024
+ requiredActions,
2025
+ };
2026
+ }
1848
2027
  scanKnowledgeMap(repository) {
1849
2028
  const { identity } = this.context(repository);
1850
2029
  const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
@@ -1869,11 +2048,14 @@ export class WorkflowService {
1869
2048
  return inspectKnowledgeMap(current, scanned);
1870
2049
  }
1871
2050
  approveKnowledgeMap(repository, expectedRevision, selections, actor, delegationGrantId = null) {
2051
+ return this.approveKnowledgeMapForTaskRefresh(repository, expectedRevision, selections, actor, delegationGrantId, null);
2052
+ }
2053
+ approveKnowledgeMapForTaskRefresh(repository, expectedRevision, selections, actor, delegationGrantId, autonomyTaskId) {
1872
2054
  const { identity } = this.context(repository);
1873
2055
  const current = this.store.readKnowledgeMap(identity.projectId);
1874
2056
  assertExpectedRevision(current, expectedRevision);
1875
2057
  const inspected = inspectKnowledgeMap(current, scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now()));
1876
- const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'project_memory.approve', actor, inspected);
2058
+ const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'project_memory.approve', actor, inspected, autonomyTaskId);
1877
2059
  const approved = applyKnowledgeSelections(inspected, selections, actor, this.now());
1878
2060
  if (!approved.approval) {
1879
2061
  throw new WorkflowError('STATE_CORRUPT', 'Knowledge Map approval was not recorded.');
@@ -1942,11 +2124,20 @@ export class WorkflowService {
1942
2124
  const milestonesById = new Map(status.milestones.map((milestone) => [milestone.id, milestone]));
1943
2125
  const activeTask = status.tasks.find((task) => taskIsActionableFromRepositoryNext(task, milestonesById));
1944
2126
  if (activeTask) {
2127
+ const activePlanRiskAudit = activeTask.planHash
2128
+ ? readCurrentPlanRiskAudit(this.store, activeTask)
2129
+ : null;
2130
+ const taskNext = activeTask.status === 'planning'
2131
+ ? null
2132
+ : this.nextForTaskWithDelegation(status.projectId, activeTask, activePlanRiskAudit);
2133
+ // Pending strict review is completion work for the already-recorded commit. It must
2134
+ // outrank Project Knowledge refresh/rebind; otherwise the active Step makes the rebind
2135
+ // returned by `next` impossible to execute.
2136
+ if (taskNext?.action === 'task step-review') {
2137
+ return withAdoption(taskNext);
2138
+ }
1945
2139
  if (!taskCanRebindKnowledge(activeTask) && activeTask.status !== 'planning') {
1946
- const planRiskAudit = activeTask.planHash
1947
- ? readCurrentPlanRiskAudit(this.store, activeTask)
1948
- : null;
1949
- return withAdoption(this.nextForTaskWithDelegation(status.projectId, activeTask, planRiskAudit));
2140
+ return withAdoption(taskNext);
1950
2141
  }
1951
2142
  const knowledgeMap = this.knowledgeMapStatus(repository);
1952
2143
  if (knowledgeMap.status !== 'active' || !knowledgeMap.approval) {
@@ -1994,10 +2185,7 @@ export class WorkflowService {
1994
2185
  knowledgeMapHash,
1995
2186
  });
1996
2187
  }
1997
- const planRiskAudit = activeTask.planHash && activeTask.status !== 'planning'
1998
- ? readCurrentPlanRiskAudit(this.store, activeTask)
1999
- : null;
2000
- return withAdoption(this.nextForTaskWithDelegation(status.projectId, activeTask, planRiskAudit));
2188
+ return withAdoption(taskNext ?? this.nextForTaskWithDelegation(status.projectId, activeTask, null));
2001
2189
  }
2002
2190
  const activeMilestone = status.milestones.find((milestone) => !['accepted', 'cancelled'].includes(milestone.status));
2003
2191
  if (activeMilestone) {
@@ -2082,7 +2270,7 @@ export class WorkflowService {
2082
2270
  }
2083
2271
  return withAdoption({ scope: 'repository', status: 'idle', action: 'discovery start' });
2084
2272
  }
2085
- delegatedAuthorization(projectId, grantId, transition, actor, target) {
2273
+ delegatedAuthorization(projectId, grantId, transition, actor, target, autonomyTaskId = null) {
2086
2274
  if (!grantId) {
2087
2275
  if (actor.trim().startsWith('agent:')) {
2088
2276
  throw new WorkflowError('TRANSITION_BLOCKED', 'An agent actor requires an explicit delegated approval grant.');
@@ -2090,6 +2278,52 @@ export class WorkflowService {
2090
2278
  return null;
2091
2279
  }
2092
2280
  const grant = this.store.readDelegation(projectId, grantId);
2281
+ if (grant.scope.kind === 'milestone') {
2282
+ const autonomyContract = readMilestoneAutonomyEvents(this.store, projectId, grant.scope.id)
2283
+ .find((event) => event.delegationGrantId === grant.id) ?? null;
2284
+ if (autonomyContract) {
2285
+ const contextTask = target.kind === 'knowledge-map' && autonomyTaskId
2286
+ ? this.store.readTask(projectId, autonomyTaskId)
2287
+ : null;
2288
+ const milestone = target.kind === 'milestone'
2289
+ ? target
2290
+ : target.kind === 'task' && target.milestoneId === grant.scope.id
2291
+ ? this.store.readMilestone(projectId, grant.scope.id)
2292
+ : contextTask?.milestoneId === grant.scope.id
2293
+ ? this.store.readMilestone(projectId, grant.scope.id)
2294
+ : null;
2295
+ if (!milestone) {
2296
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone autonomy grant cannot authorize this target.');
2297
+ }
2298
+ const activeAutonomy = requireActiveMilestoneAutonomy({
2299
+ store: this.store,
2300
+ projectId,
2301
+ milestone,
2302
+ actor,
2303
+ now: this.now(),
2304
+ });
2305
+ if (activeAutonomy.grant.id !== grant.id) {
2306
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone autonomy grant has been superseded.');
2307
+ }
2308
+ if (target.kind === 'knowledge-map') {
2309
+ if (transition !== 'project_memory.approve'
2310
+ || !contextTask
2311
+ || !taskCanRebindKnowledge(contextTask)) {
2312
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone autonomy may approve Project Memory only inside a content-only Task context refresh.');
2313
+ }
2314
+ if (!grant.transitions.includes(transition)) {
2315
+ throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grant.id} does not allow ${transition}.`);
2316
+ }
2317
+ return {
2318
+ grantId: grant.id,
2319
+ policyHash: grant.policyHash,
2320
+ principal: grant.principal,
2321
+ delegate: grant.delegate,
2322
+ transition,
2323
+ };
2324
+ }
2325
+ }
2326
+ }
2093
2327
  return assertDelegationCanAuthorize(grant, transition, actor, target, this.now());
2094
2328
  }
2095
2329
  assertHandoffGrant(projectId, task, targetActor, grantId) {
@@ -2121,6 +2355,23 @@ export class WorkflowService {
2121
2355
  grantExpiresAt: grant.expiresAt,
2122
2356
  });
2123
2357
  }
2358
+ if (grant.scope.kind === 'milestone') {
2359
+ const autonomyContract = readMilestoneAutonomyEvents(this.store, projectId, grant.scope.id)
2360
+ .find((event) => event.delegationGrantId === grant.id) ?? null;
2361
+ if (autonomyContract) {
2362
+ const milestone = this.store.readMilestone(projectId, grant.scope.id);
2363
+ const activeAutonomy = requireActiveMilestoneAutonomy({
2364
+ store: this.store,
2365
+ projectId,
2366
+ milestone,
2367
+ actor: targetActor,
2368
+ now: this.now(),
2369
+ });
2370
+ if (activeAutonomy.grant.id !== grant.id) {
2371
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone autonomy grant has been superseded.');
2372
+ }
2373
+ }
2374
+ }
2124
2375
  }
2125
2376
  nextForTaskWithDelegation(projectId, task, planRiskAudit = null) {
2126
2377
  const transition = task.status === 'awaiting_execution_authorization'
@@ -2132,6 +2383,9 @@ export class WorkflowService {
2132
2383
  ? this.delegatedApprovalOptions(projectId, transition, task)
2133
2384
  : [];
2134
2385
  const failedReviewAttempts = countFailedReviewAttempts(this.store.taskRoot(projectId, task.id));
2386
+ const guardedCorrectiveGate = task.status === 'needs_fix' && planRiskAudit
2387
+ ? findFailedGuardedStepRequiringCorrectiveDecision(this.store, task, planRiskAudit)
2388
+ : null;
2135
2389
  const next = nextForTask(task);
2136
2390
  const handoffPosture = readTaskC1Posture(this.store, task);
2137
2391
  const strictStepReviewStep = planRiskAudit
@@ -2163,7 +2417,17 @@ export class WorkflowService {
2163
2417
  requiredActor: handoffPosture.claimant,
2164
2418
  },
2165
2419
  }
2166
- : {};
2420
+ : task.milestoneId && next.action === 'task start'
2421
+ ? {
2422
+ action: 'task handoff-prepare',
2423
+ blockedAction: next.action,
2424
+ c1Handoff: {
2425
+ state: 'required',
2426
+ recommendedTargetActor: defaultTaskWorkerActor(task.id),
2427
+ targetActorDerived: true,
2428
+ },
2429
+ }
2430
+ : {};
2167
2431
  const result = {
2168
2432
  ...next,
2169
2433
  ...handoffOverlay,
@@ -2204,6 +2468,21 @@ export class WorkflowService {
2204
2468
  },
2205
2469
  }
2206
2470
  : {}),
2471
+ ...(guardedCorrectiveGate
2472
+ ? {
2473
+ action: 'task corrective-decision',
2474
+ stepId: guardedCorrectiveGate.stepId,
2475
+ correctiveDecisionGate: {
2476
+ attemptCount: guardedCorrectiveGate.attemptCount,
2477
+ recommendedAuditor: defaultCorrectiveAuditorActor(task.id),
2478
+ auditorDerived: true,
2479
+ auditorMustDifferFromLatestStrictReviewer: true,
2480
+ allowedDecision: 'continue-fix',
2481
+ escalationDecisions: ['replan-required', 'split-required', 'stop-escalate'],
2482
+ humanApprovalRequired: false,
2483
+ },
2484
+ }
2485
+ : {}),
2207
2486
  ...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
2208
2487
  };
2209
2488
  if (handoffPosture.state === 'pending') {
@@ -2217,7 +2496,16 @@ export class WorkflowService {
2217
2496
  }
2218
2497
  delegatedApprovalOptions(projectId, transition, target) {
2219
2498
  return this.store.listDelegations(projectId)
2220
- .filter((grant) => delegationCanAuthorize(grant, transition, target, this.now()))
2499
+ .filter((grant) => {
2500
+ try {
2501
+ return Boolean(this.delegatedAuthorization(projectId, grant.id, transition, grant.delegate, target));
2502
+ }
2503
+ catch (error) {
2504
+ if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED')
2505
+ return false;
2506
+ throw error;
2507
+ }
2508
+ })
2221
2509
  .map((grant) => ({
2222
2510
  grantId: grant.id,
2223
2511
  principal: grant.principal,
@@ -2230,8 +2518,17 @@ export class WorkflowService {
2230
2518
  }
2231
2519
  delegatedContextRefreshOptions(projectId, task, knowledgeMap) {
2232
2520
  return this.store.listDelegations(projectId)
2233
- .filter((grant) => delegationCanAuthorize(grant, 'project_memory.approve', knowledgeMap, this.now()))
2234
- .filter((grant) => delegationCanAuthorize(grant, 'task.execution_authorize', task, this.now()))
2521
+ .filter((grant) => {
2522
+ try {
2523
+ return Boolean(this.delegatedAuthorization(projectId, grant.id, 'project_memory.approve', grant.delegate, knowledgeMap, task.id)
2524
+ && this.delegatedAuthorization(projectId, grant.id, 'task.execution_authorize', grant.delegate, task));
2525
+ }
2526
+ catch (error) {
2527
+ if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED')
2528
+ return false;
2529
+ throw error;
2530
+ }
2531
+ })
2235
2532
  .map((grant) => ({
2236
2533
  grantId: grant.id,
2237
2534
  principal: grant.principal,
@@ -2371,6 +2668,28 @@ export class WorkflowService {
2371
2668
  }
2372
2669
  return inspected;
2373
2670
  }
2671
+ strictReviewKnowledgeMap(repositoryRoot, task) {
2672
+ const stored = this.store.readKnowledgeMap(task.projectId);
2673
+ const inspected = inspectKnowledgeMap(stored, scanProjectKnowledge(repositoryRoot, task.projectId, this.now()));
2674
+ if (inspected.status === 'active' && inspected.approval)
2675
+ return inspected;
2676
+ const storedHash = hashKnowledgeMap(stored);
2677
+ if (stored.status === 'active'
2678
+ && stored.approval
2679
+ && task.knowledgeMapRevision === stored.revision
2680
+ && task.knowledgeMapHash === storedHash
2681
+ && stored.approval.mapHash === storedHash) {
2682
+ return stored;
2683
+ }
2684
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Strict Step Review has neither a current active Knowledge Map nor its exact historical Task binding.', {
2685
+ taskId: task.id,
2686
+ inspectedStatus: inspected.status,
2687
+ taskKnowledgeMapRevision: task.knowledgeMapRevision,
2688
+ storedKnowledgeMapRevision: stored.revision,
2689
+ taskKnowledgeMapHash: task.knowledgeMapHash,
2690
+ storedKnowledgeMapHash: storedHash,
2691
+ });
2692
+ }
2374
2693
  inspectAdoptionPreparation(identity, tasks, milestones) {
2375
2694
  const locks = new WriterLockManager(this.store.lockRoot(identity.projectId), this.now);
2376
2695
  const currentPosture = readCurrentAdoptionPosture(this.store, identity.projectId);