@ricsam/r5d-worker 0.0.183 → 0.0.185
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 +49 -17
- package/dist/mjs/personal/publication-refusal.mjs +15 -1
- package/dist/mjs/personal/runtime.mjs +6 -5
- package/dist/mjs/runtime/workspace/authority.mjs +35 -0
- package/dist/mjs/runtime/workspace/outer.mjs +16 -3
- package/dist/types/personal/client.d.ts +20 -0
- package/dist/types/personal/publication-refusal.d.ts +7 -0
- package/dist/types/personal/runtime.d.ts +1 -0
- package/dist/types/runtime/workspace/authority.d.ts +10 -0
- package/dist/types/runtime/workspace/outer.d.ts +6 -0
- 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.185";
|
|
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.185" : "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>] [--initial-sync merge|reset]\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.185" : "development"
|
|
19
19
|
);
|
|
20
20
|
console.log(`Worker connected: ${runtime.resourceId}`);
|
|
21
21
|
let closing = false;
|
package/dist/mjs/package.json
CHANGED
|
@@ -66,12 +66,35 @@ function parsePersonalWorkerOptions(args, environment = process.env) {
|
|
|
66
66
|
initialSync
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
+
function transportFailure(error) {
|
|
70
|
+
const name = error instanceof Error ? error.name : "";
|
|
71
|
+
return new PersonalResponseError(name === "TimeoutError" || name === "AbortError" ? "personal_transport_timeout" : "personal_transport_failed", 0, false);
|
|
72
|
+
}
|
|
73
|
+
async function readPersonalResponse(response) {
|
|
74
|
+
let data;
|
|
75
|
+
try {
|
|
76
|
+
data = await response.json();
|
|
77
|
+
} catch {
|
|
78
|
+
throw new PersonalResponseError(response.ok ? "personal_transport_invalid" : "personal_transport_rejected", response.status, false);
|
|
79
|
+
}
|
|
80
|
+
const body = data && typeof data === "object" && !Array.isArray(data) ? data : null;
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new PersonalResponseError(
|
|
83
|
+
typeof body?.error === "string" ? body.error : "personal_transport_rejected",
|
|
84
|
+
response.status,
|
|
85
|
+
body?.rejectedBeforeAdmission === true
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (!body) throw new PersonalResponseError("personal_transport_invalid", response.status, false);
|
|
89
|
+
return body;
|
|
90
|
+
}
|
|
69
91
|
function safeError(error) {
|
|
70
92
|
const value = error.code;
|
|
71
93
|
return typeof value === "string" && /^[a-zA-Z0-9_-]{1,100}$/.test(value) ? value : "personal_operation_unknown";
|
|
72
94
|
}
|
|
73
95
|
const resetBatchId = (instanceId) => `${instanceId.slice(0, 16)}-${Date.now().toString(36)}`;
|
|
74
96
|
const rejectedBeforeAdmission = (error) => error?.rejectedBeforeAdmission === true;
|
|
97
|
+
const readOnlyRequest = (request) => request?.kind === "inspect";
|
|
75
98
|
const CliUpdateRequest = z.object({ kind: z.literal("update-clis"), userId: z.string(), command: z.object({ version: z.string() }).passthrough() }).passthrough();
|
|
76
99
|
async function startPersonalWorker(options, version) {
|
|
77
100
|
const key = createHash("sha256").update(canonicalJson([options.baseUrl, options.label])).digest("hex").slice(0, 32), root = path.join(options.root, "personal", key);
|
|
@@ -93,18 +116,19 @@ async function startPersonalWorker(options, version) {
|
|
|
93
116
|
}
|
|
94
117
|
const endpoint = `/api/personal/resources/worker/${encodeURIComponent(identity.resourceId)}`;
|
|
95
118
|
async function request(pathname, body, timeoutMs = 3e4) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
119
|
+
let response;
|
|
120
|
+
try {
|
|
121
|
+
response = await fetch(options.baseUrl + pathname, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: { authorization: `Bearer ${options.credential}`, "content-type": "application/json" },
|
|
124
|
+
redirect: "error",
|
|
125
|
+
body: JSON.stringify(body),
|
|
126
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
127
|
+
});
|
|
128
|
+
} catch (error) {
|
|
129
|
+
throw transportFailure(error);
|
|
106
130
|
}
|
|
107
|
-
return
|
|
131
|
+
return readPersonalResponse(response);
|
|
108
132
|
}
|
|
109
133
|
const cli = await resolvePersonalCliEntrypoint(options.cliEntrypoint, version);
|
|
110
134
|
let bootId = randomUUID();
|
|
@@ -134,14 +158,18 @@ async function startPersonalWorker(options, version) {
|
|
|
134
158
|
if (!response || !("result" in response)) throw new Error("Invalid scoped storage response");
|
|
135
159
|
return response.result;
|
|
136
160
|
} catch (error) {
|
|
137
|
-
if (error instanceof PersonalResponseError
|
|
138
|
-
throw new WorkspaceError(
|
|
161
|
+
if (error instanceof PersonalResponseError)
|
|
162
|
+
throw new WorkspaceError(
|
|
163
|
+
error.code,
|
|
164
|
+
"Scoped storage rejected the request; retain its original operation identity",
|
|
165
|
+
error.rejectedBeforeAdmission && storageErrorCodes.has(error.code)
|
|
166
|
+
);
|
|
139
167
|
throw error;
|
|
140
168
|
}
|
|
141
169
|
},
|
|
142
170
|
publicationReport: async (report) => {
|
|
143
171
|
const response = await request(`${endpoint}/publication`, { instanceId: identity.instanceId, report });
|
|
144
|
-
const result = response
|
|
172
|
+
const result = response.result;
|
|
145
173
|
return result && typeof result === "object" && typeof result.incidentId === "string" ? { incidentId: result.incidentId } : void 0;
|
|
146
174
|
}
|
|
147
175
|
});
|
|
@@ -188,8 +216,9 @@ async function startPersonalWorker(options, version) {
|
|
|
188
216
|
} else result = await runtime.dispatch(input.request);
|
|
189
217
|
state = "completed";
|
|
190
218
|
} catch (error) {
|
|
191
|
-
|
|
192
|
-
|
|
219
|
+
const beforeAdmission = rejectedBeforeAdmission(error);
|
|
220
|
+
state = beforeAdmission || readOnlyRequest(input.request) ? "rejected" : "unknown";
|
|
221
|
+
result = { error: safeError(error), ...beforeAdmission ? { rejectedBeforeAdmission: true } : {} };
|
|
193
222
|
}
|
|
194
223
|
ledger.query("UPDATE actions SET state=?,result=? WHERE id=?").run(state, canonicalJson(result), input.id);
|
|
195
224
|
}
|
|
@@ -269,6 +298,9 @@ async function startPersonalWorker(options, version) {
|
|
|
269
298
|
};
|
|
270
299
|
}
|
|
271
300
|
export {
|
|
301
|
+
PersonalResponseError,
|
|
272
302
|
parsePersonalWorkerOptions,
|
|
273
|
-
|
|
303
|
+
readPersonalResponse,
|
|
304
|
+
startPersonalWorker,
|
|
305
|
+
transportFailure
|
|
274
306
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { WorkspaceError } from "../runtime/workspace/contracts.mjs";
|
|
1
2
|
const CONFLICT_PUBLICATION_CODES = /* @__PURE__ */ new Set([
|
|
2
3
|
"conflict",
|
|
3
4
|
"sync_conflict",
|
|
@@ -9,8 +10,20 @@ const TRANSIENT_PUBLICATION_CODES = /* @__PURE__ */ new Set([
|
|
|
9
10
|
"apply_refused",
|
|
10
11
|
"mirror_advanced",
|
|
11
12
|
"authority_closed",
|
|
12
|
-
"busy"
|
|
13
|
+
"busy",
|
|
14
|
+
// The relay or the storage behind it did not answer this cycle: nothing was
|
|
15
|
+
// refused about the workspace, the next cycle asks again.
|
|
16
|
+
"internal_error",
|
|
17
|
+
"personal_transport_failed",
|
|
18
|
+
"personal_transport_timeout",
|
|
19
|
+
"personal_transport_rejected",
|
|
20
|
+
"personal_transport_invalid"
|
|
13
21
|
]);
|
|
22
|
+
function publicationFailureDetail(error) {
|
|
23
|
+
if (error instanceof WorkspaceError) return { code: error.code };
|
|
24
|
+
const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
25
|
+
return { code: "publication_failed", detail: detail.replace(/\s+/g, " ").slice(0, 300) };
|
|
26
|
+
}
|
|
14
27
|
const BLOCKED_PUBLICATION_CODES = /* @__PURE__ */ new Set([
|
|
15
28
|
"secret_or_runtime_path",
|
|
16
29
|
"secret_content",
|
|
@@ -42,6 +55,7 @@ export {
|
|
|
42
55
|
PUBLICATION_OBSERVATION_THRESHOLDS,
|
|
43
56
|
STUCK_OPERATION_OBSERVATIONS,
|
|
44
57
|
TRANSIENT_PUBLICATION_CODES,
|
|
58
|
+
publicationFailureDetail,
|
|
45
59
|
publicationRefusalElects,
|
|
46
60
|
publicationRefusalOutcome
|
|
47
61
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { publicationRefusalElects, publicationRefusalOutcome, TRANSIENT_PUBLICATION_CODES } from "./publication-refusal.mjs";
|
|
2
|
+
import { publicationFailureDetail, publicationRefusalElects, publicationRefusalOutcome, TRANSIENT_PUBLICATION_CODES } from "./publication-refusal.mjs";
|
|
3
3
|
import { PersonalTcpManager } from "./tcp.mjs";
|
|
4
4
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
5
5
|
import { promises as fs } from "node:fs";
|
|
@@ -273,8 +273,8 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
273
273
|
failedPublicationAttempts.delete("workspace");
|
|
274
274
|
results.push({ workbenchId: "workspace", ...outer });
|
|
275
275
|
} catch (error) {
|
|
276
|
-
const code
|
|
277
|
-
results.push({ workbenchId: "workspace", error: code });
|
|
276
|
+
const { code, detail } = publicationFailureDetail(error);
|
|
277
|
+
results.push({ workbenchId: "workspace", error: code, ...detail ? { detail } : {} });
|
|
278
278
|
if (!TRANSIENT_PUBLICATION_CODES.has(code) && options.publicationReport) {
|
|
279
279
|
let startingHead = null;
|
|
280
280
|
try {
|
|
@@ -332,12 +332,13 @@ exec ${quote(executable)} ${quote(cli)} "$@"
|
|
|
332
332
|
(results) => {
|
|
333
333
|
const deferred = results.filter((result) => result.error && result.error !== "remediation_paused" && !TRANSIENT_PUBLICATION_CODES.has(result.error));
|
|
334
334
|
if (deferred.length)
|
|
335
|
-
process.stderr.write(`[r5d-worker] periodic workspace publication deferred: ${deferred.map((result) => `${result.workbenchId}:${result.error}`).join(", ")}
|
|
335
|
+
process.stderr.write(`[r5d-worker] periodic workspace publication deferred: ${deferred.map((result) => `${result.workbenchId}:${result.error}${result.detail ? ` (${result.detail})` : ""}`).join(", ")}
|
|
336
336
|
`);
|
|
337
337
|
if (publication === next) publication = null;
|
|
338
338
|
},
|
|
339
339
|
(error) => {
|
|
340
|
-
|
|
340
|
+
const failure = publicationFailureDetail(error);
|
|
341
|
+
process.stderr.write(`[r5d-worker] periodic workspace publication failed: ${failure.code}${failure.detail ? ` (${failure.detail})` : ""}
|
|
341
342
|
`);
|
|
342
343
|
if (publication === next) publication = null;
|
|
343
344
|
}
|
|
@@ -1242,9 +1242,44 @@ class WorkspaceAuthority {
|
|
|
1242
1242
|
b.state.mirroredHead = null;
|
|
1243
1243
|
await this.save(b);
|
|
1244
1244
|
const result = this.outerEnabled(b) ? await this.hydrateLinkedIdle(b, identity) : await this.hydrateIdle(b, identity);
|
|
1245
|
+
if (this.outerEnabled(b)) await this.restoreOuterEntries(b);
|
|
1245
1246
|
return { ...result, reset: true, retained: true };
|
|
1246
1247
|
}));
|
|
1247
1248
|
}
|
|
1249
|
+
/** A checkout rebuilt from its branch head is not what the workspace holds
|
|
1250
|
+
* for it: the branch does not track the files every worker synchronizes (an
|
|
1251
|
+
* install's lock file, a note, a sentinel), it has not committed the edits
|
|
1252
|
+
* they published, and it still carries what they deleted. The checkout is
|
|
1253
|
+
* brought to what a worker installing it afresh receives, the outer head's
|
|
1254
|
+
* subtree over the branch head, so the next cycle sees the canonical state
|
|
1255
|
+
* again instead of publishing the difference to every other worker (stage
|
|
1256
|
+
* 2026-09-17 run 25 published the sentinels' absence as deletions). The
|
|
1257
|
+
* discarded divergence is already retained beside the checkout's state. */
|
|
1258
|
+
async restoreOuterEntries(b) {
|
|
1259
|
+
const workTree = this.config.workspaceRoot;
|
|
1260
|
+
if (!workTree) return;
|
|
1261
|
+
const { repo, state } = await this.outer(b.config.userId);
|
|
1262
|
+
if (!state.head) return;
|
|
1263
|
+
const relative = path.relative(workTree, b.config.cwd);
|
|
1264
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) return;
|
|
1265
|
+
const root = relative.split(path.sep).join("/");
|
|
1266
|
+
const held = (await repo.listTree(await repo.treeOf(state.head))).filter((entry) => entry.mode !== "160000" && entry.file.startsWith(`${root}/`));
|
|
1267
|
+
if (!held.length) return;
|
|
1268
|
+
await repo.restore(state.head, held.map((entry) => entry.file));
|
|
1269
|
+
const entries = new Set(held.map((entry) => entry.file));
|
|
1270
|
+
const missing = [...await this.innerTracked(b)].map((file) => `${root}/${file}`).filter((file) => !entries.has(file));
|
|
1271
|
+
if (!missing.length) return;
|
|
1272
|
+
const excluded = await repo.ignored(missing);
|
|
1273
|
+
for (const file of missing) {
|
|
1274
|
+
if (excluded.has(file)) continue;
|
|
1275
|
+
const absolute = path.join(workTree, file);
|
|
1276
|
+
const stat = await fs.lstat(absolute).catch((error) => {
|
|
1277
|
+
if (error.code === "ENOENT" || error.code === "ENOTDIR") return null;
|
|
1278
|
+
throw error;
|
|
1279
|
+
});
|
|
1280
|
+
if (stat?.isFile()) await fs.rm(absolute);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1248
1283
|
// ---- Outer repository ---------------------------------------------------
|
|
1249
1284
|
outerEnabled(b) {
|
|
1250
1285
|
return !!this.options.accountStorage && (!b || b.config.rootProfile === "account" || this.linkedWorkbench(b));
|
|
@@ -218,9 +218,7 @@ class OuterRepository {
|
|
|
218
218
|
}
|
|
219
219
|
const paths = [...untracked.values()].flat();
|
|
220
220
|
if (!paths.length) return;
|
|
221
|
-
const
|
|
222
|
-
if (code !== 0 && code !== 1) throw new WorkspaceError("git_failed", "Outer repository ignore rules are unreadable");
|
|
223
|
-
const ignored = new Set(nulSplit(stdout));
|
|
221
|
+
const ignored = await this.ignored(paths);
|
|
224
222
|
const removals = [];
|
|
225
223
|
for (const [root, files] of untracked) {
|
|
226
224
|
const drop = files.filter((file) => ignored.has(file));
|
|
@@ -229,6 +227,14 @@ class OuterRepository {
|
|
|
229
227
|
}
|
|
230
228
|
if (removals.length) await this.run(["update-index", "--force-remove", "-z", "--stdin"], `${removals.join("\0")}\0`);
|
|
231
229
|
}
|
|
230
|
+
/** The subset of `paths` the workspace's ignore rules exclude. --no-index
|
|
231
|
+
* evaluates the nested ignore files for tracked paths as well. */
|
|
232
|
+
async ignored(paths) {
|
|
233
|
+
if (!paths.length) return /* @__PURE__ */ new Set();
|
|
234
|
+
const { code, stdout } = await this.result(["check-ignore", "--no-index", "-z", "--stdin"], `${paths.join("\0")}\0`);
|
|
235
|
+
if (code !== 0 && code !== 1) throw new WorkspaceError("git_failed", "Outer repository ignore rules are unreadable");
|
|
236
|
+
return new Set(nulSplit(stdout));
|
|
237
|
+
}
|
|
232
238
|
/** Staging records a symlink as a link entry, and a nested repository that
|
|
233
239
|
* appeared between the walk and the add as a gitlink. Neither can be
|
|
234
240
|
* materialized on another host; both are dropped from the index without
|
|
@@ -271,6 +277,13 @@ class OuterRepository {
|
|
|
271
277
|
await this.run(["read-tree", GitOid.parse(commit)]);
|
|
272
278
|
await this.run(["checkout-index", "-a"]);
|
|
273
279
|
}
|
|
280
|
+
/** Materialize `paths` of `commit` into the index and the working tree,
|
|
281
|
+
* leaving every other path alone. */
|
|
282
|
+
async restore(commit, paths) {
|
|
283
|
+
if (!paths.length) return;
|
|
284
|
+
for (const file of paths) safeTreePath(file);
|
|
285
|
+
await this.run(["checkout", GitOid.parse(commit), "--pathspec-from-file=-", "--pathspec-file-nul", "--"], `${paths.join("\0")}\0`);
|
|
286
|
+
}
|
|
274
287
|
/** Reset index and working tree to a commit, deleting tracked paths it lacks.
|
|
275
288
|
* Never `clean`: the second `-f` that would reach nested repositories is the
|
|
276
289
|
* one switch this repository must never pass. */
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export declare class PersonalResponseError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly rejectedBeforeAdmission: boolean;
|
|
5
|
+
constructor(code: string, status: number, rejectedBeforeAdmission: boolean);
|
|
6
|
+
}
|
|
1
7
|
/** What the first synchronization after `r5d-worker start` does to checkouts
|
|
2
8
|
* that diverged from the server while the worker was away: `merge` integrates
|
|
3
9
|
* them and opens a conflict remediation when needed (the default); `reset`
|
|
@@ -14,6 +20,20 @@ export type PersonalWorkerOptions = {
|
|
|
14
20
|
pollTimeoutMs?: number;
|
|
15
21
|
};
|
|
16
22
|
export declare function parsePersonalWorkerOptions(args: string[], environment?: Record<string, string | undefined>): PersonalWorkerOptions;
|
|
23
|
+
/** A request that never got an answer is a typed, transient transport
|
|
24
|
+
* failure: a timed-out or refused connection is not the platform refusing
|
|
25
|
+
* anything, and the journal names it rather than the bare `publication_failed`
|
|
26
|
+
* it became (stage 2026-09-17 run 25). */
|
|
27
|
+
export declare function transportFailure(error: unknown): PersonalResponseError;
|
|
28
|
+
/** Every relay answer is a JSON object: a refusal names its `error` code and
|
|
29
|
+
* whether it was rejected before admission, a success carries the route's own
|
|
30
|
+
* fields. The caller names the fields it reads. */
|
|
31
|
+
export type PersonalResponseBody = {
|
|
32
|
+
[field: string]: unknown;
|
|
33
|
+
};
|
|
34
|
+
/** The relay answers JSON; anything else (an edge's error page, an empty body)
|
|
35
|
+
* is a typed transport rejection carrying the status, never a parse error. */
|
|
36
|
+
export declare function readPersonalResponse<T extends PersonalResponseBody = PersonalResponseBody>(response: Response): Promise<T>;
|
|
17
37
|
/** HTTP reconnects never replace the local source/PTY keeper. */
|
|
18
38
|
export declare function startPersonalWorker(options: PersonalWorkerOptions, version: string): Promise<{
|
|
19
39
|
resourceId: string;
|
|
@@ -11,6 +11,13 @@ export declare const CONFLICT_PUBLICATION_CODES: ReadonlySet<string>;
|
|
|
11
11
|
* landing, or the mirror moved between read and publish. The next cycle
|
|
12
12
|
* repeats the work; nothing durable happened and nothing is reported. */
|
|
13
13
|
export declare const TRANSIENT_PUBLICATION_CODES: ReadonlySet<string>;
|
|
14
|
+
/** The journal's account of a synchronization failure: a typed refusal is its
|
|
15
|
+
* code; anything else keeps its name and message, since the code alone
|
|
16
|
+
* (`publication_failed`) told nobody what broke (stage 2026-09-17 run 25). */
|
|
17
|
+
export declare function publicationFailureDetail(error: unknown): {
|
|
18
|
+
code: string;
|
|
19
|
+
detail?: string;
|
|
20
|
+
};
|
|
14
21
|
/** Refused because the source candidate itself is unsafe or oversized. */
|
|
15
22
|
export declare const BLOCKED_PUBLICATION_CODES: ReadonlySet<string>;
|
|
16
23
|
/** Consecutive identical observations required before electing an incident.
|
|
@@ -383,6 +383,16 @@ export declare class WorkspaceAuthority {
|
|
|
383
383
|
retained: boolean;
|
|
384
384
|
head: string;
|
|
385
385
|
}>;
|
|
386
|
+
/** A checkout rebuilt from its branch head is not what the workspace holds
|
|
387
|
+
* for it: the branch does not track the files every worker synchronizes (an
|
|
388
|
+
* install's lock file, a note, a sentinel), it has not committed the edits
|
|
389
|
+
* they published, and it still carries what they deleted. The checkout is
|
|
390
|
+
* brought to what a worker installing it afresh receives, the outer head's
|
|
391
|
+
* subtree over the branch head, so the next cycle sees the canonical state
|
|
392
|
+
* again instead of publishing the difference to every other worker (stage
|
|
393
|
+
* 2026-09-17 run 25 published the sentinels' absence as deletions). The
|
|
394
|
+
* discarded divergence is already retained beside the checkout's state. */
|
|
395
|
+
private restoreOuterEntries;
|
|
386
396
|
private outerEnabled;
|
|
387
397
|
private outerStateFile;
|
|
388
398
|
private outer;
|
|
@@ -93,6 +93,9 @@ export declare class OuterRepository {
|
|
|
93
93
|
* published. The seeding invariant holds: a checkout never loses its last
|
|
94
94
|
* outer entry. */
|
|
95
95
|
private evictIgnored;
|
|
96
|
+
/** The subset of `paths` the workspace's ignore rules exclude. --no-index
|
|
97
|
+
* evaluates the nested ignore files for tracked paths as well. */
|
|
98
|
+
ignored(paths: readonly string[]): Promise<ReadonlySet<string>>;
|
|
96
99
|
/** Staging records a symlink as a link entry, and a nested repository that
|
|
97
100
|
* appeared between the walk and the add as a gitlink. Neither can be
|
|
98
101
|
* materialized on another host; both are dropped from the index without
|
|
@@ -109,6 +112,9 @@ export declare class OuterRepository {
|
|
|
109
112
|
apply(fromTree: string, toTree: string): Promise<void>;
|
|
110
113
|
/** Materialize a commit into a working tree that holds none of its paths yet. */
|
|
111
114
|
checkout(commit: string): Promise<void>;
|
|
115
|
+
/** Materialize `paths` of `commit` into the index and the working tree,
|
|
116
|
+
* leaving every other path alone. */
|
|
117
|
+
restore(commit: string, paths: readonly string[]): Promise<void>;
|
|
112
118
|
/** Reset index and working tree to a commit, deleting tracked paths it lacks.
|
|
113
119
|
* Never `clean`: the second `-f` that would reach nested repositories is the
|
|
114
120
|
* one switch this repository must never pass. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5d-worker",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.185",
|
|
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.185",
|
|
25
25
|
"node-pty": "1.1.0",
|
|
26
26
|
"zod": "^4.1.13",
|
|
27
27
|
"picomatch": "^4.0.3"
|