@mastra/factory 0.0.0 → 0.1.0-alpha.3

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 (287) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE.md +30 -0
  3. package/dist/auth.d.ts +181 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +370 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/capabilities/connection.d.ts +9 -0
  8. package/dist/capabilities/connection.d.ts.map +1 -0
  9. package/dist/capabilities/connection.js +1 -0
  10. package/dist/capabilities/connection.js.map +1 -0
  11. package/dist/capabilities/intake.d.ts +93 -0
  12. package/dist/capabilities/intake.d.ts.map +1 -0
  13. package/dist/capabilities/intake.js +1 -0
  14. package/dist/capabilities/intake.js.map +1 -0
  15. package/dist/capabilities/version-control.d.ts +239 -0
  16. package/dist/capabilities/version-control.d.ts.map +1 -0
  17. package/dist/capabilities/version-control.js +1 -0
  18. package/dist/capabilities/version-control.js.map +1 -0
  19. package/dist/factory.d.ts +145 -0
  20. package/dist/factory.d.ts.map +1 -0
  21. package/dist/factory.js +38245 -0
  22. package/dist/factory.js.map +1 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +38245 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/integrations/base.d.ts +197 -0
  28. package/dist/integrations/base.d.ts.map +1 -0
  29. package/dist/integrations/base.js +1 -0
  30. package/dist/integrations/base.js.map +1 -0
  31. package/dist/integrations/github/config.d.ts +60 -0
  32. package/dist/integrations/github/config.d.ts.map +1 -0
  33. package/dist/integrations/github/config.js +28 -0
  34. package/dist/integrations/github/config.js.map +1 -0
  35. package/dist/integrations/github/factory-session.d.ts +12 -0
  36. package/dist/integrations/github/factory-session.d.ts.map +1 -0
  37. package/dist/integrations/github/factory-session.js +41 -0
  38. package/dist/integrations/github/factory-session.js.map +1 -0
  39. package/dist/integrations/github/integration.d.ts +191 -0
  40. package/dist/integrations/github/integration.d.ts.map +1 -0
  41. package/dist/integrations/github/integration.js +3001 -0
  42. package/dist/integrations/github/integration.js.map +1 -0
  43. package/dist/integrations/github/issue-triage.d.ts +27 -0
  44. package/dist/integrations/github/issue-triage.d.ts.map +1 -0
  45. package/dist/integrations/github/issue-triage.js +82 -0
  46. package/dist/integrations/github/issue-triage.js.map +1 -0
  47. package/dist/integrations/github/project-lock.d.ts +78 -0
  48. package/dist/integrations/github/project-lock.d.ts.map +1 -0
  49. package/dist/integrations/github/project-lock.js +70 -0
  50. package/dist/integrations/github/project-lock.js.map +1 -0
  51. package/dist/integrations/github/provenance.d.ts +27 -0
  52. package/dist/integrations/github/provenance.d.ts.map +1 -0
  53. package/dist/integrations/github/provenance.js +104 -0
  54. package/dist/integrations/github/provenance.js.map +1 -0
  55. package/dist/integrations/github/routes.d.ts +72 -0
  56. package/dist/integrations/github/routes.d.ts.map +1 -0
  57. package/dist/integrations/github/routes.js +1834 -0
  58. package/dist/integrations/github/routes.js.map +1 -0
  59. package/dist/integrations/github/sandbox.d.ts +268 -0
  60. package/dist/integrations/github/sandbox.d.ts.map +1 -0
  61. package/dist/integrations/github/sandbox.js +381 -0
  62. package/dist/integrations/github/sandbox.js.map +1 -0
  63. package/dist/integrations/github/session-subscriptions.d.ts +23 -0
  64. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -0
  65. package/dist/integrations/github/session-subscriptions.js +190 -0
  66. package/dist/integrations/github/session-subscriptions.js.map +1 -0
  67. package/dist/integrations/github/subscriptions.d.ts +54 -0
  68. package/dist/integrations/github/subscriptions.d.ts.map +1 -0
  69. package/dist/integrations/github/subscriptions.js +70 -0
  70. package/dist/integrations/github/subscriptions.js.map +1 -0
  71. package/dist/integrations/github/webhook.d.ts +87 -0
  72. package/dist/integrations/github/webhook.d.ts.map +1 -0
  73. package/dist/integrations/github/webhook.js +381 -0
  74. package/dist/integrations/github/webhook.js.map +1 -0
  75. package/dist/integrations/linear/agent-tools.d.ts +34 -0
  76. package/dist/integrations/linear/agent-tools.d.ts.map +1 -0
  77. package/dist/integrations/linear/agent-tools.js +108 -0
  78. package/dist/integrations/linear/agent-tools.js.map +1 -0
  79. package/dist/integrations/linear/integration.d.ts +233 -0
  80. package/dist/integrations/linear/integration.d.ts.map +1 -0
  81. package/dist/integrations/linear/integration.js +962 -0
  82. package/dist/integrations/linear/integration.js.map +1 -0
  83. package/dist/integrations/linear/routes.d.ts +61 -0
  84. package/dist/integrations/linear/routes.d.ts.map +1 -0
  85. package/dist/integrations/linear/routes.js +242 -0
  86. package/dist/integrations/linear/routes.js.map +1 -0
  87. package/dist/integrations/linear/storage.d.ts +65 -0
  88. package/dist/integrations/linear/storage.d.ts.map +1 -0
  89. package/dist/integrations/linear/storage.js +1 -0
  90. package/dist/integrations/linear/storage.js.map +1 -0
  91. package/dist/integrations/platform/api-client.d.ts +24 -0
  92. package/dist/integrations/platform/api-client.d.ts.map +1 -0
  93. package/dist/integrations/platform/api-client.js +153 -0
  94. package/dist/integrations/platform/api-client.js.map +1 -0
  95. package/dist/integrations/platform/github/event-worker.d.ts +44 -0
  96. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -0
  97. package/dist/integrations/platform/github/event-worker.js +585 -0
  98. package/dist/integrations/platform/github/event-worker.js.map +1 -0
  99. package/dist/integrations/platform/github/integration.d.ts +36 -0
  100. package/dist/integrations/platform/github/integration.d.ts.map +1 -0
  101. package/dist/integrations/platform/github/integration.js +3253 -0
  102. package/dist/integrations/platform/github/integration.js.map +1 -0
  103. package/dist/integrations/platform/linear/integration.d.ts +46 -0
  104. package/dist/integrations/platform/linear/integration.d.ts.map +1 -0
  105. package/dist/integrations/platform/linear/integration.js +891 -0
  106. package/dist/integrations/platform/linear/integration.js.map +1 -0
  107. package/dist/integrations/workos/integration.d.ts +39 -0
  108. package/dist/integrations/workos/integration.d.ts.map +1 -0
  109. package/dist/integrations/workos/integration.js +96 -0
  110. package/dist/integrations/workos/integration.js.map +1 -0
  111. package/dist/routes/config.d.ts +220 -0
  112. package/dist/routes/config.d.ts.map +1 -0
  113. package/dist/routes/config.js +770 -0
  114. package/dist/routes/config.js.map +1 -0
  115. package/dist/routes/fs.d.ts +105 -0
  116. package/dist/routes/fs.d.ts.map +1 -0
  117. package/dist/routes/fs.js +294 -0
  118. package/dist/routes/fs.js.map +1 -0
  119. package/dist/routes/intake.d.ts +23 -0
  120. package/dist/routes/intake.d.ts.map +1 -0
  121. package/dist/routes/intake.js +179 -0
  122. package/dist/routes/intake.js.map +1 -0
  123. package/dist/routes/oauth.d.ts +43 -0
  124. package/dist/routes/oauth.d.ts.map +1 -0
  125. package/dist/routes/oauth.js +25058 -0
  126. package/dist/routes/oauth.js.map +1 -0
  127. package/dist/routes/projects.d.ts +19 -0
  128. package/dist/routes/projects.d.ts.map +1 -0
  129. package/dist/routes/projects.js +395 -0
  130. package/dist/routes/projects.js.map +1 -0
  131. package/dist/routes/provider-credentials.d.ts +81 -0
  132. package/dist/routes/provider-credentials.d.ts.map +1 -0
  133. package/dist/routes/provider-credentials.js +68 -0
  134. package/dist/routes/provider-credentials.js.map +1 -0
  135. package/dist/routes/route.d.ts +48 -0
  136. package/dist/routes/route.d.ts.map +1 -0
  137. package/dist/routes/route.js +11 -0
  138. package/dist/routes/route.js.map +1 -0
  139. package/dist/routes/skills.d.ts +30 -0
  140. package/dist/routes/skills.d.ts.map +1 -0
  141. package/dist/routes/skills.js +181 -0
  142. package/dist/routes/skills.js.map +1 -0
  143. package/dist/routes/surface.d.ts +83 -0
  144. package/dist/routes/surface.d.ts.map +1 -0
  145. package/dist/routes/surface.js +28519 -0
  146. package/dist/routes/surface.js.map +1 -0
  147. package/dist/routes/tenant-credentials.d.ts +74 -0
  148. package/dist/routes/tenant-credentials.d.ts.map +1 -0
  149. package/dist/routes/tenant-credentials.js +181 -0
  150. package/dist/routes/tenant-credentials.js.map +1 -0
  151. package/dist/routes/work-items.d.ts +40 -0
  152. package/dist/routes/work-items.d.ts.map +1 -0
  153. package/dist/routes/work-items.js +902 -0
  154. package/dist/routes/work-items.js.map +1 -0
  155. package/dist/rules/binding-context.d.ts +5 -0
  156. package/dist/rules/binding-context.d.ts.map +1 -0
  157. package/dist/rules/binding-context.js +40 -0
  158. package/dist/rules/binding-context.js.map +1 -0
  159. package/dist/rules/defaults.d.ts +9 -0
  160. package/dist/rules/defaults.d.ts.map +1 -0
  161. package/dist/rules/defaults.js +334 -0
  162. package/dist/rules/defaults.js.map +1 -0
  163. package/dist/rules/dispatcher.d.ts +40 -0
  164. package/dist/rules/dispatcher.d.ts.map +1 -0
  165. package/dist/rules/dispatcher.js +695 -0
  166. package/dist/rules/dispatcher.js.map +1 -0
  167. package/dist/rules/github-service.d.ts +29 -0
  168. package/dist/rules/github-service.d.ts.map +1 -0
  169. package/dist/rules/github-service.js +474 -0
  170. package/dist/rules/github-service.js.map +1 -0
  171. package/dist/rules/index.d.ts +7 -0
  172. package/dist/rules/index.d.ts.map +1 -0
  173. package/dist/rules/index.js +583 -0
  174. package/dist/rules/index.js.map +1 -0
  175. package/dist/rules/linear-service.d.ts +27 -0
  176. package/dist/rules/linear-service.d.ts.map +1 -0
  177. package/dist/rules/linear-service.js +343 -0
  178. package/dist/rules/linear-service.js.map +1 -0
  179. package/dist/rules/processor.d.ts +54 -0
  180. package/dist/rules/processor.d.ts.map +1 -0
  181. package/dist/rules/processor.js +629 -0
  182. package/dist/rules/processor.js.map +1 -0
  183. package/dist/rules/resolve.d.ts +17 -0
  184. package/dist/rules/resolve.d.ts.map +1 -0
  185. package/dist/rules/resolve.js +27 -0
  186. package/dist/rules/resolve.js.map +1 -0
  187. package/dist/rules/start-coordinator.d.ts +58 -0
  188. package/dist/rules/start-coordinator.d.ts.map +1 -0
  189. package/dist/rules/start-coordinator.js +136 -0
  190. package/dist/rules/start-coordinator.js.map +1 -0
  191. package/dist/rules/tools.d.ts +10 -0
  192. package/dist/rules/tools.d.ts.map +1 -0
  193. package/dist/rules/tools.js +93 -0
  194. package/dist/rules/tools.js.map +1 -0
  195. package/dist/rules/transition-service.d.ts +32 -0
  196. package/dist/rules/transition-service.d.ts.map +1 -0
  197. package/dist/rules/transition-service.js +474 -0
  198. package/dist/rules/transition-service.js.map +1 -0
  199. package/dist/rules/types.d.ts +225 -0
  200. package/dist/rules/types.d.ts.map +1 -0
  201. package/dist/rules/types.js +33 -0
  202. package/dist/rules/types.js.map +1 -0
  203. package/dist/rules/validation.d.ts +11 -0
  204. package/dist/rules/validation.d.ts.map +1 -0
  205. package/dist/rules/validation.js +310 -0
  206. package/dist/rules/validation.js.map +1 -0
  207. package/dist/sandbox/fleet.d.ts +208 -0
  208. package/dist/sandbox/fleet.d.ts.map +1 -0
  209. package/dist/sandbox/fleet.js +259 -0
  210. package/dist/sandbox/fleet.js.map +1 -0
  211. package/dist/sandbox/reattach.d.ts +3 -0
  212. package/dist/sandbox/reattach.d.ts.map +1 -0
  213. package/dist/sandbox/reattach.js +9 -0
  214. package/dist/sandbox/reattach.js.map +1 -0
  215. package/dist/server-error.d.ts +12 -0
  216. package/dist/server-error.d.ts.map +1 -0
  217. package/dist/server-error.js +20 -0
  218. package/dist/server-error.js.map +1 -0
  219. package/dist/skills/service.d.ts +48 -0
  220. package/dist/skills/service.d.ts.map +1 -0
  221. package/dist/skills/service.js +45 -0
  222. package/dist/skills/service.js.map +1 -0
  223. package/dist/spa-static.d.ts +34 -0
  224. package/dist/spa-static.d.ts.map +1 -0
  225. package/dist/spa-static.js +73 -0
  226. package/dist/spa-static.js.map +1 -0
  227. package/dist/state-signing.d.ts +48 -0
  228. package/dist/state-signing.d.ts.map +1 -0
  229. package/dist/state-signing.js +48 -0
  230. package/dist/state-signing.js.map +1 -0
  231. package/dist/storage/domains/audit/agent-audit.d.ts +27 -0
  232. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -0
  233. package/dist/storage/domains/audit/agent-audit.js +65 -0
  234. package/dist/storage/domains/audit/agent-audit.js.map +1 -0
  235. package/dist/storage/domains/audit/base.d.ts +140 -0
  236. package/dist/storage/domains/audit/base.d.ts.map +1 -0
  237. package/dist/storage/domains/audit/base.js +127 -0
  238. package/dist/storage/domains/audit/base.js.map +1 -0
  239. package/dist/storage/domains/audit/domain.d.ts +69 -0
  240. package/dist/storage/domains/audit/domain.d.ts.map +1 -0
  241. package/dist/storage/domains/audit/domain.js +165 -0
  242. package/dist/storage/domains/audit/domain.js.map +1 -0
  243. package/dist/storage/domains/credentials/base.d.ts +134 -0
  244. package/dist/storage/domains/credentials/base.d.ts.map +1 -0
  245. package/dist/storage/domains/credentials/base.js +257 -0
  246. package/dist/storage/domains/credentials/base.js.map +1 -0
  247. package/dist/storage/domains/intake/base.d.ts +38 -0
  248. package/dist/storage/domains/intake/base.d.ts.map +1 -0
  249. package/dist/storage/domains/intake/base.js +62 -0
  250. package/dist/storage/domains/intake/base.js.map +1 -0
  251. package/dist/storage/domains/integrations/base.d.ts +126 -0
  252. package/dist/storage/domains/integrations/base.d.ts.map +1 -0
  253. package/dist/storage/domains/integrations/base.js +252 -0
  254. package/dist/storage/domains/integrations/base.js.map +1 -0
  255. package/dist/storage/domains/model-packs/base.d.ts +49 -0
  256. package/dist/storage/domains/model-packs/base.d.ts.map +1 -0
  257. package/dist/storage/domains/model-packs/base.js +95 -0
  258. package/dist/storage/domains/model-packs/base.js.map +1 -0
  259. package/dist/storage/domains/projects/base.d.ts +55 -0
  260. package/dist/storage/domains/projects/base.d.ts.map +1 -0
  261. package/dist/storage/domains/projects/base.js +99 -0
  262. package/dist/storage/domains/projects/base.js.map +1 -0
  263. package/dist/storage/domains/queue-health/base.d.ts +52 -0
  264. package/dist/storage/domains/queue-health/base.d.ts.map +1 -0
  265. package/dist/storage/domains/queue-health/base.js +87 -0
  266. package/dist/storage/domains/queue-health/base.js.map +1 -0
  267. package/dist/storage/domains/source-control/base.d.ts +288 -0
  268. package/dist/storage/domains/source-control/base.d.ts.map +1 -0
  269. package/dist/storage/domains/source-control/base.js +722 -0
  270. package/dist/storage/domains/source-control/base.js.map +1 -0
  271. package/dist/storage/domains/source-control/inmemory.d.ts +158 -0
  272. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -0
  273. package/dist/storage/domains/source-control/inmemory.js +348 -0
  274. package/dist/storage/domains/source-control/inmemory.js.map +1 -0
  275. package/dist/storage/domains/work-items/base.d.ts +412 -0
  276. package/dist/storage/domains/work-items/base.d.ts.map +1 -0
  277. package/dist/storage/domains/work-items/base.js +1174 -0
  278. package/dist/storage/domains/work-items/base.js.map +1 -0
  279. package/dist/storage/domains/work-items/metrics.d.ts +101 -0
  280. package/dist/storage/domains/work-items/metrics.d.ts.map +1 -0
  281. package/dist/storage/domains/work-items/metrics.js +203 -0
  282. package/dist/storage/domains/work-items/metrics.js.map +1 -0
  283. package/dist/workspace.d.ts +20 -0
  284. package/dist/workspace.d.ts.map +1 -0
  285. package/dist/workspace.js +364 -0
  286. package/dist/workspace.js.map +1 -0
  287. package/package.json +19 -19
@@ -0,0 +1,1174 @@
1
+ // src/storage/domains/work-items/base.ts
2
+ import { createHash } from "crypto";
3
+ import { FactoryStorageDomain, UniqueViolationError } from "@mastra/core/storage";
4
+ function stableJson(value) {
5
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
6
+ if (value && typeof value === "object") {
7
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`).join(",")}}`;
8
+ }
9
+ return JSON.stringify(value) ?? "null";
10
+ }
11
+ function factoryDecisionHash(decision) {
12
+ return createHash("sha256").update(stableJson(decision)).digest("hex");
13
+ }
14
+ var AUTOMATION_ACTORS = /* @__PURE__ */ new Set([
15
+ "factory",
16
+ "system",
17
+ "automation",
18
+ "factory-rule-dispatcher",
19
+ "factory-tool-result-rule"
20
+ ]);
21
+ function isAutomationActor(by) {
22
+ if (by === void 0) return false;
23
+ return AUTOMATION_ACTORS.has(by) || by.startsWith("agent:") || by.startsWith("github:");
24
+ }
25
+ var WORK_ITEMS_SCHEMA = {
26
+ name: "work_items",
27
+ columns: {
28
+ id: { type: "uuid-pk" },
29
+ org_id: { type: "text" },
30
+ factory_project_id: { type: "text" },
31
+ external_source: { type: "json", nullable: true },
32
+ source_key: { type: "text", nullable: true },
33
+ parent_work_item_id: { type: "text", nullable: true },
34
+ title: { type: "text" },
35
+ stages: { type: "json" },
36
+ stage_history: { type: "json" },
37
+ sessions: { type: "json" },
38
+ metadata: { type: "json", nullable: true },
39
+ revision: { type: "integer", default: 1 },
40
+ created_by: { type: "text" },
41
+ created_at: { type: "timestamp" },
42
+ updated_at: { type: "timestamp" }
43
+ },
44
+ uniqueIndexes: [
45
+ {
46
+ name: "work_items_project_source_key_unique",
47
+ columns: ["factory_project_id", "source_key"]
48
+ }
49
+ ],
50
+ indexes: [
51
+ {
52
+ name: "work_items_org_project_updated_at_idx",
53
+ columns: ["org_id", "factory_project_id", "updated_at"]
54
+ },
55
+ {
56
+ name: "work_items_project_parent_idx",
57
+ columns: ["org_id", "factory_project_id", "parent_work_item_id"]
58
+ }
59
+ ]
60
+ };
61
+ function sourceKey(source) {
62
+ return source ? `${source.integrationId}:${source.type}:${source.externalId}` : null;
63
+ }
64
+ function toWorkItem(row) {
65
+ return {
66
+ id: row.id,
67
+ orgId: row.org_id,
68
+ factoryProjectId: String(row.factory_project_id),
69
+ externalSource: row.external_source,
70
+ parentWorkItemId: row.parent_work_item_id,
71
+ title: row.title,
72
+ stages: row.stages,
73
+ stageHistory: row.stage_history,
74
+ sessions: row.sessions,
75
+ metadata: row.metadata,
76
+ revision: row.revision,
77
+ createdBy: row.created_by,
78
+ createdAt: row.created_at,
79
+ updatedAt: row.updated_at
80
+ };
81
+ }
82
+ var toRow = toWorkItem;
83
+ function patchColumns(changes) {
84
+ return {
85
+ ...changes.parentWorkItemId !== void 0 ? { parent_work_item_id: changes.parentWorkItemId } : {},
86
+ ...changes.title !== void 0 ? { title: changes.title } : {},
87
+ ...changes.stages !== void 0 ? { stages: changes.stages } : {},
88
+ ...changes.stageHistory !== void 0 ? { stage_history: changes.stageHistory } : {},
89
+ ...changes.sessions !== void 0 ? { sessions: changes.sessions } : {},
90
+ ...changes.metadata !== void 0 ? { metadata: changes.metadata } : {},
91
+ ...changes.revision !== void 0 ? { revision: changes.revision } : {},
92
+ ...changes.updatedAt !== void 0 ? { updated_at: changes.updatedAt } : {}
93
+ };
94
+ }
95
+ function emptyPrior() {
96
+ return { stages: [], sessionRoles: [] };
97
+ }
98
+ function priorState(row) {
99
+ return { stages: row.stages, sessionRoles: Object.keys(row.sessions) };
100
+ }
101
+ var WorkItemRelationError = class extends Error {
102
+ code = "invalid_work_item_relation";
103
+ };
104
+ function validateParentRelation(projectItems, itemId, parentWorkItemId) {
105
+ if (parentWorkItemId === null) return;
106
+ const byId = new Map(projectItems.map((item) => [item.id, item]));
107
+ const parent = byId.get(parentWorkItemId);
108
+ if (!parent) throw new WorkItemRelationError("Related work item not found in this project.");
109
+ if (itemId === parentWorkItemId) throw new WorkItemRelationError("A work item cannot relate to itself.");
110
+ const visited = /* @__PURE__ */ new Set();
111
+ let cursor = parent;
112
+ while (cursor?.parentWorkItemId) {
113
+ if (cursor.parentWorkItemId === itemId) {
114
+ throw new WorkItemRelationError("This relationship would create a cycle.");
115
+ }
116
+ if (visited.has(cursor.id)) throw new WorkItemRelationError("The related work item chain contains a cycle.");
117
+ visited.add(cursor.id);
118
+ cursor = byId.get(cursor.parentWorkItemId);
119
+ }
120
+ }
121
+ function applyStageTransition(history, oldStages, newStages, by, now) {
122
+ const timestamp = now.toISOString();
123
+ const next = history.map((entry) => ({ ...entry }));
124
+ for (const stage of oldStages) {
125
+ if (newStages.includes(stage)) continue;
126
+ for (let i = next.length - 1; i >= 0; i--) {
127
+ const entry = next[i];
128
+ if (entry.stage === stage && entry.exitedAt === void 0) {
129
+ entry.exitedAt = timestamp;
130
+ entry.exitedBy = by;
131
+ break;
132
+ }
133
+ }
134
+ }
135
+ for (const stage of newStages) {
136
+ if (!oldStages.includes(stage)) next.push({ stage, enteredAt: timestamp, by });
137
+ }
138
+ return next;
139
+ }
140
+ function stampSessions(sessions, by) {
141
+ return Object.fromEntries(Object.entries(sessions).map(([role, session]) => [role, { ...session, startedBy: by }]));
142
+ }
143
+ function applyUpdate({
144
+ current,
145
+ userId,
146
+ input
147
+ }) {
148
+ const now = /* @__PURE__ */ new Date();
149
+ return {
150
+ ...input.parentWorkItemId !== void 0 ? { parent_work_item_id: input.parentWorkItemId } : {},
151
+ ...input.title !== void 0 ? { title: input.title } : {},
152
+ ...input.stages !== void 0 ? {
153
+ stages: input.stages,
154
+ stage_history: applyStageTransition(current.stage_history, current.stages, input.stages, userId, now)
155
+ } : {},
156
+ ...input.sessions !== void 0 ? { sessions: { ...current.sessions, ...stampSessions(input.sessions, userId) } } : {},
157
+ ...input.metadata !== void 0 ? { metadata: input.metadata === null ? null : { ...current.metadata ?? {}, ...input.metadata } } : {},
158
+ revision: current.revision + 1,
159
+ updated_at: now
160
+ };
161
+ }
162
+ var projectRelationLocks = /* @__PURE__ */ new Map();
163
+ function withInProcessProjectLock(key, fn) {
164
+ const previous = projectRelationLocks.get(key) ?? Promise.resolve();
165
+ const result = previous.then(fn, fn);
166
+ const tail = result.then(
167
+ () => void 0,
168
+ () => void 0
169
+ );
170
+ projectRelationLocks.set(key, tail);
171
+ void tail.then(() => {
172
+ if (projectRelationLocks.get(key) === tail) projectRelationLocks.delete(key);
173
+ });
174
+ return result;
175
+ }
176
+ var FACTORY_GOVERNANCE_SCHEMAS = [
177
+ {
178
+ name: "factory_rule_ingress",
179
+ columns: {
180
+ id: { type: "uuid-pk" },
181
+ org_id: { type: "text" },
182
+ factory_project_id: { type: "text" },
183
+ identity: { type: "text" },
184
+ trigger_type: { type: "text" },
185
+ transition_id: { type: "text" },
186
+ result: { type: "json" },
187
+ created_at: { type: "timestamp" }
188
+ },
189
+ uniqueIndexes: [
190
+ { name: "factory_rule_ingress_tenant_identity_unique", columns: ["org_id", "factory_project_id", "identity"] }
191
+ ]
192
+ },
193
+ {
194
+ name: "factory_rule_evaluations",
195
+ columns: {
196
+ id: { type: "uuid-pk" },
197
+ ingress_id: { type: "text" },
198
+ work_item_id: { type: "text", nullable: true },
199
+ rule_set_version: { type: "text" },
200
+ expected_revision: { type: "integer", nullable: true },
201
+ outcome: { type: "text" },
202
+ code: { type: "text", nullable: true },
203
+ reason: { type: "text", nullable: true },
204
+ causal_chain: { type: "json" },
205
+ created_at: { type: "timestamp" }
206
+ }
207
+ },
208
+ {
209
+ name: "factory_deferred_decisions",
210
+ columns: {
211
+ id: { type: "uuid-pk" },
212
+ org_id: { type: "text" },
213
+ factory_project_id: { type: "text" },
214
+ evaluation_id: { type: "text" },
215
+ work_item_id: { type: "text", nullable: true },
216
+ idempotency_key: { type: "text" },
217
+ effect_ordinal: { type: "integer" },
218
+ effect_hash: { type: "text" },
219
+ causal_chain: { type: "json" },
220
+ actor: { type: "json", nullable: true },
221
+ decision: { type: "json" },
222
+ status: { type: "text" },
223
+ attempts: { type: "integer" },
224
+ available_at: { type: "timestamp" },
225
+ lease_owner: { type: "text", nullable: true },
226
+ lease_expires_at: { type: "timestamp", nullable: true },
227
+ last_error: { type: "text", nullable: true },
228
+ completed_at: { type: "timestamp", nullable: true },
229
+ created_at: { type: "timestamp" },
230
+ updated_at: { type: "timestamp" }
231
+ },
232
+ uniqueIndexes: [
233
+ {
234
+ name: "factory_deferred_decisions_tenant_key_unique",
235
+ columns: ["org_id", "factory_project_id", "idempotency_key"]
236
+ }
237
+ ]
238
+ },
239
+ {
240
+ name: "factory_run_bindings",
241
+ columns: {
242
+ id: { type: "uuid-pk" },
243
+ org_id: { type: "text" },
244
+ factory_project_id: { type: "text" },
245
+ work_item_id: { type: "text" },
246
+ role: { type: "text" },
247
+ thread_id: { type: "text" },
248
+ resource_id: { type: "text" },
249
+ session_id: { type: "text" },
250
+ branch: { type: "text" },
251
+ status: { type: "text" },
252
+ created_at: { type: "timestamp" },
253
+ revoked_at: { type: "timestamp", nullable: true }
254
+ }
255
+ },
256
+ {
257
+ name: "factory_tool_result_cursors",
258
+ columns: {
259
+ binding_id: { type: "text", primaryKey: true },
260
+ org_id: { type: "text" },
261
+ factory_project_id: { type: "text" },
262
+ last_message_id: { type: "text" },
263
+ last_message_created_at: { type: "timestamp" },
264
+ updated_at: { type: "timestamp" }
265
+ }
266
+ },
267
+ {
268
+ name: "factory_pending_starts",
269
+ columns: {
270
+ id: { type: "uuid-pk" },
271
+ org_id: { type: "text" },
272
+ factory_project_id: { type: "text" },
273
+ binding_id: { type: "text" },
274
+ kickoff_key: { type: "text" },
275
+ message: { type: "text", nullable: true },
276
+ status: { type: "text" },
277
+ attempts: { type: "integer" },
278
+ available_at: { type: "timestamp" },
279
+ lease_owner: { type: "text", nullable: true },
280
+ lease_expires_at: { type: "timestamp", nullable: true },
281
+ last_error: { type: "text", nullable: true },
282
+ completed_at: { type: "timestamp", nullable: true },
283
+ created_at: { type: "timestamp" },
284
+ updated_at: { type: "timestamp" }
285
+ },
286
+ uniqueIndexes: [
287
+ {
288
+ name: "factory_pending_starts_tenant_kickoff_unique",
289
+ columns: ["org_id", "factory_project_id", "kickoff_key"]
290
+ }
291
+ ]
292
+ }
293
+ ];
294
+ function toBinding(row) {
295
+ return {
296
+ id: row.id,
297
+ orgId: row.org_id,
298
+ factoryProjectId: String(row.factory_project_id),
299
+ workItemId: String(row.work_item_id),
300
+ role: String(row.role),
301
+ threadId: String(row.thread_id),
302
+ resourceId: String(row.resource_id),
303
+ sessionId: String(row.session_id),
304
+ branch: String(row.branch),
305
+ status: row.status,
306
+ createdAt: row.created_at,
307
+ revokedAt: row.revoked_at ?? null
308
+ };
309
+ }
310
+ function toDeferredDecision(row) {
311
+ return {
312
+ id: row.id,
313
+ orgId: row.org_id,
314
+ factoryProjectId: String(row.factory_project_id),
315
+ evaluationId: String(row.evaluation_id),
316
+ workItemId: row.work_item_id === null || row.work_item_id === void 0 ? null : String(row.work_item_id),
317
+ idempotencyKey: String(row.idempotency_key),
318
+ effectOrdinal: Number(row.effect_ordinal),
319
+ effectHash: String(row.effect_hash),
320
+ causalChain: row.causal_chain ?? [],
321
+ actor: row.actor ?? null,
322
+ decision: row.decision,
323
+ status: row.status,
324
+ attempts: Number(row.attempts),
325
+ availableAt: row.available_at,
326
+ leaseOwner: row.lease_owner ?? null,
327
+ leaseExpiresAt: row.lease_expires_at ?? null,
328
+ lastError: row.last_error ?? null,
329
+ completedAt: row.completed_at ?? null,
330
+ createdAt: row.created_at,
331
+ updatedAt: row.updated_at
332
+ };
333
+ }
334
+ function toPendingStart(row) {
335
+ return {
336
+ id: row.id,
337
+ orgId: row.org_id,
338
+ factoryProjectId: String(row.factory_project_id),
339
+ bindingId: String(row.binding_id),
340
+ kickoffKey: String(row.kickoff_key),
341
+ message: row.message ?? null,
342
+ status: row.status,
343
+ attempts: Number(row.attempts),
344
+ availableAt: row.available_at,
345
+ leaseOwner: row.lease_owner ?? null,
346
+ leaseExpiresAt: row.lease_expires_at ?? null,
347
+ lastError: row.last_error ?? null,
348
+ completedAt: row.completed_at ?? null,
349
+ createdAt: row.created_at,
350
+ updatedAt: row.updated_at
351
+ };
352
+ }
353
+ var WorkItemsStorage = class extends FactoryStorageDomain {
354
+ constructor() {
355
+ super("work-items");
356
+ }
357
+ async init() {
358
+ await this.ensureCollections([WORK_ITEMS_SCHEMA, ...FACTORY_GOVERNANCE_SCHEMAS]);
359
+ }
360
+ async dangerouslyClearAll() {
361
+ await this.ops.deleteMany("work_items", {});
362
+ }
363
+ #leaseQueue = Promise.resolve();
364
+ get #db() {
365
+ return this.ops;
366
+ }
367
+ async #withLocalLeaseLock(fn) {
368
+ const prior = this.#leaseQueue;
369
+ let release;
370
+ this.#leaseQueue = new Promise((resolve) => {
371
+ release = resolve;
372
+ });
373
+ await prior;
374
+ try {
375
+ return await fn();
376
+ } finally {
377
+ release();
378
+ }
379
+ }
380
+ async #withProjectRelationLock(orgId, factoryProjectId, fn) {
381
+ const key = `work-items:${orgId}:${factoryProjectId}`;
382
+ return withInProcessProjectLock(
383
+ key,
384
+ () => this.storage.withDistributedLock ? this.storage.withDistributedLock(key, fn) : fn()
385
+ );
386
+ }
387
+ async #claimLeases(table, input, map) {
388
+ const claim = () => this.storage.withTransaction(async (ops) => {
389
+ const candidates = await ops.findMany(table, {}, { orderBy: [["created_at", "asc"]] });
390
+ const claimed = [];
391
+ for (const candidate of candidates) {
392
+ if (claimed.length >= input.limit) break;
393
+ const availableAt = new Date(candidate.available_at).getTime();
394
+ const leaseExpiresAt = candidate.lease_expires_at ? new Date(candidate.lease_expires_at).getTime() : 0;
395
+ const claimable = (candidate.status === "pending" || candidate.status === "retry") && availableAt <= input.now.getTime();
396
+ const expired = candidate.status === "leased" && leaseExpiresAt <= input.now.getTime();
397
+ if (!claimable && !expired) continue;
398
+ let didClaim = false;
399
+ const row = await ops.updateAtomic(table, { id: candidate.id }, (current) => {
400
+ const currentAvailable = new Date(current.available_at).getTime();
401
+ const currentExpiry = current.lease_expires_at ? new Date(current.lease_expires_at).getTime() : 0;
402
+ const currentClaimable = (current.status === "pending" || current.status === "retry") && currentAvailable <= input.now.getTime();
403
+ const currentExpired = current.status === "leased" && currentExpiry <= input.now.getTime();
404
+ if (!currentClaimable && !currentExpired) return null;
405
+ didClaim = true;
406
+ return {
407
+ status: "leased",
408
+ attempts: Number(current.attempts) + 1,
409
+ lease_owner: input.ownerId,
410
+ lease_expires_at: input.leaseExpiresAt,
411
+ updated_at: input.now
412
+ };
413
+ });
414
+ if (didClaim && row) claimed.push(map(row));
415
+ }
416
+ return claimed;
417
+ });
418
+ return this.storage.withDistributedLock ? this.storage.withDistributedLock(`factory-lease:${table}`, claim) : this.#withLocalLeaseLock(claim);
419
+ }
420
+ async #renewLease(table, identity, leaseExpiresAt) {
421
+ let renewed = false;
422
+ await this.#db.updateAtomic(
423
+ table,
424
+ { id: identity.id, org_id: identity.orgId, factory_project_id: identity.factoryProjectId },
425
+ (current) => {
426
+ if (current.status !== "leased" || current.lease_owner !== identity.ownerId) return null;
427
+ renewed = true;
428
+ return { lease_expires_at: leaseExpiresAt, updated_at: /* @__PURE__ */ new Date() };
429
+ }
430
+ );
431
+ return renewed;
432
+ }
433
+ async #completeLease(table, identity, now) {
434
+ let completed = false;
435
+ const row = await this.#db.updateAtomic(
436
+ table,
437
+ { id: identity.id, org_id: identity.orgId, factory_project_id: identity.factoryProjectId },
438
+ (current) => {
439
+ if (current.status !== "leased" || current.lease_owner !== identity.ownerId) return null;
440
+ completed = true;
441
+ return {
442
+ status: table === "factory_pending_starts" ? "sent" : "succeeded",
443
+ lease_owner: null,
444
+ lease_expires_at: null,
445
+ completed_at: now,
446
+ updated_at: now
447
+ };
448
+ }
449
+ );
450
+ return completed ? row : null;
451
+ }
452
+ async #failLease(table, input) {
453
+ let failed = false;
454
+ const row = await this.#db.updateAtomic(
455
+ table,
456
+ { id: input.id, org_id: input.orgId, factory_project_id: input.factoryProjectId },
457
+ (current) => {
458
+ if (current.status !== "leased" || current.lease_owner !== input.ownerId) return null;
459
+ failed = true;
460
+ return {
461
+ status: input.terminal ? "failed" : "retry",
462
+ available_at: input.availableAt,
463
+ lease_owner: null,
464
+ lease_expires_at: null,
465
+ last_error: input.lastError,
466
+ completed_at: input.terminal ? input.now : null,
467
+ updated_at: input.now
468
+ };
469
+ }
470
+ );
471
+ return failed ? row : null;
472
+ }
473
+ /** List the org's work items for a project, newest first. */
474
+ async list({ orgId, factoryProjectId }) {
475
+ const rows = await this.#db.findMany(
476
+ "work_items",
477
+ { org_id: orgId, factory_project_id: factoryProjectId },
478
+ { orderBy: [["updated_at", "desc"]] }
479
+ );
480
+ return rows.map(toWorkItem);
481
+ }
482
+ async get({ orgId, id }) {
483
+ const row = await this.#db.findOne("work_items", { org_id: orgId, id });
484
+ return row ? toWorkItem(row) : null;
485
+ }
486
+ async getForProject(orgId, factoryProjectId, id) {
487
+ const row = await this.#db.findOne("work_items", {
488
+ id,
489
+ org_id: orgId,
490
+ factory_project_id: factoryProjectId
491
+ });
492
+ return row ? toRow(row) : null;
493
+ }
494
+ async getTransitionResultByIngress(orgId, factoryProjectId, identity) {
495
+ const row = await this.#db.findOne("factory_rule_ingress", {
496
+ org_id: orgId,
497
+ factory_project_id: factoryProjectId,
498
+ identity
499
+ });
500
+ return row?.result ?? null;
501
+ }
502
+ async commitTransition(input) {
503
+ const commit = () => this.storage.withTransaction(async (ops) => {
504
+ const prior = await ops.findOne("factory_rule_ingress", {
505
+ org_id: input.orgId,
506
+ factory_project_id: input.factoryProjectId,
507
+ identity: input.ingress.identity
508
+ });
509
+ if (prior)
510
+ return {
511
+ status: "replayed",
512
+ item: await this.getForProject(input.orgId, input.factoryProjectId, input.workItemId),
513
+ result: prior.result
514
+ };
515
+ const now = /* @__PURE__ */ new Date();
516
+ let item = null;
517
+ let code = null;
518
+ let reason = null;
519
+ let result;
520
+ const updated = await ops.updateAtomic(
521
+ "work_items",
522
+ {
523
+ id: input.workItemId,
524
+ org_id: input.orgId,
525
+ factory_project_id: input.factoryProjectId
526
+ },
527
+ (row) => {
528
+ const existing = toRow(row);
529
+ item = existing;
530
+ if (existing.revision !== input.expectedRevision) {
531
+ code = "stale";
532
+ reason = "The work item changed before this transition committed.";
533
+ return null;
534
+ }
535
+ if (input.evaluation.outcome === "rejected") {
536
+ code = input.evaluation.code;
537
+ reason = input.evaluation.reason;
538
+ return null;
539
+ }
540
+ if (existing.stages.length === 1 && existing.stages[0] === input.destinationStage) return null;
541
+ return patchColumns({
542
+ stages: [input.destinationStage],
543
+ stageHistory: applyStageTransition(
544
+ existing.stageHistory,
545
+ existing.stages,
546
+ [input.destinationStage],
547
+ input.actorId,
548
+ now
549
+ ),
550
+ revision: existing.revision + 1,
551
+ updatedAt: now
552
+ });
553
+ }
554
+ );
555
+ if (updated) item = toRow(updated);
556
+ if (!item) {
557
+ code = input.evaluation.outcome === "rejected" ? input.evaluation.code : "invalid_transition";
558
+ reason = input.evaluation.outcome === "rejected" ? input.evaluation.reason : "Work item not found.";
559
+ }
560
+ const outcome = item && code === null && input.evaluation.outcome === "accepted" ? "accepted" : "rejected";
561
+ result = outcome === "accepted" ? {
562
+ status: "accepted",
563
+ transitionId: input.ingress.transitionId,
564
+ itemId: item.id,
565
+ revision: item.revision,
566
+ stage: input.destinationStage,
567
+ decisions: input.evaluation.outcome === "accepted" ? input.evaluation.decisions : []
568
+ } : { status: "rejected", transitionId: input.ingress.transitionId, itemId: input.workItemId, code, reason };
569
+ const ingress = await ops.insertOne("factory_rule_ingress", {
570
+ org_id: input.orgId,
571
+ factory_project_id: input.factoryProjectId,
572
+ identity: input.ingress.identity,
573
+ trigger_type: input.ingress.triggerType,
574
+ transition_id: input.ingress.transitionId,
575
+ result,
576
+ created_at: now
577
+ });
578
+ if (item) {
579
+ const evaluation = await ops.insertOne("factory_rule_evaluations", {
580
+ ingress_id: ingress.id,
581
+ work_item_id: item.id,
582
+ rule_set_version: input.ruleSetVersion,
583
+ expected_revision: input.expectedRevision,
584
+ outcome,
585
+ code,
586
+ reason,
587
+ causal_chain: input.causalChain,
588
+ created_at: now
589
+ });
590
+ if (outcome === "accepted" && input.evaluation.outcome === "accepted") {
591
+ for (const [index, decision] of input.evaluation.decisions.entries()) {
592
+ await ops.insertOne("factory_deferred_decisions", {
593
+ org_id: input.orgId,
594
+ factory_project_id: input.factoryProjectId,
595
+ evaluation_id: evaluation.id,
596
+ work_item_id: item.id,
597
+ idempotency_key: String(decision.idempotencyKey),
598
+ effect_ordinal: index,
599
+ effect_hash: factoryDecisionHash(decision),
600
+ causal_chain: input.causalChain,
601
+ actor: null,
602
+ decision,
603
+ status: "pending",
604
+ attempts: 0,
605
+ available_at: now,
606
+ lease_owner: null,
607
+ lease_expires_at: null,
608
+ last_error: null,
609
+ completed_at: null,
610
+ created_at: new Date(now.getTime() + index),
611
+ updated_at: now
612
+ });
613
+ }
614
+ }
615
+ }
616
+ return { status: "committed", item, result };
617
+ });
618
+ return this.storage.withDistributedLock ? this.storage.withDistributedLock(
619
+ `factory-ingress:${input.orgId}:${input.factoryProjectId}:${input.ingress.identity}`,
620
+ commit
621
+ ) : commit();
622
+ }
623
+ async commitRuleEvaluation(input) {
624
+ const commit = () => this.storage.withTransaction(async (ops) => {
625
+ const prior = await ops.findOne("factory_rule_ingress", {
626
+ org_id: input.orgId,
627
+ factory_project_id: input.factoryProjectId,
628
+ identity: input.ingress.identity
629
+ });
630
+ if (prior) {
631
+ const result2 = prior.result;
632
+ const decisions2 = Array.isArray(result2.decisions) ? result2.decisions : [];
633
+ const evaluation2 = await ops.findOne("factory_rule_evaluations", { ingress_id: prior.id });
634
+ for (const decision of decisions2) {
635
+ if (!evaluation2 || !decision || typeof decision !== "object" || decision.type !== "upsertLinkedWorkItem" || typeof decision.sourceKey !== "string" || typeof decision.idempotencyKey !== "string") {
636
+ continue;
637
+ }
638
+ const materialization = decision;
639
+ const item2 = await ops.findOne("work_items", {
640
+ org_id: input.orgId,
641
+ factory_project_id: input.factoryProjectId,
642
+ source_key: materialization.sourceKey
643
+ });
644
+ if (item2) continue;
645
+ await ops.updateAtomic(
646
+ "factory_deferred_decisions",
647
+ {
648
+ org_id: input.orgId,
649
+ factory_project_id: input.factoryProjectId,
650
+ evaluation_id: evaluation2.id,
651
+ idempotency_key: materialization.idempotencyKey
652
+ },
653
+ (current) => current.status === "succeeded" ? {
654
+ status: "retry",
655
+ attempts: 0,
656
+ available_at: input.now,
657
+ lease_owner: null,
658
+ lease_expires_at: null,
659
+ last_error: null,
660
+ completed_at: null,
661
+ updated_at: input.now
662
+ } : null
663
+ );
664
+ }
665
+ return { status: "replayed", result: result2 };
666
+ }
667
+ const itemRow = input.workItemId ? await ops.findOne("work_items", {
668
+ id: input.workItemId,
669
+ org_id: input.orgId,
670
+ factory_project_id: input.factoryProjectId
671
+ }) : null;
672
+ if (input.workItemId !== null && !itemRow) return { status: "missing" };
673
+ const item = itemRow ? toRow(itemRow) : null;
674
+ const stale = item !== null && item.revision !== input.expectedRevision;
675
+ const outcome = stale ? "rejected" : input.outcome.status;
676
+ const code = stale ? "stale" : input.outcome.code ?? null;
677
+ const reason = stale ? "The work item changed before this rule evaluation committed." : input.outcome.reason ?? null;
678
+ const decisions = outcome === "accepted" ? input.decisions : [];
679
+ const result = {
680
+ status: outcome,
681
+ itemId: item?.id ?? null,
682
+ revision: item?.revision ?? null,
683
+ code,
684
+ reason,
685
+ decisions
686
+ };
687
+ const ingress = await ops.insertOne("factory_rule_ingress", {
688
+ org_id: input.orgId,
689
+ factory_project_id: input.factoryProjectId,
690
+ identity: input.ingress.identity,
691
+ trigger_type: input.ingress.triggerType,
692
+ transition_id: input.ingress.identity,
693
+ result,
694
+ created_at: input.now
695
+ });
696
+ const evaluation = await ops.insertOne("factory_rule_evaluations", {
697
+ ingress_id: ingress.id,
698
+ work_item_id: item?.id ?? null,
699
+ rule_set_version: input.ruleSetVersion,
700
+ expected_revision: input.expectedRevision,
701
+ outcome,
702
+ code,
703
+ reason,
704
+ causal_chain: input.causalChain,
705
+ created_at: input.now
706
+ });
707
+ for (const [effectOrdinal, decision] of decisions.entries()) {
708
+ await ops.insertOne("factory_deferred_decisions", {
709
+ org_id: input.orgId,
710
+ factory_project_id: input.factoryProjectId,
711
+ evaluation_id: evaluation.id,
712
+ work_item_id: item?.id ?? null,
713
+ idempotency_key: String(decision.idempotencyKey),
714
+ effect_ordinal: effectOrdinal,
715
+ effect_hash: factoryDecisionHash(decision),
716
+ causal_chain: input.causalChain,
717
+ actor: input.actor,
718
+ decision,
719
+ status: "pending",
720
+ attempts: 0,
721
+ available_at: input.now,
722
+ lease_owner: null,
723
+ lease_expires_at: null,
724
+ last_error: null,
725
+ completed_at: null,
726
+ created_at: new Date(input.now.getTime() + effectOrdinal),
727
+ updated_at: input.now
728
+ });
729
+ }
730
+ return { status: "committed", result };
731
+ });
732
+ return this.storage.withDistributedLock ? this.storage.withDistributedLock(
733
+ `factory-ingress:${input.orgId}:${input.factoryProjectId}:${input.ingress.identity}`,
734
+ commit
735
+ ) : commit();
736
+ }
737
+ async getToolResultCursor(orgId, factoryProjectId, bindingId) {
738
+ const row = await this.#db.findOne("factory_tool_result_cursors", {
739
+ org_id: orgId,
740
+ factory_project_id: factoryProjectId,
741
+ binding_id: bindingId
742
+ });
743
+ return row ? {
744
+ bindingId: String(row.binding_id),
745
+ orgId: row.org_id,
746
+ factoryProjectId: String(row.factory_project_id),
747
+ lastMessageId: String(row.last_message_id),
748
+ lastMessageCreatedAt: row.last_message_created_at,
749
+ updatedAt: row.updated_at
750
+ } : null;
751
+ }
752
+ async advanceToolResultCursor(cursor) {
753
+ const current = await this.getToolResultCursor(cursor.orgId, cursor.factoryProjectId, cursor.bindingId);
754
+ if (current && current.lastMessageCreatedAt > cursor.lastMessageCreatedAt) return;
755
+ await this.#db.upsertOne("factory_tool_result_cursors", ["binding_id"], {
756
+ binding_id: cursor.bindingId,
757
+ org_id: cursor.orgId,
758
+ factory_project_id: cursor.factoryProjectId,
759
+ last_message_id: cursor.lastMessageId,
760
+ last_message_created_at: cursor.lastMessageCreatedAt,
761
+ updated_at: cursor.updatedAt
762
+ });
763
+ }
764
+ async listDeferredDecisions(orgId, factoryProjectId) {
765
+ return (await this.#db.findMany(
766
+ "factory_deferred_decisions",
767
+ { org_id: orgId, factory_project_id: factoryProjectId },
768
+ { orderBy: [["created_at", "asc"]] }
769
+ )).map(toDeferredDecision);
770
+ }
771
+ /** Read a bounded newest-first status page without exposing another tenant. */
772
+ async listDeferredDecisionPage(input) {
773
+ const rows = await this.#db.findMany(
774
+ "factory_deferred_decisions",
775
+ {
776
+ org_id: input.orgId,
777
+ factory_project_id: input.factoryProjectId,
778
+ ...input.statuses ? { status: { in: input.statuses } } : {}
779
+ },
780
+ {
781
+ orderBy: [
782
+ ["created_at", "desc"],
783
+ ["id", "desc"]
784
+ ],
785
+ limit: input.limit + 1,
786
+ ...input.before ? { cursor: { values: [input.before.createdAt, input.before.id] } } : {}
787
+ }
788
+ );
789
+ return { decisions: rows.slice(0, input.limit).map(toDeferredDecision), hasMore: rows.length > input.limit };
790
+ }
791
+ async claimDeferredDecisions(input) {
792
+ return this.#claimLeases("factory_deferred_decisions", input, toDeferredDecision);
793
+ }
794
+ async renewDeferredDecisionLease(identity, leaseExpiresAt) {
795
+ return this.#renewLease("factory_deferred_decisions", identity, leaseExpiresAt);
796
+ }
797
+ async completeDeferredDecision(identity, now) {
798
+ const row = await this.#completeLease("factory_deferred_decisions", identity, now);
799
+ return row ? toDeferredDecision(row) : null;
800
+ }
801
+ async failDeferredDecision(input) {
802
+ const row = await this.#failLease("factory_deferred_decisions", input);
803
+ return row ? toDeferredDecision(row) : null;
804
+ }
805
+ /** Requeue the same idempotent terminal effect; non-failed decisions are never rerun. */
806
+ async retryDeferredDecision(orgId, factoryProjectId, decisionId, now) {
807
+ let retried = false;
808
+ const row = await this.#db.updateAtomic(
809
+ "factory_deferred_decisions",
810
+ { id: decisionId, org_id: orgId, factory_project_id: factoryProjectId },
811
+ (current) => {
812
+ if (current.status !== "failed") return null;
813
+ retried = true;
814
+ return {
815
+ status: "retry",
816
+ attempts: 0,
817
+ available_at: now,
818
+ lease_owner: null,
819
+ lease_expires_at: null,
820
+ last_error: null,
821
+ completed_at: null,
822
+ updated_at: now
823
+ };
824
+ }
825
+ );
826
+ return retried && row ? toDeferredDecision(row) : null;
827
+ }
828
+ /** Resolve exact active agent authority; partial session matches never authorize. */
829
+ async findActiveRunBinding(address) {
830
+ const row = await this.#db.findOne("factory_run_bindings", {
831
+ org_id: address.orgId,
832
+ factory_project_id: address.factoryProjectId,
833
+ thread_id: address.threadId,
834
+ resource_id: address.resourceId,
835
+ session_id: address.sessionId,
836
+ status: "active"
837
+ });
838
+ return row ? toBinding(row) : null;
839
+ }
840
+ /** Resolve exact bound-session state for processor awareness; ambiguous cross-tenant matches return null. */
841
+ async findRunBindingBySession(address) {
842
+ const rows = await this.#db.findMany("factory_run_bindings", {
843
+ factory_project_id: address.factoryProjectId,
844
+ thread_id: address.threadId,
845
+ resource_id: address.resourceId,
846
+ session_id: address.sessionId
847
+ });
848
+ if (new Set(rows.map((row2) => row2.org_id)).size !== 1) return null;
849
+ const row = rows.sort((left, right) => {
850
+ if (left.status === "active" && right.status !== "active") return -1;
851
+ if (right.status === "active" && left.status !== "active") return 1;
852
+ return right.created_at.getTime() - left.created_at.getTime();
853
+ })[0];
854
+ return row ? toBinding(row) : null;
855
+ }
856
+ /** Revoke one exact tenant-scoped binding. */
857
+ async revokeRunBinding(input) {
858
+ let revoked = false;
859
+ const row = await this.#db.updateAtomic(
860
+ "factory_run_bindings",
861
+ { id: input.bindingId, org_id: input.orgId, factory_project_id: input.factoryProjectId },
862
+ (current) => {
863
+ if (current.status !== "active") return null;
864
+ revoked = true;
865
+ return { status: "revoked", revoked_at: input.revokedAt };
866
+ }
867
+ );
868
+ return revoked && row ? toBinding(row) : null;
869
+ }
870
+ /** Enumerate active bindings for the server-owned restart reconciler. */
871
+ async listActiveRunBindings() {
872
+ return (await this.#db.findMany("factory_run_bindings", { status: "active" })).map(toBinding);
873
+ }
874
+ /** List binding history, optionally narrowed to one work item. */
875
+ async listRunBindings(orgId, factoryProjectId, workItemId) {
876
+ return (await this.#db.findMany(
877
+ "factory_run_bindings",
878
+ {
879
+ org_id: orgId,
880
+ factory_project_id: factoryProjectId,
881
+ ...workItemId ? { work_item_id: workItemId } : {}
882
+ },
883
+ { orderBy: [["created_at", "asc"]] }
884
+ )).map(toBinding);
885
+ }
886
+ async listPendingStarts(orgId, factoryProjectId) {
887
+ return (await this.#db.findMany(
888
+ "factory_pending_starts",
889
+ { org_id: orgId, factory_project_id: factoryProjectId },
890
+ { orderBy: [["created_at", "asc"]] }
891
+ )).map(toPendingStart);
892
+ }
893
+ async claimPendingStarts(input) {
894
+ return this.#claimLeases("factory_pending_starts", input, toPendingStart);
895
+ }
896
+ async renewPendingStartLease(identity, leaseExpiresAt) {
897
+ return this.#renewLease("factory_pending_starts", identity, leaseExpiresAt);
898
+ }
899
+ async completePendingStart(identity, now) {
900
+ const row = await this.#completeLease("factory_pending_starts", identity, now);
901
+ return row ? toPendingStart(row) : null;
902
+ }
903
+ async failPendingStart(input) {
904
+ const row = await this.#failLease("factory_pending_starts", input);
905
+ return row ? toPendingStart(row) : null;
906
+ }
907
+ async prepareRunStart(input) {
908
+ const prepare = () => this.storage.withTransaction(async (ops) => {
909
+ const prior = await ops.findOne("factory_pending_starts", {
910
+ org_id: input.orgId,
911
+ factory_project_id: input.factoryProjectId,
912
+ kickoff_key: input.kickoffKey
913
+ });
914
+ if (prior) {
915
+ const bindingRow2 = await ops.findOne("factory_run_bindings", {
916
+ id: String(prior.binding_id),
917
+ org_id: input.orgId,
918
+ factory_project_id: input.factoryProjectId
919
+ });
920
+ const itemRow = bindingRow2 && await ops.findOne("work_items", {
921
+ id: String(bindingRow2.work_item_id),
922
+ org_id: input.orgId,
923
+ factory_project_id: input.factoryProjectId
924
+ });
925
+ if (!bindingRow2 || !itemRow) throw new Error("Factory start replay references missing state.");
926
+ return {
927
+ item: toRow(itemRow),
928
+ binding: toBinding(bindingRow2),
929
+ pendingStart: toPendingStart(prior),
930
+ replayed: true
931
+ };
932
+ }
933
+ const now = /* @__PURE__ */ new Date();
934
+ const create = input.workItem.input;
935
+ let row = input.workItem.id ? await ops.findOne("work_items", {
936
+ id: input.workItem.id,
937
+ org_id: input.orgId,
938
+ factory_project_id: input.factoryProjectId
939
+ }) : sourceKey(create.externalSource) ? await ops.findOne("work_items", {
940
+ org_id: input.orgId,
941
+ factory_project_id: input.factoryProjectId,
942
+ source_key: sourceKey(create.externalSource)
943
+ }) : null;
944
+ let item;
945
+ if (row) {
946
+ row = await ops.updateAtomic("work_items", { id: row.id }, (current) => {
947
+ const roles = /* @__PURE__ */ new Set([...Object.keys(current.sessions), input.role]);
948
+ const sessions = Object.fromEntries([...roles].map((role) => [role, input.session]));
949
+ return applyUpdate({ current, userId: input.userId, input: { sessions } });
950
+ });
951
+ item = toRow(row);
952
+ } else {
953
+ if (create.parentWorkItemId)
954
+ validateParentRelation(
955
+ (await ops.findMany("work_items", {
956
+ org_id: input.orgId,
957
+ factory_project_id: input.factoryProjectId
958
+ })).map(toRow),
959
+ void 0,
960
+ create.parentWorkItemId
961
+ );
962
+ row = await ops.insertOne("work_items", {
963
+ org_id: input.orgId,
964
+ created_by: input.userId,
965
+ factory_project_id: input.factoryProjectId,
966
+ external_source: create.externalSource ?? null,
967
+ source_key: sourceKey(create.externalSource),
968
+ parent_work_item_id: create.parentWorkItemId ?? null,
969
+ title: create.title,
970
+ stages: create.stages ?? [],
971
+ stage_history: applyStageTransition([], [], create.stages ?? [], input.userId, now),
972
+ sessions: stampSessions({ [input.role]: input.session }, input.userId),
973
+ metadata: create.metadata ?? null,
974
+ revision: 1,
975
+ created_at: now,
976
+ updated_at: now
977
+ });
978
+ item = toRow(row);
979
+ }
980
+ await ops.updateMany(
981
+ "factory_run_bindings",
982
+ {
983
+ org_id: input.orgId,
984
+ factory_project_id: input.factoryProjectId,
985
+ work_item_id: item.id,
986
+ role: input.role,
987
+ status: "active"
988
+ },
989
+ { status: "revoked", revoked_at: now }
990
+ );
991
+ const bindingRow = await ops.insertOne("factory_run_bindings", {
992
+ org_id: input.orgId,
993
+ factory_project_id: input.factoryProjectId,
994
+ work_item_id: item.id,
995
+ role: input.role,
996
+ thread_id: input.session.threadId,
997
+ resource_id: input.resourceId,
998
+ session_id: input.session.sessionId,
999
+ branch: input.session.branch,
1000
+ status: "active",
1001
+ created_at: now,
1002
+ revoked_at: null
1003
+ });
1004
+ const pendingRow = await ops.insertOne("factory_pending_starts", {
1005
+ org_id: input.orgId,
1006
+ factory_project_id: input.factoryProjectId,
1007
+ binding_id: bindingRow.id,
1008
+ kickoff_key: input.kickoffKey,
1009
+ message: input.kickoffMessage,
1010
+ status: "pending",
1011
+ attempts: 0,
1012
+ available_at: now,
1013
+ lease_owner: null,
1014
+ lease_expires_at: null,
1015
+ last_error: null,
1016
+ completed_at: null,
1017
+ created_at: now,
1018
+ updated_at: now
1019
+ });
1020
+ return { item, binding: toBinding(bindingRow), pendingStart: toPendingStart(pendingRow), replayed: false };
1021
+ });
1022
+ return this.storage.withDistributedLock ? this.storage.withDistributedLock(
1023
+ `factory-start:${input.orgId}:${input.factoryProjectId}:${input.kickoffKey}`,
1024
+ prepare
1025
+ ) : prepare();
1026
+ }
1027
+ async markPendingStart(bindingId, status, lastError) {
1028
+ const row = await this.#db.updateAtomic(
1029
+ "factory_pending_starts",
1030
+ { binding_id: bindingId },
1031
+ () => ({ status, last_error: lastError ?? null, updated_at: /* @__PURE__ */ new Date() })
1032
+ );
1033
+ return row ? toPendingStart(row) : null;
1034
+ }
1035
+ /**
1036
+ * Create a work item, reusing the existing record when `sourceKey` already
1037
+ * has one for the project (acting twice on the same issue must not duplicate
1038
+ * the card). On reuse the provided stages replace the current ones (with the
1039
+ * transition recorded in history) and sessions/metadata are merged in. The
1040
+ * result discriminates insert from reuse so callers can audit the actual
1041
+ * outcome.
1042
+ */
1043
+ async upsert(params) {
1044
+ const run = () => this.#upsert(params);
1045
+ return params.input.parentWorkItemId ? this.#withProjectRelationLock(params.orgId, params.factoryProjectId, run) : run();
1046
+ }
1047
+ async #upsert({
1048
+ orgId,
1049
+ userId,
1050
+ factoryProjectId,
1051
+ input,
1052
+ reuseMode = "update"
1053
+ }) {
1054
+ const key = sourceKey(input.externalSource);
1055
+ const reuse = async () => {
1056
+ if (!key) return null;
1057
+ const existing = await this.#db.findOne("work_items", {
1058
+ org_id: orgId,
1059
+ factory_project_id: factoryProjectId,
1060
+ source_key: key
1061
+ });
1062
+ if (!existing) return null;
1063
+ if (reuseMode === "preserve") {
1064
+ const item = toWorkItem(existing);
1065
+ return { created: false, item, previous: priorState(existing) };
1066
+ }
1067
+ let previous = emptyPrior();
1068
+ const updated = await this.#db.updateAtomic(
1069
+ "work_items",
1070
+ { org_id: orgId, factory_project_id: factoryProjectId, source_key: key },
1071
+ async (current) => {
1072
+ previous = priorState(current);
1073
+ const fullPatch = input.parentWorkItemId === null ? { ...input, parentWorkItemId: void 0 } : input;
1074
+ const patch = reuseMode === "non-stage" ? {
1075
+ title: input.title,
1076
+ parentWorkItemId: input.parentWorkItemId ?? void 0,
1077
+ metadata: input.metadata
1078
+ } : fullPatch;
1079
+ if (patch.parentWorkItemId !== void 0) {
1080
+ validateParentRelation(await this.list({ orgId, factoryProjectId }), current.id, patch.parentWorkItemId);
1081
+ }
1082
+ return {
1083
+ external_source: input.externalSource ?? null,
1084
+ ...applyUpdate({ current, userId, input: patch })
1085
+ };
1086
+ }
1087
+ );
1088
+ return updated ? { item: toWorkItem(updated), created: false, previous } : null;
1089
+ };
1090
+ const reused = await reuse();
1091
+ if (reused) return reused;
1092
+ const now = /* @__PURE__ */ new Date();
1093
+ const stages = input.stages ?? ["intake"];
1094
+ try {
1095
+ validateParentRelation(await this.list({ orgId, factoryProjectId }), void 0, input.parentWorkItemId ?? null);
1096
+ const row = await this.#db.insertOne("work_items", {
1097
+ org_id: orgId,
1098
+ factory_project_id: factoryProjectId,
1099
+ external_source: input.externalSource ?? null,
1100
+ source_key: key,
1101
+ parent_work_item_id: input.parentWorkItemId ?? null,
1102
+ title: input.title,
1103
+ stages,
1104
+ stage_history: stages.map((stage) => ({ stage, enteredAt: now.toISOString(), by: userId })),
1105
+ sessions: stampSessions(input.sessions ?? {}, userId),
1106
+ metadata: input.metadata ?? null,
1107
+ revision: 1,
1108
+ created_by: userId,
1109
+ created_at: now,
1110
+ updated_at: now
1111
+ });
1112
+ return { item: toWorkItem(row), created: true, previous: emptyPrior() };
1113
+ } catch (error) {
1114
+ if (!(error instanceof UniqueViolationError)) throw error;
1115
+ const winner = await reuse();
1116
+ if (winner) return winner;
1117
+ throw error;
1118
+ }
1119
+ }
1120
+ async update({
1121
+ orgId,
1122
+ id,
1123
+ userId,
1124
+ patch
1125
+ }) {
1126
+ const run = async () => {
1127
+ let previous = emptyPrior();
1128
+ const row = await this.#db.updateAtomic("work_items", { org_id: orgId, id }, async (current) => {
1129
+ previous = priorState(current);
1130
+ if (patch.parentWorkItemId !== void 0) {
1131
+ validateParentRelation(
1132
+ await this.list({ orgId, factoryProjectId: current.factory_project_id }),
1133
+ current.id,
1134
+ patch.parentWorkItemId
1135
+ );
1136
+ }
1137
+ return applyUpdate({ current, userId, input: patch });
1138
+ });
1139
+ return row ? { item: toWorkItem(row), previous } : null;
1140
+ };
1141
+ if (patch.parentWorkItemId === void 0) return run();
1142
+ const candidate = await this.#db.findOne("work_items", { org_id: orgId, id });
1143
+ if (!candidate) return null;
1144
+ return this.#withProjectRelationLock(orgId, candidate.factory_project_id, run);
1145
+ }
1146
+ async delete({ orgId, id }) {
1147
+ const candidate = await this.#db.findOne("work_items", { org_id: orgId, id });
1148
+ if (!candidate) return null;
1149
+ return this.#withProjectRelationLock(orgId, candidate.factory_project_id, async () => {
1150
+ const existing = await this.#db.findOne("work_items", { org_id: orgId, id });
1151
+ if (!existing) return null;
1152
+ const deleted = await this.#db.deleteMany("work_items", { org_id: orgId, id });
1153
+ if (deleted === 0) return null;
1154
+ await this.#db.updateMany(
1155
+ "work_items",
1156
+ { org_id: orgId, parent_work_item_id: id },
1157
+ { parent_work_item_id: null, updated_at: /* @__PURE__ */ new Date() }
1158
+ );
1159
+ return toWorkItem(existing);
1160
+ });
1161
+ }
1162
+ };
1163
+ export {
1164
+ AUTOMATION_ACTORS,
1165
+ WORK_ITEMS_SCHEMA,
1166
+ WorkItemRelationError,
1167
+ WorkItemsStorage,
1168
+ applyStageTransition,
1169
+ factoryDecisionHash,
1170
+ isAutomationActor,
1171
+ stampSessions,
1172
+ validateParentRelation
1173
+ };
1174
+ //# sourceMappingURL=base.js.map