codex-workflow-v2 2.0.0-alpha.2 → 2.0.0-alpha.4

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 (36) hide show
  1. package/README.md +24 -3
  2. package/dist/src/cli.js +25 -6
  3. package/dist/src/cli.js.map +1 -1
  4. package/dist/src/contracts.d.ts +70 -1
  5. package/dist/src/delegation.d.ts +8 -0
  6. package/dist/src/delegation.js +155 -0
  7. package/dist/src/delegation.js.map +1 -0
  8. package/dist/src/index.d.ts +1 -0
  9. package/dist/src/index.js +1 -0
  10. package/dist/src/index.js.map +1 -1
  11. package/dist/src/state/store.d.ts +5 -1
  12. package/dist/src/state/store.js +13 -0
  13. package/dist/src/state/store.js.map +1 -1
  14. package/dist/src/ulid.d.ts +1 -1
  15. package/dist/src/ulid.js.map +1 -1
  16. package/dist/src/version.d.ts +1 -1
  17. package/dist/src/version.js +1 -1
  18. package/dist/src/workflow.d.ts +14 -6
  19. package/dist/src/workflow.js +242 -14
  20. package/dist/src/workflow.js.map +1 -1
  21. package/docs/decisions.md +15 -0
  22. package/docs/delegated-approval.md +164 -0
  23. package/docs/development-flow.md +27 -1
  24. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +336 -339
  25. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +761 -650
  26. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +502 -447
  27. package/docs/updating-existing-project.md +243 -0
  28. package/docs/validation-report.md +22 -6
  29. package/package.json +1 -1
  30. package/plugins/codex-workflow-gateway/references/protocol.md +30 -0
  31. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +36 -1
  32. package/references/state-machine.md +23 -1
  33. package/references/validation-and-review.md +5 -0
  34. package/roles/delivery-coordinator.md +5 -0
  35. package/schemas/authorization-event.schema.json +55 -0
  36. package/schemas/delegation-grant.schema.json +69 -0
@@ -1 +1 @@
1
- {"version":3,"file":"ulid.js","sourceRoot":"","sources":["../../src/ulid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,QAAQ,GAAG,kCAAkC,CAAC;AAEpD,SAAS,MAAM,CAAC,KAAa,EAAE,MAAc;IAC3C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC/C,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC;QAChD,KAAK,KAAK,EAAE,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IACzC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC/B,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,KAAK,MAAM,IAAI,IAAI,MAAM;QAAE,WAAW,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5E,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAA8B,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAC7E,OAAO,GAAG,MAAM,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;AACxC,CAAC"}
1
+ {"version":3,"file":"ulid.js","sourceRoot":"","sources":["../../src/ulid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,QAAQ,GAAG,kCAAkC,CAAC;AAEpD,SAAS,MAAM,CAAC,KAAa,EAAE,MAAc;IAC3C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC/C,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC;QAChD,KAAK,KAAK,EAAE,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IACzC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC/B,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,KAAK,MAAM,IAAI,IAAI,MAAM;QAAE,WAAW,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5E,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAsC,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IACrF,OAAO,GAAG,MAAM,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;AACxC,CAAC"}
@@ -1,2 +1,2 @@
1
1
  export declare const PACKAGE_NAME = "codex-workflow-v2";
2
- export declare const PACKAGE_VERSION = "2.0.0-alpha.2";
2
+ export declare const PACKAGE_VERSION = "2.0.0-alpha.4";
@@ -1,3 +1,3 @@
1
1
  export const PACKAGE_NAME = 'codex-workflow-v2';
2
- export const PACKAGE_VERSION = '2.0.0-alpha.2';
2
+ export const PACKAGE_VERSION = '2.0.0-alpha.4';
3
3
  //# sourceMappingURL=version.js.map
@@ -1,4 +1,4 @@
1
- import { type ContextEnvelope, type DiscoveryState, type MilestonePlanInput, type MilestoneState, type KnowledgeSelection, type GraphBinding, type GraphKind, type GraphMode, type GraphRefreshEvidence, type GraphRefreshRequest, type ProjectKnowledgeMap, type ReviewInput, type TaskPlanInput, type TaskState, type WriterLease, type WorkflowRole, type WorkspaceOwner } from './contracts.js';
1
+ import { type ContextEnvelope, type DelegationGrant, type DelegationGrantGate, type DelegationPolicyInput, type DiscoveryState, type MilestonePlanInput, type MilestoneState, type KnowledgeSelection, type GraphBinding, type GraphKind, type GraphMode, type GraphRefreshEvidence, type GraphRefreshRequest, type ProjectKnowledgeMap, type ReviewInput, type TaskPlanInput, type TaskState, type WriterLease, type WorkflowRole, type WorkspaceOwner } from './contracts.js';
2
2
  import { type RepositoryIdentity } from './repository.js';
3
3
  import { WriterLockManager } from './state/lock.js';
4
4
  import { FileStateStore } from './state/store.js';
@@ -29,6 +29,9 @@ export declare class WorkflowService {
29
29
  private readonly now;
30
30
  constructor(store?: FileStateStore, now?: () => Date);
31
31
  context(repository: string): WorkflowContext;
32
+ prepareDelegation(repository: string, input: DelegationPolicyInput): DelegationGrantGate;
33
+ grantDelegation(repository: string, input: DelegationPolicyInput, confirmationCode: string): DelegationGrant;
34
+ revokeDelegation(repository: string, grantId: string, expectedRevision: number, actor: string, reason: string): DelegationGrant;
32
35
  startDiscovery(repository: string, input: DiscoveryInput): DiscoveryState;
33
36
  updateDiscovery(repository: string, discoveryId: string, expectedRevision: number, patch: DiscoveryPatch): DiscoveryState;
34
37
  materializeTask(repository: string, discoveryId: string, expectedRevision: number, title: string, baseBranch?: string, milestoneId?: string | null): {
@@ -42,13 +45,13 @@ export declare class WorkflowService {
42
45
  milestoneRoot: string;
43
46
  };
44
47
  setMilestonePlan(repository: string, milestoneId: string, expectedRevision: number, plan: MilestonePlanInput): MilestoneState;
45
- authorizeMilestone(repository: string, milestoneId: string, expectedRevision: number, actor: string, reason?: string): MilestoneState;
48
+ authorizeMilestone(repository: string, milestoneId: string, expectedRevision: number, actor: string, reason?: string, delegationGrantId?: string | null): MilestoneState;
46
49
  validateMilestone(repository: string, milestoneId: string, expectedRevision: number): MilestoneState;
47
- acceptMilestone(repository: string, milestoneId: string, expectedRevision: number, actor: string): MilestoneState;
50
+ acceptMilestone(repository: string, milestoneId: string, expectedRevision: number, actor: string, confirmationCode: string, delegationGrantId?: string | null): MilestoneState;
48
51
  cancelMilestone(repository: string, milestoneId: string, expectedRevision: number, reason: string): MilestoneState;
49
52
  setTaskPlan(repository: string, taskId: string, expectedRevision: number, plan: TaskPlanInput): TaskState;
50
53
  rebindTaskKnowledge(repository: string, taskId: string, expectedRevision: number): TaskState;
51
- authorizeTask(repository: string, taskId: string, expectedRevision: number, actor: string, reason?: string): TaskState;
54
+ authorizeTask(repository: string, taskId: string, expectedRevision: number, actor: string, reason?: string, delegationGrantId?: string | null): TaskState;
52
55
  startTask(repository: string, taskId: string, expectedRevision: number, workspaceOwner: WorkspaceOwner): TaskState;
53
56
  runStep(repository: string, taskId: string, stepId: string, expectedRevision: number, owner: string, writerToken: string | null): {
54
57
  task: TaskState;
@@ -60,7 +63,7 @@ export declare class WorkflowService {
60
63
  submitTask(repository: string, taskId: string, expectedRevision: number, writerToken: string): TaskState;
61
64
  recordReview(repository: string, taskId: string, expectedRevision: number, review: ReviewInput): TaskState;
62
65
  setTaskResult(repository: string, taskId: string, expectedRevision: number, summary: string, limitations: string[]): TaskState;
63
- acceptTask(repository: string, taskId: string, expectedRevision: number, actor: string): TaskState;
66
+ acceptTask(repository: string, taskId: string, expectedRevision: number, actor: string, delegationGrantId?: string | null): TaskState;
64
67
  syncTaskBase(repository: string, taskId: string, expectedRevision: number): TaskState;
65
68
  mergeTask(repository: string, taskId: string, expectedRevision: number, writerToken: string): TaskState;
66
69
  confirmExternalMerge(repository: string, taskId: string, expectedRevision: number, writerToken: string): TaskState;
@@ -69,11 +72,12 @@ export declare class WorkflowService {
69
72
  discoveries: DiscoveryState[];
70
73
  tasks: TaskState[];
71
74
  milestones: MilestoneState[];
75
+ delegations: DelegationGrant[];
72
76
  };
73
77
  scanKnowledgeMap(repository: string): ProjectKnowledgeMap;
74
78
  showKnowledgeMap(repository: string): ProjectKnowledgeMap;
75
79
  knowledgeMapStatus(repository: string): ProjectKnowledgeMap;
76
- approveKnowledgeMap(repository: string, expectedRevision: number, selections: KnowledgeSelection[], actor: string): ProjectKnowledgeMap;
80
+ approveKnowledgeMap(repository: string, expectedRevision: number, selections: KnowledgeSelection[], actor: string, delegationGrantId?: string | null): ProjectKnowledgeMap;
77
81
  reconcileKnowledgeMap(repository: string, expectedRevision: number): ProjectKnowledgeMap;
78
82
  graphRefreshRequest(repository: string, graphKind: GraphKind, mode?: GraphMode): GraphRefreshRequest;
79
83
  graphStatus(repository: string, graphKind: GraphKind, mode?: GraphMode): {
@@ -83,6 +87,10 @@ export declare class WorkflowService {
83
87
  bindGraph(repository: string, expectedRevision: number | null, request: GraphRefreshRequest, evidence: GraphRefreshEvidence): GraphBinding;
84
88
  recordGraphFallback(repository: string, graphKind: GraphKind, mode: GraphMode, expectedRevision: number | null, reason: string): GraphBinding;
85
89
  next(repository: string): Record<string, unknown>;
90
+ private delegatedAuthorization;
91
+ private nextForTaskWithDelegation;
92
+ private delegatedApprovalOptions;
93
+ private assertDelegationScopeExists;
86
94
  private assertExecutionAuthorizationFresh;
87
95
  private assertMilestoneAuthorizationFresh;
88
96
  private readGraphBindingOrNull;
@@ -1,7 +1,9 @@
1
1
  import { appendFileSync, readFileSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
2
3
  import path from 'node:path';
3
4
  import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION, } from './contracts.js';
4
5
  import { renderBrief, renderPlan, renderResult } from './artifacts.js';
6
+ import { assertDelegationCanAuthorize, delegationCanAuthorize, prepareDelegationGrantGate, } from './delegation.js';
5
7
  import { assessDiscovery, mergeUnique } from './domain/discovery.js';
6
8
  import { pathAllowed, validatePlan } from './domain/validation.js';
7
9
  import { WorkflowError } from './errors.js';
@@ -29,6 +31,70 @@ export class WorkflowService {
29
31
  locks: new WriterLockManager(this.store.lockRoot(identity.projectId), this.now),
30
32
  };
31
33
  }
34
+ prepareDelegation(repository, input) {
35
+ const { identity } = this.context(repository);
36
+ const gate = prepareDelegationGrantGate(identity.projectId, input, this.now());
37
+ this.assertDelegationScopeExists(identity.projectId, gate.policy);
38
+ return gate;
39
+ }
40
+ grantDelegation(repository, input, confirmationCode) {
41
+ const { identity } = this.context(repository);
42
+ const gate = prepareDelegationGrantGate(identity.projectId, input, this.now());
43
+ this.assertDelegationScopeExists(identity.projectId, gate.policy);
44
+ if (confirmationCode.trim() !== gate.confirmationCode) {
45
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Delegation confirmation does not match the exact project and policy.', { projectId: identity.projectId, policyHash: gate.policyHash });
46
+ }
47
+ const duplicate = this.store.listDelegations(identity.projectId)
48
+ .find((grant) => grant.policyHash === gate.policyHash);
49
+ if (duplicate) {
50
+ throw new WorkflowError('TRANSITION_BLOCKED', 'This exact delegation policy was already issued.', {
51
+ grantId: duplicate.id,
52
+ status: duplicate.status,
53
+ });
54
+ }
55
+ const timestamp = this.now().toISOString();
56
+ return this.store.writeDelegation({
57
+ schemaVersion: STATE_SCHEMA_VERSION,
58
+ revision: 0,
59
+ id: createEntityId('DGR', this.now().getTime()),
60
+ projectId: identity.projectId,
61
+ createdAt: timestamp,
62
+ updatedAt: timestamp,
63
+ kind: 'delegation-grant',
64
+ status: 'active',
65
+ principal: gate.policy.principal,
66
+ delegate: gate.policy.delegate,
67
+ scope: gate.policy.scope,
68
+ transitions: gate.policy.transitions,
69
+ expiresAt: gate.policy.expiresAt,
70
+ policyHash: gate.policyHash,
71
+ revokedAt: null,
72
+ revokedBy: null,
73
+ revokeReason: null,
74
+ }, null);
75
+ }
76
+ revokeDelegation(repository, grantId, expectedRevision, actor, reason) {
77
+ const { identity } = this.context(repository);
78
+ const grant = this.store.readDelegation(identity.projectId, grantId);
79
+ assertExpectedRevision(grant, expectedRevision);
80
+ if (grant.status !== 'active') {
81
+ throw new WorkflowError('TRANSITION_BLOCKED', `Delegation ${grantId} is already ${grant.status}.`);
82
+ }
83
+ if (actor.trim() !== grant.principal) {
84
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Only the recorded principal may revoke a delegation.', {
85
+ principal: grant.principal,
86
+ });
87
+ }
88
+ if (!reason.trim())
89
+ throw new WorkflowError('INVALID_ARGUMENT', 'Delegation revocation requires a reason.');
90
+ return this.store.writeDelegation({
91
+ ...grant,
92
+ status: 'revoked',
93
+ revokedAt: this.now().toISOString(),
94
+ revokedBy: grant.principal,
95
+ revokeReason: reason.trim(),
96
+ }, expectedRevision);
97
+ }
32
98
  startDiscovery(repository, input) {
33
99
  if (!input.goal.trim())
34
100
  throw new WorkflowError('INVALID_ARGUMENT', 'Discovery requires a substantive goal.');
@@ -265,7 +331,7 @@ export class WorkflowService {
265
331
  this.store.publishArtifact(stagedPlan);
266
332
  return saved;
267
333
  }
268
- authorizeMilestone(repository, milestoneId, expectedRevision, actor, reason = '') {
334
+ authorizeMilestone(repository, milestoneId, expectedRevision, actor, reason = '', delegationGrantId = null) {
269
335
  const { identity } = this.context(repository);
270
336
  const milestone = this.store.readMilestone(identity.projectId, milestoneId);
271
337
  assertExpectedRevision(milestone, expectedRevision);
@@ -277,6 +343,7 @@ export class WorkflowService {
277
343
  if (planHash !== milestone.planHash) {
278
344
  throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone Plan changed after it was recorded.');
279
345
  }
346
+ const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'milestone.execution_authorize', actor, milestone);
280
347
  const event = {
281
348
  kind: 'execution',
282
349
  decision: 'approved',
@@ -288,6 +355,8 @@ export class WorkflowService {
288
355
  evidenceHash: null,
289
356
  headCommit: runGit(identity.repositoryRoot, ['rev-parse', milestone.baseBranch]),
290
357
  reason,
358
+ authorizationMode: delegation ? 'delegated' : 'human',
359
+ ...(delegation ? { delegation } : {}),
291
360
  };
292
361
  return this.store.writeMilestone({ ...milestone, status: 'active', planHash, authorizations: [...milestone.authorizations, event] }, expectedRevision);
293
362
  }
@@ -295,8 +364,8 @@ export class WorkflowService {
295
364
  const { identity } = this.context(repository);
296
365
  const milestone = this.store.readMilestone(identity.projectId, milestoneId);
297
366
  assertExpectedRevision(milestone, expectedRevision);
298
- if (milestone.status !== 'active') {
299
- throw new WorkflowError('TRANSITION_BLOCKED', `Milestone ${milestoneId} is not active.`);
367
+ if (!['active', 'awaiting_final_acceptance'].includes(milestone.status)) {
368
+ throw new WorkflowError('TRANSITION_BLOCKED', `Milestone ${milestoneId} cannot be validated while ${milestone.status}.`);
300
369
  }
301
370
  this.assertMilestoneAuthorizationFresh(milestone);
302
371
  if (currentBranch(identity.repositoryRoot) !== milestone.baseBranch || !isClean(identity.repositoryRoot)) {
@@ -354,7 +423,7 @@ export class WorkflowService {
354
423
  this.store.publishArtifact(stagedResult);
355
424
  return saved;
356
425
  }
357
- acceptMilestone(repository, milestoneId, expectedRevision, actor) {
426
+ acceptMilestone(repository, milestoneId, expectedRevision, actor, confirmationCode, delegationGrantId = null) {
358
427
  const { identity } = this.context(repository);
359
428
  const milestone = this.store.readMilestone(identity.projectId, milestoneId);
360
429
  assertExpectedRevision(milestone, expectedRevision);
@@ -372,17 +441,31 @@ export class WorkflowService {
372
441
  if (evidence.headCommit !== currentHead || resultHash !== milestone.resultHash || evidenceHash !== milestone.evidenceHash) {
373
442
  throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone validation evidence is stale.');
374
443
  }
444
+ const normalizedActor = actor.trim();
445
+ if (!normalizedActor) {
446
+ throw new WorkflowError('INVALID_ARGUMENT', 'Milestone final acceptance requires an explicit actor.');
447
+ }
448
+ const gate = milestoneFinalAcceptanceGate(milestone, currentHead);
449
+ if (confirmationCode.trim() !== gate.confirmationCode) {
450
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone final acceptance confirmation does not match the current validated state.', {
451
+ milestoneId,
452
+ revision: milestone.revision,
453
+ });
454
+ }
455
+ const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'milestone.final_accept', normalizedActor, milestone);
375
456
  const event = {
376
457
  kind: 'final_acceptance',
377
458
  decision: 'approved',
378
- actor: actor.trim() || 'user',
459
+ actor: normalizedActor,
379
460
  recordedAt: this.now().toISOString(),
380
461
  briefHash: null,
381
462
  planHash: milestone.planHash,
382
463
  resultHash,
383
464
  evidenceHash,
384
465
  headCommit: currentHead,
385
- reason: '',
466
+ reason: `Confirmed with ${gate.confirmationCode}.`,
467
+ authorizationMode: delegation ? 'delegated' : 'human',
468
+ ...(delegation ? { delegation } : {}),
386
469
  };
387
470
  return this.store.writeMilestone({
388
471
  ...milestone,
@@ -547,7 +630,7 @@ export class WorkflowService {
547
630
  this.store.publishArtifact(stagedPlan);
548
631
  return saved;
549
632
  }
550
- authorizeTask(repository, taskId, expectedRevision, actor, reason = '') {
633
+ authorizeTask(repository, taskId, expectedRevision, actor, reason = '', delegationGrantId = null) {
551
634
  const { identity } = this.context(repository);
552
635
  const task = this.store.readTask(identity.projectId, taskId);
553
636
  assertExpectedRevision(task, expectedRevision);
@@ -558,6 +641,7 @@ export class WorkflowService {
558
641
  const root = this.store.taskRoot(identity.projectId, taskId);
559
642
  const briefHash = this.store.hashArtifact(root, 'brief.md');
560
643
  const planHash = this.store.hashArtifact(root, 'plan.md');
644
+ const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'task.execution_authorize', actor, task);
561
645
  const event = {
562
646
  kind: 'execution',
563
647
  decision: 'approved',
@@ -569,6 +653,8 @@ export class WorkflowService {
569
653
  evidenceHash: null,
570
654
  headCommit: null,
571
655
  reason,
656
+ authorizationMode: delegation ? 'delegated' : 'human',
657
+ ...(delegation ? { delegation } : {}),
572
658
  };
573
659
  return this.store.writeTask({ ...task, status: 'ready', briefHash, planHash, authorizations: [...task.authorizations, event] }, expectedRevision);
574
660
  }
@@ -883,7 +969,7 @@ export class WorkflowService {
883
969
  this.store.publishArtifact(stagedResult);
884
970
  return saved;
885
971
  }
886
- acceptTask(repository, taskId, expectedRevision, actor) {
972
+ acceptTask(repository, taskId, expectedRevision, actor, delegationGrantId = null) {
887
973
  const { identity } = this.context(repository);
888
974
  const task = this.store.readTask(identity.projectId, taskId);
889
975
  assertExpectedRevision(task, expectedRevision);
@@ -898,6 +984,7 @@ export class WorkflowService {
898
984
  this.assertReviewFresh(identity.repositoryRoot, task);
899
985
  const resultHash = this.store.hashArtifact(root, 'result.md');
900
986
  const evidenceHash = this.store.hashArtifact(root, 'evidence.json');
987
+ const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'task.final_accept', actor, task);
901
988
  const event = {
902
989
  kind: 'final_acceptance',
903
990
  decision: 'approved',
@@ -909,6 +996,8 @@ export class WorkflowService {
909
996
  evidenceHash,
910
997
  headCommit: headCommit(identity.repositoryRoot),
911
998
  reason: '',
999
+ authorizationMode: delegation ? 'delegated' : 'human',
1000
+ ...(delegation ? { delegation } : {}),
912
1001
  };
913
1002
  return this.store.writeTask({
914
1003
  ...task,
@@ -1012,6 +1101,7 @@ export class WorkflowService {
1012
1101
  discoveries: this.store.listDiscoveries(identity.projectId),
1013
1102
  tasks: this.store.listTasks(identity.projectId),
1014
1103
  milestones: this.store.listMilestones(identity.projectId),
1104
+ delegations: this.store.listDelegations(identity.projectId),
1015
1105
  };
1016
1106
  }
1017
1107
  scanKnowledgeMap(repository) {
@@ -1037,12 +1127,21 @@ export class WorkflowService {
1037
1127
  const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
1038
1128
  return inspectKnowledgeMap(current, scanned);
1039
1129
  }
1040
- approveKnowledgeMap(repository, expectedRevision, selections, actor) {
1130
+ approveKnowledgeMap(repository, expectedRevision, selections, actor, delegationGrantId = null) {
1041
1131
  const { identity } = this.context(repository);
1042
1132
  const current = this.store.readKnowledgeMap(identity.projectId);
1043
1133
  assertExpectedRevision(current, expectedRevision);
1044
1134
  const inspected = inspectKnowledgeMap(current, scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now()));
1135
+ const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'project_memory.approve', actor, inspected);
1045
1136
  const approved = applyKnowledgeSelections(inspected, selections, actor, this.now());
1137
+ if (!approved.approval) {
1138
+ throw new WorkflowError('STATE_CORRUPT', 'Knowledge Map approval was not recorded.');
1139
+ }
1140
+ approved.approval = {
1141
+ ...approved.approval,
1142
+ authorizationMode: delegation ? 'delegated' : 'human',
1143
+ ...(delegation ? { delegation } : {}),
1144
+ };
1046
1145
  return this.store.writeKnowledgeMap(approved, expectedRevision);
1047
1146
  }
1048
1147
  reconcileKnowledgeMap(repository, expectedRevision) {
@@ -1078,19 +1177,26 @@ export class WorkflowService {
1078
1177
  }
1079
1178
  next(repository) {
1080
1179
  const status = this.status(repository);
1081
- const activeTask = status.tasks.find((task) => !['merged', 'cancelled'].includes(task.status));
1180
+ const repositoryRoot = resolveRepositoryIdentity(repository).repositoryRoot;
1181
+ const milestonesById = new Map(status.milestones.map((milestone) => [milestone.id, milestone]));
1182
+ const activeTask = status.tasks.find((task) => taskIsActionableFromRepositoryNext(task, milestonesById));
1082
1183
  if (activeTask) {
1083
1184
  if (!taskCanRebindKnowledge(activeTask) && activeTask.status !== 'planning') {
1084
- return nextForTask(activeTask);
1185
+ return this.nextForTaskWithDelegation(status.projectId, activeTask);
1085
1186
  }
1086
1187
  const knowledgeMap = this.knowledgeMapStatus(repository);
1087
1188
  if (knowledgeMap.status !== 'active' || !knowledgeMap.approval) {
1189
+ const action = knowledgeMap.status === 'stale' ? 'project-memory reconcile' : 'project-memory approve';
1190
+ const delegatedApprovalOptions = action === 'project-memory approve'
1191
+ ? this.delegatedApprovalOptions(status.projectId, 'project_memory.approve', knowledgeMap)
1192
+ : [];
1088
1193
  return {
1089
1194
  scope: 'project-memory',
1090
1195
  status: knowledgeMap.status,
1091
- action: knowledgeMap.status === 'stale' ? 'project-memory reconcile' : 'project-memory approve',
1196
+ action,
1092
1197
  revision: knowledgeMap.revision,
1093
1198
  taskId: activeTask.id,
1199
+ ...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
1094
1200
  };
1095
1201
  }
1096
1202
  const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
@@ -1108,16 +1214,56 @@ export class WorkflowService {
1108
1214
  knowledgeMapHash,
1109
1215
  };
1110
1216
  }
1111
- return nextForTask(activeTask);
1217
+ return this.nextForTaskWithDelegation(status.projectId, activeTask);
1112
1218
  }
1113
1219
  const activeMilestone = status.milestones.find((milestone) => !['accepted', 'cancelled'].includes(milestone.status));
1114
1220
  if (activeMilestone) {
1221
+ if (activeMilestone.status === 'awaiting_final_acceptance') {
1222
+ const root = this.store.milestoneRoot(activeMilestone.projectId, activeMilestone.id);
1223
+ const evidence = JSON.parse(readFileSync(path.join(root, 'evidence.json'), 'utf8'));
1224
+ if (!evidence.headCommit || !activeMilestone.resultHash || !activeMilestone.evidenceHash) {
1225
+ throw new WorkflowError('STATE_CORRUPT', `Milestone ${activeMilestone.id} has incomplete final evidence.`);
1226
+ }
1227
+ const currentHead = headCommit(repositoryRoot);
1228
+ const resultHash = this.store.hashArtifact(root, 'result.md');
1229
+ const evidenceHash = this.store.hashArtifact(root, 'evidence.json');
1230
+ if (currentBranch(repositoryRoot) !== activeMilestone.baseBranch ||
1231
+ !isClean(repositoryRoot) ||
1232
+ evidence.headCommit !== currentHead ||
1233
+ resultHash !== activeMilestone.resultHash ||
1234
+ evidenceHash !== activeMilestone.evidenceHash) {
1235
+ return {
1236
+ scope: 'milestone',
1237
+ id: activeMilestone.id,
1238
+ status: activeMilestone.status,
1239
+ action: 'restore clean base, then milestone validate',
1240
+ revision: activeMilestone.revision,
1241
+ reason: 'final validation evidence is stale',
1242
+ };
1243
+ }
1244
+ const delegatedApprovalOptions = this.delegatedApprovalOptions(status.projectId, 'milestone.final_accept', activeMilestone);
1245
+ return {
1246
+ scope: 'milestone',
1247
+ id: activeMilestone.id,
1248
+ status: activeMilestone.status,
1249
+ action: delegatedApprovalOptions.length > 0
1250
+ ? 'milestone accept using delegated approval or await explicit human final acceptance'
1251
+ : 'await explicit human final acceptance',
1252
+ revision: activeMilestone.revision,
1253
+ requiredHumanGate: milestoneFinalAcceptanceGate(activeMilestone, evidence.headCommit),
1254
+ ...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
1255
+ };
1256
+ }
1257
+ const delegatedApprovalOptions = activeMilestone.status === 'awaiting_execution_authorization'
1258
+ ? this.delegatedApprovalOptions(status.projectId, 'milestone.execution_authorize', activeMilestone)
1259
+ : [];
1115
1260
  return {
1116
1261
  scope: 'milestone',
1117
1262
  id: activeMilestone.id,
1118
1263
  status: activeMilestone.status,
1119
1264
  action: nextForMilestone(activeMilestone),
1120
1265
  revision: activeMilestone.revision,
1266
+ ...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
1121
1267
  };
1122
1268
  }
1123
1269
  const activeDiscovery = status.discoveries.find((discovery) => !['materialized', 'abandoned'].includes(discovery.status));
@@ -1132,6 +1278,51 @@ export class WorkflowService {
1132
1278
  }
1133
1279
  return { scope: 'repository', status: 'idle', action: 'discovery start' };
1134
1280
  }
1281
+ delegatedAuthorization(projectId, grantId, transition, actor, target) {
1282
+ if (!grantId) {
1283
+ if (actor.trim().startsWith('agent:')) {
1284
+ throw new WorkflowError('TRANSITION_BLOCKED', 'An agent actor requires an explicit delegated approval grant.');
1285
+ }
1286
+ return null;
1287
+ }
1288
+ const grant = this.store.readDelegation(projectId, grantId);
1289
+ return assertDelegationCanAuthorize(grant, transition, actor, target, this.now());
1290
+ }
1291
+ nextForTaskWithDelegation(projectId, task) {
1292
+ const transition = task.status === 'awaiting_execution_authorization'
1293
+ ? 'task.execution_authorize'
1294
+ : task.status === 'awaiting_final_acceptance'
1295
+ ? 'task.final_accept'
1296
+ : null;
1297
+ const delegatedApprovalOptions = transition
1298
+ ? this.delegatedApprovalOptions(projectId, transition, task)
1299
+ : [];
1300
+ return {
1301
+ ...nextForTask(task),
1302
+ ...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
1303
+ };
1304
+ }
1305
+ delegatedApprovalOptions(projectId, transition, target) {
1306
+ return this.store.listDelegations(projectId)
1307
+ .filter((grant) => delegationCanAuthorize(grant, transition, target, this.now()))
1308
+ .map((grant) => ({
1309
+ grantId: grant.id,
1310
+ principal: grant.principal,
1311
+ delegate: grant.delegate,
1312
+ transition,
1313
+ scope: grant.scope,
1314
+ expiresAt: grant.expiresAt,
1315
+ policyHash: grant.policyHash,
1316
+ }));
1317
+ }
1318
+ assertDelegationScopeExists(projectId, policy) {
1319
+ if (policy.scope.kind === 'task') {
1320
+ this.store.readTask(projectId, policy.scope.id);
1321
+ return;
1322
+ }
1323
+ if (policy.scope.kind === 'milestone')
1324
+ this.store.readMilestone(projectId, policy.scope.id);
1325
+ }
1135
1326
  assertExecutionAuthorizationFresh(task) {
1136
1327
  const root = this.store.taskRoot(task.projectId, task.id);
1137
1328
  const authorization = [...task.authorizations]
@@ -1363,13 +1554,50 @@ function taskCanRebindKnowledge(task) {
1363
1554
  'awaiting_final_acceptance',
1364
1555
  ].includes(task.status);
1365
1556
  }
1557
+ function taskIsActionableFromRepositoryNext(task, milestonesById) {
1558
+ if (task.status === 'merged' || task.status === 'cancelled')
1559
+ return false;
1560
+ if (!task.milestoneId)
1561
+ return true;
1562
+ const milestone = milestonesById.get(task.milestoneId);
1563
+ if (!milestone)
1564
+ return false;
1565
+ if (taskHasStarted(task))
1566
+ return !['accepted', 'cancelled'].includes(milestone.status);
1567
+ const membership = milestone.memberships.find((candidate) => candidate.taskId === task.id);
1568
+ return milestone.status === 'active' && membership?.disposition === 'required';
1569
+ }
1570
+ function milestoneFinalAcceptanceGate(milestone, headCommit) {
1571
+ if (!milestone.resultHash || !milestone.evidenceHash) {
1572
+ throw new WorkflowError('STATE_CORRUPT', `Milestone ${milestone.id} has no final acceptance artifacts.`);
1573
+ }
1574
+ const binding = [
1575
+ 'milestone-final-acceptance-v1',
1576
+ milestone.id,
1577
+ String(milestone.revision),
1578
+ milestone.planHash ?? '',
1579
+ milestone.resultHash,
1580
+ milestone.evidenceHash,
1581
+ headCommit,
1582
+ ].join('\0');
1583
+ return {
1584
+ kind: 'milestone_final_acceptance',
1585
+ milestoneId: milestone.id,
1586
+ revision: milestone.revision,
1587
+ planHash: milestone.planHash,
1588
+ resultHash: milestone.resultHash,
1589
+ evidenceHash: milestone.evidenceHash,
1590
+ headCommit,
1591
+ confirmationCode: `MSA-${createHash('sha256').update(binding).digest('hex').slice(0, 24).toUpperCase()}`,
1592
+ };
1593
+ }
1366
1594
  function nextForMilestone(milestone) {
1367
1595
  const actions = {
1368
1596
  planning: 'milestone plan-set',
1369
1597
  awaiting_execution_authorization: 'milestone authorize',
1370
1598
  active: 'complete required Tasks, then milestone validate',
1371
1599
  validating: 'milestone validate',
1372
- awaiting_final_acceptance: 'milestone accept',
1600
+ awaiting_final_acceptance: 'await explicit human final acceptance',
1373
1601
  accepted: 'none',
1374
1602
  blocked: 'milestone plan-set or milestone cancel',
1375
1603
  cancelled: 'none',