codex-workflow-v2 2.0.0-alpha.3 → 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.
@@ -1,3 +1,3 @@
1
1
  export const PACKAGE_NAME = 'codex-workflow-v2';
2
- export const PACKAGE_VERSION = '2.0.0-alpha.3';
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, confirmationCode: 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;
@@ -3,6 +3,7 @@ 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, prepareDelegationGrantGate, } from './delegation.js';
6
7
  import { assessDiscovery, mergeUnique } from './domain/discovery.js';
7
8
  import { pathAllowed, validatePlan } from './domain/validation.js';
8
9
  import { WorkflowError } from './errors.js';
@@ -30,6 +31,70 @@ export class WorkflowService {
30
31
  locks: new WriterLockManager(this.store.lockRoot(identity.projectId), this.now),
31
32
  };
32
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
+ }
33
98
  startDiscovery(repository, input) {
34
99
  if (!input.goal.trim())
35
100
  throw new WorkflowError('INVALID_ARGUMENT', 'Discovery requires a substantive goal.');
@@ -266,7 +331,7 @@ export class WorkflowService {
266
331
  this.store.publishArtifact(stagedPlan);
267
332
  return saved;
268
333
  }
269
- authorizeMilestone(repository, milestoneId, expectedRevision, actor, reason = '') {
334
+ authorizeMilestone(repository, milestoneId, expectedRevision, actor, reason = '', delegationGrantId = null) {
270
335
  const { identity } = this.context(repository);
271
336
  const milestone = this.store.readMilestone(identity.projectId, milestoneId);
272
337
  assertExpectedRevision(milestone, expectedRevision);
@@ -278,6 +343,7 @@ export class WorkflowService {
278
343
  if (planHash !== milestone.planHash) {
279
344
  throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone Plan changed after it was recorded.');
280
345
  }
346
+ const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'milestone.execution_authorize', actor, milestone);
281
347
  const event = {
282
348
  kind: 'execution',
283
349
  decision: 'approved',
@@ -289,6 +355,8 @@ export class WorkflowService {
289
355
  evidenceHash: null,
290
356
  headCommit: runGit(identity.repositoryRoot, ['rev-parse', milestone.baseBranch]),
291
357
  reason,
358
+ authorizationMode: delegation ? 'delegated' : 'human',
359
+ ...(delegation ? { delegation } : {}),
292
360
  };
293
361
  return this.store.writeMilestone({ ...milestone, status: 'active', planHash, authorizations: [...milestone.authorizations, event] }, expectedRevision);
294
362
  }
@@ -355,7 +423,7 @@ export class WorkflowService {
355
423
  this.store.publishArtifact(stagedResult);
356
424
  return saved;
357
425
  }
358
- acceptMilestone(repository, milestoneId, expectedRevision, actor, confirmationCode) {
426
+ acceptMilestone(repository, milestoneId, expectedRevision, actor, confirmationCode, delegationGrantId = null) {
359
427
  const { identity } = this.context(repository);
360
428
  const milestone = this.store.readMilestone(identity.projectId, milestoneId);
361
429
  assertExpectedRevision(milestone, expectedRevision);
@@ -384,6 +452,7 @@ export class WorkflowService {
384
452
  revision: milestone.revision,
385
453
  });
386
454
  }
455
+ const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'milestone.final_accept', normalizedActor, milestone);
387
456
  const event = {
388
457
  kind: 'final_acceptance',
389
458
  decision: 'approved',
@@ -395,6 +464,8 @@ export class WorkflowService {
395
464
  evidenceHash,
396
465
  headCommit: currentHead,
397
466
  reason: `Confirmed with ${gate.confirmationCode}.`,
467
+ authorizationMode: delegation ? 'delegated' : 'human',
468
+ ...(delegation ? { delegation } : {}),
398
469
  };
399
470
  return this.store.writeMilestone({
400
471
  ...milestone,
@@ -559,7 +630,7 @@ export class WorkflowService {
559
630
  this.store.publishArtifact(stagedPlan);
560
631
  return saved;
561
632
  }
562
- authorizeTask(repository, taskId, expectedRevision, actor, reason = '') {
633
+ authorizeTask(repository, taskId, expectedRevision, actor, reason = '', delegationGrantId = null) {
563
634
  const { identity } = this.context(repository);
564
635
  const task = this.store.readTask(identity.projectId, taskId);
565
636
  assertExpectedRevision(task, expectedRevision);
@@ -570,6 +641,7 @@ export class WorkflowService {
570
641
  const root = this.store.taskRoot(identity.projectId, taskId);
571
642
  const briefHash = this.store.hashArtifact(root, 'brief.md');
572
643
  const planHash = this.store.hashArtifact(root, 'plan.md');
644
+ const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'task.execution_authorize', actor, task);
573
645
  const event = {
574
646
  kind: 'execution',
575
647
  decision: 'approved',
@@ -581,6 +653,8 @@ export class WorkflowService {
581
653
  evidenceHash: null,
582
654
  headCommit: null,
583
655
  reason,
656
+ authorizationMode: delegation ? 'delegated' : 'human',
657
+ ...(delegation ? { delegation } : {}),
584
658
  };
585
659
  return this.store.writeTask({ ...task, status: 'ready', briefHash, planHash, authorizations: [...task.authorizations, event] }, expectedRevision);
586
660
  }
@@ -895,7 +969,7 @@ export class WorkflowService {
895
969
  this.store.publishArtifact(stagedResult);
896
970
  return saved;
897
971
  }
898
- acceptTask(repository, taskId, expectedRevision, actor) {
972
+ acceptTask(repository, taskId, expectedRevision, actor, delegationGrantId = null) {
899
973
  const { identity } = this.context(repository);
900
974
  const task = this.store.readTask(identity.projectId, taskId);
901
975
  assertExpectedRevision(task, expectedRevision);
@@ -910,6 +984,7 @@ export class WorkflowService {
910
984
  this.assertReviewFresh(identity.repositoryRoot, task);
911
985
  const resultHash = this.store.hashArtifact(root, 'result.md');
912
986
  const evidenceHash = this.store.hashArtifact(root, 'evidence.json');
987
+ const delegation = this.delegatedAuthorization(identity.projectId, delegationGrantId, 'task.final_accept', actor, task);
913
988
  const event = {
914
989
  kind: 'final_acceptance',
915
990
  decision: 'approved',
@@ -921,6 +996,8 @@ export class WorkflowService {
921
996
  evidenceHash,
922
997
  headCommit: headCommit(identity.repositoryRoot),
923
998
  reason: '',
999
+ authorizationMode: delegation ? 'delegated' : 'human',
1000
+ ...(delegation ? { delegation } : {}),
924
1001
  };
925
1002
  return this.store.writeTask({
926
1003
  ...task,
@@ -1024,6 +1101,7 @@ export class WorkflowService {
1024
1101
  discoveries: this.store.listDiscoveries(identity.projectId),
1025
1102
  tasks: this.store.listTasks(identity.projectId),
1026
1103
  milestones: this.store.listMilestones(identity.projectId),
1104
+ delegations: this.store.listDelegations(identity.projectId),
1027
1105
  };
1028
1106
  }
1029
1107
  scanKnowledgeMap(repository) {
@@ -1049,12 +1127,21 @@ export class WorkflowService {
1049
1127
  const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
1050
1128
  return inspectKnowledgeMap(current, scanned);
1051
1129
  }
1052
- approveKnowledgeMap(repository, expectedRevision, selections, actor) {
1130
+ approveKnowledgeMap(repository, expectedRevision, selections, actor, delegationGrantId = null) {
1053
1131
  const { identity } = this.context(repository);
1054
1132
  const current = this.store.readKnowledgeMap(identity.projectId);
1055
1133
  assertExpectedRevision(current, expectedRevision);
1056
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);
1057
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
+ };
1058
1145
  return this.store.writeKnowledgeMap(approved, expectedRevision);
1059
1146
  }
1060
1147
  reconcileKnowledgeMap(repository, expectedRevision) {
@@ -1095,16 +1182,21 @@ export class WorkflowService {
1095
1182
  const activeTask = status.tasks.find((task) => taskIsActionableFromRepositoryNext(task, milestonesById));
1096
1183
  if (activeTask) {
1097
1184
  if (!taskCanRebindKnowledge(activeTask) && activeTask.status !== 'planning') {
1098
- return nextForTask(activeTask);
1185
+ return this.nextForTaskWithDelegation(status.projectId, activeTask);
1099
1186
  }
1100
1187
  const knowledgeMap = this.knowledgeMapStatus(repository);
1101
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
+ : [];
1102
1193
  return {
1103
1194
  scope: 'project-memory',
1104
1195
  status: knowledgeMap.status,
1105
- action: knowledgeMap.status === 'stale' ? 'project-memory reconcile' : 'project-memory approve',
1196
+ action,
1106
1197
  revision: knowledgeMap.revision,
1107
1198
  taskId: activeTask.id,
1199
+ ...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
1108
1200
  };
1109
1201
  }
1110
1202
  const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
@@ -1122,7 +1214,7 @@ export class WorkflowService {
1122
1214
  knowledgeMapHash,
1123
1215
  };
1124
1216
  }
1125
- return nextForTask(activeTask);
1217
+ return this.nextForTaskWithDelegation(status.projectId, activeTask);
1126
1218
  }
1127
1219
  const activeMilestone = status.milestones.find((milestone) => !['accepted', 'cancelled'].includes(milestone.status));
1128
1220
  if (activeMilestone) {
@@ -1149,21 +1241,29 @@ export class WorkflowService {
1149
1241
  reason: 'final validation evidence is stale',
1150
1242
  };
1151
1243
  }
1244
+ const delegatedApprovalOptions = this.delegatedApprovalOptions(status.projectId, 'milestone.final_accept', activeMilestone);
1152
1245
  return {
1153
1246
  scope: 'milestone',
1154
1247
  id: activeMilestone.id,
1155
1248
  status: activeMilestone.status,
1156
- action: 'await explicit human final acceptance',
1249
+ action: delegatedApprovalOptions.length > 0
1250
+ ? 'milestone accept using delegated approval or await explicit human final acceptance'
1251
+ : 'await explicit human final acceptance',
1157
1252
  revision: activeMilestone.revision,
1158
1253
  requiredHumanGate: milestoneFinalAcceptanceGate(activeMilestone, evidence.headCommit),
1254
+ ...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
1159
1255
  };
1160
1256
  }
1257
+ const delegatedApprovalOptions = activeMilestone.status === 'awaiting_execution_authorization'
1258
+ ? this.delegatedApprovalOptions(status.projectId, 'milestone.execution_authorize', activeMilestone)
1259
+ : [];
1161
1260
  return {
1162
1261
  scope: 'milestone',
1163
1262
  id: activeMilestone.id,
1164
1263
  status: activeMilestone.status,
1165
1264
  action: nextForMilestone(activeMilestone),
1166
1265
  revision: activeMilestone.revision,
1266
+ ...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
1167
1267
  };
1168
1268
  }
1169
1269
  const activeDiscovery = status.discoveries.find((discovery) => !['materialized', 'abandoned'].includes(discovery.status));
@@ -1178,6 +1278,51 @@ export class WorkflowService {
1178
1278
  }
1179
1279
  return { scope: 'repository', status: 'idle', action: 'discovery start' };
1180
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
+ }
1181
1326
  assertExecutionAuthorizationFresh(task) {
1182
1327
  const root = this.store.taskRoot(task.projectId, task.id);
1183
1328
  const authorization = [...task.authorizations]