@apexops/kernel 0.10.0-next.4 → 0.11.0-next.0
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/README.md +28 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/directory-lock.d.ts +118 -0
- package/dist/directory-lock.d.ts.map +1 -0
- package/dist/directory-lock.js +587 -0
- package/dist/directory-lock.js.map +1 -0
- package/dist/files.d.ts +63 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +158 -2
- package/dist/files.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/object-store.js +2 -2
- package/dist/object-store.js.map +1 -1
- package/dist/project-store.d.ts +2 -1
- package/dist/project-store.d.ts.map +1 -1
- package/dist/project-store.js +1 -0
- package/dist/project-store.js.map +1 -1
- package/dist/repeat-files.d.ts +66 -0
- package/dist/repeat-files.d.ts.map +1 -0
- package/dist/repeat-files.js +214 -0
- package/dist/repeat-files.js.map +1 -0
- package/dist/repeat-guard.d.ts +197 -0
- package/dist/repeat-guard.d.ts.map +1 -0
- package/dist/repeat-guard.js +462 -0
- package/dist/repeat-guard.js.map +1 -0
- package/dist/review-capture.d.ts +170 -0
- package/dist/review-capture.d.ts.map +1 -0
- package/dist/review-capture.js +570 -0
- package/dist/review-capture.js.map +1 -0
- package/dist/run-repository.d.ts +52 -2
- package/dist/run-repository.d.ts.map +1 -1
- package/dist/run-repository.js +198 -52
- package/dist/run-repository.js.map +1 -1
- package/dist/tasks.d.ts.map +1 -1
- package/dist/tasks.js +24 -2
- package/dist/tasks.js.map +1 -1
- package/dist/test/files.test.d.ts +2 -0
- package/dist/test/files.test.d.ts.map +1 -0
- package/dist/test/files.test.js +140 -0
- package/dist/test/files.test.js.map +1 -0
- package/dist/test/policies.test.js +1 -1
- package/dist/test/policies.test.js.map +1 -1
- package/dist/test/repeat-guard.test.d.ts +2 -0
- package/dist/test/repeat-guard.test.d.ts.map +1 -0
- package/dist/test/repeat-guard.test.js +1106 -0
- package/dist/test/repeat-guard.test.js.map +1 -0
- package/dist/test/review-capture.test.d.ts +2 -0
- package/dist/test/review-capture.test.d.ts.map +1 -0
- package/dist/test/review-capture.test.js +291 -0
- package/dist/test/review-capture.test.js.map +1 -0
- package/dist/test/run-repository.test.js +451 -7
- package/dist/test/run-repository.test.js.map +1 -1
- package/dist/test/workflow.test.js +32 -1
- package/dist/test/workflow.test.js.map +1 -1
- package/dist/test/writer-transfer.test.js +6 -1
- package/dist/test/writer-transfer.test.js.map +1 -1
- package/dist/validator-registry.d.ts.map +1 -1
- package/dist/validator-registry.js +10 -1
- package/dist/validator-registry.js.map +1 -1
- package/dist/workflow-validator-ownership.d.ts.map +1 -1
- package/dist/workflow-validator-ownership.js +2 -1
- package/dist/workflow-validator-ownership.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { lstat, open, readdir } from "node:fs/promises";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { sha256Json } from "./canonical.js";
|
|
6
|
+
import { retrySharingViolations } from "./files.js";
|
|
7
|
+
/** Most entries a repeat file snapshot covers; a larger tree fails the snapshot, which disables replay and storage. */
|
|
8
|
+
export const MAX_REPEAT_FILE_ENTRIES = 20_000;
|
|
9
|
+
/** Most file bytes a repeat file snapshot hashes; a larger tree fails the snapshot, which disables replay and storage. */
|
|
10
|
+
export const MAX_REPEAT_FILE_BYTES = 64 * 1024 * 1024;
|
|
11
|
+
const HASH_CHUNK_BYTES = 64 * 1024;
|
|
12
|
+
const HASH_ATTEMPTS = 3;
|
|
13
|
+
/** A bound tree is too large or kept changing while it was read, so it cannot be bound by content. */
|
|
14
|
+
export class RepeatFileSnapshotError extends Error {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "RepeatFileSnapshotError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function within(root, path) {
|
|
21
|
+
const offset = relative(root, path);
|
|
22
|
+
return offset === "" || (offset !== ".." && !offset.startsWith(`..${sep}`) && !isAbsolute(offset));
|
|
23
|
+
}
|
|
24
|
+
function statKey(metadata) {
|
|
25
|
+
return [metadata.dev, metadata.ino, metadata.mode, metadata.size, metadata.mtimeNs, metadata.ctimeNs].join(":");
|
|
26
|
+
}
|
|
27
|
+
function missing(error) {
|
|
28
|
+
const code = error.code;
|
|
29
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Reads the trees under `roots` and hashes every regular file with streaming SHA-256. Roots nested in another root are
|
|
33
|
+
* read once. Symbolic links below a root are bound by their own stat and never followed; a root that is a symbolic
|
|
34
|
+
* link fails the snapshot. A file that keeps changing while it is hashed, or a tree over the entry or byte budget,
|
|
35
|
+
* fails the snapshot with {@link RepeatFileSnapshotError}. Windows sharing violations are retried.
|
|
36
|
+
*/
|
|
37
|
+
export async function snapshotRepeatFiles(roots, options = {}) {
|
|
38
|
+
const maxEntries = options.maxEntries ?? MAX_REPEAT_FILE_ENTRIES;
|
|
39
|
+
const maxBytes = options.maxBytes ?? MAX_REPEAT_FILE_BYTES;
|
|
40
|
+
const retry = {
|
|
41
|
+
...(options.platform === undefined ? {} : { platform: options.platform }),
|
|
42
|
+
...(options.attempts === undefined ? {} : { attempts: options.attempts }),
|
|
43
|
+
...(options.sleep === undefined ? {} : { sleep: options.sleep }),
|
|
44
|
+
};
|
|
45
|
+
const unique = [...new Set(roots.map((root) => resolve(root)))].sort();
|
|
46
|
+
const entries = {};
|
|
47
|
+
let count = 0;
|
|
48
|
+
let bytes = 0;
|
|
49
|
+
const add = (path, entry) => {
|
|
50
|
+
if (++count > maxEntries)
|
|
51
|
+
throw new RepeatFileSnapshotError("Repeat-bound files exceed their entry budget");
|
|
52
|
+
entries[path] = entry;
|
|
53
|
+
};
|
|
54
|
+
const hash = async (path) => {
|
|
55
|
+
for (let attempt = 1;; attempt += 1) {
|
|
56
|
+
let handle;
|
|
57
|
+
try {
|
|
58
|
+
handle = await retrySharingViolations(() => open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)), retry);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (missing(error))
|
|
62
|
+
return undefined;
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const before = await handle.stat({ bigint: true });
|
|
67
|
+
if (!before.isFile())
|
|
68
|
+
throw new RepeatFileSnapshotError("A repeat-bound file changed type while it was read");
|
|
69
|
+
const digest = createHash("sha256");
|
|
70
|
+
const buffer = Buffer.allocUnsafe(HASH_CHUNK_BYTES);
|
|
71
|
+
let read = 0;
|
|
72
|
+
for (;;) {
|
|
73
|
+
const { bytesRead } = await retrySharingViolations(() => handle.read(buffer, 0, buffer.byteLength, null), retry);
|
|
74
|
+
if (bytesRead === 0)
|
|
75
|
+
break;
|
|
76
|
+
read += bytesRead;
|
|
77
|
+
if (bytes + read > maxBytes)
|
|
78
|
+
throw new RepeatFileSnapshotError("Repeat-bound files exceed their byte budget");
|
|
79
|
+
digest.update(buffer.subarray(0, bytesRead));
|
|
80
|
+
}
|
|
81
|
+
const after = await handle.stat({ bigint: true });
|
|
82
|
+
if (statKey(before) === statKey(after) && BigInt(read) === after.size) {
|
|
83
|
+
bytes += read;
|
|
84
|
+
return { type: "file", sha256: digest.digest("hex"), stat: statKey(after) };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await handle.close();
|
|
89
|
+
}
|
|
90
|
+
if (attempt >= HASH_ATTEMPTS)
|
|
91
|
+
throw new RepeatFileSnapshotError("A repeat-bound file kept changing while it was read");
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const visit = async (path, root) => {
|
|
95
|
+
let metadata;
|
|
96
|
+
try {
|
|
97
|
+
metadata = await retrySharingViolations(() => lstat(path, { bigint: true }), retry);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (missing(error))
|
|
101
|
+
return;
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
if (metadata.isDirectory()) {
|
|
105
|
+
add(path, { type: "directory" });
|
|
106
|
+
let names;
|
|
107
|
+
try {
|
|
108
|
+
names = await retrySharingViolations(() => readdir(path), retry);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (missing(error))
|
|
112
|
+
return;
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
for (const name of names.sort())
|
|
116
|
+
await visit(join(path, name), false);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (root && metadata.isSymbolicLink())
|
|
120
|
+
throw new RepeatFileSnapshotError("A repeat-bound root is a symbolic link");
|
|
121
|
+
if (!metadata.isFile()) {
|
|
122
|
+
add(path, { type: "other", stat: statKey(metadata) });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const entry = await hash(path);
|
|
126
|
+
if (entry !== undefined)
|
|
127
|
+
add(path, entry);
|
|
128
|
+
};
|
|
129
|
+
for (const root of unique) {
|
|
130
|
+
if (!unique.some((other) => other !== root && within(other, root)))
|
|
131
|
+
continue;
|
|
132
|
+
// A nested root is read through its enclosing root, so check it here for the symbolic-link rule.
|
|
133
|
+
try {
|
|
134
|
+
const metadata = await retrySharingViolations(() => lstat(root, { bigint: true }), retry);
|
|
135
|
+
if (metadata.isSymbolicLink())
|
|
136
|
+
throw new RepeatFileSnapshotError("A repeat-bound root is a symbolic link");
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (!missing(error))
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
for (const root of unique) {
|
|
144
|
+
if (unique.some((other) => other !== root && within(other, root)))
|
|
145
|
+
continue;
|
|
146
|
+
await visit(root, true);
|
|
147
|
+
}
|
|
148
|
+
return { roots: unique, entries };
|
|
149
|
+
}
|
|
150
|
+
/** Digest of a snapshot for the repeat state token; equal only for equal roots, paths, contents and stat identities. */
|
|
151
|
+
export function repeatFilesDigest(snapshot) {
|
|
152
|
+
return sha256Json({ version: 1, roots: snapshot.roots, entries: snapshot.entries });
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Writes a repeat-guarded operation made itself to bound files, in order. The guard compares the bound trees after the
|
|
156
|
+
* operation with their content at its start changed only by these writes.
|
|
157
|
+
*/
|
|
158
|
+
export class RepeatFileWrites {
|
|
159
|
+
#writes = [];
|
|
160
|
+
/** The operation wrote `path` with content of SHA-256 `sha256`, creating missing parent directories. */
|
|
161
|
+
file(path, sha256) {
|
|
162
|
+
if (!/^[0-9a-f]{64}$/u.test(sha256))
|
|
163
|
+
throw new TypeError("Repeat file write needs a SHA-256 content hash");
|
|
164
|
+
this.#writes.push({ path: resolve(path), sha256 });
|
|
165
|
+
}
|
|
166
|
+
/** The operation removed `path` and everything below it. */
|
|
167
|
+
removed(path) {
|
|
168
|
+
this.#writes.push({ path: resolve(path), sha256: null });
|
|
169
|
+
}
|
|
170
|
+
get writes() {
|
|
171
|
+
return this.#writes;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* True when every tree bound at `start` holds, in `end`, exactly its start content changed only by the operation's own
|
|
176
|
+
* `writes`: a file the operation wrote holds what it wrote, and every other path is unchanged in content and stat
|
|
177
|
+
* identity. A bound root missing from `end` fails the check, so `end` must be read over at least the start roots.
|
|
178
|
+
*/
|
|
179
|
+
export function repeatFilesHeld(start, end, writes) {
|
|
180
|
+
if (!start.roots.every((root) => end.roots.includes(root)))
|
|
181
|
+
return false;
|
|
182
|
+
const rootOf = (path) => start.roots.filter((root) => within(root, path)).sort((left, right) => right.length - left.length)[0];
|
|
183
|
+
const expected = new Map(Object.entries(start.entries).filter(([path]) => rootOf(path) !== undefined));
|
|
184
|
+
for (const { path, sha256 } of writes?.writes ?? []) {
|
|
185
|
+
if (sha256 === null) {
|
|
186
|
+
for (const key of [...expected.keys()])
|
|
187
|
+
if (within(path, key))
|
|
188
|
+
expected.delete(key);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const root = rootOf(path);
|
|
192
|
+
if (root === undefined)
|
|
193
|
+
continue;
|
|
194
|
+
expected.set(path, { type: "written", sha256 });
|
|
195
|
+
for (let parent = dirname(path); within(root, parent); parent = dirname(parent)) {
|
|
196
|
+
if (!expected.has(parent))
|
|
197
|
+
expected.set(parent, { type: "directory" });
|
|
198
|
+
if (parent === root)
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const actual = Object.entries(end.entries).filter(([path]) => rootOf(path) !== undefined);
|
|
203
|
+
if (actual.length !== expected.size)
|
|
204
|
+
return false;
|
|
205
|
+
return actual.every(([path, entry]) => {
|
|
206
|
+
const wanted = expected.get(path);
|
|
207
|
+
if (wanted === undefined)
|
|
208
|
+
return false;
|
|
209
|
+
if (wanted.type === "written")
|
|
210
|
+
return entry.type === "file" && entry.sha256 === wanted.sha256;
|
|
211
|
+
return sha256Json(wanted) === sha256Json(entry);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=repeat-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repeat-files.js","sourceRoot":"","sources":["../src/repeat-files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { type EventV1, type ProjectId, type RepeatGuardRecordV1, type RunId } from "@apexops/contracts";
|
|
2
|
+
import { type RepeatFileSnapshot, type RepeatFileWrites } from "./repeat-files.js";
|
|
3
|
+
/** Run-scoped file holding the bounded set of calls that may still be answered from their original result. */
|
|
4
|
+
export declare const REPEAT_GUARD_FILE = ".repeat-guard.json";
|
|
5
|
+
/** Run-scoped, hash-chained audit journal of answered repeats. Separate from the run journal, whose head it keeps. */
|
|
6
|
+
export declare const REPEAT_JOURNAL_DIRECTORY = "repeats";
|
|
7
|
+
export declare const REPEAT_EVENT_TYPE = "call.repeated";
|
|
8
|
+
/**
|
|
9
|
+
* Workspace-scoped directory lock serializing guarded calls across processes from record lookup through record
|
|
10
|
+
* publication. It does not depend on the selected run, so calls that change the selection are serialized too.
|
|
11
|
+
*/
|
|
12
|
+
export declare const REPEAT_LOCK_FILE = ".repeat-guard.lock";
|
|
13
|
+
export declare const DEFAULT_REPEAT_LOCK_WAIT_MS = 30000;
|
|
14
|
+
export declare const DEFAULT_REPEAT_WINDOW_MS: number;
|
|
15
|
+
export declare const MAX_REPEAT_RECORDS = 16;
|
|
16
|
+
export declare const MAX_REPEAT_RESULT_BYTES: number;
|
|
17
|
+
export interface RepeatCall {
|
|
18
|
+
/** Stable operation name, for example the MCP tool name. */
|
|
19
|
+
operation: string;
|
|
20
|
+
/** Worktree that issued the call; compared in canonical form. */
|
|
21
|
+
workspace: string;
|
|
22
|
+
/** JSON arguments after input validation; undefined members are dropped and keys are sorted. */
|
|
23
|
+
arguments: unknown;
|
|
24
|
+
}
|
|
25
|
+
export interface RepeatStateInput {
|
|
26
|
+
selection: {
|
|
27
|
+
projectId: string;
|
|
28
|
+
runId: string;
|
|
29
|
+
};
|
|
30
|
+
projects: readonly string[];
|
|
31
|
+
runHash: string;
|
|
32
|
+
journalHead: string | null;
|
|
33
|
+
writer: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Content digest of the mutable files the run's operations read, such as staged work and generated source trees, from
|
|
36
|
+
* {@link repeatFilesDigest}.
|
|
37
|
+
*/
|
|
38
|
+
files?: string | null;
|
|
39
|
+
}
|
|
40
|
+
/** The selected run a call applies to, with the state token that decides the repeat window. */
|
|
41
|
+
export interface RepeatScope {
|
|
42
|
+
runDirectory: string;
|
|
43
|
+
projectId: ProjectId;
|
|
44
|
+
runId: RunId;
|
|
45
|
+
ownerEpoch: number;
|
|
46
|
+
state: string;
|
|
47
|
+
/**
|
|
48
|
+
* Content of the mutable files the state token covers, read with it. The snapshot taken before a call executes binds
|
|
49
|
+
* its record: the result is stored only if those trees still hold that content afterwards, changed only by the
|
|
50
|
+
* call's own {@link RepeatSafeHooks.writes}.
|
|
51
|
+
*/
|
|
52
|
+
files?: RepeatFileSnapshot;
|
|
53
|
+
}
|
|
54
|
+
export interface RepeatSafeHooks<T> {
|
|
55
|
+
/**
|
|
56
|
+
* Machine-local workspace directory that holds the repeat lock, created when missing. While its parent does not exist
|
|
57
|
+
* there is no workspace state yet, and calls run unserialized.
|
|
58
|
+
*/
|
|
59
|
+
lockDirectory: string;
|
|
60
|
+
/** Current selected run and state token, or undefined when no run is selected. */
|
|
61
|
+
scope(): Promise<RepeatScope | undefined>;
|
|
62
|
+
/** Ownership check for an answered repeat, such as the run writer lease. Must not write state. */
|
|
63
|
+
assertReplayAllowed(scope: RepeatScope): Promise<void>;
|
|
64
|
+
execute(): Promise<T>;
|
|
65
|
+
/** Optional instant after which the original result must not be returned, such as a task expiry. */
|
|
66
|
+
validUntil?(value: T): string | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Optional check that the call identity still holds, for example that a file bound by content was not modified since
|
|
69
|
+
* the identity was taken. Before a replay, false makes the call execute instead; after execution, false returns the
|
|
70
|
+
* result without storing it.
|
|
71
|
+
*/
|
|
72
|
+
identityStable?(): Promise<boolean>;
|
|
73
|
+
/**
|
|
74
|
+
* Writes the operation makes to files the scope binds, filled while `execute` runs. Any other change to a bound file
|
|
75
|
+
* between the scope read before execution and the one after it returns the result without storing it.
|
|
76
|
+
*/
|
|
77
|
+
writes?: RepeatFileWrites;
|
|
78
|
+
}
|
|
79
|
+
export interface RepeatSafeOptions {
|
|
80
|
+
clock?: () => Date;
|
|
81
|
+
idSource?: () => string;
|
|
82
|
+
windowMs?: number;
|
|
83
|
+
maxRecords?: number;
|
|
84
|
+
/** Longest wait for another guarded call in the workspace before failing with {@link RepeatGuardBusyError}. */
|
|
85
|
+
lockWaitMs?: number;
|
|
86
|
+
/** Cancels the call while it waits for the lock, and before lookup or execution once the lock is held. */
|
|
87
|
+
signal?: AbortSignal;
|
|
88
|
+
}
|
|
89
|
+
/** Another guarded call in the workspace is still running; the caller should refresh state and retry later. */
|
|
90
|
+
/** The caller cancelled the call before it started; nothing was looked up, executed or recorded. */
|
|
91
|
+
export declare class RepeatGuardCancelledError extends Error {
|
|
92
|
+
constructor();
|
|
93
|
+
}
|
|
94
|
+
export declare class RepeatGuardBusyError extends Error {
|
|
95
|
+
constructor();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The workspace changed while its lock holder waited without the lock, so a decision taken during the wait would apply
|
|
99
|
+
* to state it was not taken against. Nothing after the wait ran; the caller should refresh state and run again.
|
|
100
|
+
*/
|
|
101
|
+
export declare class RepeatGuardStaleError extends Error {
|
|
102
|
+
constructor();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A snapshot read reached a step that needs the workspace lock, such as finishing a pending recovery. Nothing was
|
|
106
|
+
* written; {@link withRepeatGuardReadLock} discards the attempt and waits for the lock instead.
|
|
107
|
+
*/
|
|
108
|
+
export declare class RepeatGuardSnapshotUnavailableError extends Error {
|
|
109
|
+
constructor();
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A holder's share window could not be closed, so snapshot readers might still trust it. Every later atomic write and
|
|
113
|
+
* share in the holder's operation fails with this error, and so does the operation itself, even if it handled the
|
|
114
|
+
* error; releasing the lock then closes the window. The caller should run the operation again.
|
|
115
|
+
*/
|
|
116
|
+
export declare class RepeatGuardShareCloseError extends Error {
|
|
117
|
+
constructor(cause: unknown);
|
|
118
|
+
}
|
|
119
|
+
/** How {@link withRepeatGuardReadLock} runs a read: holding the workspace lock, or as a validated snapshot without it. */
|
|
120
|
+
export type RepeatGuardReadMode = "locked" | "snapshot";
|
|
121
|
+
/** The workspace lock as held by one {@link withRepeatGuardLock} operation. */
|
|
122
|
+
export interface WorkspaceLockHold {
|
|
123
|
+
/**
|
|
124
|
+
* Gives the workspace lock up while `wait` runs, for example while a command waits for interactive input, then takes
|
|
125
|
+
* it again and throws {@link RepeatGuardStaleError} unless `state` returns the token it returned before the wait.
|
|
126
|
+
* When `state` fails before the wait the lock is kept, because an unchanged state could not be proven afterwards. A
|
|
127
|
+
* workspace created during the wait is locked from then on. Not reentrant: one suspension at a time.
|
|
128
|
+
*/
|
|
129
|
+
suspend<R>(wait: () => Promise<R>, state: () => Promise<string>): Promise<R>;
|
|
130
|
+
/**
|
|
131
|
+
* Keeps the workspace lock while `operation` runs, for example a long provider, Git or Azure CLI process, but lets
|
|
132
|
+
* {@link withRepeatGuardReadLock} readers in other processes read the workspace meanwhile. The holder must not write
|
|
133
|
+
* workspace state until `operation` settles. Writers still wait. While this hold has no lock to share, because the
|
|
134
|
+
* workspace has no state yet or the hold is suspended, it runs `operation` without a share window.
|
|
135
|
+
* When the window cannot be closed afterwards it throws {@link RepeatGuardShareCloseError}, which also fences every
|
|
136
|
+
* later write of the holding operation.
|
|
137
|
+
*/
|
|
138
|
+
share<R>(operation: () => Promise<R>): Promise<R>;
|
|
139
|
+
}
|
|
140
|
+
export interface RepeatSafeOutcome<T> {
|
|
141
|
+
value: T;
|
|
142
|
+
repeated: boolean;
|
|
143
|
+
fingerprint: string;
|
|
144
|
+
}
|
|
145
|
+
/** A stored result; the persisted file is the versioned `repeat-guard-records-v1` contract. */
|
|
146
|
+
export type RepeatRecord = RepeatGuardRecordV1;
|
|
147
|
+
/** Identifies a call independent of state: operation, canonical worktree and canonical arguments. */
|
|
148
|
+
export declare function repeatCallHash(call: RepeatCall): string;
|
|
149
|
+
/** Fingerprint of a call against the run state it applies to. */
|
|
150
|
+
export declare function repeatFingerprint(callHash: string, state: string | null): string;
|
|
151
|
+
/**
|
|
152
|
+
* State token for the repeat window. It changes whenever the selection, the project set, the selected run document,
|
|
153
|
+
* its journal head, its writer lease holder or the content of its mutable files changes; lease expiry renewals do not
|
|
154
|
+
* change it.
|
|
155
|
+
*/
|
|
156
|
+
export declare function repeatStateToken(input: RepeatStateInput): string;
|
|
157
|
+
/**
|
|
158
|
+
* Reads the run's repeat records. An absent, unsafe or malformed file yields no records: losing a record only makes a
|
|
159
|
+
* repeat a new request that normal kernel checks decide, so corruption cannot replay a forged result.
|
|
160
|
+
*/
|
|
161
|
+
export declare function readRepeatRecords(runDirectory: string): Promise<RepeatRecord[]>;
|
|
162
|
+
export declare function readRepeatEvents(runDirectory: string): Promise<EventV1[]>;
|
|
163
|
+
/**
|
|
164
|
+
* Executes a state-changing call at most once per run state. An identical call (same operation, worktree and
|
|
165
|
+
* arguments) made while the selected run is unchanged since the original succeeded, and within the window, returns the
|
|
166
|
+
* original serialized result and appends a `call.repeated` audit event instead of executing again. Any intervening
|
|
167
|
+
* state change, an expired window, a failed original call or a missing record makes the call a new request. A result
|
|
168
|
+
* is stored only if the files the scope binds kept their content from the start of the call, changed only by the
|
|
169
|
+
* call's own writes, so a later repeat is never answered from a result computed on other content. Guarded
|
|
170
|
+
* calls in one workspace are serialized across processes from record lookup through record publication, including
|
|
171
|
+
* calls that create or change the selected run.
|
|
172
|
+
*/
|
|
173
|
+
export declare function executeRepeatSafe<T extends object>(call: RepeatCall, hooks: RepeatSafeHooks<T>, options?: RepeatSafeOptions): Promise<RepeatSafeOutcome<T>>;
|
|
174
|
+
/**
|
|
175
|
+
* Runs `operation` while holding the workspace repeat lock in `lockDirectory`. Guarded calls take it, and so must every
|
|
176
|
+
* other writer of the same workspace (such as state-changing CLI commands), so that no write lands between a guarded
|
|
177
|
+
* call's state reads, execution and record publication. While the parent of `lockDirectory` does not exist there is no
|
|
178
|
+
* workspace state yet, and `operation` runs unserialized. The lock is not reentrant. `operation` may give the lock up
|
|
179
|
+
* while it waits, for example for interactive input, through {@link WorkspaceLockHold.suspend}; guarded calls never do.
|
|
180
|
+
*/
|
|
181
|
+
export declare function withRepeatGuardLock<T>(lockDirectory: string, operation: (hold: WorkspaceLockHold) => Promise<T>, options?: {
|
|
182
|
+
lockWaitMs?: number;
|
|
183
|
+
signal?: AbortSignal;
|
|
184
|
+
}): Promise<T>;
|
|
185
|
+
/**
|
|
186
|
+
* Runs a read that may need the workspace lock, for example to finish a pending recovery. It takes the lock like
|
|
187
|
+
* {@link withRepeatGuardLock} when the lock is free. While another process holds the lock but shares it through
|
|
188
|
+
* {@link WorkspaceLockHold.share}, `operation` runs in `snapshot` mode without the lock instead, and its outcome counts
|
|
189
|
+
* only if the holder kept the same share window open throughout, so the holder wrote nothing meanwhile. In `snapshot`
|
|
190
|
+
* mode `operation` must not write workspace state; it throws {@link RepeatGuardSnapshotUnavailableError} instead, and
|
|
191
|
+
* the read waits for the lock. Fails with {@link RepeatGuardBusyError} after `lockWaitMs` without either.
|
|
192
|
+
*/
|
|
193
|
+
export declare function withRepeatGuardReadLock<T>(lockDirectory: string, operation: (mode: RepeatGuardReadMode) => Promise<T>, options?: {
|
|
194
|
+
lockWaitMs?: number;
|
|
195
|
+
signal?: AbortSignal;
|
|
196
|
+
}): Promise<T>;
|
|
197
|
+
//# sourceMappingURL=repeat-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repeat-guard.d.ts","sourceRoot":"","sources":["../src/repeat-guard.ts"],"names":[],"mappings":"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"}
|