@guilz-dev/belay 0.6.0 → 0.8.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 +39 -20
- package/dist/adapters/claude/runtime-entry.js +54 -25
- package/dist/adapters/shared/gate-runtime.d.ts +7 -0
- package/dist/adapters/shared/gate-runtime.js +443 -225
- package/dist/bundle/claude-runtime.mjs +13027 -7271
- package/dist/bundle/codex-runtime.mjs +12700 -6977
- package/dist/bundle/cursor-runtime.mjs +12695 -6977
- package/dist/cli.js +37 -1
- package/dist/commands/approve.js +34 -10
- package/dist/commands/doctor.js +58 -0
- package/dist/commands/dogfood.js +1 -1
- package/dist/commands/explain.js +2 -2
- package/dist/commands/harvest.d.ts +0 -1
- package/dist/commands/harvest.js +1 -4
- package/dist/commands/metrics.js +6 -0
- package/dist/commands/quality.js +1 -3
- package/dist/commands/recovery-checkpoints.d.ts +164 -0
- package/dist/commands/recovery-checkpoints.js +347 -0
- package/dist/commands/revoke.js +19 -7
- package/dist/commands/status.js +7 -0
- package/dist/config-io.js +49 -18
- package/dist/conformance/guarantee-table.js +137 -9
- package/dist/conformance/types.d.ts +2 -0
- package/dist/core/approval-replay.d.ts +1 -1
- package/dist/core/approval-replay.js +4 -1
- package/dist/core/approval-service.d.ts +6 -1
- package/dist/core/approval-service.js +86 -27
- package/dist/core/approval.d.ts +2 -0
- package/dist/core/approval.js +6 -3
- package/dist/core/audit-metrics.d.ts +1 -0
- package/dist/core/audit-metrics.js +12 -4
- package/dist/core/audit-types.d.ts +16 -0
- package/dist/core/capability/approval-state-mutation.d.ts +19 -0
- package/dist/core/capability/approval-state-mutation.js +76 -3
- package/dist/core/capability/approval-v3.d.ts +18 -1
- package/dist/core/capability/approval-v3.js +63 -23
- package/dist/core/capability/attestation.d.ts +3 -0
- package/dist/core/capability/attestation.js +7 -0
- package/dist/core/capability/boundary-driver-container.d.ts +12 -0
- package/dist/core/capability/boundary-driver-container.js +101 -34
- package/dist/core/capability/boundary-driver.js +1 -0
- package/dist/core/capability/boundary-grant-materialize.d.ts +1 -0
- package/dist/core/capability/boundary-grant-materialize.js +15 -0
- package/dist/core/capability/boundary-run.d.ts +25 -0
- package/dist/core/capability/boundary-run.js +37 -9
- package/dist/core/capability/boundary-session.d.ts +4 -0
- package/dist/core/capability/boundary-session.js +9 -0
- package/dist/core/capability/boundary-workspace-mount.d.ts +5 -0
- package/dist/core/capability/boundary-workspace-mount.js +68 -0
- package/dist/core/capability/gate-policy-shadow.js +23 -1
- package/dist/core/capability/grant-consumption.d.ts +4 -0
- package/dist/core/capability/grant-consumption.js +11 -0
- package/dist/core/capability/grant-lease.d.ts +36 -1
- package/dist/core/capability/grant-lease.js +213 -16
- package/dist/core/capability/grant-match.js +3 -0
- package/dist/core/capability/index.d.ts +2 -2
- package/dist/core/capability/index.js +1 -1
- package/dist/core/capability/policy-engine.d.ts +38 -1
- package/dist/core/capability/policy-engine.js +388 -15
- package/dist/core/capability/request.d.ts +12 -0
- package/dist/core/capability/resolver.d.ts +0 -1
- package/dist/core/capability/resolver.js +0 -1
- package/dist/core/config.d.ts +20 -0
- package/dist/core/config.js +62 -2
- package/dist/core/effect-ir/audit.d.ts +9 -0
- package/dist/core/effect-ir/audit.js +87 -0
- package/dist/core/effect-ir/build.d.ts +31 -0
- package/dist/core/effect-ir/build.js +281 -0
- package/dist/core/effect-ir/index.d.ts +13 -0
- package/dist/core/effect-ir/index.js +9 -0
- package/dist/core/effect-ir/normalize.d.ts +4 -0
- package/dist/core/effect-ir/normalize.js +110 -0
- package/dist/core/effect-ir/package-exec-eval.d.ts +8 -0
- package/dist/core/effect-ir/package-exec-eval.js +152 -0
- package/dist/core/effect-ir/package-exec.d.ts +29 -0
- package/dist/core/effect-ir/package-exec.js +292 -0
- package/dist/core/effect-ir/policy.d.ts +14 -0
- package/dist/core/effect-ir/policy.js +164 -0
- package/dist/core/effect-ir/shell-build.d.ts +43 -0
- package/dist/core/effect-ir/shell-build.js +77 -0
- package/dist/core/effect-ir/shell-lower.d.ts +14 -0
- package/dist/core/effect-ir/shell-lower.js +1509 -0
- package/dist/core/effect-ir/types.d.ts +62 -0
- package/dist/core/effect-ir/types.js +1 -0
- package/dist/core/egress/allowlist.d.ts +1 -0
- package/dist/core/egress/allowlist.js +8 -0
- package/dist/core/egress-approval.js +45 -51
- package/dist/core/gate-contract.d.ts +2 -0
- package/dist/core/gate-contract.js +2 -0
- package/dist/core/gate-engine.js +82 -25
- package/dist/core/git-resource-identity.d.ts +26 -0
- package/dist/core/git-resource-identity.js +284 -0
- package/dist/core/harvest.d.ts +3 -7
- package/dist/core/harvest.js +4 -27
- package/dist/core/index.d.ts +5 -1
- package/dist/core/index.js +3 -1
- package/dist/core/judge-cloud-consent.js +29 -15
- package/dist/core/network-endpoint.d.ts +10 -0
- package/dist/core/network-endpoint.js +51 -0
- package/dist/core/path-utils.js +42 -23
- package/dist/core/process-runner.d.ts +10 -0
- package/dist/core/process-runner.js +124 -0
- package/dist/core/recover-advice.js +2 -1
- package/dist/core/recovery/artifact-store.d.ts +29 -0
- package/dist/core/recovery/artifact-store.js +336 -0
- package/dist/core/recovery/checkpoint.d.ts +25 -0
- package/dist/core/recovery/checkpoint.js +260 -0
- package/dist/core/recovery/index.d.ts +3 -1
- package/dist/core/recovery/index.js +2 -0
- package/dist/core/recovery/operator-guidance.d.ts +7 -0
- package/dist/core/recovery/operator-guidance.js +39 -0
- package/dist/core/recovery/reconcile.d.ts +3 -0
- package/dist/core/recovery/reconcile.js +48 -0
- package/dist/core/recovery/resource-identity.d.ts +4 -0
- package/dist/core/recovery/resource-identity.js +34 -0
- package/dist/core/recovery/restore.d.ts +13 -0
- package/dist/core/recovery/restore.js +114 -0
- package/dist/core/recovery/snapshot-node.d.ts +27 -0
- package/dist/core/recovery/snapshot-node.js +261 -0
- package/dist/core/recovery/types.d.ts +85 -1
- package/dist/core/shell-substitution.d.ts +1 -0
- package/dist/core/shell-substitution.js +61 -0
- package/dist/core/standing-allow.d.ts +1 -2
- package/dist/core/standing-allow.js +3 -20
- package/dist/core/transactional/apply-observed-changes.d.ts +13 -0
- package/dist/core/transactional/apply-observed-changes.js +275 -0
- package/dist/core/transactional/backend-selector.d.ts +8 -0
- package/dist/core/transactional/backend-selector.js +118 -0
- package/dist/core/transactional/backend.d.ts +55 -0
- package/dist/core/transactional/backend.js +1 -0
- package/dist/core/transactional/file-checkpoint-backend.d.ts +3 -0
- package/dist/core/transactional/file-checkpoint-backend.js +278 -0
- package/dist/core/transactional/file-checkpoint-git.d.ts +11 -0
- package/dist/core/transactional/file-checkpoint-git.js +217 -0
- package/dist/core/transactional/file-checkpoint-staging.d.ts +13 -0
- package/dist/core/transactional/file-checkpoint-staging.js +54 -0
- package/dist/core/transactional/file-clone.d.ts +15 -0
- package/dist/core/transactional/file-clone.js +139 -0
- package/dist/core/transactional/file-tree-path.d.ts +7 -0
- package/dist/core/transactional/file-tree-path.js +50 -0
- package/dist/core/transactional/file-tree.d.ts +41 -0
- package/dist/core/transactional/file-tree.js +186 -0
- package/dist/core/transactional/git-worktree-backend.d.ts +2 -0
- package/dist/core/transactional/git-worktree-backend.js +50 -0
- package/dist/core/transactional/git-worktree.d.ts +9 -11
- package/dist/core/transactional/git-worktree.js +24 -119
- package/dist/core/transactional/index.d.ts +11 -0
- package/dist/core/transactional/index.js +10 -0
- package/dist/core/transactional/reasons.js +1 -0
- package/dist/core/transactional/runner.js +157 -25
- package/dist/core/transactional/snapshot-node.d.ts +26 -0
- package/dist/core/transactional/snapshot-node.js +87 -0
- package/dist/core/transactional/types.d.ts +17 -1
- package/dist/core/types.d.ts +13 -1
- package/dist/core/verdict/adapter.js +23 -15
- package/dist/core/verdict/containment.js +4 -0
- package/dist/core/verdict/egress-classify.d.ts +12 -0
- package/dist/core/verdict/egress-classify.js +746 -0
- package/dist/core/verdict/git-classifier.d.ts +12 -0
- package/dist/core/verdict/git-classifier.js +320 -0
- package/dist/core/verdict/launcher-resolve.js +79 -37
- package/dist/core/verdict/parser.d.ts +2 -0
- package/dist/core/verdict/parser.js +95 -1
- package/dist/core/verdict/types.d.ts +6 -2
- package/dist/core/verdict/verdict.js +103 -935
- package/dist/corpus/must-allow-commands.d.ts +2 -1
- package/dist/corpus/must-allow-commands.js +2 -1
- package/dist/corpus/runtime-match.d.ts +2 -1
- package/dist/corpus/runtime-match.js +2 -1
- package/dist/corpus/types.d.ts +2 -2
- package/dist/templates.js +7 -2
- package/dist/types.d.ts +11 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/skills/belay/SKILL.md +8 -3
- package/skills/belay/belay-approve.md +10 -7
- package/dist/core/verdict/overrides.d.ts +0 -7
- package/dist/core/verdict/overrides.js +0 -37
- package/dist/core/verdict/shell-policy.d.ts +0 -25
- package/dist/core/verdict/shell-policy.js +0 -40
- package/dist/corpus/standing-allow-catalog.generated.d.ts +0 -13
- package/dist/corpus/standing-allow-catalog.generated.js +0 -99
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
4
|
-
import { copyFile, mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
5
2
|
import os from 'node:os';
|
|
6
3
|
import path from 'node:path';
|
|
7
4
|
import { canonicalPath, isPathOutsideRoot, pathWithinRoot } from '../path-utils.js';
|
|
8
|
-
|
|
5
|
+
import { runProcessWithBoundedOutput } from '../process-runner.js';
|
|
6
|
+
import { applyObservedChanges, buildObservedChangesFromTransactional, TRANSACTIONAL_APPLY_ROLLBACK_FAILED, TRANSACTIONAL_APPLY_TOCTOU, } from './apply-observed-changes.js';
|
|
7
|
+
export { TRANSACTIONAL_APPLY_ROLLBACK_FAILED, TRANSACTIONAL_APPLY_TOCTOU };
|
|
9
8
|
function execGit(repoRoot, args) {
|
|
10
9
|
return new Promise((resolve, reject) => {
|
|
11
10
|
const child = spawn('git', ['-C', repoRoot, ...args], {
|
|
@@ -29,7 +28,7 @@ function porcelainRelativePath(line) {
|
|
|
29
28
|
if (line.length < 4) {
|
|
30
29
|
return null;
|
|
31
30
|
}
|
|
32
|
-
const relativePath = line.slice(3)
|
|
31
|
+
const relativePath = line.slice(3);
|
|
33
32
|
return relativePath || null;
|
|
34
33
|
}
|
|
35
34
|
function isIgnoredDirtyPath(repoRoot, relativePath, ignoreRoots) {
|
|
@@ -41,22 +40,6 @@ function isIgnoredDirtyPath(repoRoot, relativePath, ignoreRoots) {
|
|
|
41
40
|
root === absolutePath ||
|
|
42
41
|
pathWithinRoot(absolutePath, root));
|
|
43
42
|
}
|
|
44
|
-
export async function hashRepoFile(filePath) {
|
|
45
|
-
const content = await readFile(filePath);
|
|
46
|
-
return createHash('sha256').update(content).digest('hex');
|
|
47
|
-
}
|
|
48
|
-
export async function captureRepoFileHashes(repoRoot, changes) {
|
|
49
|
-
const hashes = new Map();
|
|
50
|
-
for (const change of changes) {
|
|
51
|
-
const target = path.join(repoRoot, change.relativePath);
|
|
52
|
-
if (!existsSync(target)) {
|
|
53
|
-
hashes.set(change.relativePath, null);
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
hashes.set(change.relativePath, await hashRepoFile(target));
|
|
57
|
-
}
|
|
58
|
-
return hashes;
|
|
59
|
-
}
|
|
60
43
|
export async function isGitWorktreeAvailable(repoRoot) {
|
|
61
44
|
try {
|
|
62
45
|
await execGit(repoRoot, ['rev-parse', '--git-dir']);
|
|
@@ -91,6 +74,7 @@ export async function isDirtyWorktree(repoRoot, options) {
|
|
|
91
74
|
}
|
|
92
75
|
}
|
|
93
76
|
export async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
|
|
77
|
+
const { mkdtemp, rm } = await import('node:fs/promises');
|
|
94
78
|
const worktreePath = await mkdtemp(path.join(os.tmpdir(), 'belay-tx-'));
|
|
95
79
|
await execGit(repoRoot, ['worktree', 'add', '--detach', worktreePath, 'HEAD']);
|
|
96
80
|
return {
|
|
@@ -123,38 +107,14 @@ export function resolveWorktreeCwd(repoRoot, worktreePath, cwd) {
|
|
|
123
107
|
return path.join(worktreePath, relative);
|
|
124
108
|
}
|
|
125
109
|
export function runShellCommand(command, cwd, timeoutMs) {
|
|
126
|
-
return
|
|
127
|
-
const child = spawn(command, {
|
|
128
|
-
cwd,
|
|
129
|
-
shell: true,
|
|
130
|
-
stdio: 'ignore',
|
|
131
|
-
env: process.env,
|
|
132
|
-
});
|
|
133
|
-
let timedOut = false;
|
|
134
|
-
const timer = setTimeout(() => {
|
|
135
|
-
timedOut = true;
|
|
136
|
-
child.kill('SIGTERM');
|
|
137
|
-
}, timeoutMs);
|
|
138
|
-
child.on('error', () => {
|
|
139
|
-
clearTimeout(timer);
|
|
140
|
-
resolve({ exitCode: 1, signal: null, timedOut });
|
|
141
|
-
});
|
|
142
|
-
child.on('close', (exitCode, signal) => {
|
|
143
|
-
clearTimeout(timer);
|
|
144
|
-
resolve({
|
|
145
|
-
exitCode,
|
|
146
|
-
signal: signal ? String(signal) : null,
|
|
147
|
-
timedOut,
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
});
|
|
110
|
+
return runProcessWithBoundedOutput(command, [], { cwd, shell: true, env: process.env }, timeoutMs);
|
|
151
111
|
}
|
|
152
112
|
function parseStatusLine(line) {
|
|
153
113
|
if (line.length < 4) {
|
|
154
114
|
return null;
|
|
155
115
|
}
|
|
156
116
|
const status = line.slice(0, 2);
|
|
157
|
-
const relativePath = line.slice(3)
|
|
117
|
+
const relativePath = line.slice(3);
|
|
158
118
|
if (!relativePath) {
|
|
159
119
|
return null;
|
|
160
120
|
}
|
|
@@ -170,11 +130,17 @@ function parseStatusLine(line) {
|
|
|
170
130
|
return { relativePath, kind: 'modified' };
|
|
171
131
|
}
|
|
172
132
|
export async function collectWorktreeChanges(worktreePath) {
|
|
173
|
-
const status = await execGit(worktreePath, [
|
|
133
|
+
const status = await execGit(worktreePath, [
|
|
134
|
+
'status',
|
|
135
|
+
'--porcelain=v1',
|
|
136
|
+
'-z',
|
|
137
|
+
'-uall',
|
|
138
|
+
'--no-renames',
|
|
139
|
+
]);
|
|
174
140
|
const changes = [];
|
|
175
141
|
const seen = new Set();
|
|
176
|
-
for (const line of status.split('\
|
|
177
|
-
if (!line
|
|
142
|
+
for (const line of status.split('\0')) {
|
|
143
|
+
if (!line) {
|
|
178
144
|
continue;
|
|
179
145
|
}
|
|
180
146
|
const change = parseStatusLine(line);
|
|
@@ -186,74 +152,13 @@ export async function collectWorktreeChanges(worktreePath) {
|
|
|
186
152
|
}
|
|
187
153
|
return changes;
|
|
188
154
|
}
|
|
189
|
-
async function rollbackAppliedChanges(actions) {
|
|
190
|
-
for (const action of [...actions].reverse()) {
|
|
191
|
-
try {
|
|
192
|
-
if (action.type === 'restore') {
|
|
193
|
-
await mkdir(path.dirname(action.target), { recursive: true });
|
|
194
|
-
await copyFile(action.backupPath, action.target);
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
await rm(action.target, { force: true });
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
catch {
|
|
201
|
-
// best effort
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
async function assertRepoFilesUnchanged(repoRoot, changes, baseHashes) {
|
|
206
|
-
for (const change of changes) {
|
|
207
|
-
const target = path.join(repoRoot, change.relativePath);
|
|
208
|
-
const expected = baseHashes.get(change.relativePath);
|
|
209
|
-
const exists = existsSync(target);
|
|
210
|
-
if (expected === null) {
|
|
211
|
-
if (exists) {
|
|
212
|
-
throw new Error(TRANSACTIONAL_APPLY_TOCTOU);
|
|
213
|
-
}
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (!exists) {
|
|
217
|
-
throw new Error(TRANSACTIONAL_APPLY_TOCTOU);
|
|
218
|
-
}
|
|
219
|
-
const current = await hashRepoFile(target);
|
|
220
|
-
if (current !== expected) {
|
|
221
|
-
throw new Error(TRANSACTIONAL_APPLY_TOCTOU);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
155
|
export async function applyWorktreeChanges(worktreePath, repoRoot, changes, options) {
|
|
226
|
-
|
|
227
|
-
await
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (existsSync(target)) {
|
|
235
|
-
const backupPath = path.join(backupRoot, change.relativePath);
|
|
236
|
-
await mkdir(path.dirname(backupPath), { recursive: true });
|
|
237
|
-
await copyFile(target, backupPath);
|
|
238
|
-
rollbackActions.push({ type: 'restore', target, backupPath });
|
|
239
|
-
}
|
|
240
|
-
else if (change.kind !== 'deleted') {
|
|
241
|
-
rollbackActions.push({ type: 'remove', target });
|
|
242
|
-
}
|
|
243
|
-
if (change.kind === 'deleted') {
|
|
244
|
-
await rm(target, { force: true });
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
const source = path.join(worktreePath, change.relativePath);
|
|
248
|
-
await mkdir(path.dirname(target), { recursive: true });
|
|
249
|
-
await copyFile(source, target);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
catch (error) {
|
|
253
|
-
await rollbackAppliedChanges(rollbackActions);
|
|
254
|
-
throw error;
|
|
255
|
-
}
|
|
256
|
-
finally {
|
|
257
|
-
await rm(backupRoot, { recursive: true, force: true });
|
|
258
|
-
}
|
|
156
|
+
const observed = options?.observedChanges ??
|
|
157
|
+
(await buildObservedChangesFromTransactional(repoRoot, worktreePath, changes));
|
|
158
|
+
await applyObservedChanges({
|
|
159
|
+
sourceRoot: worktreePath,
|
|
160
|
+
targetRoot: repoRoot,
|
|
161
|
+
changes: observed,
|
|
162
|
+
afterApply: options?.afterApply,
|
|
163
|
+
});
|
|
259
164
|
}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
export { applyObservedChanges, buildObservedChangesFromTransactional, TRANSACTIONAL_APPLY_CONFLICT, TRANSACTIONAL_APPLY_ROLLBACK_FAILED, TRANSACTIONAL_APPLY_TOCTOU, } from './apply-observed-changes.js';
|
|
2
|
+
export type { TransactionalBackend, TransactionalBackendContext, TransactionalBackendId, TransactionalBackendProbe, TransactionalBackendSelection, TransactionalSnapshot, } from './backend.js';
|
|
3
|
+
export { FILE_CHECKPOINT_DISABLED, FILE_CHECKPOINT_DURABLE_REQUIRED, FILE_CHECKPOINT_ISOLATION_UNAVAILABLE, FILE_CHECKPOINT_NON_GIT_DISABLED, FILE_CHECKPOINT_NOT_IMPLEMENTED, probeTransactionalBackends, selectTransactionalBackend, } from './backend-selector.js';
|
|
1
4
|
export { evaluateTransactionalDiff } from './diff-evaluator.js';
|
|
2
5
|
export { isTransactionalEligible } from './eligibility.js';
|
|
6
|
+
export { FILE_CHECKPOINT_PROTECTED_PATH_CHANGED, fileCheckpointBackend, } from './file-checkpoint-backend.js';
|
|
7
|
+
export { FILE_CHECKPOINT_CWD_OUTSIDE_ROOT, FILE_CHECKPOINT_GIT_METADATA_CHANGED, FILE_CHECKPOINT_SOURCE_CHANGED, FILE_CHECKPOINT_SPLIT_INDEX_UNSUPPORTED, resolveExecutionCwdRelative, } from './file-checkpoint-git.js';
|
|
8
|
+
export { collectDeadOwnerStaging, type FileCheckpointOwnerMarker, isOwnerProcessAlive, removeDeadOwnerStaging, writeOwnerMarker, } from './file-checkpoint-staging.js';
|
|
9
|
+
export { cloneDirectoryTree, type FileCloneResult, type FileCloneStrategy, probeFileCloneStrategy, } from './file-clone.js';
|
|
10
|
+
export { buildFileTreeIndex, diffFileTreeIndices, FileCheckpointDiagnosticError, type FileTreeEntry, type FileTreeIndex, type ObservedFileChange, readObservedChanges, } from './file-tree.js';
|
|
11
|
+
export { compareRelativePathsBytewise, joinRelativePath, validateRelativePath, } from './file-tree-path.js';
|
|
12
|
+
export { gitWorktreeBackend } from './git-worktree-backend.js';
|
|
3
13
|
export { TRANSACTIONAL_ALREADY_APPLIED, TRANSACTIONAL_APPROVAL_BYPASS_REASONS, TRANSACTIONAL_OBSERVED_RISK, } from './reasons.js';
|
|
4
14
|
export { runTransactionalExecution } from './runner.js';
|
|
15
|
+
export { hashDirectoryNode, hashFileContent, type PresentSnapshotNode, readSnapshotNode, type SnapshotNode, snapshotNodesEqual, } from './snapshot-node.js';
|
|
5
16
|
export type { TransactionalDiffEvaluation, TransactionalExecutionResult, TransactionalFileChange, TransactionalRunnerParams, } from './types.js';
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
+
export { applyObservedChanges, buildObservedChangesFromTransactional, TRANSACTIONAL_APPLY_CONFLICT, TRANSACTIONAL_APPLY_ROLLBACK_FAILED, TRANSACTIONAL_APPLY_TOCTOU, } from './apply-observed-changes.js';
|
|
2
|
+
export { FILE_CHECKPOINT_DISABLED, FILE_CHECKPOINT_DURABLE_REQUIRED, FILE_CHECKPOINT_ISOLATION_UNAVAILABLE, FILE_CHECKPOINT_NON_GIT_DISABLED, FILE_CHECKPOINT_NOT_IMPLEMENTED, probeTransactionalBackends, selectTransactionalBackend, } from './backend-selector.js';
|
|
1
3
|
export { evaluateTransactionalDiff } from './diff-evaluator.js';
|
|
2
4
|
export { isTransactionalEligible } from './eligibility.js';
|
|
5
|
+
export { FILE_CHECKPOINT_PROTECTED_PATH_CHANGED, fileCheckpointBackend, } from './file-checkpoint-backend.js';
|
|
6
|
+
export { FILE_CHECKPOINT_CWD_OUTSIDE_ROOT, FILE_CHECKPOINT_GIT_METADATA_CHANGED, FILE_CHECKPOINT_SOURCE_CHANGED, FILE_CHECKPOINT_SPLIT_INDEX_UNSUPPORTED, resolveExecutionCwdRelative, } from './file-checkpoint-git.js';
|
|
7
|
+
export { collectDeadOwnerStaging, isOwnerProcessAlive, removeDeadOwnerStaging, writeOwnerMarker, } from './file-checkpoint-staging.js';
|
|
8
|
+
export { cloneDirectoryTree, probeFileCloneStrategy, } from './file-clone.js';
|
|
9
|
+
export { buildFileTreeIndex, diffFileTreeIndices, FileCheckpointDiagnosticError, readObservedChanges, } from './file-tree.js';
|
|
10
|
+
export { compareRelativePathsBytewise, joinRelativePath, validateRelativePath, } from './file-tree-path.js';
|
|
11
|
+
export { gitWorktreeBackend } from './git-worktree-backend.js';
|
|
3
12
|
export { TRANSACTIONAL_ALREADY_APPLIED, TRANSACTIONAL_APPROVAL_BYPASS_REASONS, TRANSACTIONAL_OBSERVED_RISK, } from './reasons.js';
|
|
4
13
|
export { runTransactionalExecution } from './runner.js';
|
|
14
|
+
export { hashDirectoryNode, hashFileContent, readSnapshotNode, snapshotNodesEqual, } from './snapshot-node.js';
|
|
@@ -1,41 +1,90 @@
|
|
|
1
1
|
import { boundaryMountReadOnlyFromPrediction, runWithBoundaryRunnable, } from '../capability/boundary-run.js';
|
|
2
2
|
import { hostIntegrationBoundaryContext } from '../capability/boundary-session.js';
|
|
3
|
+
import { resolveGuestWorkdir } from '../capability/boundary-workspace-mount.js';
|
|
4
|
+
import { discardPreparedRecoveryCheckpoint, listRecoveryCheckpoints, markRecoveryCheckpointApplied, markRecoveryCheckpointApplying, prepareRecoveryCheckpoint, reconcileRecoveryCheckpoint, } from '../recovery/checkpoint.js';
|
|
5
|
+
import { buildObservedChangesFromTransactional } from './apply-observed-changes.js';
|
|
6
|
+
import { selectTransactionalBackend } from './backend-selector.js';
|
|
3
7
|
import { evaluateTransactionalDiff } from './diff-evaluator.js';
|
|
4
|
-
import {
|
|
8
|
+
import { FileCheckpointDiagnosticError } from './file-tree.js';
|
|
9
|
+
import { applyWorktreeChanges, resolveWorktreeCwd, TRANSACTIONAL_APPLY_ROLLBACK_FAILED, TRANSACTIONAL_APPLY_TOCTOU, } from './git-worktree.js';
|
|
5
10
|
import { TRANSACTIONAL_ALREADY_APPLIED, TRANSACTIONAL_APPLY_FAILED, TRANSACTIONAL_OBSERVED_RISK, } from './reasons.js';
|
|
6
11
|
export async function runTransactionalExecution(params) {
|
|
7
12
|
const { predicted, repoRoot, stateDir, command, cwd, timeoutMs, diffContext } = params;
|
|
8
|
-
|
|
13
|
+
const backendContext = {
|
|
14
|
+
repoRoot,
|
|
15
|
+
stateDir,
|
|
16
|
+
cwd,
|
|
17
|
+
dirtyIgnoreRoots: params.dirtyIgnoreRoots,
|
|
18
|
+
fileCheckpoint: params.fileCheckpoint,
|
|
19
|
+
durableCheckpointEnabled: params.checkpoint?.enabled === true,
|
|
20
|
+
boundaryAttestation: params.boundaryContext?.attestation ?? null,
|
|
21
|
+
boundaryAttestationFresh: params.boundaryContext?.attestationFresh ?? false,
|
|
22
|
+
boundaryDriverId: params.boundaryContext?.driverId,
|
|
23
|
+
};
|
|
24
|
+
const selection = await selectTransactionalBackend(backendContext);
|
|
25
|
+
if (!selection.backend) {
|
|
26
|
+
const skipReason = params.fileCheckpoint.enabled
|
|
27
|
+
? (selection.probe.reason ?? selection.skipReason)
|
|
28
|
+
: (selection.skipReason ?? selection.probe.reason);
|
|
9
29
|
return {
|
|
10
30
|
ok: false,
|
|
11
31
|
skipped: true,
|
|
12
|
-
skipReason: '
|
|
32
|
+
skipReason: skipReason ?? 'transactional_execution_failed',
|
|
13
33
|
predicted,
|
|
14
34
|
result: predicted,
|
|
15
35
|
};
|
|
16
36
|
}
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
if (params.checkpoint?.enabled) {
|
|
38
|
+
try {
|
|
39
|
+
await listRecoveryCheckpoints(stateDir, repoRoot);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
skipped: true,
|
|
45
|
+
skipReason: error instanceof Error ? error.message : 'recovery_checkpoint_reconcile_failed',
|
|
46
|
+
predicted,
|
|
47
|
+
result: predicted,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
25
50
|
}
|
|
26
51
|
let snapshot = null;
|
|
27
52
|
try {
|
|
28
|
-
snapshot = await
|
|
29
|
-
const
|
|
53
|
+
snapshot = await selection.backend.prepare(backendContext);
|
|
54
|
+
const snapshotAudit = {
|
|
55
|
+
transactionalBackend: snapshot.backend,
|
|
56
|
+
resourceKind: snapshot.resourceKind,
|
|
57
|
+
baselineTreeHash: snapshot.baselineTreeHash,
|
|
58
|
+
snapshotFileCount: snapshot.snapshotFileCount,
|
|
59
|
+
snapshotSourceBytes: snapshot.snapshotSourceBytes,
|
|
60
|
+
snapshotWorkspaceBytes: snapshot.snapshotWorkspaceBytes,
|
|
61
|
+
snapshotCopyStrategy: snapshot.copyStrategy,
|
|
62
|
+
snapshotPrepareMs: snapshot.snapshotPrepareMs,
|
|
63
|
+
};
|
|
64
|
+
const runOptions = snapshot.backend === 'file_checkpoint' && snapshot.executionCwdRelative !== undefined
|
|
65
|
+
? {
|
|
66
|
+
mountReadOnly: boundaryMountReadOnlyFromPrediction(predicted),
|
|
67
|
+
workspaceMount: {
|
|
68
|
+
hostSourceRoot: snapshot.executionRoot,
|
|
69
|
+
guestTargetRoot: snapshot.resourceRoot,
|
|
70
|
+
cwdRelative: snapshot.executionCwdRelative,
|
|
71
|
+
writable: true,
|
|
72
|
+
hideHostSourcePath: true,
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
: {
|
|
76
|
+
mountReadOnly: boundaryMountReadOnlyFromPrediction(predicted),
|
|
77
|
+
};
|
|
78
|
+
const execCwd = snapshot.backend === 'file_checkpoint' && runOptions.workspaceMount
|
|
79
|
+
? resolveGuestWorkdir(runOptions.workspaceMount)
|
|
80
|
+
: resolveWorktreeCwd(repoRoot, snapshot.executionRoot, cwd);
|
|
30
81
|
const boundaryContext = params.boundaryContext ?? hostIntegrationBoundaryContext(params.repoRoot);
|
|
31
82
|
const shellResult = await runWithBoundaryRunnable(boundaryContext.driver, {
|
|
32
83
|
prepareContext: boundaryContext.prepareContext,
|
|
33
84
|
command,
|
|
34
85
|
cwd: execCwd,
|
|
35
86
|
timeoutMs,
|
|
36
|
-
runOptions
|
|
37
|
-
mountReadOnly: boundaryMountReadOnlyFromPrediction(predicted),
|
|
38
|
-
},
|
|
87
|
+
runOptions,
|
|
39
88
|
});
|
|
40
89
|
if (shellResult.timedOut) {
|
|
41
90
|
return {
|
|
@@ -44,6 +93,7 @@ export async function runTransactionalExecution(params) {
|
|
|
44
93
|
skipReason: 'transactional_timed_out',
|
|
45
94
|
predicted,
|
|
46
95
|
result: predicted,
|
|
96
|
+
...snapshotAudit,
|
|
47
97
|
commandExitCode: shellResult.exitCode,
|
|
48
98
|
commandSignal: shellResult.signal,
|
|
49
99
|
timedOut: true,
|
|
@@ -56,19 +106,77 @@ export async function runTransactionalExecution(params) {
|
|
|
56
106
|
skipReason: 'transactional_command_failed',
|
|
57
107
|
predicted,
|
|
58
108
|
result: predicted,
|
|
109
|
+
...snapshotAudit,
|
|
59
110
|
commandExitCode: shellResult.exitCode,
|
|
60
111
|
commandSignal: shellResult.signal,
|
|
61
112
|
};
|
|
62
113
|
}
|
|
63
|
-
const changes = await
|
|
114
|
+
const changes = await snapshot.collectChanges();
|
|
64
115
|
const observed = evaluateTransactionalDiff(changes, diffContext);
|
|
65
116
|
if (observed.verdict === 'allow') {
|
|
66
|
-
|
|
117
|
+
await snapshot.validateSourceState?.();
|
|
118
|
+
const observedChanges = await buildObservedChangesFromTransactional(snapshot.baselineRoot ?? repoRoot, snapshot.executionRoot, changes);
|
|
119
|
+
const checkpoint = params.checkpoint?.enabled && changes.length > 0
|
|
120
|
+
? await prepareRecoveryCheckpoint({
|
|
121
|
+
stateDir,
|
|
122
|
+
repoRoot,
|
|
123
|
+
baselinePath: snapshot.baselineRoot ?? repoRoot,
|
|
124
|
+
worktreePath: snapshot.executionRoot,
|
|
125
|
+
commandFingerprint: predicted.fingerprint,
|
|
126
|
+
changes,
|
|
127
|
+
protectedRoots: diffContext.protectedRoots,
|
|
128
|
+
config: params.checkpoint,
|
|
129
|
+
backend: snapshot.backend,
|
|
130
|
+
})
|
|
131
|
+
: null;
|
|
67
132
|
try {
|
|
68
|
-
|
|
133
|
+
if (checkpoint) {
|
|
134
|
+
await markRecoveryCheckpointApplying(stateDir, checkpoint);
|
|
135
|
+
}
|
|
136
|
+
let receipt;
|
|
137
|
+
await applyWorktreeChanges(snapshot.executionRoot, repoRoot, changes, {
|
|
138
|
+
observedChanges,
|
|
139
|
+
afterApply: checkpoint
|
|
140
|
+
? async () => {
|
|
141
|
+
receipt = await markRecoveryCheckpointApplied(stateDir, checkpoint);
|
|
142
|
+
}
|
|
143
|
+
: undefined,
|
|
144
|
+
});
|
|
145
|
+
const result = {
|
|
146
|
+
...predicted,
|
|
147
|
+
verdict: 'allow',
|
|
148
|
+
reason: TRANSACTIONAL_ALREADY_APPLIED,
|
|
149
|
+
assessment: observed.assessment,
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
predicted,
|
|
154
|
+
observed,
|
|
155
|
+
result,
|
|
156
|
+
worktreePath: snapshot.executionRoot,
|
|
157
|
+
...snapshotAudit,
|
|
158
|
+
commandExitCode: shellResult.exitCode,
|
|
159
|
+
commandSignal: shellResult.signal,
|
|
160
|
+
timedOut: shellResult.timedOut,
|
|
161
|
+
...(checkpoint && receipt
|
|
162
|
+
? {
|
|
163
|
+
recoveryCheckpointId: checkpoint.checkpointId,
|
|
164
|
+
recoveryBackend: snapshot.backend,
|
|
165
|
+
recoveryProofHash: receipt.proofHash,
|
|
166
|
+
recoveryState: 'applied',
|
|
167
|
+
}
|
|
168
|
+
: {}),
|
|
169
|
+
};
|
|
69
170
|
}
|
|
70
171
|
catch (error) {
|
|
172
|
+
if (checkpoint) {
|
|
173
|
+
const recoveryState = await reconcileRecoveryCheckpoint(stateDir, checkpoint.checkpointId);
|
|
174
|
+
if (recoveryState === 'prepared') {
|
|
175
|
+
await discardPreparedRecoveryCheckpoint(stateDir, checkpoint.checkpointId);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
71
178
|
const toctou = error instanceof Error && error.message === TRANSACTIONAL_APPLY_TOCTOU;
|
|
179
|
+
const rollbackFailed = error instanceof Error && error.message === TRANSACTIONAL_APPLY_ROLLBACK_FAILED;
|
|
72
180
|
const result = {
|
|
73
181
|
...predicted,
|
|
74
182
|
verdict: 'deny_pending_approval',
|
|
@@ -79,7 +187,11 @@ export async function runTransactionalExecution(params) {
|
|
|
79
187
|
confidence: 1,
|
|
80
188
|
signals: [
|
|
81
189
|
...observed.assessment.signals,
|
|
82
|
-
|
|
190
|
+
rollbackFailed
|
|
191
|
+
? 'transactional_apply_rollback_failed'
|
|
192
|
+
: toctou
|
|
193
|
+
? 'transactional_apply_toctou'
|
|
194
|
+
: 'transactional_apply_failed',
|
|
83
195
|
],
|
|
84
196
|
},
|
|
85
197
|
};
|
|
@@ -88,7 +200,8 @@ export async function runTransactionalExecution(params) {
|
|
|
88
200
|
predicted,
|
|
89
201
|
observed,
|
|
90
202
|
result,
|
|
91
|
-
worktreePath: snapshot.
|
|
203
|
+
worktreePath: snapshot.executionRoot,
|
|
204
|
+
...snapshotAudit,
|
|
92
205
|
commandExitCode: shellResult.exitCode,
|
|
93
206
|
commandSignal: shellResult.signal,
|
|
94
207
|
timedOut: shellResult.timedOut,
|
|
@@ -97,8 +210,8 @@ export async function runTransactionalExecution(params) {
|
|
|
97
210
|
}
|
|
98
211
|
const result = {
|
|
99
212
|
...predicted,
|
|
100
|
-
verdict:
|
|
101
|
-
reason:
|
|
213
|
+
verdict: 'deny_pending_approval',
|
|
214
|
+
reason: TRANSACTIONAL_OBSERVED_RISK,
|
|
102
215
|
assessment: observed.assessment,
|
|
103
216
|
};
|
|
104
217
|
return {
|
|
@@ -106,7 +219,8 @@ export async function runTransactionalExecution(params) {
|
|
|
106
219
|
predicted,
|
|
107
220
|
observed,
|
|
108
221
|
result,
|
|
109
|
-
worktreePath: snapshot.
|
|
222
|
+
worktreePath: snapshot.executionRoot,
|
|
223
|
+
...snapshotAudit,
|
|
110
224
|
commandExitCode: shellResult.exitCode,
|
|
111
225
|
commandSignal: shellResult.signal,
|
|
112
226
|
timedOut: shellResult.timedOut,
|
|
@@ -117,13 +231,31 @@ export async function runTransactionalExecution(params) {
|
|
|
117
231
|
ok: false,
|
|
118
232
|
skipped: true,
|
|
119
233
|
skipReason: error instanceof Error ? error.message : 'transactional_execution_failed',
|
|
234
|
+
...(error instanceof FileCheckpointDiagnosticError ? { skipDetail: error.diagnostic } : {}),
|
|
120
235
|
predicted,
|
|
121
236
|
result: predicted,
|
|
237
|
+
...(snapshot
|
|
238
|
+
? {
|
|
239
|
+
transactionalBackend: snapshot.backend,
|
|
240
|
+
resourceKind: snapshot.resourceKind,
|
|
241
|
+
baselineTreeHash: snapshot.baselineTreeHash,
|
|
242
|
+
snapshotFileCount: snapshot.snapshotFileCount,
|
|
243
|
+
snapshotSourceBytes: snapshot.snapshotSourceBytes,
|
|
244
|
+
snapshotWorkspaceBytes: snapshot.snapshotWorkspaceBytes,
|
|
245
|
+
snapshotCopyStrategy: snapshot.copyStrategy,
|
|
246
|
+
snapshotPrepareMs: snapshot.snapshotPrepareMs,
|
|
247
|
+
}
|
|
248
|
+
: {}),
|
|
122
249
|
};
|
|
123
250
|
}
|
|
124
251
|
finally {
|
|
125
252
|
if (snapshot) {
|
|
126
|
-
|
|
253
|
+
try {
|
|
254
|
+
await snapshot.cleanup();
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// Cleanup is best effort and must not overturn a verified apply or observed-risk result.
|
|
258
|
+
}
|
|
127
259
|
}
|
|
128
260
|
}
|
|
129
261
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type SnapshotNode = {
|
|
2
|
+
kind: 'absent';
|
|
3
|
+
} | {
|
|
4
|
+
kind: 'file';
|
|
5
|
+
mode: number;
|
|
6
|
+
size: number;
|
|
7
|
+
hash: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'symlink';
|
|
10
|
+
target: string;
|
|
11
|
+
hash: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'directory';
|
|
14
|
+
mode: number;
|
|
15
|
+
hash: string;
|
|
16
|
+
};
|
|
17
|
+
export type PresentSnapshotNode = Exclude<SnapshotNode, {
|
|
18
|
+
kind: 'absent';
|
|
19
|
+
}>;
|
|
20
|
+
export declare function hashFileContent(filePath: string): Promise<string>;
|
|
21
|
+
export declare function hashSymlinkTarget(target: string): string;
|
|
22
|
+
export declare function hashDirectoryNode(mode: number): string;
|
|
23
|
+
export declare function hashFileNode(mode: number, contentHash: string): string;
|
|
24
|
+
export declare function snapshotNodesEqual(left: SnapshotNode, right: SnapshotNode): boolean;
|
|
25
|
+
export declare function readSnapshotNode(absolutePath: string): Promise<SnapshotNode>;
|
|
26
|
+
export declare function presentNodeKind(node: PresentSnapshotNode): 'file' | 'symlink' | 'directory';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { lstat, readlink } from 'node:fs/promises';
|
|
4
|
+
export async function hashFileContent(filePath) {
|
|
5
|
+
const hash = createHash('sha256');
|
|
6
|
+
await new Promise((resolve, reject) => {
|
|
7
|
+
const stream = createReadStream(filePath);
|
|
8
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
9
|
+
stream.on('error', reject);
|
|
10
|
+
stream.on('end', () => resolve());
|
|
11
|
+
});
|
|
12
|
+
return hash.digest('hex');
|
|
13
|
+
}
|
|
14
|
+
export function hashSymlinkTarget(target) {
|
|
15
|
+
return createHash('sha256').update(`symlink:${target}`).digest('hex');
|
|
16
|
+
}
|
|
17
|
+
export function hashDirectoryNode(mode) {
|
|
18
|
+
return createHash('sha256')
|
|
19
|
+
.update(`directory:${mode & 0o777}`)
|
|
20
|
+
.digest('hex');
|
|
21
|
+
}
|
|
22
|
+
export function hashFileNode(mode, contentHash) {
|
|
23
|
+
return createHash('sha256')
|
|
24
|
+
.update(`file:${mode & 0o777}:${contentHash}`)
|
|
25
|
+
.digest('hex');
|
|
26
|
+
}
|
|
27
|
+
export function snapshotNodesEqual(left, right) {
|
|
28
|
+
if (left.kind === 'absent' || right.kind === 'absent') {
|
|
29
|
+
return left.kind === right.kind;
|
|
30
|
+
}
|
|
31
|
+
if (left.kind !== right.kind) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
switch (left.kind) {
|
|
35
|
+
case 'file':
|
|
36
|
+
return (right.kind === 'file' &&
|
|
37
|
+
left.mode === right.mode &&
|
|
38
|
+
left.size === right.size &&
|
|
39
|
+
left.hash === right.hash);
|
|
40
|
+
case 'symlink':
|
|
41
|
+
return right.kind === 'symlink' && left.target === right.target && left.hash === right.hash;
|
|
42
|
+
case 'directory':
|
|
43
|
+
return right.kind === 'directory' && left.mode === right.mode && left.hash === right.hash;
|
|
44
|
+
default:
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function readSnapshotNode(absolutePath) {
|
|
49
|
+
let info;
|
|
50
|
+
try {
|
|
51
|
+
info = await lstat(absolutePath);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (['ENOENT', 'ENOTDIR'].includes(error.code ?? '')) {
|
|
55
|
+
return { kind: 'absent' };
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
if (info.isSymbolicLink()) {
|
|
60
|
+
const target = await readlink(absolutePath);
|
|
61
|
+
return {
|
|
62
|
+
kind: 'symlink',
|
|
63
|
+
target,
|
|
64
|
+
hash: hashSymlinkTarget(target),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (info.isFile()) {
|
|
68
|
+
const contentHash = await hashFileContent(absolutePath);
|
|
69
|
+
return {
|
|
70
|
+
kind: 'file',
|
|
71
|
+
mode: info.mode,
|
|
72
|
+
size: info.size,
|
|
73
|
+
hash: hashFileNode(info.mode, contentHash),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (info.isDirectory()) {
|
|
77
|
+
return {
|
|
78
|
+
kind: 'directory',
|
|
79
|
+
mode: info.mode,
|
|
80
|
+
hash: hashDirectoryNode(info.mode),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
throw new Error('file_checkpoint_unsupported_node');
|
|
84
|
+
}
|
|
85
|
+
export function presentNodeKind(node) {
|
|
86
|
+
return node.kind;
|
|
87
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ResolvedBoundaryDriverContext } from '../capability/boundary-session.js';
|
|
2
|
+
import type { BelayFileCheckpointConfig, BelayTransactionalConfig } from '../config.js';
|
|
2
3
|
import type { Assessment, ClassifyResult, HookVerdict } from '../types.js';
|
|
3
4
|
export type TransactionalFileChangeKind = 'added' | 'modified' | 'deleted';
|
|
4
5
|
export interface TransactionalFileChange {
|
|
@@ -18,15 +19,28 @@ export interface TransactionalExecutionResult {
|
|
|
18
19
|
ok: boolean;
|
|
19
20
|
skipped?: boolean;
|
|
20
21
|
skipReason?: string;
|
|
22
|
+
skipDetail?: string;
|
|
21
23
|
predicted: ClassifyResult;
|
|
22
24
|
observed?: TransactionalDiffEvaluation;
|
|
23
25
|
result: ClassifyResult;
|
|
24
26
|
worktreePath?: string;
|
|
27
|
+
transactionalBackend?: 'git_worktree' | 'file_checkpoint';
|
|
28
|
+
resourceKind?: 'git_repository' | 'directory';
|
|
29
|
+
baselineTreeHash?: string;
|
|
30
|
+
snapshotFileCount?: number;
|
|
31
|
+
snapshotSourceBytes?: number;
|
|
32
|
+
snapshotWorkspaceBytes?: number;
|
|
33
|
+
snapshotCopyStrategy?: 'clonefile' | 'reflink' | 'copy';
|
|
34
|
+
snapshotPrepareMs?: number;
|
|
25
35
|
commandExitCode?: number | null;
|
|
26
36
|
commandSignal?: string | null;
|
|
27
37
|
timedOut?: boolean;
|
|
38
|
+
recoveryCheckpointId?: string;
|
|
39
|
+
recoveryBackend?: 'git_worktree' | 'file_checkpoint';
|
|
40
|
+
recoveryProofHash?: string;
|
|
41
|
+
recoveryState?: 'applied';
|
|
28
42
|
}
|
|
29
|
-
export interface
|
|
43
|
+
export interface GitWorktreeSnapshot {
|
|
30
44
|
worktreePath: string;
|
|
31
45
|
cleanup: () => Promise<void>;
|
|
32
46
|
}
|
|
@@ -47,4 +61,6 @@ export interface TransactionalRunnerParams {
|
|
|
47
61
|
boundaryContext?: ResolvedBoundaryDriverContext;
|
|
48
62
|
/** Repo-local Belay paths excluded from dirty-worktree gating (init artifacts). */
|
|
49
63
|
dirtyIgnoreRoots?: string[];
|
|
64
|
+
fileCheckpoint: BelayFileCheckpointConfig;
|
|
65
|
+
checkpoint?: NonNullable<BelayTransactionalConfig['checkpoint']>;
|
|
50
66
|
}
|