@claudexor/workspace 4.8.2 → 5.0.1
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/anchor-store.d.ts +20 -9
- package/dist/anchor-store.d.ts.map +1 -1
- package/dist/anchor-store.js +89 -14
- package/dist/anchor-store.js.map +1 -1
- package/dist/capture-failure.d.ts.map +1 -1
- package/dist/capture-failure.js +7 -2
- package/dist/capture-failure.js.map +1 -1
- package/dist/capture-inventory.d.ts +10 -0
- package/dist/capture-inventory.d.ts.map +1 -0
- package/dist/capture-inventory.js +63 -0
- package/dist/capture-inventory.js.map +1 -0
- package/dist/capture-objects.d.ts +32 -0
- package/dist/capture-objects.d.ts.map +1 -0
- package/dist/capture-objects.js +129 -0
- package/dist/capture-objects.js.map +1 -0
- package/dist/cleanup-fault.d.ts +18 -0
- package/dist/cleanup-fault.d.ts.map +1 -0
- package/dist/cleanup-fault.js +45 -0
- package/dist/cleanup-fault.js.map +1 -0
- package/dist/continuation-evidence.d.ts +61 -15
- package/dist/continuation-evidence.d.ts.map +1 -1
- package/dist/continuation-evidence.js +151 -36
- package/dist/continuation-evidence.js.map +1 -1
- package/dist/continuation-facts.d.ts +93 -0
- package/dist/continuation-facts.d.ts.map +1 -0
- package/dist/continuation-facts.js +162 -0
- package/dist/continuation-facts.js.map +1 -0
- package/dist/directory-workspace.d.ts.map +1 -1
- package/dist/directory-workspace.js +1 -3
- package/dist/directory-workspace.js.map +1 -1
- package/dist/files-io.d.ts.map +1 -1
- package/dist/files-io.js +1 -7
- package/dist/files-io.js.map +1 -1
- package/dist/fs-presence.d.ts +12 -0
- package/dist/fs-presence.d.ts.map +1 -0
- package/dist/fs-presence.js +35 -0
- package/dist/fs-presence.js.map +1 -0
- package/dist/git-nested-repositories.d.ts.map +1 -1
- package/dist/git-nested-repositories.js +11 -2
- package/dist/git-nested-repositories.js.map +1 -1
- package/dist/git-observed.d.ts +75 -0
- package/dist/git-observed.d.ts.map +1 -0
- package/dist/git-observed.js +172 -0
- package/dist/git-observed.js.map +1 -0
- package/dist/git-result.d.ts +36 -0
- package/dist/git-result.d.ts.map +1 -0
- package/dist/git-result.js +96 -0
- package/dist/git-result.js.map +1 -0
- package/dist/git-run.d.ts +11 -0
- package/dist/git-run.d.ts.map +1 -1
- package/dist/git-run.js +24 -0
- package/dist/git-run.js.map +1 -1
- package/dist/git-snapshot.d.ts +40 -0
- package/dist/git-snapshot.d.ts.map +1 -0
- package/dist/git-snapshot.js +238 -0
- package/dist/git-snapshot.js.map +1 -0
- package/dist/git-transient-capture.d.ts +63 -16
- package/dist/git-transient-capture.d.ts.map +1 -1
- package/dist/git-transient-capture.js +170 -57
- package/dist/git-transient-capture.js.map +1 -1
- package/dist/git.d.ts +46 -21
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +164 -103
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/lanes.d.ts +2 -1
- package/dist/lanes.d.ts.map +1 -1
- package/dist/lanes.js +7 -6
- package/dist/lanes.js.map +1 -1
- package/dist/manager.d.ts +21 -7
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +30 -17
- package/dist/manager.js.map +1 -1
- package/dist/object-compose.d.ts +58 -0
- package/dist/object-compose.d.ts.map +1 -0
- package/dist/object-compose.js +444 -0
- package/dist/object-compose.js.map +1 -0
- package/dist/object-materialize.d.ts +52 -0
- package/dist/object-materialize.d.ts.map +1 -0
- package/dist/object-materialize.js +467 -0
- package/dist/object-materialize.js.map +1 -0
- package/dist/object-occupied.d.ts +27 -0
- package/dist/object-occupied.d.ts.map +1 -0
- package/dist/object-occupied.js +131 -0
- package/dist/object-occupied.js.map +1 -0
- package/dist/object-revert.d.ts +45 -0
- package/dist/object-revert.d.ts.map +1 -0
- package/dist/object-revert.js +265 -0
- package/dist/object-revert.js.map +1 -0
- package/dist/object-saved-files.d.ts +50 -0
- package/dist/object-saved-files.d.ts.map +1 -0
- package/dist/object-saved-files.js +176 -0
- package/dist/object-saved-files.js.map +1 -0
- package/dist/object-sparse.d.ts +22 -0
- package/dist/object-sparse.d.ts.map +1 -0
- package/dist/object-sparse.js +72 -0
- package/dist/object-sparse.js.map +1 -0
- package/dist/object-store-leases.d.ts +41 -0
- package/dist/object-store-leases.d.ts.map +1 -0
- package/dist/object-store-leases.js +277 -0
- package/dist/object-store-leases.js.map +1 -0
- package/dist/object-store-roots.d.ts +25 -0
- package/dist/object-store-roots.d.ts.map +1 -0
- package/dist/object-store-roots.js +114 -0
- package/dist/object-store-roots.js.map +1 -0
- package/dist/object-store.d.ts +110 -0
- package/dist/object-store.d.ts.map +1 -0
- package/dist/object-store.js +404 -0
- package/dist/object-store.js.map +1 -0
- package/dist/object-transfer.d.ts +41 -0
- package/dist/object-transfer.d.ts.map +1 -0
- package/dist/object-transfer.js +267 -0
- package/dist/object-transfer.js.map +1 -0
- package/dist/plain-diff-capture.d.ts +1 -2
- package/dist/plain-diff-capture.d.ts.map +1 -1
- package/dist/plain-diff-capture.js +2 -7
- package/dist/plain-diff-capture.js.map +1 -1
- package/dist/plain-diff.d.ts +0 -4
- package/dist/plain-diff.d.ts.map +1 -1
- package/dist/plain-diff.js +2 -44
- package/dist/plain-diff.js.map +1 -1
- package/dist/raw-patch.d.ts.map +1 -1
- package/dist/raw-patch.js +5 -8
- package/dist/raw-patch.js.map +1 -1
- package/dist/run-expiry.d.ts +3 -0
- package/dist/run-expiry.d.ts.map +1 -0
- package/dist/run-expiry.js +15 -0
- package/dist/run-expiry.js.map +1 -0
- package/dist/thread-tree.d.ts +36 -2
- package/dist/thread-tree.d.ts.map +1 -1
- package/dist/thread-tree.js +87 -11
- package/dist/thread-tree.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { lstatPresent } from "./fs-presence.js";
|
|
2
|
+
import { classifyGitFailure, gitObserved, GitOperationError } from "./git-observed.js";
|
|
3
|
+
/** `git sparse-checkout check-rules` arrived in Git 2.41; an older Git answers its
|
|
4
|
+
* usage error (exit 129: an unknown subcommand) instead. */
|
|
5
|
+
const CHECK_RULES_UNKNOWN = 129;
|
|
6
|
+
/**
|
|
7
|
+
* The paths among `paths` that the worktree's sparse-checkout patterns leave out
|
|
8
|
+
* (none when it is not a sparse checkout), from one `check-rules` call, or null when
|
|
9
|
+
* this Git cannot evaluate the patterns (older than 2.41). A snapshot's `add -A`
|
|
10
|
+
* skips such a path, a tracked one keeping HEAD's blob whether its file is absent,
|
|
11
|
+
* as a sparse checkout usually leaves it, or present, so an operation that addresses
|
|
12
|
+
* one could not tell what it holds before or after writing. With
|
|
13
|
+
* `core.sparseCheckout` set but no patterns file, Git checks out everything, and so
|
|
14
|
+
* nothing is left out here either. A worktree that is not sparse costs one
|
|
15
|
+
* `git config` and never asks `check-rules`.
|
|
16
|
+
*/
|
|
17
|
+
export async function outsideSparseCheckout(worktree, paths, env) {
|
|
18
|
+
if (paths.length === 0)
|
|
19
|
+
return [];
|
|
20
|
+
const sparse = await gitObserved(worktree, ["config", "--type=bool", "--get", "core.sparseCheckout"], { env, label: "config core.sparseCheckout", allowFailure: true });
|
|
21
|
+
if (sparse.stdout.trim() !== "true")
|
|
22
|
+
return [];
|
|
23
|
+
const patterns = await gitObserved(worktree, ["rev-parse", "--path-format=absolute", "--git-path", "info/sparse-checkout"], { env, label: "rev-parse --git-path info/sparse-checkout" });
|
|
24
|
+
if (!lstatPresent(patterns.stdout.trim()))
|
|
25
|
+
return [];
|
|
26
|
+
const kept = await gitObserved(worktree, ["sparse-checkout", "check-rules", "-z"], {
|
|
27
|
+
env,
|
|
28
|
+
label: "sparse-checkout check-rules",
|
|
29
|
+
input: paths.map((path) => `${path}\0`).join(""),
|
|
30
|
+
allowFailure: true,
|
|
31
|
+
});
|
|
32
|
+
if (kept.code === CHECK_RULES_UNKNOWN)
|
|
33
|
+
return null;
|
|
34
|
+
if (kept.code !== 0)
|
|
35
|
+
throw new GitOperationError(classifyGitFailure(kept.stderr), `git sparse-checkout check-rules failed: ${kept.stderr.trim().slice(0, 2_000)}`);
|
|
36
|
+
const inside = new Set(kept.stdout.split("\0"));
|
|
37
|
+
return paths.filter((path) => !inside.has(path));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The refusal of an operation that must write or remove a path outside the
|
|
41
|
+
* sparse-checkout patterns, naming those paths, or of any operation in a sparse
|
|
42
|
+
* checkout whose Git cannot evaluate its patterns; nothing is written. Widening the
|
|
43
|
+
* sparse checkout (`git sparse-checkout add`) lets the same operation run. Paths it
|
|
44
|
+
* does not address keep their files: the checkout runs with the patterns off.
|
|
45
|
+
*/
|
|
46
|
+
export async function sparseCheckoutRefusal(worktree, paths, operation, env = {}) {
|
|
47
|
+
const found = await outsideSparseCheckout(worktree, paths, env);
|
|
48
|
+
if (found === null) {
|
|
49
|
+
const version = await gitObserved(worktree, ["version"], {
|
|
50
|
+
env,
|
|
51
|
+
label: "version",
|
|
52
|
+
allowFailure: true,
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
code: "git_failed",
|
|
56
|
+
message: `this checkout is sparse, and its Git (${version.stdout.trim() || "version unknown"}) cannot evaluate the sparse-checkout patterns: Claudexor asks \`git sparse-checkout check-rules\`, which Git 2.41 added, which of the paths this ${operation} changes are inside them; nothing was written. Use Git 2.41 or newer, or turn the sparse checkout off (git sparse-checkout disable), and try again`,
|
|
57
|
+
paths: [],
|
|
58
|
+
pathCount: 0,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const outside = [...new Set(found)].sort();
|
|
62
|
+
if (outside.length === 0)
|
|
63
|
+
return null;
|
|
64
|
+
const what = operation === "delivery" ? "this result changes" : "this revert must change";
|
|
65
|
+
return {
|
|
66
|
+
code: "delivery_outside_sparse_checkout",
|
|
67
|
+
message: `${outside.length} path(s) ${what} are outside this checkout's sparse-checkout patterns, so nothing was written; widen the sparse checkout (git sparse-checkout add) to include them and try again: ${outside.slice(0, 20).join(", ")}`.slice(0, 4_000),
|
|
68
|
+
paths: outside.slice(0, 200),
|
|
69
|
+
pathCount: outside.length,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=object-sparse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-sparse.js","sourceRoot":"","sources":["../src/object-sparse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** The store's private control directories (pending, roots, tmp, the prune lock). */
|
|
2
|
+
export declare function controlDir(store: string, name: string): string;
|
|
3
|
+
export interface PendingRoots {
|
|
4
|
+
store: string;
|
|
5
|
+
path: string;
|
|
6
|
+
roots: string[];
|
|
7
|
+
/** Renews the record's mtime while the import runs. */
|
|
8
|
+
renewal?: ReturnType<typeof setInterval>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Two-flag protocol with prune: an import first records its pending roots, then
|
|
12
|
+
* waits out a prune already holding the lock; a prune first takes the lock,
|
|
13
|
+
* then yields when any live pending record exists. One of the two always sees
|
|
14
|
+
* the other, so a prune never deletes an object a concurrent import relies on.
|
|
15
|
+
* The record names this process by PID and kernel birth: it stays live for as
|
|
16
|
+
* long as this process runs, however long the import takes.
|
|
17
|
+
*/
|
|
18
|
+
export declare function beginPendingRoots(store: string, roots: string[]): Promise<PendingRoots>;
|
|
19
|
+
/**
|
|
20
|
+
* End a pending lease. Never throws: what the leased operation answered stays its
|
|
21
|
+
* answer, and its caller's remaining cleanup still runs. A record that cannot be
|
|
22
|
+
* removed (a directory that refuses the unlink) is not released: it stays, naming
|
|
23
|
+
* this process, and a prune counts it as it counts any record (see
|
|
24
|
+
* `livePendingRecords`), so it can only delay pruning. Returns that removal fault
|
|
25
|
+
* as the caller's cleanup diagnostic, else undefined.
|
|
26
|
+
*/
|
|
27
|
+
export declare function endPendingRoots(pending: PendingRoots | null | undefined): unknown;
|
|
28
|
+
/**
|
|
29
|
+
* Take the prune lock atomically: the owner record is written into a private
|
|
30
|
+
* directory that becomes the lock by one rename, so no reader ever sees a lock
|
|
31
|
+
* without its owner. A stale lock (dead or reused-PID owner, or ownerless
|
|
32
|
+
* debris) is moved aside by one rename — of racing reapers only one moves it —
|
|
33
|
+
* and checked to be the lock judged stale before it is removed. Returns the
|
|
34
|
+
* hold, or null when another live pruner holds the lock.
|
|
35
|
+
*/
|
|
36
|
+
export declare function acquirePruneLock(store: string): {
|
|
37
|
+
stillHeld: () => boolean;
|
|
38
|
+
release: () => void;
|
|
39
|
+
} | null;
|
|
40
|
+
export declare function livePendingRecords(store: string, graceMs: number): string[][];
|
|
41
|
+
//# sourceMappingURL=object-store-leases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-store-leases.d.ts","sourceRoot":"","sources":["../src/object-store-leases.ts"],"names":[],"mappings":"AAgCA,qFAAqF;AACrF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9D;AA6DD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,uDAAuD;IACvD,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;CAC1C;AAKD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAoC7F;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CASjF;AAqCD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,GACZ;IAAE,SAAS,EAAE,MAAM,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,GAAG,IAAI,CAmD1D;AAOD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAoC7E"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, utimesSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
5
|
+
import { compareProcessBirth, defaultProcessIdentityService, isKnownProcessIdentity, WorkspaceError, } from "@claudexor/core";
|
|
6
|
+
import { noteCleanupFault } from "./cleanup-fault.js";
|
|
7
|
+
import { existsStrict } from "./fs-presence.js";
|
|
8
|
+
/*
|
|
9
|
+
* Leases of the project object store (#535): the pending records that keep a
|
|
10
|
+
* running import's objects from a concurrent prune, and the prune lock. Both
|
|
11
|
+
* name their process by PID and, where the platform reports one, its kernel
|
|
12
|
+
* birth, so a reused PID never keeps a lease alive.
|
|
13
|
+
*/
|
|
14
|
+
/** The store's private control directories (pending, roots, tmp, the prune lock). */
|
|
15
|
+
export function controlDir(store, name) {
|
|
16
|
+
return join(store, "claudexor", name);
|
|
17
|
+
}
|
|
18
|
+
function processAlive(pid) {
|
|
19
|
+
try {
|
|
20
|
+
process.kill(pid, 0);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return error.code === "EPERM";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function selfOwner() {
|
|
28
|
+
const identity = defaultProcessIdentityService.self();
|
|
29
|
+
return { pid: process.pid, ...(identity.status === "known" ? { identity } : {}) };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `alive`: the recorded process still runs (same PID and same kernel birth);
|
|
33
|
+
* `dead`: it exited, or its PID now names another process; `unverified`: the
|
|
34
|
+
* PID is in use but no birth was recorded or it cannot be read now.
|
|
35
|
+
*/
|
|
36
|
+
function ownerState(owner) {
|
|
37
|
+
const pid = Number(owner.pid);
|
|
38
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || !processAlive(pid))
|
|
39
|
+
return "dead";
|
|
40
|
+
if (!isKnownProcessIdentity(owner.identity))
|
|
41
|
+
return "unverified";
|
|
42
|
+
const birth = compareProcessBirth(owner.identity, defaultProcessIdentityService.read(pid));
|
|
43
|
+
if (birth === "same")
|
|
44
|
+
return "alive";
|
|
45
|
+
return birth === "unknown" ? "unverified" : "dead";
|
|
46
|
+
}
|
|
47
|
+
/** Milliseconds since `path` was last written; vanished paths are infinitely old. */
|
|
48
|
+
function ageOf(path) {
|
|
49
|
+
try {
|
|
50
|
+
return Date.now() - statSync(path).mtimeMs;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return Number.POSITIVE_INFINITY;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Keep `path`'s mtime fresh while its holder runs (the bounded liveness of an
|
|
57
|
+
* unverifiable owner); unref'd, so it never keeps a process alive. */
|
|
58
|
+
function renewWhileHeld(path, everyMs, held = () => true) {
|
|
59
|
+
const timer = setInterval(() => {
|
|
60
|
+
try {
|
|
61
|
+
if (!held())
|
|
62
|
+
return;
|
|
63
|
+
const now = new Date();
|
|
64
|
+
utimesSync(path, now, now);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Gone: its holder released it.
|
|
68
|
+
}
|
|
69
|
+
}, everyMs);
|
|
70
|
+
timer.unref?.();
|
|
71
|
+
return timer;
|
|
72
|
+
}
|
|
73
|
+
/** Bound of a prune-lock wait before an import gives up. */
|
|
74
|
+
const PRUNE_WAIT_MS = 600_000;
|
|
75
|
+
/**
|
|
76
|
+
* Two-flag protocol with prune: an import first records its pending roots, then
|
|
77
|
+
* waits out a prune already holding the lock; a prune first takes the lock,
|
|
78
|
+
* then yields when any live pending record exists. One of the two always sees
|
|
79
|
+
* the other, so a prune never deletes an object a concurrent import relies on.
|
|
80
|
+
* The record names this process by PID and kernel birth: it stays live for as
|
|
81
|
+
* long as this process runs, however long the import takes.
|
|
82
|
+
*/
|
|
83
|
+
export async function beginPendingRoots(store, roots) {
|
|
84
|
+
const dir = controlDir(store, "pending");
|
|
85
|
+
mkdirSync(dir, { recursive: true });
|
|
86
|
+
const path = join(dir, `${process.pid}-${randomUUID()}.json`);
|
|
87
|
+
writeFileSync(path, JSON.stringify({ ...selfOwner(), created_at: new Date().toISOString(), roots }) + "\n", { mode: 0o600 });
|
|
88
|
+
const pending = {
|
|
89
|
+
store,
|
|
90
|
+
path,
|
|
91
|
+
roots,
|
|
92
|
+
renewal: renewWhileHeld(path, PENDING_RENEW_MS),
|
|
93
|
+
};
|
|
94
|
+
try {
|
|
95
|
+
const lock = controlDir(store, "prune.lock");
|
|
96
|
+
// A holder verified once (by kernel birth) needs only a PID check per poll.
|
|
97
|
+
let verified;
|
|
98
|
+
const held = () => {
|
|
99
|
+
const owner = existsSync(lock) ? readLockOwner(lock) : null;
|
|
100
|
+
if (owner?.nonce && owner.nonce === verified && processAlive(Number(owner.pid)))
|
|
101
|
+
return true;
|
|
102
|
+
const live = pruneLockHeld(lock);
|
|
103
|
+
verified = live ? owner?.nonce : undefined;
|
|
104
|
+
return live;
|
|
105
|
+
};
|
|
106
|
+
for (let waited = 0; held(); waited += 100) {
|
|
107
|
+
if (waited > PRUNE_WAIT_MS)
|
|
108
|
+
throw new WorkspaceError("object store prune did not finish in time");
|
|
109
|
+
await sleep(100);
|
|
110
|
+
}
|
|
111
|
+
return pending;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
noteCleanupFault(error, endPendingRoots(pending));
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* End a pending lease. Never throws: what the leased operation answered stays its
|
|
120
|
+
* answer, and its caller's remaining cleanup still runs. A record that cannot be
|
|
121
|
+
* removed (a directory that refuses the unlink) is not released: it stays, naming
|
|
122
|
+
* this process, and a prune counts it as it counts any record (see
|
|
123
|
+
* `livePendingRecords`), so it can only delay pruning. Returns that removal fault
|
|
124
|
+
* as the caller's cleanup diagnostic, else undefined.
|
|
125
|
+
*/
|
|
126
|
+
export function endPendingRoots(pending) {
|
|
127
|
+
if (!pending)
|
|
128
|
+
return undefined;
|
|
129
|
+
if (pending.renewal)
|
|
130
|
+
clearInterval(pending.renewal);
|
|
131
|
+
try {
|
|
132
|
+
rmSync(pending.path, { force: true });
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
return error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function readLockOwner(lock) {
|
|
140
|
+
try {
|
|
141
|
+
const owner = JSON.parse(readFileSync(join(lock, "owner.json"), "utf8"));
|
|
142
|
+
return owner && typeof owner === "object" ? owner : null;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** A lock appears whole (owner included) by one rename; one without an owner
|
|
149
|
+
* is debris of an interrupted writer, held only while this young. */
|
|
150
|
+
const LOCK_SETTLE_MS = 60_000;
|
|
151
|
+
/** A pruner renews its lock this often; an unverifiable holder silent for
|
|
152
|
+
* LOCK_STALE_MS is gone (a verified live holder is never stale). */
|
|
153
|
+
const LOCK_RENEW_MS = 30_000;
|
|
154
|
+
const LOCK_STALE_MS = 10 * 60_000;
|
|
155
|
+
function pruneLockHeld(lock) {
|
|
156
|
+
if (!existsSync(lock))
|
|
157
|
+
return false;
|
|
158
|
+
const owner = readLockOwner(lock);
|
|
159
|
+
if (owner === null)
|
|
160
|
+
return ageOf(lock) < LOCK_SETTLE_MS;
|
|
161
|
+
const state = ownerState(owner);
|
|
162
|
+
return (state === "alive" || (state === "unverified" && ageOf(join(lock, "owner.json")) < LOCK_STALE_MS));
|
|
163
|
+
}
|
|
164
|
+
/** Errors meaning "the lock path is taken" (a directory rename onto an existing one). */
|
|
165
|
+
const LOCK_TAKEN = new Set(["EEXIST", "ENOTEMPTY", "EPERM", "EACCES", "EISDIR"]);
|
|
166
|
+
/**
|
|
167
|
+
* Take the prune lock atomically: the owner record is written into a private
|
|
168
|
+
* directory that becomes the lock by one rename, so no reader ever sees a lock
|
|
169
|
+
* without its owner. A stale lock (dead or reused-PID owner, or ownerless
|
|
170
|
+
* debris) is moved aside by one rename — of racing reapers only one moves it —
|
|
171
|
+
* and checked to be the lock judged stale before it is removed. Returns the
|
|
172
|
+
* hold, or null when another live pruner holds the lock.
|
|
173
|
+
*/
|
|
174
|
+
export function acquirePruneLock(store) {
|
|
175
|
+
const lock = controlDir(store, "prune.lock");
|
|
176
|
+
const tmp = controlDir(store, "tmp");
|
|
177
|
+
mkdirSync(tmp, { recursive: true });
|
|
178
|
+
const nonce = randomUUID();
|
|
179
|
+
const staged = join(tmp, `prune-lock-${nonce}`);
|
|
180
|
+
mkdirSync(staged);
|
|
181
|
+
try {
|
|
182
|
+
writeFileSync(join(staged, "owner.json"), JSON.stringify({ ...selfOwner(), nonce }) + "\n");
|
|
183
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
184
|
+
// An existing (even empty) lock is never replaced by the rename below.
|
|
185
|
+
if (!existsSync(lock)) {
|
|
186
|
+
try {
|
|
187
|
+
renameSync(staged, lock);
|
|
188
|
+
const stillHeld = () => readLockOwner(lock)?.nonce === nonce;
|
|
189
|
+
const renewal = renewWhileHeld(join(lock, "owner.json"), LOCK_RENEW_MS, stillHeld);
|
|
190
|
+
return {
|
|
191
|
+
stillHeld,
|
|
192
|
+
release: () => {
|
|
193
|
+
clearInterval(renewal);
|
|
194
|
+
if (stillHeld())
|
|
195
|
+
rmSync(lock, { recursive: true, force: true });
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
if (!LOCK_TAKEN.has(error.code ?? "") || !existsSync(lock))
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (pruneLockHeld(lock))
|
|
205
|
+
return null;
|
|
206
|
+
const judged = readLockOwner(lock)?.nonce;
|
|
207
|
+
const aside = join(tmp, `prune-lock-stale-${randomUUID()}`);
|
|
208
|
+
try {
|
|
209
|
+
renameSync(lock, aside);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return null; // another pruner reaped or replaced it first
|
|
213
|
+
}
|
|
214
|
+
if (readLockOwner(aside)?.nonce !== judged) {
|
|
215
|
+
// A fresh lock replaced the stale one meanwhile: hand it back and yield.
|
|
216
|
+
try {
|
|
217
|
+
renameSync(aside, lock);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
// The slot is taken again; that holder's own checks see the change.
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
rmSync(aside, { recursive: true, force: true });
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
rmSync(staged, { recursive: true, force: true });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/** Without a readable kernel birth a pending record is live this long after
|
|
233
|
+
* its last renewal, so a leaked record can delay pruning but never block it. */
|
|
234
|
+
const PENDING_MAX_AGE_MS = 60 * 60 * 1000;
|
|
235
|
+
const PENDING_RENEW_MS = 60_000;
|
|
236
|
+
export function livePendingRecords(store, graceMs) {
|
|
237
|
+
const dir = controlDir(store, "pending");
|
|
238
|
+
// Only ENOENT and ENOTDIR say there are none: an unreadable one may hold a live lease.
|
|
239
|
+
if (!existsStrict(dir))
|
|
240
|
+
return [];
|
|
241
|
+
const live = [];
|
|
242
|
+
for (const name of readdirSync(dir)) {
|
|
243
|
+
const path = join(dir, name);
|
|
244
|
+
try {
|
|
245
|
+
const record = JSON.parse(readFileSync(path, "utf8"));
|
|
246
|
+
const age = Date.now() - statSync(path).mtimeMs;
|
|
247
|
+
// A running import has no wall-clock limit: its record stays live while
|
|
248
|
+
// its own process (PID and kernel birth) runs. A dead owner's or a
|
|
249
|
+
// reused PID's record is reclaimed once past the grace window.
|
|
250
|
+
const state = ownerState(record);
|
|
251
|
+
if (age < graceMs ||
|
|
252
|
+
state === "alive" ||
|
|
253
|
+
(state === "unverified" && age < PENDING_MAX_AGE_MS))
|
|
254
|
+
live.push(record.roots ?? []);
|
|
255
|
+
else
|
|
256
|
+
unlinkSync(path);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// A record being written is young: it counts as live. An unreadable one
|
|
260
|
+
// past the maximum age is debris and must not block pruning forever.
|
|
261
|
+
const age = (() => {
|
|
262
|
+
try {
|
|
263
|
+
return Date.now() - statSync(path).mtimeMs;
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
})();
|
|
269
|
+
if (age < PENDING_MAX_AGE_MS)
|
|
270
|
+
live.push([]);
|
|
271
|
+
else
|
|
272
|
+
rmSync(path, { force: true });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return live;
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=object-store-leases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-store-leases.js","sourceRoot":"","sources":["../src/object-store-leases.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { GitDeliveryPair } from "@claudexor/schema";
|
|
2
|
+
/**
|
|
3
|
+
* Durable custody: a root record names the objects it keeps and the path that
|
|
4
|
+
* owns them (a run directory, a thread directory, an envelope, an anchor). It
|
|
5
|
+
* is live while its owner exists, so retention of the owner is retention of its
|
|
6
|
+
* objects, and roots are durable the moment an import is verified (no caller
|
|
7
|
+
* has to finish later). A run directory retention retired keeps only
|
|
8
|
+
* `tombstone.yaml`: its payload is gone, and so is its custody. A record appears
|
|
9
|
+
* whole by one rename: one torn by a crash or a full disk would refuse every
|
|
10
|
+
* later prune of its project.
|
|
11
|
+
*/
|
|
12
|
+
export declare function writeRootRecord(store: string, owner: string, roots: string[], pair?: GitDeliveryPair): string;
|
|
13
|
+
/**
|
|
14
|
+
* The newest delivery pair kept under `owner` whose post-tree is `postTree`:
|
|
15
|
+
* a delivery retains its pair before it writes, so a replay that finds the
|
|
16
|
+
* result already applied (its caller died before recording it) recovers the
|
|
17
|
+
* pair revert needs. Null when no such record exists; a record directory that
|
|
18
|
+
* cannot be looked at throws, since it may hold that pair.
|
|
19
|
+
*/
|
|
20
|
+
export declare function retainedPairOf(store: string, owner: string, postTree: string): GitDeliveryPair | null;
|
|
21
|
+
/** Roots of every record whose owner still holds custody; dead records are
|
|
22
|
+
* removed unless `dryRun`. `retired` names owners a retention pass retires now
|
|
23
|
+
* (a dry run has not written their tombstones, yet must count the same set). */
|
|
24
|
+
export declare function liveRootRecords(store: string, dryRun?: boolean, retired?: ReadonlySet<string>): string[];
|
|
25
|
+
//# sourceMappingURL=object-store-roots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-store-roots.d.ts","sourceRoot":"","sources":["../src/object-store-roots.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAMpD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,CAAC,EAAE,eAAe,GACrB,MAAM,CAuBR;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,eAAe,GAAG,IAAI,CAkBxB;AAcD;;gFAEgF;AAChF,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,MAAM,UAAQ,EACd,OAAO,GAAE,WAAW,CAAC,MAAM,CAAa,GACvC,MAAM,EAAE,CAoBV"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { GitDeliveryPair } from "@claudexor/schema";
|
|
5
|
+
import { WorkspaceError } from "@claudexor/core";
|
|
6
|
+
import { existsStrict } from "./fs-presence.js";
|
|
7
|
+
import { controlDir } from "./object-store-leases.js";
|
|
8
|
+
import { runFilesExpired } from "./run-expiry.js";
|
|
9
|
+
/**
|
|
10
|
+
* Durable custody: a root record names the objects it keeps and the path that
|
|
11
|
+
* owns them (a run directory, a thread directory, an envelope, an anchor). It
|
|
12
|
+
* is live while its owner exists, so retention of the owner is retention of its
|
|
13
|
+
* objects, and roots are durable the moment an import is verified (no caller
|
|
14
|
+
* has to finish later). A run directory retention retired keeps only
|
|
15
|
+
* `tombstone.yaml`: its payload is gone, and so is its custody. A record appears
|
|
16
|
+
* whole by one rename: one torn by a crash or a full disk would refuse every
|
|
17
|
+
* later prune of its project.
|
|
18
|
+
*/
|
|
19
|
+
export function writeRootRecord(store, owner, roots, pair) {
|
|
20
|
+
const dir = controlDir(store, "roots");
|
|
21
|
+
const tmp = controlDir(store, "tmp");
|
|
22
|
+
mkdirSync(dir, { recursive: true });
|
|
23
|
+
mkdirSync(tmp, { recursive: true });
|
|
24
|
+
const name = `${Date.now()}-${process.pid}-${randomUUID()}.json`;
|
|
25
|
+
const staged = join(tmp, `root-${name}`);
|
|
26
|
+
try {
|
|
27
|
+
writeFileSync(staged, JSON.stringify({
|
|
28
|
+
owner,
|
|
29
|
+
roots,
|
|
30
|
+
created_at: new Date().toISOString(),
|
|
31
|
+
...(pair ? { pair } : {}),
|
|
32
|
+
}) + "\n", { mode: 0o600 });
|
|
33
|
+
renameSync(staged, join(dir, name));
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
rmSync(staged, { force: true });
|
|
37
|
+
}
|
|
38
|
+
return join(dir, name);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The newest delivery pair kept under `owner` whose post-tree is `postTree`:
|
|
42
|
+
* a delivery retains its pair before it writes, so a replay that finds the
|
|
43
|
+
* result already applied (its caller died before recording it) recovers the
|
|
44
|
+
* pair revert needs. Null when no such record exists; a record directory that
|
|
45
|
+
* cannot be looked at throws, since it may hold that pair.
|
|
46
|
+
*/
|
|
47
|
+
export function retainedPairOf(store, owner, postTree) {
|
|
48
|
+
const dir = controlDir(store, "roots");
|
|
49
|
+
if (!existsStrict(dir))
|
|
50
|
+
return null;
|
|
51
|
+
let newest = null;
|
|
52
|
+
for (const name of readdirSync(dir)) {
|
|
53
|
+
let record;
|
|
54
|
+
try {
|
|
55
|
+
record = JSON.parse(readFileSync(join(dir, name), "utf8"));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (record.owner !== owner || record.pair === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
const pair = GitDeliveryPair.safeParse(record.pair);
|
|
63
|
+
if (!pair.success || pair.data.post_tree !== postTree)
|
|
64
|
+
continue;
|
|
65
|
+
const at = Date.parse(String(record.created_at));
|
|
66
|
+
if (!newest || at >= newest.at)
|
|
67
|
+
newest = { at, pair: pair.data };
|
|
68
|
+
}
|
|
69
|
+
return newest?.pair ?? null;
|
|
70
|
+
}
|
|
71
|
+
/** An owner path that still holds custody: it exists and is not a retired run. */
|
|
72
|
+
function ownerHoldsCustody(owner, retired) {
|
|
73
|
+
if (retired.has(owner))
|
|
74
|
+
return false;
|
|
75
|
+
try {
|
|
76
|
+
return existsStrict(owner) && !runFilesExpired(owner);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// An owner whose presence or retirement cannot be read (only ENOENT and ENOTDIR
|
|
80
|
+
// say it is gone) keeps its objects.
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Roots of every record whose owner still holds custody; dead records are
|
|
85
|
+
* removed unless `dryRun`. `retired` names owners a retention pass retires now
|
|
86
|
+
* (a dry run has not written their tombstones, yet must count the same set). */
|
|
87
|
+
export function liveRootRecords(store, dryRun = false, retired = new Set()) {
|
|
88
|
+
const dir = controlDir(store, "roots");
|
|
89
|
+
// A record directory that cannot be looked at may name live roots: it throws.
|
|
90
|
+
if (!existsStrict(dir))
|
|
91
|
+
return [];
|
|
92
|
+
const roots = [];
|
|
93
|
+
for (const name of readdirSync(dir)) {
|
|
94
|
+
const path = join(dir, name);
|
|
95
|
+
let record;
|
|
96
|
+
try {
|
|
97
|
+
record = JSON.parse(readFileSync(path, "utf8"));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Unreadable custody is never treated as absent: keep everything it could name.
|
|
101
|
+
throw new WorkspaceError(`object store root record ${path} is unreadable; pruning refused`);
|
|
102
|
+
}
|
|
103
|
+
if (typeof record.owner === "string" && ownerHoldsCustody(record.owner, retired)) {
|
|
104
|
+
if (Array.isArray(record.roots))
|
|
105
|
+
for (const root of record.roots)
|
|
106
|
+
if (typeof root === "string")
|
|
107
|
+
roots.push(root);
|
|
108
|
+
}
|
|
109
|
+
else if (!dryRun)
|
|
110
|
+
unlinkSync(path);
|
|
111
|
+
}
|
|
112
|
+
return roots;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=object-store-roots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-store-roots.js","sourceRoot":"","sources":["../src/object-store-roots.ts"],"names":[],"mappings":"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"}
|