@ricsam/r5d-worker 0.0.173 → 0.0.175

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.173",
3
+ "version": "0.0.175",
4
4
  "type": "commonjs"
5
5
  }
@@ -20605,7 +20605,7 @@ function resolveEntrypointPath(entrypoint) {
20605
20605
  }
20606
20606
  }
20607
20607
  function getR5dctlVersion() {
20608
- if (true) return "0.0.173";
20608
+ if (true) return "0.0.175";
20609
20609
  const entrypoint = process.argv[1] ? resolveEntrypointPath(process.argv[1]) : null;
20610
20610
  let current = entrypoint ? import_node_path2.default.dirname(entrypoint) : process.cwd();
20611
20611
  for (let index = 0; index < 12; index += 1) {
package/dist/mjs/main.mjs CHANGED
@@ -7,7 +7,7 @@ import { startManagerRpc } from "./runtime/releases/rpc-main.mjs";
7
7
  import { ManagerRpcConfig } from "./runtime/releases/rpc-protocol.mjs";
8
8
  const args = process.argv.slice(2);
9
9
  if (args.includes("--version")) {
10
- console.log(`r5d-worker ${true ? "0.0.173" : "development"}`);
10
+ console.log(`r5d-worker ${true ? "0.0.175" : "development"}`);
11
11
  } else if (!args.length || args.includes("--help")) {
12
12
  console.log(
13
13
  "Usage: r5d-worker start --label <label> [--root <dir>] [--base-url <url>] [--token <worker-token>]\n r5d-worker executor /absolute/private/config.json\n r5d-worker manager /absolute/private/config.json\n\nRun independently supervised durable runtime services. Provision configuration with r5dinfra."
@@ -15,7 +15,7 @@ if (args.includes("--version")) {
15
15
  } else if (args[0] === "start") {
16
16
  const runtime = await startPersonalWorker(
17
17
  parsePersonalWorkerOptions(args.slice(1)),
18
- true ? "0.0.173" : "development"
18
+ true ? "0.0.175" : "development"
19
19
  );
20
20
  console.log(`Worker connected: ${runtime.resourceId}`);
21
21
  let closing = false;
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.173",
3
+ "version": "0.0.175",
4
4
  "type": "module"
5
5
  }
@@ -164,6 +164,7 @@ class WorkspaceAuthority {
164
164
  for (const id of await fs.readdir(path.join(config.root, "state"))) {
165
165
  if (authority.benches.has(id)) continue;
166
166
  const directory = path.join(config.root, "state", id);
167
+ if (!(await fs.lstat(directory)).isDirectory()) continue;
167
168
  await noSymlinkAncestors(directory);
168
169
  const b = ApprovedWorkbench.parse(JSON.parse((await readRegular(path.join(directory, "workbench.json"), 128 * 1024)).toString()));
169
170
  if (b.id !== id || b.cwd !== authority.expectedCwd(b)) throw new WorkspaceError("wrong_binding", "Invalid durable dynamic workbench");
@@ -264,8 +265,13 @@ class WorkspaceAuthority {
264
265
  const result = [];
265
266
  for (const [operationId, metadata] of Object.entries(b.state.runs)) {
266
267
  if (metadata.state === "rejected_capacity" || (metadata.sessionId ?? b.config.sessionId) !== identity.sessionId) continue;
267
- const poll = await this.poll(identity, { userId: identity.userId, sessionId: identity.sessionId, operationId, fence: route.workerFence }, 0, 0, 1);
268
- result.push({ ...metadata, ...poll.receipt, workerLabel: route.workerFence.resourceId });
268
+ try {
269
+ const poll = await this.poll(identity, { userId: identity.userId, sessionId: identity.sessionId, operationId, fence: route.workerFence }, 0, 0, 1);
270
+ result.push({ ...metadata, ...poll.receipt, workerLabel: route.workerFence.resourceId });
271
+ } catch (error) {
272
+ if (!(error instanceof ExecutorError && error.code === "run_not_found")) throw error;
273
+ result.push({ ...metadata, operationId, userId: identity.userId, sessionId: identity.sessionId, state: "unknown", pid: null, pty: false, lane: "general", stdoutBytes: 0, stderrBytes: 0, stdinClosed: true, exitCode: null, signal: null, reason: "run_not_found", workerLabel: route.workerFence.resourceId });
274
+ }
269
275
  }
270
276
  return result;
271
277
  }
@@ -406,8 +412,20 @@ class WorkspaceAuthority {
406
412
  return { head, treeHash, files: changes, ...patch === void 0 ? {} : { patch } };
407
413
  });
408
414
  }
415
+ /** Bench state is one in-memory object with several writers on different
416
+ * queues (checkout work, run receipts). Writes of its file are chained so
417
+ * they never overlap and the write that lands last always serializes the
418
+ * newest state; the chain covers only the write itself, never the work
419
+ * around it, so a run receipt does not wait for a publication. */
409
420
  save(b) {
410
- return durableJson(path.join(b.directory, "state.json"), b.state);
421
+ const write = (b.saving ?? Promise.resolve()).catch(() => {
422
+ }).then(() => durableJson(path.join(b.directory, "state.json"), b.state));
423
+ b.saving = write;
424
+ void write.finally(() => {
425
+ if (b.saving === write) b.saving = void 0;
426
+ }).catch(() => {
427
+ });
428
+ return write;
411
429
  }
412
430
  async owned(admitted = false) {
413
431
  if (this.closing && !admitted) throw new WorkspaceError("authority_closed", "Workspace authority is closing");
@@ -1846,7 +1864,12 @@ class WorkspaceAuthority {
1846
1864
  }
1847
1865
  const nonmutating = Object.keys(requestedEnv).length === 0 && payload.credentials.length === 0 && b.config.nonmutatingArgv.some((argv) => canonicalJson(argv) === canonicalJson(payload.argv));
1848
1866
  b.state.runs[operation.operationId] = { artifactEnvironment: true, sessionId: identity.sessionId, payloadHash: hash, mutating: !nonmutating, state: "unknown", argv: payload.argv, cwd: payload.cwd, startedAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1849
- await this.save(b);
1867
+ try {
1868
+ await this.save(b);
1869
+ } catch (error) {
1870
+ delete b.state.runs[operation.operationId];
1871
+ throw error;
1872
+ }
1850
1873
  for (let attempt = 0; ; attempt++) {
1851
1874
  try {
1852
1875
  const receipt = await route.client.start(operation);
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
2
2
  import { constants } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { spawn } from "node:child_process";
5
- import { createHash } from "node:crypto";
5
+ import { createHash, randomBytes } from "node:crypto";
6
6
  import { safeTreePath, STORAGE_LIMITS } from "./storage-wire.mjs";
7
7
  import { WorkspaceError } from "./contracts.mjs";
8
8
  const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
@@ -84,14 +84,17 @@ async function ensureAuthorityGitRepositoryLayout(repo) {
84
84
  }
85
85
  }
86
86
  async function durableJson(file, value) {
87
- const temporary = `${file}.next`;
87
+ const temporary = `${file}.${randomBytes(6).toString("hex")}.next`;
88
88
  const handle = await fs.open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
89
89
  try {
90
90
  await handle.writeFile(JSON.stringify(value));
91
91
  await handle.sync();
92
- } finally {
92
+ } catch (error) {
93
93
  await handle.close();
94
+ await fs.rm(temporary, { force: true });
95
+ throw error;
94
96
  }
97
+ await handle.close();
95
98
  await fs.rename(temporary, file);
96
99
  const dir = await fs.open(path.dirname(file), constants.O_RDONLY);
97
100
  try {
@@ -57,7 +57,7 @@ export declare class WorkspaceAuthority {
57
57
  content: string;
58
58
  bytes: number;
59
59
  }>;
60
- processes(identity: WorkspaceIdentity): Promise<{
60
+ processes(identity: WorkspaceIdentity): Promise<({
61
61
  workerLabel: string;
62
62
  operationId: string;
63
63
  userId: string;
@@ -80,7 +80,30 @@ export declare class WorkspaceAuthority {
80
80
  startedAt?: string;
81
81
  updatedAt?: string;
82
82
  completedAt?: string;
83
- }[]>;
83
+ } | {
84
+ operationId: string;
85
+ userId: string;
86
+ sessionId: string;
87
+ state: string;
88
+ pid: null;
89
+ pty: boolean;
90
+ lane: string;
91
+ stdoutBytes: number;
92
+ stderrBytes: number;
93
+ stdinClosed: boolean;
94
+ exitCode: null;
95
+ signal: null;
96
+ reason: string;
97
+ workerLabel: string;
98
+ artifactEnvironment?: boolean;
99
+ mutating: boolean;
100
+ payloadHash: string;
101
+ argv?: string[];
102
+ cwd?: string;
103
+ startedAt?: string;
104
+ updatedAt?: string;
105
+ completedAt?: string;
106
+ })[]>;
84
107
  fileHistoryDates(identity: WorkspaceIdentity, file: string): Promise<{
85
108
  createdAt: string | null;
86
109
  updatedAt: string;
@@ -194,6 +217,11 @@ export declare class WorkspaceAuthority {
194
217
  original?: undefined;
195
218
  modified?: undefined;
196
219
  }>;
220
+ /** Bench state is one in-memory object with several writers on different
221
+ * queues (checkout work, run receipts). Writes of its file are chained so
222
+ * they never overlap and the write that lands last always serializes the
223
+ * newest state; the chain covers only the write itself, never the work
224
+ * around it, so a run receipt does not wait for a publication. */
197
225
  private save;
198
226
  private owned;
199
227
  private serial;
@@ -223,11 +251,7 @@ export declare class WorkspaceAuthority {
223
251
  code: string;
224
252
  message: string;
225
253
  operationId?: string;
226
- commit? /** Storage for the account's outer repository, bound to no session. Its
227
- * presence selects the outer-repository model: the workspace root is one
228
- * repository whose files synchronize as a unit and whose project checkouts
229
- * are mirrored, not published, per branch. Without it linked checkouts keep
230
- * publishing per project, which the server workspace service relies on. */: string;
254
+ commit?: string;
231
255
  };
232
256
  mirroredHead?: string | null;
233
257
  runs: Record<string, {
@@ -15,6 +15,10 @@ export declare function readHostRegular(file: string, limit?: number): Promise<B
15
15
  * Restore only that directory skeleton after confirming the authority-owned
16
16
  * repository markers; refs, objects, indexes and configuration are untouched. */
17
17
  export declare function ensureAuthorityGitRepositoryLayout(repo: string): Promise<void>;
18
+ /** Writes `value` atomically: a private temporary file, fsync, rename, directory
19
+ * fsync. Concurrent writers of the same file each get their own temporary name,
20
+ * so neither fails on the other's; the rename that lands last wins, which is why
21
+ * callers that share in-memory state also serialize their writes. */
18
22
  export declare function durableJson(file: string, value: unknown): Promise<void>;
19
23
  /** All Git operates in authority-created repos; NEVER workbench .git/config/index.
20
24
  * hash-object --no-filters and update-index --index-info replace git add/checkout.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.173",
3
+ "version": "0.0.175",
4
4
  "type": "module",
5
5
  "main": "./dist/mjs/main.mjs",
6
6
  "module": "./dist/mjs/main.mjs",
@@ -21,7 +21,7 @@
21
21
  "r5d-worker": "dist/mjs/main.mjs"
22
22
  },
23
23
  "dependencies": {
24
- "@ricsam/r5d-api": "^0.0.173",
24
+ "@ricsam/r5d-api": "^0.0.175",
25
25
  "node-pty": "1.1.0",
26
26
  "zod": "^4.1.13",
27
27
  "picomatch": "^4.0.3"