@ricsam/r5d-worker 0.0.156 → 0.0.158
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.
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/internal-r5dctl.cjs +1 -1
- package/dist/mjs/main.mjs +2 -2
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/personal/client.mjs +3 -0
- package/dist/mjs/personal/runtime.mjs +57 -7
- package/dist/mjs/runtime/workspace/authority.mjs +27 -9
- package/dist/mjs/runtime/workspace/files.mjs +20 -10
- package/dist/types/personal/runtime.d.ts +5 -0
- package/dist/types/runtime/workspace/authority.d.ts +6 -3
- package/dist/types/runtime/workspace/files.d.ts +1 -1
- package/package.json +2 -2
package/dist/cjs/package.json
CHANGED
|
@@ -20605,7 +20605,7 @@ function resolveEntrypointPath(entrypoint) {
|
|
|
20605
20605
|
}
|
|
20606
20606
|
}
|
|
20607
20607
|
function getR5dctlVersion() {
|
|
20608
|
-
if (true) return "0.0.
|
|
20608
|
+
if (true) return "0.0.158";
|
|
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.
|
|
10
|
+
console.log(`r5d-worker ${true ? "0.0.158" : "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.
|
|
18
|
+
true ? "0.0.158" : "development"
|
|
19
19
|
);
|
|
20
20
|
console.log(`Worker connected: ${runtime.resourceId}`);
|
|
21
21
|
let closing = false;
|
package/dist/mjs/package.json
CHANGED
|
@@ -125,6 +125,9 @@ async function startPersonalWorker(options, version) {
|
|
|
125
125
|
throw new WorkspaceError(error.code, "Scoped storage rejected the request; retain its original operation identity", error.rejectedBeforeAdmission);
|
|
126
126
|
throw error;
|
|
127
127
|
}
|
|
128
|
+
},
|
|
129
|
+
publicationReport: async (report) => {
|
|
130
|
+
await request(`${endpoint}/publication`, { instanceId: identity.instanceId, report });
|
|
128
131
|
}
|
|
129
132
|
});
|
|
130
133
|
const ledgerFile = path.join(root, "relay.sqlite");
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { PersonalTcpManager } from "./tcp.mjs";
|
|
3
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
4
4
|
import { promises as fs } from "node:fs";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { RuntimeHello, RuntimeId, OwnershipFence, OperationEnvelope, canonicalJson } from "@ricsam/r5d-api/runtime-protocol";
|
|
6
|
+
import { RuntimeHello, RuntimeId, OwnershipFence, OperationEnvelope, WorkspacePublicationReport, canonicalJson } from "@ricsam/r5d-api/runtime-protocol";
|
|
7
7
|
import { startHostExecutor } from "../runtime/daemon.mjs";
|
|
8
8
|
import { HostExecutorClient } from "../runtime/client.mjs";
|
|
9
9
|
import { tokenHash } from "../runtime/executor.mjs";
|
|
@@ -42,7 +42,8 @@ const PersonalWorkspaceRequest = z.object({
|
|
|
42
42
|
sharedWorkbenches: z.array(Workbench).max(32).optional(),
|
|
43
43
|
kind: z.enum(["inspect", "terminal", "product", "agent", "tcp"]),
|
|
44
44
|
command: z.record(z.string(), z.unknown()),
|
|
45
|
-
credentials: z.array(ExecutorCredential).max(32).optional()
|
|
45
|
+
credentials: z.array(ExecutorCredential).max(32).optional(),
|
|
46
|
+
remediation: z.literal(true).optional()
|
|
46
47
|
}).strict();
|
|
47
48
|
async function personalExecutorToken(root, grant) {
|
|
48
49
|
const identity = { installationId: grant.installationId, userId: grant.userId, resourceId: grant.resourceId, instanceId: grant.instanceId, workerId: grant.workerFence.resourceId };
|
|
@@ -252,6 +253,18 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
252
253
|
}
|
|
253
254
|
}
|
|
254
255
|
}
|
|
256
|
+
const blockedPublicationCodes = /* @__PURE__ */ new Set([
|
|
257
|
+
"ignore_policy",
|
|
258
|
+
"secret_or_runtime_path",
|
|
259
|
+
"secret_content",
|
|
260
|
+
"unsafe_file",
|
|
261
|
+
"unsafe_git",
|
|
262
|
+
"unsafe_path",
|
|
263
|
+
"unsafe_tree",
|
|
264
|
+
"too_large"
|
|
265
|
+
]);
|
|
266
|
+
const conflictPublicationCodes = /* @__PURE__ */ new Set(["conflict", "workbench_conflict"]);
|
|
267
|
+
const failedPublicationAttempts = /* @__PURE__ */ new Map();
|
|
255
268
|
let publication = null, closing = false;
|
|
256
269
|
const synchronize = () => {
|
|
257
270
|
if (publication) return publication;
|
|
@@ -263,10 +276,43 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
263
276
|
if (!manifest || manifest.rootProfile !== "project") continue;
|
|
264
277
|
try {
|
|
265
278
|
const result = await authority.publish({ userId: grant.userId, sessionId });
|
|
279
|
+
failedPublicationAttempts.delete(workbenchId);
|
|
266
280
|
results.push({ workbenchId, ...result });
|
|
267
281
|
} catch (error) {
|
|
268
282
|
const code = error instanceof WorkspaceError ? error.code : "publication_failed";
|
|
269
283
|
results.push({ workbenchId, error: code });
|
|
284
|
+
if (code !== "not_initialized" && options.publicationReport) {
|
|
285
|
+
let startingHead = null;
|
|
286
|
+
try {
|
|
287
|
+
const status = await authority.status({ userId: grant.userId, sessionId });
|
|
288
|
+
startingHead = status.head;
|
|
289
|
+
} catch {
|
|
290
|
+
}
|
|
291
|
+
const outcome = conflictPublicationCodes.has(code) ? "conflict_blocked" : blockedPublicationCodes.has(code) ? "large_diff_blocked" : "failed";
|
|
292
|
+
const fingerprint = canonicalJson({ code, startingHead, outcome });
|
|
293
|
+
let attempt = failedPublicationAttempts.get(workbenchId);
|
|
294
|
+
if (!attempt || attempt.fingerprint !== fingerprint) {
|
|
295
|
+
attempt = { fingerprint, attemptId: randomUUID() };
|
|
296
|
+
failedPublicationAttempts.set(workbenchId, attempt);
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
await options.publicationReport(WorkspacePublicationReport.parse({
|
|
300
|
+
protocol: 1,
|
|
301
|
+
attemptId: attempt.attemptId,
|
|
302
|
+
sessionId,
|
|
303
|
+
workbenchId,
|
|
304
|
+
repositoryId: manifest.repositoryId,
|
|
305
|
+
branch: manifest.branch,
|
|
306
|
+
outcome,
|
|
307
|
+
startingHead,
|
|
308
|
+
diffSizeBytes: outcome === "large_diff_blocked" && code === "too_large" ? 5 * 1024 * 1024 + 1 : 0,
|
|
309
|
+
error: code
|
|
310
|
+
}));
|
|
311
|
+
} catch (reportError) {
|
|
312
|
+
process.stderr.write(`[r5d-worker] publication report deferred: ${reportError instanceof WorkspaceError ? reportError.code : "publication_report_failed"}
|
|
313
|
+
`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
270
316
|
}
|
|
271
317
|
}
|
|
272
318
|
return results;
|
|
@@ -335,7 +381,8 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
335
381
|
...command.method === "open" ? { interactive: true, pty: { cols: command.cols, rows: command.rows } } : {},
|
|
336
382
|
credentials: (request.credentials ?? []).map(({ id, generation }) => ({ id, generation }))
|
|
337
383
|
}
|
|
338
|
-
})
|
|
384
|
+
}),
|
|
385
|
+
request.remediation === true
|
|
339
386
|
),
|
|
340
387
|
workerLabel: grant.label ?? grant.resourceId
|
|
341
388
|
};
|
|
@@ -350,7 +397,7 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
350
397
|
if (request.kind === "product") {
|
|
351
398
|
if (command.method === "fileWrite") {
|
|
352
399
|
await authority.ensureHydrated(identity);
|
|
353
|
-
return authority.fileWrite(identity, command);
|
|
400
|
+
return authority.fileWrite(identity, command, request.remediation === true);
|
|
354
401
|
}
|
|
355
402
|
if (command.method === "materializeArtifact") return authority.materializeArtifact(identity, command);
|
|
356
403
|
if (command.method === "seed") return authority.seedReadme(identity, command.readme);
|
|
@@ -362,7 +409,10 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
362
409
|
if (command.method === "commit") return authority.commit(identity, command);
|
|
363
410
|
if (command.method === "push") return authority.push(identity, command);
|
|
364
411
|
if (command.method === "reset") return authority.reset(identity, command);
|
|
365
|
-
if (command.method === "publish") return authority.publish(identity
|
|
412
|
+
if (command.method === "publish") return authority.publish(identity, {
|
|
413
|
+
allowLargeDiff: request.remediation === true && command.allowLargeDiff === true,
|
|
414
|
+
allowBlockedConflict: request.remediation === true
|
|
415
|
+
});
|
|
366
416
|
if (command.method === "reconcilePublication") return authority.reconcilePublication(identity);
|
|
367
417
|
}
|
|
368
418
|
if (request.kind === "agent") {
|
|
@@ -387,7 +437,7 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
387
437
|
cwd: manifests.get(routes.get(identity.sessionId)).cwd,
|
|
388
438
|
credentials: (request.credentials ?? []).map(({ id, generation }) => ({ id, generation }))
|
|
389
439
|
}
|
|
390
|
-
});
|
|
440
|
+
}, request.remediation === true);
|
|
391
441
|
}
|
|
392
442
|
if (command.method === "poll")
|
|
393
443
|
return authority.poll(identity, run(command.run.operationId), command.stdoutOffset, command.stderrOffset, command.maxBytes);
|
|
@@ -866,15 +866,18 @@ class WorkspaceAuthority {
|
|
|
866
866
|
return { state: "completed", result: receipt.result };
|
|
867
867
|
});
|
|
868
868
|
}
|
|
869
|
-
async fileWrite(identity, raw) {
|
|
869
|
+
async fileWrite(identity, raw, allowBlockedConflict = false) {
|
|
870
870
|
const input = WorkspaceFileWrite.parse(raw), bytes = Buffer.from(input.base64, "base64");
|
|
871
871
|
if (bytes.toString("base64") !== input.base64 || bytes.length > 768 * 1024)
|
|
872
872
|
throw new WorkspaceError("invalid_input", "Expected canonical base64 for at most 768 KiB", true);
|
|
873
873
|
const b = await this.bench(identity), target = input.hostPath ? this.resolveHostPath(input.path) : path.join(b.config.cwd, input.path);
|
|
874
874
|
const fingerprint = { ...input, userId: identity.userId, sessionId: identity.sessionId };
|
|
875
875
|
let mode = 420;
|
|
876
|
+
let retainedBlock = null;
|
|
876
877
|
const check = async () => {
|
|
877
|
-
if (b.state.blocked
|
|
878
|
+
if (b.state.blocked && !(allowBlockedConflict && (b.state.blocked.code === "conflict" || b.state.blocked.code === "workbench_conflict")))
|
|
879
|
+
throw new WorkspaceError(b.state.blocked.code, b.state.blocked.message);
|
|
880
|
+
retainedBlock = b.state.blocked;
|
|
878
881
|
if (!b.state.initialized) throw new WorkspaceError("uninitialized", "Hydrate this workspace before writing files");
|
|
879
882
|
let current = input.hostPath ? path.parse(target).root : b.config.cwd;
|
|
880
883
|
const parentParts = (input.hostPath ? target.slice(current.length) : input.path).split(path.sep).filter(Boolean).slice(0, -1);
|
|
@@ -936,7 +939,7 @@ class WorkspaceAuthority {
|
|
|
936
939
|
};
|
|
937
940
|
}, check);
|
|
938
941
|
if (b.state.blocked?.code === "file_write_unknown" && b.state.blocked.operationId === input.id) {
|
|
939
|
-
b.state.blocked =
|
|
942
|
+
b.state.blocked = retainedBlock;
|
|
940
943
|
await this.save(b);
|
|
941
944
|
}
|
|
942
945
|
return result;
|
|
@@ -1167,15 +1170,16 @@ ${DEFAULT_WORKSPACE_IGNORE}`, { mode: 384 });
|
|
|
1167
1170
|
return { ...result, reset: true, retained: true };
|
|
1168
1171
|
}));
|
|
1169
1172
|
}
|
|
1170
|
-
async publish(identity) {
|
|
1173
|
+
async publish(identity, options = {}) {
|
|
1171
1174
|
const b = await this.bench(identity);
|
|
1172
1175
|
if (b.config.rootProfile === "account") return { head: "", unchanged: true };
|
|
1173
1176
|
return this.serial(b, async () => {
|
|
1174
|
-
|
|
1175
|
-
|
|
1177
|
+
const conflictBlocked = b.state.blocked?.code === "conflict" || b.state.blocked?.code === "workbench_conflict";
|
|
1178
|
+
await this.assertAvailable(b, options.allowBlockedConflict === true && conflictBlocked);
|
|
1179
|
+
return this.publishIdle(b, identity, "Destination workspace snapshot", options.allowLargeDiff === true);
|
|
1176
1180
|
});
|
|
1177
1181
|
}
|
|
1178
|
-
async publishIdle(b, identity, message = "Destination workspace snapshot") {
|
|
1182
|
+
async publishIdle(b, identity, message = "Destination workspace snapshot", allowLargeDiff = false) {
|
|
1179
1183
|
if (!b.state.initialized) throw new WorkspaceError("not_initialized", "Hydrate or explicitly seed first");
|
|
1180
1184
|
const storage = await this.storage(b, identity);
|
|
1181
1185
|
const latest = await storage.read({
|
|
@@ -1194,6 +1198,19 @@ ${DEFAULT_WORKSPACE_IGNORE}`, { mode: 384 });
|
|
|
1194
1198
|
const tree = await snapshotTree(b.repo, b.config.cwd, b.state.head);
|
|
1195
1199
|
if (b.state.head && tree === (await git(b.repo, ["rev-parse", `${b.state.head}^{tree}`])).toString().trim())
|
|
1196
1200
|
return { head: b.state.head, unchanged: true };
|
|
1201
|
+
if (!allowLargeDiff) {
|
|
1202
|
+
const base = b.state.head ?? (await git(b.repo, ["hash-object", "-t", "tree", "--stdin"], "")).toString().trim();
|
|
1203
|
+
let diff;
|
|
1204
|
+
try {
|
|
1205
|
+
diff = await git(b.repo, ["diff", "--binary", "--no-ext-diff", "--no-textconv", base, tree]);
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
if (error instanceof WorkspaceError && error.code === "git_failed")
|
|
1208
|
+
throw new WorkspaceError("too_large", "Source diff exceeds the 5242880-byte automatic publication limit");
|
|
1209
|
+
throw error;
|
|
1210
|
+
}
|
|
1211
|
+
if (diff.length > 5 * 1024 * 1024)
|
|
1212
|
+
throw new WorkspaceError("too_large", "Source diff exceeds the 5242880-byte automatic publication limit");
|
|
1213
|
+
}
|
|
1197
1214
|
const commit = (await git(b.repo, ["commit-tree", tree, ...b.state.head ? ["-p", b.state.head] : []], `${message}
|
|
1198
1215
|
`)).toString().trim();
|
|
1199
1216
|
await git(b.repo, ["update-ref", this.canonicalRef(b), commit]);
|
|
@@ -1312,11 +1329,12 @@ ${DEFAULT_WORKSPACE_IGNORE}`, { mode: 384 });
|
|
|
1312
1329
|
return this.serial(b, () => new SessionArtifactStore(this.config.workspaceRoot ?? this.config.root).materialize(identity, input));
|
|
1313
1330
|
}
|
|
1314
1331
|
/** Default arbitrary shell is mutating. Exact trusted config approval is the sole exemption. */
|
|
1315
|
-
async start(identity, input) {
|
|
1332
|
+
async start(identity, input, allowBlockedConflict = false) {
|
|
1316
1333
|
const b = await this.bench(identity);
|
|
1317
1334
|
const operation = OperationEnvelope.parse(JSON.parse(canonicalJson(input)));
|
|
1318
1335
|
return this.serial(b, async () => {
|
|
1319
|
-
|
|
1336
|
+
const conflictBlocked = b.state.blocked?.code === "conflict" || b.state.blocked?.code === "workbench_conflict";
|
|
1337
|
+
if (!b.state.initialized || b.state.blocked && !(allowBlockedConflict && conflictBlocked))
|
|
1320
1338
|
throw new WorkspaceError("workbench_blocked", b.state.blocked?.message ?? "Initialize workbench first");
|
|
1321
1339
|
if (operation.installationId !== this.config.installationId || operation.userId !== identity.userId || operation.sessionId !== identity.sessionId || operation.kind !== "host.shell")
|
|
1322
1340
|
throw new WorkspaceError("forbidden", "Shell identity does not match approved workbench");
|
|
@@ -254,7 +254,13 @@ async function materializeTree(repo, destination, entries, limit, message) {
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
async function verifyIgnore(repo, cwd) {
|
|
257
|
-
|
|
257
|
+
try {
|
|
258
|
+
await readRegular(path.join(cwd, ".gitignore"), 64 * 1024);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (error.code === "ENOENT")
|
|
261
|
+
throw new WorkspaceError("ignore_policy", "Restore an effective .gitignore before publishing source");
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
258
264
|
const probes = [
|
|
259
265
|
".env",
|
|
260
266
|
".env.local",
|
|
@@ -276,7 +282,7 @@ async function verifyIgnore(repo, cwd) {
|
|
|
276
282
|
if (ignored.length !== probes.length || probes.some((p) => !ignored.includes(p)))
|
|
277
283
|
throw new WorkspaceError("ignore_policy", "Restore effective .gitignore rules for secrets, dependency, cache and runtime roots");
|
|
278
284
|
}
|
|
279
|
-
async function snapshotTree(repo, cwd, canonicalHead) {
|
|
285
|
+
async function snapshotTree(repo, cwd, canonicalHead, maxBytes = 128 * 1024 * 1024) {
|
|
280
286
|
const metadata = path.join(cwd, ".git");
|
|
281
287
|
let workbenchIndex = null;
|
|
282
288
|
if (await fs.lstat(metadata).then(
|
|
@@ -328,15 +334,16 @@ async function snapshotTree(repo, cwd, canonicalHead) {
|
|
|
328
334
|
}
|
|
329
335
|
const canonical = canonicalHead ? await selectedTree(repo, canonicalHead) : [];
|
|
330
336
|
const indexedGitlinks = /* @__PURE__ */ new Map();
|
|
337
|
+
const indexedFiles = /* @__PURE__ */ new Set();
|
|
331
338
|
if (workbenchIndex) {
|
|
332
339
|
const indexed = await git(repo, ["ls-files", "--stage", "-z"], void 0, workbenchIndex);
|
|
333
340
|
if (!Buffer.from(indexed.toString()).equals(indexed)) throw new WorkspaceError("unsafe_path", "Non-UTF8 index names unsupported");
|
|
334
341
|
for (const record of indexed.toString().split("\0").filter(Boolean)) {
|
|
335
|
-
const match = /^160000 ([0-9a-f]{40}) 0\t(.+)$/.exec(record);
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
342
|
+
const match = /^(100644|100755|160000) ([0-9a-f]{40}) 0\t(.+)$/.exec(record);
|
|
343
|
+
if (!match) throw new WorkspaceError("unsafe_tree", "Malformed workbench index entry");
|
|
344
|
+
sourcePath(match[3]);
|
|
345
|
+
if (match[1] === "160000") indexedGitlinks.set(match[3], match[2]);
|
|
346
|
+
else indexedFiles.add(match[3]);
|
|
340
347
|
}
|
|
341
348
|
}
|
|
342
349
|
const canonicalGitlinks = new Map(canonical.filter((entry) => entry.mode === "160000").map((entry) => [entry.file, entry.oid]));
|
|
@@ -363,7 +370,6 @@ async function snapshotTree(repo, cwd, canonicalHead) {
|
|
|
363
370
|
if (nested) gitlinks.delete(file);
|
|
364
371
|
else gitlinkRoots.push(file);
|
|
365
372
|
}
|
|
366
|
-
await verifyIgnore(repo, cwd);
|
|
367
373
|
await git(repo, ["read-tree", "--empty"]);
|
|
368
374
|
const listing = await git(repo, [
|
|
369
375
|
`--work-tree=${cwd}`,
|
|
@@ -385,7 +391,7 @@ async function snapshotTree(repo, cwd, canonicalHead) {
|
|
|
385
391
|
current = parent;
|
|
386
392
|
}
|
|
387
393
|
};
|
|
388
|
-
const tracked = canonical.filter((entry) => entry.mode !== "160000").map((entry) => entry.file);
|
|
394
|
+
const tracked = [.../* @__PURE__ */ new Set([...canonical.filter((entry) => entry.mode !== "160000").map((entry) => entry.file), ...indexedFiles])];
|
|
389
395
|
const existingTracked = [];
|
|
390
396
|
for (const file of tracked) {
|
|
391
397
|
const exists = await fs.lstat(path.join(cwd, file)).then(() => true, (error) => {
|
|
@@ -395,6 +401,8 @@ async function snapshotTree(repo, cwd, canonicalHead) {
|
|
|
395
401
|
if (exists) existingTracked.push(file);
|
|
396
402
|
}
|
|
397
403
|
const files = [.../* @__PURE__ */ new Set([...listing.toString().split("\0").filter(Boolean), ...existingTracked])].filter((file) => !beneathGitlink(file)).sort();
|
|
404
|
+
if (files.some((file) => file.split("/").some((part) => part.toLowerCase() === "node_modules")))
|
|
405
|
+
throw new WorkspaceError("too_large", "An unignored dependency tree exceeds the automatic publication limit");
|
|
398
406
|
if (files.length + gitlinks.size > STORAGE_LIMITS.treeEntries) throw new WorkspaceError("too_large", "Too many source files");
|
|
399
407
|
let size = 0;
|
|
400
408
|
const records = [...gitlinks].map(([file, oid]) => ({
|
|
@@ -405,12 +413,14 @@ async function snapshotTree(repo, cwd, canonicalHead) {
|
|
|
405
413
|
sourcePath(file);
|
|
406
414
|
const bytes = await readRegular(path.join(cwd, file));
|
|
407
415
|
sourceBytes(bytes, file);
|
|
408
|
-
if ((size += bytes.length) >
|
|
416
|
+
if ((size += bytes.length) > maxBytes)
|
|
417
|
+
throw new WorkspaceError("too_large", `Source snapshot exceeds the ${maxBytes}-byte automatic publication limit`);
|
|
409
418
|
const st = await fs.lstat(path.join(cwd, file));
|
|
410
419
|
const oid = (await git(repo, ["hash-object", "-w", "--stdin", "--no-filters"], bytes)).toString().trim();
|
|
411
420
|
records.push({ file, value: `${st.mode & 73 ? "100755" : "100644"} ${oid} ${file}\0` });
|
|
412
421
|
}
|
|
413
422
|
if (!files.includes(".gitignore")) throw new WorkspaceError("ignore_policy", "The .gitignore policy itself must be published");
|
|
423
|
+
await verifyIgnore(repo, cwd);
|
|
414
424
|
await git(repo, ["update-index", "-z", "--index-info"], records.sort((a, b) => a.file.localeCompare(b.file)).map((record) => record.value).join(""));
|
|
415
425
|
return (await git(repo, ["write-tree"])).toString().trim();
|
|
416
426
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { WorkspacePublicationReport } from "@ricsam/r5d-api/runtime-protocol";
|
|
2
3
|
import { WorkspaceAuthority } from "../runtime/workspace/authority";
|
|
3
4
|
import { type StorageTransport } from "../runtime/workspace/storage-client";
|
|
4
5
|
export declare const PersonalWorkerGrant: z.ZodObject<{
|
|
@@ -96,6 +97,7 @@ export declare const PersonalWorkspaceRequest: z.ZodObject<{
|
|
|
96
97
|
env: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
97
98
|
files: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
98
99
|
}, z.core.$strict>>>;
|
|
100
|
+
remediation: z.ZodOptional<z.ZodLiteral<true>>;
|
|
99
101
|
}, z.core.$strict>;
|
|
100
102
|
export type PersonalWorkerRuntime = Awaited<ReturnType<typeof openPersonalWorkerRuntime>>;
|
|
101
103
|
/** The host owns this keeper independently of every replaceable remote web/gateway generation. */
|
|
@@ -106,6 +108,9 @@ export declare function openPersonalWorkerRuntime(options: {
|
|
|
106
108
|
cliEntrypoint?: string;
|
|
107
109
|
/** Internal test/embedding override. Personal workers publish every minute. */
|
|
108
110
|
publicationIntervalMs?: number;
|
|
111
|
+
/** Authenticated transport for autonomous publication evidence. Failures are
|
|
112
|
+
* retried with the same attempt identity on the next publication cycle. */
|
|
113
|
+
publicationReport?: (report: z.infer<typeof WorkspacePublicationReport>) => Promise<void>;
|
|
109
114
|
}): Promise<{
|
|
110
115
|
grant: {
|
|
111
116
|
[x: string]: unknown;
|
|
@@ -256,7 +256,7 @@ export declare class WorkspaceAuthority {
|
|
|
256
256
|
state: "completed";
|
|
257
257
|
result: any;
|
|
258
258
|
}>;
|
|
259
|
-
fileWrite(identity: WorkspaceIdentity, raw: WorkspaceFileWrite): Promise<{
|
|
259
|
+
fileWrite(identity: WorkspaceIdentity, raw: WorkspaceFileWrite, allowBlockedConflict?: boolean): Promise<{
|
|
260
260
|
intentHash?: string | undefined;
|
|
261
261
|
method: string;
|
|
262
262
|
id: string;
|
|
@@ -323,7 +323,10 @@ export declare class WorkspaceAuthority {
|
|
|
323
323
|
retained: boolean;
|
|
324
324
|
head: string;
|
|
325
325
|
}>;
|
|
326
|
-
publish(identity: WorkspaceIdentity
|
|
326
|
+
publish(identity: WorkspaceIdentity, options?: {
|
|
327
|
+
allowLargeDiff?: boolean;
|
|
328
|
+
allowBlockedConflict?: boolean;
|
|
329
|
+
}): Promise<{
|
|
327
330
|
head: string;
|
|
328
331
|
unchanged?: boolean;
|
|
329
332
|
}>;
|
|
@@ -347,7 +350,7 @@ export declare class WorkspaceAuthority {
|
|
|
347
350
|
complete: boolean;
|
|
348
351
|
}>;
|
|
349
352
|
/** Default arbitrary shell is mutating. Exact trusted config approval is the sole exemption. */
|
|
350
|
-
start(identity: WorkspaceIdentity, input: OperationEnvelope): Promise<import("..").RunReceipt>;
|
|
353
|
+
start(identity: WorkspaceIdentity, input: OperationEnvelope, allowBlockedConflict?: boolean): Promise<import("..").RunReceipt>;
|
|
351
354
|
private runRoute;
|
|
352
355
|
private action;
|
|
353
356
|
poll(identity: WorkspaceIdentity, run: RunIdentity, stdoutOffset?: number, stderrOffset?: number, maxBytes?: number): Promise<PollResult>;
|
|
@@ -26,4 +26,4 @@ export type TreeEntry = {
|
|
|
26
26
|
export declare function selectedTree(repo: string, commit: string): Promise<TreeEntry[]>;
|
|
27
27
|
export declare function materializeTree(repo: string, destination: string, entries: TreeEntry[], limit: number, message: string): Promise<void>;
|
|
28
28
|
export declare function verifyIgnore(repo: string, cwd: string): Promise<void>;
|
|
29
|
-
export declare function snapshotTree(repo: string, cwd: string, canonicalHead?: string | null): Promise<string>;
|
|
29
|
+
export declare function snapshotTree(repo: string, cwd: string, canonicalHead?: string | null, maxBytes?: number): Promise<string>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5d-worker",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.158",
|
|
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.
|
|
24
|
+
"@ricsam/r5d-api": "^0.0.158",
|
|
25
25
|
"node-pty": "1.1.0",
|
|
26
26
|
"zod": "^4.1.13",
|
|
27
27
|
"picomatch": "^4.0.3"
|