@ricsam/r5d-worker 0.0.138 → 0.0.139

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.138",
3
+ "version": "0.0.139",
4
4
  "type": "commonjs"
5
5
  }
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.138" : "development"}`);
10
+ console.log(`r5d-worker ${true ? "0.0.139" : "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.138" : "development"
18
+ true ? "0.0.139" : "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.138",
3
+ "version": "0.0.139",
4
4
  "type": "module"
5
5
  }
@@ -35,6 +35,33 @@ const PersonalWorkspaceRequest = z.object({
35
35
  command: z.record(z.string(), z.unknown()),
36
36
  credentials: z.array(ExecutorCredential).max(32).optional()
37
37
  }).strict();
38
+ async function personalExecutorToken(root, grant) {
39
+ const identity = { installationId: grant.installationId, userId: grant.userId, resourceId: grant.resourceId, instanceId: grant.instanceId, workerId: grant.workerFence.resourceId };
40
+ const file = path.join(root, "executor-identity.json");
41
+ try {
42
+ const handle = await fs.open(file, "wx", 384);
43
+ try {
44
+ await handle.writeFile(canonicalJson({ version: 1, ...identity, token: randomBytes(32).toString("hex") }));
45
+ await handle.sync();
46
+ } finally {
47
+ await handle.close();
48
+ }
49
+ const directory = await fs.open(root, "r");
50
+ try {
51
+ await directory.sync();
52
+ } finally {
53
+ await directory.close();
54
+ }
55
+ } catch (error) {
56
+ if (error.code !== "EEXIST") throw error;
57
+ }
58
+ const stat = await fs.lstat(file);
59
+ if (stat.nlink !== 1) throw new Error("Personal executor identity must be a private single-link file");
60
+ const stored = z.object({ version: z.literal(1), installationId: RuntimeId, userId: RuntimeId, resourceId: RuntimeId, instanceId: RuntimeId, workerId: RuntimeId, token: z.string().regex(/^[a-f0-9]{64}$/) }).strict().parse(readPrivateJson(file));
61
+ const { token, version: _, ...bound } = stored;
62
+ if (canonicalJson(bound) !== canonicalJson(identity)) throw new Error("Personal executor identity changed; retain the original keeper");
63
+ return token;
64
+ }
38
65
  async function openPersonalWorkerRuntime(options) {
39
66
  const grant = PersonalWorkerGrant.parse(options.grant), root = path.resolve(options.root);
40
67
  privateDirectory(root);
@@ -43,7 +70,7 @@ async function openPersonalWorkerRuntime(options) {
43
70
  const credentialsRoot = path.join(root, "credentials"), workspaceRoot = path.join(root, "workspace", grant.installationId);
44
71
  privateDirectory(credentialsRoot);
45
72
  privateDirectory(workspaceRoot);
46
- const token = randomBytes(32).toString("hex"), controllerId = `controller-${createHash("sha256").update(grant.instanceId).digest("hex").slice(0, 24)}`;
73
+ const token = await personalExecutorToken(root, grant), controllerId = `controller-${createHash("sha256").update(grant.instanceId).digest("hex").slice(0, 24)}`;
47
74
  const daemon = await startHostExecutor({
48
75
  installationId: grant.installationId,
49
76
  workerId: grant.workerFence.resourceId,
@@ -62,16 +89,6 @@ async function openPersonalWorkerRuntime(options) {
62
89
  cwdRoots: [workspaceRoot],
63
90
  lanes: ["general", "utility", "control"],
64
91
  credentialIds: []
65
- },
66
- {
67
- id: grant.workerHello.instanceId,
68
- role: "adapter",
69
- ownerId: grant.workerHello.instanceId,
70
- tokenHash: tokenHash(token),
71
- userIds: [grant.userId],
72
- cwdRoots: [workspaceRoot],
73
- lanes: ["general", "utility", "control"],
74
- credentialIds: []
75
92
  }
76
93
  ]
77
94
  });
@@ -105,7 +122,7 @@ exec ${quote(executable)} ${quote(cli)} "$@"
105
122
  hello: grant.workerHello,
106
123
  timeoutMs: 1e4
107
124
  });
108
- let current = grant;
125
+ let current = grant, installed = false, retirementPending = false;
109
126
  const manifests = /* @__PURE__ */ new Map();
110
127
  const manifestFile = path.join(root, "workbenches.json");
111
128
  try {
@@ -118,12 +135,20 @@ exec ${quote(executable)} ${quote(cli)} "$@"
118
135
  if (error.code !== "ENOENT") throw error;
119
136
  }
120
137
  const artifactDigest = createHash("sha256").update(await fs.readFile(new URL(import.meta.url))).digest("hex");
138
+ async function retireObsoleteAdapters(retain) {
139
+ const status = await controller.status();
140
+ for (const adapter of status.adapters) {
141
+ if (adapter.state === "registered" && adapter.ownerId !== status.authority?.fence.ownerId && !retain.includes(adapter.ownerId))
142
+ await controller.retireAdapter(adapter.principalId);
143
+ }
144
+ }
121
145
  async function renew(value) {
122
146
  const next = PersonalWorkerGrant.parse(value);
123
147
  if (next.instanceId !== grant.instanceId || next.installationId !== grant.installationId || next.resourceId !== grant.resourceId || next.userId !== grant.userId || next.workerFence.installationId !== grant.installationId || next.workerHello.installationId !== grant.installationId || next.workerHello.role !== "worker-adapter" || next.workerFence.resourceId !== grant.workerFence.resourceId || next.workerHello.instanceId !== next.workerFence.ownerId)
124
148
  throw new Error("Personal worker resource identity changed; inspect retained keeper before rebinding");
125
149
  let successor = client;
126
- if (canonicalJson(next.workerHello) !== canonicalJson(current.workerHello)) {
150
+ if (!installed || canonicalJson(next.workerHello) !== canonicalJson(current.workerHello)) {
151
+ await retireObsoleteAdapters([next.workerHello.instanceId]);
127
152
  await controller.registerAdapter({
128
153
  installationId: grant.installationId,
129
154
  workerId: grant.workerFence.resourceId,
@@ -149,8 +174,14 @@ exec ${quote(executable)} ${quote(cli)} "$@"
149
174
  });
150
175
  }
151
176
  await controller.setAuthority(next.workerFence, next.leaseExpiresAt);
177
+ if (!installed || canonicalJson(next.workerHello) !== canonicalJson(current.workerHello)) retirementPending = true;
152
178
  client = successor;
153
179
  current = next;
180
+ installed = true;
181
+ if (retirementPending) {
182
+ await retireObsoleteAdapters([next.workerHello.instanceId]);
183
+ retirementPending = false;
184
+ }
154
185
  }
155
186
  await renew(grant);
156
187
  const authority = await WorkspaceAuthority.open({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.138",
3
+ "version": "0.0.139",
4
4
  "type": "module",
5
5
  "main": "./dist/mjs/main.mjs",
6
6
  "module": "./dist/mjs/main.mjs",
@@ -21,8 +21,8 @@
21
21
  "r5d-worker": "dist/mjs/main.mjs"
22
22
  },
23
23
  "dependencies": {
24
- "@ricsam/r5d-api": "^0.0.138",
25
- "@ricsam/r5dctl": "0.0.138",
24
+ "@ricsam/r5d-api": "^0.0.139",
25
+ "@ricsam/r5dctl": "0.0.139",
26
26
  "node-pty": "1.1.0",
27
27
  "zod": "^4.1.13",
28
28
  "picomatch": "^4.0.3"