codex-workflow-v2 2.0.0-alpha.1

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 (90) hide show
  1. package/README.md +71 -0
  2. package/dist/src/artifacts.d.ts +4 -0
  3. package/dist/src/artifacts.js +106 -0
  4. package/dist/src/artifacts.js.map +1 -0
  5. package/dist/src/cli.d.ts +2 -0
  6. package/dist/src/cli.js +356 -0
  7. package/dist/src/cli.js.map +1 -0
  8. package/dist/src/contracts.d.ts +313 -0
  9. package/dist/src/contracts.js +3 -0
  10. package/dist/src/contracts.js.map +1 -0
  11. package/dist/src/diagnostics.d.ts +19 -0
  12. package/dist/src/diagnostics.js +25 -0
  13. package/dist/src/diagnostics.js.map +1 -0
  14. package/dist/src/domain/discovery.d.ts +8 -0
  15. package/dist/src/domain/discovery.js +25 -0
  16. package/dist/src/domain/discovery.js.map +1 -0
  17. package/dist/src/domain/validation.d.ts +3 -0
  18. package/dist/src/domain/validation.js +96 -0
  19. package/dist/src/domain/validation.js.map +1 -0
  20. package/dist/src/errors.d.ts +6 -0
  21. package/dist/src/errors.js +11 -0
  22. package/dist/src/errors.js.map +1 -0
  23. package/dist/src/fs-utils.d.ts +4 -0
  24. package/dist/src/fs-utils.js +52 -0
  25. package/dist/src/fs-utils.js.map +1 -0
  26. package/dist/src/git.d.ts +19 -0
  27. package/dist/src/git.js +115 -0
  28. package/dist/src/git.js.map +1 -0
  29. package/dist/src/graph.d.ts +28 -0
  30. package/dist/src/graph.js +263 -0
  31. package/dist/src/graph.js.map +1 -0
  32. package/dist/src/index.d.ts +12 -0
  33. package/dist/src/index.js +13 -0
  34. package/dist/src/index.js.map +1 -0
  35. package/dist/src/memory.d.ts +9 -0
  36. package/dist/src/memory.js +242 -0
  37. package/dist/src/memory.js.map +1 -0
  38. package/dist/src/migration.d.ts +46 -0
  39. package/dist/src/migration.js +112 -0
  40. package/dist/src/migration.js.map +1 -0
  41. package/dist/src/repository.d.ts +12 -0
  42. package/dist/src/repository.js +69 -0
  43. package/dist/src/repository.js.map +1 -0
  44. package/dist/src/reviewer.d.ts +82 -0
  45. package/dist/src/reviewer.js +184 -0
  46. package/dist/src/reviewer.js.map +1 -0
  47. package/dist/src/state/lock.d.ts +13 -0
  48. package/dist/src/state/lock.js +100 -0
  49. package/dist/src/state/lock.js.map +1 -0
  50. package/dist/src/state/store.d.ts +45 -0
  51. package/dist/src/state/store.js +185 -0
  52. package/dist/src/state/store.js.map +1 -0
  53. package/dist/src/ulid.d.ts +2 -0
  54. package/dist/src/ulid.js +22 -0
  55. package/dist/src/ulid.js.map +1 -0
  56. package/dist/src/version.d.ts +2 -0
  57. package/dist/src/version.js +3 -0
  58. package/dist/src/version.js.map +1 -0
  59. package/dist/src/workflow.d.ts +93 -0
  60. package/dist/src/workflow.js +1276 -0
  61. package/dist/src/workflow.js.map +1 -0
  62. package/docs/decisions.md +38 -0
  63. package/docs/development-flow.md +69 -0
  64. package/docs/project-memory.md +38 -0
  65. package/docs/release.md +26 -0
  66. package/docs/validation-report.md +19 -0
  67. package/package.json +56 -0
  68. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +25 -0
  69. package/plugins/codex-workflow-gateway/references/protocol.md +66 -0
  70. package/plugins/codex-workflow-gateway/scripts/install-or-update.sh +26 -0
  71. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +72 -0
  72. package/references/discovery.md +12 -0
  73. package/references/git-policy.md +7 -0
  74. package/references/state-machine.md +11 -0
  75. package/references/validation-and-review.md +13 -0
  76. package/roles/delivery-coordinator.md +10 -0
  77. package/roles/independent-reviewer.md +10 -0
  78. package/roles/scope-lead.md +11 -0
  79. package/roles/technical-planner.md +10 -0
  80. package/roles/worker.md +10 -0
  81. package/schemas/context-envelope.schema.json +58 -0
  82. package/schemas/discovery.schema.json +19 -0
  83. package/schemas/graph-binding.schema.json +30 -0
  84. package/schemas/milestone.schema.json +58 -0
  85. package/schemas/project-knowledge-map.schema.json +37 -0
  86. package/schemas/review-result.schema.json +30 -0
  87. package/schemas/task.schema.json +39 -0
  88. package/templates/brief.md +21 -0
  89. package/templates/plan.md +17 -0
  90. package/templates/result.md +13 -0
@@ -0,0 +1,1276 @@
1
+ import { appendFileSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION, } from './contracts.js';
4
+ import { renderBrief, renderPlan, renderResult } from './artifacts.js';
5
+ import { assessDiscovery, mergeUnique } from './domain/discovery.js';
6
+ import { pathAllowed, validatePlan } from './domain/validation.js';
7
+ import { WorkflowError } from './errors.js';
8
+ import { bindGraphEvidence, createGraphRefreshRequest, fallbackGraphBinding, inspectGraphBinding, validateGraphRequestCurrent, } from './graph.js';
9
+ import { applyKnowledgeSelections, hashKnowledgeMap, inspectKnowledgeMap, normalizeRelativePath, reconcileKnowledgeMap, scanProjectKnowledge, selectKnowledgeSources, } from './memory.js';
10
+ import { commitStep, mergeTaskBranch, runChecks, startLocalTaskBranch, syncBaseIntoTask, validateTaskHistory, } from './git.js';
11
+ import { changedFiles, currentBranch, gitIsAncestor, headCommit, isClean, resolveRepositoryIdentity, runGit, } from './repository.js';
12
+ import { WriterLockManager } from './state/lock.js';
13
+ import { FileStateStore } from './state/store.js';
14
+ import { createEntityId } from './ulid.js';
15
+ import { PACKAGE_VERSION } from './version.js';
16
+ export class WorkflowService {
17
+ store;
18
+ now;
19
+ constructor(store = new FileStateStore(), now = () => new Date()) {
20
+ this.store = store;
21
+ this.now = now;
22
+ }
23
+ context(repository) {
24
+ const identity = resolveRepositoryIdentity(repository);
25
+ this.store.registerProject(identity);
26
+ return {
27
+ identity,
28
+ store: this.store,
29
+ locks: new WriterLockManager(this.store.lockRoot(identity.projectId), this.now),
30
+ };
31
+ }
32
+ startDiscovery(repository, input) {
33
+ if (!input.goal.trim())
34
+ throw new WorkflowError('INVALID_ARGUMENT', 'Discovery requires a substantive goal.');
35
+ const { identity } = this.context(repository);
36
+ const timestamp = this.now().toISOString();
37
+ const initial = {
38
+ schemaVersion: STATE_SCHEMA_VERSION,
39
+ revision: 0,
40
+ id: createEntityId('DISC', this.now().getTime()),
41
+ projectId: identity.projectId,
42
+ createdAt: timestamp,
43
+ updatedAt: timestamp,
44
+ kind: 'discovery',
45
+ status: 'needs_discovery',
46
+ goal: input.goal.trim(),
47
+ outcome: input.outcome?.trim() || null,
48
+ facts: mergeUnique([], input.facts ?? []),
49
+ decisions: mergeUnique([], input.decisions ?? []),
50
+ assumptions: mergeUnique([], input.assumptions ?? []),
51
+ unknowns: mergeUnique([], input.unknowns ?? []),
52
+ blockingUnknowns: mergeUnique([], input.blockingUnknowns ?? []),
53
+ scope: mergeUnique([], input.scope ?? []),
54
+ outOfScope: mergeUnique([], input.outOfScope ?? []),
55
+ constraints: mergeUnique([], input.constraints ?? []),
56
+ acceptance: mergeUnique([], input.acceptance ?? []),
57
+ materializedEntityId: null,
58
+ };
59
+ initial.status = assessDiscovery(initial).status;
60
+ return this.store.writeDiscovery(initial, null);
61
+ }
62
+ updateDiscovery(repository, discoveryId, expectedRevision, patch) {
63
+ const { identity } = this.context(repository);
64
+ const current = this.store.readDiscovery(identity.projectId, discoveryId);
65
+ if (current.status === 'materialized' || current.status === 'abandoned') {
66
+ throw new WorkflowError('TRANSITION_BLOCKED', `Discovery ${discoveryId} is ${current.status}.`);
67
+ }
68
+ const resolved = new Set(patch.resolveUnknowns ?? []);
69
+ const updated = {
70
+ ...current,
71
+ goal: patch.goal?.trim() || current.goal,
72
+ outcome: patch.outcome?.trim() || current.outcome,
73
+ facts: mergeUnique(current.facts, patch.facts ?? []),
74
+ decisions: mergeUnique(current.decisions, patch.decisions ?? []),
75
+ assumptions: mergeUnique(current.assumptions, patch.assumptions ?? []),
76
+ unknowns: mergeUnique(current.unknowns, patch.unknowns ?? []).filter((value) => !resolved.has(value)),
77
+ blockingUnknowns: mergeUnique(current.blockingUnknowns, patch.blockingUnknowns ?? []).filter((value) => !resolved.has(value)),
78
+ scope: mergeUnique(current.scope, patch.scope ?? []),
79
+ outOfScope: mergeUnique(current.outOfScope, patch.outOfScope ?? []),
80
+ constraints: mergeUnique(current.constraints, patch.constraints ?? []),
81
+ acceptance: mergeUnique(current.acceptance, patch.acceptance ?? []),
82
+ };
83
+ updated.status = assessDiscovery(updated).status;
84
+ return this.store.writeDiscovery(updated, expectedRevision);
85
+ }
86
+ materializeTask(repository, discoveryId, expectedRevision, title, baseBranch = 'main', milestoneId = null) {
87
+ const { identity } = this.context(repository);
88
+ const discovery = this.store.readDiscovery(identity.projectId, discoveryId);
89
+ assertExpectedRevision(discovery, expectedRevision);
90
+ const readiness = assessDiscovery(discovery);
91
+ if (!readiness.ready || discovery.status !== 'ready_to_materialize') {
92
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Discovery is not ready to materialize.', {
93
+ missing: readiness.missing,
94
+ });
95
+ }
96
+ if (!title.trim())
97
+ throw new WorkflowError('INVALID_ARGUMENT', 'Task title is required.');
98
+ if (milestoneId) {
99
+ const milestone = this.store.readMilestone(identity.projectId, milestoneId);
100
+ if (milestone.status === 'accepted' || milestone.status === 'cancelled') {
101
+ throw new WorkflowError('TRANSITION_BLOCKED', `Milestone ${milestoneId} is ${milestone.status}.`);
102
+ }
103
+ if (milestone.baseBranch !== baseBranch) {
104
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Task and Milestone must use the same base branch.', {
105
+ taskBaseBranch: baseBranch,
106
+ milestoneBaseBranch: milestone.baseBranch,
107
+ });
108
+ }
109
+ }
110
+ const taskId = createEntityId('TASK', this.now().getTime());
111
+ const taskRoot = this.store.taskRoot(identity.projectId, taskId);
112
+ const briefHash = this.store.writeArtifact(taskRoot, 'brief.md', renderBrief(discovery, title.trim()));
113
+ const timestamp = this.now().toISOString();
114
+ const task = {
115
+ schemaVersion: STATE_SCHEMA_VERSION,
116
+ revision: 0,
117
+ id: taskId,
118
+ projectId: identity.projectId,
119
+ createdAt: timestamp,
120
+ updatedAt: timestamp,
121
+ kind: 'task',
122
+ status: 'planning',
123
+ title: title.trim(),
124
+ slug: slugify(title),
125
+ discoveryId,
126
+ milestoneId,
127
+ repositoryRoot: identity.repositoryRoot,
128
+ baseBranch,
129
+ baseCommit: null,
130
+ taskBranch: null,
131
+ workspaceOwner: null,
132
+ briefHash,
133
+ planHash: null,
134
+ resultHash: null,
135
+ evidenceHash: null,
136
+ requirements: discovery.acceptance.map((_, index) => `REQ-${String(index + 1).padStart(3, '0')}`),
137
+ acceptance: discovery.acceptance.map((_, index) => `AC-${String(index + 1).padStart(3, '0')}`),
138
+ risks: discovery.assumptions,
139
+ knowledgeImpact: null,
140
+ knowledgeImpactReason: null,
141
+ knowledgeTargets: [],
142
+ knowledgeMapRevision: null,
143
+ knowledgeMapHash: null,
144
+ graphUse: null,
145
+ steps: [],
146
+ authorizations: [],
147
+ review: emptyReview(),
148
+ blockReason: null,
149
+ mergedCommit: null,
150
+ systemCommits: [],
151
+ };
152
+ const savedTask = this.store.writeTask(task, null);
153
+ const savedDiscovery = this.store.writeDiscovery({ ...discovery, status: 'materialized', materializedEntityId: taskId }, expectedRevision);
154
+ return { discovery: savedDiscovery, task: savedTask, taskRoot };
155
+ }
156
+ materializeMilestone(repository, discoveryId, expectedRevision, title, baseBranch = 'main') {
157
+ const { identity } = this.context(repository);
158
+ const discovery = this.store.readDiscovery(identity.projectId, discoveryId);
159
+ assertExpectedRevision(discovery, expectedRevision);
160
+ const readiness = assessDiscovery(discovery);
161
+ if (!readiness.ready || discovery.status !== 'ready_to_materialize') {
162
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Discovery is not ready to materialize.', {
163
+ missing: readiness.missing,
164
+ });
165
+ }
166
+ if (!title.trim())
167
+ throw new WorkflowError('INVALID_ARGUMENT', 'Milestone title is required.');
168
+ const id = createEntityId('MS', this.now().getTime());
169
+ const timestamp = this.now().toISOString();
170
+ const milestone = {
171
+ schemaVersion: STATE_SCHEMA_VERSION,
172
+ revision: 0,
173
+ id,
174
+ projectId: identity.projectId,
175
+ createdAt: timestamp,
176
+ updatedAt: timestamp,
177
+ kind: 'milestone',
178
+ status: 'planning',
179
+ title: title.trim(),
180
+ discoveryId,
181
+ outcome: discovery.outcome ?? '',
182
+ successSignal: '',
183
+ acceptance: discovery.acceptance,
184
+ baseBranch,
185
+ taskIds: [],
186
+ memberships: [],
187
+ checks: [],
188
+ membershipRevision: 0,
189
+ authorizations: [],
190
+ planHash: null,
191
+ resultHash: null,
192
+ evidenceHash: null,
193
+ acceptedHead: null,
194
+ cancellationReason: null,
195
+ };
196
+ const savedMilestone = this.store.writeMilestone(milestone, null);
197
+ const savedDiscovery = this.store.writeDiscovery({ ...discovery, status: 'materialized', materializedEntityId: id }, expectedRevision);
198
+ return {
199
+ discovery: savedDiscovery,
200
+ milestone: savedMilestone,
201
+ milestoneRoot: this.store.milestoneRoot(identity.projectId, id),
202
+ };
203
+ }
204
+ setMilestonePlan(repository, milestoneId, expectedRevision, plan) {
205
+ const { identity } = this.context(repository);
206
+ const milestone = this.store.readMilestone(identity.projectId, milestoneId);
207
+ assertExpectedRevision(milestone, expectedRevision);
208
+ if (!['planning', 'awaiting_execution_authorization', 'active', 'blocked'].includes(milestone.status)) {
209
+ throw new WorkflowError('TRANSITION_BLOCKED', `Cannot update Milestone Plan while ${milestone.status}.`);
210
+ }
211
+ validateMilestonePlan(plan);
212
+ const taskIds = plan.memberships.map((membership) => membership.taskId);
213
+ if (new Set(taskIds).size !== taskIds.length) {
214
+ throw new WorkflowError('INVALID_ARGUMENT', 'Milestone memberships must have unique Task IDs.');
215
+ }
216
+ for (const membership of plan.memberships) {
217
+ const task = this.store.readTask(identity.projectId, membership.taskId);
218
+ if (task.milestoneId !== milestoneId) {
219
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} is not linked to Milestone ${milestoneId}.`);
220
+ }
221
+ }
222
+ const nextIds = new Set(taskIds);
223
+ for (const existing of milestone.memberships) {
224
+ if (nextIds.has(existing.taskId))
225
+ continue;
226
+ const task = this.store.readTask(identity.projectId, existing.taskId);
227
+ if (taskHasStarted(task)) {
228
+ throw new WorkflowError('TRANSITION_BLOCKED', `Started Task ${task.id} cannot be removed from a Milestone.`);
229
+ }
230
+ }
231
+ const root = this.store.milestoneRoot(identity.projectId, milestoneId);
232
+ const normalized = {
233
+ outcome: plan.outcome.trim(),
234
+ successSignal: plan.successSignal.trim(),
235
+ acceptance: [...plan.acceptance],
236
+ memberships: plan.memberships.map((membership) => ({
237
+ ...membership,
238
+ reason: membership.reason.trim(),
239
+ })),
240
+ checks: [...plan.checks],
241
+ };
242
+ const stagedPlan = this.store.stageArtifact(root, 'plan.json', JSON.stringify(normalized, null, 2));
243
+ const authorizations = milestone.authorizations.map((authorization) => authorization.kind === 'execution' && authorization.decision === 'approved'
244
+ ? { ...authorization, decision: 'superseded' }
245
+ : authorization);
246
+ const saved = this.store.writeMilestone({
247
+ ...milestone,
248
+ status: 'awaiting_execution_authorization',
249
+ outcome: normalized.outcome,
250
+ successSignal: normalized.successSignal,
251
+ acceptance: normalized.acceptance,
252
+ taskIds,
253
+ memberships: normalized.memberships,
254
+ checks: normalized.checks,
255
+ membershipRevision: milestone.membershipRevision + 1,
256
+ authorizations,
257
+ planHash: stagedPlan.hash,
258
+ resultHash: null,
259
+ evidenceHash: null,
260
+ acceptedHead: null,
261
+ cancellationReason: null,
262
+ }, expectedRevision);
263
+ this.store.publishArtifact(stagedPlan);
264
+ return saved;
265
+ }
266
+ authorizeMilestone(repository, milestoneId, expectedRevision, actor, reason = '') {
267
+ const { identity } = this.context(repository);
268
+ const milestone = this.store.readMilestone(identity.projectId, milestoneId);
269
+ assertExpectedRevision(milestone, expectedRevision);
270
+ if (milestone.status !== 'awaiting_execution_authorization' || !milestone.planHash) {
271
+ throw new WorkflowError('TRANSITION_BLOCKED', `Milestone ${milestoneId} is not awaiting authorization.`);
272
+ }
273
+ const root = this.store.milestoneRoot(identity.projectId, milestoneId);
274
+ const planHash = this.store.hashArtifact(root, 'plan.json');
275
+ if (planHash !== milestone.planHash) {
276
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone Plan changed after it was recorded.');
277
+ }
278
+ const event = {
279
+ kind: 'execution',
280
+ decision: 'approved',
281
+ actor: actor.trim() || 'user',
282
+ recordedAt: this.now().toISOString(),
283
+ briefHash: null,
284
+ planHash,
285
+ resultHash: null,
286
+ evidenceHash: null,
287
+ headCommit: runGit(identity.repositoryRoot, ['rev-parse', milestone.baseBranch]),
288
+ reason,
289
+ };
290
+ return this.store.writeMilestone({ ...milestone, status: 'active', planHash, authorizations: [...milestone.authorizations, event] }, expectedRevision);
291
+ }
292
+ validateMilestone(repository, milestoneId, expectedRevision) {
293
+ const { identity } = this.context(repository);
294
+ const milestone = this.store.readMilestone(identity.projectId, milestoneId);
295
+ assertExpectedRevision(milestone, expectedRevision);
296
+ if (milestone.status !== 'active') {
297
+ throw new WorkflowError('TRANSITION_BLOCKED', `Milestone ${milestoneId} is not active.`);
298
+ }
299
+ this.assertMilestoneAuthorizationFresh(milestone);
300
+ if (currentBranch(identity.repositoryRoot) !== milestone.baseBranch || !isClean(identity.repositoryRoot)) {
301
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Milestone validation requires a clean base branch checkout.', {
302
+ expectedBranch: milestone.baseBranch,
303
+ actualBranch: currentBranch(identity.repositoryRoot),
304
+ changedFiles: changedFiles(identity.repositoryRoot),
305
+ });
306
+ }
307
+ const incomplete = milestone.memberships.filter((membership) => {
308
+ if (membership.disposition !== 'required')
309
+ return false;
310
+ return this.store.readTask(identity.projectId, membership.taskId).status !== 'merged';
311
+ });
312
+ if (incomplete.length > 0) {
313
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Required Milestone Tasks are not merged.', {
314
+ taskIds: incomplete.map((membership) => membership.taskId),
315
+ });
316
+ }
317
+ const checks = runChecks(identity.repositoryRoot, milestone.checks);
318
+ const baseHead = headCommit(identity.repositoryRoot);
319
+ const root = this.store.milestoneRoot(identity.projectId, milestoneId);
320
+ const stagedEvidence = this.store.stageArtifact(root, 'evidence.json', JSON.stringify({
321
+ milestoneId,
322
+ baseBranch: milestone.baseBranch,
323
+ headCommit: baseHead,
324
+ generatedAt: this.now().toISOString(),
325
+ membershipRevision: milestone.membershipRevision,
326
+ memberships: milestone.memberships,
327
+ checks,
328
+ }, null, 2));
329
+ if (checks.some((check) => !check.passed)) {
330
+ const saved = this.store.writeMilestone({ ...milestone, status: 'blocked', evidenceHash: stagedEvidence.hash }, expectedRevision);
331
+ this.store.publishArtifact(stagedEvidence);
332
+ return saved;
333
+ }
334
+ const result = [
335
+ `# Milestone result: ${milestone.title}`,
336
+ '',
337
+ `Outcome: ${milestone.outcome}`,
338
+ '',
339
+ `Validated base: ${milestone.baseBranch}@${baseHead}`,
340
+ '',
341
+ 'All required memberships are merged and all Milestone checks passed.',
342
+ '',
343
+ ].join('\n');
344
+ const stagedResult = this.store.stageArtifact(root, 'result.md', result);
345
+ const saved = this.store.writeMilestone({
346
+ ...milestone,
347
+ status: 'awaiting_final_acceptance',
348
+ evidenceHash: stagedEvidence.hash,
349
+ resultHash: stagedResult.hash,
350
+ }, expectedRevision);
351
+ this.store.publishArtifact(stagedEvidence);
352
+ this.store.publishArtifact(stagedResult);
353
+ return saved;
354
+ }
355
+ acceptMilestone(repository, milestoneId, expectedRevision, actor) {
356
+ const { identity } = this.context(repository);
357
+ const milestone = this.store.readMilestone(identity.projectId, milestoneId);
358
+ assertExpectedRevision(milestone, expectedRevision);
359
+ if (milestone.status !== 'awaiting_final_acceptance' || !milestone.resultHash || !milestone.evidenceHash) {
360
+ throw new WorkflowError('TRANSITION_BLOCKED', `Milestone ${milestoneId} is not ready for acceptance.`);
361
+ }
362
+ if (currentBranch(identity.repositoryRoot) !== milestone.baseBranch || !isClean(identity.repositoryRoot)) {
363
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Milestone acceptance requires a clean base branch checkout.');
364
+ }
365
+ const root = this.store.milestoneRoot(identity.projectId, milestoneId);
366
+ const evidence = JSON.parse(readFileSync(path.join(root, 'evidence.json'), 'utf8'));
367
+ const currentHead = headCommit(identity.repositoryRoot);
368
+ const resultHash = this.store.hashArtifact(root, 'result.md');
369
+ const evidenceHash = this.store.hashArtifact(root, 'evidence.json');
370
+ if (evidence.headCommit !== currentHead || resultHash !== milestone.resultHash || evidenceHash !== milestone.evidenceHash) {
371
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone validation evidence is stale.');
372
+ }
373
+ const event = {
374
+ kind: 'final_acceptance',
375
+ decision: 'approved',
376
+ actor: actor.trim() || 'user',
377
+ recordedAt: this.now().toISOString(),
378
+ briefHash: null,
379
+ planHash: milestone.planHash,
380
+ resultHash,
381
+ evidenceHash,
382
+ headCommit: currentHead,
383
+ reason: '',
384
+ };
385
+ return this.store.writeMilestone({
386
+ ...milestone,
387
+ status: 'accepted',
388
+ resultHash,
389
+ evidenceHash,
390
+ acceptedHead: currentHead,
391
+ authorizations: [...milestone.authorizations, event],
392
+ }, expectedRevision);
393
+ }
394
+ cancelMilestone(repository, milestoneId, expectedRevision, reason) {
395
+ if (!reason.trim())
396
+ throw new WorkflowError('INVALID_ARGUMENT', 'Milestone cancellation requires a reason.');
397
+ const { identity } = this.context(repository);
398
+ const milestone = this.store.readMilestone(identity.projectId, milestoneId);
399
+ assertExpectedRevision(milestone, expectedRevision);
400
+ if (['accepted', 'cancelled'].includes(milestone.status)) {
401
+ throw new WorkflowError('TRANSITION_BLOCKED', `Milestone ${milestoneId} is ${milestone.status}.`);
402
+ }
403
+ return this.store.writeMilestone({ ...milestone, status: 'cancelled', cancellationReason: reason.trim() }, expectedRevision);
404
+ }
405
+ setTaskPlan(repository, taskId, expectedRevision, plan) {
406
+ validatePlan(plan);
407
+ const { identity } = this.context(repository);
408
+ const knowledgeMap = this.assertKnowledgeMapBinding(identity.repositoryRoot, identity.projectId, plan.knowledgeMapRevision, plan.knowledgeMapHash);
409
+ this.validateKnowledgeTargets(knowledgeMap, plan);
410
+ const task = this.store.readTask(identity.projectId, taskId);
411
+ assertExpectedRevision(task, expectedRevision);
412
+ if (!['planning', 'awaiting_execution_authorization', 'needs_fix'].includes(task.status)) {
413
+ throw new WorkflowError('TRANSITION_BLOCKED', `Cannot update Plan while Task is ${task.status}.`);
414
+ }
415
+ for (const requirement of task.requirements) {
416
+ if (!plan.requirements.includes(requirement)) {
417
+ throw new WorkflowError('TRANSITION_BLOCKED', `Plan omits Task requirement ${requirement}.`);
418
+ }
419
+ }
420
+ for (const acceptance of task.acceptance) {
421
+ if (!plan.acceptance.includes(acceptance)) {
422
+ throw new WorkflowError('TRANSITION_BLOCKED', `Plan omits Task acceptance ${acceptance}.`);
423
+ }
424
+ }
425
+ const root = this.store.taskRoot(identity.projectId, taskId);
426
+ const stagedPlan = this.store.stageArtifact(root, 'plan.md', renderPlan(task, plan));
427
+ const authorizations = task.authorizations.map((authorization) => authorization.kind === 'execution' && authorization.decision === 'approved'
428
+ ? { ...authorization, decision: 'superseded' }
429
+ : authorization);
430
+ const completedIds = task.steps
431
+ .filter((candidate) => candidate.status === 'completed')
432
+ .map((candidate) => candidate.id);
433
+ const plannedSteps = plan.steps.map((step) => preserveCompletedStep(task, step) ?? ({
434
+ ...step,
435
+ status: dependenciesComplete(step, completedIds) ? 'planned' : 'blocked',
436
+ evidence: null,
437
+ }));
438
+ if (task.status === 'needs_fix' &&
439
+ task.review.status === 'failed' &&
440
+ plannedSteps.every((step) => step.status === 'completed')) {
441
+ throw new WorkflowError('TRANSITION_BLOCKED', 'A failed review requires at least one new or changed remediation Step.');
442
+ }
443
+ const saved = this.store.writeTask({
444
+ ...task,
445
+ status: 'awaiting_execution_authorization',
446
+ planHash: stagedPlan.hash,
447
+ requirements: plan.requirements,
448
+ acceptance: plan.acceptance,
449
+ risks: plan.risks,
450
+ knowledgeImpact: plan.knowledgeImpact,
451
+ knowledgeImpactReason: plan.knowledgeImpactReason,
452
+ knowledgeTargets: plan.knowledgeTargets,
453
+ knowledgeMapRevision: plan.knowledgeMapRevision,
454
+ knowledgeMapHash: plan.knowledgeMapHash,
455
+ graphUse: plan.graphUse,
456
+ steps: plannedSteps,
457
+ authorizations,
458
+ review: emptyReview(),
459
+ resultHash: null,
460
+ evidenceHash: null,
461
+ blockReason: null,
462
+ }, expectedRevision);
463
+ this.store.publishArtifact(stagedPlan);
464
+ return saved;
465
+ }
466
+ authorizeTask(repository, taskId, expectedRevision, actor, reason = '') {
467
+ const { identity } = this.context(repository);
468
+ const task = this.store.readTask(identity.projectId, taskId);
469
+ assertExpectedRevision(task, expectedRevision);
470
+ if (task.status !== 'awaiting_execution_authorization' || !task.planHash) {
471
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} is not awaiting execution authorization.`);
472
+ }
473
+ this.assertTaskKnowledgeBinding(identity.repositoryRoot, task);
474
+ const root = this.store.taskRoot(identity.projectId, taskId);
475
+ const briefHash = this.store.hashArtifact(root, 'brief.md');
476
+ const planHash = this.store.hashArtifact(root, 'plan.md');
477
+ const event = {
478
+ kind: 'execution',
479
+ decision: 'approved',
480
+ actor: actor.trim() || 'user',
481
+ recordedAt: this.now().toISOString(),
482
+ briefHash,
483
+ planHash,
484
+ resultHash: null,
485
+ evidenceHash: null,
486
+ headCommit: null,
487
+ reason,
488
+ };
489
+ return this.store.writeTask({ ...task, status: 'ready', briefHash, planHash, authorizations: [...task.authorizations, event] }, expectedRevision);
490
+ }
491
+ startTask(repository, taskId, expectedRevision, workspaceOwner) {
492
+ const { identity } = this.context(repository);
493
+ const task = this.store.readTask(identity.projectId, taskId);
494
+ assertExpectedRevision(task, expectedRevision);
495
+ if (task.status !== 'ready')
496
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} is ${task.status}.`);
497
+ this.assertExecutionAuthorizationFresh(task);
498
+ if (task.milestoneId) {
499
+ const milestone = this.store.readMilestone(identity.projectId, task.milestoneId);
500
+ const membership = milestone.memberships.find((candidate) => candidate.taskId === taskId);
501
+ if (milestone.status !== 'active' || membership?.disposition !== 'required') {
502
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone-linked Task requires an active, authorized membership.', {
503
+ milestoneId: milestone.id,
504
+ milestoneStatus: milestone.status,
505
+ disposition: membership?.disposition ?? null,
506
+ });
507
+ }
508
+ }
509
+ if (!isClean(identity.repositoryRoot)) {
510
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Task start requires a clean checkout.', {
511
+ changedFiles: changedFiles(identity.repositoryRoot),
512
+ });
513
+ }
514
+ const branch = `codex/task-${task.id.slice(5, 15).toLowerCase()}-${task.slug}`;
515
+ let baseCommit;
516
+ let taskBranch;
517
+ if (workspaceOwner === 'local') {
518
+ baseCommit = startLocalTaskBranch(identity.repositoryRoot, branch, task.baseBranch);
519
+ taskBranch = branch;
520
+ }
521
+ else {
522
+ baseCommit = runGit(identity.repositoryRoot, ['rev-parse', task.baseBranch]);
523
+ taskBranch = currentBranch(identity.repositoryRoot);
524
+ if (!taskBranch || taskBranch === task.baseBranch) {
525
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', `Externally owned execution requires a dedicated branch different from ${task.baseBranch}.`);
526
+ }
527
+ if (headCommit(identity.repositoryRoot) !== baseCommit) {
528
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Externally owned execution branch must start at the recorded base commit.', { baseCommit, headCommit: headCommit(identity.repositoryRoot) });
529
+ }
530
+ }
531
+ return this.store.writeTask({ ...task, baseCommit, taskBranch, workspaceOwner, blockReason: null }, expectedRevision);
532
+ }
533
+ runStep(repository, taskId, stepId, expectedRevision, owner, writerToken) {
534
+ const context = this.context(repository);
535
+ const task = this.store.readTask(context.identity.projectId, taskId);
536
+ assertExpectedRevision(task, expectedRevision);
537
+ if (!['ready', 'in_progress', 'needs_fix'].includes(task.status)) {
538
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} cannot run a Step while ${task.status}.`);
539
+ }
540
+ this.assertExecutionAuthorizationFresh(task);
541
+ const knowledgeMap = this.assertTaskKnowledgeBinding(context.identity.repositoryRoot, task);
542
+ assertTaskBranch(context.identity.repositoryRoot, task);
543
+ const step = requireStep(task, stepId);
544
+ if (!['planned', 'failed'].includes(step.status)) {
545
+ throw new WorkflowError('TRANSITION_BLOCKED', `${stepId} cannot start from ${step.status}.`);
546
+ }
547
+ const dirty = changedFiles(context.identity.repositoryRoot);
548
+ if (step.status === 'planned' && dirty.length > 0) {
549
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} must start from a clean checkout.`, {
550
+ changedFiles: dirty,
551
+ });
552
+ }
553
+ const dirtyOutsideStep = dirty.filter((file) => !pathAllowed(file, step.allowedWrites));
554
+ if (step.status === 'failed' && dirtyOutsideStep.length > 0) {
555
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} retry has changes outside allowedWrites.`, {
556
+ changedFiles: dirtyOutsideStep,
557
+ });
558
+ }
559
+ const incompleteDependencies = step.dependencies.filter((dependency) => requireStep(task, dependency).status !== 'completed');
560
+ if (incompleteDependencies.length > 0) {
561
+ throw new WorkflowError('TRANSITION_BLOCKED', `${stepId} has incomplete dependencies.`, {
562
+ incompleteDependencies,
563
+ });
564
+ }
565
+ const existing = context.locks.inspect(taskId);
566
+ let acquiredHere = false;
567
+ const lease = existing
568
+ ? writerToken
569
+ ? context.locks.heartbeat(taskId, writerToken)
570
+ : (() => {
571
+ throw new WorkflowError('LOCKED', `${taskId} already has a writer.`, { owner: existing.owner });
572
+ })()
573
+ : (() => {
574
+ acquiredHere = true;
575
+ return context.locks.acquire(taskId, owner.trim() || 'worker');
576
+ })();
577
+ const steps = task.steps.map((candidate) => candidate.id === stepId ? { ...candidate, status: 'in_progress' } : candidate);
578
+ let saved;
579
+ try {
580
+ saved = this.store.writeTask({ ...task, status: 'in_progress', steps, blockReason: null }, expectedRevision);
581
+ }
582
+ catch (error) {
583
+ if (acquiredHere)
584
+ context.locks.release(taskId, lease.token);
585
+ throw error;
586
+ }
587
+ return {
588
+ task: saved,
589
+ lease,
590
+ envelope: {
591
+ protocolVersion: PROTOCOL_VERSION,
592
+ packageVersion: PACKAGE_VERSION,
593
+ role: 'worker',
594
+ objective: step.objective,
595
+ inputs: [
596
+ path.join(this.store.taskRoot(task.projectId, task.id), 'brief.md'),
597
+ path.join(this.store.taskRoot(task.projectId, task.id), 'plan.md'),
598
+ ],
599
+ allowedReads: [identityRelative(context.identity.repositoryRoot, '.')],
600
+ allowedWrites: step.allowedWrites,
601
+ commands: step.checks,
602
+ forbiddenScope: step.forbiddenScope,
603
+ resultSchema: {
604
+ type: 'object',
605
+ required: ['summary', 'changedFiles', 'checks'],
606
+ properties: {
607
+ summary: { type: 'string' },
608
+ changedFiles: { type: 'array', items: { type: 'string' } },
609
+ checks: { type: 'array', items: { type: 'string' } },
610
+ },
611
+ },
612
+ stopConditions: [
613
+ 'Required change falls outside allowedWrites.',
614
+ 'Implementation requires changing Brief, Plan, or acceptance.',
615
+ 'A configured check cannot be run or interpreted safely.',
616
+ ],
617
+ contextBudget: 'medium',
618
+ taskId,
619
+ stepId,
620
+ expectedRevision: saved.revision,
621
+ writerToken: lease.token,
622
+ knowledgeMap: knowledgeSummary(knowledgeMap),
623
+ knowledgeSources: selectKnowledgeSources(knowledgeMap, 'worker', task.knowledgeTargets.map((target) => target.path)),
624
+ },
625
+ };
626
+ }
627
+ taskContext(repository, taskId, role) {
628
+ const { identity } = this.context(repository);
629
+ const task = this.store.readTask(identity.projectId, taskId);
630
+ const knowledgeMap = task.knowledgeMapRevision === null
631
+ ? this.requireActiveKnowledgeMap(identity.repositoryRoot, identity.projectId)
632
+ : this.assertTaskKnowledgeBinding(identity.repositoryRoot, task);
633
+ const root = this.store.taskRoot(task.projectId, task.id);
634
+ const common = {
635
+ protocolVersion: PROTOCOL_VERSION,
636
+ packageVersion: PACKAGE_VERSION,
637
+ role,
638
+ inputs: [path.join(root, 'brief.md'), ...(task.planHash ? [path.join(root, 'plan.md')] : [])],
639
+ allowedReads: [identity.repositoryRoot, root],
640
+ allowedWrites: [],
641
+ commands: [],
642
+ forbiddenScope: ['Production file changes unless a Worker envelope explicitly permits them.'],
643
+ stopConditions: ['A required decision is absent from the approved Brief or Plan.'],
644
+ contextBudget: 'medium',
645
+ taskId,
646
+ stepId: 'STEP-000-CONTEXT',
647
+ expectedRevision: task.revision,
648
+ writerToken: null,
649
+ knowledgeMap: knowledgeSummary(knowledgeMap),
650
+ knowledgeSources: selectKnowledgeSources(knowledgeMap, role, task.knowledgeTargets.map((target) => target.path)),
651
+ };
652
+ if (role === 'technical-planner') {
653
+ return {
654
+ ...common,
655
+ objective: `Produce an execution-ready Plan for ${task.title}.`,
656
+ resultSchema: {
657
+ type: 'object',
658
+ required: ['objective', 'requirements', 'acceptance', 'risks', 'steps'],
659
+ },
660
+ };
661
+ }
662
+ if (role === 'independent-reviewer') {
663
+ return {
664
+ ...common,
665
+ objective: `Review ${task.title} against Brief, Plan, diff, checks, and evidence without modifying files.`,
666
+ inputs: [...common.inputs, ...(task.evidenceHash ? [path.join(root, 'evidence.json')] : [])],
667
+ resultSchema: {
668
+ type: 'object',
669
+ required: ['status', 'reviewer', 'summary', 'findings'],
670
+ },
671
+ stopConditions: [
672
+ ...common.stopConditions,
673
+ 'The checkout is dirty or the evidence does not identify the reviewed commit.',
674
+ 'Read-only reviewer isolation cannot be established; return unverified.',
675
+ ],
676
+ };
677
+ }
678
+ return {
679
+ ...common,
680
+ objective: `Coordinate the next valid state transition for ${task.title}.`,
681
+ resultSchema: { type: 'object', required: ['recommendedAction', 'reason'] },
682
+ };
683
+ }
684
+ completeStep(repository, taskId, stepId, expectedRevision, writerToken, notes) {
685
+ const context = this.context(repository);
686
+ context.locks.heartbeat(taskId, writerToken);
687
+ const task = this.store.readTask(context.identity.projectId, taskId);
688
+ assertExpectedRevision(task, expectedRevision);
689
+ assertTaskBranch(context.identity.repositoryRoot, task);
690
+ const step = requireStep(task, stepId);
691
+ if (task.status !== 'in_progress' || step.status !== 'in_progress') {
692
+ throw new WorkflowError('TRANSITION_BLOCKED', `${stepId} is not in progress.`);
693
+ }
694
+ const checks = runChecks(context.identity.repositoryRoot, step.checks);
695
+ const failed = checks.filter((check) => !check.passed);
696
+ if (failed.length > 0) {
697
+ const steps = task.steps.map((candidate) => candidate.id === stepId ? { ...candidate, status: 'failed' } : candidate);
698
+ return this.store.writeTask({ ...task, status: 'needs_fix', steps, blockReason: `${stepId} checks failed.` }, expectedRevision);
699
+ }
700
+ const committed = commitStep(context.identity.repositoryRoot, task.id, step.id, step.title, step.allowedWrites);
701
+ const evidence = {
702
+ recordedAt: this.now().toISOString(),
703
+ commitSha: committed.commitSha,
704
+ changedFiles: committed.files,
705
+ checks,
706
+ notes,
707
+ };
708
+ const completedIds = new Set([
709
+ ...task.steps.filter((candidate) => candidate.status === 'completed').map((candidate) => candidate.id),
710
+ stepId,
711
+ ]);
712
+ const steps = task.steps.map((candidate) => {
713
+ if (candidate.id === stepId)
714
+ return { ...candidate, status: 'completed', evidence };
715
+ if (candidate.status === 'blocked' && dependenciesComplete(candidate, completedIds)) {
716
+ return { ...candidate, status: 'planned' };
717
+ }
718
+ return candidate;
719
+ });
720
+ return this.store.writeTask({ ...task, status: 'in_progress', steps, blockReason: null }, expectedRevision);
721
+ }
722
+ submitTask(repository, taskId, expectedRevision, writerToken) {
723
+ const context = this.context(repository);
724
+ context.locks.heartbeat(taskId, writerToken);
725
+ const task = this.store.readTask(context.identity.projectId, taskId);
726
+ assertExpectedRevision(task, expectedRevision);
727
+ assertTaskBranch(context.identity.repositoryRoot, task);
728
+ if (!['in_progress', 'ready'].includes(task.status)) {
729
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} cannot be submitted while ${task.status}.`);
730
+ }
731
+ if (!isClean(context.identity.repositoryRoot)) {
732
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Task submission requires a clean checkout.');
733
+ }
734
+ const incomplete = task.steps.filter((step) => step.status !== 'completed' && step.status !== 'skipped');
735
+ if (incomplete.length > 0) {
736
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Task has incomplete Steps.', {
737
+ steps: incomplete.map((step) => `${step.id}:${step.status}`),
738
+ });
739
+ }
740
+ validateTaskHistory(context.identity.repositoryRoot, task);
741
+ const root = this.store.taskRoot(task.projectId, task.id);
742
+ const stagedEvidence = this.store.stageArtifact(root, 'evidence.json', JSON.stringify({
743
+ taskId: task.id,
744
+ headCommit: headCommit(context.identity.repositoryRoot),
745
+ generatedAt: this.now().toISOString(),
746
+ steps: task.steps.map((step) => ({ id: step.id, evidence: step.evidence })),
747
+ systemCommits: task.systemCommits,
748
+ }, null, 2));
749
+ const saved = this.store.writeTask({ ...task, status: 'validating', evidenceHash: stagedEvidence.hash, review: emptyReview(), blockReason: null }, expectedRevision);
750
+ this.store.publishArtifact(stagedEvidence);
751
+ return saved;
752
+ }
753
+ recordReview(repository, taskId, expectedRevision, review) {
754
+ const { identity } = this.context(repository);
755
+ const task = this.store.readTask(identity.projectId, taskId);
756
+ assertExpectedRevision(task, expectedRevision);
757
+ const retryingUnverified = task.status === 'blocked' && task.review.status === 'unverified';
758
+ if (task.status !== 'validating' && !retryingUnverified) {
759
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} is not ready for review.`);
760
+ }
761
+ validateReviewInput(review);
762
+ assertTaskBranch(identity.repositoryRoot, task);
763
+ if (!isClean(identity.repositoryRoot)) {
764
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Review requires a clean Task checkout.');
765
+ }
766
+ const root = this.store.taskRoot(task.projectId, task.id);
767
+ const recorded = {
768
+ ...review,
769
+ recordedAt: this.now().toISOString(),
770
+ headCommit: headCommit(identity.repositoryRoot),
771
+ briefHash: this.store.hashArtifact(root, 'brief.md'),
772
+ planHash: this.store.hashArtifact(root, 'plan.md'),
773
+ evidenceHash: this.store.hashArtifact(root, 'evidence.json'),
774
+ };
775
+ const status = recorded.status === 'passed' ? 'validating' : recorded.status === 'failed' ? 'needs_fix' : 'blocked';
776
+ const saved = this.store.writeTask({ ...task, status, review: recorded, blockReason: recorded.status === 'passed' ? null : recorded.summary }, expectedRevision);
777
+ if (recorded.findings.length > 0) {
778
+ const file = path.join(this.store.taskRoot(task.projectId, task.id), 'findings.jsonl');
779
+ for (const finding of recorded.findings) {
780
+ appendFileSync(file, `${JSON.stringify({ ...finding, recordedAt: recorded.recordedAt })}\n`, {
781
+ encoding: 'utf8',
782
+ mode: 0o600,
783
+ });
784
+ }
785
+ }
786
+ return saved;
787
+ }
788
+ setTaskResult(repository, taskId, expectedRevision, summary, limitations) {
789
+ const { identity } = this.context(repository);
790
+ const task = this.store.readTask(identity.projectId, taskId);
791
+ assertExpectedRevision(task, expectedRevision);
792
+ if (task.status !== 'validating' || task.review.status !== 'passed') {
793
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Result requires a passing independent review.');
794
+ }
795
+ this.assertReviewFresh(identity.repositoryRoot, task);
796
+ const root = this.store.taskRoot(task.projectId, task.id);
797
+ const stagedResult = this.store.stageArtifact(root, 'result.md', renderResult(task, summary, limitations));
798
+ const saved = this.store.writeTask({ ...task, status: 'awaiting_final_acceptance', resultHash: stagedResult.hash, blockReason: null }, expectedRevision);
799
+ this.store.publishArtifact(stagedResult);
800
+ return saved;
801
+ }
802
+ acceptTask(repository, taskId, expectedRevision, actor) {
803
+ const { identity } = this.context(repository);
804
+ const task = this.store.readTask(identity.projectId, taskId);
805
+ assertExpectedRevision(task, expectedRevision);
806
+ if (task.status !== 'awaiting_final_acceptance' || !task.resultHash || !task.evidenceHash) {
807
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} is not ready for final acceptance.`);
808
+ }
809
+ const root = this.store.taskRoot(task.projectId, task.id);
810
+ assertTaskBranch(identity.repositoryRoot, task);
811
+ if (!isClean(identity.repositoryRoot)) {
812
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Final acceptance requires a clean Task checkout.');
813
+ }
814
+ this.assertReviewFresh(identity.repositoryRoot, task);
815
+ const resultHash = this.store.hashArtifact(root, 'result.md');
816
+ const evidenceHash = this.store.hashArtifact(root, 'evidence.json');
817
+ const event = {
818
+ kind: 'final_acceptance',
819
+ decision: 'approved',
820
+ actor: actor.trim() || 'user',
821
+ recordedAt: this.now().toISOString(),
822
+ briefHash: task.briefHash,
823
+ planHash: task.planHash,
824
+ resultHash,
825
+ evidenceHash,
826
+ headCommit: headCommit(identity.repositoryRoot),
827
+ reason: '',
828
+ };
829
+ return this.store.writeTask({
830
+ ...task,
831
+ status: task.workspaceOwner === 'external' ? 'ready_to_merge' : 'merging',
832
+ resultHash,
833
+ evidenceHash,
834
+ authorizations: [...task.authorizations, event],
835
+ }, expectedRevision);
836
+ }
837
+ syncTaskBase(repository, taskId, expectedRevision) {
838
+ const { identity } = this.context(repository);
839
+ const task = this.store.readTask(identity.projectId, taskId);
840
+ assertExpectedRevision(task, expectedRevision);
841
+ if (task.status !== 'merging' || task.workspaceOwner !== 'local' || !task.taskBranch || !task.baseCommit) {
842
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Base sync is available only for an accepted local Task.');
843
+ }
844
+ const currentBase = runGit(identity.repositoryRoot, ['rev-parse', task.baseBranch]);
845
+ if (currentBase === task.baseCommit) {
846
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Base branch has not advanced; synchronization is unnecessary.');
847
+ }
848
+ const synced = syncBaseIntoTask(identity.repositoryRoot, task.taskBranch, task.baseBranch);
849
+ const checks = runChecks(identity.repositoryRoot, [...new Set(task.steps.flatMap((step) => step.checks))]);
850
+ const failed = checks.filter((check) => !check.passed);
851
+ if (failed.length > 0) {
852
+ return this.store.writeTask({
853
+ ...task,
854
+ status: 'needs_fix',
855
+ baseCommit: synced.baseCommit,
856
+ systemCommits: [...task.systemCommits, synced.syncCommit],
857
+ review: emptyReview(),
858
+ resultHash: null,
859
+ blockReason: 'Checks failed after synchronizing the base branch.',
860
+ }, expectedRevision);
861
+ }
862
+ const root = this.store.taskRoot(task.projectId, task.id);
863
+ const stagedEvidence = this.store.stageArtifact(root, 'evidence.json', JSON.stringify({
864
+ taskId: task.id,
865
+ headCommit: headCommit(identity.repositoryRoot),
866
+ generatedAt: this.now().toISOString(),
867
+ reason: 'base-sync',
868
+ checks,
869
+ steps: task.steps.map((step) => ({ id: step.id, evidence: step.evidence })),
870
+ systemCommits: [...task.systemCommits, synced.syncCommit],
871
+ }, null, 2));
872
+ const authorizations = task.authorizations.map((authorization) => authorization.kind === 'final_acceptance' && authorization.decision === 'approved'
873
+ ? { ...authorization, decision: 'superseded' }
874
+ : authorization);
875
+ const saved = this.store.writeTask({
876
+ ...task,
877
+ status: 'validating',
878
+ baseCommit: synced.baseCommit,
879
+ systemCommits: [...task.systemCommits, synced.syncCommit],
880
+ evidenceHash: stagedEvidence.hash,
881
+ review: emptyReview(),
882
+ resultHash: null,
883
+ authorizations,
884
+ blockReason: 'Base changed; validation, review, and final acceptance must be repeated.',
885
+ }, expectedRevision);
886
+ this.store.publishArtifact(stagedEvidence);
887
+ return saved;
888
+ }
889
+ mergeTask(repository, taskId, expectedRevision, writerToken) {
890
+ const context = this.context(repository);
891
+ const task = this.store.readTask(context.identity.projectId, taskId);
892
+ assertExpectedRevision(task, expectedRevision);
893
+ if (task.status !== 'merging' || task.workspaceOwner !== 'local' || !task.taskBranch || !task.baseCommit) {
894
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} is not ready for a local merge.`);
895
+ }
896
+ const currentBase = runGit(context.identity.repositoryRoot, ['rev-parse', task.baseBranch]);
897
+ if (currentBase !== task.baseCommit) {
898
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Base branch advanced after Task start.', {
899
+ recordedBase: task.baseCommit,
900
+ currentBase,
901
+ next: 'task sync-base',
902
+ });
903
+ }
904
+ const mergedCommit = mergeTaskBranch(context.identity.repositoryRoot, task.taskBranch, task.baseBranch);
905
+ const saved = this.store.writeTask({ ...task, status: 'merged', mergedCommit, blockReason: null }, expectedRevision);
906
+ context.locks.release(taskId, writerToken);
907
+ return saved;
908
+ }
909
+ confirmExternalMerge(repository, taskId, expectedRevision, writerToken) {
910
+ const context = this.context(repository);
911
+ const task = this.store.readTask(context.identity.projectId, taskId);
912
+ assertExpectedRevision(task, expectedRevision);
913
+ if (task.status !== 'ready_to_merge' || task.workspaceOwner !== 'external') {
914
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} is not awaiting external merge confirmation.`);
915
+ }
916
+ const taskHead = finalAcceptedHead(task);
917
+ if (!gitIsAncestor(context.identity.repositoryRoot, taskHead, task.baseBranch)) {
918
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', `Accepted Task head is not merged into ${task.baseBranch}.`);
919
+ }
920
+ const saved = this.store.writeTask({ ...task, status: 'merged', mergedCommit: runGit(context.identity.repositoryRoot, ['rev-parse', task.baseBranch]) }, expectedRevision);
921
+ context.locks.release(taskId, writerToken);
922
+ return saved;
923
+ }
924
+ status(repository) {
925
+ const { identity } = this.context(repository);
926
+ return {
927
+ projectId: identity.projectId,
928
+ discoveries: this.store.listDiscoveries(identity.projectId),
929
+ tasks: this.store.listTasks(identity.projectId),
930
+ milestones: this.store.listMilestones(identity.projectId),
931
+ };
932
+ }
933
+ scanKnowledgeMap(repository) {
934
+ const { identity } = this.context(repository);
935
+ const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
936
+ try {
937
+ const current = this.store.readKnowledgeMap(identity.projectId);
938
+ return inspectKnowledgeMap(current, scanned);
939
+ }
940
+ catch (error) {
941
+ if (!(error instanceof WorkflowError) || error.code !== 'NOT_FOUND')
942
+ throw error;
943
+ return this.store.writeKnowledgeMap(scanned, null);
944
+ }
945
+ }
946
+ showKnowledgeMap(repository) {
947
+ const { identity } = this.context(repository);
948
+ return this.store.readKnowledgeMap(identity.projectId);
949
+ }
950
+ knowledgeMapStatus(repository) {
951
+ const { identity } = this.context(repository);
952
+ const current = this.store.readKnowledgeMap(identity.projectId);
953
+ const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
954
+ return inspectKnowledgeMap(current, scanned);
955
+ }
956
+ approveKnowledgeMap(repository, expectedRevision, selections, actor) {
957
+ const { identity } = this.context(repository);
958
+ const current = this.store.readKnowledgeMap(identity.projectId);
959
+ assertExpectedRevision(current, expectedRevision);
960
+ const inspected = inspectKnowledgeMap(current, scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now()));
961
+ const approved = applyKnowledgeSelections(inspected, selections, actor, this.now());
962
+ return this.store.writeKnowledgeMap(approved, expectedRevision);
963
+ }
964
+ reconcileKnowledgeMap(repository, expectedRevision) {
965
+ const { identity } = this.context(repository);
966
+ const current = this.store.readKnowledgeMap(identity.projectId);
967
+ assertExpectedRevision(current, expectedRevision);
968
+ const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
969
+ return this.store.writeKnowledgeMap(reconcileKnowledgeMap(current, scanned), expectedRevision);
970
+ }
971
+ graphRefreshRequest(repository, graphKind, mode = 'moderate') {
972
+ const { identity } = this.context(repository);
973
+ return createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
974
+ }
975
+ graphStatus(repository, graphKind, mode = 'moderate') {
976
+ const { identity } = this.context(repository);
977
+ const request = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
978
+ const binding = this.readGraphBindingOrNull(identity.projectId, graphKind);
979
+ return { request, binding: binding ? inspectGraphBinding(binding, request) : null };
980
+ }
981
+ bindGraph(repository, expectedRevision, request, evidence) {
982
+ const { identity } = this.context(repository);
983
+ const currentRequest = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, request.graphKind, request.mode, this.now());
984
+ validateGraphRequestCurrent(request, currentRequest);
985
+ const existing = this.readGraphBindingOrNull(identity.projectId, request.graphKind);
986
+ const binding = bindGraphEvidence(identity.projectId, request, evidence, existing, this.now());
987
+ return this.store.writeGraphBinding(binding, expectedRevision);
988
+ }
989
+ recordGraphFallback(repository, graphKind, mode, expectedRevision, reason) {
990
+ const { identity } = this.context(repository);
991
+ const request = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
992
+ const existing = this.readGraphBindingOrNull(identity.projectId, graphKind);
993
+ return this.store.writeGraphBinding(fallbackGraphBinding(identity.projectId, request, reason, existing, this.now()), expectedRevision);
994
+ }
995
+ next(repository) {
996
+ const status = this.status(repository);
997
+ const activeTask = status.tasks.find((task) => !['merged', 'cancelled'].includes(task.status));
998
+ if (activeTask)
999
+ return nextForTask(activeTask);
1000
+ const activeMilestone = status.milestones.find((milestone) => !['accepted', 'cancelled'].includes(milestone.status));
1001
+ if (activeMilestone) {
1002
+ return {
1003
+ scope: 'milestone',
1004
+ id: activeMilestone.id,
1005
+ status: activeMilestone.status,
1006
+ action: nextForMilestone(activeMilestone),
1007
+ revision: activeMilestone.revision,
1008
+ };
1009
+ }
1010
+ const activeDiscovery = status.discoveries.find((discovery) => !['materialized', 'abandoned'].includes(discovery.status));
1011
+ if (activeDiscovery) {
1012
+ return {
1013
+ scope: 'discovery',
1014
+ id: activeDiscovery.id,
1015
+ status: activeDiscovery.status,
1016
+ action: activeDiscovery.status === 'ready_to_materialize' ? 'discovery materialize' : 'discovery update',
1017
+ readiness: assessDiscovery(activeDiscovery),
1018
+ };
1019
+ }
1020
+ return { scope: 'repository', status: 'idle', action: 'discovery start' };
1021
+ }
1022
+ assertExecutionAuthorizationFresh(task) {
1023
+ const root = this.store.taskRoot(task.projectId, task.id);
1024
+ const authorization = [...task.authorizations]
1025
+ .reverse()
1026
+ .find((event) => event.kind === 'execution' && event.decision === 'approved');
1027
+ if (!authorization)
1028
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Execution authorization is missing.');
1029
+ const briefHash = this.store.hashArtifact(root, 'brief.md');
1030
+ const planHash = this.store.hashArtifact(root, 'plan.md');
1031
+ if (authorization.briefHash !== briefHash || authorization.planHash !== planHash) {
1032
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Execution authorization is stale.', {
1033
+ authorizedBriefHash: authorization.briefHash,
1034
+ currentBriefHash: briefHash,
1035
+ authorizedPlanHash: authorization.planHash,
1036
+ currentPlanHash: planHash,
1037
+ });
1038
+ }
1039
+ }
1040
+ assertMilestoneAuthorizationFresh(milestone) {
1041
+ const authorization = [...milestone.authorizations]
1042
+ .reverse()
1043
+ .find((event) => event.kind === 'execution' && event.decision === 'approved');
1044
+ if (!authorization)
1045
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone authorization is missing.');
1046
+ const planHash = this.store.hashArtifact(this.store.milestoneRoot(milestone.projectId, milestone.id), 'plan.json');
1047
+ if (authorization.planHash !== planHash || milestone.planHash !== planHash) {
1048
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Milestone authorization is stale.');
1049
+ }
1050
+ }
1051
+ readGraphBindingOrNull(projectId, graphKind) {
1052
+ try {
1053
+ return this.store.readGraphBinding(projectId, graphKind);
1054
+ }
1055
+ catch (error) {
1056
+ if (error instanceof WorkflowError && error.code === 'NOT_FOUND')
1057
+ return null;
1058
+ throw error;
1059
+ }
1060
+ }
1061
+ requireActiveKnowledgeMap(repositoryRoot, projectId) {
1062
+ const current = this.store.readKnowledgeMap(projectId);
1063
+ const inspected = inspectKnowledgeMap(current, scanProjectKnowledge(repositoryRoot, projectId, this.now()));
1064
+ if (inspected.status !== 'active' || !inspected.approval) {
1065
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Project Knowledge Map is not active.', {
1066
+ status: inspected.status,
1067
+ gaps: inspected.gaps,
1068
+ conflicts: inspected.conflicts,
1069
+ });
1070
+ }
1071
+ return inspected;
1072
+ }
1073
+ assertKnowledgeMapBinding(repositoryRoot, projectId, revision, mapHash) {
1074
+ const map = this.requireActiveKnowledgeMap(repositoryRoot, projectId);
1075
+ const currentHash = hashKnowledgeMap(map);
1076
+ if (map.revision !== revision || currentHash !== mapHash || map.approval?.mapHash !== currentHash) {
1077
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Task Plan is bound to a stale Project Knowledge Map.', {
1078
+ expectedRevision: revision,
1079
+ actualRevision: map.revision,
1080
+ expectedHash: mapHash,
1081
+ actualHash: currentHash,
1082
+ });
1083
+ }
1084
+ return map;
1085
+ }
1086
+ assertTaskKnowledgeBinding(repositoryRoot, task) {
1087
+ if (task.knowledgeMapRevision === null || !task.knowledgeMapHash) {
1088
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} has no Project Knowledge Map binding.`);
1089
+ }
1090
+ return this.assertKnowledgeMapBinding(repositoryRoot, task.projectId, task.knowledgeMapRevision, task.knowledgeMapHash);
1091
+ }
1092
+ validateKnowledgeTargets(map, plan) {
1093
+ const known = new Set(map.entries.map((entry) => `${entry.category}\0${entry.path}`));
1094
+ for (const target of plan.knowledgeTargets) {
1095
+ const normalized = normalizeRelativePath(target.path);
1096
+ if (normalized !== target.path) {
1097
+ throw new WorkflowError('TRANSITION_BLOCKED', `Knowledge target path is not normalized: ${target.path}`);
1098
+ }
1099
+ if (plan.knowledgeImpact !== 'create' && !known.has(`${target.category}\0${target.path}`)) {
1100
+ throw new WorkflowError('TRANSITION_BLOCKED', `Knowledge target is not present in the approved map: ${target.path}`);
1101
+ }
1102
+ for (const requirement of target.requirementIds) {
1103
+ if (!plan.requirements.includes(requirement)) {
1104
+ throw new WorkflowError('TRANSITION_BLOCKED', `Knowledge target references unknown requirement ${requirement}.`);
1105
+ }
1106
+ }
1107
+ }
1108
+ }
1109
+ assertReviewFresh(repositoryRoot, task) {
1110
+ const root = this.store.taskRoot(task.projectId, task.id);
1111
+ const current = {
1112
+ headCommit: headCommit(repositoryRoot),
1113
+ briefHash: this.store.hashArtifact(root, 'brief.md'),
1114
+ planHash: this.store.hashArtifact(root, 'plan.md'),
1115
+ evidenceHash: this.store.hashArtifact(root, 'evidence.json'),
1116
+ };
1117
+ const stale = Object.entries(current).filter(([key, value]) => task.review[key] !== value);
1118
+ if (stale.length > 0) {
1119
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Independent review is stale.', {
1120
+ stale: stale.map(([key]) => key),
1121
+ });
1122
+ }
1123
+ }
1124
+ }
1125
+ function emptyReview() {
1126
+ return {
1127
+ status: 'pending',
1128
+ reviewer: '',
1129
+ recordedAt: null,
1130
+ headCommit: null,
1131
+ briefHash: null,
1132
+ planHash: null,
1133
+ evidenceHash: null,
1134
+ summary: '',
1135
+ findings: [],
1136
+ };
1137
+ }
1138
+ function slugify(value) {
1139
+ const slug = value
1140
+ .normalize('NFKD')
1141
+ .toLowerCase()
1142
+ .replace(/[^a-z0-9]+/g, '-')
1143
+ .replace(/^-|-$/g, '')
1144
+ .slice(0, 40);
1145
+ return slug || 'task';
1146
+ }
1147
+ function requireStep(task, stepId) {
1148
+ const step = task.steps.find((candidate) => candidate.id === stepId);
1149
+ if (!step)
1150
+ throw new WorkflowError('NOT_FOUND', `Step not found: ${stepId}`);
1151
+ return step;
1152
+ }
1153
+ function dependenciesComplete(step, completed) {
1154
+ const completedIds = new Set(completed);
1155
+ return step.dependencies.every((dependency) => completedIds.has(dependency));
1156
+ }
1157
+ function identityRelative(repositoryRoot, value) {
1158
+ return path.resolve(repositoryRoot, value);
1159
+ }
1160
+ function finalAcceptedHead(task) {
1161
+ const event = [...task.authorizations]
1162
+ .reverse()
1163
+ .find((authorization) => authorization.kind === 'final_acceptance' && authorization.decision === 'approved');
1164
+ if (!event?.headCommit)
1165
+ throw new WorkflowError('STATE_CORRUPT', 'Final acceptance has no head commit.');
1166
+ return event.headCommit;
1167
+ }
1168
+ function assertExpectedRevision(record, expectedRevision) {
1169
+ if (record.revision !== expectedRevision) {
1170
+ throw new WorkflowError('STATE_CONFLICT', `State revision conflict for ${record.id}`, {
1171
+ expectedRevision,
1172
+ actualRevision: record.revision,
1173
+ });
1174
+ }
1175
+ }
1176
+ function assertTaskBranch(repositoryRoot, task) {
1177
+ if (!task.taskBranch)
1178
+ throw new WorkflowError('STATE_CORRUPT', `Task ${task.id} has no execution branch.`);
1179
+ const branch = currentBranch(repositoryRoot);
1180
+ if (branch !== task.taskBranch) {
1181
+ throw new WorkflowError('GIT_PRECONDITION_FAILED', `Expected Task branch ${task.taskBranch}, found ${branch ?? 'detached HEAD'}.`);
1182
+ }
1183
+ }
1184
+ function preserveCompletedStep(task, next) {
1185
+ const previous = task.steps.find((candidate) => candidate.id === next.id && candidate.status === 'completed');
1186
+ if (!previous)
1187
+ return null;
1188
+ const comparable = ({ status: _status, evidence: _evidence, ...definition }) => definition;
1189
+ return JSON.stringify(comparable(previous)) === JSON.stringify(next) ? previous : null;
1190
+ }
1191
+ function validateReviewInput(review) {
1192
+ if (!review.reviewer.trim())
1193
+ throw new WorkflowError('INVALID_ARGUMENT', 'Review requires reviewer identity.');
1194
+ if (!review.summary.trim())
1195
+ throw new WorkflowError('INVALID_ARGUMENT', 'Review requires a summary.');
1196
+ if (review.status === 'pending') {
1197
+ throw new WorkflowError('INVALID_ARGUMENT', 'Recorded review status cannot be pending.');
1198
+ }
1199
+ if (review.status === 'passed' && review.findings.length > 0) {
1200
+ throw new WorkflowError('INVALID_ARGUMENT', 'A passing review cannot contain findings.');
1201
+ }
1202
+ if (review.status === 'failed' && review.findings.length === 0) {
1203
+ throw new WorkflowError('INVALID_ARGUMENT', 'A failed review requires at least one finding.');
1204
+ }
1205
+ }
1206
+ function validateMilestonePlan(plan) {
1207
+ if (!plan.outcome.trim())
1208
+ throw new WorkflowError('INVALID_ARGUMENT', 'Milestone outcome is required.');
1209
+ if (!plan.successSignal.trim())
1210
+ throw new WorkflowError('INVALID_ARGUMENT', 'Milestone success signal is required.');
1211
+ if (plan.acceptance.length === 0 || plan.acceptance.some((value) => !value.trim())) {
1212
+ throw new WorkflowError('INVALID_ARGUMENT', 'Milestone acceptance must contain substantive entries.');
1213
+ }
1214
+ if (plan.memberships.length === 0) {
1215
+ throw new WorkflowError('INVALID_ARGUMENT', 'Milestone requires at least one Task membership.');
1216
+ }
1217
+ for (const membership of plan.memberships) {
1218
+ if (!membership.taskId.trim())
1219
+ throw new WorkflowError('INVALID_ARGUMENT', 'Milestone membership requires Task ID.');
1220
+ if (membership.disposition !== 'required' && !membership.reason.trim()) {
1221
+ throw new WorkflowError('INVALID_ARGUMENT', `Milestone ${membership.disposition} membership requires a reason.`);
1222
+ }
1223
+ }
1224
+ if (plan.checks.length === 0 || plan.checks.some((value) => !value.trim())) {
1225
+ throw new WorkflowError('INVALID_ARGUMENT', 'Milestone requires at least one cross-Task check.');
1226
+ }
1227
+ }
1228
+ function taskHasStarted(task) {
1229
+ return task.workspaceOwner !== null || [
1230
+ 'in_progress',
1231
+ 'validating',
1232
+ 'needs_fix',
1233
+ 'awaiting_final_acceptance',
1234
+ 'merging',
1235
+ 'ready_to_merge',
1236
+ 'merged',
1237
+ ].includes(task.status);
1238
+ }
1239
+ function nextForMilestone(milestone) {
1240
+ const actions = {
1241
+ planning: 'milestone plan-set',
1242
+ awaiting_execution_authorization: 'milestone authorize',
1243
+ active: 'complete required Tasks, then milestone validate',
1244
+ validating: 'milestone validate',
1245
+ awaiting_final_acceptance: 'milestone accept',
1246
+ accepted: 'none',
1247
+ blocked: 'milestone plan-set or milestone cancel',
1248
+ cancelled: 'none',
1249
+ };
1250
+ return actions[milestone.status];
1251
+ }
1252
+ function nextForTask(task) {
1253
+ const actionByStatus = {
1254
+ planning: 'task plan-set',
1255
+ awaiting_execution_authorization: 'task authorize',
1256
+ ready: task.workspaceOwner ? 'task run' : 'task start',
1257
+ in_progress: 'task run or task submit',
1258
+ validating: task.review.status === 'passed' ? 'task result-set' : 'task review-record',
1259
+ needs_fix: task.steps.some((step) => step.status === 'failed') ? 'task run' : 'task plan-set',
1260
+ awaiting_final_acceptance: 'task accept',
1261
+ merging: 'task merge',
1262
+ ready_to_merge: 'task merge-confirm',
1263
+ merged: 'none',
1264
+ blocked: task.review.status === 'unverified' ? 'task review-record' : 'doctor',
1265
+ cancelled: 'none',
1266
+ };
1267
+ return { scope: 'task', id: task.id, status: task.status, action: actionByStatus[task.status], revision: task.revision };
1268
+ }
1269
+ function knowledgeSummary(map) {
1270
+ return {
1271
+ revision: map.revision,
1272
+ hash: hashKnowledgeMap(map),
1273
+ status: map.status,
1274
+ };
1275
+ }
1276
+ //# sourceMappingURL=workflow.js.map