@guilz-dev/belay 0.3.0 → 0.6.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 +7 -1
- package/dist/adapters/cursor/runtime-entry.js +1 -0
- package/dist/adapters/layouts/scope.js +3 -2
- package/dist/adapters/shared/gate-runtime.d.ts +12 -16
- package/dist/adapters/shared/gate-runtime.js +499 -43
- package/dist/bundle/claude-runtime.mjs +8004 -2676
- package/dist/bundle/codex-runtime.mjs +8021 -2683
- package/dist/bundle/cursor-runtime.mjs +7993 -2667
- package/dist/cli.js +180 -6
- package/dist/commands/approve.d.ts +1 -0
- package/dist/commands/approve.js +45 -26
- package/dist/commands/classify-for-report.js +1 -1
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +264 -58
- package/dist/commands/doctor.js +47 -2
- package/dist/commands/explain.js +17 -2
- package/dist/commands/harvest.d.ts +29 -0
- package/dist/commands/harvest.js +72 -0
- package/dist/commands/judge.d.ts +11 -1
- package/dist/commands/judge.js +24 -4
- package/dist/commands/metrics.js +34 -2
- package/dist/commands/quality.d.ts +38 -0
- package/dist/commands/quality.js +105 -0
- package/dist/commands/session.d.ts +20 -0
- package/dist/commands/session.js +51 -0
- package/dist/commands/simulate.d.ts +1 -0
- package/dist/commands/simulate.js +7 -2
- package/dist/commands/standing-allow.d.ts +10 -0
- package/dist/commands/standing-allow.js +26 -0
- package/dist/commands/tui.d.ts +48 -0
- package/dist/commands/tui.js +150 -0
- package/dist/config-io.js +59 -17
- package/dist/conformance/guarantee-posture.d.ts +21 -0
- package/dist/conformance/guarantee-posture.js +46 -0
- package/dist/conformance/guarantee-table.d.ts +1 -0
- package/dist/conformance/guarantee-table.js +14 -9
- package/dist/core/approval-replay-cli.d.ts +7 -0
- package/dist/core/approval-replay-cli.js +36 -0
- package/dist/core/approval-replay.d.ts +45 -0
- package/dist/core/approval-replay.js +141 -0
- package/dist/core/approval-service.d.ts +15 -0
- package/dist/core/approval-service.js +53 -4
- package/dist/core/approval.d.ts +26 -3
- package/dist/core/approval.js +61 -7
- package/dist/core/audit-analysis.d.ts +7 -1
- package/dist/core/audit-analysis.js +111 -0
- package/dist/core/audit-metrics.d.ts +7 -0
- package/dist/core/audit-metrics.js +24 -4
- package/dist/core/audit-query.d.ts +1 -0
- package/dist/core/audit-query.js +3 -0
- package/dist/core/audit-replay-context.d.ts +35 -0
- package/dist/core/audit-replay-context.js +88 -0
- package/dist/core/audit-types.d.ts +24 -1
- package/dist/core/audit-types.js +1 -1
- package/dist/core/capability/approval-state-mutation.d.ts +13 -0
- package/dist/core/capability/approval-state-mutation.js +92 -0
- package/dist/core/capability/approval-v3.d.ts +15 -0
- package/dist/core/capability/approval-v3.js +74 -0
- package/dist/core/capability/attestation.d.ts +13 -0
- package/dist/core/capability/attestation.js +55 -0
- package/dist/core/capability/boundary-attestation-sign.d.ts +21 -0
- package/dist/core/capability/boundary-attestation-sign.js +84 -0
- package/dist/core/capability/boundary-driver-container.d.ts +7 -0
- package/dist/core/capability/boundary-driver-container.js +122 -0
- package/dist/core/capability/boundary-driver.d.ts +29 -0
- package/dist/core/capability/boundary-driver.js +43 -0
- package/dist/core/capability/boundary-egress.d.ts +21 -0
- package/dist/core/capability/boundary-egress.js +109 -0
- package/dist/core/capability/boundary-grant-materialize.d.ts +13 -0
- package/dist/core/capability/boundary-grant-materialize.js +78 -0
- package/dist/core/capability/boundary-profile.d.ts +15 -0
- package/dist/core/capability/boundary-profile.js +25 -0
- package/dist/core/capability/boundary-run.d.ts +23 -0
- package/dist/core/capability/boundary-run.js +23 -0
- package/dist/core/capability/boundary-session.d.ts +57 -0
- package/dist/core/capability/boundary-session.js +129 -0
- package/dist/core/capability/capability-request-hash.d.ts +2 -0
- package/dist/core/capability/capability-request-hash.js +8 -0
- package/dist/core/capability/gate-latency-ratchet.d.ts +7 -0
- package/dist/core/capability/gate-latency-ratchet.js +18 -0
- package/dist/core/capability/gate-policy-shadow.d.ts +25 -0
- package/dist/core/capability/gate-policy-shadow.js +108 -0
- package/dist/core/capability/gate-shadow-audit.d.ts +1 -0
- package/dist/core/capability/gate-shadow-audit.js +1 -0
- package/dist/core/capability/gate-shadow-ratchet.d.ts +14 -0
- package/dist/core/capability/gate-shadow-ratchet.js +81 -0
- package/dist/core/capability/grant-lease.d.ts +9 -0
- package/dist/core/capability/grant-lease.js +46 -0
- package/dist/core/capability/grant-loader.d.ts +12 -0
- package/dist/core/capability/grant-loader.js +23 -0
- package/dist/core/capability/grant-match.d.ts +6 -0
- package/dist/core/capability/grant-match.js +93 -0
- package/dist/core/capability/grant.d.ts +16 -0
- package/dist/core/capability/grant.js +16 -0
- package/dist/core/capability/index.d.ts +10 -1
- package/dist/core/capability/index.js +8 -0
- package/dist/core/capability/limits.d.ts +7 -0
- package/dist/core/capability/limits.js +55 -0
- package/dist/core/capability/paths.d.ts +2 -2
- package/dist/core/capability/paths.js +9 -9
- package/dist/core/capability/policy-bridge.d.ts +18 -0
- package/dist/core/capability/policy-bridge.js +51 -0
- package/dist/core/capability/policy-engine.d.ts +70 -0
- package/dist/core/capability/policy-engine.js +476 -0
- package/dist/core/capability/policy-types.d.ts +20 -0
- package/dist/core/capability/policy-types.js +1 -0
- package/dist/core/capability/request.d.ts +45 -0
- package/dist/core/capability/request.js +1 -0
- package/dist/core/capability/resolver.d.ts +4 -0
- package/dist/core/capability/resolver.js +4 -0
- package/dist/core/capability/trusted-workspace-roots.d.ts +25 -0
- package/dist/core/capability/trusted-workspace-roots.js +149 -0
- package/dist/core/capability/types.d.ts +11 -1
- package/dist/core/capability-approval.d.ts +2 -1
- package/dist/core/capability-approval.js +100 -2
- package/dist/core/classify-subagent.d.ts +2 -1
- package/dist/core/classify-subagent.js +40 -22
- package/dist/core/classify-tool.js +157 -64
- package/dist/core/config.d.ts +32 -3
- package/dist/core/config.js +105 -3
- package/dist/core/gate-contract.d.ts +3 -0
- package/dist/core/gate-contract.js +3 -0
- package/dist/core/gate-engine.js +12 -7
- package/dist/core/harvest.d.ts +53 -0
- package/dist/core/harvest.js +276 -0
- package/dist/core/index.d.ts +9 -3
- package/dist/core/index.js +6 -2
- package/dist/core/judge-doctor.d.ts +3 -0
- package/dist/core/judge-doctor.js +90 -11
- package/dist/core/judge-fallback-hints.d.ts +13 -0
- package/dist/core/judge-fallback-hints.js +108 -0
- package/dist/core/path-utils.d.ts +12 -0
- package/dist/core/path-utils.js +60 -7
- package/dist/core/reclassify.d.ts +3 -0
- package/dist/core/reclassify.js +44 -14
- package/dist/core/recovery/capability.d.ts +3 -0
- package/dist/core/recovery/capability.js +22 -0
- package/dist/core/recovery/fail-closed.d.ts +6 -0
- package/dist/core/recovery/fail-closed.js +25 -0
- package/dist/core/recovery/index.d.ts +3 -0
- package/dist/core/recovery/index.js +2 -0
- package/dist/core/recovery/types.d.ts +20 -0
- package/dist/core/recovery/types.js +1 -0
- package/dist/core/replay-scrub.d.ts +9 -0
- package/dist/core/replay-scrub.js +43 -0
- package/dist/core/shell-tokenizer.d.ts +2 -0
- package/dist/core/shell-tokenizer.js +82 -23
- package/dist/core/standing-allow.d.ts +50 -0
- package/dist/core/standing-allow.js +178 -0
- package/dist/core/transactional/diff-evaluator.js +4 -1
- package/dist/core/transactional/eligibility.js +4 -0
- package/dist/core/transactional/git-worktree.d.ts +10 -3
- package/dist/core/transactional/git-worktree.js +81 -11
- package/dist/core/transactional/reasons.d.ts +1 -0
- package/dist/core/transactional/reasons.js +4 -0
- package/dist/core/transactional/runner.js +22 -6
- package/dist/core/transactional/types.d.ts +4 -0
- package/dist/core/types.d.ts +36 -3
- package/dist/core/verdict/adapter.d.ts +3 -4
- package/dist/core/verdict/adapter.js +19 -6
- package/dist/core/verdict/containment.d.ts +4 -3
- package/dist/core/verdict/containment.js +17 -12
- package/dist/core/verdict/cursor-acp-client.d.ts +67 -0
- package/dist/core/verdict/cursor-acp-client.js +345 -0
- package/dist/core/verdict/egress-classify.d.ts +2 -2
- package/dist/core/verdict/egress-classify.js +2 -2
- package/dist/core/verdict/git-classifier.d.ts +26 -0
- package/dist/core/verdict/git-classifier.js +533 -0
- package/dist/core/verdict/judge-audit.d.ts +1 -0
- package/dist/core/verdict/judge-audit.js +33 -1
- package/dist/core/verdict/judge-baseline.d.ts +20 -0
- package/dist/core/verdict/judge-baseline.js +49 -0
- package/dist/core/verdict/judge-broker-service.d.ts +42 -0
- package/dist/core/verdict/judge-broker-service.js +291 -0
- package/dist/core/verdict/judge-catalog.js +1 -0
- package/dist/core/verdict/judge-cli-fingerprint.d.ts +6 -0
- package/dist/core/verdict/judge-cli-fingerprint.js +67 -0
- package/dist/core/verdict/judge-cli.d.ts +23 -0
- package/dist/core/verdict/judge-cli.js +139 -16
- package/dist/core/verdict/judge-factory.js +13 -4
- package/dist/core/verdict/judge-provider-matrix.d.ts +20 -0
- package/dist/core/verdict/judge-provider-matrix.js +66 -0
- package/dist/core/verdict/judge-runtime-config.d.ts +44 -0
- package/dist/core/verdict/judge-runtime-config.js +96 -0
- package/dist/core/verdict/judge-session-broker.d.ts +48 -0
- package/dist/core/verdict/judge-session-broker.js +200 -0
- package/dist/core/verdict/judge-session-guard.d.ts +27 -0
- package/dist/core/verdict/judge-session-guard.js +91 -0
- package/dist/core/verdict/judge-session-kill-switch.d.ts +11 -0
- package/dist/core/verdict/judge-session-kill-switch.js +43 -0
- package/dist/core/verdict/judge-session-mutex.d.ts +9 -0
- package/dist/core/verdict/judge-session-mutex.js +23 -0
- package/dist/core/verdict/judge-shadow.d.ts +24 -0
- package/dist/core/verdict/judge-shadow.js +116 -0
- package/dist/core/verdict/judge-transport.d.ts +44 -0
- package/dist/core/verdict/judge-transport.js +268 -0
- package/dist/core/verdict/judge.d.ts +14 -14
- package/dist/core/verdict/judge.js +5 -67
- package/dist/core/verdict/launcher-resolve.d.ts +2 -0
- package/dist/core/verdict/launcher-resolve.js +16 -0
- package/dist/core/verdict/parser.js +6 -1
- package/dist/core/verdict/prescan.d.ts +15 -0
- package/dist/core/verdict/prescan.js +68 -0
- package/dist/core/verdict/shell-policy.d.ts +25 -0
- package/dist/core/verdict/shell-policy.js +40 -0
- package/dist/core/verdict/shell-semantics.d.ts +24 -0
- package/dist/core/verdict/shell-semantics.js +225 -0
- package/dist/core/verdict/types.d.ts +26 -1
- package/dist/core/verdict/verdict.js +300 -179
- package/dist/corpus/adversarial-probe.d.ts +86 -0
- package/dist/corpus/adversarial-probe.js +220 -0
- package/dist/corpus/evaluate.d.ts +45 -12
- package/dist/corpus/evaluate.js +61 -8
- package/dist/corpus/gate-latency-budget.d.ts +39 -0
- package/dist/corpus/gate-latency-budget.js +33 -0
- package/dist/corpus/gates.d.ts +35 -0
- package/dist/corpus/gates.js +81 -0
- package/dist/corpus/judge-accuracy.d.ts +8 -0
- package/dist/corpus/judge-accuracy.js +39 -0
- package/dist/corpus/must-allow-commands.d.ts +5 -0
- package/dist/corpus/must-allow-commands.js +13 -0
- package/dist/corpus/mutators.d.ts +28 -0
- package/dist/corpus/mutators.js +162 -0
- package/dist/corpus/ratchet.d.ts +42 -0
- package/dist/corpus/ratchet.js +116 -0
- package/dist/corpus/runtime-match.d.ts +19 -0
- package/dist/corpus/runtime-match.js +43 -0
- package/dist/corpus/standing-allow-catalog.generated.d.ts +13 -0
- package/dist/corpus/standing-allow-catalog.generated.js +99 -0
- package/dist/corpus/types.d.ts +47 -0
- package/dist/corpus/types.js +157 -0
- package/dist/installer.js +4 -1
- package/dist/judge-broker-daemon.d.ts +1 -0
- package/dist/judge-broker-daemon.js +137 -0
- package/dist/services/sandbox-service.d.ts +5 -0
- package/dist/services/sandbox-service.js +30 -2
- package/dist/types.d.ts +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -3
- package/skills/belay/SKILL.md +7 -1
- package/skills/belay/belay-approve.md +17 -0
package/dist/config-io.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { chmod, copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { getAdapterLayout } from './adapters/layouts/index.js';
|
|
5
5
|
import { compactApprovals, isExpired, mergeApprovalStates } from './core/approval.js';
|
|
@@ -42,13 +42,49 @@ export function approvedApprovalsPath(repoRoot, config) {
|
|
|
42
42
|
export { belayStateDir };
|
|
43
43
|
export async function ensureBelayStateDir(config, repoRoot) {
|
|
44
44
|
const stateDir = belayStateDir(config, repoLocalStateDirFor(repoRoot, config));
|
|
45
|
-
await mkdir(stateDir, { recursive: true });
|
|
45
|
+
await mkdir(stateDir, { recursive: true, mode: 0o700 });
|
|
46
46
|
return stateDir;
|
|
47
47
|
}
|
|
48
48
|
const APPROVAL_STATE_FILES = ['pending-approvals.json', 'approved-approvals.json'];
|
|
49
49
|
function approvalFilesExist(dir) {
|
|
50
50
|
return APPROVAL_STATE_FILES.some((fileName) => existsSync(path.join(dir, fileName)));
|
|
51
51
|
}
|
|
52
|
+
async function isCorruptApprovalStateFile(filePath) {
|
|
53
|
+
const raw = await readFile(filePath, 'utf8');
|
|
54
|
+
try {
|
|
55
|
+
JSON.parse(raw);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return error instanceof SyntaxError;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function normalizeApprovalStateFile(parsed) {
|
|
63
|
+
const version = parsed.version === 3 ? 3 : parsed.version === 2 ? 2 : 1;
|
|
64
|
+
return {
|
|
65
|
+
version,
|
|
66
|
+
revision: typeof parsed.revision === 'number' ? parsed.revision : undefined,
|
|
67
|
+
approvals: Array.isArray(parsed.approvals) ? parsed.approvals : [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Gate reads: corrupt JSON is treated as empty state (fail-closed). */
|
|
71
|
+
async function readApprovalStateFileForGate(filePath) {
|
|
72
|
+
const raw = await readFile(filePath, 'utf8');
|
|
73
|
+
try {
|
|
74
|
+
return normalizeApprovalStateFile(JSON.parse(raw));
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof SyntaxError) {
|
|
78
|
+
return { version: 1, approvals: [] };
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Migration reads: corrupt JSON must not be treated as empty. */
|
|
84
|
+
async function readApprovalStateFileStrict(filePath) {
|
|
85
|
+
const raw = await readFile(filePath, 'utf8');
|
|
86
|
+
return normalizeApprovalStateFile(JSON.parse(raw));
|
|
87
|
+
}
|
|
52
88
|
async function repoLocalApprovalsEmpty(repoRoot, config) {
|
|
53
89
|
const repoLocalDir = repoLocalStateDirFor(repoRoot, config);
|
|
54
90
|
if (!approvalFilesExist(repoLocalDir)) {
|
|
@@ -59,27 +95,26 @@ async function repoLocalApprovalsEmpty(repoRoot, config) {
|
|
|
59
95
|
if (!existsSync(filePath)) {
|
|
60
96
|
continue;
|
|
61
97
|
}
|
|
62
|
-
|
|
98
|
+
if (await isCorruptApprovalStateFile(filePath)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const state = await readApprovalStateFileForGate(filePath);
|
|
63
102
|
if (state.approvals.length > 0) {
|
|
64
103
|
return false;
|
|
65
104
|
}
|
|
66
105
|
}
|
|
67
106
|
return true;
|
|
68
107
|
}
|
|
69
|
-
async function readApprovalStateFile(filePath) {
|
|
70
|
-
const raw = await readFile(filePath, 'utf8');
|
|
71
|
-
const parsed = JSON.parse(raw);
|
|
72
|
-
return {
|
|
73
|
-
version: parsed.version === 2 ? 2 : 1,
|
|
74
|
-
approvals: Array.isArray(parsed.approvals) ? parsed.approvals : [],
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
108
|
async function writeApprovalStateFile(filePath, state) {
|
|
78
|
-
await mkdir(path.dirname(filePath), { recursive: true });
|
|
79
|
-
await writeFile(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}\n`,
|
|
109
|
+
await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
110
|
+
await writeFile(filePath, `${JSON.stringify(compactApprovals(state), null, 2)}\n`, {
|
|
111
|
+
encoding: 'utf8',
|
|
112
|
+
mode: 0o600,
|
|
113
|
+
});
|
|
114
|
+
await chmod(filePath, 0o600);
|
|
80
115
|
}
|
|
81
116
|
async function migrateApprovalFilesBetween(sourceDir, targetDir) {
|
|
82
|
-
await mkdir(targetDir, { recursive: true });
|
|
117
|
+
await mkdir(targetDir, { recursive: true, mode: 0o700 });
|
|
83
118
|
for (const fileName of APPROVAL_STATE_FILES) {
|
|
84
119
|
const from = path.join(sourceDir, fileName);
|
|
85
120
|
const to = path.join(targetDir, fileName);
|
|
@@ -87,11 +122,18 @@ async function migrateApprovalFilesBetween(sourceDir, targetDir) {
|
|
|
87
122
|
continue;
|
|
88
123
|
}
|
|
89
124
|
if (!existsSync(to)) {
|
|
125
|
+
if (await isCorruptApprovalStateFile(from)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
90
128
|
await copyFile(from, to);
|
|
129
|
+
await chmod(to, 0o600);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if ((await isCorruptApprovalStateFile(from)) || (await isCorruptApprovalStateFile(to))) {
|
|
91
133
|
continue;
|
|
92
134
|
}
|
|
93
|
-
const targetState = await
|
|
94
|
-
const sourceState = await
|
|
135
|
+
const targetState = await readApprovalStateFileStrict(to);
|
|
136
|
+
const sourceState = await readApprovalStateFileStrict(from);
|
|
95
137
|
await writeApprovalStateFile(to, mergeApprovalStates(targetState, sourceState));
|
|
96
138
|
}
|
|
97
139
|
}
|
|
@@ -167,7 +209,7 @@ export async function loadApprovalState(repoRoot, fileName, config) {
|
|
|
167
209
|
if (!existsSync(filePath)) {
|
|
168
210
|
return { version: 1, approvals: [] };
|
|
169
211
|
}
|
|
170
|
-
return
|
|
212
|
+
return readApprovalStateFileForGate(filePath);
|
|
171
213
|
}
|
|
172
214
|
export async function saveApprovalState(repoRoot, fileName, state, config) {
|
|
173
215
|
const filePath = fileName === 'pending-approvals.json'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BoundaryAttestation } from '../core/capability/attestation.js';
|
|
2
|
+
import type { BelayConfigV4 } from '../core/config.js';
|
|
3
|
+
import type { LayerProfileId } from './types.js';
|
|
4
|
+
export interface GuaranteePosture {
|
|
5
|
+
configuredProfile: LayerProfileId;
|
|
6
|
+
attestedProfile: LayerProfileId;
|
|
7
|
+
attestationFresh: boolean;
|
|
8
|
+
l1FullConfigured: boolean;
|
|
9
|
+
l1FullAttested: boolean;
|
|
10
|
+
postureMismatch: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveConfiguredProfile(config: BelayConfigV4, egressProxyRunning: boolean): LayerProfileId;
|
|
13
|
+
export declare function resolveAttestedProfile(params: {
|
|
14
|
+
attestation: BoundaryAttestation | null;
|
|
15
|
+
l1FullConfigured: boolean;
|
|
16
|
+
}): LayerProfileId;
|
|
17
|
+
export declare function evaluateGuaranteePosture(params: {
|
|
18
|
+
config: BelayConfigV4;
|
|
19
|
+
attestation: BoundaryAttestation | null;
|
|
20
|
+
egressProxyRunning: boolean;
|
|
21
|
+
}): GuaranteePosture;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { isAttestationFresh } from '../core/capability/attestation.js';
|
|
2
|
+
import { evaluateL1FullStatus } from '../core/capability/broker.js';
|
|
3
|
+
export function resolveConfiguredProfile(config, egressProxyRunning) {
|
|
4
|
+
const l1Full = evaluateL1FullStatus({ config, egressProxyRunning });
|
|
5
|
+
if (l1Full.active) {
|
|
6
|
+
return 'l1-full';
|
|
7
|
+
}
|
|
8
|
+
if (config.egress.enabled) {
|
|
9
|
+
return 'l1-partial-egress';
|
|
10
|
+
}
|
|
11
|
+
if (config.policy.transactional.enabled) {
|
|
12
|
+
return 'l1-l2-transactional';
|
|
13
|
+
}
|
|
14
|
+
return 'l3-l4-only';
|
|
15
|
+
}
|
|
16
|
+
export function resolveAttestedProfile(params) {
|
|
17
|
+
if (!params.attestation || !isAttestationFresh(params.attestation)) {
|
|
18
|
+
return 'l3-l4-only';
|
|
19
|
+
}
|
|
20
|
+
if (params.l1FullConfigured &&
|
|
21
|
+
params.attestation.deniesUngrantedEffects &&
|
|
22
|
+
params.attestation.materializesGrants) {
|
|
23
|
+
return 'l1-full';
|
|
24
|
+
}
|
|
25
|
+
return 'l3-l4-only';
|
|
26
|
+
}
|
|
27
|
+
export function evaluateGuaranteePosture(params) {
|
|
28
|
+
const l1Full = evaluateL1FullStatus({
|
|
29
|
+
config: params.config,
|
|
30
|
+
egressProxyRunning: params.egressProxyRunning,
|
|
31
|
+
});
|
|
32
|
+
const configuredProfile = resolveConfiguredProfile(params.config, params.egressProxyRunning);
|
|
33
|
+
const attestedProfile = resolveAttestedProfile({
|
|
34
|
+
attestation: params.attestation,
|
|
35
|
+
l1FullConfigured: l1Full.active,
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
configuredProfile,
|
|
39
|
+
attestedProfile,
|
|
40
|
+
attestationFresh: params.attestation ? isAttestationFresh(params.attestation) : false,
|
|
41
|
+
l1FullConfigured: l1Full.active,
|
|
42
|
+
l1FullAttested: attestedProfile === 'l1-full' &&
|
|
43
|
+
Boolean(params.attestation?.deniesUngrantedEffects && params.attestation.materializesGrants),
|
|
44
|
+
postureMismatch: configuredProfile === 'l1-full' && attestedProfile !== 'l1-full',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LayerConformanceScenario, LayerProfileId } from './types.js';
|
|
2
|
+
export { evaluateGuaranteePosture, type GuaranteePosture } from './guarantee-posture.js';
|
|
2
3
|
export type { LayerConformanceScenario, LayerProfileId } from './types.js';
|
|
3
4
|
export interface GuaranteeTableRow {
|
|
4
5
|
profile: LayerProfileId;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { evaluateGuaranteePosture } from './guarantee-posture.js';
|
|
1
2
|
/** Normative rows — keep in sync with docs/guarantee-table.md */
|
|
2
3
|
export const GUARANTEE_TABLE_ROWS = [
|
|
3
4
|
{
|
|
@@ -33,9 +34,10 @@ export const GUARANTEE_SCENARIOS = {
|
|
|
33
34
|
permission: 'allow',
|
|
34
35
|
},
|
|
35
36
|
{
|
|
36
|
-
id: 'l3-
|
|
37
|
+
id: 'l3-deny-read-egress',
|
|
37
38
|
command: 'curl https://example.com',
|
|
38
|
-
permission: '
|
|
39
|
+
permission: 'deny',
|
|
40
|
+
reason: 'external_effect',
|
|
39
41
|
},
|
|
40
42
|
],
|
|
41
43
|
'l1-partial-egress': [
|
|
@@ -45,9 +47,10 @@ export const GUARANTEE_SCENARIOS = {
|
|
|
45
47
|
permission: 'allow',
|
|
46
48
|
},
|
|
47
49
|
{
|
|
48
|
-
id: 'l1p-
|
|
50
|
+
id: 'l1p-deny-read-egress',
|
|
49
51
|
command: 'curl https://example.com',
|
|
50
|
-
permission: '
|
|
52
|
+
permission: 'deny',
|
|
53
|
+
reason: 'external_effect',
|
|
51
54
|
},
|
|
52
55
|
{
|
|
53
56
|
id: 'l1p-deny-write-egress',
|
|
@@ -63,9 +66,10 @@ export const GUARANTEE_SCENARIOS = {
|
|
|
63
66
|
permission: 'allow',
|
|
64
67
|
},
|
|
65
68
|
{
|
|
66
|
-
id: 'l2-
|
|
69
|
+
id: 'l2-deny-read-egress',
|
|
67
70
|
command: 'curl https://example.com',
|
|
68
|
-
permission: '
|
|
71
|
+
permission: 'deny',
|
|
72
|
+
reason: 'external_effect',
|
|
69
73
|
},
|
|
70
74
|
],
|
|
71
75
|
'l1-full': [
|
|
@@ -75,9 +79,10 @@ export const GUARANTEE_SCENARIOS = {
|
|
|
75
79
|
permission: 'allow',
|
|
76
80
|
},
|
|
77
81
|
{
|
|
78
|
-
id: 'l1f-
|
|
82
|
+
id: 'l1f-deny-read-egress',
|
|
79
83
|
command: 'curl https://example.com',
|
|
80
|
-
permission: '
|
|
84
|
+
permission: 'deny',
|
|
85
|
+
reason: 'external_effect',
|
|
81
86
|
},
|
|
82
87
|
{
|
|
83
88
|
id: 'l1f-deny-write-egress',
|
|
@@ -90,7 +95,7 @@ export const GUARANTEE_SCENARIOS = {
|
|
|
90
95
|
id: 'l1f-deny-outside-repo',
|
|
91
96
|
command: 'echo hi > ../../outside.txt',
|
|
92
97
|
permission: 'deny',
|
|
93
|
-
reason: '
|
|
98
|
+
reason: 'outside_repo_mutation',
|
|
94
99
|
},
|
|
95
100
|
{
|
|
96
101
|
id: 'l1f-deny-outside-repo-write',
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const DEFAULT_CLI_REPLAY_TIMEOUT_MS = 300_000;
|
|
4
|
+
export async function replayShellCommand(command, cwd, timeoutMs = DEFAULT_CLI_REPLAY_TIMEOUT_MS) {
|
|
5
|
+
const resolvedCwd = path.resolve(cwd);
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const child = spawn(command, {
|
|
8
|
+
cwd: resolvedCwd,
|
|
9
|
+
shell: true,
|
|
10
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
11
|
+
env: { ...process.env },
|
|
12
|
+
});
|
|
13
|
+
const stdout = [];
|
|
14
|
+
const stderr = [];
|
|
15
|
+
let timedOut = false;
|
|
16
|
+
const timer = setTimeout(() => {
|
|
17
|
+
timedOut = true;
|
|
18
|
+
child.kill('SIGTERM');
|
|
19
|
+
}, timeoutMs);
|
|
20
|
+
child.stdout?.on('data', (chunk) => stdout.push(Buffer.from(chunk)));
|
|
21
|
+
child.stderr?.on('data', (chunk) => stderr.push(Buffer.from(chunk)));
|
|
22
|
+
child.on('error', (error) => {
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
reject(error);
|
|
25
|
+
});
|
|
26
|
+
child.on('close', (code) => {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
resolve({
|
|
29
|
+
exitCode: timedOut ? 124 : (code ?? 1),
|
|
30
|
+
stdout: Buffer.concat(stdout).toString('utf8'),
|
|
31
|
+
stderr: Buffer.concat(stderr).toString('utf8'),
|
|
32
|
+
...(timedOut ? { timedOut: true } : {}),
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ApprovalFlow, BelayConfigV4 } from './config.js';
|
|
2
|
+
import type { ApprovalRecord } from './types.js';
|
|
3
|
+
export type { ApprovalFlow } from './config.js';
|
|
4
|
+
export type ReplayAdapterId = 'cursor' | 'claude' | 'codex';
|
|
5
|
+
export { DEFAULT_CLI_REPLAY_TIMEOUT_MS, replayShellCommand } from './approval-replay-cli.js';
|
|
6
|
+
export interface ApprovalReplayHint {
|
|
7
|
+
kind: 'shell' | 'tool' | 'subagent';
|
|
8
|
+
input: string;
|
|
9
|
+
cwd?: string;
|
|
10
|
+
toolName?: string;
|
|
11
|
+
fingerprint: string;
|
|
12
|
+
approvalId: string;
|
|
13
|
+
autoReplay: boolean;
|
|
14
|
+
fallbackToTwoStep: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface ReplayEnvelopeInput {
|
|
17
|
+
kind: ApprovalRecord['kind'];
|
|
18
|
+
cwd?: string;
|
|
19
|
+
toolName?: string;
|
|
20
|
+
command?: string;
|
|
21
|
+
input?: string;
|
|
22
|
+
inputKind?: 'shell' | 'tool' | 'subagent';
|
|
23
|
+
payload?: Record<string, unknown>;
|
|
24
|
+
fingerprint: string;
|
|
25
|
+
repoRoot: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ReplayActionContext {
|
|
28
|
+
kind: ApprovalRecord['kind'];
|
|
29
|
+
cwd?: string;
|
|
30
|
+
toolName?: string;
|
|
31
|
+
command?: string;
|
|
32
|
+
payload?: Record<string, unknown>;
|
|
33
|
+
fingerprint: string;
|
|
34
|
+
repoRoot: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function replayPayloadHash(kind: ApprovalRecord['kind'], payload: Record<string, unknown> | undefined, repoRoot: string): string | undefined;
|
|
37
|
+
export declare function getExecutionLeaseMs(config: BelayConfigV4): number;
|
|
38
|
+
export declare function approvalFlow(config: BelayConfigV4): ApprovalFlow;
|
|
39
|
+
export declare function buildReplayEnvelopeFields(input: ReplayEnvelopeInput): Partial<ApprovalRecord>;
|
|
40
|
+
export declare function hasReplayEnvelope(approval: ApprovalRecord): boolean;
|
|
41
|
+
export declare function validateReplayEnvelope(approval: ApprovalRecord, action: ReplayActionContext): boolean;
|
|
42
|
+
export declare function canAutoReplay(config: BelayConfigV4, kind: ApprovalRecord['kind'], adapter?: ReplayAdapterId): boolean;
|
|
43
|
+
export declare function buildRetryInstructionForConfig(config: BelayConfigV4, tokenPrefix: string, approvalId: string): string;
|
|
44
|
+
export declare function buildApprovalRecordedMessage(config: BelayConfigV4, approval: ApprovalRecord, adapter?: ReplayAdapterId): string;
|
|
45
|
+
export declare function buildReplayHint(config: BelayConfigV4, approval: ApprovalRecord, adapter?: ReplayAdapterId): ApprovalReplayHint | null;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { canonicalStringify, hashValue } from './fingerprint.js';
|
|
3
|
+
export { DEFAULT_CLI_REPLAY_TIMEOUT_MS, replayShellCommand } from './approval-replay-cli.js';
|
|
4
|
+
const MAX_PAYLOAD_JSON_BYTES = 16_384;
|
|
5
|
+
export function replayPayloadHash(kind, payload, repoRoot) {
|
|
6
|
+
if (!payload || Object.keys(payload).length === 0) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const json = canonicalStringify(payload);
|
|
10
|
+
return hashValue(`payload:${kind}:${json}:${repoRoot}`);
|
|
11
|
+
}
|
|
12
|
+
const DEFAULT_REPLAY_CAPABILITIES = {
|
|
13
|
+
shell: true,
|
|
14
|
+
tool: false,
|
|
15
|
+
subagent: false,
|
|
16
|
+
};
|
|
17
|
+
const ADAPTER_REPLAY_CAPABLE = {
|
|
18
|
+
cursor: { ...DEFAULT_REPLAY_CAPABILITIES },
|
|
19
|
+
claude: { ...DEFAULT_REPLAY_CAPABILITIES },
|
|
20
|
+
codex: { ...DEFAULT_REPLAY_CAPABILITIES },
|
|
21
|
+
};
|
|
22
|
+
export function getExecutionLeaseMs(config) {
|
|
23
|
+
const ms = config.approval?.executionLeaseMs;
|
|
24
|
+
return typeof ms === 'number' && ms > 0 ? ms : 60_000;
|
|
25
|
+
}
|
|
26
|
+
export function approvalFlow(config) {
|
|
27
|
+
return config.approval?.flow === 'two_step' ? 'two_step' : 'one_step';
|
|
28
|
+
}
|
|
29
|
+
export function buildReplayEnvelopeFields(input) {
|
|
30
|
+
const fields = {};
|
|
31
|
+
const cmd = input.command ?? input.input ?? '';
|
|
32
|
+
if (cmd) {
|
|
33
|
+
fields.input = cmd;
|
|
34
|
+
fields.inputKind = input.inputKind ?? input.kind;
|
|
35
|
+
}
|
|
36
|
+
if (input.cwd) {
|
|
37
|
+
fields.cwd = path.resolve(input.cwd);
|
|
38
|
+
}
|
|
39
|
+
if (input.toolName) {
|
|
40
|
+
fields.toolName = input.toolName;
|
|
41
|
+
}
|
|
42
|
+
if (input.payload && Object.keys(input.payload).length > 0) {
|
|
43
|
+
const json = canonicalStringify(input.payload);
|
|
44
|
+
if (Buffer.byteLength(json, 'utf8') <= MAX_PAYLOAD_JSON_BYTES) {
|
|
45
|
+
fields.payloadJson = json;
|
|
46
|
+
}
|
|
47
|
+
fields.payloadHash = replayPayloadHash(input.kind, input.payload, input.repoRoot);
|
|
48
|
+
}
|
|
49
|
+
return fields;
|
|
50
|
+
}
|
|
51
|
+
export function hasReplayEnvelope(approval) {
|
|
52
|
+
return Boolean(approval.cwd || approval.toolName || approval.payloadHash);
|
|
53
|
+
}
|
|
54
|
+
export function validateReplayEnvelope(approval, action) {
|
|
55
|
+
if (!hasReplayEnvelope(approval)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (approval.kind !== action.kind) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (approval.fingerprint !== action.fingerprint) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (approval.repoRoot !== action.repoRoot) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (approval.cwd && path.resolve(approval.cwd) !== path.resolve(action.cwd ?? '')) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (approval.toolName && approval.toolName !== action.toolName) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (approval.payloadHash) {
|
|
74
|
+
const hash = replayPayloadHash(action.kind, action.payload, action.repoRoot);
|
|
75
|
+
if (!hash || approval.payloadHash !== hash) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
export function canAutoReplay(config, kind, adapter) {
|
|
82
|
+
if (config.approvalSigning?.required) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (approvalFlow(config) !== 'one_step') {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (kind !== 'shell' && kind !== 'tool' && kind !== 'subagent') {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const scopes = config.approval?.autoReplayScopes;
|
|
92
|
+
if (kind === 'shell' && scopes?.shell === false) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (kind === 'tool' && scopes?.tool !== true) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (kind === 'subagent' && scopes?.subagent !== true) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (adapter) {
|
|
102
|
+
return ADAPTER_REPLAY_CAPABLE[adapter][kind] === true;
|
|
103
|
+
}
|
|
104
|
+
return kind === 'shell' && scopes?.shell !== false;
|
|
105
|
+
}
|
|
106
|
+
export function buildRetryInstructionForConfig(config, tokenPrefix, approvalId) {
|
|
107
|
+
if (approvalFlow(config) === 'one_step') {
|
|
108
|
+
return `To allow this action once, send ${tokenPrefix} ${approvalId}. After approval, retry the same action unchanged.`;
|
|
109
|
+
}
|
|
110
|
+
return `To allow the next matching action once, send ${tokenPrefix} ${approvalId} and then retry the original action unchanged.`;
|
|
111
|
+
}
|
|
112
|
+
export function buildApprovalRecordedMessage(config, approval, adapter) {
|
|
113
|
+
if (!canAutoReplay(config, approval.kind, adapter)) {
|
|
114
|
+
return `Belay approval recorded for ${approval.approvalId}. Retry the original action once before it expires.`;
|
|
115
|
+
}
|
|
116
|
+
if (approval.kind === 'shell' && approval.input) {
|
|
117
|
+
return (`Belay approval recorded for ${approval.approvalId}. ` +
|
|
118
|
+
`Retry this shell command unchanged: ${approval.input}`);
|
|
119
|
+
}
|
|
120
|
+
return `Belay approval recorded for ${approval.approvalId}. Retry the original action once before it expires.`;
|
|
121
|
+
}
|
|
122
|
+
export function buildReplayHint(config, approval, adapter) {
|
|
123
|
+
if (approvalFlow(config) !== 'one_step') {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const input = approval.input ?? approval.summary;
|
|
127
|
+
if (!input) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const autoReplay = canAutoReplay(config, approval.kind, adapter);
|
|
131
|
+
return {
|
|
132
|
+
kind: approval.kind,
|
|
133
|
+
input,
|
|
134
|
+
cwd: approval.cwd,
|
|
135
|
+
toolName: approval.toolName,
|
|
136
|
+
fingerprint: approval.fingerprint,
|
|
137
|
+
approvalId: approval.approvalId,
|
|
138
|
+
autoReplay,
|
|
139
|
+
fallbackToTwoStep: !autoReplay,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReplayAdapterId } from './approval-replay.js';
|
|
1
2
|
import type { BelayConfigV3 } from './config.js';
|
|
2
3
|
import type { ApprovalStateFile } from './types.js';
|
|
3
4
|
export interface ApprovalStore {
|
|
@@ -19,8 +20,22 @@ export declare function recordApproval(params: {
|
|
|
19
20
|
token?: string;
|
|
20
21
|
/** When true, require a signed token (out-of-band CLI path). Editor prompts skip this. */
|
|
21
22
|
requireSignedToken?: boolean;
|
|
23
|
+
adapter?: ReplayAdapterId;
|
|
22
24
|
}): Promise<{
|
|
23
25
|
ok: boolean;
|
|
24
26
|
message: string;
|
|
25
27
|
approval?: ApprovalStateFile['approvals'][number];
|
|
26
28
|
}>;
|
|
29
|
+
/** CLI `--replay` already executed the shell command; drop the one-shot grant. */
|
|
30
|
+
export declare function consumeApprovedAfterCliReplay(params: {
|
|
31
|
+
approvalId: string;
|
|
32
|
+
store: ApprovalStore;
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
export declare function createGateApprovalStore(repoRoot: string, config: BelayConfigV3): ApprovalStore;
|
|
35
|
+
export declare function gateApprovalStoreFromDeps(deps: {
|
|
36
|
+
loadApprovals: (fileName: 'pending-approvals.json' | 'approved-approvals.json') => Promise<{
|
|
37
|
+
filePath: string;
|
|
38
|
+
state: ApprovalStateFile;
|
|
39
|
+
}>;
|
|
40
|
+
writeApprovals: (filePath: string, state: ApprovalStateFile) => Promise<void>;
|
|
41
|
+
}): ApprovalStore;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { approvedApprovalsPath, loadApprovalState, pendingApprovalsPath, saveApprovalState, } from '../config-io.js';
|
|
1
2
|
import { compactApprovals } from './approval.js';
|
|
3
|
+
import { buildApprovalRecordedMessage } from './approval-replay.js';
|
|
2
4
|
import { verifyApprovalToken } from './approval-token.js';
|
|
5
|
+
import { APPROVAL_STATE_VERSION_V3, mintGrantForApprovedRecord } from './capability/approval-v3.js';
|
|
3
6
|
import { configuredControlPlaneDir } from './config.js';
|
|
4
7
|
export async function recordApproval(params) {
|
|
5
|
-
const { approvalId, config, store, token, requireSignedToken = false } = params;
|
|
8
|
+
const { approvalId, config, store, token, requireSignedToken = false, adapter } = params;
|
|
6
9
|
const pending = await store.loadPending();
|
|
7
10
|
pending.state = compactApprovals(pending.state);
|
|
8
11
|
const index = pending.state.approvals.findIndex((approval) => approval.approvalId === approvalId);
|
|
@@ -28,14 +31,60 @@ export async function recordApproval(params) {
|
|
|
28
31
|
await store.writePending(pending.filePath, pending.state);
|
|
29
32
|
const approved = await store.loadApproved();
|
|
30
33
|
approved.state = compactApprovals(approved.state);
|
|
31
|
-
|
|
34
|
+
const approvedRecord = mintGrantForApprovedRecord({
|
|
32
35
|
...approval,
|
|
33
36
|
approvedAt: new Date().toISOString(),
|
|
34
37
|
});
|
|
38
|
+
approved.state.version = APPROVAL_STATE_VERSION_V3;
|
|
39
|
+
approved.state.revision = (approved.state.revision ?? 0) + 1;
|
|
40
|
+
approved.state.approvals.push(approvedRecord);
|
|
35
41
|
await store.writeApproved(approved.filePath, approved.state);
|
|
36
42
|
return {
|
|
37
43
|
ok: true,
|
|
38
|
-
message:
|
|
39
|
-
approval,
|
|
44
|
+
message: buildApprovalRecordedMessage(config, approval, adapter),
|
|
45
|
+
approval: approvedRecord,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** CLI `--replay` already executed the shell command; drop the one-shot grant. */
|
|
49
|
+
export async function consumeApprovedAfterCliReplay(params) {
|
|
50
|
+
const approved = await params.store.loadApproved();
|
|
51
|
+
approved.state = compactApprovals(approved.state);
|
|
52
|
+
const remaining = approved.state.approvals.filter((approval) => approval.approvalId !== params.approvalId);
|
|
53
|
+
if (remaining.length === approved.state.approvals.length) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
approved.state.approvals = remaining;
|
|
57
|
+
await params.store.writeApproved(approved.filePath, approved.state);
|
|
58
|
+
}
|
|
59
|
+
export function createGateApprovalStore(repoRoot, config) {
|
|
60
|
+
return {
|
|
61
|
+
async loadPending() {
|
|
62
|
+
const filePath = pendingApprovalsPath(repoRoot, config);
|
|
63
|
+
return {
|
|
64
|
+
filePath,
|
|
65
|
+
state: await loadApprovalState(repoRoot, 'pending-approvals.json', config),
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
async loadApproved() {
|
|
69
|
+
const filePath = approvedApprovalsPath(repoRoot, config);
|
|
70
|
+
return {
|
|
71
|
+
filePath,
|
|
72
|
+
state: await loadApprovalState(repoRoot, 'approved-approvals.json', config),
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
async writePending(_filePath, state) {
|
|
76
|
+
await saveApprovalState(repoRoot, 'pending-approvals.json', state, config);
|
|
77
|
+
},
|
|
78
|
+
async writeApproved(_filePath, state) {
|
|
79
|
+
await saveApprovalState(repoRoot, 'approved-approvals.json', state, config);
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function gateApprovalStoreFromDeps(deps) {
|
|
84
|
+
return {
|
|
85
|
+
loadPending: () => deps.loadApprovals('pending-approvals.json'),
|
|
86
|
+
loadApproved: () => deps.loadApprovals('approved-approvals.json'),
|
|
87
|
+
writePending: (filePath, state) => deps.writeApprovals(filePath, state),
|
|
88
|
+
writeApproved: (filePath, state) => deps.writeApprovals(filePath, state),
|
|
40
89
|
};
|
|
41
90
|
}
|
package/dist/core/approval.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApprovalRecord, ApprovalStateFile } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
export declare const APPROVAL_EXECUTION_LEASE_MS
|
|
2
|
+
/** @deprecated Use `DEFAULT_APPROVAL_CONFIG.executionLeaseMs` */
|
|
3
|
+
export declare const APPROVAL_EXECUTION_LEASE_MS: number;
|
|
4
4
|
export declare function nowIso(): string;
|
|
5
5
|
export declare function isExpired(approval: ApprovalRecord): boolean;
|
|
6
6
|
export declare function isExecutionLeaseExpired(approval: ApprovalRecord): boolean;
|
|
@@ -8,7 +8,24 @@ export declare function compactApprovals(state: ApprovalStateFile): ApprovalStat
|
|
|
8
8
|
export declare function mergeApprovalStates(target: ApprovalStateFile, source: ApprovalStateFile): ApprovalStateFile;
|
|
9
9
|
export declare function escapeRegex(value: string): string;
|
|
10
10
|
export declare function approvalCommandMatch(prompt: string, tokenPrefix: string): string | null;
|
|
11
|
-
export declare function
|
|
11
|
+
export declare function createApprovalRecordWithEnvelope(params: {
|
|
12
|
+
kind: ApprovalRecord['kind'];
|
|
13
|
+
fingerprint: string;
|
|
14
|
+
repoRoot: string;
|
|
15
|
+
reason: string;
|
|
16
|
+
summary: string;
|
|
17
|
+
approvalTtlMinutes: number;
|
|
18
|
+
approvalId: string;
|
|
19
|
+
approvalInput?: {
|
|
20
|
+
input: string;
|
|
21
|
+
inputKind: 'shell' | 'tool' | 'subagent';
|
|
22
|
+
cwd?: string;
|
|
23
|
+
toolName?: string;
|
|
24
|
+
payload?: Record<string, unknown>;
|
|
25
|
+
};
|
|
26
|
+
scopeHint?: ApprovalRecord['scopeHint'];
|
|
27
|
+
capabilityRequests?: ApprovalRecord['capabilityRequests'];
|
|
28
|
+
}): ApprovalRecord;
|
|
12
29
|
export declare function createApprovalRecord(params: {
|
|
13
30
|
kind: ApprovalRecord['kind'];
|
|
14
31
|
fingerprint: string;
|
|
@@ -19,4 +36,10 @@ export declare function createApprovalRecord(params: {
|
|
|
19
36
|
approvalId: string;
|
|
20
37
|
input?: string;
|
|
21
38
|
inputKind?: 'shell' | 'tool' | 'subagent';
|
|
39
|
+
cwd?: string;
|
|
40
|
+
toolName?: string;
|
|
41
|
+
payloadHash?: string;
|
|
42
|
+
payloadJson?: string;
|
|
43
|
+
scopeHint?: ApprovalRecord['scopeHint'];
|
|
44
|
+
capabilityRequests?: ApprovalRecord['capabilityRequests'];
|
|
22
45
|
}): ApprovalRecord;
|