@mastra/factory 0.13.0-alpha.13 → 0.13.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import { createBoardRegistry } from "../boards/registry.js";
2
2
  import "../boards/index.js";
3
3
  import { FACTORY_PULL_REQUEST_RECONCILIATION_KEY, FACTORY_RULE_MATERIALIZATION_KEY, WorkItemRelationError, WorkItemUpdateConflictError } from "../storage/domains/work-items/base.js";
4
+ import { FACTORY_ROUTE_CONTRACTS } from "./contracts.js";
4
5
  import { Route } from "./route.js";
5
6
  import { factoryDispatchFailureMetadata } from "../rules/dispatch-errors.js";
6
7
  import { FactoryStartTransitionError } from "../rules/start-coordinator.js";
@@ -28,124 +29,20 @@ function runningSessionIds(items, liveSessions) {
28
29
  function loose(c) {
29
30
  return c;
30
31
  }
31
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
32
- const MAX_STAGES = 16;
33
- const MAX_STAGE_LENGTH = 64;
34
- const MAX_METADATA_BYTES = 16 * 1024;
35
- function isRecord(value) {
36
- return typeof value === "object" && value !== null && !Array.isArray(value);
37
- }
38
- function validStages(value) {
39
- 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;
40
- }
41
- function validMetadata(value) {
42
- if (value === null) return true;
43
- if (!isRecord(value)) return false;
44
- try {
45
- return JSON.stringify(value).length <= MAX_METADATA_BYTES;
46
- } catch {
47
- return false;
48
- }
49
- }
50
32
  function publicWorkItemMetadata(value) {
51
33
  if (value === null) return null;
52
34
  const { [FACTORY_RULE_MATERIALIZATION_KEY]: _materialization, [FACTORY_PULL_REQUEST_RECONCILIATION_KEY]: _reconciliation, ...metadata } = value;
53
35
  return metadata;
54
36
  }
55
- function parseExternalSource(value) {
56
- if (value === void 0 || value === null) return value;
57
- if (!isRecord(value)) return void 0;
58
- const { integrationId, type, externalId, url } = value;
59
- if (typeof integrationId !== "string" || integrationId.length === 0 || integrationId.length > 128) return void 0;
60
- if (typeof type !== "string" || type.length === 0 || type.length > 128) return void 0;
61
- if (typeof externalId !== "string" || externalId.length === 0 || externalId.length > 512) return void 0;
62
- if (url !== void 0 && (typeof url !== "string" || url.length > 2048)) return void 0;
63
- return {
64
- integrationId,
65
- type,
66
- externalId,
67
- ...url !== void 0 ? { url } : {}
68
- };
69
- }
70
- function parseParentWorkItemId(value) {
71
- if (value === null) return null;
72
- if (typeof value !== "string" || !UUID_RE.test(value)) return void 0;
73
- return value;
74
- }
75
- function parseSessions(value) {
76
- if (!isRecord(value)) return void 0;
77
- const out = {};
78
- for (const [role, session] of Object.entries(value)) {
79
- if (!role || role.length > 64 || !isRecord(session)) return void 0;
80
- const { sessionId, branch, threadId } = session;
81
- if (typeof sessionId !== "string" || sessionId.length === 0 || sessionId.length > 512) return void 0;
82
- if (typeof branch !== "string" || branch.length === 0 || branch.length > 512) return void 0;
83
- if (typeof threadId !== "string" || threadId.length === 0 || threadId.length > 512) return void 0;
84
- out[role] = {
85
- sessionId,
86
- branch,
87
- threadId
88
- };
89
- }
90
- return out;
91
- }
92
37
  /** Validate an untrusted create body. Unknown keys are dropped. */
93
38
  function parseCreateWorkItem(body) {
94
- if (!isRecord(body)) return null;
95
- const { board, externalSource, title, stages, sessions, metadata } = body;
96
- if (typeof title !== "string" || title.trim().length === 0 || title.length > 500) return null;
97
- if (board !== void 0 && (typeof board !== "string" || board.length === 0 || board.length > 128)) return null;
98
- const hasParentWorkItemId = "parentWorkItemId" in body;
99
- const parentWorkItemId = hasParentWorkItemId ? parseParentWorkItemId(body.parentWorkItemId) : void 0;
100
- if (hasParentWorkItemId && parentWorkItemId === void 0) return null;
101
- const parsedSource = parseExternalSource(externalSource);
102
- if (externalSource !== void 0 && parsedSource === void 0) return null;
103
- if (stages !== void 0 && !validStages(stages)) return null;
104
- const parsedSessions = sessions === void 0 ? void 0 : parseSessions(sessions);
105
- if (sessions !== void 0 && parsedSessions === void 0) return null;
106
- let parsedMetadata;
107
- if (metadata !== void 0) {
108
- if (!validMetadata(metadata)) return null;
109
- parsedMetadata = publicWorkItemMetadata(metadata);
110
- }
111
- return {
112
- title: title.trim(),
113
- ...board !== void 0 ? { board } : {},
114
- ...parsedSource !== void 0 ? { externalSource: parsedSource } : {},
115
- ...hasParentWorkItemId ? { parentWorkItemId: parentWorkItemId ?? null } : {},
116
- ...stages !== void 0 ? { stages } : {},
117
- ...parsedSessions !== void 0 ? { sessions: parsedSessions } : {},
118
- ...parsedMetadata !== void 0 ? { metadata: parsedMetadata } : {}
119
- };
39
+ const parsed = FACTORY_ROUTE_CONTRACTS.workItemCreate.bodySchema.safeParse(body);
40
+ return parsed.success ? parsed.data : null;
120
41
  }
121
42
  /** Validate an untrusted patch body. Unknown keys are dropped. */
122
43
  function parseUpdateWorkItem(body) {
123
- if (!isRecord(body)) return null;
124
- const { board, title, stages, sessions, metadata, plansPreapproved } = body;
125
- const hasParentWorkItemId = "parentWorkItemId" in body;
126
- if (board === void 0 && title === void 0 && stages === void 0 && sessions === void 0 && metadata === void 0 && plansPreapproved === void 0 && !hasParentWorkItemId) return null;
127
- if (board !== void 0 && (typeof board !== "string" || board.length === 0 || board.length > 128)) return null;
128
- if (plansPreapproved !== void 0 && plansPreapproved !== true) return null;
129
- const parentWorkItemId = hasParentWorkItemId ? parseParentWorkItemId(body.parentWorkItemId) : void 0;
130
- if (hasParentWorkItemId && parentWorkItemId === void 0) return null;
131
- if (title !== void 0 && (typeof title !== "string" || title.trim().length === 0 || title.length > 500)) return null;
132
- if (stages !== void 0 && !validStages(stages)) return null;
133
- const parsedSessions = sessions === void 0 ? void 0 : parseSessions(sessions);
134
- if (sessions !== void 0 && parsedSessions === void 0) return null;
135
- let parsedMetadata;
136
- if (metadata !== void 0) {
137
- if (!validMetadata(metadata)) return null;
138
- parsedMetadata = publicWorkItemMetadata(metadata);
139
- }
140
- return {
141
- ...board !== void 0 ? { board } : {},
142
- ...hasParentWorkItemId ? { parentWorkItemId: parentWorkItemId ?? null } : {},
143
- ...title !== void 0 ? { title: title.trim() } : {},
144
- ...stages !== void 0 ? { stages } : {},
145
- ...parsedSessions !== void 0 ? { sessions: parsedSessions } : {},
146
- ...parsedMetadata !== void 0 ? { metadata: parsedMetadata } : {},
147
- ...plansPreapproved === true ? { plansPreapproved: true } : {}
148
- };
44
+ const parsed = FACTORY_ROUTE_CONTRACTS.workItemUpdate.bodySchema.safeParse(body);
45
+ return parsed.success ? parsed.data : null;
149
46
  }
150
47
  async function readJson(c) {
151
48
  try {
@@ -158,92 +55,21 @@ async function readJson(c) {
158
55
  function patchedFields(patch) {
159
56
  return Object.keys(patch).filter((key) => patch[key] !== void 0);
160
57
  }
161
- function boundedText(value, max) {
162
- if (typeof value !== "string") return void 0;
163
- const normalized = value.trim();
164
- return normalized.length > 0 && normalized.length <= max ? normalized : void 0;
165
- }
166
58
  function parseTransitionBody(body) {
167
- if (!isRecord(body)) return null;
168
- const board = typeof body.board === "string" && body.board.length > 0 ? body.board : void 0;
169
- const stage = typeof body.stage === "string" && body.stage.length > 0 ? body.stage : void 0;
170
- const requestId = boundedText(body.requestId, 256);
171
- const cause = boundedText(body.cause, 256);
172
- if (!board || !stage || !requestId || !UUID_RE.test(requestId) || !cause || !Number.isInteger(body.expectedRevision) || Number(body.expectedRevision) < 1) return null;
173
- return {
174
- board,
175
- stage,
176
- expectedRevision: Number(body.expectedRevision),
177
- ingress: {
178
- type: "human",
179
- identity: requestId
180
- },
181
- cause,
182
- ...body.reenter === true ? { reenter: true } : {}
183
- };
59
+ const parsed = FACTORY_ROUTE_CONTRACTS.workItemTransition.bodySchema.safeParse(body);
60
+ return parsed.success ? parsed.data : null;
184
61
  }
185
62
  function parseStartBody(body, tenant, factoryProjectId) {
186
- if (!isRecord(body) || !isRecord(body.workItem)) return null;
187
- const input = parseCreateWorkItem(body.workItem.input);
188
- const sessionId = boundedText(body.sessionId, 256);
189
- const threadTitle = boundedText(body.threadTitle, 512);
190
- const kickoffKey = boundedText(body.kickoffKey, 256);
191
- const role = boundedText(body.workItem.role, 32);
192
- const id = boundedText(body.workItem.id, 64);
193
- if (!input || !sessionId || !UUID_RE.test(sessionId) || !threadTitle || !kickoffKey || !UUID_RE.test(kickoffKey) || !id || !UUID_RE.test(id) || !role) return null;
194
- return {
63
+ const parsed = FACTORY_ROUTE_CONTRACTS.workItemStart.bodySchema.safeParse(body);
64
+ return parsed.success ? {
195
65
  ...tenant,
196
66
  factoryProjectId,
197
- sessionId,
198
- threadTitle,
199
- kickoffKey,
200
- workItem: {
201
- id,
202
- role,
203
- input
204
- }
205
- };
206
- }
207
- const DECISION_STATUSES = /* @__PURE__ */ new Set([
208
- "pending",
209
- "proposed",
210
- "dismissed",
211
- "superseded",
212
- "leased",
213
- "retry",
214
- "succeeded",
215
- "failed"
216
- ]);
217
- const DEFAULT_DECISION_PAGE_SIZE = 25;
218
- const MAX_DECISION_PAGE_SIZE = 50;
219
- function parseDecisionStatuses(raw) {
220
- if (!raw) return void 0;
221
- const statuses = [...new Set(raw.split(",").map((status) => status.trim()))].filter((status) => DECISION_STATUSES.has(status));
222
- return statuses.length > 0 ? statuses : void 0;
223
- }
224
- function parseDecisionLimit(raw) {
225
- const parsed = raw ? Number.parseInt(raw, 10) : DEFAULT_DECISION_PAGE_SIZE;
226
- if (!Number.isFinite(parsed)) return DEFAULT_DECISION_PAGE_SIZE;
227
- return Math.max(1, Math.min(MAX_DECISION_PAGE_SIZE, parsed));
67
+ ...parsed.data
68
+ } : null;
228
69
  }
229
70
  function encodeDecisionCursor(decision) {
230
71
  return Buffer.from(JSON.stringify([decision.createdAt.toISOString(), decision.id]), "utf8").toString("base64url");
231
72
  }
232
- function parseDecisionCursor(raw) {
233
- if (!raw) return void 0;
234
- try {
235
- const decoded = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
236
- if (!Array.isArray(decoded) || decoded.length !== 2 || typeof decoded[0] !== "string" || typeof decoded[1] !== "string") return;
237
- const createdAt = new Date(decoded[0]);
238
- if (Number.isNaN(createdAt.getTime()) || !UUID_RE.test(decoded[1])) return void 0;
239
- return {
240
- createdAt,
241
- id: decoded[1]
242
- };
243
- } catch {
244
- return;
245
- }
246
- }
247
73
  /** A proposed transition names the seat its lane addresses, so the card can label what approving starts. */
248
74
  function summaryRole(boards, decision) {
249
75
  if (typeof decision.role === "string") return decision.role.slice(0, 32);
@@ -304,8 +130,9 @@ var WorkItemRoutes = class extends Route {
304
130
  async #resolveProject(c) {
305
131
  const tenant = await this.#resolveTenant(c);
306
132
  if ("response" in tenant) return tenant;
307
- const projectId = c.req.param("id");
308
- if (!projectId || !UUID_RE.test(projectId)) return { response: c.json({ error: "Project not found" }, 404) };
133
+ const parsedPath = FACTORY_ROUTE_CONTRACTS.projectGet.pathSchema.safeParse({ id: c.req.param("id") });
134
+ if (!parsedPath.success) return { response: c.json({ error: "Project not found" }, 404) };
135
+ const projectId = parsedPath.data.id;
309
136
  const { projects } = this.deps;
310
137
  await projects.ensureReady();
311
138
  const project = await projects.get({
@@ -374,15 +201,20 @@ var WorkItemRoutes = class extends Route {
374
201
  /** Releasing a parked run and dropping it: same request, opposite outcomes, both audited as consent. */
375
202
  #proposalRoute({ verb, settle }) {
376
203
  const { audit, workItems } = this.deps;
377
- return registerApiRoute(`/web/factory/projects/:id/decisions/:decisionId/${verb}`, {
378
- method: "POST",
204
+ const contract = verb === "approve" ? FACTORY_ROUTE_CONTRACTS.decisionApprove : FACTORY_ROUTE_CONTRACTS.decisionDismiss;
205
+ return registerApiRoute(contract.path, {
206
+ method: contract.method,
379
207
  requiresAuth: false,
380
208
  handler: async (c) => {
381
209
  const context = loose(c);
382
210
  const resolved = await this.#resolveProject(context);
383
211
  if ("response" in resolved) return resolved.response;
384
- const decisionId = context.req.param("decisionId");
385
- if (!decisionId || !UUID_RE.test(decisionId)) return c.json({ error: "invalid_decision_id" }, 422);
212
+ const parsedPath = contract.pathSchema.safeParse({
213
+ id: resolved.factoryProjectId,
214
+ decisionId: context.req.param("decisionId")
215
+ });
216
+ if (!parsedPath.success) return c.json({ error: "invalid_decision_id" }, 422);
217
+ const { decisionId } = parsedPath.data;
386
218
  await workItems.ensureReady();
387
219
  const now = /* @__PURE__ */ new Date();
388
220
  const decision = await settle(resolved.orgId, resolved.factoryProjectId, decisionId, now, resolved.userId);
@@ -413,8 +245,8 @@ var WorkItemRoutes = class extends Route {
413
245
  routes() {
414
246
  const { audit, workItems, queueHealth, transitionService, startCoordinator, liveSessions } = this.deps;
415
247
  return [
416
- registerApiRoute("/web/factory/projects/:id/work-items", {
417
- method: "GET",
248
+ registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemList.path, {
249
+ method: FACTORY_ROUTE_CONTRACTS.workItemList.method,
418
250
  requiresAuth: false,
419
251
  handler: async (c) => {
420
252
  const resolved = await this.#resolveProject(loose(c));
@@ -430,13 +262,19 @@ var WorkItemRoutes = class extends Route {
430
262
  });
431
263
  }
432
264
  }),
433
- registerApiRoute("/web/factory/projects/:id/metrics", {
434
- method: "GET",
265
+ registerApiRoute(FACTORY_ROUTE_CONTRACTS.metricsGet.path, {
266
+ method: FACTORY_ROUTE_CONTRACTS.metricsGet.method,
435
267
  requiresAuth: false,
436
268
  handler: async (c) => {
437
- const resolved = await this.#resolveProject(loose(c));
269
+ const context = loose(c);
270
+ const resolved = await this.#resolveProject(context);
438
271
  if ("response" in resolved) return resolved.response;
439
- const { windowStart, windowEnd } = parseMetricsRange(loose(c).req.query("from"), loose(c).req.query("to"), /* @__PURE__ */ new Date());
272
+ const query = FACTORY_ROUTE_CONTRACTS.metricsGet.querySchema.safeParse({
273
+ from: context.req.query("from"),
274
+ to: context.req.query("to")
275
+ });
276
+ if (!query.success) return c.json({ error: "invalid_metrics_range" }, 400);
277
+ const { windowStart, windowEnd } = parseMetricsRange(query.data.from, query.data.to, /* @__PURE__ */ new Date());
440
278
  await workItems.ensureReady();
441
279
  const items = await workItems.list({
442
280
  orgId: resolved.orgId,
@@ -448,8 +286,8 @@ var WorkItemRoutes = class extends Route {
448
286
  }) });
449
287
  }
450
288
  }),
451
- registerApiRoute("/web/factory/projects/:id/health/thresholds", {
452
- method: "GET",
289
+ registerApiRoute(FACTORY_ROUTE_CONTRACTS.healthThresholdsGet.path, {
290
+ method: FACTORY_ROUTE_CONTRACTS.healthThresholdsGet.method,
453
291
  requiresAuth: false,
454
292
  handler: async (c) => {
455
293
  const resolved = await this.#resolveProject(loose(c));
@@ -459,23 +297,29 @@ var WorkItemRoutes = class extends Route {
459
297
  return c.json({ thresholds: thresholdsOrDefault(stored) });
460
298
  }
461
299
  }),
462
- registerApiRoute("/web/factory/projects/:id/decisions", {
463
- method: "GET",
300
+ registerApiRoute(FACTORY_ROUTE_CONTRACTS.decisionList.path, {
301
+ method: FACTORY_ROUTE_CONTRACTS.decisionList.method,
464
302
  requiresAuth: false,
465
303
  handler: async (c) => {
466
304
  const context = loose(c);
467
305
  const resolved = await this.#resolveProject(context);
468
306
  if ("response" in resolved) return resolved.response;
469
- const cursorRaw = context.req.query("before");
470
- const before = parseDecisionCursor(cursorRaw);
471
- if (cursorRaw && !before) return c.json({ error: "invalid_cursor" }, 400);
307
+ const query = FACTORY_ROUTE_CONTRACTS.decisionList.querySchema.safeParse({
308
+ statuses: context.req.query("statuses"),
309
+ before: context.req.query("before"),
310
+ limit: context.req.query("limit")
311
+ });
312
+ if (!query.success) {
313
+ const field = query.error.issues[0]?.path[0];
314
+ return c.json({ error: field === "before" ? "invalid_cursor" : "invalid_decision_query" }, 400);
315
+ }
472
316
  await workItems.ensureReady();
473
317
  const page = await workItems.listDeferredDecisionPage({
474
318
  orgId: resolved.orgId,
475
319
  factoryProjectId: resolved.factoryProjectId,
476
- statuses: parseDecisionStatuses(context.req.query("statuses")),
477
- before,
478
- limit: parseDecisionLimit(context.req.query("limit"))
320
+ statuses: query.data.statuses,
321
+ before: query.data.before,
322
+ limit: query.data.limit
479
323
  });
480
324
  const last = page.decisions.at(-1);
481
325
  return c.json({
@@ -502,15 +346,19 @@ var WorkItemRoutes = class extends Route {
502
346
  verb: "dismiss",
503
347
  settle: workItems.dismissDeferredDecision.bind(workItems)
504
348
  }),
505
- registerApiRoute("/web/factory/projects/:id/decisions/:decisionId/retry", {
506
- method: "POST",
349
+ registerApiRoute(FACTORY_ROUTE_CONTRACTS.decisionRetry.path, {
350
+ method: FACTORY_ROUTE_CONTRACTS.decisionRetry.method,
507
351
  requiresAuth: false,
508
352
  handler: async (c) => {
509
353
  const context = loose(c);
510
354
  const resolved = await this.#resolveProject(context);
511
355
  if ("response" in resolved) return resolved.response;
512
- const decisionId = context.req.param("decisionId");
513
- if (!decisionId || !UUID_RE.test(decisionId)) return c.json({ error: "invalid_decision_id" }, 422);
356
+ const parsedPath = FACTORY_ROUTE_CONTRACTS.decisionRetry.pathSchema.safeParse({
357
+ id: resolved.factoryProjectId,
358
+ decisionId: context.req.param("decisionId")
359
+ });
360
+ if (!parsedPath.success) return c.json({ error: "invalid_decision_id" }, 422);
361
+ const { decisionId } = parsedPath.data;
514
362
  await workItems.ensureReady();
515
363
  const current = await workItems.getDeferredDecision(resolved.orgId, resolved.factoryProjectId, decisionId);
516
364
  if (!current || current.status !== "failed" || !factoryDispatchFailureMetadata(current.failureCode).canRetry) return c.json({ error: "decision_not_retryable" }, 409);
@@ -519,8 +367,8 @@ var WorkItemRoutes = class extends Route {
519
367
  return c.json({ decision: decisionSummary(this.#boards, decision) });
520
368
  }
521
369
  }),
522
- registerApiRoute("/web/factory/projects/:id/work-items", {
523
- method: "POST",
370
+ registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemCreate.path, {
371
+ method: FACTORY_ROUTE_CONTRACTS.workItemCreate.method,
524
372
  requiresAuth: false,
525
373
  handler: async (c) => {
526
374
  const resolved = await this.#resolveProject(loose(c));
@@ -627,15 +475,20 @@ var WorkItemRoutes = class extends Route {
627
475
  }
628
476
  }
629
477
  }),
630
- registerApiRoute("/web/factory/projects/:id/work-items/:workItemId/transition", {
631
- method: "POST",
478
+ registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemTransition.path, {
479
+ method: FACTORY_ROUTE_CONTRACTS.workItemTransition.method,
632
480
  requiresAuth: false,
633
481
  handler: async (c) => {
634
482
  const resolved = await this.#resolveProject(loose(c));
635
483
  if ("response" in resolved) return resolved.response;
636
- const workItemId = loose(c).req.param("workItemId");
637
- if (!workItemId || !UUID_RE.test(workItemId)) return c.json({ error: "Work item not found" }, 404);
638
- const parsed = parseTransitionBody(await readJson(loose(c)));
484
+ const context = loose(c);
485
+ const parsedPath = FACTORY_ROUTE_CONTRACTS.workItemTransition.pathSchema.safeParse({
486
+ id: resolved.factoryProjectId,
487
+ workItemId: context.req.param("workItemId")
488
+ });
489
+ if (!parsedPath.success) return c.json({ error: "Work item not found" }, 404);
490
+ const { workItemId } = parsedPath.data;
491
+ const parsed = parseTransitionBody(await readJson(context));
639
492
  if (!parsed) return c.json({ error: "invalid_transition_request" }, 400);
640
493
  if (!transitionService) return c.json({ error: "factory_transition_unavailable" }, 503);
641
494
  await workItems.ensureReady();
@@ -680,8 +533,8 @@ var WorkItemRoutes = class extends Route {
680
533
  return c.json({ result }, result.code === "stale" ? 409 : 422);
681
534
  }
682
535
  }),
683
- registerApiRoute("/web/factory/projects/:id/runs/start", {
684
- method: "POST",
536
+ registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemStart.path, {
537
+ method: FACTORY_ROUTE_CONTRACTS.workItemStart.method,
685
538
  requiresAuth: false,
686
539
  handler: async (c) => {
687
540
  const resolved = await this.#resolveProject(loose(c));
@@ -720,15 +573,17 @@ var WorkItemRoutes = class extends Route {
720
573
  return c.json({ prepared }, 202);
721
574
  }
722
575
  }),
723
- registerApiRoute("/web/factory/work-items/:id", {
724
- method: "PATCH",
576
+ registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemUpdate.path, {
577
+ method: FACTORY_ROUTE_CONTRACTS.workItemUpdate.method,
725
578
  requiresAuth: false,
726
579
  handler: async (c) => {
727
580
  const tenant = await this.#resolveTenant(loose(c));
728
581
  if ("response" in tenant) return tenant.response;
729
- const id = loose(c).req.param("id");
730
- if (!id || !UUID_RE.test(id)) return c.json({ error: "Work item not found" }, 404);
731
- const body = await readJson(loose(c));
582
+ const context = loose(c);
583
+ const parsedPath = FACTORY_ROUTE_CONTRACTS.workItemUpdate.pathSchema.safeParse({ id: context.req.param("id") });
584
+ if (!parsedPath.success) return c.json({ error: "Work item not found" }, 404);
585
+ const { id } = parsedPath.data;
586
+ const body = await readJson(context);
732
587
  if (body === void 0) return c.json({ error: "Invalid JSON body" }, 400);
733
588
  const patch = parseUpdateWorkItem(body);
734
589
  if (!patch) return c.json({ error: "invalid_work_item_patch" }, 400);
@@ -777,14 +632,16 @@ var WorkItemRoutes = class extends Route {
777
632
  }
778
633
  }
779
634
  }),
780
- registerApiRoute("/web/factory/work-items/:id", {
781
- method: "DELETE",
635
+ registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemDelete.path, {
636
+ method: FACTORY_ROUTE_CONTRACTS.workItemDelete.method,
782
637
  requiresAuth: false,
783
638
  handler: async (c) => {
784
639
  const tenant = await this.#resolveTenant(loose(c));
785
640
  if ("response" in tenant) return tenant.response;
786
- const id = loose(c).req.param("id");
787
- if (!id || !UUID_RE.test(id)) return c.json({ error: "Work item not found" }, 404);
641
+ const context = loose(c);
642
+ const parsedPath = FACTORY_ROUTE_CONTRACTS.workItemDelete.pathSchema.safeParse({ id: context.req.param("id") });
643
+ if (!parsedPath.success) return c.json({ error: "Work item not found" }, 404);
644
+ const { id } = parsedPath.data;
788
645
  await workItems.ensureReady();
789
646
  const deleted = await workItems.delete({
790
647
  orgId: tenant.orgId,