@mastra/factory 0.0.0-fix-thread-subscription-multi-instance-redis-streams-20260730204150

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 (313) hide show
  1. package/CHANGELOG.md +695 -0
  2. package/LICENSE.md +30 -0
  3. package/README.md +27 -0
  4. package/dist/auth.d.ts +181 -0
  5. package/dist/auth.d.ts.map +1 -0
  6. package/dist/auth.js +505 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/capabilities/connection.d.ts +9 -0
  9. package/dist/capabilities/connection.d.ts.map +1 -0
  10. package/dist/capabilities/connection.js +1 -0
  11. package/dist/capabilities/intake.d.ts +140 -0
  12. package/dist/capabilities/intake.d.ts.map +1 -0
  13. package/dist/capabilities/intake.js +1 -0
  14. package/dist/capabilities/version-control.d.ts +245 -0
  15. package/dist/capabilities/version-control.d.ts.map +1 -0
  16. package/dist/capabilities/version-control.js +1 -0
  17. package/dist/factory.d.ts +156 -0
  18. package/dist/factory.d.ts.map +1 -0
  19. package/dist/factory.js +483 -0
  20. package/dist/factory.js.map +1 -0
  21. package/dist/index.d.ts +14 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +7 -0
  24. package/dist/integrations/base.d.ts +210 -0
  25. package/dist/integrations/base.d.ts.map +1 -0
  26. package/dist/integrations/base.js +1 -0
  27. package/dist/integrations/github/config.d.ts +60 -0
  28. package/dist/integrations/github/config.d.ts.map +1 -0
  29. package/dist/integrations/github/config.js +42 -0
  30. package/dist/integrations/github/config.js.map +1 -0
  31. package/dist/integrations/github/factory-session.d.ts +12 -0
  32. package/dist/integrations/github/factory-session.d.ts.map +1 -0
  33. package/dist/integrations/github/factory-session.js +37 -0
  34. package/dist/integrations/github/factory-session.js.map +1 -0
  35. package/dist/integrations/github/integration.d.ts +191 -0
  36. package/dist/integrations/github/integration.d.ts.map +1 -0
  37. package/dist/integrations/github/integration.js +1077 -0
  38. package/dist/integrations/github/integration.js.map +1 -0
  39. package/dist/integrations/github/issue-triage.d.ts +27 -0
  40. package/dist/integrations/github/issue-triage.d.ts.map +1 -0
  41. package/dist/integrations/github/issue-triage.js +82 -0
  42. package/dist/integrations/github/issue-triage.js.map +1 -0
  43. package/dist/integrations/github/pat.d.ts +38 -0
  44. package/dist/integrations/github/pat.d.ts.map +1 -0
  45. package/dist/integrations/github/pat.js +59 -0
  46. package/dist/integrations/github/pat.js.map +1 -0
  47. package/dist/integrations/github/provenance.d.ts +27 -0
  48. package/dist/integrations/github/provenance.d.ts.map +1 -0
  49. package/dist/integrations/github/provenance.js +75 -0
  50. package/dist/integrations/github/provenance.js.map +1 -0
  51. package/dist/integrations/github/routes.d.ts +69 -0
  52. package/dist/integrations/github/routes.d.ts.map +1 -0
  53. package/dist/integrations/github/routes.js +1324 -0
  54. package/dist/integrations/github/routes.js.map +1 -0
  55. package/dist/integrations/github/rules.d.ts +78 -0
  56. package/dist/integrations/github/rules.d.ts.map +1 -0
  57. package/dist/integrations/github/rules.js +362 -0
  58. package/dist/integrations/github/rules.js.map +1 -0
  59. package/dist/integrations/github/sandbox-release.d.ts +38 -0
  60. package/dist/integrations/github/sandbox-release.d.ts.map +1 -0
  61. package/dist/integrations/github/sandbox-release.js +74 -0
  62. package/dist/integrations/github/sandbox-release.js.map +1 -0
  63. package/dist/integrations/github/sandbox.d.ts +281 -0
  64. package/dist/integrations/github/sandbox.d.ts.map +1 -0
  65. package/dist/integrations/github/sandbox.js +547 -0
  66. package/dist/integrations/github/sandbox.js.map +1 -0
  67. package/dist/integrations/github/session-subscriptions.d.ts +26 -0
  68. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -0
  69. package/dist/integrations/github/session-subscriptions.js +192 -0
  70. package/dist/integrations/github/session-subscriptions.js.map +1 -0
  71. package/dist/integrations/github/subscriptions.d.ts +54 -0
  72. package/dist/integrations/github/subscriptions.d.ts.map +1 -0
  73. package/dist/integrations/github/subscriptions.js +62 -0
  74. package/dist/integrations/github/subscriptions.js.map +1 -0
  75. package/dist/integrations/github/token-refresh.d.ts +12 -0
  76. package/dist/integrations/github/token-refresh.d.ts.map +1 -0
  77. package/dist/integrations/github/token-refresh.js +24 -0
  78. package/dist/integrations/github/token-refresh.js.map +1 -0
  79. package/dist/integrations/github/webhook.d.ts +87 -0
  80. package/dist/integrations/github/webhook.d.ts.map +1 -0
  81. package/dist/integrations/github/webhook.js +397 -0
  82. package/dist/integrations/github/webhook.js.map +1 -0
  83. package/dist/integrations/linear/agent-tools.d.ts +34 -0
  84. package/dist/integrations/linear/agent-tools.d.ts.map +1 -0
  85. package/dist/integrations/linear/agent-tools.js +85 -0
  86. package/dist/integrations/linear/agent-tools.js.map +1 -0
  87. package/dist/integrations/linear/integration.d.ts +233 -0
  88. package/dist/integrations/linear/integration.d.ts.map +1 -0
  89. package/dist/integrations/linear/integration.js +672 -0
  90. package/dist/integrations/linear/integration.js.map +1 -0
  91. package/dist/integrations/linear/routes.d.ts +61 -0
  92. package/dist/integrations/linear/routes.d.ts.map +1 -0
  93. package/dist/integrations/linear/routes.js +239 -0
  94. package/dist/integrations/linear/routes.js.map +1 -0
  95. package/dist/integrations/linear/rules.d.ts +42 -0
  96. package/dist/integrations/linear/rules.d.ts.map +1 -0
  97. package/dist/integrations/linear/rules.js +133 -0
  98. package/dist/integrations/linear/rules.js.map +1 -0
  99. package/dist/integrations/linear/storage.d.ts +65 -0
  100. package/dist/integrations/linear/storage.d.ts.map +1 -0
  101. package/dist/integrations/linear/storage.js +1 -0
  102. package/dist/integrations/platform/api-client.d.ts +26 -0
  103. package/dist/integrations/platform/api-client.d.ts.map +1 -0
  104. package/dist/integrations/platform/api-client.js +156 -0
  105. package/dist/integrations/platform/api-client.js.map +1 -0
  106. package/dist/integrations/platform/github/event-worker.d.ts +49 -0
  107. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -0
  108. package/dist/integrations/platform/github/event-worker.js +348 -0
  109. package/dist/integrations/platform/github/event-worker.js.map +1 -0
  110. package/dist/integrations/platform/github/integration.d.ts +50 -0
  111. package/dist/integrations/platform/github/integration.d.ts.map +1 -0
  112. package/dist/integrations/platform/github/integration.js +961 -0
  113. package/dist/integrations/platform/github/integration.js.map +1 -0
  114. package/dist/integrations/platform/linear/integration.d.ts +46 -0
  115. package/dist/integrations/platform/linear/integration.d.ts.map +1 -0
  116. package/dist/integrations/platform/linear/integration.js +483 -0
  117. package/dist/integrations/platform/linear/integration.js.map +1 -0
  118. package/dist/integrations/workos/integration.d.ts +39 -0
  119. package/dist/integrations/workos/integration.d.ts.map +1 -0
  120. package/dist/integrations/workos/integration.js +91 -0
  121. package/dist/integrations/workos/integration.js.map +1 -0
  122. package/dist/routes/config.d.ts +235 -0
  123. package/dist/routes/config.d.ts.map +1 -0
  124. package/dist/routes/config.js +972 -0
  125. package/dist/routes/config.js.map +1 -0
  126. package/dist/routes/custom-provider-source.d.ts +52 -0
  127. package/dist/routes/custom-provider-source.d.ts.map +1 -0
  128. package/dist/routes/custom-provider-source.js +124 -0
  129. package/dist/routes/custom-provider-source.js.map +1 -0
  130. package/dist/routes/fs.d.ts +151 -0
  131. package/dist/routes/fs.d.ts.map +1 -0
  132. package/dist/routes/fs.js +708 -0
  133. package/dist/routes/fs.js.map +1 -0
  134. package/dist/routes/intake.d.ts +23 -0
  135. package/dist/routes/intake.d.ts.map +1 -0
  136. package/dist/routes/intake.js +179 -0
  137. package/dist/routes/intake.js.map +1 -0
  138. package/dist/routes/oauth.d.ts +43 -0
  139. package/dist/routes/oauth.d.ts.map +1 -0
  140. package/dist/routes/oauth.js +463 -0
  141. package/dist/routes/oauth.js.map +1 -0
  142. package/dist/routes/projects.d.ts +19 -0
  143. package/dist/routes/projects.d.ts.map +1 -0
  144. package/dist/routes/projects.js +461 -0
  145. package/dist/routes/projects.js.map +1 -0
  146. package/dist/routes/provider-credentials.d.ts +81 -0
  147. package/dist/routes/provider-credentials.d.ts.map +1 -0
  148. package/dist/routes/provider-credentials.js +86 -0
  149. package/dist/routes/provider-credentials.js.map +1 -0
  150. package/dist/routes/route.d.ts +48 -0
  151. package/dist/routes/route.d.ts.map +1 -0
  152. package/dist/routes/route.js +15 -0
  153. package/dist/routes/route.js.map +1 -0
  154. package/dist/routes/skills.d.ts +30 -0
  155. package/dist/routes/skills.d.ts.map +1 -0
  156. package/dist/routes/skills.js +168 -0
  157. package/dist/routes/skills.js.map +1 -0
  158. package/dist/routes/surface.d.ts +91 -0
  159. package/dist/routes/surface.d.ts.map +1 -0
  160. package/dist/routes/surface.js +260 -0
  161. package/dist/routes/surface.js.map +1 -0
  162. package/dist/routes/tenant-credentials.d.ts +74 -0
  163. package/dist/routes/tenant-credentials.d.ts.map +1 -0
  164. package/dist/routes/tenant-credentials.js +156 -0
  165. package/dist/routes/tenant-credentials.js.map +1 -0
  166. package/dist/routes/work-items.d.ts +40 -0
  167. package/dist/routes/work-items.d.ts.map +1 -0
  168. package/dist/routes/work-items.js +695 -0
  169. package/dist/routes/work-items.js.map +1 -0
  170. package/dist/rules/binding-context.d.ts +5 -0
  171. package/dist/rules/binding-context.d.ts.map +1 -0
  172. package/dist/rules/binding-context.js +28 -0
  173. package/dist/rules/binding-context.js.map +1 -0
  174. package/dist/rules/defaults.d.ts +9 -0
  175. package/dist/rules/defaults.d.ts.map +1 -0
  176. package/dist/rules/defaults.js +260 -0
  177. package/dist/rules/defaults.js.map +1 -0
  178. package/dist/rules/dispatcher.d.ts +42 -0
  179. package/dist/rules/dispatcher.d.ts.map +1 -0
  180. package/dist/rules/dispatcher.js +518 -0
  181. package/dist/rules/dispatcher.js.map +1 -0
  182. package/dist/rules/index.d.ts +7 -0
  183. package/dist/rules/index.d.ts.map +1 -0
  184. package/dist/rules/index.js +5 -0
  185. package/dist/rules/processor.d.ts +54 -0
  186. package/dist/rules/processor.d.ts.map +1 -0
  187. package/dist/rules/processor.js +378 -0
  188. package/dist/rules/processor.js.map +1 -0
  189. package/dist/rules/resolve.d.ts +17 -0
  190. package/dist/rules/resolve.d.ts.map +1 -0
  191. package/dist/rules/resolve.js +30 -0
  192. package/dist/rules/resolve.js.map +1 -0
  193. package/dist/rules/start-coordinator.d.ts +60 -0
  194. package/dist/rules/start-coordinator.d.ts.map +1 -0
  195. package/dist/rules/start-coordinator.js +194 -0
  196. package/dist/rules/start-coordinator.js.map +1 -0
  197. package/dist/rules/tools.d.ts +10 -0
  198. package/dist/rules/tools.d.ts.map +1 -0
  199. package/dist/rules/tools.js +58 -0
  200. package/dist/rules/tools.js.map +1 -0
  201. package/dist/rules/transition-service.d.ts +49 -0
  202. package/dist/rules/transition-service.d.ts.map +1 -0
  203. package/dist/rules/transition-service.js +232 -0
  204. package/dist/rules/transition-service.js.map +1 -0
  205. package/dist/rules/types.d.ts +234 -0
  206. package/dist/rules/types.d.ts.map +1 -0
  207. package/dist/rules/types.js +38 -0
  208. package/dist/rules/types.js.map +1 -0
  209. package/dist/rules/validation.d.ts +11 -0
  210. package/dist/rules/validation.d.ts.map +1 -0
  211. package/dist/rules/validation.js +308 -0
  212. package/dist/rules/validation.js.map +1 -0
  213. package/dist/sandbox/fleet.d.ts +222 -0
  214. package/dist/sandbox/fleet.d.ts.map +1 -0
  215. package/dist/sandbox/fleet.js +322 -0
  216. package/dist/sandbox/fleet.js.map +1 -0
  217. package/dist/sandbox/reattach.d.ts +3 -0
  218. package/dist/sandbox/reattach.d.ts.map +1 -0
  219. package/dist/sandbox/reattach.js +16 -0
  220. package/dist/sandbox/reattach.js.map +1 -0
  221. package/dist/server-error.d.ts +12 -0
  222. package/dist/server-error.d.ts.map +1 -0
  223. package/dist/server-error.js +16 -0
  224. package/dist/server-error.js.map +1 -0
  225. package/dist/skills/service.d.ts +48 -0
  226. package/dist/skills/service.d.ts.map +1 -0
  227. package/dist/skills/service.js +40 -0
  228. package/dist/skills/service.js.map +1 -0
  229. package/dist/spa-static.d.ts +33 -0
  230. package/dist/spa-static.d.ts.map +1 -0
  231. package/dist/spa-static.js +104 -0
  232. package/dist/spa-static.js.map +1 -0
  233. package/dist/state-signing.d.ts +59 -0
  234. package/dist/state-signing.d.ts.map +1 -0
  235. package/dist/state-signing.js +83 -0
  236. package/dist/state-signing.js.map +1 -0
  237. package/dist/storage/domains/audit/agent-audit.d.ts +27 -0
  238. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -0
  239. package/dist/storage/domains/audit/agent-audit.js +66 -0
  240. package/dist/storage/domains/audit/agent-audit.js.map +1 -0
  241. package/dist/storage/domains/audit/base.d.ts +140 -0
  242. package/dist/storage/domains/audit/base.d.ts.map +1 -0
  243. package/dist/storage/domains/audit/base.js +184 -0
  244. package/dist/storage/domains/audit/base.js.map +1 -0
  245. package/dist/storage/domains/audit/domain.d.ts +69 -0
  246. package/dist/storage/domains/audit/domain.d.ts.map +1 -0
  247. package/dist/storage/domains/audit/domain.js +167 -0
  248. package/dist/storage/domains/audit/domain.js.map +1 -0
  249. package/dist/storage/domains/channel-identity/base.d.ts +96 -0
  250. package/dist/storage/domains/channel-identity/base.d.ts.map +1 -0
  251. package/dist/storage/domains/channel-identity/base.js +149 -0
  252. package/dist/storage/domains/channel-identity/base.js.map +1 -0
  253. package/dist/storage/domains/credentials/base.d.ts +134 -0
  254. package/dist/storage/domains/credentials/base.d.ts.map +1 -0
  255. package/dist/storage/domains/credentials/base.js +273 -0
  256. package/dist/storage/domains/credentials/base.js.map +1 -0
  257. package/dist/storage/domains/custom-providers/base.d.ts +57 -0
  258. package/dist/storage/domains/custom-providers/base.d.ts.map +1 -0
  259. package/dist/storage/domains/custom-providers/base.js +145 -0
  260. package/dist/storage/domains/custom-providers/base.js.map +1 -0
  261. package/dist/storage/domains/intake/base.d.ts +38 -0
  262. package/dist/storage/domains/intake/base.d.ts.map +1 -0
  263. package/dist/storage/domains/intake/base.js +80 -0
  264. package/dist/storage/domains/intake/base.js.map +1 -0
  265. package/dist/storage/domains/integrations/base.d.ts +126 -0
  266. package/dist/storage/domains/integrations/base.d.ts.map +1 -0
  267. package/dist/storage/domains/integrations/base.js +317 -0
  268. package/dist/storage/domains/integrations/base.js.map +1 -0
  269. package/dist/storage/domains/memory-settings/base.d.ts +62 -0
  270. package/dist/storage/domains/memory-settings/base.d.ts.map +1 -0
  271. package/dist/storage/domains/memory-settings/base.js +126 -0
  272. package/dist/storage/domains/memory-settings/base.js.map +1 -0
  273. package/dist/storage/domains/model-packs/base.d.ts +49 -0
  274. package/dist/storage/domains/model-packs/base.d.ts.map +1 -0
  275. package/dist/storage/domains/model-packs/base.js +96 -0
  276. package/dist/storage/domains/model-packs/base.js.map +1 -0
  277. package/dist/storage/domains/projects/base.d.ts +58 -0
  278. package/dist/storage/domains/projects/base.d.ts.map +1 -0
  279. package/dist/storage/domains/projects/base.js +111 -0
  280. package/dist/storage/domains/projects/base.js.map +1 -0
  281. package/dist/storage/domains/queue-health/base.d.ts +52 -0
  282. package/dist/storage/domains/queue-health/base.d.ts.map +1 -0
  283. package/dist/storage/domains/queue-health/base.js +122 -0
  284. package/dist/storage/domains/queue-health/base.js.map +1 -0
  285. package/dist/storage/domains/source-control/base.d.ts +358 -0
  286. package/dist/storage/domains/source-control/base.d.ts.map +1 -0
  287. package/dist/storage/domains/source-control/base.js +908 -0
  288. package/dist/storage/domains/source-control/base.js.map +1 -0
  289. package/dist/storage/domains/source-control/inmemory.d.ts +175 -0
  290. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -0
  291. package/dist/storage/domains/source-control/inmemory.js +377 -0
  292. package/dist/storage/domains/source-control/inmemory.js.map +1 -0
  293. package/dist/storage/domains/work-items/base.d.ts +412 -0
  294. package/dist/storage/domains/work-items/base.d.ts.map +1 -0
  295. package/dist/storage/domains/work-items/base.js +1309 -0
  296. package/dist/storage/domains/work-items/base.js.map +1 -0
  297. package/dist/storage/domains/work-items/metrics.d.ts +101 -0
  298. package/dist/storage/domains/work-items/metrics.d.ts.map +1 -0
  299. package/dist/storage/domains/work-items/metrics.js +221 -0
  300. package/dist/storage/domains/work-items/metrics.js.map +1 -0
  301. package/dist/timing.d.ts +15 -0
  302. package/dist/timing.d.ts.map +1 -0
  303. package/dist/timing.js +34 -0
  304. package/dist/timing.js.map +1 -0
  305. package/dist/workspace.d.ts +24 -0
  306. package/dist/workspace.d.ts.map +1 -0
  307. package/dist/workspace.js +286 -0
  308. package/dist/workspace.js.map +1 -0
  309. package/factory-skills/configure-factory-rules/SKILL.md +67 -0
  310. package/factory-skills/factory-plan/SKILL.md +61 -0
  311. package/factory-skills/factory-review/SKILL.md +155 -0
  312. package/factory-skills/factory-triage/SKILL.md +77 -0
  313. package/package.json +81 -0
@@ -0,0 +1,695 @@
1
+ import { FACTORY_RULE_BOARDS, FACTORY_RULE_STAGES } from "../rules/types.js";
2
+ import { Route } from "./route.js";
3
+ import { FactoryStartTransitionError } from "../rules/start-coordinator.js";
4
+ import { thresholdsOrDefault } from "../storage/domains/queue-health/base.js";
5
+ import { WorkItemRelationError } from "../storage/domains/work-items/base.js";
6
+ import { computeFactoryMetrics, parseMetricsRange } from "../storage/domains/work-items/metrics.js";
7
+ import { registerApiRoute } from "@mastra/core/server";
8
+ //#region src/routes/work-items.ts
9
+ function loose(c) {
10
+ return c;
11
+ }
12
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13
+ const MAX_STAGES = 16;
14
+ const MAX_STAGE_LENGTH = 64;
15
+ const MAX_METADATA_BYTES = 16 * 1024;
16
+ function isRecord(value) {
17
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18
+ }
19
+ function validStages(value) {
20
+ return Array.isArray(value) && value.length > 0 && value.length <= MAX_STAGES && value.every((stage) => typeof stage === "string" && stage.length <= MAX_STAGE_LENGTH && /^[a-z0-9][a-z0-9_-]*$/i.test(stage)) && new Set(value).size === value.length;
21
+ }
22
+ function validMetadata(value) {
23
+ if (value === null) return true;
24
+ if (!isRecord(value)) return false;
25
+ try {
26
+ return JSON.stringify(value).length <= MAX_METADATA_BYTES;
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+ function parseExternalSource(value) {
32
+ if (value === void 0 || value === null) return value;
33
+ if (!isRecord(value)) return void 0;
34
+ const { integrationId, type, externalId, url } = value;
35
+ if (typeof integrationId !== "string" || integrationId.length === 0 || integrationId.length > 128) return void 0;
36
+ if (typeof type !== "string" || type.length === 0 || type.length > 128) return void 0;
37
+ if (typeof externalId !== "string" || externalId.length === 0 || externalId.length > 512) return void 0;
38
+ if (url !== void 0 && (typeof url !== "string" || url.length > 2048)) return void 0;
39
+ return {
40
+ integrationId,
41
+ type,
42
+ externalId,
43
+ ...url !== void 0 ? { url } : {}
44
+ };
45
+ }
46
+ function parseParentWorkItemId(value) {
47
+ if (value === null) return null;
48
+ if (typeof value !== "string" || !UUID_RE.test(value)) return void 0;
49
+ return value;
50
+ }
51
+ function parseSessions(value) {
52
+ if (!isRecord(value)) return void 0;
53
+ const out = {};
54
+ for (const [role, session] of Object.entries(value)) {
55
+ if (!role || role.length > 64 || !isRecord(session)) return void 0;
56
+ const { sessionId, branch, threadId } = session;
57
+ if (typeof sessionId !== "string" || sessionId.length === 0 || sessionId.length > 512) return void 0;
58
+ if (typeof branch !== "string" || branch.length === 0 || branch.length > 512) return void 0;
59
+ if (typeof threadId !== "string" || threadId.length === 0 || threadId.length > 512) return void 0;
60
+ out[role] = {
61
+ sessionId,
62
+ branch,
63
+ threadId
64
+ };
65
+ }
66
+ return out;
67
+ }
68
+ /** Validate an untrusted create body. Unknown keys are dropped. */
69
+ function parseCreateWorkItem(body) {
70
+ if (!isRecord(body)) return null;
71
+ const { externalSource, title, stages, sessions, metadata } = body;
72
+ if (typeof title !== "string" || title.trim().length === 0 || title.length > 500) return null;
73
+ const hasParentWorkItemId = "parentWorkItemId" in body;
74
+ const parentWorkItemId = hasParentWorkItemId ? parseParentWorkItemId(body.parentWorkItemId) : void 0;
75
+ if (hasParentWorkItemId && parentWorkItemId === void 0) return null;
76
+ const parsedSource = parseExternalSource(externalSource);
77
+ if (externalSource !== void 0 && parsedSource === void 0) return null;
78
+ if (stages !== void 0 && !validStages(stages)) return null;
79
+ const parsedSessions = sessions === void 0 ? void 0 : parseSessions(sessions);
80
+ if (sessions !== void 0 && parsedSessions === void 0) return null;
81
+ if (metadata !== void 0 && !validMetadata(metadata)) return null;
82
+ return {
83
+ title: title.trim(),
84
+ ...parsedSource !== void 0 ? { externalSource: parsedSource } : {},
85
+ ...hasParentWorkItemId ? { parentWorkItemId: parentWorkItemId ?? null } : {},
86
+ ...stages !== void 0 ? { stages } : {},
87
+ ...parsedSessions !== void 0 ? { sessions: parsedSessions } : {},
88
+ ...metadata !== void 0 ? { metadata } : {}
89
+ };
90
+ }
91
+ /** Validate an untrusted patch body. Unknown keys are dropped. */
92
+ function parseUpdateWorkItem(body) {
93
+ if (!isRecord(body)) return null;
94
+ const { title, stages, sessions, metadata } = body;
95
+ const hasParentWorkItemId = "parentWorkItemId" in body;
96
+ if (title === void 0 && stages === void 0 && sessions === void 0 && metadata === void 0 && !hasParentWorkItemId) return null;
97
+ const parentWorkItemId = hasParentWorkItemId ? parseParentWorkItemId(body.parentWorkItemId) : void 0;
98
+ if (hasParentWorkItemId && parentWorkItemId === void 0) return null;
99
+ if (title !== void 0 && (typeof title !== "string" || title.trim().length === 0 || title.length > 500)) return null;
100
+ if (stages !== void 0 && !validStages(stages)) return null;
101
+ const parsedSessions = sessions === void 0 ? void 0 : parseSessions(sessions);
102
+ if (sessions !== void 0 && parsedSessions === void 0) return null;
103
+ if (metadata !== void 0 && !validMetadata(metadata)) return null;
104
+ return {
105
+ ...hasParentWorkItemId ? { parentWorkItemId: parentWorkItemId ?? null } : {},
106
+ ...title !== void 0 ? { title: title.trim() } : {},
107
+ ...stages !== void 0 ? { stages } : {},
108
+ ...parsedSessions !== void 0 ? { sessions: parsedSessions } : {},
109
+ ...metadata !== void 0 ? { metadata } : {}
110
+ };
111
+ }
112
+ async function readJson(c) {
113
+ try {
114
+ return await c.req.json();
115
+ } catch {
116
+ return;
117
+ }
118
+ }
119
+ /** Fields a PATCH touched, for the bounded `updated` event summary. */
120
+ function patchedFields(patch) {
121
+ return Object.keys(patch).filter((key) => patch[key] !== void 0);
122
+ }
123
+ function boundedText(value, max) {
124
+ if (typeof value !== "string") return void 0;
125
+ const normalized = value.trim();
126
+ return normalized.length > 0 && normalized.length <= max ? normalized : void 0;
127
+ }
128
+ function parseTransitionBody(body) {
129
+ if (!isRecord(body)) return null;
130
+ const board = FACTORY_RULE_BOARDS.includes(body.board) ? body.board : void 0;
131
+ const stage = FACTORY_RULE_STAGES.includes(body.stage) ? body.stage : void 0;
132
+ const requestId = boundedText(body.requestId, 256);
133
+ const cause = boundedText(body.cause, 256);
134
+ if (!board || !stage || !requestId || !UUID_RE.test(requestId) || !cause || !Number.isInteger(body.expectedRevision) || Number(body.expectedRevision) < 1) return null;
135
+ return {
136
+ board,
137
+ stage,
138
+ expectedRevision: Number(body.expectedRevision),
139
+ ingress: {
140
+ type: "human",
141
+ identity: requestId
142
+ },
143
+ cause
144
+ };
145
+ }
146
+ function parseInvocation(value) {
147
+ if (value === void 0) return void 0;
148
+ if (!isRecord(value)) return null;
149
+ if (value.type === "prompt") {
150
+ const prompt = boundedText(value.prompt, 16384);
151
+ return prompt ? {
152
+ type: "prompt",
153
+ prompt
154
+ } : null;
155
+ }
156
+ if (value.type === "skill") {
157
+ const skillName = boundedText(value.skillName, 64);
158
+ const args = typeof value.arguments === "string" && value.arguments.length <= 16384 ? value.arguments : void 0;
159
+ return skillName && args !== void 0 ? {
160
+ type: "skill",
161
+ skillName,
162
+ arguments: args
163
+ } : null;
164
+ }
165
+ return null;
166
+ }
167
+ function parseStartBody(body, tenant, factoryProjectId) {
168
+ if (!isRecord(body) || !isRecord(body.workItem)) return null;
169
+ const input = parseCreateWorkItem(body.workItem.input);
170
+ const sessionId = boundedText(body.sessionId, 256);
171
+ const threadTitle = boundedText(body.threadTitle, 512);
172
+ const kickoffKey = boundedText(body.kickoffKey, 256);
173
+ const invocation = parseInvocation(body.invocation);
174
+ const destinationStage = FACTORY_RULE_STAGES.includes(body.destinationStage) ? body.destinationStage : void 0;
175
+ const role = boundedText(body.workItem.role, 32);
176
+ const id = body.workItem.id === void 0 ? void 0 : boundedText(body.workItem.id, 64);
177
+ if (body.workItem.id !== void 0 && (!id || !UUID_RE.test(id))) return null;
178
+ if (!input || !sessionId || !UUID_RE.test(sessionId) || !threadTitle || !kickoffKey || !UUID_RE.test(kickoffKey) || invocation === null || !destinationStage || !role) return null;
179
+ const threadTags = isRecord(body.threadTags) ? Object.fromEntries(Object.entries(body.threadTags).filter((entry) => boundedText(entry[0], 64) !== void 0 && boundedText(entry[1], 256) !== void 0).map(([key, value]) => [key, value.trim()])) : void 0;
180
+ return {
181
+ ...tenant,
182
+ factoryProjectId,
183
+ sessionId,
184
+ threadTitle,
185
+ threadTags,
186
+ kickoffKey,
187
+ invocation,
188
+ destinationStage,
189
+ workItem: {
190
+ id,
191
+ role,
192
+ input
193
+ }
194
+ };
195
+ }
196
+ const DECISION_STATUSES = /* @__PURE__ */ new Set([
197
+ "pending",
198
+ "leased",
199
+ "retry",
200
+ "succeeded",
201
+ "failed"
202
+ ]);
203
+ const DEFAULT_DECISION_PAGE_SIZE = 25;
204
+ const MAX_DECISION_PAGE_SIZE = 50;
205
+ function parseDecisionStatuses(raw) {
206
+ if (!raw) return void 0;
207
+ const statuses = [...new Set(raw.split(",").map((status) => status.trim()))].filter((status) => DECISION_STATUSES.has(status));
208
+ return statuses.length > 0 ? statuses : void 0;
209
+ }
210
+ function parseDecisionLimit(raw) {
211
+ const parsed = raw ? Number.parseInt(raw, 10) : DEFAULT_DECISION_PAGE_SIZE;
212
+ if (!Number.isFinite(parsed)) return DEFAULT_DECISION_PAGE_SIZE;
213
+ return Math.max(1, Math.min(MAX_DECISION_PAGE_SIZE, parsed));
214
+ }
215
+ function encodeDecisionCursor(decision) {
216
+ return Buffer.from(JSON.stringify([decision.createdAt.toISOString(), decision.id]), "utf8").toString("base64url");
217
+ }
218
+ function parseDecisionCursor(raw) {
219
+ if (!raw) return void 0;
220
+ try {
221
+ const decoded = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
222
+ if (!Array.isArray(decoded) || decoded.length !== 2 || typeof decoded[0] !== "string" || typeof decoded[1] !== "string") return;
223
+ const createdAt = new Date(decoded[0]);
224
+ if (Number.isNaN(createdAt.getTime()) || !UUID_RE.test(decoded[1])) return void 0;
225
+ return {
226
+ createdAt,
227
+ id: decoded[1]
228
+ };
229
+ } catch {
230
+ return;
231
+ }
232
+ }
233
+ function decisionSummary(decision) {
234
+ const type = typeof decision.decision.type === "string" ? decision.decision.type.slice(0, 64) : "unknown";
235
+ return {
236
+ id: decision.id,
237
+ evaluationId: decision.evaluationId,
238
+ workItemId: decision.workItemId,
239
+ type,
240
+ status: decision.status,
241
+ attempts: decision.attempts,
242
+ lastError: decision.lastError?.slice(0, 512) ?? null,
243
+ createdAt: decision.createdAt.toISOString(),
244
+ updatedAt: decision.updatedAt.toISOString(),
245
+ completedAt: decision.completedAt?.toISOString() ?? null
246
+ };
247
+ }
248
+ var WorkItemRoutes = class extends Route {
249
+ /** Resolve the `(orgId, userId)` tenant or a ready-to-return error response. */
250
+ async #resolveTenant(c) {
251
+ await this.deps.auth.ensureUser(c);
252
+ const tenant = this.deps.auth.tenant(c);
253
+ if (!tenant) return { response: c.json({ error: "unauthorized" }, 401) };
254
+ if (!tenant.orgId) return { response: c.json({
255
+ error: "organization_required",
256
+ message: "The Factory board requires an organization."
257
+ }, 403) };
258
+ return {
259
+ orgId: tenant.orgId,
260
+ userId: tenant.userId
261
+ };
262
+ }
263
+ /**
264
+ * Resolve the tenant AND the org-owned project from the `:id` param. Work
265
+ * items hang off a project, so listing/creating requires the project to
266
+ * exist in the caller's org.
267
+ */
268
+ async #resolveProject(c) {
269
+ const tenant = await this.#resolveTenant(c);
270
+ if ("response" in tenant) return tenant;
271
+ const projectId = c.req.param("id");
272
+ if (!projectId || !UUID_RE.test(projectId)) return { response: c.json({ error: "Project not found" }, 404) };
273
+ const { projects } = this.deps;
274
+ await projects.ensureReady();
275
+ const project = await projects.get({
276
+ orgId: tenant.orgId,
277
+ id: projectId
278
+ });
279
+ if (!project) return { response: c.json({ error: "Project not found" }, 404) };
280
+ return {
281
+ ...tenant,
282
+ factoryProjectId: projectId,
283
+ defaultModelId: project.defaultModelId
284
+ };
285
+ }
286
+ /**
287
+ * Emit the audit events a successful work-item PATCH implies: always
288
+ * `updated`, plus `stage_moved` when the stages actually changed and one
289
+ * `run.started` per session role the patch introduced.
290
+ */
291
+ async #auditWorkItemPatch({ context, item, previous, patch }) {
292
+ const { audit } = this.deps;
293
+ const target = {
294
+ type: "work_item",
295
+ id: item.id,
296
+ name: item.title
297
+ };
298
+ await audit.emit({
299
+ context,
300
+ input: {
301
+ action: "factory.work_item.updated",
302
+ factoryProjectId: item.factoryProjectId,
303
+ targets: [target],
304
+ metadata: { fields: patchedFields(patch) }
305
+ }
306
+ });
307
+ if (patch.stages !== void 0 && (previous.stages.length !== item.stages.length || previous.stages.some((s, i) => s !== item.stages[i]))) await audit.emit({
308
+ context,
309
+ input: {
310
+ action: "factory.work_item.stage_moved",
311
+ factoryProjectId: item.factoryProjectId,
312
+ targets: [target],
313
+ metadata: {
314
+ from: previous.stages,
315
+ to: item.stages
316
+ }
317
+ }
318
+ });
319
+ const newRoles = Object.keys(item.sessions).filter((role) => !previous.sessionRoles.includes(role));
320
+ for (const role of newRoles) {
321
+ const session = item.sessions[role];
322
+ await audit.emit({
323
+ context,
324
+ input: {
325
+ action: "factory.run.started",
326
+ factoryProjectId: item.factoryProjectId,
327
+ targets: [target],
328
+ metadata: {
329
+ role,
330
+ branch: session?.branch,
331
+ threadId: session?.threadId,
332
+ sessionId: session?.sessionId
333
+ }
334
+ }
335
+ });
336
+ }
337
+ }
338
+ /** Build the Factory work-item routes as Mastra `apiRoutes`. */
339
+ routes() {
340
+ const { audit, workItems, queueHealth, transitionService, startCoordinator } = this.deps;
341
+ return [
342
+ registerApiRoute("/web/factory/projects/:id/work-items", {
343
+ method: "GET",
344
+ requiresAuth: false,
345
+ handler: async (c) => {
346
+ const resolved = await this.#resolveProject(loose(c));
347
+ if ("response" in resolved) return resolved.response;
348
+ await workItems.ensureReady();
349
+ const items = await workItems.list({
350
+ orgId: resolved.orgId,
351
+ factoryProjectId: resolved.factoryProjectId
352
+ });
353
+ return c.json({ workItems: items });
354
+ }
355
+ }),
356
+ registerApiRoute("/web/factory/projects/:id/metrics", {
357
+ method: "GET",
358
+ requiresAuth: false,
359
+ handler: async (c) => {
360
+ const resolved = await this.#resolveProject(loose(c));
361
+ if ("response" in resolved) return resolved.response;
362
+ const { windowStart, windowEnd } = parseMetricsRange(loose(c).req.query("from"), loose(c).req.query("to"), /* @__PURE__ */ new Date());
363
+ await workItems.ensureReady();
364
+ const items = await workItems.list({
365
+ orgId: resolved.orgId,
366
+ factoryProjectId: resolved.factoryProjectId
367
+ });
368
+ return c.json({ metrics: computeFactoryMetrics(items, {
369
+ windowStart,
370
+ windowEnd
371
+ }) });
372
+ }
373
+ }),
374
+ registerApiRoute("/web/factory/projects/:id/health/thresholds", {
375
+ method: "GET",
376
+ requiresAuth: false,
377
+ handler: async (c) => {
378
+ const resolved = await this.#resolveProject(loose(c));
379
+ if ("response" in resolved) return resolved.response;
380
+ await queueHealth.ensureReady();
381
+ const stored = await queueHealth.getConfig(resolved.orgId, resolved.factoryProjectId);
382
+ return c.json({ thresholds: thresholdsOrDefault(stored) });
383
+ }
384
+ }),
385
+ registerApiRoute("/web/factory/projects/:id/decisions", {
386
+ method: "GET",
387
+ requiresAuth: false,
388
+ handler: async (c) => {
389
+ const context = loose(c);
390
+ const resolved = await this.#resolveProject(context);
391
+ if ("response" in resolved) return resolved.response;
392
+ const cursorRaw = context.req.query("before");
393
+ const before = parseDecisionCursor(cursorRaw);
394
+ if (cursorRaw && !before) return c.json({ error: "invalid_cursor" }, 400);
395
+ await workItems.ensureReady();
396
+ const page = await workItems.listDeferredDecisionPage({
397
+ orgId: resolved.orgId,
398
+ factoryProjectId: resolved.factoryProjectId,
399
+ statuses: parseDecisionStatuses(context.req.query("statuses")),
400
+ before,
401
+ limit: parseDecisionLimit(context.req.query("limit"))
402
+ });
403
+ const last = page.decisions.at(-1);
404
+ return c.json({
405
+ decisions: page.decisions.map(decisionSummary),
406
+ ...page.hasMore && last ? { nextCursor: encodeDecisionCursor(last) } : {}
407
+ });
408
+ }
409
+ }),
410
+ registerApiRoute("/web/factory/projects/:id/decisions/:decisionId/retry", {
411
+ method: "POST",
412
+ requiresAuth: false,
413
+ handler: async (c) => {
414
+ const context = loose(c);
415
+ const resolved = await this.#resolveProject(context);
416
+ if ("response" in resolved) return resolved.response;
417
+ const decisionId = context.req.param("decisionId");
418
+ if (!decisionId || !UUID_RE.test(decisionId)) return c.json({ error: "invalid_decision_id" }, 422);
419
+ await workItems.ensureReady();
420
+ const decision = await workItems.retryDeferredDecision(resolved.orgId, resolved.factoryProjectId, decisionId, /* @__PURE__ */ new Date());
421
+ if (!decision) return c.json({ error: "decision_not_retryable" }, 409);
422
+ return c.json({ decision: decisionSummary(decision) });
423
+ }
424
+ }),
425
+ registerApiRoute("/web/factory/projects/:id/work-items", {
426
+ method: "POST",
427
+ requiresAuth: false,
428
+ handler: async (c) => {
429
+ const resolved = await this.#resolveProject(loose(c));
430
+ if ("response" in resolved) return resolved.response;
431
+ const body = await readJson(loose(c));
432
+ if (body === void 0) return c.json({ error: "Invalid JSON body" }, 400);
433
+ const input = parseCreateWorkItem(body);
434
+ if (!input) return c.json({ error: "invalid_work_item" }, 400);
435
+ if ((input.stages ?? ["intake"]).length !== 1 || (input.stages ?? ["intake"])[0] !== "intake") return c.json({
436
+ error: "governed_transition_required",
437
+ message: "New work items must enter through Factory intake."
438
+ }, 409);
439
+ await workItems.ensureReady();
440
+ try {
441
+ const result = await workItems.upsert({
442
+ orgId: resolved.orgId,
443
+ userId: resolved.userId,
444
+ factoryProjectId: resolved.factoryProjectId,
445
+ input,
446
+ reuseMode: "non-stage"
447
+ });
448
+ let item = result.item;
449
+ if (result.created) {
450
+ if (!transitionService) {
451
+ await workItems.delete({
452
+ orgId: resolved.orgId,
453
+ id: item.id
454
+ });
455
+ return c.json({ error: "factory_transitions_unavailable" }, 503);
456
+ }
457
+ const entered = await transitionService.transition({
458
+ orgId: resolved.orgId,
459
+ factoryProjectId: resolved.factoryProjectId,
460
+ workItemId: item.id,
461
+ board: item.externalSource?.type === "pull-request" ? "review" : "work",
462
+ stage: "intake",
463
+ expectedRevision: item.revision,
464
+ actor: {
465
+ type: "human",
466
+ id: resolved.userId
467
+ },
468
+ ingress: {
469
+ type: "human",
470
+ identity: `work-item:${item.id}:initial-entry`
471
+ },
472
+ cause: "work_item_created",
473
+ initialEntry: true
474
+ });
475
+ if (entered.status === "rejected") {
476
+ await workItems.delete({
477
+ orgId: resolved.orgId,
478
+ id: item.id
479
+ });
480
+ return c.json({
481
+ status: "rejected",
482
+ code: entered.code,
483
+ reason: entered.reason
484
+ }, 422);
485
+ }
486
+ item = await workItems.getForProject(resolved.orgId, resolved.factoryProjectId, item.id) ?? item;
487
+ await audit.emit({
488
+ context: loose(c),
489
+ input: {
490
+ action: "factory.work_item.created",
491
+ factoryProjectId: resolved.factoryProjectId,
492
+ targets: [{
493
+ type: "work_item",
494
+ id: item.id,
495
+ name: item.title
496
+ }],
497
+ metadata: {
498
+ externalSource: item.externalSource,
499
+ stages: item.stages
500
+ }
501
+ }
502
+ });
503
+ } else {
504
+ const { stages: _stages, sessions: _sessions, ...boundedPatch } = input;
505
+ await this.#auditWorkItemPatch({
506
+ context: loose(c),
507
+ item,
508
+ previous: result.previous,
509
+ patch: boundedPatch
510
+ });
511
+ }
512
+ return c.json({ workItem: item });
513
+ } catch (error) {
514
+ if (error instanceof WorkItemRelationError) return c.json({
515
+ error: error.code,
516
+ message: error.message
517
+ }, 400);
518
+ throw error;
519
+ }
520
+ }
521
+ }),
522
+ registerApiRoute("/web/factory/projects/:id/work-items/:workItemId/transition", {
523
+ method: "POST",
524
+ requiresAuth: false,
525
+ handler: async (c) => {
526
+ const resolved = await this.#resolveProject(loose(c));
527
+ if ("response" in resolved) return resolved.response;
528
+ const workItemId = loose(c).req.param("workItemId");
529
+ if (!workItemId || !UUID_RE.test(workItemId)) return c.json({ error: "Work item not found" }, 404);
530
+ const parsed = parseTransitionBody(await readJson(loose(c)));
531
+ if (!parsed) return c.json({ error: "invalid_transition_request" }, 400);
532
+ if (!transitionService) return c.json({ error: "factory_transition_unavailable" }, 503);
533
+ await workItems.ensureReady();
534
+ const result = await transitionService.transition({
535
+ ...parsed,
536
+ orgId: resolved.orgId,
537
+ factoryProjectId: resolved.factoryProjectId,
538
+ workItemId,
539
+ actor: {
540
+ type: "human",
541
+ id: resolved.userId
542
+ },
543
+ ingress: {
544
+ ...parsed.ingress,
545
+ identity: `human:${resolved.userId}:${parsed.ingress.identity}`
546
+ }
547
+ });
548
+ await audit.emit({
549
+ context: loose(c),
550
+ input: {
551
+ action: result.status === "accepted" ? "factory.work_item.stage_moved" : "factory.work_item.transition_rejected",
552
+ factoryProjectId: resolved.factoryProjectId,
553
+ targets: [{
554
+ type: "work_item",
555
+ id: workItemId
556
+ }],
557
+ metadata: {
558
+ transitionId: result.transitionId,
559
+ ingressType: parsed.ingress.type,
560
+ ruleSetVersion: transitionService.ruleSetVersion,
561
+ ...result.status === "accepted" ? {
562
+ to: result.stage,
563
+ revision: result.revision
564
+ } : {
565
+ code: result.code,
566
+ reason: result.reason
567
+ }
568
+ }
569
+ }
570
+ });
571
+ if (result.status === "accepted") return c.json({ result });
572
+ return c.json({ result }, result.code === "stale" ? 409 : 422);
573
+ }
574
+ }),
575
+ registerApiRoute("/web/factory/projects/:id/runs/start", {
576
+ method: "POST",
577
+ requiresAuth: false,
578
+ handler: async (c) => {
579
+ const resolved = await this.#resolveProject(loose(c));
580
+ if ("response" in resolved) return resolved.response;
581
+ if (!startCoordinator) return c.json({ error: "factory_start_unavailable" }, 503);
582
+ const input = parseStartBody(await readJson(loose(c)), resolved, resolved.factoryProjectId);
583
+ if (!input) return c.json({ error: "invalid_factory_start" }, 400);
584
+ input.requestContext = loose(c).get("requestContext");
585
+ input.defaultModelId = resolved.defaultModelId ?? void 0;
586
+ if (!input.workItem.id && ((input.workItem.input.stages ?? ["intake"]).length !== 1 || (input.workItem.input.stages ?? ["intake"])[0] !== "intake")) return c.json({
587
+ error: "governed_transition_required",
588
+ message: "Create the work item in Intake before starting it."
589
+ }, 409);
590
+ await workItems.ensureReady();
591
+ let prepared;
592
+ try {
593
+ prepared = await startCoordinator.prepare(input);
594
+ } catch (error) {
595
+ if (error instanceof FactoryStartTransitionError) return c.json({ result: error.result }, error.result.code === "stale" ? 409 : 422);
596
+ throw error;
597
+ }
598
+ await audit.emit({
599
+ context: loose(c),
600
+ input: {
601
+ action: "factory.run.started",
602
+ factoryProjectId: resolved.factoryProjectId,
603
+ targets: [{
604
+ type: "work_item",
605
+ id: prepared.workItemId
606
+ }],
607
+ metadata: {
608
+ role: input.workItem.role,
609
+ branch: prepared.branch,
610
+ threadId: prepared.threadId,
611
+ sessionId: prepared.sessionId,
612
+ bindingId: prepared.bindingId
613
+ }
614
+ }
615
+ });
616
+ return c.json({ prepared }, 202);
617
+ }
618
+ }),
619
+ registerApiRoute("/web/factory/work-items/:id", {
620
+ method: "PATCH",
621
+ requiresAuth: false,
622
+ handler: async (c) => {
623
+ const tenant = await this.#resolveTenant(loose(c));
624
+ if ("response" in tenant) return tenant.response;
625
+ const id = loose(c).req.param("id");
626
+ if (!id || !UUID_RE.test(id)) return c.json({ error: "Work item not found" }, 404);
627
+ const body = await readJson(loose(c));
628
+ if (body === void 0) return c.json({ error: "Invalid JSON body" }, 400);
629
+ const patch = parseUpdateWorkItem(body);
630
+ if (!patch) return c.json({ error: "invalid_work_item_patch" }, 400);
631
+ if (patch.stages !== void 0) return c.json({
632
+ error: "governed_transition_required",
633
+ message: "Use the Factory transition endpoint to move stages."
634
+ }, 409);
635
+ await workItems.ensureReady();
636
+ try {
637
+ const updated = await workItems.update({
638
+ orgId: tenant.orgId,
639
+ id,
640
+ userId: tenant.userId,
641
+ patch
642
+ });
643
+ if (!updated) return c.json({ error: "Work item not found" }, 404);
644
+ await this.#auditWorkItemPatch({
645
+ context: loose(c),
646
+ item: updated.item,
647
+ previous: updated.previous,
648
+ patch
649
+ });
650
+ return c.json({ workItem: updated.item });
651
+ } catch (error) {
652
+ if (error instanceof WorkItemRelationError) return c.json({
653
+ error: error.code,
654
+ message: error.message
655
+ }, 400);
656
+ throw error;
657
+ }
658
+ }
659
+ }),
660
+ registerApiRoute("/web/factory/work-items/:id", {
661
+ method: "DELETE",
662
+ requiresAuth: false,
663
+ handler: async (c) => {
664
+ const tenant = await this.#resolveTenant(loose(c));
665
+ if ("response" in tenant) return tenant.response;
666
+ const id = loose(c).req.param("id");
667
+ if (!id || !UUID_RE.test(id)) return c.json({ error: "Work item not found" }, 404);
668
+ await workItems.ensureReady();
669
+ const deleted = await workItems.delete({
670
+ orgId: tenant.orgId,
671
+ id
672
+ });
673
+ if (!deleted) return c.json({ error: "Work item not found" }, 404);
674
+ await audit.emit({
675
+ context: loose(c),
676
+ input: {
677
+ action: "factory.work_item.deleted",
678
+ factoryProjectId: deleted.factoryProjectId,
679
+ targets: [{
680
+ type: "work_item",
681
+ id: deleted.id,
682
+ name: deleted.title
683
+ }]
684
+ }
685
+ });
686
+ return c.json({ ok: true });
687
+ }
688
+ })
689
+ ];
690
+ }
691
+ };
692
+ //#endregion
693
+ export { WorkItemRoutes, parseCreateWorkItem, parseUpdateWorkItem };
694
+
695
+ //# sourceMappingURL=work-items.js.map