@mastra/factory 0.13.0-alpha.8 → 0.13.0
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/README.md +232 -10
- package/dist/auth.d.ts +2 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +29 -4
- package/dist/auth.js.map +1 -1
- package/dist/boards/define-board.d.ts +36 -2
- package/dist/boards/define-board.d.ts.map +1 -1
- package/dist/boards/define-board.js +58 -1
- package/dist/boards/define-board.js.map +1 -1
- package/dist/boards/index.d.ts +4 -2
- package/dist/boards/index.d.ts.map +1 -1
- package/dist/boards/index.js +2 -1
- package/dist/boards/index.js.map +1 -1
- package/dist/boards/relocate.d.ts +17 -0
- package/dist/boards/relocate.d.ts.map +1 -0
- package/dist/boards/relocate.js +53 -0
- package/dist/boards/relocate.js.map +1 -0
- package/dist/boards/review.d.ts +1 -1
- package/dist/boards/review.d.ts.map +1 -1
- package/dist/boards/review.js +17 -4
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/semantics.d.ts +28 -0
- package/dist/boards/semantics.d.ts.map +1 -0
- package/dist/boards/semantics.js +41 -0
- package/dist/boards/semantics.js.map +1 -0
- package/dist/boards/work-tool-rules.d.ts +8 -0
- package/dist/boards/work-tool-rules.d.ts.map +1 -0
- package/dist/boards/work-tool-rules.js +20 -0
- package/dist/boards/work-tool-rules.js.map +1 -0
- package/dist/boards/work.d.ts +1 -1
- package/dist/boards/work.d.ts.map +1 -1
- package/dist/boards/work.js +13 -0
- package/dist/boards/work.js.map +1 -1
- package/dist/capabilities/intake.d.ts +2 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/factory.d.ts +5 -6
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +41 -12
- package/dist/factory.js.map +1 -1
- package/dist/integrations/base.d.ts +7 -4
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/default-rules.d.ts +2 -2
- package/dist/integrations/github/default-rules.d.ts.map +1 -1
- package/dist/integrations/github/default-rules.js +2 -2
- package/dist/integrations/github/default-rules.js.map +1 -1
- package/dist/integrations/github/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/github/issue-reconciler.js +4 -3
- package/dist/integrations/github/issue-reconciler.js.map +1 -1
- package/dist/integrations/github/rules.d.ts +6 -2
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +96 -15
- package/dist/integrations/github/rules.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/integrations/issue-reconciler.d.ts +2 -0
- package/dist/integrations/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/issue-reconciler.js +1 -3
- package/dist/integrations/issue-reconciler.js.map +1 -1
- package/dist/integrations/linear/agent-tools.d.ts.map +1 -1
- package/dist/integrations/linear/agent-tools.js.map +1 -1
- package/dist/integrations/linear/default-rules.d.ts +2 -2
- package/dist/integrations/linear/default-rules.d.ts.map +1 -1
- package/dist/integrations/linear/default-rules.js +2 -2
- package/dist/integrations/linear/default-rules.js.map +1 -1
- package/dist/integrations/linear/integration.js +1 -0
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/issue-reconciler.d.ts.map +1 -1
- package/dist/integrations/linear/issue-reconciler.js +9 -4
- package/dist/integrations/linear/issue-reconciler.js.map +1 -1
- package/dist/integrations/linear/reconciliation-config.d.ts.map +1 -1
- package/dist/integrations/linear/reconciliation-config.js.map +1 -1
- package/dist/integrations/linear/routes.d.ts.map +1 -1
- package/dist/integrations/linear/routes.js +19 -26
- package/dist/integrations/linear/routes.js.map +1 -1
- package/dist/integrations/linear/rules.d.ts +7 -2
- package/dist/integrations/linear/rules.d.ts.map +1 -1
- package/dist/integrations/linear/rules.js +20 -8
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/platform/api-client.d.ts.map +1 -1
- package/dist/integrations/platform/api-client.js +25 -4
- package/dist/integrations/platform/api-client.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts +1 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +5 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
- package/dist/integrations/platform/linear/integration.js +5 -2
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/integrations/slack/emoji-shortcodes.generated.d.ts +8 -0
- package/dist/integrations/slack/emoji-shortcodes.generated.d.ts.map +1 -0
- package/dist/integrations/slack/emoji-shortcodes.generated.js +1985 -0
- package/dist/integrations/slack/emoji-shortcodes.generated.js.map +1 -0
- package/dist/integrations/slack/emoji.d.ts +3 -0
- package/dist/integrations/slack/emoji.d.ts.map +1 -0
- package/dist/integrations/slack/emoji.js +12 -0
- package/dist/integrations/slack/emoji.js.map +1 -0
- package/dist/integrations/slack/integration.js +1 -1
- package/dist/integrations/slack/integration.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +16 -2
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/routes/attention-activity.d.ts +2 -3
- package/dist/routes/attention-activity.d.ts.map +1 -1
- package/dist/routes/attention-activity.js +12 -3
- package/dist/routes/attention-activity.js.map +1 -1
- package/dist/routes/attention-parked.d.ts +22 -0
- package/dist/routes/attention-parked.d.ts.map +1 -0
- package/dist/routes/attention-parked.js +136 -0
- package/dist/routes/attention-parked.js.map +1 -0
- package/dist/routes/attention-providers.js +8 -8
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/attention.d.ts +2 -0
- package/dist/routes/attention.d.ts.map +1 -1
- package/dist/routes/attention.js +47 -118
- package/dist/routes/attention.js.map +1 -1
- package/dist/routes/contracts.d.ts +627 -0
- package/dist/routes/contracts.d.ts.map +1 -0
- package/dist/routes/contracts.js +468 -0
- package/dist/routes/contracts.js.map +1 -0
- package/dist/routes/intake.d.ts +18 -0
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +265 -15
- package/dist/routes/intake.js.map +1 -1
- 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 +2 -0
- package/dist/routes/supervisor.d.ts.map +1 -1
- package/dist/routes/supervisor.js +7 -6
- package/dist/routes/supervisor.js.map +1 -1
- package/dist/routes/surface.d.ts +7 -4
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +17 -11
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/telemetry.d.ts +13 -0
- package/dist/routes/telemetry.d.ts.map +1 -0
- package/dist/routes/telemetry.js +60 -0
- package/dist/routes/telemetry.js.map +1 -0
- package/dist/routes/work-items.d.ts +3 -3
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +134 -250
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts +2 -2
- package/dist/rules/dispatch-errors.d.ts.map +1 -1
- package/dist/rules/dispatch-errors.js +10 -6
- package/dist/rules/dispatch-errors.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +9 -2
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +76 -23
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/index.d.ts +4 -5
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +3 -4
- package/dist/rules/processor.d.ts +3 -3
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +24 -31
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/resolve.d.ts +1 -3
- package/dist/rules/resolve.d.ts.map +1 -1
- package/dist/rules/resolve.js +1 -4
- package/dist/rules/resolve.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +3 -1
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +20 -10
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +7 -7
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +21 -25
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +16 -11
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.d.ts +12 -2
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +29 -20
- package/dist/rules/validation.js.map +1 -1
- package/dist/session/live-sessions.d.ts +33 -3
- package/dist/session/live-sessions.d.ts.map +1 -1
- package/dist/session/live-sessions.js +67 -5
- package/dist/session/live-sessions.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +54 -1
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +136 -4
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +22 -7
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +41 -26
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/supervisor/health-worker.d.ts +2 -0
- package/dist/supervisor/health-worker.d.ts.map +1 -1
- package/dist/supervisor/health-worker.js +3 -1
- package/dist/supervisor/health-worker.js.map +1 -1
- package/dist/supervisor/health.d.ts +12 -14
- package/dist/supervisor/health.d.ts.map +1 -1
- package/dist/supervisor/health.js +24 -52
- package/dist/supervisor/health.js.map +1 -1
- package/dist/supervisor/instructions.d.ts +1 -1
- package/dist/supervisor/instructions.d.ts.map +1 -1
- package/dist/supervisor/instructions.js +10 -8
- package/dist/supervisor/instructions.js.map +1 -1
- package/dist/supervisor/read-tools.d.ts +2 -0
- package/dist/supervisor/read-tools.d.ts.map +1 -1
- package/dist/supervisor/read-tools.js +2 -2
- package/dist/supervisor/read-tools.js.map +1 -1
- package/dist/supervisor/write-tools.d.ts.map +1 -1
- package/dist/supervisor/write-tools.js +6 -4
- package/dist/supervisor/write-tools.js.map +1 -1
- package/dist/telemetry-types.d.ts +7 -0
- package/dist/telemetry-types.d.ts.map +1 -0
- package/dist/telemetry-types.js +24 -0
- package/dist/telemetry-types.js.map +1 -0
- package/dist/telemetry.d.ts +9 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +47 -0
- package/dist/telemetry.js.map +1 -0
- 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/factory-skills/configure-factory-rules/SKILL.md +30 -10
- package/package.json +11 -9
- package/dist/rules/defaults.d.ts +0 -9
- package/dist/rules/defaults.d.ts.map +0 -1
- package/dist/rules/defaults.js +0 -50
- package/dist/rules/defaults.js.map +0 -1
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { isFactoryRuleStage } from "../rules/types.js";
|
|
2
1
|
import { createBoardRegistry } from "../boards/registry.js";
|
|
3
2
|
import "../boards/index.js";
|
|
4
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";
|
|
5
5
|
import { Route } from "./route.js";
|
|
6
6
|
import { factoryDispatchFailureMetadata } from "../rules/dispatch-errors.js";
|
|
7
7
|
import { FactoryStartTransitionError } from "../rules/start-coordinator.js";
|
|
8
|
-
import { roleForStage } from "../rules/transition-service.js";
|
|
9
8
|
import { thresholdsOrDefault } from "../storage/domains/queue-health/base.js";
|
|
10
9
|
import { computeFactoryMetrics, parseMetricsRange } from "../storage/domains/work-items/metrics.js";
|
|
11
10
|
import { factoryDecisionType } from "./attention-providers.js";
|
|
@@ -21,133 +20,29 @@ function toWireWorkItem(item) {
|
|
|
21
20
|
metadata: publicWorkItemMetadata(item.metadata) ?? {}
|
|
22
21
|
};
|
|
23
22
|
}
|
|
24
|
-
/** Session ids of the listed cards
|
|
25
|
-
function
|
|
26
|
-
const
|
|
27
|
-
for (const item of items) for (const { sessionId } of Object.values(item.sessions)) if (
|
|
28
|
-
return [...
|
|
23
|
+
/** Session ids of the listed cards that the predicate picks, each once. */
|
|
24
|
+
function sessionIdsWhere(items, predicate) {
|
|
25
|
+
const picked = /* @__PURE__ */ new Set();
|
|
26
|
+
for (const item of items) for (const { sessionId } of Object.values(item.sessions)) if (predicate(sessionId)) picked.add(sessionId);
|
|
27
|
+
return [...picked];
|
|
29
28
|
}
|
|
30
29
|
function loose(c) {
|
|
31
30
|
return c;
|
|
32
31
|
}
|
|
33
|
-
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
34
|
-
const MAX_STAGES = 16;
|
|
35
|
-
const MAX_STAGE_LENGTH = 64;
|
|
36
|
-
const MAX_METADATA_BYTES = 16 * 1024;
|
|
37
|
-
function isRecord(value) {
|
|
38
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39
|
-
}
|
|
40
|
-
function validStages(value) {
|
|
41
|
-
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;
|
|
42
|
-
}
|
|
43
|
-
function validMetadata(value) {
|
|
44
|
-
if (value === null) return true;
|
|
45
|
-
if (!isRecord(value)) return false;
|
|
46
|
-
try {
|
|
47
|
-
return JSON.stringify(value).length <= MAX_METADATA_BYTES;
|
|
48
|
-
} catch {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
32
|
function publicWorkItemMetadata(value) {
|
|
53
33
|
if (value === null) return null;
|
|
54
34
|
const { [FACTORY_RULE_MATERIALIZATION_KEY]: _materialization, [FACTORY_PULL_REQUEST_RECONCILIATION_KEY]: _reconciliation, ...metadata } = value;
|
|
55
35
|
return metadata;
|
|
56
36
|
}
|
|
57
|
-
function parseExternalSource(value) {
|
|
58
|
-
if (value === void 0 || value === null) return value;
|
|
59
|
-
if (!isRecord(value)) return void 0;
|
|
60
|
-
const { integrationId, type, externalId, url } = value;
|
|
61
|
-
if (typeof integrationId !== "string" || integrationId.length === 0 || integrationId.length > 128) return void 0;
|
|
62
|
-
if (typeof type !== "string" || type.length === 0 || type.length > 128) return void 0;
|
|
63
|
-
if (typeof externalId !== "string" || externalId.length === 0 || externalId.length > 512) return void 0;
|
|
64
|
-
if (url !== void 0 && (typeof url !== "string" || url.length > 2048)) return void 0;
|
|
65
|
-
return {
|
|
66
|
-
integrationId,
|
|
67
|
-
type,
|
|
68
|
-
externalId,
|
|
69
|
-
...url !== void 0 ? { url } : {}
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function parseParentWorkItemId(value) {
|
|
73
|
-
if (value === null) return null;
|
|
74
|
-
if (typeof value !== "string" || !UUID_RE.test(value)) return void 0;
|
|
75
|
-
return value;
|
|
76
|
-
}
|
|
77
|
-
function parseSessions(value) {
|
|
78
|
-
if (!isRecord(value)) return void 0;
|
|
79
|
-
const out = {};
|
|
80
|
-
for (const [role, session] of Object.entries(value)) {
|
|
81
|
-
if (!role || role.length > 64 || !isRecord(session)) return void 0;
|
|
82
|
-
const { sessionId, branch, threadId } = session;
|
|
83
|
-
if (typeof sessionId !== "string" || sessionId.length === 0 || sessionId.length > 512) return void 0;
|
|
84
|
-
if (typeof branch !== "string" || branch.length === 0 || branch.length > 512) return void 0;
|
|
85
|
-
if (typeof threadId !== "string" || threadId.length === 0 || threadId.length > 512) return void 0;
|
|
86
|
-
out[role] = {
|
|
87
|
-
sessionId,
|
|
88
|
-
branch,
|
|
89
|
-
threadId
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
return out;
|
|
93
|
-
}
|
|
94
37
|
/** Validate an untrusted create body. Unknown keys are dropped. */
|
|
95
38
|
function parseCreateWorkItem(body) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (typeof title !== "string" || title.trim().length === 0 || title.length > 500) return null;
|
|
99
|
-
if (board !== void 0 && (typeof board !== "string" || board.length === 0 || board.length > 128)) return null;
|
|
100
|
-
const hasParentWorkItemId = "parentWorkItemId" in body;
|
|
101
|
-
const parentWorkItemId = hasParentWorkItemId ? parseParentWorkItemId(body.parentWorkItemId) : void 0;
|
|
102
|
-
if (hasParentWorkItemId && parentWorkItemId === void 0) return null;
|
|
103
|
-
const parsedSource = parseExternalSource(externalSource);
|
|
104
|
-
if (externalSource !== void 0 && parsedSource === void 0) return null;
|
|
105
|
-
if (stages !== void 0 && !validStages(stages)) return null;
|
|
106
|
-
const parsedSessions = sessions === void 0 ? void 0 : parseSessions(sessions);
|
|
107
|
-
if (sessions !== void 0 && parsedSessions === void 0) return null;
|
|
108
|
-
let parsedMetadata;
|
|
109
|
-
if (metadata !== void 0) {
|
|
110
|
-
if (!validMetadata(metadata)) return null;
|
|
111
|
-
parsedMetadata = publicWorkItemMetadata(metadata);
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
title: title.trim(),
|
|
115
|
-
...board !== void 0 ? { board } : {},
|
|
116
|
-
...parsedSource !== void 0 ? { externalSource: parsedSource } : {},
|
|
117
|
-
...hasParentWorkItemId ? { parentWorkItemId: parentWorkItemId ?? null } : {},
|
|
118
|
-
...stages !== void 0 ? { stages } : {},
|
|
119
|
-
...parsedSessions !== void 0 ? { sessions: parsedSessions } : {},
|
|
120
|
-
...parsedMetadata !== void 0 ? { metadata: parsedMetadata } : {}
|
|
121
|
-
};
|
|
39
|
+
const parsed = FACTORY_ROUTE_CONTRACTS.workItemCreate.bodySchema.safeParse(body);
|
|
40
|
+
return parsed.success ? parsed.data : null;
|
|
122
41
|
}
|
|
123
42
|
/** Validate an untrusted patch body. Unknown keys are dropped. */
|
|
124
43
|
function parseUpdateWorkItem(body) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const hasParentWorkItemId = "parentWorkItemId" in body;
|
|
128
|
-
if (board === void 0 && title === void 0 && stages === void 0 && sessions === void 0 && metadata === void 0 && plansPreapproved === void 0 && !hasParentWorkItemId) return null;
|
|
129
|
-
if (board !== void 0 && (typeof board !== "string" || board.length === 0 || board.length > 128)) return null;
|
|
130
|
-
if (plansPreapproved !== void 0 && plansPreapproved !== true) return null;
|
|
131
|
-
const parentWorkItemId = hasParentWorkItemId ? parseParentWorkItemId(body.parentWorkItemId) : void 0;
|
|
132
|
-
if (hasParentWorkItemId && parentWorkItemId === void 0) return null;
|
|
133
|
-
if (title !== void 0 && (typeof title !== "string" || title.trim().length === 0 || title.length > 500)) return null;
|
|
134
|
-
if (stages !== void 0 && !validStages(stages)) return null;
|
|
135
|
-
const parsedSessions = sessions === void 0 ? void 0 : parseSessions(sessions);
|
|
136
|
-
if (sessions !== void 0 && parsedSessions === void 0) return null;
|
|
137
|
-
let parsedMetadata;
|
|
138
|
-
if (metadata !== void 0) {
|
|
139
|
-
if (!validMetadata(metadata)) return null;
|
|
140
|
-
parsedMetadata = publicWorkItemMetadata(metadata);
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
...board !== void 0 ? { board } : {},
|
|
144
|
-
...hasParentWorkItemId ? { parentWorkItemId: parentWorkItemId ?? null } : {},
|
|
145
|
-
...title !== void 0 ? { title: title.trim() } : {},
|
|
146
|
-
...stages !== void 0 ? { stages } : {},
|
|
147
|
-
...parsedSessions !== void 0 ? { sessions: parsedSessions } : {},
|
|
148
|
-
...parsedMetadata !== void 0 ? { metadata: parsedMetadata } : {},
|
|
149
|
-
...plansPreapproved === true ? { plansPreapproved: true } : {}
|
|
150
|
-
};
|
|
44
|
+
const parsed = FACTORY_ROUTE_CONTRACTS.workItemUpdate.bodySchema.safeParse(body);
|
|
45
|
+
return parsed.success ? parsed.data : null;
|
|
151
46
|
}
|
|
152
47
|
async function readJson(c) {
|
|
153
48
|
try {
|
|
@@ -160,100 +55,28 @@ async function readJson(c) {
|
|
|
160
55
|
function patchedFields(patch) {
|
|
161
56
|
return Object.keys(patch).filter((key) => patch[key] !== void 0);
|
|
162
57
|
}
|
|
163
|
-
function boundedText(value, max) {
|
|
164
|
-
if (typeof value !== "string") return void 0;
|
|
165
|
-
const normalized = value.trim();
|
|
166
|
-
return normalized.length > 0 && normalized.length <= max ? normalized : void 0;
|
|
167
|
-
}
|
|
168
58
|
function parseTransitionBody(body) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const stage = typeof body.stage === "string" && body.stage.length > 0 ? body.stage : void 0;
|
|
172
|
-
const requestId = boundedText(body.requestId, 256);
|
|
173
|
-
const cause = boundedText(body.cause, 256);
|
|
174
|
-
if (!board || !stage || !requestId || !UUID_RE.test(requestId) || !cause || !Number.isInteger(body.expectedRevision) || Number(body.expectedRevision) < 1) return null;
|
|
175
|
-
return {
|
|
176
|
-
board,
|
|
177
|
-
stage,
|
|
178
|
-
expectedRevision: Number(body.expectedRevision),
|
|
179
|
-
ingress: {
|
|
180
|
-
type: "human",
|
|
181
|
-
identity: requestId
|
|
182
|
-
},
|
|
183
|
-
cause,
|
|
184
|
-
...body.reenter === true ? { reenter: true } : {}
|
|
185
|
-
};
|
|
59
|
+
const parsed = FACTORY_ROUTE_CONTRACTS.workItemTransition.bodySchema.safeParse(body);
|
|
60
|
+
return parsed.success ? parsed.data : null;
|
|
186
61
|
}
|
|
187
62
|
function parseStartBody(body, tenant, factoryProjectId) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const sessionId = boundedText(body.sessionId, 256);
|
|
191
|
-
const threadTitle = boundedText(body.threadTitle, 512);
|
|
192
|
-
const kickoffKey = boundedText(body.kickoffKey, 256);
|
|
193
|
-
const role = boundedText(body.workItem.role, 32);
|
|
194
|
-
const id = boundedText(body.workItem.id, 64);
|
|
195
|
-
if (!input || !sessionId || !UUID_RE.test(sessionId) || !threadTitle || !kickoffKey || !UUID_RE.test(kickoffKey) || !id || !UUID_RE.test(id) || !role) return null;
|
|
196
|
-
return {
|
|
63
|
+
const parsed = FACTORY_ROUTE_CONTRACTS.workItemStart.bodySchema.safeParse(body);
|
|
64
|
+
return parsed.success ? {
|
|
197
65
|
...tenant,
|
|
198
66
|
factoryProjectId,
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
kickoffKey,
|
|
202
|
-
workItem: {
|
|
203
|
-
id,
|
|
204
|
-
role,
|
|
205
|
-
input
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
const DECISION_STATUSES = /* @__PURE__ */ new Set([
|
|
210
|
-
"pending",
|
|
211
|
-
"proposed",
|
|
212
|
-
"dismissed",
|
|
213
|
-
"superseded",
|
|
214
|
-
"leased",
|
|
215
|
-
"retry",
|
|
216
|
-
"succeeded",
|
|
217
|
-
"failed"
|
|
218
|
-
]);
|
|
219
|
-
const DEFAULT_DECISION_PAGE_SIZE = 25;
|
|
220
|
-
const MAX_DECISION_PAGE_SIZE = 50;
|
|
221
|
-
function parseDecisionStatuses(raw) {
|
|
222
|
-
if (!raw) return void 0;
|
|
223
|
-
const statuses = [...new Set(raw.split(",").map((status) => status.trim()))].filter((status) => DECISION_STATUSES.has(status));
|
|
224
|
-
return statuses.length > 0 ? statuses : void 0;
|
|
225
|
-
}
|
|
226
|
-
function parseDecisionLimit(raw) {
|
|
227
|
-
const parsed = raw ? Number.parseInt(raw, 10) : DEFAULT_DECISION_PAGE_SIZE;
|
|
228
|
-
if (!Number.isFinite(parsed)) return DEFAULT_DECISION_PAGE_SIZE;
|
|
229
|
-
return Math.max(1, Math.min(MAX_DECISION_PAGE_SIZE, parsed));
|
|
67
|
+
...parsed.data
|
|
68
|
+
} : null;
|
|
230
69
|
}
|
|
231
70
|
function encodeDecisionCursor(decision) {
|
|
232
71
|
return Buffer.from(JSON.stringify([decision.createdAt.toISOString(), decision.id]), "utf8").toString("base64url");
|
|
233
72
|
}
|
|
234
|
-
function parseDecisionCursor(raw) {
|
|
235
|
-
if (!raw) return void 0;
|
|
236
|
-
try {
|
|
237
|
-
const decoded = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
|
|
238
|
-
if (!Array.isArray(decoded) || decoded.length !== 2 || typeof decoded[0] !== "string" || typeof decoded[1] !== "string") return;
|
|
239
|
-
const createdAt = new Date(decoded[0]);
|
|
240
|
-
if (Number.isNaN(createdAt.getTime()) || !UUID_RE.test(decoded[1])) return void 0;
|
|
241
|
-
return {
|
|
242
|
-
createdAt,
|
|
243
|
-
id: decoded[1]
|
|
244
|
-
};
|
|
245
|
-
} catch {
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
73
|
/** A proposed transition names the seat its lane addresses, so the card can label what approving starts. */
|
|
250
|
-
function summaryRole(decision) {
|
|
74
|
+
function summaryRole(boards, decision) {
|
|
251
75
|
if (typeof decision.role === "string") return decision.role.slice(0, 32);
|
|
252
76
|
if (decision.type !== "transition") return null;
|
|
253
|
-
const board = decision
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return roleForStage(board, stage);
|
|
77
|
+
const { board, stage } = decision;
|
|
78
|
+
if (typeof board !== "string" || typeof stage !== "string") return null;
|
|
79
|
+
return boards.get(board)?.roleForPhase(stage) ?? null;
|
|
257
80
|
}
|
|
258
81
|
/** A linked-card decision names where the card is synced from, so the UI can say "GitHub" rather than "a linked card". */
|
|
259
82
|
function summarySource(decision) {
|
|
@@ -261,13 +84,13 @@ function summarySource(decision) {
|
|
|
261
84
|
const source = decision.source;
|
|
262
85
|
return source === "github-issue" || source === "github-pr" || source === "linear-issue" || source === "manual" ? source : null;
|
|
263
86
|
}
|
|
264
|
-
function decisionSummary(decision) {
|
|
87
|
+
function decisionSummary(boards, decision) {
|
|
265
88
|
return {
|
|
266
89
|
id: decision.id,
|
|
267
90
|
evaluationId: decision.evaluationId,
|
|
268
91
|
workItemId: decision.workItemId,
|
|
269
92
|
type: factoryDecisionType(decision),
|
|
270
|
-
role: summaryRole(decision.decision),
|
|
93
|
+
role: summaryRole(boards, decision.decision),
|
|
271
94
|
source: summarySource(decision.decision),
|
|
272
95
|
status: decision.status,
|
|
273
96
|
attempts: decision.attempts,
|
|
@@ -281,6 +104,10 @@ function decisionSummary(decision) {
|
|
|
281
104
|
};
|
|
282
105
|
}
|
|
283
106
|
var WorkItemRoutes = class extends Route {
|
|
107
|
+
#defaultBoards;
|
|
108
|
+
get #boards() {
|
|
109
|
+
return this.deps.boardRegistry ?? (this.#defaultBoards ??= createBoardRegistry());
|
|
110
|
+
}
|
|
284
111
|
/** Resolve the `(orgId, userId)` tenant or a ready-to-return error response. */
|
|
285
112
|
async #resolveTenant(c) {
|
|
286
113
|
await this.deps.auth.ensureUser(c);
|
|
@@ -303,8 +130,9 @@ var WorkItemRoutes = class extends Route {
|
|
|
303
130
|
async #resolveProject(c) {
|
|
304
131
|
const tenant = await this.#resolveTenant(c);
|
|
305
132
|
if ("response" in tenant) return tenant;
|
|
306
|
-
const
|
|
307
|
-
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;
|
|
308
136
|
const { projects } = this.deps;
|
|
309
137
|
await projects.ensureReady();
|
|
310
138
|
const project = await projects.get({
|
|
@@ -373,15 +201,20 @@ var WorkItemRoutes = class extends Route {
|
|
|
373
201
|
/** Releasing a parked run and dropping it: same request, opposite outcomes, both audited as consent. */
|
|
374
202
|
#proposalRoute({ verb, settle }) {
|
|
375
203
|
const { audit, workItems } = this.deps;
|
|
376
|
-
|
|
377
|
-
|
|
204
|
+
const contract = verb === "approve" ? FACTORY_ROUTE_CONTRACTS.decisionApprove : FACTORY_ROUTE_CONTRACTS.decisionDismiss;
|
|
205
|
+
return registerApiRoute(contract.path, {
|
|
206
|
+
method: contract.method,
|
|
378
207
|
requiresAuth: false,
|
|
379
208
|
handler: async (c) => {
|
|
380
209
|
const context = loose(c);
|
|
381
210
|
const resolved = await this.#resolveProject(context);
|
|
382
211
|
if ("response" in resolved) return resolved.response;
|
|
383
|
-
const
|
|
384
|
-
|
|
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;
|
|
385
218
|
await workItems.ensureReady();
|
|
386
219
|
const now = /* @__PURE__ */ new Date();
|
|
387
220
|
const decision = await settle(resolved.orgId, resolved.factoryProjectId, decisionId, now, resolved.userId);
|
|
@@ -404,7 +237,7 @@ var WorkItemRoutes = class extends Route {
|
|
|
404
237
|
}
|
|
405
238
|
}
|
|
406
239
|
});
|
|
407
|
-
return c.json({ decision: decisionSummary(decision) });
|
|
240
|
+
return c.json({ decision: decisionSummary(this.#boards, decision) });
|
|
408
241
|
}
|
|
409
242
|
});
|
|
410
243
|
}
|
|
@@ -412,8 +245,31 @@ var WorkItemRoutes = class extends Route {
|
|
|
412
245
|
routes() {
|
|
413
246
|
const { audit, workItems, queueHealth, transitionService, startCoordinator, liveSessions } = this.deps;
|
|
414
247
|
return [
|
|
415
|
-
registerApiRoute(
|
|
416
|
-
method:
|
|
248
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.boardCatalog.path, {
|
|
249
|
+
method: FACTORY_ROUTE_CONTRACTS.boardCatalog.method,
|
|
250
|
+
requiresAuth: false,
|
|
251
|
+
handler: async (c) => {
|
|
252
|
+
const resolved = await this.#resolveProject(loose(c));
|
|
253
|
+
if ("response" in resolved) return resolved.response;
|
|
254
|
+
return c.json({ boards: [...this.#boards].map(([id, board]) => ({
|
|
255
|
+
id,
|
|
256
|
+
title: board.title,
|
|
257
|
+
initialPhase: board.initialPhase,
|
|
258
|
+
phases: Object.entries(board.phases).map(([phaseId, phase]) => ({
|
|
259
|
+
id: phaseId,
|
|
260
|
+
title: phase.title,
|
|
261
|
+
kind: phase.kind,
|
|
262
|
+
...phase.kind === "working" ? { role: phase.role } : {},
|
|
263
|
+
transitions: (board.transitions[phaseId] ?? []).map(({ outcome, to }) => ({
|
|
264
|
+
outcome,
|
|
265
|
+
to
|
|
266
|
+
}))
|
|
267
|
+
}))
|
|
268
|
+
})) });
|
|
269
|
+
}
|
|
270
|
+
}),
|
|
271
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemList.path, {
|
|
272
|
+
method: FACTORY_ROUTE_CONTRACTS.workItemList.method,
|
|
417
273
|
requiresAuth: false,
|
|
418
274
|
handler: async (c) => {
|
|
419
275
|
const resolved = await this.#resolveProject(loose(c));
|
|
@@ -425,17 +281,24 @@ var WorkItemRoutes = class extends Route {
|
|
|
425
281
|
});
|
|
426
282
|
return c.json({
|
|
427
283
|
workItems: items.map(toWireWorkItem),
|
|
428
|
-
runningSessionIds:
|
|
284
|
+
runningSessionIds: sessionIdsWhere(items, (sessionId) => liveSessions.isRunning(sessionId)),
|
|
285
|
+
parkedSessionIds: sessionIdsWhere(items, (sessionId) => liveSessions.parked(sessionId) !== void 0)
|
|
429
286
|
});
|
|
430
287
|
}
|
|
431
288
|
}),
|
|
432
|
-
registerApiRoute(
|
|
433
|
-
method:
|
|
289
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.metricsGet.path, {
|
|
290
|
+
method: FACTORY_ROUTE_CONTRACTS.metricsGet.method,
|
|
434
291
|
requiresAuth: false,
|
|
435
292
|
handler: async (c) => {
|
|
436
|
-
const
|
|
293
|
+
const context = loose(c);
|
|
294
|
+
const resolved = await this.#resolveProject(context);
|
|
437
295
|
if ("response" in resolved) return resolved.response;
|
|
438
|
-
const
|
|
296
|
+
const query = FACTORY_ROUTE_CONTRACTS.metricsGet.querySchema.safeParse({
|
|
297
|
+
from: context.req.query("from"),
|
|
298
|
+
to: context.req.query("to")
|
|
299
|
+
});
|
|
300
|
+
if (!query.success) return c.json({ error: "invalid_metrics_range" }, 400);
|
|
301
|
+
const { windowStart, windowEnd } = parseMetricsRange(query.data.from, query.data.to, /* @__PURE__ */ new Date());
|
|
439
302
|
await workItems.ensureReady();
|
|
440
303
|
const items = await workItems.list({
|
|
441
304
|
orgId: resolved.orgId,
|
|
@@ -447,8 +310,8 @@ var WorkItemRoutes = class extends Route {
|
|
|
447
310
|
}) });
|
|
448
311
|
}
|
|
449
312
|
}),
|
|
450
|
-
registerApiRoute(
|
|
451
|
-
method:
|
|
313
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.healthThresholdsGet.path, {
|
|
314
|
+
method: FACTORY_ROUTE_CONTRACTS.healthThresholdsGet.method,
|
|
452
315
|
requiresAuth: false,
|
|
453
316
|
handler: async (c) => {
|
|
454
317
|
const resolved = await this.#resolveProject(loose(c));
|
|
@@ -458,27 +321,33 @@ var WorkItemRoutes = class extends Route {
|
|
|
458
321
|
return c.json({ thresholds: thresholdsOrDefault(stored) });
|
|
459
322
|
}
|
|
460
323
|
}),
|
|
461
|
-
registerApiRoute(
|
|
462
|
-
method:
|
|
324
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.decisionList.path, {
|
|
325
|
+
method: FACTORY_ROUTE_CONTRACTS.decisionList.method,
|
|
463
326
|
requiresAuth: false,
|
|
464
327
|
handler: async (c) => {
|
|
465
328
|
const context = loose(c);
|
|
466
329
|
const resolved = await this.#resolveProject(context);
|
|
467
330
|
if ("response" in resolved) return resolved.response;
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
331
|
+
const query = FACTORY_ROUTE_CONTRACTS.decisionList.querySchema.safeParse({
|
|
332
|
+
statuses: context.req.query("statuses"),
|
|
333
|
+
before: context.req.query("before"),
|
|
334
|
+
limit: context.req.query("limit")
|
|
335
|
+
});
|
|
336
|
+
if (!query.success) {
|
|
337
|
+
const field = query.error.issues[0]?.path[0];
|
|
338
|
+
return c.json({ error: field === "before" ? "invalid_cursor" : "invalid_decision_query" }, 400);
|
|
339
|
+
}
|
|
471
340
|
await workItems.ensureReady();
|
|
472
341
|
const page = await workItems.listDeferredDecisionPage({
|
|
473
342
|
orgId: resolved.orgId,
|
|
474
343
|
factoryProjectId: resolved.factoryProjectId,
|
|
475
|
-
statuses:
|
|
476
|
-
before,
|
|
477
|
-
limit:
|
|
344
|
+
statuses: query.data.statuses,
|
|
345
|
+
before: query.data.before,
|
|
346
|
+
limit: query.data.limit
|
|
478
347
|
});
|
|
479
348
|
const last = page.decisions.at(-1);
|
|
480
349
|
return c.json({
|
|
481
|
-
decisions: page.decisions.map(decisionSummary),
|
|
350
|
+
decisions: page.decisions.map((decision) => decisionSummary(this.#boards, decision)),
|
|
482
351
|
...page.hasMore && last ? { nextCursor: encodeDecisionCursor(last) } : {}
|
|
483
352
|
});
|
|
484
353
|
}
|
|
@@ -486,10 +355,12 @@ var WorkItemRoutes = class extends Route {
|
|
|
486
355
|
...buildAttentionRoutes({
|
|
487
356
|
workItems,
|
|
488
357
|
comments: this.deps.comments,
|
|
358
|
+
liveSessions,
|
|
489
359
|
resolveProject: (context) => this.#resolveProject(loose(context))
|
|
490
360
|
}),
|
|
491
361
|
...buildSupervisorRoutes({
|
|
492
362
|
workItems,
|
|
363
|
+
boards: this.#boards,
|
|
493
364
|
resolveProject: (context) => this.#resolveProject(loose(context))
|
|
494
365
|
}),
|
|
495
366
|
this.#proposalRoute({
|
|
@@ -500,25 +371,29 @@ var WorkItemRoutes = class extends Route {
|
|
|
500
371
|
verb: "dismiss",
|
|
501
372
|
settle: workItems.dismissDeferredDecision.bind(workItems)
|
|
502
373
|
}),
|
|
503
|
-
registerApiRoute(
|
|
504
|
-
method:
|
|
374
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.decisionRetry.path, {
|
|
375
|
+
method: FACTORY_ROUTE_CONTRACTS.decisionRetry.method,
|
|
505
376
|
requiresAuth: false,
|
|
506
377
|
handler: async (c) => {
|
|
507
378
|
const context = loose(c);
|
|
508
379
|
const resolved = await this.#resolveProject(context);
|
|
509
380
|
if ("response" in resolved) return resolved.response;
|
|
510
|
-
const
|
|
511
|
-
|
|
381
|
+
const parsedPath = FACTORY_ROUTE_CONTRACTS.decisionRetry.pathSchema.safeParse({
|
|
382
|
+
id: resolved.factoryProjectId,
|
|
383
|
+
decisionId: context.req.param("decisionId")
|
|
384
|
+
});
|
|
385
|
+
if (!parsedPath.success) return c.json({ error: "invalid_decision_id" }, 422);
|
|
386
|
+
const { decisionId } = parsedPath.data;
|
|
512
387
|
await workItems.ensureReady();
|
|
513
388
|
const current = await workItems.getDeferredDecision(resolved.orgId, resolved.factoryProjectId, decisionId);
|
|
514
389
|
if (!current || current.status !== "failed" || !factoryDispatchFailureMetadata(current.failureCode).canRetry) return c.json({ error: "decision_not_retryable" }, 409);
|
|
515
390
|
const decision = await workItems.retryDeferredDecision(resolved.orgId, resolved.factoryProjectId, decisionId, /* @__PURE__ */ new Date());
|
|
516
391
|
if (!decision) return c.json({ error: "decision_not_retryable" }, 409);
|
|
517
|
-
return c.json({ decision: decisionSummary(decision) });
|
|
392
|
+
return c.json({ decision: decisionSummary(this.#boards, decision) });
|
|
518
393
|
}
|
|
519
394
|
}),
|
|
520
|
-
registerApiRoute(
|
|
521
|
-
method:
|
|
395
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemCreate.path, {
|
|
396
|
+
method: FACTORY_ROUTE_CONTRACTS.workItemCreate.method,
|
|
522
397
|
requiresAuth: false,
|
|
523
398
|
handler: async (c) => {
|
|
524
399
|
const resolved = await this.#resolveProject(loose(c));
|
|
@@ -528,7 +403,7 @@ var WorkItemRoutes = class extends Route {
|
|
|
528
403
|
const input = parseCreateWorkItem(body);
|
|
529
404
|
if (!input) return c.json({ error: "invalid_work_item" }, 400);
|
|
530
405
|
const boardId = input.board ?? (input.externalSource?.type === "pull-request" ? "review" : "work");
|
|
531
|
-
const board =
|
|
406
|
+
const board = this.#boards.get(boardId);
|
|
532
407
|
if (!board) return c.json({
|
|
533
408
|
error: "invalid_board",
|
|
534
409
|
message: `Board '${boardId}' is not installed.`
|
|
@@ -625,15 +500,20 @@ var WorkItemRoutes = class extends Route {
|
|
|
625
500
|
}
|
|
626
501
|
}
|
|
627
502
|
}),
|
|
628
|
-
registerApiRoute(
|
|
629
|
-
method:
|
|
503
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemTransition.path, {
|
|
504
|
+
method: FACTORY_ROUTE_CONTRACTS.workItemTransition.method,
|
|
630
505
|
requiresAuth: false,
|
|
631
506
|
handler: async (c) => {
|
|
632
507
|
const resolved = await this.#resolveProject(loose(c));
|
|
633
508
|
if ("response" in resolved) return resolved.response;
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
509
|
+
const context = loose(c);
|
|
510
|
+
const parsedPath = FACTORY_ROUTE_CONTRACTS.workItemTransition.pathSchema.safeParse({
|
|
511
|
+
id: resolved.factoryProjectId,
|
|
512
|
+
workItemId: context.req.param("workItemId")
|
|
513
|
+
});
|
|
514
|
+
if (!parsedPath.success) return c.json({ error: "Work item not found" }, 404);
|
|
515
|
+
const { workItemId } = parsedPath.data;
|
|
516
|
+
const parsed = parseTransitionBody(await readJson(context));
|
|
637
517
|
if (!parsed) return c.json({ error: "invalid_transition_request" }, 400);
|
|
638
518
|
if (!transitionService) return c.json({ error: "factory_transition_unavailable" }, 503);
|
|
639
519
|
await workItems.ensureReady();
|
|
@@ -663,7 +543,7 @@ var WorkItemRoutes = class extends Route {
|
|
|
663
543
|
metadata: {
|
|
664
544
|
transitionId: result.transitionId,
|
|
665
545
|
ingressType: parsed.ingress.type,
|
|
666
|
-
|
|
546
|
+
configVersion: transitionService.configVersion,
|
|
667
547
|
...result.status === "accepted" ? {
|
|
668
548
|
to: result.stage,
|
|
669
549
|
revision: result.revision
|
|
@@ -678,8 +558,8 @@ var WorkItemRoutes = class extends Route {
|
|
|
678
558
|
return c.json({ result }, result.code === "stale" ? 409 : 422);
|
|
679
559
|
}
|
|
680
560
|
}),
|
|
681
|
-
registerApiRoute(
|
|
682
|
-
method:
|
|
561
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemStart.path, {
|
|
562
|
+
method: FACTORY_ROUTE_CONTRACTS.workItemStart.method,
|
|
683
563
|
requiresAuth: false,
|
|
684
564
|
handler: async (c) => {
|
|
685
565
|
const resolved = await this.#resolveProject(loose(c));
|
|
@@ -718,15 +598,17 @@ var WorkItemRoutes = class extends Route {
|
|
|
718
598
|
return c.json({ prepared }, 202);
|
|
719
599
|
}
|
|
720
600
|
}),
|
|
721
|
-
registerApiRoute(
|
|
722
|
-
method:
|
|
601
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemUpdate.path, {
|
|
602
|
+
method: FACTORY_ROUTE_CONTRACTS.workItemUpdate.method,
|
|
723
603
|
requiresAuth: false,
|
|
724
604
|
handler: async (c) => {
|
|
725
605
|
const tenant = await this.#resolveTenant(loose(c));
|
|
726
606
|
if ("response" in tenant) return tenant.response;
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
607
|
+
const context = loose(c);
|
|
608
|
+
const parsedPath = FACTORY_ROUTE_CONTRACTS.workItemUpdate.pathSchema.safeParse({ id: context.req.param("id") });
|
|
609
|
+
if (!parsedPath.success) return c.json({ error: "Work item not found" }, 404);
|
|
610
|
+
const { id } = parsedPath.data;
|
|
611
|
+
const body = await readJson(context);
|
|
730
612
|
if (body === void 0) return c.json({ error: "Invalid JSON body" }, 400);
|
|
731
613
|
const patch = parseUpdateWorkItem(body);
|
|
732
614
|
if (!patch) return c.json({ error: "invalid_work_item_patch" }, 400);
|
|
@@ -737,7 +619,7 @@ var WorkItemRoutes = class extends Route {
|
|
|
737
619
|
});
|
|
738
620
|
if (!existing) return c.json({ error: "Work item not found" }, 404);
|
|
739
621
|
if (patch.board !== void 0) {
|
|
740
|
-
const board =
|
|
622
|
+
const board = this.#boards.get(patch.board);
|
|
741
623
|
const stage = patch.stages?.length === 1 ? patch.stages[0] : void 0;
|
|
742
624
|
if (existing.board !== null) return c.json({ error: "board_already_assigned" }, 409);
|
|
743
625
|
if (!board) return c.json({ error: "board_not_installed" }, 400);
|
|
@@ -775,14 +657,16 @@ var WorkItemRoutes = class extends Route {
|
|
|
775
657
|
}
|
|
776
658
|
}
|
|
777
659
|
}),
|
|
778
|
-
registerApiRoute(
|
|
779
|
-
method:
|
|
660
|
+
registerApiRoute(FACTORY_ROUTE_CONTRACTS.workItemDelete.path, {
|
|
661
|
+
method: FACTORY_ROUTE_CONTRACTS.workItemDelete.method,
|
|
780
662
|
requiresAuth: false,
|
|
781
663
|
handler: async (c) => {
|
|
782
664
|
const tenant = await this.#resolveTenant(loose(c));
|
|
783
665
|
if ("response" in tenant) return tenant.response;
|
|
784
|
-
const
|
|
785
|
-
|
|
666
|
+
const context = loose(c);
|
|
667
|
+
const parsedPath = FACTORY_ROUTE_CONTRACTS.workItemDelete.pathSchema.safeParse({ id: context.req.param("id") });
|
|
668
|
+
if (!parsedPath.success) return c.json({ error: "Work item not found" }, 404);
|
|
669
|
+
const { id } = parsedPath.data;
|
|
786
670
|
await workItems.ensureReady();
|
|
787
671
|
const deleted = await workItems.delete({
|
|
788
672
|
orgId: tenant.orgId,
|