@nestos/kanban 0.0.0-stage → 0.5.2

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 (124) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +165 -2
  3. package/dist/activity-BbC4sgKL.d.cts +258 -0
  4. package/dist/activity-Czlan5xW.d.ts +258 -0
  5. package/dist/activity-module-DaO1Rry0.d.cts +22 -0
  6. package/dist/activity-module-T22wliZu.d.ts +22 -0
  7. package/dist/activity-ports-Dqxa86zT.d.cts +12 -0
  8. package/dist/activity-ports-cxfrelaL.d.ts +12 -0
  9. package/dist/activity.cjs +16 -0
  10. package/dist/activity.cjs.map +1 -0
  11. package/dist/activity.d.cts +12 -0
  12. package/dist/activity.d.ts +12 -0
  13. package/dist/activity.js +16 -0
  14. package/dist/activity.js.map +1 -0
  15. package/dist/chunk-3JYLJPE5.js +95 -0
  16. package/dist/chunk-3JYLJPE5.js.map +1 -0
  17. package/dist/chunk-3LEIHB7Y.js +221 -0
  18. package/dist/chunk-3LEIHB7Y.js.map +1 -0
  19. package/dist/chunk-4XF6LQSU.cjs +9 -0
  20. package/dist/chunk-4XF6LQSU.cjs.map +1 -0
  21. package/dist/chunk-5Q7WB63A.cjs +282 -0
  22. package/dist/chunk-5Q7WB63A.cjs.map +1 -0
  23. package/dist/chunk-7KDOTBRX.cjs +58 -0
  24. package/dist/chunk-7KDOTBRX.cjs.map +1 -0
  25. package/dist/chunk-AHHEGTUA.js +148 -0
  26. package/dist/chunk-AHHEGTUA.js.map +1 -0
  27. package/dist/chunk-AOHQCX7K.cjs +95 -0
  28. package/dist/chunk-AOHQCX7K.cjs.map +1 -0
  29. package/dist/chunk-DINLR6E6.cjs +443 -0
  30. package/dist/chunk-DINLR6E6.cjs.map +1 -0
  31. package/dist/chunk-EYTVC2H5.cjs +1612 -0
  32. package/dist/chunk-EYTVC2H5.cjs.map +1 -0
  33. package/dist/chunk-FEE2YBZ7.js +1972 -0
  34. package/dist/chunk-FEE2YBZ7.js.map +1 -0
  35. package/dist/chunk-FNO5K3A7.js +282 -0
  36. package/dist/chunk-FNO5K3A7.js.map +1 -0
  37. package/dist/chunk-IHHDXZPZ.js +382 -0
  38. package/dist/chunk-IHHDXZPZ.js.map +1 -0
  39. package/dist/chunk-J3EAIVA3.js +280 -0
  40. package/dist/chunk-J3EAIVA3.js.map +1 -0
  41. package/dist/chunk-KH3WF3K4.js +443 -0
  42. package/dist/chunk-KH3WF3K4.js.map +1 -0
  43. package/dist/chunk-M35X5PHO.js +196 -0
  44. package/dist/chunk-M35X5PHO.js.map +1 -0
  45. package/dist/chunk-QRGZD2JU.cjs +1972 -0
  46. package/dist/chunk-QRGZD2JU.cjs.map +1 -0
  47. package/dist/chunk-RBOER6EW.cjs +221 -0
  48. package/dist/chunk-RBOER6EW.cjs.map +1 -0
  49. package/dist/chunk-RQP77ZTY.js +9 -0
  50. package/dist/chunk-RQP77ZTY.js.map +1 -0
  51. package/dist/chunk-RSH6QSEC.cjs +196 -0
  52. package/dist/chunk-RSH6QSEC.cjs.map +1 -0
  53. package/dist/chunk-VMRDXOLX.cjs +148 -0
  54. package/dist/chunk-VMRDXOLX.cjs.map +1 -0
  55. package/dist/chunk-X4UHZX6F.js +58 -0
  56. package/dist/chunk-X4UHZX6F.js.map +1 -0
  57. package/dist/chunk-XBSGUVCY.cjs +382 -0
  58. package/dist/chunk-XBSGUVCY.cjs.map +1 -0
  59. package/dist/chunk-XKDZHAUE.js +1612 -0
  60. package/dist/chunk-XKDZHAUE.js.map +1 -0
  61. package/dist/chunk-ZUZGAO6G.cjs +280 -0
  62. package/dist/chunk-ZUZGAO6G.cjs.map +1 -0
  63. package/dist/content-C5aJZFlw.d.cts +35 -0
  64. package/dist/content-hLH5BNud.d.ts +35 -0
  65. package/dist/contracts/index.cjs +31 -0
  66. package/dist/contracts/index.cjs.map +1 -0
  67. package/dist/contracts/index.d.cts +5 -0
  68. package/dist/contracts/index.d.ts +5 -0
  69. package/dist/contracts/index.js +31 -0
  70. package/dist/contracts/index.js.map +1 -0
  71. package/dist/discovery-9E-LfwIw.d.cts +61 -0
  72. package/dist/discovery-D60yrjej.d.ts +61 -0
  73. package/dist/discovery.cjs +377 -0
  74. package/dist/discovery.cjs.map +1 -0
  75. package/dist/discovery.d.cts +22 -0
  76. package/dist/discovery.d.ts +22 -0
  77. package/dist/discovery.js +377 -0
  78. package/dist/discovery.js.map +1 -0
  79. package/dist/execution-bridge-fpCnH6z5.d.cts +52 -0
  80. package/dist/execution-bridge-rk0uVy_n.d.ts +52 -0
  81. package/dist/execution.cjs +18 -0
  82. package/dist/execution.cjs.map +1 -0
  83. package/dist/execution.d.cts +42 -0
  84. package/dist/execution.d.ts +42 -0
  85. package/dist/execution.js +18 -0
  86. package/dist/execution.js.map +1 -0
  87. package/dist/index.cjs +74 -0
  88. package/dist/index.cjs.map +1 -0
  89. package/dist/index.d.cts +54 -0
  90. package/dist/index.d.ts +54 -0
  91. package/dist/index.js +74 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/module-CYfE7YhM.d.ts +11 -0
  94. package/dist/module-Ca9L3qCn.d.cts +11 -0
  95. package/dist/planning-CHrOqQ6b.d.cts +879 -0
  96. package/dist/planning-CHrOqQ6b.d.ts +879 -0
  97. package/dist/ports-B0McVg7B.d.ts +143 -0
  98. package/dist/ports-BeGO1VRI.d.cts +49 -0
  99. package/dist/ports-GQy0x-RI.d.cts +143 -0
  100. package/dist/ports-ykGiNUSx.d.ts +49 -0
  101. package/dist/runtime/index.cjs +205 -0
  102. package/dist/runtime/index.cjs.map +1 -0
  103. package/dist/runtime/index.d.cts +75 -0
  104. package/dist/runtime/index.d.ts +75 -0
  105. package/dist/runtime/index.js +205 -0
  106. package/dist/runtime/index.js.map +1 -0
  107. package/dist/runtime/integration.cjs +423 -0
  108. package/dist/runtime/integration.cjs.map +1 -0
  109. package/dist/runtime/integration.d.cts +658 -0
  110. package/dist/runtime/integration.d.ts +658 -0
  111. package/dist/runtime/integration.js +423 -0
  112. package/dist/runtime/integration.js.map +1 -0
  113. package/dist/storage.cjs +21 -0
  114. package/dist/storage.cjs.map +1 -0
  115. package/dist/storage.d.cts +71 -0
  116. package/dist/storage.d.ts +71 -0
  117. package/dist/storage.js +21 -0
  118. package/dist/storage.js.map +1 -0
  119. package/dist/store-DPwHzGiz.d.cts +28 -0
  120. package/dist/store-DgdrPCV9.d.ts +28 -0
  121. package/dist/task-projection-CSorlMws.d.ts +11 -0
  122. package/dist/task-projection-DKKB_ueb.d.cts +11 -0
  123. package/examples/standalone.ts +22 -0
  124. package/package.json +143 -4
@@ -0,0 +1,1612 @@
1
+ import {
2
+ executionObservationChanged
3
+ } from "./chunk-RQP77ZTY.js";
4
+ import {
5
+ assigneeKey,
6
+ candidates,
7
+ discoveryState,
8
+ eligible,
9
+ executable,
10
+ identity,
11
+ owned,
12
+ participant,
13
+ policy,
14
+ principal,
15
+ principalKey,
16
+ requireValue,
17
+ text,
18
+ validateBoard,
19
+ validateCandidates,
20
+ validateEstimate,
21
+ workType
22
+ } from "./chunk-3LEIHB7Y.js";
23
+ import {
24
+ executableWorkItem,
25
+ sameAutomationBinding
26
+ } from "./chunk-X4UHZX6F.js";
27
+ import {
28
+ KanbanError,
29
+ randomUUID,
30
+ sha256HexSync
31
+ } from "./chunk-M35X5PHO.js";
32
+ import {
33
+ TASK_START_DELAY_MS,
34
+ boardAllowsStart,
35
+ taskDue,
36
+ taskEditing,
37
+ taskStartAt
38
+ } from "./chunk-3JYLJPE5.js";
39
+
40
+ // src/planning.ts
41
+ function plan(item) {
42
+ return item.planning ?? { kind: "task", priority: 2, dependencies: [], references: [] };
43
+ }
44
+ function summary(item) {
45
+ return {
46
+ deletedAt: item.deletedAt,
47
+ launch: item.launch,
48
+ analysis: item.analysis,
49
+ control: item.control,
50
+ issues: item.issues,
51
+ blocked: item.blocked,
52
+ id: item.id,
53
+ number: item.number,
54
+ title: item.title,
55
+ status: item.status,
56
+ revision: item.revision,
57
+ createdAt: item.createdAt,
58
+ updatedAt: item.updatedAt,
59
+ discoveryFingerprint: item.origin?.fingerprint,
60
+ pendingAmendments: item.amendments?.filter((proposal) => proposal.status === "pending").length,
61
+ externalRefs: item.externalRefs,
62
+ closedAt: item.closedAt,
63
+ planning: plan(item),
64
+ sessionIds: item.participants.map((p) => p.sessionId)
65
+ };
66
+ }
67
+ function terminal(item) {
68
+ return item.status === "completed" || item.status === "cancelled";
69
+ }
70
+ function catalog(state) {
71
+ return [...state.board.items.map(summary), ...state.archiveIndex ?? []];
72
+ }
73
+ function limits(state) {
74
+ const p = state.board.policy;
75
+ return { ready: p.maxReady ?? 20, inProgress: p.maxInProgress ?? 3, perAgent: p.maxInProgressPerAssignee ?? p.maxInProgressPerAgent ?? 1, perAssignee: p.maxInProgressPerAssignee ?? p.maxInProgressPerAgent ?? 1 };
76
+ }
77
+ function validatePlan(state, item, value) {
78
+ requireValue(typeof value.kind === "string" && Number.isInteger(value.priority) && value.priority >= 0 && value.priority <= 4, "INVALID_PLANNING");
79
+ requireValue(Array.isArray(value.dependencies) && value.dependencies.length <= 100 && Array.isArray(value.references) && value.references.length <= 100, "PLANNING_LIMIT");
80
+ value.dependencies.forEach(identity);
81
+ value.references.forEach(text);
82
+ if (value.area !== void 0) identity(value.area);
83
+ if (value.milestone !== void 0) identity(value.milestone);
84
+ requireValue(value.execution === void 0 || ["direct", "children"].includes(value.execution), "INVALID_EXECUTION_MODE");
85
+ const type = workType(state.board, value.kind);
86
+ if (value.dueAt !== void 0) requireValue(Number.isFinite(value.dueAt) && value.dueAt >= 0, "INVALID_DUE_DATE");
87
+ const all = catalog(state), parent = all.find((i) => i.id === value.parentId);
88
+ if (value.parentId) {
89
+ identity(value.parentId);
90
+ requireValue(parent && !parent.archivedAt && !parent.deletedAt && !terminal(parent) && parent.control?.action !== "cancel" && !["claimed", "review"].includes(parent.status), "PARENT_UNAVAILABLE");
91
+ requireValue(parent.id !== item.id && workType(state.board, parent.planning.kind).rank < type.rank, "INVALID_PARENT_KIND");
92
+ }
93
+ requireValue(value.execution === "children" || !all.some((child) => child.planning.parentId === item.id), "CHILDREN_REQUIRE_AGGREGATION");
94
+ requireValue(!all.some((child) => child.planning.parentId === item.id && workType(state.board, child.planning.kind).rank <= type.rank), "INVALID_CHILD_KIND");
95
+ for (const id of value.dependencies) requireValue(id !== item.id && all.some((i) => i.id === id), "INVALID_DEPENDENCY");
96
+ const visited = /* @__PURE__ */ new Set(), pending = [...value.dependencies];
97
+ while (pending.length) {
98
+ const id = pending.pop();
99
+ requireValue(id !== item.id, "DEPENDENCY_CYCLE");
100
+ if (visited.has(id)) continue;
101
+ visited.add(id);
102
+ pending.push(...all.find((i) => i.id === id)?.planning.dependencies ?? []);
103
+ }
104
+ }
105
+ function assertReady(state, item) {
106
+ requireValue(!state.board.lifecycle, "BOARD_STOPPING");
107
+ requireValue(!state.board.executionControl?.paused, "BOARD_EXECUTION_PAUSED");
108
+ requireValue(executable(state.board, item), "PLAN_NOT_EXECUTABLE");
109
+ requireValue(!item.blocked, "ITEM_BLOCKED");
110
+ requireValue(!item.control || item.control.action === "resume" && item.control.state === "resumed", "TASK_CONTROL_PENDING");
111
+ const all = catalog(state);
112
+ requireValue(plan(item).dependencies.every((id) => all.find((i) => i.id === id)?.status === "completed"), "DEPENDENCIES_UNFINISHED");
113
+ }
114
+ function setStage(state, item, stage, now) {
115
+ requireValue(["proposed", "backlog", "open"].includes(item.status), "ITEM_NOT_PLANNABLE");
116
+ requireValue(stage === "backlog" || stage === "open", "INVALID_STAGE");
117
+ if (stage === "open") {
118
+ assertReady(state, item);
119
+ requireValue(item.status === "open" || state.board.items.filter((i) => i.status === "open").length < limits(state).ready, "READY_LIMIT");
120
+ }
121
+ item.status = stage;
122
+ item.readyAt = now;
123
+ item.updatedAt = now;
124
+ item.revision++;
125
+ }
126
+ function automaticStage(state, item) {
127
+ const all = catalog(state);
128
+ const dependenciesComplete = plan(item).dependencies.every((id) => all.find((i) => i.id === id)?.status === "completed");
129
+ return executable(state.board, item) && !item.blocked && dependenciesComplete && state.board.items.filter((i) => i.status === "open").length < limits(state).ready ? "open" : "backlog";
130
+ }
131
+
132
+ // src/amendments.ts
133
+ var textOf = (item) => ({ title: item.title, responsibility: item.responsibility, acceptance: item.acceptance });
134
+ var textDigest = (item) => sha256HexSync(JSON.stringify(textOf(item)));
135
+ function workFingerprint(item) {
136
+ const planning = plan(item);
137
+ return sha256HexSync(JSON.stringify({
138
+ text: textOf(item),
139
+ kind: planning.kind,
140
+ parentId: planning.parentId ?? null,
141
+ area: planning.area ?? null,
142
+ milestone: planning.milestone ?? null,
143
+ priority: planning.priority,
144
+ dependencies: [...planning.dependencies].sort(),
145
+ dueAt: planning.dueAt ?? null
146
+ }));
147
+ }
148
+ function validateSources(sources) {
149
+ requireValue(Array.isArray(sources) && sources.length <= 100, "SOURCE_LIMIT");
150
+ for (const source of sources) {
151
+ identity(source.sessionId);
152
+ identity(source.messageId);
153
+ identity(source.revision);
154
+ requireValue(source.role === "user" || source.role === "assistant", "INVALID_SOURCE");
155
+ }
156
+ }
157
+ function proposeAmendment(item, change, actor, now, runId) {
158
+ text(change.title);
159
+ text(change.responsibility);
160
+ text(change.acceptance);
161
+ validateSources(change.sources);
162
+ const proposed = textOf(change), fingerprint = textDigest(proposed);
163
+ if (textDigest(item) === fingerprint || item.amendments?.some((p) => p.fingerprint === fingerprint)) return;
164
+ requireValue((item.amendments?.length ?? 0) < 20, "AMENDMENT_LIMIT");
165
+ (item.amendments ??= []).push({
166
+ runId,
167
+ id: `amendment-${sha256HexSync(JSON.stringify([runId, item.id, fingerprint]))}`,
168
+ fingerprint,
169
+ baseRevision: item.revision,
170
+ baseDigest: textDigest(item),
171
+ proposed,
172
+ proposedBy: principal(actor),
173
+ sources: structuredClone(change.sources),
174
+ createdAt: now,
175
+ status: "pending"
176
+ });
177
+ }
178
+ function requireReviewer(board, actor) {
179
+ const key = principalKey(principal(actor));
180
+ requireValue(
181
+ board.members?.some((m) => principalKey(m) === key && m.roles?.some((role) => role === "coordinator" || role === "reviewer")),
182
+ "REVIEWER_REQUIRED"
183
+ );
184
+ }
185
+ function reviewAmendment(board, command, actor, now) {
186
+ requireReviewer(board, actor);
187
+ const item = board.items.find((i) => i.id === command.itemId), proposal = item?.amendments?.find((p) => p.id === command.amendmentId);
188
+ requireValue(item && proposal?.status === "pending", "AMENDMENT_NOT_PENDING");
189
+ if (command.accept) {
190
+ requireValue(principal(actor).kind === "human" || principalKey(proposal.proposedBy) !== principalKey(principal(actor)), "INDEPENDENT_REVIEW_REQUIRED");
191
+ requireValue(["proposed", "backlog", "open"].includes(item.status), "ITEM_NOT_PLANNABLE");
192
+ requireValue(textDigest(item) === proposal.baseDigest, "AMENDMENT_STALE");
193
+ Object.assign(item, proposal.proposed);
194
+ if (item.status === "open") {
195
+ item.status = "backlog";
196
+ item.readyAt = now;
197
+ }
198
+ } else {
199
+ requireValue(!!command.reason?.trim(), "AMENDMENT_REASON_REQUIRED");
200
+ text(command.reason);
201
+ }
202
+ proposal.status = command.accept ? "accepted" : "rejected";
203
+ proposal.reviewedAt = now;
204
+ proposal.reviewedBy = principal(actor);
205
+ proposal.reason = command.reason;
206
+ item.updatedAt = now;
207
+ item.revision++;
208
+ }
209
+
210
+ // src/project-records.ts
211
+ function isRecordCommand(command) {
212
+ return ["append-record", "review-record", "link-record", "request-record-cancellation", "cancel-record", "delete-record"].includes(command.type);
213
+ }
214
+ function applyRecord(state, command, actor, now, forceDraft = false) {
215
+ if (command.type === "append-record") {
216
+ append(state, command, actor, now, forceDraft);
217
+ return;
218
+ }
219
+ const record = state.records?.find((record2) => record2.id === command.recordId);
220
+ requireValue(record, "RECORD_NOT_FOUND");
221
+ requireValue(record.deletedAt === void 0, "RECORD_DELETED");
222
+ if (command.type === "request-record-cancellation" || command.type === "cancel-record" || command.type === "delete-record") {
223
+ requireReviewer(state.board, actor);
224
+ requireValue(principal(actor).kind === "human", "FORBIDDEN");
225
+ requireValue(record.kind === "decision", "DECISION_REQUIRED");
226
+ if (command.type !== "request-record-cancellation") requireValue(record.itemIds.every((id) => {
227
+ const item = state.board.items.find((i) => i.id === id) ?? state.archiveIndex?.find((i) => i.id === id);
228
+ return item && ["completed", "cancelled"].includes(item.status);
229
+ }), "RECORD_EXECUTION_UNSETTLED");
230
+ if (command.type === "request-record-cancellation") record.status = record.status === "cancelled" ? "cancelled" : "cancelling";
231
+ else if (command.type === "cancel-record") record.status = "cancelled";
232
+ else {
233
+ requireValue(["cancelled", "rejected", "superseded"].includes(record.status), "RECORD_CANCEL_REQUIRED");
234
+ record.deletedAt = now;
235
+ }
236
+ } else if (command.type === "link-record") {
237
+ requireValue(!["cancelling", "cancelled"].includes(record.status), "RECORD_CANCELLING");
238
+ validateItems(state, [command.itemId]);
239
+ if (!record.itemIds.includes(command.itemId)) {
240
+ requireValue(record.itemIds.length < 100, "RECORD_LINK_LIMIT");
241
+ record.itemIds.push(command.itemId);
242
+ }
243
+ } else {
244
+ requireReviewer(state.board, actor);
245
+ requireValue(record.status === "draft", "RECORD_NOT_DRAFT");
246
+ if (command.accept) {
247
+ requireValue(principal(actor).kind === "human" || principalKey(record.author) !== principalKey(principal(actor)), "INDEPENDENT_REVIEW_REQUIRED");
248
+ supersede(state, record, now);
249
+ record.status = "confirmed";
250
+ } else record.status = "rejected";
251
+ record.reviewedBy = principal(actor);
252
+ }
253
+ record.updatedAt = now;
254
+ record.revision++;
255
+ }
256
+ function append(state, command, actor, now, forceDraft) {
257
+ validateSources(command.record.sources);
258
+ const { id, kind, title, content, rationale, itemIds, sources, supersedes, coordination, externalRefs } = command.record;
259
+ const input = structuredClone({ id, kind, title, content, rationale, itemIds, sources: sources.map((source) => ({ sessionId: source.sessionId, messageId: source.messageId, revision: source.revision, role: source.role })), supersedes, ...coordination ? { coordination } : {}, ...externalRefs ? { externalRefs } : {} });
260
+ identity(input.id);
261
+ text(input.title);
262
+ text(input.content);
263
+ validateRecordReferences(externalRefs);
264
+ requireValue(input.kind === "log" || input.kind === "decision", "INVALID_RECORD_KIND");
265
+ requireValue(!state.records?.some((record) => record.id === input.id), "RECORD_EXISTS");
266
+ requireValue((state.records?.length ?? 0) < 200, "RECORD_LIMIT");
267
+ validateItems(state, input.itemIds);
268
+ validateSources(input.sources);
269
+ if (input.rationale !== void 0) text(input.rationale);
270
+ if (input.kind === "decision") text(input.rationale ?? "");
271
+ if (coordination) validateCoordination(state, input);
272
+ if (input.supersedes) {
273
+ identity(input.supersedes);
274
+ requireValue(input.kind === "decision" && input.supersedes !== input.id && state.records?.some((record) => record.id === input.supersedes && record.kind === "decision" && record.status === "confirmed"), "INVALID_SUPERSEDED_RECORD");
275
+ }
276
+ const author = principal(actor), manual = author.kind === "human" && !forceDraft;
277
+ (state.records ??= []).push({
278
+ ...input,
279
+ author,
280
+ origin: manual ? "manual" : "flow",
281
+ status: manual && input.kind === "log" ? "confirmed" : "draft",
282
+ revision: 1,
283
+ createdAt: now,
284
+ updatedAt: now
285
+ });
286
+ }
287
+ function validateCoordination(state, input) {
288
+ requireValue(input.kind === "decision", "DECISION_COORDINATION_ONLY");
289
+ for (const owner of [input.coordination.manager, input.coordination.executor].filter(Boolean)) {
290
+ requireValue(owner && ["agent", "human", "service"].includes(owner.kind), "INVALID_PRINCIPAL");
291
+ identity(owner.id);
292
+ const member = state.board.members?.find((member2) => principalKey(member2) === principalKey(owner));
293
+ requireValue(member, "ASSIGNEE_NOT_REGISTERED");
294
+ if (owner === input.coordination.manager) requireValue(member.roles?.includes("coordinator"), "COORDINATOR_REQUIRED");
295
+ }
296
+ }
297
+ function validateItems(state, ids) {
298
+ requireValue(Array.isArray(ids) && ids.length <= 100 && new Set(ids).size === ids.length, "RECORD_LINK_LIMIT");
299
+ for (const id of ids) {
300
+ identity(id);
301
+ requireValue(state.board.items.some((item) => item.id === id) || state.archiveIndex?.some((item) => item.id === id), "ITEM_NOT_FOUND");
302
+ }
303
+ }
304
+ function supersede(state, record, now) {
305
+ if (!record.supersedes) return;
306
+ const previous = state.records?.find((entry) => entry.id === record.supersedes);
307
+ requireValue(previous?.kind === "decision" && previous.status === "confirmed", "SUPERSEDED_RECORD_CHANGED");
308
+ previous.status = "superseded";
309
+ previous.supersededBy = record.id;
310
+ previous.revision++;
311
+ previous.updatedAt = now;
312
+ }
313
+ function queryRecords(state, query = {}, summaries = (state.records ?? []).map(recordSummary)) {
314
+ const { cursor, ...filter } = query, limit = query.limit ?? 50;
315
+ requireValue(Number.isSafeInteger(limit) && limit > 0 && limit <= 200, "INVALID_LIMIT");
316
+ if (query.kind) requireValue(["log", "decision"].includes(query.kind), "INVALID_RECORD_KIND");
317
+ if (query.itemId !== void 0) identity(query.itemId);
318
+ if (query.status) requireValue(["draft", "confirmed", "rejected", "superseded", "cancelling", "cancelled"].includes(query.status), "INVALID_RECORD_STATUS");
319
+ const digest = sha256HexSync(JSON.stringify(filter));
320
+ let offset = 0;
321
+ if (cursor) {
322
+ let decoded;
323
+ try {
324
+ decoded = JSON.parse(decodeURIComponent(cursor));
325
+ } catch {
326
+ requireValue(false, "INVALID_CURSOR");
327
+ }
328
+ requireValue(decoded && typeof decoded === "object" && !Array.isArray(decoded) && typeof decoded.filter === "string" && typeof decoded.revision === "number", "INVALID_CURSOR");
329
+ requireValue(decoded.revision === state.board.revision && decoded.filter === digest, "CURSOR_STALE");
330
+ offset = decoded.offset;
331
+ requireValue(Number.isSafeInteger(offset) && offset >= 0, "INVALID_CURSOR");
332
+ }
333
+ const matched = summaries.filter((record) => (query.includeDeleted || record.deletedAt === void 0) && (!query.kind || query.kind === record.kind) && (!query.itemId || record.itemIds.includes(query.itemId)) && (!query.status || query.status === record.status)).sort((a, b) => b.createdAt - a.createdAt || a.id.localeCompare(b.id));
334
+ const items = matched.slice(offset, offset + limit);
335
+ return { revision: state.board.revision, total: matched.length, items, ...offset + limit < matched.length ? { nextCursor: encodeURIComponent(JSON.stringify({ revision: state.board.revision, filter: digest, offset: offset + limit })) } : {} };
336
+ }
337
+ function recordFingerprint(record) {
338
+ return sha256HexSync(JSON.stringify({
339
+ kind: record.kind,
340
+ title: record.title,
341
+ content: record.content,
342
+ rationale: record.rationale ?? null,
343
+ itemIds: [...record.itemIds].sort(),
344
+ sources: record.sources.map((source) => JSON.stringify([source.sessionId, source.messageId, source.role])).sort(),
345
+ ...record.coordination ? { coordination: record.coordination } : {},
346
+ ...record.externalRefs ? { externalRefs: record.externalRefs } : {}
347
+ }));
348
+ }
349
+ function validateRecordReferences(refs) {
350
+ if (refs === void 0) return;
351
+ requireValue(Array.isArray(refs) && refs.length <= 100, "REFERENCE_LIMIT");
352
+ const keys = /* @__PURE__ */ new Set();
353
+ for (const ref of refs) {
354
+ requireValue(ref && typeof ref === "object", "INVALID_REFERENCE");
355
+ identity(ref.system);
356
+ identity(ref.id);
357
+ if (ref.revision !== void 0) text(ref.revision);
358
+ const key = JSON.stringify([ref.system, ref.id]);
359
+ requireValue(!keys.has(key), "DUPLICATE_REFERENCE");
360
+ keys.add(key);
361
+ }
362
+ }
363
+ function recordSummary(record) {
364
+ const { content: _content, rationale: _rationale, sources, ...metadata } = record;
365
+ return { ...metadata, sessionIds: [...new Set(sources.map((source) => source.sessionId))] };
366
+ }
367
+
368
+ // src/board.ts
369
+ function createBoardState(input, actor, now) {
370
+ const board = { ...structuredClone(input), policy: policy(input.policy), revision: 1, items: [], participants: [] };
371
+ validateBoard(board);
372
+ if (actor.sessionId) participant(board.participants, actor.sessionId, actor.agentId, "creator", true, now);
373
+ for (const session of board.policy.escalationSessionIds) participant(board.participants, session, void 0, "escalation", true, now);
374
+ return { schema: 1, board, sequence: 0, events: [], escalations: [] };
375
+ }
376
+
377
+ // src/storage-codec.ts
378
+ var boardKey = (id) => `board/${encodeURIComponent(id)}`;
379
+ var archiveKey = (board, item) => `archive/${encodeURIComponent(board)}/${encodeURIComponent(item)}`;
380
+ var rowKey = (kind, board, id) => `${kind}/${encodeURIComponent(board)}/${encodeURIComponent(id)}`;
381
+ function rowKeys(raw) {
382
+ if (raw === null) return [];
383
+ const saved = JSON.parse(raw);
384
+ if (saved.layout !== 2) return [];
385
+ requireValue(Array.isArray(saved.itemIds) && Array.isArray(saved.recordIds) && saved.state?.board, "INVALID_STORAGE");
386
+ return [...saved.itemIds.map((id) => rowKey("item", saved.state.board.id, id)), ...saved.recordIds.map((id) => rowKey("record", saved.state.board.id, id))];
387
+ }
388
+ async function hydrate(saved, bodies) {
389
+ if (saved.raw === null) return null;
390
+ const parsed = JSON.parse(saved.raw);
391
+ if (parsed.layout !== 2) return validateState(parsed);
392
+ const state = validateState(parsed.state), id = state.board.id;
393
+ state.board.items = await Promise.all(parsed.itemIds.map(async (itemId) => {
394
+ const item = await decodeRow(requiredRow(saved, rowKey("item", id, itemId)), bodies);
395
+ requireValue(item.id === itemId, "INVALID_STORAGE");
396
+ return item;
397
+ }));
398
+ state.records = await Promise.all(parsed.recordIds.map(async (recordId) => {
399
+ const record = await decodeRow(requiredRow(saved, rowKey("record", id, recordId)), bodies);
400
+ requireValue(record.id === recordId, "INVALID_STORAGE");
401
+ return record;
402
+ }));
403
+ return state;
404
+ }
405
+ async function encodeBoard(state, bodies) {
406
+ const rows = {}, id = state.board.id;
407
+ for (const item of state.board.items) rows[rowKey("item", id, item.id)] = await encodeRow(item, bodies);
408
+ for (const record of state.records ?? []) rows[rowKey("record", id, record.id)] = await encodeRow(record, bodies);
409
+ const manifest = {
410
+ itemIndex: state.board.items.map(summary),
411
+ recordIndex: (state.records ?? []).map(recordSummary),
412
+ layout: 2,
413
+ generation: randomUUID(),
414
+ state: { ...state, board: { ...state.board, items: [] }, records: [] },
415
+ itemIds: state.board.items.map((item) => item.id),
416
+ recordIds: (state.records ?? []).map((record) => record.id)
417
+ };
418
+ return { raw: JSON.stringify(manifest), rows };
419
+ }
420
+ function rowChanges(before, after) {
421
+ const writes = Object.entries(after.rows).filter(([key, value]) => before.rows[key] !== value).map(([key, value]) => ({ key, value }));
422
+ for (const key of Object.keys(before.rows)) if (!(key in after.rows)) writes.push({ key, value: null });
423
+ return writes;
424
+ }
425
+ function receiptDigest(fingerprint) {
426
+ return `sha256:v1:${sha256HexSync(fingerprint)}`;
427
+ }
428
+ function verifyReceipt(saved, fingerprint) {
429
+ requireValue(saved === fingerprint || saved === receiptDigest(fingerprint), "IDEMPOTENCY_CONFLICT");
430
+ }
431
+ async function encodeRow(value, bodies) {
432
+ const raw = JSON.stringify(value), bytes = new TextEncoder().encode(raw);
433
+ if (!bodies || bytes.length <= 8192) return raw;
434
+ const ref = { body: "kanban-body/v1", sha256: sha256HexSync(bytes), bytes: bytes.length };
435
+ const existing = await bodies.read(ref.sha256);
436
+ if (existing !== null) verifyBody(existing, ref);
437
+ else {
438
+ let failure;
439
+ try {
440
+ await bodies.write(ref.sha256, bytes.buffer);
441
+ } catch (error) {
442
+ failure = error;
443
+ }
444
+ const confirmed = await bodies.read(ref.sha256);
445
+ if (confirmed === null) throw failure ?? new KanbanError("BODY_WRITE_UNCONFIRMED");
446
+ verifyBody(confirmed, ref);
447
+ }
448
+ return JSON.stringify(ref);
449
+ }
450
+ async function decodeRow(raw, bodies) {
451
+ const parsed = JSON.parse(raw);
452
+ if (parsed?.body !== "kanban-body/v1") return parsed;
453
+ requireValue(/^[a-f0-9]{64}$/.test(parsed.sha256) && Number.isSafeInteger(parsed.bytes) && parsed.bytes > 8192, "INVALID_BODY_REFERENCE");
454
+ requireValue(bodies, "BODY_STORE_REQUIRED");
455
+ const bytes = await bodies.read(parsed.sha256);
456
+ requireValue(bytes !== null, "BODY_MISSING");
457
+ verifyBody(bytes, parsed);
458
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
459
+ }
460
+ function verifyBody(bytes, ref) {
461
+ requireValue(bytes instanceof ArrayBuffer && bytes.byteLength === ref.bytes && sha256HexSync(bytes) === ref.sha256, "BODY_INTEGRITY");
462
+ }
463
+ function requiredRow(saved, key) {
464
+ requireValue(typeof saved.rows[key] === "string", "MISSING_STORAGE_ROW");
465
+ return saved.rows[key];
466
+ }
467
+ function validateState(state) {
468
+ requireValue(state?.schema === 1 && state.board && Array.isArray(state.board.items) && Array.isArray(state.events), "INVALID_STORAGE");
469
+ return state;
470
+ }
471
+ function indexedView(raw) {
472
+ if (raw === null) return null;
473
+ const parsed = JSON.parse(raw);
474
+ if (parsed.layout === 2 && Array.isArray(parsed.itemIndex) && Array.isArray(parsed.recordIndex))
475
+ return { state: validateState(parsed.state), items: parsed.itemIndex, records: parsed.recordIndex };
476
+ return null;
477
+ }
478
+ function viewFromState(state) {
479
+ return { state: { ...state, board: { ...state.board, items: [] }, records: [] }, items: state.board.items.map(summary), records: (state.records ?? []).map(recordSummary) };
480
+ }
481
+
482
+ // src/numbering.ts
483
+ var boardCounterKey = "counter/board";
484
+ var boardNumberKey = (number) => `reference/board/${number}`;
485
+ function counter(raw) {
486
+ requireValue(raw === null || /^(0|[1-9]\d*)$/.test(raw), "INVALID_NUMBER_COUNTER");
487
+ const value = raw === null ? 0 : Number(raw);
488
+ requireValue(Number.isSafeInteger(value) && value >= 0 && value <= Number.MAX_SAFE_INTEGER, "INVALID_NUMBER_COUNTER");
489
+ return value;
490
+ }
491
+ function storedBoardNumber(raw) {
492
+ if (raw === null) return void 0;
493
+ const parsed = JSON.parse(raw), number = (parsed.layout === 2 ? parsed.state?.board : parsed.board)?.number;
494
+ if (number !== void 0) requireValue(Number.isSafeInteger(number) && number > 0, "INVALID_NUMBERING");
495
+ return number;
496
+ }
497
+ function patchBoardNumber(raw, number) {
498
+ const parsed = JSON.parse(raw);
499
+ parsed.state.board.number = number;
500
+ return JSON.stringify(parsed);
501
+ }
502
+ function numberEntities(state) {
503
+ const high = state.numbering ??= { item: 0, log: 0, decision: 0 };
504
+ const items = [...state.board.items, ...state.archiveIndex ?? [], ...(state.archiveWrites ?? []).flatMap((write) => write.value ? [write.value.item] : [])];
505
+ high.item = assign(items, high.item);
506
+ for (const kind of ["log", "decision"]) high[kind] = assign((state.records ?? []).filter((record) => record.kind === kind), high[kind]);
507
+ }
508
+ function assign(rows, high) {
509
+ counter(String(high));
510
+ const used = /* @__PURE__ */ new Map(), known = /* @__PURE__ */ new Map();
511
+ for (const row of rows) if (row.number !== void 0) {
512
+ requireValue(Number.isSafeInteger(row.number) && row.number > 0 && (!used.has(row.number) || used.get(row.number) === row.id) && (!known.has(row.id) || known.get(row.id) === row.number), "INVALID_NUMBERING");
513
+ used.set(row.number, row.id);
514
+ known.set(row.id, row.number);
515
+ high = Math.max(high, row.number);
516
+ }
517
+ for (const row of rows) if (row.number === void 0) {
518
+ if (!known.has(row.id)) {
519
+ requireValue(high < Number.MAX_SAFE_INTEGER, "NUMBER_COUNTER_EXHAUSTED");
520
+ known.set(row.id, ++high);
521
+ }
522
+ row.number = known.get(row.id);
523
+ }
524
+ return high;
525
+ }
526
+ function allocateBoardNumber(raw, high) {
527
+ const known = storedBoardNumber(raw);
528
+ if (known !== void 0) return known;
529
+ requireValue(high < Number.MAX_SAFE_INTEGER, "NUMBER_COUNTER_EXHAUSTED");
530
+ return high + 1;
531
+ }
532
+
533
+ // src/workflow-plans.ts
534
+ function requireCoordinator(state, actor) {
535
+ requireValue(state.board.members?.some((member) => principalKey(member) === principalKey(principal(actor)) && member.roles?.includes("coordinator")), "COORDINATOR_REQUIRED");
536
+ }
537
+ function publishPlan(state, plan2, actor, apply) {
538
+ requireCoordinator(state, actor);
539
+ requireValue(plan2 && typeof plan2 === "object", "INVALID_WORK_PLAN");
540
+ identity(plan2.id);
541
+ requireValue(plan2.schema === "kanban.plan/1" && typeof plan2.sealed === "boolean" && Array.isArray(plan2.items) && plan2.items.length > 0 && plan2.items.length <= 100, "INVALID_WORK_PLAN");
542
+ const workflow = state.workflow, previous = workflow.plans[plan2.id];
543
+ requireValue(Number.isSafeInteger(plan2.revision) && plan2.revision === (previous?.revision ?? 0) + 1, "PLAN_REVISION_CONFLICT");
544
+ requireValue(!Object.values(workflow.dispatches).some((dispatch) => dispatch.planId === plan2.id && ["prepared", "running", "unknown", "succeeded"].includes(dispatch.outcome)), "PLAN_EXECUTION_UNSETTLED");
545
+ if (plan2.decisionId) {
546
+ const decision = state.records?.find((record) => record.id === plan2.decisionId && record.kind === "decision" && record.status === "confirmed");
547
+ requireValue(decision, "DECISION_NOT_CONFIRMED");
548
+ if (decision.coordination) requireValue(principalKey(decision.coordination.manager) === principalKey(principal(actor)), "DECISION_MANAGER_REQUIRED");
549
+ }
550
+ if (plan2.rootItemId) {
551
+ const root = state.board.items.find((item) => item.id === plan2.rootItemId);
552
+ requireValue(root && !root.owner && ["backlog", "open"].includes(root.status), "PLAN_ROOT_UNAVAILABLE");
553
+ if (root.planning?.execution !== "children") apply({ type: "plan-item", itemId: root.id, planning: { ...plan(root), execution: "children" } });
554
+ }
555
+ const ordered = orderWork(plan2.items, state), definitions = {};
556
+ for (const item of ordered) {
557
+ const digest = sha256HexSync(JSON.stringify(item));
558
+ requireValue(!Object.values(workflow.plans).some((other) => other.id !== plan2.id && other.itemIds.includes(item.id)), "ITEM_IN_OTHER_PLAN");
559
+ const exists = state.board.items.some((old) => old.id === item.id) || state.archiveIndex?.some((old) => old.id === item.id);
560
+ if (exists && previous?.definitions[item.id]?.digest !== digest) {
561
+ requireValue(!previous?.definitions[item.id], "PLAN_ITEM_CHANGED_REQUIRES_NEW_ID");
562
+ adoptItem(state, item, apply);
563
+ }
564
+ if (!exists) apply({
565
+ type: "create-item",
566
+ item: { id: item.id, title: item.title, responsibility: item.responsibility, acceptance: item.acceptance, eligiblePrincipals: [item.assignee], externalRefs: item.externalRefs },
567
+ planning: { kind: item.workType ?? state.board.defaultWorkType ?? "task", priority: 2, dependencies: item.dependsOn, references: [], ...plan2.rootItemId ? { parentId: plan2.rootItemId } : {} }
568
+ });
569
+ definitions[item.id] = { digest, inputs: item.inputs, outputs: item.outputs ?? [] };
570
+ }
571
+ workflow.plans[plan2.id] = { schema: plan2.schema, id: plan2.id, revision: plan2.revision, decisionId: plan2.decisionId, rootItemId: plan2.rootItemId, sealed: plan2.sealed, itemIds: ordered.map((item) => item.id), definitions };
572
+ }
573
+ function adoptItem(state, definition, apply) {
574
+ const item = state.board.items.find((item2) => item2.id === definition.id);
575
+ requireValue(item && definition.expectedRevision === item.revision && ["backlog", "open"].includes(item.status) && !item.owner && !item.blocked && (!item.control || item.control.action === "resume" && item.control.state === "resumed"), "PLAN_ITEM_CHANGED_REQUIRES_NEW_ID");
576
+ requireValue(!definition.workType || definition.workType === item.planning?.kind, "PLAN_ITEM_CHANGED_REQUIRES_NEW_ID");
577
+ requireValue(!definition.externalRefs || definition.externalRefs.every((ref) => item.externalRefs?.some((existing) => JSON.stringify(existing) === JSON.stringify(ref))), "PLAN_RESOURCE_MISMATCH");
578
+ requireValue(item.title === definition.title && item.responsibility === definition.responsibility && item.acceptance === definition.acceptance, "PLAN_ITEM_CHANGED_REQUIRES_NEW_ID");
579
+ requireValue(plan(item).dependencies.every((id) => definition.dependsOn.includes(id)), "EXISTING_DEPENDENCY_REQUIRED");
580
+ apply({ type: "plan-item", itemId: item.id, planning: { ...plan(item), dependencies: definition.dependsOn } });
581
+ item.eligiblePrincipals = [definition.assignee];
582
+ delete item.eligibleAgentIds;
583
+ validateCandidates(state.board, item, true);
584
+ }
585
+ function orderWork(items, state) {
586
+ for (const item of items) validateDefinition(item);
587
+ const ids = new Set(items.map((item) => item.id));
588
+ requireValue(ids.size === items.length, "DUPLICATE_PLAN_ITEM");
589
+ for (const item of items) {
590
+ identity(item.id);
591
+ requireValue(Array.isArray(item.dependsOn) && item.dependsOn.length <= 100 && new Set(item.dependsOn).size === item.dependsOn.length, "INVALID_DEPENDENCIES");
592
+ requireValue(Object.keys(item.inputs ?? {}).length <= 100 && Array.isArray(item.outputs ?? []) && (item.outputs ?? []).length <= 100 && new Set(item.outputs ?? []).size === (item.outputs ?? []).length, "PLAN_BINDING_LIMIT");
593
+ for (const name of [...Object.keys(item.inputs ?? {}), ...item.outputs ?? []]) requireValue(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(name), "INVALID_OUTPUT_NAME");
594
+ for (const binding of Object.values(item.inputs ?? {})) {
595
+ requireValue(binding && item.dependsOn.includes(binding.fromItemId) && typeof binding.output === "string", "INPUT_DEPENDENCY_REQUIRED");
596
+ const producer = items.find((other) => other.id === binding.fromItemId), existing = Object.values(state.workflow.dispatches).some((dispatch) => dispatch.itemId === binding.fromItemId && dispatch.outcome === "accepted" && dispatch.outputs?.some((output) => output.name === binding.output));
597
+ requireValue(producer ? producer.outputs?.includes(binding.output) : existing, "OUTPUT_NOT_DECLARED");
598
+ }
599
+ for (const dependency of item.dependsOn) requireValue(ids.has(dependency) || [...state.board.items, ...state.archiveIndex ?? []].some((old) => old.id === dependency), "DEPENDENCY_NOT_FOUND");
600
+ }
601
+ const ordered = [], remaining = [...items], done = /* @__PURE__ */ new Set();
602
+ while (remaining.length) {
603
+ const next = remaining.findIndex((item2) => item2.dependsOn.every((id) => !ids.has(id) || done.has(id)));
604
+ requireValue(next >= 0, "DEPENDENCY_CYCLE");
605
+ const [item] = remaining.splice(next, 1);
606
+ ordered.push(item);
607
+ done.add(item.id);
608
+ }
609
+ return ordered;
610
+ }
611
+ function validateDefinition(item) {
612
+ requireValue(item && typeof item === "object" && !Array.isArray(item), "INVALID_WORK_DEFINITION");
613
+ const assignee = item.assignee;
614
+ requireValue(assignee && ["agent", "human", "service"].includes(assignee.kind), "INVALID_PRINCIPAL");
615
+ identity(assignee.id);
616
+ if (item.inputs !== void 0) requireValue(item.inputs && typeof item.inputs === "object" && !Array.isArray(item.inputs), "INVALID_INPUT_BINDINGS");
617
+ if (item.outputs !== void 0) requireValue(Array.isArray(item.outputs) && item.outputs.every((name) => typeof name === "string"), "INVALID_OUTPUT_NAMES");
618
+ }
619
+
620
+ // src/archives.ts
621
+ function archiveItems(state, ids, reason, now) {
622
+ text(reason);
623
+ requireValue(ids.length > 0 && ids.length <= 50 && new Set(ids).size === ids.length, "ARCHIVE_BATCH_LIMIT");
624
+ const items = ids.map((id) => state.board.items.find((i) => i.id === id));
625
+ requireValue(items.every((item) => item && terminal(item)), "ARCHIVE_REQUIRES_CLOSED_ITEM");
626
+ for (const item of items) {
627
+ const archived = { item, archivedAt: now, reason };
628
+ (state.archiveIndex ??= []).push({ ...summary(item), archivedAt: now, archiveReason: reason });
629
+ (state.archiveWrites ??= []).push({ id: item.id, value: archived });
630
+ }
631
+ const selected = new Set(ids);
632
+ state.board.items = state.board.items.filter((i) => !selected.has(i.id));
633
+ }
634
+ function restoreItem(state, id, now) {
635
+ const record = state.restore;
636
+ requireValue(record?.item.id === id && state.archiveIndex?.some((i) => i.id === id), "ARCHIVED_ITEM_NOT_FOUND");
637
+ requireValue(!state.board.items.some((i) => i.id === id), "ITEM_EXISTS");
638
+ record.item.restoredAt = now;
639
+ record.item.updatedAt = now;
640
+ record.item.revision++;
641
+ state.board.items.push(record.item);
642
+ state.archiveIndex = state.archiveIndex.filter((i) => i.id !== id);
643
+ (state.archiveWrites ??= []).push({ id, value: null });
644
+ delete state.restore;
645
+ }
646
+ function archiveExpired(state, now) {
647
+ const age = state.board.policy.archiveAfterMs;
648
+ if (age === void 0) return [];
649
+ const ids = state.board.items.filter((i) => terminal(i) && now - Math.max(i.closedAt ?? i.updatedAt, i.restoredAt ?? 0) >= age).slice(0, 50).map((i) => i.id);
650
+ if (ids.length) archiveItems(state, ids, "retention-policy", now);
651
+ return ids;
652
+ }
653
+
654
+ // src/planning-commands.ts
655
+ function planningCommand(command) {
656
+ return ["edit-item", "plan-item", "set-stage", "publish-proposal", "cancel-item", "close-plan", "archive-items", "restore-item", "block-item", "unblock-item", "link-reference", "release-item"].includes(command.type);
657
+ }
658
+ function applyPlanningCommand(state, command, now, actor, internal = false) {
659
+ if (command.type === "archive-items") return archiveItems(state, command.itemIds, command.reason, now);
660
+ if (command.type === "restore-item") return restoreItem(state, command.itemId, now);
661
+ const item = state.board.items.find((i) => i.id === command.itemId);
662
+ requireValue(item, "ITEM_NOT_FOUND");
663
+ if (isOperational(command)) operate(item, command, now, actor);
664
+ else {
665
+ if (!internal && ["edit-item", "plan-item", "review-amendment"].includes(command.type)) assertDefinitionEditable(item, now);
666
+ updatePlan(state, item, command, now);
667
+ }
668
+ }
669
+ function isOperational(command) {
670
+ return ["block-item", "unblock-item", "link-reference", "release-item"].includes(command.type);
671
+ }
672
+ function operate(item, command, now, actor) {
673
+ if (command.type === "release-item") {
674
+ requireValue(["claimed", "review"].includes(item.status) && item.owner?.token === command.token, "OWNER_MISMATCH");
675
+ requireValue(["backlog", "cancelled"].includes(command.next), "INVALID_STAGE");
676
+ text(command.reason);
677
+ item.status = command.next;
678
+ if (command.next === "cancelled") {
679
+ item.closedAt = now;
680
+ item.closureReason = command.reason;
681
+ }
682
+ delete item.owner;
683
+ delete item.transfer;
684
+ delete item.estimate;
685
+ } else if (command.type === "link-reference") attachReference(item, command.reference);
686
+ else {
687
+ requireValue(!terminal(item), "ITEM_COMPLETED");
688
+ if (item.status === "claimed") owned(item, actor, command.token ?? "", now);
689
+ if (command.type === "block-item") {
690
+ text(command.reason);
691
+ item.blocked = { reason: command.reason, at: now };
692
+ } else delete item.blocked;
693
+ }
694
+ item.updatedAt = now;
695
+ item.revision++;
696
+ }
697
+ function updatePlan(state, item, command, now) {
698
+ if (command.type === "close-plan") requireValue(item.control?.action !== "cancel", "TASK_CANCELLING");
699
+ if (command.type === "cancel-item") requireValue(item.launch?.admittedAt === void 0, "TASK_CONTROL_REQUIRED");
700
+ if (command.type === "publish-proposal") {
701
+ requireValue(item.status === "proposed", "NOT_PROPOSED");
702
+ return setStage(state, item, command.stage ?? "backlog", now);
703
+ }
704
+ if (command.type === "set-stage") return setStage(state, item, command.stage, now);
705
+ requireValue(["proposed", "backlog", "open"].includes(item.status), "ITEM_NOT_PLANNABLE");
706
+ if (command.type === "edit-item") {
707
+ text(command.title);
708
+ text(command.responsibility);
709
+ text(command.acceptance);
710
+ item.title = command.title;
711
+ item.responsibility = command.responsibility;
712
+ item.acceptance = command.acceptance;
713
+ if (item.status === "open") return setStage(state, item, "backlog", now);
714
+ } else if (command.type === "plan-item") {
715
+ validatePlan(state, item, command.planning);
716
+ item.planning = command.planning;
717
+ if (item.status === "open") return setStage(state, item, "backlog", now);
718
+ } else {
719
+ requireValue(catalog(state).filter((i) => i.planning.parentId === item.id).every(terminal), "CHILDREN_UNFINISHED");
720
+ if (command.type === "close-plan") {
721
+ requireValue(!executable(state.board, item), "TASK_REQUIRES_REVIEW");
722
+ text(command.resultRef);
723
+ const children = catalog(state).filter((child) => child.planning.parentId === item.id);
724
+ if (item.planning?.execution === "children") requireValue(children.length > 0 && children.every((child) => child.status === "completed"), "CHILDREN_UNFINISHED");
725
+ item.status = "completed";
726
+ item.resultRef = command.resultRef;
727
+ } else {
728
+ text(command.reason);
729
+ item.status = "cancelled";
730
+ item.closureReason = command.reason;
731
+ }
732
+ item.closedAt = now;
733
+ }
734
+ item.updatedAt = now;
735
+ item.revision++;
736
+ }
737
+ function attachReference(item, reference) {
738
+ identity(reference.system);
739
+ identity(reference.id);
740
+ if (reference.revision !== void 0) text(reference.revision);
741
+ item.externalRefs ??= [];
742
+ if (item.externalRefs.some((r) => r.system === reference.system && r.id === reference.id && r.revision === reference.revision)) return;
743
+ requireValue(item.externalRefs.length < 100, "REFERENCE_LIMIT");
744
+ item.externalRefs.push(reference);
745
+ }
746
+
747
+ // src/task-commands.ts
748
+ var types = ["set-launch-mode", "retry-analysis", "begin-edit", "end-edit", "save-edit", "request-run", "admit-item", "report-analysis", "request-control", "control-receipt", "report-issue", "resolve-issue"];
749
+ function taskCommand(command) {
750
+ return types.includes(command.type);
751
+ }
752
+ function assertDefinitionEditable(item, now, editId, actor) {
753
+ requireValue(item.control?.action !== "cancel", "TASK_CANCELLING");
754
+ requireValue(["proposed", "backlog", "open"].includes(item.status) && !item.owner && item.launch?.admittedAt === void 0, "TASK_DEFINITION_FROZEN");
755
+ if (taskEditing(item, now)) requireValue(item.launch.editing.id === editId && !!actor && principalKey(item.launch.editing.principal) === principalKey(principal(actor)), "EDIT_LEASE_ACTIVE");
756
+ }
757
+ function applyTaskCommand(state, command, actor, now) {
758
+ const item = state.board.items.find((i) => i.id === command.itemId);
759
+ requireValue(item, "ITEM_NOT_FOUND");
760
+ item.launch ??= { startAt: taskStartAt(item) };
761
+ if (["begin-edit", "end-edit", "save-edit"].includes(command.type)) edit(state, item, command, actor, now);
762
+ else if (command.type === "retry-analysis") {
763
+ identity(command.retryId);
764
+ requireValue(["failed", "clarification"].includes(item.analysis?.state ?? "") && !item.owner, "ANALYSIS_NOT_RETRYABLE");
765
+ item.launch.analysisEpoch = command.retryId;
766
+ delete item.analysis;
767
+ } else if (command.type === "set-launch-mode") {
768
+ assertDefinitionEditable(item, now);
769
+ setMode(item, command.mode);
770
+ } else if (command.type === "request-run") {
771
+ assertDefinitionEditable(item, now);
772
+ item.launch.requestedAt ??= now;
773
+ } else if (command.type === "admit-item") {
774
+ requireCoordinator(state, actor);
775
+ assertDefinitionEditable(item, now);
776
+ requireValue(boardAllowsStart(state.board, item), "BOARD_EXECUTION_PAUSED");
777
+ requireValue(!item.control || item.control.action === "resume" && item.control.state === "resumed", "TASK_CONTROL_PENDING");
778
+ requireValue(command.trigger === "manual" ? item.launch.requestedAt !== void 0 : taskDue(item, now), "START_NOT_DUE");
779
+ requireValue(!item.blocked && item.control?.action !== "pause", "ITEM_BLOCKED");
780
+ requireValue(item.planning?.dependencies.every((id) => catalog(state).find((i) => i.id === id)?.status === "completed") ?? true, "DEPENDENCIES_UNFINISHED");
781
+ item.launch.admittedAt = now;
782
+ } else if (command.type === "report-analysis") {
783
+ requireCoordinator(state, actor);
784
+ identity(command.operationId);
785
+ requireValue(command.status.itemId === item.id, "ANALYSIS_SCOPE_MISMATCH");
786
+ requireValue(item.control?.action !== "cancel" && !["completed", "cancelled"].includes(item.status), "TASK_CANCELLING");
787
+ item.analysis = structuredClone(command.status);
788
+ } else if (command.type === "request-control") {
789
+ requestControl(state, item, command);
790
+ if (command.action === "cancel") cancelChildren(state, item, command.controlId);
791
+ } else if (command.type === "control-receipt") applyControlReceipt(state, item, command, actor, now);
792
+ else issues(state, item, command, actor);
793
+ item.updatedAt = now;
794
+ item.revision++;
795
+ }
796
+ function edit(state, item, command, actor, now) {
797
+ if (command.type === "begin-edit") {
798
+ assertDefinitionEditable(item, now, command.editId, actor);
799
+ identity(command.editId);
800
+ requireValue(Number.isSafeInteger(command.leaseMs) && command.leaseMs >= 1e3 && command.leaseMs <= 3e5, "INVALID_EDIT_LEASE");
801
+ item.launch.editing = { id: command.editId, principal: principal(actor), expiresAt: now + command.leaseMs };
802
+ return;
803
+ }
804
+ if (command.type !== "save-edit" && command.type !== "end-edit") return;
805
+ assertDefinitionEditable(item, now, command.editId, actor);
806
+ requireValue(taskEditing(item, now) && item.launch.editing.id === command.editId, "EDIT_LEASE_EXPIRED");
807
+ if (command.type === "save-edit") {
808
+ text(command.title);
809
+ text(command.responsibility);
810
+ text(command.acceptance);
811
+ validatePlan(state, item, command.planning);
812
+ if (command.launchMode !== void 0) setMode(item, command.launchMode);
813
+ Object.assign(item, { title: command.title, responsibility: command.responsibility, acceptance: command.acceptance, planning: command.planning });
814
+ if (command.externalRefs) {
815
+ item.externalRefs = [];
816
+ for (const ref of command.externalRefs) attachReference(item, ref);
817
+ }
818
+ }
819
+ if (command.type === "save-edit") for (const plan2 of Object.values(state.workflow?.plans ?? {})) {
820
+ const definition = plan2.definitions[item.id];
821
+ if (!definition) continue;
822
+ requireValue(Object.values(definition.inputs ?? {}).every((binding) => item.planning?.dependencies.includes(binding.fromItemId)), "EXISTING_DEPENDENCY_REQUIRED");
823
+ definition.digest = sha256HexSync(JSON.stringify([item.title, item.responsibility, item.acceptance, item.planning, item.externalRefs, definition.inputs, definition.outputs]));
824
+ }
825
+ delete item.launch.editing;
826
+ }
827
+ function requestControl(state, item, command) {
828
+ requireValue(!["completed", "cancelled"].includes(item.status), "ITEM_COMPLETED");
829
+ identity(command.controlId);
830
+ requireValue(["pause", "resume", "cancel"].includes(command.action), "INVALID_CONTROL_ACTION");
831
+ if (item.control?.id === command.controlId) {
832
+ requireValue(item.control.action === command.action, "IDEMPOTENCY_CONFLICT");
833
+ return;
834
+ }
835
+ if (command.action === "cancel" && item.control?.action === "cancel" && item.control.state !== "failed") return;
836
+ requireValue(command.action === "cancel" || !item.control || !["requested", "unknown"].includes(item.control.state), "CONTROL_UNSETTLED");
837
+ const dispatch = Object.values(state.workflow?.dispatches ?? {}).filter((d) => d.itemId === item.id).sort((a, b) => b.attempt - a.attempt)[0];
838
+ requireValue(!item.owner || dispatch || command.action === "cancel", "TASK_CONTROL_UNSUPPORTED");
839
+ requireValue(command.action !== "resume" || item.control?.action === "pause", "TASK_NOT_PAUSED");
840
+ item.control = { id: command.controlId, dispatchId: dispatch?.id, action: command.action, state: "requested" };
841
+ }
842
+ function issues(state, item, command, actor) {
843
+ requireCoordinator(state, actor);
844
+ if (command.type === "report-issue") {
845
+ const issue = command.issue;
846
+ identity(issue.id);
847
+ identity(issue.attemptId);
848
+ requireValue(!issue.resolvedBy && ["clarification", "error"].includes(issue.type), "INVALID_ISSUE");
849
+ requireValue(state.workflow?.dispatches[issue.attemptId]?.itemId === item.id && item.owner?.token === state.workflow.dispatches[issue.attemptId].fence, "STALE_EXECUTION_RESULT");
850
+ item.issues ??= [];
851
+ const old = item.issues.find((i) => i.id === issue.id && i.attemptId === issue.attemptId);
852
+ if (!old) {
853
+ requireValue(item.issues.length < 200, "ISSUE_LIMIT");
854
+ item.issues.push(structuredClone(issue));
855
+ }
856
+ } else if (command.type === "resolve-issue") {
857
+ const issue = item.issues?.find((i) => i.id === command.issueId && i.attemptId === command.attemptId);
858
+ requireValue(issue, "ISSUE_NOT_FOUND");
859
+ identity(command.resolvedBy);
860
+ requireValue(!issue.resolvedBy || issue.resolvedBy === command.resolvedBy, "ISSUE_ALREADY_RESOLVED");
861
+ issue.resolvedBy = command.resolvedBy;
862
+ }
863
+ }
864
+ function initializeTaskLaunch(state, item) {
865
+ if (item.launch) return false;
866
+ const first = Object.values(state.workflow?.dispatches ?? {}).filter((d) => d.itemId === item.id).sort((a, b) => a.attempt - b.attempt)[0];
867
+ const admittedAt = first?.preparedAt ?? item.owner?.claimedAt;
868
+ item.launch = { startAt: taskStartAt(item), ...admittedAt === void 0 ? {} : { admittedAt } };
869
+ item.revision++;
870
+ return true;
871
+ }
872
+ function setMode(item, mode) {
873
+ requireValue(["automatic", "manual"].includes(mode), "INVALID_LAUNCH_MODE");
874
+ item.launch.mode = mode;
875
+ delete item.launch.requestedAt;
876
+ }
877
+ function cancelChildren(state, parent, controlId) {
878
+ const ids = /* @__PURE__ */ new Set([parent.id]);
879
+ for (let pass = 0; pass < state.board.items.length; pass++) {
880
+ const children = state.board.items.filter((item) => item.planning?.parentId && ids.has(item.planning.parentId) && !ids.has(item.id));
881
+ if (!children.length) return;
882
+ for (const child of children) {
883
+ ids.add(child.id);
884
+ if (["completed", "cancelled"].includes(child.status) || child.control?.action === "cancel") continue;
885
+ requestControl(state, child, { type: "request-control", itemId: child.id, controlId: sha256HexSync(JSON.stringify([controlId, child.id])), action: "cancel" });
886
+ child.updatedAt = parent.updatedAt;
887
+ child.revision++;
888
+ }
889
+ }
890
+ }
891
+ function applyControlReceipt(state, item, command, actor, now) {
892
+ requireCoordinator(state, actor);
893
+ requireValue(item.control?.id === command.controlId, "STALE_CONTROL_RECEIPT");
894
+ requireValue(["accepted", "paused", "resumed", "cancelled", "failed", "unknown"].includes(command.state), "INVALID_CONTROL_STATE");
895
+ requireValue(command.code === void 0 || /^[A-Z][A-Z0-9_]{0,79}$/.test(command.code), "INVALID_ERROR_CODE");
896
+ if (command.state === "cancelled") {
897
+ requireValue(item.control.action === "cancel", "INVALID_CONTROL_STATE");
898
+ for (const dispatch of Object.values(state.workflow?.dispatches ?? {}).filter((d) => d.itemId === item.id && ["prepared", "running", "unknown", "succeeded"].includes(d.outcome))) {
899
+ requireValue(dispatch.id === item.control.dispatchId && item.owner?.token === dispatch.fence, "STALE_EXECUTION_RESULT");
900
+ dispatch.outcome = "cancelled";
901
+ dispatch.code = "USER_CANCELLED";
902
+ }
903
+ requireValue(!catalog(state).some((i) => i.planning.parentId === item.id && !["completed", "cancelled"].includes(i.status)), "CHILDREN_UNFINISHED");
904
+ item.status = "cancelled";
905
+ item.closedAt = now;
906
+ item.closureReason = "USER_CANCELLED";
907
+ delete item.owner;
908
+ delete item.transfer;
909
+ delete item.blocked;
910
+ delete item.launch.editing;
911
+ }
912
+ requireValue(item.control.state !== "cancelled" || command.state === "cancelled", "STALE_CONTROL_RECEIPT");
913
+ item.control.state = command.state;
914
+ item.control.code = command.code;
915
+ }
916
+
917
+ // src/board-stop.ts
918
+ function stopBoard(state, command, actor, now) {
919
+ requireCoordinator(state, actor);
920
+ requireValue(actor.kind === "human", "FORBIDDEN");
921
+ identity(command.controlId);
922
+ if (command.type === "observe-board-stop") {
923
+ requireValue(state.board.lifecycle?.operationId === command.controlId, "STALE_CONTROL_RECEIPT");
924
+ requireValue(["waiting", "ready", "blocked"].includes(command.phase), "INVALID_CONTROL_STATE");
925
+ requireValue(command.code === void 0 || /^[A-Z][A-Z0-9_]{0,79}$/.test(command.code), "INVALID_ERROR_CODE");
926
+ state.board.lifecycle.phase = command.phase;
927
+ state.board.lifecycle.code = command.code;
928
+ return;
929
+ }
930
+ requireValue(["cancel", "delete"].includes(command.intent) && ["drain", "cancel"].includes(command.mode), "INVALID_BOARD_CONTROL");
931
+ const old = state.board.lifecycle;
932
+ if (old) {
933
+ requireValue(old.intent === "cancel" && command.intent === "delete" || old.operationId === command.controlId, "BOARD_STOP_PENDING");
934
+ if (old.operationId === command.controlId) return;
935
+ }
936
+ state.board.lifecycle = { operationId: command.controlId, intent: command.intent, mode: command.mode, phase: "waiting" };
937
+ state.board.executionControl = { ...state.board.executionControl, mode: "manual", paused: true, controls: {} };
938
+ if (state.board.automation) state.board.automation.enabled = false;
939
+ if (command.mode !== "cancel") return;
940
+ for (const item of state.board.items) if (!["completed", "cancelled"].includes(item.status)) applyTaskCommand(
941
+ state,
942
+ { type: "request-control", itemId: item.id, controlId: sha256HexSync(JSON.stringify([command.controlId, item.id])), action: "cancel" },
943
+ actor,
944
+ now
945
+ );
946
+ }
947
+ function protectStoppedBoard(state, command) {
948
+ if (!state.board.lifecycle) return;
949
+ if (command.type === "append-record" && command.record.kind === "decision") requireValue(false, "BOARD_STOPPING");
950
+ const fresh = ["create-item", "admit-item", "prepare-dispatch", "continue-execution", "publish-plan", "request-run", "apply-discovery", "begin-edit", "save-edit"];
951
+ requireValue(!fresh.includes(command.type), "BOARD_STOPPING");
952
+ if (command.type === "set-board-control") requireValue(command.paused !== false && command.mode !== "automatic", "BOARD_STOPPING");
953
+ if (command.type === "configure-automation") requireValue(!command.settings.enabled, "BOARD_STOPPING");
954
+ }
955
+
956
+ // src/board-control.ts
957
+ function setBoardControl(state, command, actor, now, token) {
958
+ requireValue(actor.kind === "human", "FORBIDDEN");
959
+ requireValue(command.mode !== void 0 || command.paused !== void 0, "INVALID_BOARD_CONTROL");
960
+ requireValue(command.mode === void 0 || ["automatic", "manual"].includes(command.mode), "INVALID_LAUNCH_MODE");
961
+ requireValue(command.paused === void 0 || typeof command.paused === "boolean", "INVALID_BOARD_CONTROL");
962
+ const policy2 = state.board.executionControl ??= { mode: "automatic", paused: false, controls: {} };
963
+ if (command.mode) policy2.mode = command.mode;
964
+ if (command.paused === void 0 || command.paused === policy2.paused) return;
965
+ if (!command.paused) requireValue(!state.board.items.some((item) => policy2.controls[item.id] === item.control?.id && ["requested", "unknown"].includes(item.control.state)), "CONTROL_UNSETTLED");
966
+ policy2.paused = command.paused;
967
+ if (policy2.paused) policy2.controls = {};
968
+ state.board.items.forEach((item, index) => {
969
+ if (["completed", "cancelled", "review"].includes(item.status)) return;
970
+ const active = item.analysis?.state === "analyzing" || item.launch?.admittedAt !== void 0 || Object.values(state.workflow?.dispatches ?? {}).some((d) => d.itemId === item.id && ["prepared", "running", "unknown"].includes(d.outcome));
971
+ const managed = policy2.controls[item.id] === item.control?.id;
972
+ if (policy2.paused ? !active || item.control?.action === "pause" : !managed || item.control?.action !== "pause") return;
973
+ if (item.control && ["requested", "unknown"].includes(item.control.state)) return;
974
+ const controlId = token + "-" + index;
975
+ applyTaskCommand(state, { type: "request-control", itemId: item.id, controlId, action: policy2.paused ? "pause" : "resume" }, actor, now);
976
+ policy2.controls[item.id] = controlId;
977
+ });
978
+ }
979
+
980
+ // src/scan-commit.ts
981
+ function applyDiscovery(state, batch, actor, now) {
982
+ const discovery = discoveryState(state), pending = discovery?.pending;
983
+ requireValue(pending && pending.runId === batch.runId && pending.token === batch.leaseToken && pending.leaseUntil > now, "SCAN_LEASE_EXPIRED");
984
+ requireValue(pending.request?.command.type === "apply-discovery" && JSON.stringify(pending.request.command.batch) === JSON.stringify(batch), "SCAN_BATCH_MISMATCH");
985
+ requireValue(batch.changes.length <= 50, "SCAN_CHANGE_LIMIT");
986
+ for (const change of batch.changes) {
987
+ identity(change.itemId);
988
+ validateSources(change.sources);
989
+ requireValue(change.sources.length > 0 && change.sources.some((s) => s.role === "user"), "USER_EVIDENCE_REQUIRED");
990
+ const item = change.action === "create" ? createItem(state, change, actor, now, batch.autoCreate, batch.runId) : updateItem(state, change, actor, now, batch.runId);
991
+ if (item) attachSources(item, change, now);
992
+ }
993
+ requireValue((batch.records?.length ?? 0) <= 20, "SCAN_RECORD_LIMIT");
994
+ for (const record of batch.records ?? []) {
995
+ requireValue(record.sources.some((source) => source.role === "user"), "USER_EVIDENCE_REQUIRED");
996
+ const duplicate = state.records?.some((old) => recordFingerprint(old) === recordFingerprint(record));
997
+ if (!duplicate) {
998
+ applyRecord(state, { type: "append-record", record }, actor, now, true);
999
+ state.records.find((entry) => entry.id === record.id).originRunId = batch.runId;
1000
+ }
1001
+ }
1002
+ for (const checkpoint of batch.checkpoints) discovery.checkpoints[checkpoint.sessionId] = checkpoint.revision;
1003
+ discovery.lastRun = { runId: batch.runId, outcome: "committed", at: now };
1004
+ delete discovery.pending;
1005
+ }
1006
+ function createItem(state, change, actor, now, autoCreate, runId) {
1007
+ text(change.title);
1008
+ text(change.responsibility);
1009
+ text(change.acceptance);
1010
+ const item = {
1011
+ origin: { kind: "discovery", principal: principal(actor), runId },
1012
+ id: change.itemId,
1013
+ title: change.title,
1014
+ responsibility: change.responsibility,
1015
+ acceptance: change.acceptance,
1016
+ eligibleAgentIds: change.eligibleAgentIds,
1017
+ eligiblePrincipals: change.eligiblePrincipals,
1018
+ status: "proposed",
1019
+ revision: 1,
1020
+ createdAt: now,
1021
+ readyAt: now,
1022
+ updatedAt: now,
1023
+ workUnits: 0,
1024
+ participants: [],
1025
+ sources: []
1026
+ };
1027
+ item.launch = { startAt: now + TASK_START_DELAY_MS };
1028
+ item.planning = change.planning ?? { ...plan(item), kind: state.board.defaultWorkType ?? "task" };
1029
+ const fingerprint = workFingerprint(item);
1030
+ item.origin.fingerprint = fingerprint;
1031
+ validateCandidates(state.board, item, executable(state.board, item));
1032
+ if (state.archiveIndex?.some((old) => old.discoveryFingerprint === fingerprint)) return void 0;
1033
+ const existing = state.board.items.find((old) => workFingerprint(old) === fingerprint || old.origin?.fingerprint === fingerprint);
1034
+ if (existing) {
1035
+ existing.revision++;
1036
+ existing.updatedAt = now;
1037
+ return existing;
1038
+ }
1039
+ requireValue(!state.board.items.some((old) => old.id === change.itemId) && !state.archiveIndex?.some((old) => old.id === change.itemId), "ITEM_EXISTS");
1040
+ validatePlan(state, item, item.planning);
1041
+ state.board.items.push(item);
1042
+ if (autoCreate) setStage(state, item, automaticStage(state, item), now);
1043
+ if (actor.sessionId) participant(item.participants, actor.sessionId, actor.agentId, "creator", true, now);
1044
+ return item;
1045
+ }
1046
+ function updateItem(state, change, actor, now, runId) {
1047
+ const item = state.board.items.find((i) => i.id === change.itemId);
1048
+ requireValue(item, "ITEM_NOT_FOUND");
1049
+ if (change.action === "update") {
1050
+ requireValue(!change.planning, "SCAN_PLANNING_REQUIRES_REVIEW");
1051
+ const ownDraft = item.status === "proposed" && item.origin?.kind === "discovery" && principalKey(item.origin.principal) === principalKey(principal(actor));
1052
+ if (ownDraft) {
1053
+ text(change.title);
1054
+ text(change.responsibility);
1055
+ text(change.acceptance);
1056
+ item.title = change.title;
1057
+ item.responsibility = change.responsibility;
1058
+ item.acceptance = change.acceptance;
1059
+ } else proposeAmendment(item, change, actor, now, runId);
1060
+ } else requireValue(change.action === "link", "INVALID_CHANGE");
1061
+ item.revision++;
1062
+ item.updatedAt = now;
1063
+ return item;
1064
+ }
1065
+ function attachSources(item, change, now) {
1066
+ for (const source of change.sources) {
1067
+ const subscribed = item.participants.find((p) => p.sessionId === source.sessionId)?.subscribed ?? false;
1068
+ participant(item.participants, source.sessionId, void 0, source.role === "user" ? "creator" : "consultant", subscribed, now);
1069
+ item.sources ??= [];
1070
+ if (!item.sources.some((s) => s.sessionId === source.sessionId && s.messageId === source.messageId && s.revision === source.revision)) item.sources.push(source);
1071
+ }
1072
+ }
1073
+
1074
+ // src/automation.ts
1075
+ function configureAutomation(board, settings, actor) {
1076
+ requireValue(settings && typeof settings.enabled === "boolean" && typeof settings.autoAccept === "boolean", "INVALID_AUTOMATION");
1077
+ for (const ref of [settings.planner, settings.executor, ...settings.preparation ? [settings.preparation] : []]) {
1078
+ requireValue(ref && typeof ref === "object", "INVALID_AUTOMATION_TARGET");
1079
+ identity(ref.system);
1080
+ identity(ref.id);
1081
+ identity(ref.revision ?? "");
1082
+ }
1083
+ validateOptions(settings.preparationOptions);
1084
+ requireValue(settings.preparation || !Object.keys(settings.preparationOptions ?? {}).length, "PREPARATION_REQUIRED");
1085
+ const members = [...board.members ?? [], ...(board.agents ?? []).filter((agent) => !board.members?.some((member) => member.kind === "agent" && member.id === agent.agentId)).map((agent) => ({ kind: "agent", id: agent.agentId, responsibility: agent.responsibility }))];
1086
+ for (const [principal2, role] of [[settings.coordinator, "coordinator"], [settings.worker, "worker"]]) {
1087
+ requireValue(principal2 && ["agent", "human", "service"].includes(principal2.kind), "INVALID_PRINCIPAL");
1088
+ identity(principal2.id);
1089
+ const existing = members.find((member) => member.kind === principal2.kind && member.id === principal2.id);
1090
+ if (existing) existing.roles = [.../* @__PURE__ */ new Set([...existing.roles ?? [], role])];
1091
+ else members.push({ ...principal2, responsibility: role, roles: [role] });
1092
+ }
1093
+ addReviewer(members, actor);
1094
+ board.members = members;
1095
+ delete board.agents;
1096
+ board.automation = structuredClone(settings);
1097
+ board.policy.review = settings.autoAccept ? "assignee" : "independent";
1098
+ validateBoard(board);
1099
+ }
1100
+ function addReviewer(members, actor) {
1101
+ if (actor.kind !== "human" || !actor.id) return;
1102
+ const existing = members.find((member) => member.kind === "human" && member.id === actor.id);
1103
+ if (existing) existing.roles = [.../* @__PURE__ */ new Set([...existing.roles ?? [], "reviewer", "coordinator"])];
1104
+ else members.push({ kind: "human", id: actor.id, responsibility: "Review work", roles: ["reviewer", "coordinator"] });
1105
+ }
1106
+ function validateOptions(options) {
1107
+ if (options === void 0) return;
1108
+ requireValue(options && typeof options === "object" && !Array.isArray(options) && Object.keys(options).length <= 16, "INVALID_PREPARATION_OPTIONS");
1109
+ for (const [key, value] of Object.entries(options)) {
1110
+ requireValue(/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/.test(key) && typeof value === "string" && value.length <= 4096, "INVALID_PREPARATION_OPTIONS");
1111
+ }
1112
+ }
1113
+
1114
+ // src/workflow-continuation.ts
1115
+ function continueExecution(state, command, actor, now, apply) {
1116
+ requireCoordinator(state, actor);
1117
+ identity(command.dispatchId);
1118
+ const workflow = state.workflow, previous = workflow.dispatches[command.previousDispatchId];
1119
+ requireValue(previous && ["failed", "cancelled"].includes(previous.outcome), "EXECUTION_NOT_CONTINUABLE");
1120
+ requireValue(!workflow.dispatches[command.dispatchId], "DISPATCH_EXISTS");
1121
+ const item = state.board.items.find((item2) => item2.id === previous.itemId), plan2 = workflow.plans[previous.planId];
1122
+ requireValue(item && ["backlog", "open"].includes(item.status) && !item.owner, "ITEM_NOT_CONTINUABLE");
1123
+ const attempts = Object.values(workflow.dispatches).filter((row) => row.itemId === item.id);
1124
+ requireValue(attempts.every((row) => row.attempt <= previous.attempt), "STALE_CONTINUATION");
1125
+ requireValue(!attempts.some((row) => ["prepared", "running", "unknown", "succeeded"].includes(row.outcome)), "EXECUTION_UNSETTLED");
1126
+ requireValue(plan2?.revision === previous.planRevision && plan2.definitions[item.id]?.digest === previous.definitionDigest, "PLAN_CHANGED");
1127
+ requireValue(command.instruction === void 0 || typeof command.instruction === "string" && command.instruction.trim().length > 0 && command.instruction.length <= 8e3, "INVALID_CONTINUATION_INSTRUCTION");
1128
+ requireValue(!item.blocked || item.blocked.reason === (previous.code ?? "EXECUTION_FAILED"), "ITEM_BLOCKED");
1129
+ delete item.blocked;
1130
+ if (item.status === "backlog") apply({ type: "set-stage", itemId: item.id, stage: "open" });
1131
+ apply({ type: "claim", itemId: item.id, estimate: item.estimate }, previous.principal);
1132
+ workflow.dispatches[command.dispatchId] = {
1133
+ id: command.dispatchId,
1134
+ itemId: item.id,
1135
+ itemRevision: item.revision,
1136
+ planId: previous.planId,
1137
+ planRevision: previous.planRevision,
1138
+ principal: previous.principal,
1139
+ definitionDigest: previous.definitionDigest,
1140
+ attempt: previous.attempt + 1,
1141
+ fence: item.owner.token,
1142
+ preparedAt: now,
1143
+ outcome: "prepared",
1144
+ inputs: structuredClone(previous.inputs),
1145
+ continuation: { previousDispatchId: previous.id, ...command.instruction ? { instruction: command.instruction } : {} }
1146
+ };
1147
+ }
1148
+
1149
+ // src/workflow.ts
1150
+ function isWorkflowCommand(command) {
1151
+ return ["publish-plan", "prepare-dispatch", "continue-execution", "observe-execution", "accept-execution"].includes(command.type);
1152
+ }
1153
+ function protectWorkflowItem(state, command) {
1154
+ if (command.type === "configure-automation" && Object.values(state.workflow?.dispatches ?? {}).some((dispatch) => ["prepared", "running", "unknown", "succeeded"].includes(dispatch.outcome))) {
1155
+ requireValue(sameAutomationBinding(state.board.automation, command.settings), "PLAN_EXECUTION_UNSETTLED");
1156
+ }
1157
+ if (!("itemId" in command) || !command.itemId || !state.workflow) return;
1158
+ const plan2 = Object.values(state.workflow.plans).find((plan3) => plan3.itemIds.includes(command.itemId));
1159
+ if (!plan2) return;
1160
+ requireValue(!["edit-item", "plan-item", "review-amendment"].includes(command.type), "PLAN_ITEM_CHANGED_REQUIRES_NEW_ID");
1161
+ if (plan2.sealed) requireValue(!["claim", "submit", "review", "close-plan"].includes(command.type), "WORKFLOW_COMMAND_REQUIRED");
1162
+ const live = Object.values(state.workflow.dispatches).some((dispatch) => dispatch.itemId === command.itemId && ["prepared", "running", "unknown", "succeeded"].includes(dispatch.outcome));
1163
+ if (live) requireValue(!["delete-item", "request-transfer", "accept-transfer", "release-item", "cancel-item"].includes(command.type), "EXECUTION_UNSETTLED");
1164
+ }
1165
+ function applyWorkflow(state, command, actor, now, apply) {
1166
+ state.workflow ??= { plans: {}, dispatches: {}, ready: [], completedPlans: [] };
1167
+ let changed = true;
1168
+ if (command.type === "publish-plan") publishPlan(state, command.plan, actor, apply);
1169
+ else if (command.type === "prepare-dispatch") prepare(state, command, actor, now, apply);
1170
+ else if (command.type === "continue-execution") {
1171
+ requireValue(!state.board.executionControl?.paused, "BOARD_EXECUTION_PAUSED");
1172
+ continueExecution(state, command, actor, now, apply);
1173
+ } else if (command.type === "observe-execution") changed = observe(state, command, actor, now);
1174
+ else accept(state, command, actor, apply);
1175
+ refreshReady(state);
1176
+ return changed;
1177
+ }
1178
+ function prepare(state, command, actor, now, apply) {
1179
+ identity(command.dispatchId);
1180
+ const workflow = state.workflow;
1181
+ const controlled = state.board.items.find((item2) => item2.id === command.itemId);
1182
+ requireValue(!controlled?.control || controlled.control.action === "resume" && controlled.control.state === "resumed", "TASK_CONTROL_PENDING");
1183
+ requireValue(!workflow.dispatches[command.dispatchId], "DISPATCH_EXISTS");
1184
+ refreshReady(state);
1185
+ requireValue(workflow.ready.includes(command.itemId), "WORK_NOT_READY");
1186
+ const plan2 = Object.values(workflow.plans).find((plan3) => plan3.itemIds.includes(command.itemId));
1187
+ requireValue(!Object.values(workflow.dispatches).some((dispatch) => dispatch.itemId === command.itemId && ["prepared", "running", "unknown", "succeeded"].includes(dispatch.outcome)), "EXECUTION_UNSETTLED");
1188
+ const item = state.board.items.find((item2) => item2.id === command.itemId);
1189
+ if (item.status === "backlog") apply({ type: "set-stage", itemId: item.id, stage: "open" });
1190
+ requireValue(!taskEditing(item, now), "EDIT_LEASE_ACTIVE");
1191
+ requireValue(!state.board.executionControl?.paused, "BOARD_EXECUTION_PAUSED");
1192
+ if (command.trigger === "automatic") requireValue(boardAllowsStart(state.board, item) && taskDue(item, now), "START_NOT_DUE");
1193
+ item.launch ??= { startAt: taskStartAt(item) };
1194
+ item.launch.admittedAt ??= now;
1195
+ apply({ type: "claim", itemId: item.id, estimate: command.estimate });
1196
+ const inputs = {};
1197
+ for (const [name, binding] of Object.entries(plan2.definitions[item.id].inputs ?? {})) {
1198
+ const producer = Object.values(workflow.dispatches).reverse().find((dispatch) => dispatch.itemId === binding.fromItemId && dispatch.outcome === "accepted");
1199
+ const output = producer?.outputs?.find((output2) => output2.name === binding.output);
1200
+ requireValue(output, "DEPENDENCY_OUTPUT_MISSING");
1201
+ inputs[name] = structuredClone(output);
1202
+ }
1203
+ const attempt = Math.max(0, ...Object.values(workflow.dispatches).filter((dispatch) => dispatch.itemId === item.id).map((dispatch) => dispatch.attempt)) + 1;
1204
+ workflow.dispatches[command.dispatchId] = { id: command.dispatchId, itemId: item.id, itemRevision: item.revision, planId: plan2.id, planRevision: plan2.revision, principal: principal(actor), definitionDigest: plan2.definitions[item.id].digest, attempt, fence: item.owner.token, preparedAt: now, outcome: "prepared", inputs };
1205
+ }
1206
+ function observe(state, command, actor, now) {
1207
+ requireCoordinator(state, actor);
1208
+ const dispatch = activeDispatch(state, command.dispatchId);
1209
+ requireValue(command.fence === dispatch.fence && ["prepared", "running", "unknown"].includes(dispatch.outcome), "STALE_EXECUTION_RESULT");
1210
+ requireValue(["running", "unknown", "succeeded", "failed", "cancelled"].includes(command.outcome), "INVALID_EXECUTION_OUTCOME");
1211
+ if (command.code !== void 0) requireValue(/^[A-Z][A-Z0-9_]{0,79}$/.test(command.code), "INVALID_ERROR_CODE");
1212
+ const item = state.board.items.find((item2) => item2.id === dispatch.itemId);
1213
+ if (command.executionSessionId) {
1214
+ identity(command.executionSessionId);
1215
+ requireValue(!dispatch.executionSessionId || dispatch.executionSessionId === command.executionSessionId, "EXECUTION_SESSION_LOCKED");
1216
+ dispatch.executionSessionId = command.executionSessionId;
1217
+ }
1218
+ if (command.controlCapabilities) {
1219
+ requireValue(["boundary", "none"].includes(command.controlCapabilities.pause) && typeof command.controlCapabilities.resume === "boolean", "INVALID_CONTROL_CAPABILITIES");
1220
+ dispatch.controlCapabilities = structuredClone(command.controlCapabilities);
1221
+ }
1222
+ const outputs = command.outputs ?? [];
1223
+ validateOutputs(outputs);
1224
+ if (["running", "unknown"].includes(command.outcome)) item.owner.leaseUntil = Math.max(item.owner.leaseUntil, now + state.board.policy.leaseMs);
1225
+ if (!executionObservationChanged(dispatch, command)) return false;
1226
+ if (command.outcome === "succeeded") {
1227
+ const required = state.workflow.plans[dispatch.planId].definitions[item.id].outputs;
1228
+ requireValue(required.every((name) => outputs.some((output) => output.name === name)), "REQUIRED_OUTPUT_MISSING");
1229
+ item.status = state.board.policy.review === "assignee" ? "completed" : "review";
1230
+ if (item.status === "completed") item.closedAt = now;
1231
+ item.resultRef = `kanban:dispatch:${dispatch.id}`;
1232
+ dispatch.outputs = structuredClone(outputs);
1233
+ } else if (command.outcome === "failed" || command.outcome === "cancelled") {
1234
+ item.status = "backlog";
1235
+ item.blocked = { reason: command.code ?? "EXECUTION_FAILED", at: now };
1236
+ delete item.owner;
1237
+ }
1238
+ dispatch.outcome = command.outcome === "succeeded" && state.board.policy.review === "assignee" ? "accepted" : command.outcome;
1239
+ dispatch.code = command.code;
1240
+ item.updatedAt = now;
1241
+ item.revision++;
1242
+ dispatch.itemRevision = item.revision;
1243
+ return true;
1244
+ }
1245
+ function accept(state, command, actor, apply) {
1246
+ requireCoordinator(state, actor);
1247
+ const dispatch = activeDispatch(state, command.dispatchId);
1248
+ requireValue(state.board.items.find((item) => item.id === dispatch.itemId)?.control?.action !== "cancel", "TASK_CANCELLING");
1249
+ requireValue(dispatch.outcome === "succeeded", "EXECUTION_NOT_SUCCEEDED");
1250
+ apply({ type: "review", itemId: dispatch.itemId, accept: command.accept }, actor);
1251
+ dispatch.outcome = command.accept ? "accepted" : "rejected";
1252
+ if (!command.accept) {
1253
+ const item = state.board.items.find((item2) => item2.id === dispatch.itemId);
1254
+ item.status = "backlog";
1255
+ item.blocked = { reason: "EXECUTION_REJECTED", at: item.updatedAt };
1256
+ }
1257
+ }
1258
+ function activeDispatch(state, id) {
1259
+ const dispatch = state.workflow.dispatches[id];
1260
+ requireValue(dispatch, "DISPATCH_NOT_FOUND");
1261
+ const item = state.board.items.find((item2) => item2.id === dispatch.itemId), plan2 = state.workflow.plans[dispatch.planId];
1262
+ requireValue(plan2?.revision === dispatch.planRevision && item?.owner?.token === dispatch.fence && plan2.definitions[dispatch.itemId]?.digest === dispatch.definitionDigest, "STALE_EXECUTION_RESULT");
1263
+ return dispatch;
1264
+ }
1265
+ function validateOutputs(outputs) {
1266
+ requireValue(Array.isArray(outputs) && outputs.length <= 100 && outputs.every((output) => output && typeof output === "object" && typeof output.name === "string") && new Set(outputs.map((output) => output.name)).size === outputs.length, "INVALID_OUTPUTS");
1267
+ for (const output of outputs) requireValue(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(output.name) && typeof output.ref === "string" && output.ref.length > 0 && output.ref.length <= 2048 && /^[a-f0-9]{64}$/.test(output.sha256) && Number.isSafeInteger(output.bytes) && output.bytes >= 0, "INVALID_OUTPUT_REFERENCE");
1268
+ }
1269
+ function refreshReady(state) {
1270
+ if (!state.workflow) return;
1271
+ const all = catalog(state), included = new Set(Object.values(state.workflow.plans).filter((plan2) => plan2.sealed).flatMap((plan2) => plan2.itemIds));
1272
+ state.workflow.completedPlans = Object.values(state.workflow.plans).filter((plan2) => plan2.sealed && plan2.itemIds.every((id) => all.find((item) => item.id === id)?.status === "completed")).map((plan2) => plan2.id);
1273
+ state.workflow.ready = state.board.items.filter((item) => included.has(item.id) && executableWorkItem(state.board, item) && ["backlog", "open"].includes(item.status) && !item.owner && !item.blocked && (!item.control || item.control.action === "resume" && item.control.state === "resumed") && (item.planning?.dependencies ?? []).every((id) => all.find((other) => other.id === id)?.status === "completed")).map((item) => item.id);
1274
+ }
1275
+
1276
+ // src/transitions.ts
1277
+ function transition(state, item, command, actor, now, token) {
1278
+ requireValue(item.status !== "completed" && item.status !== "cancelled", "ITEM_COMPLETED");
1279
+ if (command.type === "claim" || command.type === "accept-transfer") claim(state, item, command, actor, now, token);
1280
+ else if (command.type === "review") review(state, item, command.accept, actor, now);
1281
+ else {
1282
+ owned(item, actor, command.token, now);
1283
+ if (command.type === "submit") requireValue(catalog(state).filter((i) => i.planning.parentId === item.id).every(terminal), "CHILDREN_UNFINISHED");
1284
+ ownerUpdate(state.board, item, command, now, token);
1285
+ }
1286
+ if (command.type !== "renew") {
1287
+ item.revision++;
1288
+ item.updatedAt = now;
1289
+ }
1290
+ }
1291
+ function claim(state, item, command, actor, now, token) {
1292
+ const board = state.board, key = principalKey(principal(actor));
1293
+ requireValue(!taskEditing(item, now), "EDIT_LEASE_ACTIVE");
1294
+ eligible(board, item, actor);
1295
+ assertReady(state, item);
1296
+ requireValue(command.estimate || board.policy.requireEstimate === false, "ESTIMATE_REQUIRED");
1297
+ if (command.estimate) validateEstimate(command.estimate);
1298
+ const wip = board.items.filter((i) => i.status === "claimed" || i.status === "review");
1299
+ if (command.type === "claim") requireValue(wip.length < limits(state).inProgress, "WIP_LIMIT");
1300
+ requireValue(wip.filter((i) => i.id !== item.id && i.owner && assigneeKey(i.owner) === key).length < limits(state).perAssignee, principal(actor).kind === "agent" ? "AGENT_WIP_LIMIT" : "ASSIGNEE_WIP_LIMIT");
1301
+ if (command.type === "claim") requireValue(item.status === "open" && !item.owner, "ALREADY_OWNED");
1302
+ else {
1303
+ requireValue(item.status === "claimed" && item.transfer && principalKey(transferTarget(item.transfer)) === key, "TRANSFER_NOT_OFFERED");
1304
+ requireValue(item.transfer.expiresAt > now && item.owner && item.owner.leaseUntil > now, "TRANSFER_EXPIRED");
1305
+ requireValue(assigneeKey(item.owner) !== key, "SELF_TRANSFER");
1306
+ }
1307
+ item.owner = { ...actor, principalId: key, token, claimedAt: now, leaseUntil: now + board.policy.leaseMs };
1308
+ item.estimate = command.estimate;
1309
+ item.status = "claimed";
1310
+ item.progressAt = now;
1311
+ delete item.transfer;
1312
+ if (actor.sessionId) participant(item.participants, actor.sessionId, actor.agentId, "owner", true, now);
1313
+ }
1314
+ function review(state, item, accept2, actor, now) {
1315
+ requireValue(item.control?.action !== "cancel", "TASK_CANCELLING");
1316
+ requireValue(item.status === "review", "NOT_IN_REVIEW");
1317
+ const key = principalKey(principal(actor));
1318
+ const registered = actor.sessionId && item.participants.some((p) => p.sessionId === actor.sessionId && p.roles.includes("reviewer")) || state.board.members?.some((m) => principalKey(m) === key && m.roles?.includes("reviewer"));
1319
+ requireValue(registered, "REVIEWER_REQUIRED");
1320
+ requireValue(!item.owner || assigneeKey(item.owner) !== key, "INDEPENDENT_REVIEW_REQUIRED");
1321
+ item.status = accept2 ? "completed" : state.board.items.filter((i) => i.status === "open").length < limits(state).ready ? "open" : "backlog";
1322
+ if (accept2) item.closedAt = now;
1323
+ if (!accept2) {
1324
+ delete item.owner;
1325
+ delete item.estimate;
1326
+ delete item.resultRef;
1327
+ item.readyAt = now;
1328
+ }
1329
+ }
1330
+ function ownerUpdate(board, item, command, now, token) {
1331
+ switch (command.type) {
1332
+ case "renew":
1333
+ item.owner.leaseUntil = now + board.policy.leaseMs;
1334
+ break;
1335
+ case "progress":
1336
+ requireValue(Number.isFinite(command.workUnits) && command.workUnits >= item.workUnits, "INVALID_WORK_UNITS");
1337
+ item.workUnits = command.workUnits;
1338
+ item.progressAt = now;
1339
+ if (command.estimate) {
1340
+ validateEstimate(command.estimate);
1341
+ item.estimate = command.estimate;
1342
+ }
1343
+ break;
1344
+ case "request-transfer":
1345
+ requestTransfer(board, item, command, now, token);
1346
+ break;
1347
+ case "submit":
1348
+ text(command.resultRef);
1349
+ item.resultRef = command.resultRef;
1350
+ item.status = board.policy.review === "assignee" ? "completed" : "review";
1351
+ if (item.status === "completed") item.closedAt = now;
1352
+ delete item.transfer;
1353
+ break;
1354
+ }
1355
+ }
1356
+ function requestTransfer(board, item, command, now, token) {
1357
+ requireValue(Boolean(command.target) !== Boolean(command.agentId), "TRANSFER_TARGET_REQUIRED");
1358
+ const target = command.target ?? { kind: "agent", id: command.agentId }, key = principalKey(target);
1359
+ requireValue(candidates(board, item).some((p) => principalKey(p) === key), "AGENT_NOT_ELIGIBLE");
1360
+ requireValue(assigneeKey(item.owner) !== key, "SELF_TRANSFER");
1361
+ requireValue(!item.transfer, "TRANSFER_ALREADY_PENDING");
1362
+ item.transfer = { id: token, target, ...target.kind === "agent" ? { agentId: target.id } : {}, requestedAt: now, expiresAt: now + board.policy.transferTimeoutMs };
1363
+ }
1364
+ function transferTarget(value) {
1365
+ return value.target ?? { kind: "agent", id: value.agentId };
1366
+ }
1367
+
1368
+ // src/reducer.ts
1369
+ function reduce(state, command, actor, operationId, now, token, workflowInternal = false) {
1370
+ requireValue(state.board.deletedAt === void 0, "BOARD_DELETED");
1371
+ numberEntities(state);
1372
+ protectStoppedBoard(state, command);
1373
+ if ("itemId" in command && command.itemId) requireValue(state.board.items.find((i) => i.id === command.itemId)?.deletedAt === void 0, "ITEM_DELETED");
1374
+ if (!workflowInternal) protectWorkflowItem(state, command);
1375
+ const board = state.board;
1376
+ if (command.type === "delete-board") return deleteBoard(state, actor, operationId, now);
1377
+ if (command.type === "scan") {
1378
+ scan(state, actor, operationId, now);
1379
+ refreshReady(state);
1380
+ return state;
1381
+ }
1382
+ let recordEvent = command.type !== "renew";
1383
+ if (command.type === "request-board-stop" || command.type === "observe-board-stop") stopBoard(state, command, actor, now);
1384
+ else if (command.type === "set-board-control") setBoardControl(state, command, actor, now, token);
1385
+ else if (command.type === "delete-item") {
1386
+ const item = itemFor(board, command.itemId);
1387
+ requireValue(principal(actor).kind === "human", "FORBIDDEN");
1388
+ requireValue(terminal(item), "ITEM_DELETE_REQUIRES_CLOSED");
1389
+ delete item.owner;
1390
+ delete item.transfer;
1391
+ item.deletedAt = now;
1392
+ item.updatedAt = now;
1393
+ item.revision++;
1394
+ } else if (taskCommand(command)) applyTaskCommand(state, command, actor, now);
1395
+ else if (isWorkflowCommand(command)) recordEvent = applyWorkflow(state, command, actor, now, (inner, owner = actor) => {
1396
+ reduce(state, inner, owner, operationId, now, token, true);
1397
+ });
1398
+ else if (command.type === "apply-discovery") applyDiscovery(state, command.batch, actor, now);
1399
+ else if (isRecordCommand(command)) applyRecord(state, command, actor, now);
1400
+ else if (command.type === "review-amendment") {
1401
+ if (command.accept) assertDefinitionEditable(itemFor(board, command.itemId), now);
1402
+ reviewAmendment(board, command, actor, now);
1403
+ } else if (planningCommand(command)) applyPlanningCommand(state, command, now, actor, workflowInternal);
1404
+ else if (command.type === "create-item") createItem2(state, command, actor, now, token);
1405
+ else if (command.type === "configure-automation") configureAutomation(board, command.settings, actor);
1406
+ else if (command.type === "register-session") {
1407
+ const item = command.itemId ? itemFor(board, command.itemId) : void 0;
1408
+ const list = item ? item.participants : board.participants;
1409
+ participant(list, command.sessionId, command.agentId, command.role, command.subscribed, now);
1410
+ if (item) item.revision++;
1411
+ } else if (command.type === "message") {
1412
+ text(command.message);
1413
+ command.recipientSessionIds.forEach(identity);
1414
+ if (command.itemId) itemFor(board, command.itemId);
1415
+ requireValue(command.recipientSessionIds.every((id) => related(board).includes(id)), "UNKNOWN_RECIPIENT");
1416
+ } else transition(state, itemFor(board, command.itemId), command, actor, now, token);
1417
+ numberEntities(state);
1418
+ refreshReady(state);
1419
+ board.revision++;
1420
+ if (recordEvent) append2(
1421
+ state,
1422
+ actor,
1423
+ operationId,
1424
+ now,
1425
+ command.type,
1426
+ "itemId" in command ? command.itemId : command.type === "create-item" ? command.item.id : "dispatchId" in command ? state.workflow?.dispatches[command.dispatchId]?.itemId : void 0,
1427
+ eventData(command, state),
1428
+ command.type === "message" ? command.recipientSessionIds : void 0
1429
+ );
1430
+ scan(state, actor, operationId, now);
1431
+ return state;
1432
+ }
1433
+ function createItem2(state, command, actor, now, token) {
1434
+ const { id, title, responsibility, acceptance, eligibleAgentIds, eligiblePrincipals, externalRefs } = command.item;
1435
+ const input = { id, title, responsibility, acceptance, eligibleAgentIds, eligiblePrincipals, externalRefs }, board = state.board;
1436
+ identity(input.id);
1437
+ text(input.title);
1438
+ text(input.responsibility);
1439
+ text(input.acceptance);
1440
+ requireValue(!board.items.some((i) => i.id === input.id) && !state.archiveIndex?.some((i) => i.id === input.id), "ITEM_EXISTS");
1441
+ const item = { ...input, origin: { kind: "direct", principal: principal(actor) }, status: "backlog", revision: 1, createdAt: now, readyAt: now, updatedAt: now, workUnits: 0, participants: [] };
1442
+ item.launch = { startAt: now + TASK_START_DELAY_MS };
1443
+ requireValue(command.launch?.mode === void 0 || ["automatic", "manual"].includes(command.launch.mode), "INVALID_LAUNCH_MODE");
1444
+ requireValue(command.launch?.paused === void 0 || typeof command.launch.paused === "boolean", "INVALID_TASK_PAUSE");
1445
+ if (command.launch?.mode) item.launch.mode = command.launch.mode;
1446
+ if (command.launch?.paused) item.control = { id: "initial-" + token, action: "pause", state: "paused" };
1447
+ item.planning = command.planning ?? { ...plan(item), kind: board.defaultWorkType ?? "task" };
1448
+ const references = item.externalRefs ?? [];
1449
+ item.externalRefs = [];
1450
+ for (const reference of references) attachReference(item, reference);
1451
+ validateCandidates(board, item, executable(board, item));
1452
+ validatePlan(state, item, item.planning);
1453
+ if (item.planning.parentId) {
1454
+ const parent = board.items.find((parent2) => parent2.id === item.planning.parentId);
1455
+ if (parent.planning?.execution !== "children") {
1456
+ parent.planning = { ...plan(parent), execution: "children" };
1457
+ parent.updatedAt = now;
1458
+ parent.revision++;
1459
+ }
1460
+ }
1461
+ if (actor.sessionId) participant(item.participants, actor.sessionId, actor.agentId, "creator", true, now);
1462
+ board.items.push(item);
1463
+ if (command.stage === "open") setStage(state, item, "open", now);
1464
+ }
1465
+ function itemFor(board, id) {
1466
+ const item = board.items.find((value) => value.id === id);
1467
+ requireValue(item, "ITEM_NOT_FOUND");
1468
+ return item;
1469
+ }
1470
+ function related(board) {
1471
+ return [...new Set([...board.participants, ...board.items.flatMap((i) => i.participants)].map((p) => p.sessionId))];
1472
+ }
1473
+ function append2(state, actor, operationId, at, kind, itemId, data = {}, recipients) {
1474
+ const archived = state.archiveWrites?.flatMap((write) => write.value?.item.participants ?? []) ?? [];
1475
+ const members = [...archived, ...state.board.participants, ...state.board.items.filter((i) => i.id === itemId).flatMap((i) => i.participants)];
1476
+ state.events.push({
1477
+ sequence: ++state.sequence,
1478
+ boardId: state.board.id,
1479
+ itemId,
1480
+ operationId,
1481
+ at,
1482
+ kind,
1483
+ actor,
1484
+ recipients: [...new Set(recipients ?? members.filter((p) => p.subscribed).map((p) => p.sessionId))],
1485
+ data
1486
+ });
1487
+ }
1488
+ function scan(state, actor, operationId, now) {
1489
+ for (const item of state.board.items) {
1490
+ if (initializeTaskLaunch(state, item)) {
1491
+ state.board.revision++;
1492
+ append2(state, actor, operationId, now, "task-launch-initialized", item.id, { code: "LEGACY_LAUNCH_INITIALIZED" });
1493
+ }
1494
+ if (terminal(item)) continue;
1495
+ for (const reason of escalationReasons(state.board, item, now)) {
1496
+ const epoch = reason === "transfer-timeout" ? item.transfer?.id : reason === "backlog-review" ? item.updatedAt : item.owner?.token ?? item.readyAt;
1497
+ const key = JSON.stringify([item.id, epoch, reason]);
1498
+ if (state.escalations.includes(key)) continue;
1499
+ state.escalations.push(key);
1500
+ state.board.revision++;
1501
+ append2(state, actor, operationId, now, "escalated", item.id, { reason }, state.board.policy.escalationSessionIds);
1502
+ }
1503
+ if (item.transfer && item.transfer.expiresAt <= now) {
1504
+ delete item.transfer;
1505
+ item.revision++;
1506
+ item.updatedAt = now;
1507
+ state.board.revision++;
1508
+ }
1509
+ }
1510
+ const archived = archiveExpired(state, now);
1511
+ if (archived.length) {
1512
+ state.board.revision++;
1513
+ append2(state, actor, operationId, now, "items.archived", void 0, { reason: "retention-policy" });
1514
+ }
1515
+ }
1516
+ function escalationReasons(board, item, now) {
1517
+ const p = board.policy, result = [];
1518
+ if (item.status === "backlog" && p.backlogReviewMs && now - item.updatedAt >= p.backlogReviewMs) result.push("backlog-review");
1519
+ if (item.planning?.dueAt !== void 0 && now >= item.planning.dueAt) result.push("due-date");
1520
+ if (item.blocked && now - item.blocked.at >= p.progressTimeoutMs) result.push("blocked-age");
1521
+ if (!item.blocked && item.status === "open" && now - item.readyAt >= p.claimTimeoutMs) result.push("unclaimed");
1522
+ if (item.status === "review" && now - item.updatedAt >= p.progressTimeoutMs) result.push("review-timeout");
1523
+ if (item.transfer && now >= item.transfer.expiresAt) result.push("transfer-timeout");
1524
+ if (item.status !== "claimed" || !item.owner) return result;
1525
+ if (now >= item.owner.leaseUntil) result.push("lease-expired");
1526
+ if (now - (item.progressAt ?? item.owner.claimedAt) >= p.progressTimeoutMs) result.push("no-progress");
1527
+ if (now - item.owner.claimedAt >= Math.min(item.estimate?.durationMs ?? p.maxDurationMs, p.maxDurationMs)) result.push("execution-timeout");
1528
+ if ((item.estimate?.complexity ?? 0) >= p.maxComplexity) result.push("complexity-threshold");
1529
+ if (Math.max(item.workUnits, item.estimate?.workUnits ?? 0) >= p.maxWorkUnits) result.push("work-threshold");
1530
+ if ((item.estimate?.durationMs ?? 0) >= p.maxDurationMs) result.push("duration-threshold");
1531
+ return result;
1532
+ }
1533
+ function eventData(command, state) {
1534
+ if (command.type === "request-board-stop" || command.type === "observe-board-stop") return { code: state.board.lifecycle?.code, reason: state.board.lifecycle?.intent, itemIds: state.board.items.filter((item) => item.control?.action === "cancel").map((item) => item.id) };
1535
+ else if (command.type === "set-board-control") return { code: "BOARD_" + (state.board.executionControl?.paused ? "PAUSE_REQUESTED" : state.board.executionControl?.mode?.toUpperCase()) };
1536
+ if (command.type === "apply-discovery") return discoveryEvent(state, command.batch);
1537
+ if (command.type === "report-analysis") return { code: command.status.code, reason: "analysis-" + command.status.state };
1538
+ if (command.type === "control-receipt") return { code: command.code, reason: "control-" + command.state };
1539
+ if (isWorkflowCommand(command)) return { readyItemIds: state.workflow?.ready, ..."dispatchId" in command ? { dispatchId: command.dispatchId } : { planId: command.plan.id }, ..."outcome" in command ? { reason: `execution-${command.outcome}`, code: command.code, status: state.board.items.find((item) => item.id === state.workflow?.dispatches[command.dispatchId]?.itemId)?.status } : command.type === "accept-execution" ? { reason: command.accept ? "accepted" : "rejected" } : {} };
1540
+ if (command.type === "create-item") return { itemTitle: command.item.title };
1541
+ if (command.type === "link-reference") return { reference: command.reference };
1542
+ if (command.type === "block-item") return { reason: command.reason };
1543
+ if (command.type === "release-item") return { reason: command.next === "cancelled" ? "cancelled" : "released" };
1544
+ if (command.type === "review") return {
1545
+ reason: command.accept ? "accepted" : "rejected",
1546
+ dispatchId: Object.values(state.workflow?.dispatches ?? {}).find((dispatch) => dispatch.itemId === command.itemId && dispatch.outcome === "succeeded")?.id
1547
+ };
1548
+ if (command.type === "append-record") return { recordId: command.record.id, reason: command.record.kind === "decision" ? "decision-draft" : "log" };
1549
+ if (command.type === "review-record" || command.type === "link-record") return { recordId: command.recordId };
1550
+ if (command.type === "archive-items") return { itemIds: command.itemIds, reason: command.reason };
1551
+ if (command.type === "cancel-item") return { reason: command.reason };
1552
+ if (command.type === "plan-item") return { planning: command.planning };
1553
+ if (command.type === "message") return { message: command.message };
1554
+ if (command.type === "submit") return { resultRef: command.resultRef };
1555
+ if (command.type === "claim" || command.type === "accept-transfer") return { estimate: command.estimate };
1556
+ if (command.type === "progress") return { estimate: command.estimate, workUnits: command.workUnits };
1557
+ return {};
1558
+ }
1559
+ function discoveryEvent(state, batch) {
1560
+ return {
1561
+ itemIds: batch.changes.map((change) => change.itemId),
1562
+ recordIds: batch.records?.filter((record) => state.records?.some((entry) => entry.id === record.id && entry.originRunId === batch.runId)).map((record) => record.id),
1563
+ ...state.board.items.some((item) => item.amendments?.some((proposal) => proposal.runId === batch.runId && proposal.status === "pending")) ? { reason: "amendment-proposed" } : state.board.items.some((item) => item.status === "proposed" && item.origin?.runId === batch.runId) ? { reason: "proposal-created" } : {}
1564
+ };
1565
+ }
1566
+ function deleteBoard(state, actor, operationId, now) {
1567
+ const owner = principal(actor), board = state.board;
1568
+ requireValue(owner.kind === "human" && board.members?.some((member) => member.kind === owner.kind && member.id === owner.id && member.roles?.includes("coordinator")), "COORDINATOR_REQUIRED");
1569
+ requireValue(!board.items.some((item) => !terminal(item) && (item.owner || item.transfer || item.status === "claimed")), "BOARD_EXECUTION_ACTIVE");
1570
+ requireValue(!Object.values(state.workflow?.dispatches ?? {}).some((dispatch) => ["prepared", "running", "unknown", "succeeded"].includes(dispatch.outcome)), "BOARD_EXECUTION_UNSETTLED");
1571
+ requireValue(!board.items.some((item) => item.control?.action === "cancel" && item.control.state !== "cancelled" && !terminal(item)), "BOARD_CONTROL_UNSETTLED");
1572
+ board.deletedAt = now;
1573
+ board.revision++;
1574
+ if (board.automation) board.automation.enabled = false;
1575
+ if (state.workflow) state.workflow.ready = [];
1576
+ append2(state, actor, operationId, now, "delete-board");
1577
+ return state;
1578
+ }
1579
+
1580
+ export {
1581
+ summary,
1582
+ terminal,
1583
+ catalog,
1584
+ limits,
1585
+ proposeAmendment,
1586
+ queryRecords,
1587
+ recordSummary,
1588
+ createBoardState,
1589
+ boardKey,
1590
+ archiveKey,
1591
+ rowKeys,
1592
+ hydrate,
1593
+ encodeBoard,
1594
+ rowChanges,
1595
+ receiptDigest,
1596
+ verifyReceipt,
1597
+ encodeRow,
1598
+ decodeRow,
1599
+ indexedView,
1600
+ viewFromState,
1601
+ boardCounterKey,
1602
+ boardNumberKey,
1603
+ counter,
1604
+ storedBoardNumber,
1605
+ patchBoardNumber,
1606
+ numberEntities,
1607
+ allocateBoardNumber,
1608
+ reduce,
1609
+ related,
1610
+ escalationReasons
1611
+ };
1612
+ //# sourceMappingURL=chunk-XKDZHAUE.js.map