@ricsam/r5d-worker 0.0.161 → 0.0.163

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.161",
3
+ "version": "0.0.163",
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.161";
20608
+ if (true) return "0.0.163";
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.161" : "development"}`);
10
+ console.log(`r5d-worker ${true ? "0.0.163" : "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.161" : "development"
18
+ true ? "0.0.163" : "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.161",
3
+ "version": "0.0.163",
4
4
  "type": "module"
5
5
  }
@@ -103,7 +103,7 @@ async function startPersonalWorker(options, version) {
103
103
  platform: process.platform,
104
104
  arch: process.arch,
105
105
  hostname: os.hostname(),
106
- capabilities: { updateClis: true, durableWorkspace: true }
106
+ capabilities: { updateClis: true, durableWorkspace: true, outerWorkspace: true }
107
107
  });
108
108
  const grant = PersonalWorkerGrant.parse(
109
109
  await request("/api/personal/resources/register", { kind: "worker", ...identity, label: options.label, metadata: metadata() })
@@ -116,7 +116,7 @@ async function startPersonalWorker(options, version) {
116
116
  try {
117
117
  const response = await request(`${endpoint}/storage`, {
118
118
  instanceId: identity.instanceId,
119
- request: { sessionId, envelope: { protocol: 1, installationId: grant.installationId, request: storageRequest } }
119
+ request: { ...sessionId === null ? { scope: "account" } : { sessionId }, envelope: { protocol: 1, installationId: grant.installationId, request: storageRequest } }
120
120
  });
121
121
  if (!response || !("result" in response)) throw new Error("Invalid scoped storage response");
122
122
  return response.result;
@@ -0,0 +1,47 @@
1
+ const CONFLICT_PUBLICATION_CODES = /* @__PURE__ */ new Set([
2
+ "conflict",
3
+ "sync_conflict",
4
+ "workbench_conflict",
5
+ "workbench_operation_in_progress"
6
+ ]);
7
+ const TRANSIENT_PUBLICATION_CODES = /* @__PURE__ */ new Set([
8
+ "not_initialized",
9
+ "apply_refused",
10
+ "mirror_advanced",
11
+ "authority_closed",
12
+ "busy"
13
+ ]);
14
+ const BLOCKED_PUBLICATION_CODES = /* @__PURE__ */ new Set([
15
+ "secret_or_runtime_path",
16
+ "secret_content",
17
+ "unsafe_file",
18
+ "unsafe_git",
19
+ "unsafe_path",
20
+ "unsafe_tree",
21
+ "too_large"
22
+ ]);
23
+ const PUBLICATION_OBSERVATION_THRESHOLDS = /* @__PURE__ */ new Map([["unsafe_file", 2]]);
24
+ const OPERATION_IN_PROGRESS_CODE = "workbench_operation_in_progress";
25
+ const ABANDONED_OPERATION_OBSERVATIONS = 2;
26
+ const STUCK_OPERATION_OBSERVATIONS = 30;
27
+ function publicationRefusalOutcome(code) {
28
+ if (CONFLICT_PUBLICATION_CODES.has(code)) return "conflict_blocked";
29
+ if (BLOCKED_PUBLICATION_CODES.has(code)) return "large_diff_blocked";
30
+ return "failed";
31
+ }
32
+ function publicationRefusalElects(code, observations, context = {}) {
33
+ if (code === OPERATION_IN_PROGRESS_CODE)
34
+ return context.workbenchHasLiveRun ? observations >= STUCK_OPERATION_OBSERVATIONS : observations >= ABANDONED_OPERATION_OBSERVATIONS;
35
+ return observations >= (PUBLICATION_OBSERVATION_THRESHOLDS.get(code) ?? 1);
36
+ }
37
+ export {
38
+ ABANDONED_OPERATION_OBSERVATIONS,
39
+ BLOCKED_PUBLICATION_CODES,
40
+ CONFLICT_PUBLICATION_CODES,
41
+ OPERATION_IN_PROGRESS_CODE,
42
+ PUBLICATION_OBSERVATION_THRESHOLDS,
43
+ STUCK_OPERATION_OBSERVATIONS,
44
+ TRANSIENT_PUBLICATION_CODES,
45
+ publicationRefusalElects,
46
+ publicationRefusalOutcome
47
+ };
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { publicationRefusalElects, publicationRefusalOutcome, TRANSIENT_PUBLICATION_CODES } from "./publication-refusal.mjs";
2
3
  import { PersonalTcpManager } from "./tcp.mjs";
3
4
  import { createHash, randomBytes, randomUUID } from "node:crypto";
4
5
  import { promises as fs } from "node:fs";
@@ -11,6 +12,7 @@ import { EXECUTOR_CAPABILITY, ExecutorCredential } from "../runtime/protocol.mjs
11
12
  import { privateDirectory, readPrivateJson } from "../runtime/storage.mjs";
12
13
  import { WorkspaceAuthority } from "../runtime/workspace/authority.mjs";
13
14
  import { ApprovedWorkbench, WorkspaceError } from "../runtime/workspace/contracts.mjs";
15
+ import { OuterSnapshotRefusal } from "../runtime/workspace/outer.mjs";
14
16
  import { WorkspaceStorageClient } from "../runtime/workspace/storage-client.mjs";
15
17
  import { BranchName, GitOid, StorageId } from "../runtime/workspace/storage-wire.mjs";
16
18
  const PersonalWorkerGrant = z.object({
@@ -203,7 +205,11 @@ exec ${quote(executable)} ${quote(cli)} "$@"
203
205
  config: { installationId: grant.installationId, root: authorityRoot, workspaceRoot, dynamicWorkbenches: true, workbenches: [] },
204
206
  executor: async () => ({ client, workerFence: current.workerFence }),
205
207
  resolveWorkbench: async (identity) => identity.userId === grant.userId ? manifests.get(routes.get(identity.sessionId) ?? "") ?? null : null,
206
- storage: async (identity) => new WorkspaceStorageClient(grant.installationId, options.storage(identity.sessionId), async () => current.workerFence)
208
+ storage: async (identity) => new WorkspaceStorageClient(grant.installationId, options.storage(identity.sessionId), async () => current.workerFence),
209
+ accountStorage: async (userId) => {
210
+ if (userId !== grant.userId) throw new Error("Wrong personal worker principal");
211
+ return new WorkspaceStorageClient(grant.installationId, options.storage(null), async () => current.workerFence);
212
+ }
207
213
  });
208
214
  async function approve(input) {
209
215
  const cwd = input.rootProfile === "account" ? workspaceRoot : input.namespace && input.projectName ? path.join(workspaceRoot, "projects", input.namespace, input.projectName, ...input.branch.split("/")) : path.join(workspaceRoot, "workbenches", input.id);
@@ -253,62 +259,46 @@ exec ${quote(executable)} ${quote(cli)} "$@"
253
259
  }
254
260
  }
255
261
  }
256
- const blockedPublicationCodes = /* @__PURE__ */ new Set([
257
- "secret_or_runtime_path",
258
- "secret_content",
259
- "unsafe_file",
260
- "unsafe_git",
261
- "unsafe_path",
262
- "unsafe_tree",
263
- "too_large"
264
- ]);
265
- const conflictPublicationCodes = /* @__PURE__ */ new Set(["conflict", "workbench_conflict"]);
266
262
  const failedPublicationAttempts = /* @__PURE__ */ new Map();
267
263
  let publication = null, closing = false;
268
264
  const synchronize = () => {
269
265
  if (publication) return publication;
270
266
  const next = (async () => {
271
267
  const results = [];
272
- for (const [workbenchId, sessionId] of publicationSessions) {
273
- if (closing) break;
274
- const manifest = manifests.get(workbenchId);
275
- if (!manifest || manifest.rootProfile !== "project") continue;
276
- try {
277
- const result = await authority.publish({ userId: grant.userId, sessionId });
278
- failedPublicationAttempts.delete(workbenchId);
279
- results.push({ workbenchId, ...result });
280
- } catch (error) {
281
- const code = error instanceof WorkspaceError ? error.code : "publication_failed";
282
- results.push({ workbenchId, error: code });
283
- if (code !== "not_initialized" && options.publicationReport) {
284
- let startingHead = null;
285
- try {
286
- const status = await authority.status({ userId: grant.userId, sessionId });
287
- startingHead = status.head;
288
- } catch {
289
- }
290
- const outcome = conflictPublicationCodes.has(code) ? "conflict_blocked" : blockedPublicationCodes.has(code) ? "large_diff_blocked" : "failed";
291
- const fingerprint = canonicalJson({ code, startingHead, outcome });
292
- let attempt = failedPublicationAttempts.get(workbenchId);
293
- if (!attempt || attempt.fingerprint !== fingerprint) {
294
- attempt = { fingerprint, attemptId: randomUUID(), observations: 1 };
295
- failedPublicationAttempts.set(workbenchId, attempt);
296
- } else {
297
- attempt.observations++;
298
- }
299
- if (code === "unsafe_file" && attempt.observations < 2) continue;
268
+ try {
269
+ const outer = await authority.synchronizeOuter(grant.userId);
270
+ failedPublicationAttempts.delete("workspace");
271
+ results.push({ workbenchId: "workspace", ...outer });
272
+ } catch (error) {
273
+ const code = error instanceof WorkspaceError ? error.code : "publication_failed";
274
+ results.push({ workbenchId: "workspace", error: code });
275
+ if (!TRANSIENT_PUBLICATION_CODES.has(code) && options.publicationReport) {
276
+ let startingHead = null;
277
+ try {
278
+ startingHead = (await authority.outerStatus(grant.userId)).publishedHead;
279
+ } catch {
280
+ }
281
+ const outcome = publicationRefusalOutcome(code);
282
+ const paths = error instanceof OuterSnapshotRefusal ? error.paths : [];
283
+ const fingerprint = canonicalJson({ code, startingHead, outcome, paths });
284
+ let attempt = failedPublicationAttempts.get("workspace");
285
+ if (!attempt || attempt.fingerprint !== fingerprint) {
286
+ attempt = { fingerprint, attemptId: randomUUID(), observations: 1 };
287
+ failedPublicationAttempts.set("workspace", attempt);
288
+ } else {
289
+ attempt.observations++;
290
+ }
291
+ if (publicationRefusalElects(code, attempt.observations)) {
300
292
  try {
301
293
  await options.publicationReport(WorkspacePublicationReport.parse({
302
294
  protocol: 1,
303
295
  attemptId: attempt.attemptId,
304
- sessionId,
305
- workbenchId,
306
- repositoryId: manifest.repositoryId,
307
- branch: manifest.branch,
296
+ scope: "workspace",
308
297
  outcome,
309
298
  startingHead,
310
299
  diffSizeBytes: outcome === "large_diff_blocked" && code === "too_large" ? 5 * 1024 * 1024 + 1 : 0,
311
- error: code
300
+ error: code,
301
+ ...paths.length ? { paths } : {}
312
302
  }));
313
303
  } catch (reportError) {
314
304
  process.stderr.write(`[r5d-worker] publication report deferred: ${reportError instanceof WorkspaceError ? reportError.code : "publication_report_failed"}
@@ -317,13 +307,24 @@ exec ${quote(executable)} ${quote(cli)} "$@"
317
307
  }
318
308
  }
319
309
  }
310
+ for (const [workbenchId, sessionId] of publicationSessions) {
311
+ if (closing) break;
312
+ const manifest = manifests.get(workbenchId);
313
+ if (!manifest || manifest.rootProfile !== "project") continue;
314
+ try {
315
+ results.push({ workbenchId, ...await authority.mirror({ userId: grant.userId, sessionId }) });
316
+ } catch (error) {
317
+ results.push({ workbenchId, error: error instanceof WorkspaceError ? error.code : "mirror_failed" });
318
+ }
319
+ }
320
320
  return results;
321
321
  })();
322
322
  publication = next;
323
323
  void next.then(
324
324
  (results) => {
325
- if (results.some((result) => result.error && result.error !== "not_initialized"))
326
- process.stderr.write(`[r5d-worker] periodic workspace publication deferred: ${results.filter((result) => result.error).map((result) => `${result.workbenchId}:${result.error}`).join(", ")}
325
+ const deferred = results.filter((result) => result.error && !TRANSIENT_PUBLICATION_CODES.has(result.error));
326
+ if (deferred.length)
327
+ process.stderr.write(`[r5d-worker] periodic workspace publication deferred: ${deferred.map((result) => `${result.workbenchId}:${result.error}`).join(", ")}
327
328
  `);
328
329
  if (publication === next) publication = null;
329
330
  },