@generacy-ai/control-plane 0.2.0 → 0.3.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.
Files changed (36) hide show
  1. package/dist/src/errors.d.ts +1 -1
  2. package/dist/src/errors.d.ts.map +1 -1
  3. package/dist/src/errors.js +1 -0
  4. package/dist/src/errors.js.map +1 -1
  5. package/dist/src/index.d.ts +3 -0
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/index.js +4 -0
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/routes/app-config.d.ts +1 -1
  10. package/dist/src/routes/app-config.d.ts.map +1 -1
  11. package/dist/src/routes/app-config.js +5 -16
  12. package/dist/src/routes/app-config.js.map +1 -1
  13. package/dist/src/routes/lifecycle.d.ts.map +1 -1
  14. package/dist/src/routes/lifecycle.js +64 -1
  15. package/dist/src/routes/lifecycle.js.map +1 -1
  16. package/dist/src/schemas.d.ts +73 -4
  17. package/dist/src/schemas.d.ts.map +1 -1
  18. package/dist/src/schemas.js +23 -0
  19. package/dist/src/schemas.js.map +1 -1
  20. package/dist/src/services/docker-engine-client.d.ts +57 -0
  21. package/dist/src/services/docker-engine-client.d.ts.map +1 -0
  22. package/dist/src/services/docker-engine-client.js +326 -0
  23. package/dist/src/services/docker-engine-client.js.map +1 -0
  24. package/dist/src/services/docker-engine-types.d.ts +152 -0
  25. package/dist/src/services/docker-engine-types.d.ts.map +1 -0
  26. package/dist/src/services/docker-engine-types.js +37 -0
  27. package/dist/src/services/docker-engine-types.js.map +1 -0
  28. package/dist/src/services/worker-enumeration.d.ts +31 -0
  29. package/dist/src/services/worker-enumeration.d.ts.map +1 -0
  30. package/dist/src/services/worker-enumeration.js +71 -0
  31. package/dist/src/services/worker-enumeration.js.map +1 -0
  32. package/dist/src/services/worker-scaler.d.ts +128 -0
  33. package/dist/src/services/worker-scaler.d.ts.map +1 -0
  34. package/dist/src/services/worker-scaler.js +405 -0
  35. package/dist/src/services/worker-scaler.js.map +1 -0
  36. package/package.json +2 -1
@@ -0,0 +1,128 @@
1
+ import { DockerEngineClient } from './docker-engine-client.js';
2
+ import { type ContainerInspect, type ContainerCreateBody, DockerEngineError } from './docker-engine-types.js';
3
+ import { type WorkerReplica, computeProjectName, enumerateWorkers } from './worker-enumeration.js';
4
+ export { computeProjectName, enumerateWorkers, type WorkerReplica };
5
+ export interface ScaleOptions {
6
+ /** Target worker count. Must be >= 1 (validated upstream in lifecycle route). */
7
+ count: number;
8
+ /** Override orchestrator URL for metadata-refresh callback. */
9
+ orchestratorUrl?: string;
10
+ /** Override orchestrator internal API key. */
11
+ orchestratorApiKey?: string;
12
+ /** Override Docker socket. Default: env DOCKER_HOST or unix:///var/run/docker-host.sock. */
13
+ dockerHost?: string;
14
+ /** Override engine client (test seam — production wires the default). */
15
+ engineClient?: DockerEngineClient;
16
+ }
17
+ export interface ScaleResult {
18
+ /** Worker count observed before scaling (from Engine API enumeration, not .env). */
19
+ previousCount: number;
20
+ /** Target count from ScaleOptions.count. */
21
+ requestedCount: number;
22
+ /** Actual achieved count after the operation. Equals requestedCount on success. */
23
+ actualCount: number;
24
+ }
25
+ export declare class PartialScaleError extends Error {
26
+ readonly name = "PartialScaleError";
27
+ readonly requested: number;
28
+ readonly actual: number;
29
+ readonly previousCount: number;
30
+ readonly cause: Error;
31
+ constructor(requested: number, actual: number, previousCount: number, cause: Error);
32
+ }
33
+ export interface ScalePlan {
34
+ toCreate: number[];
35
+ toRemove: string[];
36
+ }
37
+ /**
38
+ * Plan container-number assignment for a scale operation.
39
+ *
40
+ * Scale up: gap-fill ascending in [1..max(existing)], then append.
41
+ * Scale down: sort exited (highest-numbered first), then running (highest-numbered first),
42
+ * take the first (current - target) IDs.
43
+ * No-op: returns `{ toCreate: [], toRemove: [] }`.
44
+ *
45
+ * Pure function — unit-tested independently. FR-006, SC-003, SC-011.
46
+ */
47
+ export declare function assignContainerNumbers(existing: WorkerReplica[], target: number): ScalePlan;
48
+ /**
49
+ * Clone a source replica's inspect response into a create body for a new replica.
50
+ *
51
+ * Strips orchestrator-set fields (Id, Created, State, Status, Hostname, populated
52
+ * NetworkSettings) and keeps Image, Cmd, Env, Entrypoint, WorkingDir, User, Labels,
53
+ * Healthcheck, StopSignal, StopTimeout, ExposedPorts, and all of HostConfig.
54
+ *
55
+ * Overwrites the `com.docker.compose.container-number` label with the new number —
56
+ * preserves all other labels (project, service, config-hash, etc.) per FR-007.
57
+ *
58
+ * Builds NetworkingConfig with **first network only** from source.NetworkSettings.Networks
59
+ * in insertion order. Caller is responsible for `connectNetwork` calls for remaining
60
+ * networks before `startContainer` (Q1=A multi-network sequencing).
61
+ *
62
+ * Throws `Error('SOURCE_REPLICA_HAS_NO_NETWORKS')` if source has zero networks.
63
+ */
64
+ export declare function cloneInspectToCreate(inspect: ContainerInspect, newNumber: number, _newName: string): ContainerCreateBody;
65
+ interface ScaleUpResult {
66
+ created: number[];
67
+ failed?: {
68
+ number: number;
69
+ error: Error;
70
+ };
71
+ }
72
+ interface ScaleDownResult {
73
+ removed: string[];
74
+ failed?: {
75
+ id: string;
76
+ error: Error;
77
+ };
78
+ }
79
+ /**
80
+ * Create + connect-extra-networks + start, repeated per toCreate slot.
81
+ *
82
+ * Per-slot sequencing (Q1=A):
83
+ * 1. Pre-check gap-fill name collision (FR-015, Q5=A) — if any container
84
+ * already holds `<project>-worker-<n>`, force-remove it first. Edge
85
+ * case after manual `docker rm` of a running worker; normal operation
86
+ * doesn't trigger it because exited replicas are counted by FR-002.
87
+ * 2. POST /containers/create with the first network in NetworkingConfig.
88
+ * 3. POST /networks/<id>/connect per additional network from source.
89
+ * 4. POST /containers/<id>/start.
90
+ *
91
+ * On the first error, stop the loop and return `{ created, failed }`. Do NOT
92
+ * roll back already-created replicas (Q2=B: commit what succeeded).
93
+ */
94
+ export declare function scaleUp(client: DockerEngineClient, project: string, source: ContainerInspect, toCreate: number[]): Promise<ScaleUpResult>;
95
+ /**
96
+ * Stop + remove per ID, in the caller-provided order (already sorted by
97
+ * `assignContainerNumbers`: exited first, then running, highest-numbered first).
98
+ * On the first error, stop and return `{ removed, failed }`. (FR-004)
99
+ */
100
+ export declare function scaleDown(client: DockerEngineClient, toRemoveIds: string[]): Promise<ScaleDownResult>;
101
+ /**
102
+ * Scale worker replicas to the requested count via the Docker Engine API.
103
+ *
104
+ * Replaces the previous `docker compose --scale` shell-out: enumerates workers
105
+ * by `com.docker.compose.*` labels, clones an existing replica's config, and
106
+ * issues create/connect/start or stop/remove directly on the daemon. Removes
107
+ * the host compose-file dependency entirely.
108
+ *
109
+ * Concurrent invocations are serialized by an in-process async mutex (FR-014).
110
+ *
111
+ * Stale clone drift case (FR-013): if a user edits the host compose file and
112
+ * rebuilds without `docker compose up -d`, scale-up clones a stale source
113
+ * replica's config — same behavior as compose itself when scaling from an
114
+ * older replica. Documented here per the spec; not actionable in this code path.
115
+ */
116
+ export declare function scaleWorkers(options: ScaleOptions): Promise<ScaleResult>;
117
+ /**
118
+ * Update the `workers` field in cluster.local.yaml atomically.
119
+ * Creates the file if absent. Preserves any other top-level fields already
120
+ * present.
121
+ */
122
+ export declare function updateClusterLocalYaml(localYamlPath: string, count: number): Promise<void>;
123
+ /**
124
+ * POST to orchestrator /internal/refresh-metadata to trigger immediate metadata push.
125
+ */
126
+ export declare function triggerMetadataRefresh(orchestratorUrl: string, apiKey: string): Promise<void>;
127
+ export { DockerEngineError };
128
+ //# sourceMappingURL=worker-scaler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-scaler.d.ts","sourceRoot":"","sources":["../../../src/services/worker-scaler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,405 @@
1
+ import { readFile, stat, writeFile, rename } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ import { randomBytes } from 'node:crypto';
4
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
5
+ import { resolveGeneracyDir } from './project-dir-resolver.js';
6
+ import { DockerEngineClient } from './docker-engine-client.js';
7
+ import { DockerEngineError, } from './docker-engine-types.js';
8
+ import { computeProjectName, enumerateWorkers, } from './worker-enumeration.js';
9
+ // Re-export so existing callers/tests can import these symbols from this module.
10
+ export { computeProjectName, enumerateWorkers };
11
+ export class PartialScaleError extends Error {
12
+ name = 'PartialScaleError';
13
+ requested;
14
+ actual;
15
+ previousCount;
16
+ cause;
17
+ constructor(requested, actual, previousCount, cause) {
18
+ super(`Partial scale: requested ${requested}, achieved ${actual} (${cause.message})`);
19
+ this.requested = requested;
20
+ this.actual = actual;
21
+ this.previousCount = previousCount;
22
+ this.cause = cause;
23
+ }
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // Module-level async mutex (FR-014, Q4=A).
27
+ //
28
+ // Promise-chain pattern from research.md §"In-process mutex" — serializes
29
+ // concurrent scaleWorkers() calls within this process. The second caller
30
+ // waits for the first to complete, then operates on the post-first state.
31
+ // ---------------------------------------------------------------------------
32
+ let inflight = Promise.resolve();
33
+ // ---------------------------------------------------------------------------
34
+ // Pure helpers
35
+ // ---------------------------------------------------------------------------
36
+ /**
37
+ * Plan container-number assignment for a scale operation.
38
+ *
39
+ * Scale up: gap-fill ascending in [1..max(existing)], then append.
40
+ * Scale down: sort exited (highest-numbered first), then running (highest-numbered first),
41
+ * take the first (current - target) IDs.
42
+ * No-op: returns `{ toCreate: [], toRemove: [] }`.
43
+ *
44
+ * Pure function — unit-tested independently. FR-006, SC-003, SC-011.
45
+ */
46
+ export function assignContainerNumbers(existing, target) {
47
+ const current = existing.length;
48
+ if (current === target) {
49
+ return { toCreate: [], toRemove: [] };
50
+ }
51
+ if (target > current) {
52
+ // Scale up
53
+ const numbersInUse = new Set(existing.map((r) => r.number));
54
+ const max = existing.length === 0 ? 0 : Math.max(...numbersInUse);
55
+ const toCreate = [];
56
+ // Gap-fill ascending within [1..max].
57
+ for (let n = 1; n <= max; n++) {
58
+ if (toCreate.length >= target - current)
59
+ break;
60
+ if (!numbersInUse.has(n))
61
+ toCreate.push(n);
62
+ }
63
+ // Append above max until target reached.
64
+ for (let n = max + 1; toCreate.length < target - current; n++) {
65
+ toCreate.push(n);
66
+ }
67
+ return { toCreate, toRemove: [] };
68
+ }
69
+ // Scale down: exited first (highest-numbered first), then running (highest-numbered first).
70
+ const exited = existing
71
+ .filter((r) => r.state !== 'running')
72
+ .sort((a, b) => b.number - a.number);
73
+ const running = existing
74
+ .filter((r) => r.state === 'running')
75
+ .sort((a, b) => b.number - a.number);
76
+ const ordered = [...exited, ...running];
77
+ const toRemove = ordered.slice(0, current - target).map((r) => r.id);
78
+ return { toCreate: [], toRemove };
79
+ }
80
+ /**
81
+ * Clone a source replica's inspect response into a create body for a new replica.
82
+ *
83
+ * Strips orchestrator-set fields (Id, Created, State, Status, Hostname, populated
84
+ * NetworkSettings) and keeps Image, Cmd, Env, Entrypoint, WorkingDir, User, Labels,
85
+ * Healthcheck, StopSignal, StopTimeout, ExposedPorts, and all of HostConfig.
86
+ *
87
+ * Overwrites the `com.docker.compose.container-number` label with the new number —
88
+ * preserves all other labels (project, service, config-hash, etc.) per FR-007.
89
+ *
90
+ * Builds NetworkingConfig with **first network only** from source.NetworkSettings.Networks
91
+ * in insertion order. Caller is responsible for `connectNetwork` calls for remaining
92
+ * networks before `startContainer` (Q1=A multi-network sequencing).
93
+ *
94
+ * Throws `Error('SOURCE_REPLICA_HAS_NO_NETWORKS')` if source has zero networks.
95
+ */
96
+ export function cloneInspectToCreate(inspect, newNumber, _newName) {
97
+ const networkEntries = Object.entries(inspect.NetworkSettings.Networks);
98
+ if (networkEntries.length === 0) {
99
+ throw new Error('SOURCE_REPLICA_HAS_NO_NETWORKS');
100
+ }
101
+ const [firstNetworkName, firstEndpoint] = networkEntries[0];
102
+ const labels = { ...(inspect.Config.Labels ?? {}) };
103
+ labels['com.docker.compose.container-number'] = String(newNumber);
104
+ const firstEndpointConfig = {};
105
+ if (firstEndpoint.Aliases)
106
+ firstEndpointConfig.Aliases = firstEndpoint.Aliases;
107
+ if (firstEndpoint.IPAMConfig?.IPv4Address) {
108
+ firstEndpointConfig.IPAMConfig = { IPv4Address: firstEndpoint.IPAMConfig.IPv4Address };
109
+ }
110
+ const body = {
111
+ Image: inspect.Image,
112
+ HostConfig: inspect.HostConfig,
113
+ NetworkingConfig: { EndpointsConfig: { [firstNetworkName]: firstEndpointConfig } },
114
+ Labels: labels,
115
+ };
116
+ // Carry through optional config fields verbatim.
117
+ if (inspect.Config.User !== undefined)
118
+ body.User = inspect.Config.User;
119
+ if (inspect.Config.Env !== undefined)
120
+ body.Env = inspect.Config.Env;
121
+ if (inspect.Config.Cmd !== undefined)
122
+ body.Cmd = inspect.Config.Cmd;
123
+ if (inspect.Config.Entrypoint !== undefined)
124
+ body.Entrypoint = inspect.Config.Entrypoint;
125
+ if (inspect.Config.WorkingDir !== undefined)
126
+ body.WorkingDir = inspect.Config.WorkingDir;
127
+ if (inspect.Config.Healthcheck !== undefined)
128
+ body.Healthcheck = inspect.Config.Healthcheck;
129
+ if (inspect.Config.StopSignal !== undefined)
130
+ body.StopSignal = inspect.Config.StopSignal;
131
+ if (inspect.Config.StopTimeout !== undefined)
132
+ body.StopTimeout = inspect.Config.StopTimeout;
133
+ if (inspect.Config.ExposedPorts !== undefined)
134
+ body.ExposedPorts = inspect.Config.ExposedPorts;
135
+ // Hostname is intentionally NOT carried through — Docker derives it from the
136
+ // container name when absent, which avoids collisions across clones.
137
+ return body;
138
+ }
139
+ /**
140
+ * Create + connect-extra-networks + start, repeated per toCreate slot.
141
+ *
142
+ * Per-slot sequencing (Q1=A):
143
+ * 1. Pre-check gap-fill name collision (FR-015, Q5=A) — if any container
144
+ * already holds `<project>-worker-<n>`, force-remove it first. Edge
145
+ * case after manual `docker rm` of a running worker; normal operation
146
+ * doesn't trigger it because exited replicas are counted by FR-002.
147
+ * 2. POST /containers/create with the first network in NetworkingConfig.
148
+ * 3. POST /networks/<id>/connect per additional network from source.
149
+ * 4. POST /containers/<id>/start.
150
+ *
151
+ * On the first error, stop the loop and return `{ created, failed }`. Do NOT
152
+ * roll back already-created replicas (Q2=B: commit what succeeded).
153
+ */
154
+ export async function scaleUp(client, project, source, toCreate) {
155
+ const created = [];
156
+ // Source network order is daemon-authoritative (insertion order). The first
157
+ // network goes into NetworkingConfig at create time; the rest are attached
158
+ // via connectNetwork before start.
159
+ const sourceNetworkEntries = Object.entries(source.NetworkSettings.Networks);
160
+ const extraNetworks = sourceNetworkEntries.slice(1);
161
+ for (const number of toCreate) {
162
+ const name = `${project}-worker-${number}`;
163
+ try {
164
+ // Pre-check for stale container holding the target name.
165
+ const conflicts = await client.listContainers({
166
+ all: true,
167
+ filters: { name: [name] },
168
+ });
169
+ for (const conflict of conflicts) {
170
+ // Docker's name filter is substring-match — narrow to exact match.
171
+ const exactMatch = conflict.Names.some((n) => n === `/${name}` || n === name);
172
+ if (!exactMatch)
173
+ continue;
174
+ await client.removeContainer(conflict.Id, { force: true });
175
+ }
176
+ const body = cloneInspectToCreate(source, number, name);
177
+ const createResult = await client.createContainer(name, body);
178
+ // Attach remaining networks before start so workloads see full membership.
179
+ for (const [, endpoint] of extraNetworks) {
180
+ const connectBody = {
181
+ Container: createResult.Id,
182
+ };
183
+ const endpointConfig = {};
184
+ if (endpoint.Aliases)
185
+ endpointConfig.Aliases = endpoint.Aliases;
186
+ if (endpoint.IPAMConfig?.IPv4Address) {
187
+ endpointConfig.IPAMConfig = { IPv4Address: endpoint.IPAMConfig.IPv4Address };
188
+ }
189
+ if (Object.keys(endpointConfig).length > 0) {
190
+ connectBody.EndpointConfig = endpointConfig;
191
+ }
192
+ await client.connectNetwork(endpoint.NetworkID, connectBody);
193
+ }
194
+ await client.startContainer(createResult.Id);
195
+ created.push(number);
196
+ }
197
+ catch (err) {
198
+ return {
199
+ created,
200
+ failed: { number, error: err instanceof Error ? err : new Error(String(err)) },
201
+ };
202
+ }
203
+ }
204
+ return { created };
205
+ }
206
+ /**
207
+ * Stop + remove per ID, in the caller-provided order (already sorted by
208
+ * `assignContainerNumbers`: exited first, then running, highest-numbered first).
209
+ * On the first error, stop and return `{ removed, failed }`. (FR-004)
210
+ */
211
+ export async function scaleDown(client, toRemoveIds) {
212
+ const removed = [];
213
+ for (const id of toRemoveIds) {
214
+ try {
215
+ await client.stopContainer(id);
216
+ await client.removeContainer(id);
217
+ removed.push(id);
218
+ }
219
+ catch (err) {
220
+ return {
221
+ removed,
222
+ failed: { id, error: err instanceof Error ? err : new Error(String(err)) },
223
+ };
224
+ }
225
+ }
226
+ return { removed };
227
+ }
228
+ // ---------------------------------------------------------------------------
229
+ // Main entry: scaleWorkers
230
+ // ---------------------------------------------------------------------------
231
+ /**
232
+ * Scale worker replicas to the requested count via the Docker Engine API.
233
+ *
234
+ * Replaces the previous `docker compose --scale` shell-out: enumerates workers
235
+ * by `com.docker.compose.*` labels, clones an existing replica's config, and
236
+ * issues create/connect/start or stop/remove directly on the daemon. Removes
237
+ * the host compose-file dependency entirely.
238
+ *
239
+ * Concurrent invocations are serialized by an in-process async mutex (FR-014).
240
+ *
241
+ * Stale clone drift case (FR-013): if a user edits the host compose file and
242
+ * rebuilds without `docker compose up -d`, scale-up clones a stale source
243
+ * replica's config — same behavior as compose itself when scaling from an
244
+ * older replica. Documented here per the spec; not actionable in this code path.
245
+ */
246
+ export async function scaleWorkers(options) {
247
+ const previous = inflight;
248
+ let resolveNext;
249
+ inflight = new Promise((r) => {
250
+ resolveNext = r;
251
+ });
252
+ await previous;
253
+ try {
254
+ return await doScale(options);
255
+ }
256
+ finally {
257
+ resolveNext(undefined);
258
+ }
259
+ }
260
+ async function doScale(options) {
261
+ const { count } = options;
262
+ const orchestratorUrl = options.orchestratorUrl ?? process.env['ORCHESTRATOR_URL'] ?? 'http://127.0.0.1:3100';
263
+ const orchestratorApiKey = options.orchestratorApiKey ?? process.env['ORCHESTRATOR_INTERNAL_API_KEY'];
264
+ const dockerHostOption = options.dockerHost;
265
+ const client = options.engineClient ?? new DockerEngineClient(dockerHostOption !== undefined ? { dockerHost: dockerHostOption } : {});
266
+ const project = await computeProjectName(client);
267
+ const existing = await enumerateWorkers(client, project);
268
+ const previousCount = existing.length;
269
+ // No-op short-circuit: no Engine mutations, no cluster.yaml write, no metadata refresh.
270
+ if (previousCount === count) {
271
+ return { previousCount, requestedCount: count, actualCount: previousCount };
272
+ }
273
+ const plan = assignContainerNumbers(existing, count);
274
+ let scaleUpResult = { created: [] };
275
+ let scaleDownResult = { removed: [] };
276
+ let cause = null;
277
+ if (plan.toCreate.length > 0) {
278
+ // Clone source: first existing replica (any state — even an exited replica
279
+ // carries a complete inspect record).
280
+ const sourceReplica = existing[0];
281
+ const source = await client.inspectContainer(sourceReplica.id);
282
+ scaleUpResult = await scaleUp(client, project, source, plan.toCreate);
283
+ if (scaleUpResult.failed)
284
+ cause = scaleUpResult.failed.error;
285
+ }
286
+ else if (plan.toRemove.length > 0) {
287
+ scaleDownResult = await scaleDown(client, plan.toRemove);
288
+ if (scaleDownResult.failed)
289
+ cause = scaleDownResult.failed.error;
290
+ }
291
+ const actualCount = previousCount + scaleUpResult.created.length - scaleDownResult.removed.length;
292
+ const madeProgress = scaleUpResult.created.length > 0 || scaleDownResult.removed.length > 0;
293
+ if (cause && !madeProgress) {
294
+ // Full failure: no replicas created/removed. cluster.yaml NOT updated,
295
+ // metadata refresh NOT fired. Throw the underlying error directly so the
296
+ // route handler can map it (e.g. DockerDaemonUnavailableError → 503).
297
+ throw cause;
298
+ }
299
+ // Persist runtime worker count on any progress, including partial. Atomic
300
+ // temp+rename. Writes to cluster.local.yaml (git-ignored) to avoid mutating
301
+ // the template-owned, git-tracked cluster.yaml (#709).
302
+ const generacyDir = await resolveGeneracyDir();
303
+ const localYamlPath = join(generacyDir, 'cluster.local.yaml');
304
+ await updateClusterLocalYaml(localYamlPath, actualCount);
305
+ // Best-effort: keep .env's WORKER_COUNT in sync so host-side `docker compose
306
+ // up -d` doesn't undo the scale on the next re-up. Failures are non-blocking
307
+ // (cluster.local.yaml is the runtime source of truth and the CLI
308
+ // re-derivation step will reconcile .env on the next `npx generacy up` /
309
+ // `update`). See #708.
310
+ const envPath = join(generacyDir, '.env');
311
+ try {
312
+ await syncEnvWorkerCountInScaler(envPath, actualCount);
313
+ }
314
+ catch (err) {
315
+ const e = err;
316
+ if (e?.code === 'ENOENT') {
317
+ console.warn(`[worker-scaler] WORKER_COUNT sync to .env skipped: file not found at ${envPath}`);
318
+ }
319
+ else {
320
+ const msg = err instanceof Error ? err.message : String(err);
321
+ console.warn(`[worker-scaler] WORKER_COUNT sync to .env failed: ${msg}; cluster.local.yaml is the source of truth`);
322
+ }
323
+ }
324
+ if (orchestratorApiKey) {
325
+ triggerMetadataRefresh(orchestratorUrl, orchestratorApiKey).catch(() => {
326
+ // Non-fatal: metadata will refresh on the next periodic cycle.
327
+ });
328
+ }
329
+ if (cause) {
330
+ throw new PartialScaleError(count, actualCount, previousCount, cause);
331
+ }
332
+ return { previousCount, requestedCount: count, actualCount };
333
+ }
334
+ // ---------------------------------------------------------------------------
335
+ // Preserved helpers
336
+ // ---------------------------------------------------------------------------
337
+ /**
338
+ * Update the `workers` field in cluster.local.yaml atomically.
339
+ * Creates the file if absent. Preserves any other top-level fields already
340
+ * present.
341
+ */
342
+ export async function updateClusterLocalYaml(localYamlPath, count) {
343
+ let doc;
344
+ try {
345
+ const content = await readFile(localYamlPath, 'utf-8');
346
+ doc = parseYaml(content) ?? {};
347
+ }
348
+ catch {
349
+ doc = {};
350
+ }
351
+ doc.workers = count;
352
+ const output = stringifyYaml(doc);
353
+ await atomicWrite(localYamlPath, output);
354
+ }
355
+ /**
356
+ * POST to orchestrator /internal/refresh-metadata to trigger immediate metadata push.
357
+ */
358
+ export async function triggerMetadataRefresh(orchestratorUrl, apiKey) {
359
+ const response = await fetch(`${orchestratorUrl}/internal/refresh-metadata`, {
360
+ method: 'POST',
361
+ headers: {
362
+ authorization: `Bearer ${apiKey}`,
363
+ 'content-type': 'application/json',
364
+ },
365
+ signal: AbortSignal.timeout(5000),
366
+ });
367
+ if (!response.ok) {
368
+ throw new Error(`refresh-metadata returned ${response.status}`);
369
+ }
370
+ }
371
+ /**
372
+ * Atomic file write: write to temp file, then rename. Temp file is created in
373
+ * the target directory (not os.tmpdir) so rename(2) stays on a single filesystem.
374
+ */
375
+ async function atomicWrite(targetPath, content) {
376
+ const tmpPath = join(dirname(targetPath), `.${randomBytes(8).toString('hex')}.tmp`);
377
+ await writeFile(tmpPath, content, { mode: 0o644 });
378
+ await rename(tmpPath, targetPath);
379
+ }
380
+ /**
381
+ * Rewrite `WORKER_COUNT=<count>` in .env. Throws ENOENT if .env is missing
382
+ * (caller treats that as a skip-and-warn). Other errors propagate to the
383
+ * caller's catch and are logged as failures. The CLI re-derivation path in
384
+ * `worker-count-deriver.ts` is the symmetric implementation on the host side.
385
+ */
386
+ async function syncEnvWorkerCountInScaler(envPath, count) {
387
+ await stat(envPath); // throws ENOENT if missing — caller logs the skip
388
+ const existing = await readFile(envPath, 'utf-8');
389
+ const line = `WORKER_COUNT=${count}`;
390
+ const pattern = /^WORKER_COUNT=.*$/m;
391
+ let next;
392
+ if (pattern.test(existing)) {
393
+ next = existing.replace(pattern, line);
394
+ }
395
+ else if (existing.length === 0) {
396
+ next = `${line}\n`;
397
+ }
398
+ else {
399
+ next = existing.endsWith('\n') ? `${existing}${line}\n` : `${existing}\n${line}\n`;
400
+ }
401
+ await atomicWrite(envPath, next);
402
+ }
403
+ // Re-export so existing tests can import from this module.
404
+ export { DockerEngineError };
405
+ //# sourceMappingURL=worker-scaler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-scaler.js","sourceRoot":"","sources":["../../../src/services/worker-scaler.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@generacy-ai/control-plane",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "In-cluster control-plane HTTP service over Unix socket for cloud-hosted bootstrap UI",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -19,6 +19,7 @@
19
19
  "dependencies": {
20
20
  "yaml": "^2.7.0",
21
21
  "zod": "^3.23.0",
22
+ "@generacy-ai/config": "0.2.0",
22
23
  "@generacy-ai/credhelper": "0.1.1"
23
24
  },
24
25
  "devDependencies": {