@open-mercato/cezar 0.9.0-pr609.613 → 0.9.0-pr609.615

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.
@@ -114,6 +114,14 @@ export interface RemoveProjectResponse {
114
114
  removed: true;
115
115
  id: string;
116
116
  }
117
+ /** `PATCH /api/projects/:projectId` (spec 2026-07-22-per-project-concurrency)
118
+ * — sets or clears a project's per-project `maxParallel`. The entry carries
119
+ * the same `status`/`branch` probe `GET /api/projects` attaches, so the
120
+ * cockpit sees one project shape. `null` in the request clears the override
121
+ * back to "inherit the workspace cap". */
122
+ export interface UpdateProjectResponse {
123
+ project: ProjectListEntry;
124
+ }
117
125
  /** `GET/PUT /api/workspace/config` (multi-project spec, step 2.7) — the
118
126
  * settings slice of `~/.cezar/config.json`: global knobs ONLY, never the
119
127
  * project registry (that is `GET /api/projects`' job). */
@@ -1053,6 +1053,60 @@ export function createApp(deps) {
1053
1053
  const body = { removed: true, id };
1054
1054
  return c.json(body);
1055
1055
  });
1056
+ // Edit one field of an existing registry entry (spec
1057
+ // 2026-07-22-per-project-concurrency): the per-project concurrency ceiling.
1058
+ // A PATCH (not PUT) because it touches a single field, and a distinct route
1059
+ // from POST (register-a-folder) to keep register vs. edit semantics clear.
1060
+ // `maxParallel: null` clears the override back to "inherit the workspace
1061
+ // cap". Bounds mirror `workspaceProjectSchema` (config.ts) exactly, so a
1062
+ // value this route accepts can never be degraded away by the next load's
1063
+ // `.catch`.
1064
+ const updateProjectSchema = z.object({
1065
+ maxParallel: z.number().int().min(1).max(16).nullable(),
1066
+ });
1067
+ app.patch('/api/projects/:projectId', async (c) => {
1068
+ const raw = c.req.param('projectId');
1069
+ // Same gate + 404 wording as DELETE: a malformed id is an unknown project.
1070
+ if (!projectIdSchema.safeParse(raw).success) {
1071
+ return c.json({ error: `unknown project: ${raw}` }, 404);
1072
+ }
1073
+ const parsed = updateProjectSchema.safeParse(await c.req.json().catch(() => null));
1074
+ if (!parsed.success) {
1075
+ return c.json({ error: parsed.error.issues.map((i) => i.message).join('; ') }, 400);
1076
+ }
1077
+ // `default` is the boot alias the cockpit is allowed to use everywhere else.
1078
+ const id = raw === 'default' ? await resolveBootProject() : raw;
1079
+ const { maxParallel } = parsed.data;
1080
+ let updated;
1081
+ try {
1082
+ await mergeWriteWorkspaceConfig((config) => {
1083
+ const entry = config.projects.find((p) => p.id === id);
1084
+ if (!entry)
1085
+ return; // unknown id — persisted nothing; answered 404 below
1086
+ // null clears the override; a number sets it. Mutated in place so
1087
+ // `.passthrough()` keys on the entry survive.
1088
+ if (maxParallel === null)
1089
+ delete entry.maxParallel;
1090
+ else
1091
+ entry.maxParallel = maxParallel;
1092
+ updated = entry;
1093
+ });
1094
+ }
1095
+ catch (err) {
1096
+ // e.g. a read-only home — nothing was persisted (atomic tmp+rename).
1097
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1098
+ }
1099
+ if (!updated)
1100
+ return c.json({ error: `unknown project: ${id}` }, 404);
1101
+ // The new ceiling takes effect WITHOUT a restart: refresh the shared
1102
+ // semaphore's snapshot and pump every manager — the same live-apply hook
1103
+ // `PUT /api/workspace/config` fires for a workspace-cap change.
1104
+ await deps.semaphore?.refresh();
1105
+ const body = {
1106
+ project: { ...updated, ...(await probeProjectStatus(updated.root)) },
1107
+ };
1108
+ return c.json(body);
1109
+ });
1056
1110
  // ---- GUI clone (multi-project spec, step 4.3) ----------------------------
1057
1111
  // "Add project → Clone from GitHub": clone into the checkout root, then
1058
1112
  // register the result through `registerFolder` above (same guards, same