@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.
- package/dist/boards/review.d.ts.map +1 -1
- package/dist/boards/review.js +12 -4
- package/dist/boards/review.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +6 -3
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +21 -2
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/routes/attention.d.ts.map +1 -1
- package/dist/routes/attention.js +31 -82
- package/dist/routes/attention.js.map +1 -1
- package/dist/routes/contracts.d.ts +601 -0
- package/dist/routes/contracts.d.ts.map +1 -0
- package/dist/routes/contracts.js +450 -0
- package/dist/routes/contracts.js.map +1 -0
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +26 -76
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/supervisor.d.ts.map +1 -1
- package/dist/routes/supervisor.js +5 -4
- package/dist/routes/supervisor.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +86 -229
- package/dist/routes/work-items.js.map +1 -1
- package/dist/work-item-branch.d.ts +2 -0
- package/dist/work-item-branch.d.ts.map +1 -1
- package/dist/work-item-branch.js +6 -1
- package/dist/work-item-branch.js.map +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +3 -1
- package/dist/workspace.js.map +1 -1
- package/package.json +8 -7
|
@@ -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
|
-
|
|
95
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
187
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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
|
|
308
|
-
if (!
|
|
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
|
-
|
|
378
|
-
|
|
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
|
|
385
|
-
|
|
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(
|
|
417
|
-
method:
|
|
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(
|
|
434
|
-
method:
|
|
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
|
|
269
|
+
const context = loose(c);
|
|
270
|
+
const resolved = await this.#resolveProject(context);
|
|
438
271
|
if ("response" in resolved) return resolved.response;
|
|
439
|
-
const
|
|
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(
|
|
452
|
-
method:
|
|
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(
|
|
463
|
-
method:
|
|
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
|
|
470
|
-
|
|
471
|
-
|
|
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:
|
|
477
|
-
before,
|
|
478
|
-
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(
|
|
506
|
-
method:
|
|
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
|
|
513
|
-
|
|
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(
|
|
523
|
-
method:
|
|
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(
|
|
631
|
-
method:
|
|
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
|
|
637
|
-
|
|
638
|
-
|
|
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(
|
|
684
|
-
method:
|
|
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(
|
|
724
|
-
method:
|
|
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
|
|
730
|
-
|
|
731
|
-
|
|
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(
|
|
781
|
-
method:
|
|
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
|
|
787
|
-
|
|
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,
|