@guilz-dev/belay 0.2.0 → 0.4.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 -12
- package/dist/adapters/cursor/runtime-entry.js +1 -0
- package/dist/adapters/shared/gate-runtime.d.ts +11 -14
- package/dist/adapters/shared/gate-runtime.js +289 -23
- package/dist/bundle/claude-runtime.mjs +4435 -708
- package/dist/bundle/codex-runtime.mjs +4448 -710
- package/dist/bundle/cursor-runtime.mjs +4437 -710
- package/dist/cli.js +333 -11
- package/dist/commands/approve.d.ts +1 -0
- package/dist/commands/approve.js +56 -26
- package/dist/commands/config.d.ts +70 -0
- package/dist/commands/config.js +577 -0
- package/dist/commands/doctor.js +13 -2
- package/dist/commands/explain.js +6 -1
- package/dist/commands/harvest.d.ts +29 -0
- package/dist/commands/harvest.js +72 -0
- package/dist/commands/health-snapshot.d.ts +6 -0
- package/dist/commands/health-snapshot.js +34 -0
- package/dist/commands/judge.d.ts +100 -0
- package/dist/commands/judge.js +304 -0
- 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/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/status.js +2 -2
- package/dist/commands/stdin-key.d.ts +1 -0
- package/dist/commands/stdin-key.js +8 -0
- package/dist/commands/tui.d.ts +48 -0
- package/dist/commands/tui.js +150 -0
- package/dist/config-io.js +10 -5
- package/dist/conformance/guarantee-table.js +12 -0
- package/dist/conformance/types.d.ts +2 -0
- 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 +47 -2
- package/dist/core/approval.d.ts +24 -3
- package/dist/core/approval.js +47 -4
- package/dist/core/audit-analysis.d.ts +7 -1
- package/dist/core/audit-analysis.js +111 -0
- package/dist/core/audit-io.d.ts +2 -0
- package/dist/core/audit-io.js +14 -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/index.d.ts +4 -3
- package/dist/core/capability/index.js +3 -2
- package/dist/core/capability/paths.d.ts +2 -1
- package/dist/core/capability/paths.js +76 -8
- package/dist/core/capability/reasons.d.ts +3 -0
- package/dist/core/capability/reasons.js +8 -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.js +2 -20
- package/dist/core/classify-tool.js +129 -30
- package/dist/core/config-layers.js +2 -1
- package/dist/core/config.d.ts +40 -2
- package/dist/core/config.js +159 -11
- package/dist/core/credential-store.d.ts +11 -0
- package/dist/core/credential-store.js +60 -0
- package/dist/core/gate-engine.js +104 -13
- 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/integrity.d.ts +2 -0
- package/dist/core/integrity.js +13 -0
- package/dist/core/judge-api-key.d.ts +19 -0
- package/dist/core/judge-api-key.js +74 -0
- package/dist/core/judge-cloud-consent.d.ts +13 -0
- package/dist/core/judge-cloud-consent.js +38 -0
- package/dist/core/judge-config.d.ts +41 -4
- package/dist/core/judge-config.js +263 -57
- package/dist/core/judge-doctor.d.ts +9 -1
- package/dist/core/judge-doctor.js +199 -94
- package/dist/core/judge-model-discovery.d.ts +24 -0
- package/dist/core/judge-model-discovery.js +168 -0
- package/dist/core/judge-model-policy.d.ts +5 -0
- package/dist/core/judge-model-policy.js +21 -0
- package/dist/core/judge-runtime-detection.d.ts +9 -0
- package/dist/core/judge-runtime-detection.js +68 -0
- package/dist/core/path-utils.d.ts +11 -0
- package/dist/core/path-utils.js +56 -6
- package/dist/core/reclassify.d.ts +3 -0
- package/dist/core/reclassify.js +44 -14
- 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 +175 -0
- package/dist/core/transactional/diff-evaluator.js +1 -19
- package/dist/core/types.d.ts +17 -0
- package/dist/core/verdict/adapter.d.ts +1 -0
- package/dist/core/verdict/adapter.js +17 -2
- package/dist/core/verdict/containment.d.ts +8 -2
- package/dist/core/verdict/containment.js +45 -10
- package/dist/core/verdict/judge-audit.d.ts +1 -0
- package/dist/core/verdict/judge-audit.js +32 -1
- package/dist/core/verdict/judge-baseline.d.ts +20 -0
- package/dist/core/verdict/judge-baseline.js +48 -0
- package/dist/core/verdict/judge-broker-service.d.ts +42 -0
- package/dist/core/verdict/judge-broker-service.js +279 -0
- package/dist/core/verdict/judge-catalog.d.ts +40 -0
- package/dist/core/verdict/judge-catalog.js +148 -0
- package/dist/core/verdict/judge-cli-fingerprint.d.ts +5 -0
- package/dist/core/verdict/judge-cli-fingerprint.js +61 -0
- package/dist/core/verdict/judge-cli.d.ts +46 -0
- package/dist/core/verdict/judge-cli.js +401 -0
- package/dist/core/verdict/judge-factory.d.ts +15 -4
- package/dist/core/verdict/judge-factory.js +127 -15
- 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 +42 -0
- package/dist/core/verdict/judge-runtime-config.js +92 -0
- package/dist/core/verdict/judge-session-broker.d.ts +48 -0
- package/dist/core/verdict/judge-session-broker.js +195 -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 +40 -0
- package/dist/core/verdict/judge-transport.js +258 -0
- package/dist/core/verdict/judge.d.ts +33 -1
- package/dist/core/verdict/judge.js +84 -14
- package/dist/core/verdict/parser.js +6 -1
- package/dist/core/verdict/persistent-paths.d.ts +8 -0
- package/dist/core/verdict/persistent-paths.js +52 -0
- package/dist/core/verdict/types.d.ts +19 -2
- package/dist/core/verdict/verdict.js +166 -49
- 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 +62 -5
- 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 +151 -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 +45 -0
- package/dist/corpus/standing-allow-catalog.generated.d.ts +13 -0
- package/dist/corpus/standing-allow-catalog.generated.js +79 -0
- package/dist/corpus/types.d.ts +47 -0
- package/dist/corpus/types.js +157 -0
- package/dist/installer.js +69 -15
- package/dist/judge-broker-daemon.d.ts +1 -0
- package/dist/judge-broker-daemon.js +123 -0
- package/dist/services/sandbox-service.d.ts +1 -0
- package/dist/services/sandbox-service.js +2 -0
- package/dist/types.d.ts +7 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -3
- package/skills/belay/SKILL.md +15 -8
- package/skills/belay/belay-approve.md +17 -0
- package/dist/commands/init-wizard.d.ts +0 -21
- package/dist/commands/init-wizard.js +0 -63
package/dist/core/path-utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, realpathSync } from 'node:fs';
|
|
1
|
+
import { existsSync, realpathSync, statSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { isFdDuplication, isRedirectOperator } from './shell-tokenizer.js';
|
|
3
4
|
/**
|
|
4
5
|
* Resolve symlinks for the longest existing prefix of `targetPath`, then append
|
|
5
6
|
* any non-existent suffix without further resolution. Keeps path comparisons
|
|
@@ -44,18 +45,38 @@ export function pathWithinRoot(root, targetPath) {
|
|
|
44
45
|
}
|
|
45
46
|
return !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
-
const resolvedRoot = canonicalPath(
|
|
48
|
+
function relativeWithinRoot(root, targetPath, options = {}) {
|
|
49
|
+
const resolvedRoot = options.canonicalizeRoot === false ? path.resolve(root) : canonicalPath(root);
|
|
49
50
|
const resolvedTarget = canonicalPath(targetPath);
|
|
50
51
|
const relativePath = path.relative(resolvedRoot, resolvedTarget);
|
|
51
52
|
if (relativePath === '') {
|
|
52
53
|
return '.';
|
|
53
54
|
}
|
|
54
|
-
if (relativePath.startsWith('..')) {
|
|
55
|
+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
55
56
|
return null;
|
|
56
57
|
}
|
|
57
58
|
return relativePath;
|
|
58
59
|
}
|
|
60
|
+
export function relativeWithinRepo(repoRoot, targetPath) {
|
|
61
|
+
return relativeWithinRoot(repoRoot, targetPath);
|
|
62
|
+
}
|
|
63
|
+
export function resolveWorkspaceRootMatch(repoRoot, trustedRoots = [], targetPath) {
|
|
64
|
+
const canonicalRepoRoot = canonicalPath(repoRoot);
|
|
65
|
+
const repoRelative = relativeWithinRoot(repoRoot, targetPath);
|
|
66
|
+
if (repoRelative !== null) {
|
|
67
|
+
return { kind: 'repo', root: canonicalRepoRoot, relativePath: repoRelative };
|
|
68
|
+
}
|
|
69
|
+
const normalizedTrustedRoots = [...new Set(trustedRoots.map((root) => path.resolve(root)))]
|
|
70
|
+
.filter((root) => root !== canonicalRepoRoot)
|
|
71
|
+
.sort((left, right) => right.length - left.length);
|
|
72
|
+
for (const root of normalizedTrustedRoots) {
|
|
73
|
+
const trustedRelative = relativeWithinRoot(root, targetPath, { canonicalizeRoot: false });
|
|
74
|
+
if (trustedRelative !== null) {
|
|
75
|
+
return { kind: 'trusted', root, relativePath: trustedRelative };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
59
80
|
export function normalizeToken(token, repoRoot) {
|
|
60
81
|
if (!path.isAbsolute(token)) {
|
|
61
82
|
return token;
|
|
@@ -67,7 +88,14 @@ export function resolveMutationTarget(token, cwd) {
|
|
|
67
88
|
if (!token || token === '--' || token.startsWith('-')) {
|
|
68
89
|
return null;
|
|
69
90
|
}
|
|
70
|
-
if (token
|
|
91
|
+
if (isRedirectOperator(token) ||
|
|
92
|
+
isFdDuplication(token) ||
|
|
93
|
+
token === '&&' ||
|
|
94
|
+
token === '||' ||
|
|
95
|
+
token === '|' ||
|
|
96
|
+
token === '|&' ||
|
|
97
|
+
token === ';' ||
|
|
98
|
+
token === '&') {
|
|
71
99
|
return null;
|
|
72
100
|
}
|
|
73
101
|
if (path.isAbsolute(token)) {
|
|
@@ -102,6 +130,28 @@ export function hasOutsideRepoPath(tokens, cwd, repoRoot) {
|
|
|
102
130
|
if (!resolved) {
|
|
103
131
|
return false;
|
|
104
132
|
}
|
|
105
|
-
return
|
|
133
|
+
return resolveWorkspaceRootMatch(repoRoot, [], resolved) === null;
|
|
106
134
|
});
|
|
107
135
|
}
|
|
136
|
+
export function containingGitRoot(targetPath) {
|
|
137
|
+
const resolved = canonicalPath(targetPath);
|
|
138
|
+
let current = resolved;
|
|
139
|
+
try {
|
|
140
|
+
if (!statSync(current).isDirectory()) {
|
|
141
|
+
current = path.dirname(current);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
current = path.dirname(current);
|
|
146
|
+
}
|
|
147
|
+
while (true) {
|
|
148
|
+
if (existsSync(path.join(current, '.git'))) {
|
|
149
|
+
return current;
|
|
150
|
+
}
|
|
151
|
+
const parent = path.dirname(current);
|
|
152
|
+
if (parent === current) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
current = parent;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -6,6 +6,8 @@ export interface ReclassifyDiff {
|
|
|
6
6
|
event?: string;
|
|
7
7
|
summary?: string;
|
|
8
8
|
fingerprint?: string;
|
|
9
|
+
replayCwd?: string;
|
|
10
|
+
replayKind?: string;
|
|
9
11
|
previousVerdict: string;
|
|
10
12
|
previousReason: string;
|
|
11
13
|
nextVerdict: string;
|
|
@@ -13,3 +15,4 @@ export interface ReclassifyDiff {
|
|
|
13
15
|
}
|
|
14
16
|
export declare function reclassifyAuditRecord(record: AuditRecord, config: BelayConfigV3, repoRoot: string): Promise<ClassifyResult | null>;
|
|
15
17
|
export declare function diffReclassification(record: AuditRecord, config: BelayConfigV3, repoRoot: string): Promise<ReclassifyDiff | null>;
|
|
18
|
+
export declare function countMissingActionSnapshots(records: AuditRecord[]): number;
|
package/dist/core/reclassify.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getAdapter } from '../adapters/registry.js';
|
|
2
2
|
import { repoShellClassifierOptions } from '../adapters/shared/gate-runtime.js';
|
|
3
3
|
import { detectAdapterName } from '../config-io.js';
|
|
4
|
+
import { hashReplayPayload, parseAuditActionSnapshot, parseAuditReplayContext, } from './audit-replay-context.js';
|
|
4
5
|
import { GATE_EVENTS } from './audit-types.js';
|
|
5
6
|
import { classifyGatedAction, normalizeGatedAction } from './gate-engine.js';
|
|
6
7
|
function shellCommandFromSummary(summary) {
|
|
@@ -11,47 +12,68 @@ function classifierOptionsForRepo(config, repoRoot) {
|
|
|
11
12
|
const adapter = getAdapter(config.adapter ?? detectAdapterName(repoRoot));
|
|
12
13
|
return repoShellClassifierOptions(config, repoRoot, adapter.layout);
|
|
13
14
|
}
|
|
15
|
+
function trustedReplayContext(snapshot, replay) {
|
|
16
|
+
if (!replay) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (snapshot?.payloadHash &&
|
|
20
|
+
replay.payload &&
|
|
21
|
+
hashReplayPayload(replay.payload) !== snapshot.payloadHash) {
|
|
22
|
+
return { ...replay, payload: undefined };
|
|
23
|
+
}
|
|
24
|
+
return replay;
|
|
25
|
+
}
|
|
14
26
|
export async function reclassifyAuditRecord(record, config, repoRoot) {
|
|
15
27
|
if (!record.event || !GATE_EVENTS.has(record.event)) {
|
|
16
28
|
return null;
|
|
17
29
|
}
|
|
18
|
-
const
|
|
30
|
+
const snapshot = parseAuditActionSnapshot(record);
|
|
31
|
+
const replay = trustedReplayContext(snapshot, parseAuditReplayContext(record));
|
|
32
|
+
const kind = snapshot?.kind ??
|
|
33
|
+
replay?.kind ??
|
|
34
|
+
(record.kind === 'tool' || record.kind === 'subagent' ? record.kind : 'shell');
|
|
19
35
|
const summary = record.summary ?? '';
|
|
36
|
+
const cwd = snapshot?.cwd ?? replay?.cwd ?? repoRoot;
|
|
20
37
|
try {
|
|
21
38
|
if (kind === 'shell') {
|
|
22
|
-
const command = shellCommandFromSummary(summary);
|
|
39
|
+
const command = snapshot?.normalizedAction ?? replay?.command ?? shellCommandFromSummary(summary);
|
|
23
40
|
if (!command) {
|
|
24
41
|
return null;
|
|
25
42
|
}
|
|
26
43
|
const action = normalizeGatedAction({
|
|
27
44
|
kind: 'shell',
|
|
28
45
|
repoRoot,
|
|
29
|
-
cwd
|
|
46
|
+
cwd,
|
|
30
47
|
command,
|
|
31
48
|
});
|
|
32
49
|
return await classifyGatedAction(action, config, classifierOptionsForRepo(config, repoRoot));
|
|
33
50
|
}
|
|
34
51
|
if (kind === 'subagent') {
|
|
52
|
+
const payload = replay?.payload ??
|
|
53
|
+
{
|
|
54
|
+
tool_name: 'Task',
|
|
55
|
+
tool_input: { description: summary },
|
|
56
|
+
};
|
|
35
57
|
const action = normalizeGatedAction({
|
|
36
58
|
kind: 'subagent',
|
|
37
59
|
repoRoot,
|
|
38
|
-
cwd
|
|
39
|
-
payload
|
|
40
|
-
tool_name: 'Task',
|
|
41
|
-
tool_input: { description: summary },
|
|
42
|
-
},
|
|
60
|
+
cwd,
|
|
61
|
+
payload,
|
|
43
62
|
});
|
|
44
63
|
return await classifyGatedAction(action, config, classifierOptionsForRepo(config, repoRoot));
|
|
45
64
|
}
|
|
65
|
+
const toolName = snapshot?.toolName ?? replay?.toolName ?? 'Shell';
|
|
66
|
+
const payload = replay?.payload ??
|
|
67
|
+
{
|
|
68
|
+
tool_name: toolName,
|
|
69
|
+
tool_input: { command: snapshot?.normalizedAction ?? replay?.command ?? summary },
|
|
70
|
+
};
|
|
46
71
|
const action = normalizeGatedAction({
|
|
47
72
|
kind: 'tool',
|
|
48
73
|
repoRoot,
|
|
49
|
-
cwd
|
|
50
|
-
toolName
|
|
51
|
-
payload
|
|
52
|
-
tool_name: 'Shell',
|
|
53
|
-
tool_input: { command: summary },
|
|
54
|
-
},
|
|
74
|
+
cwd,
|
|
75
|
+
toolName,
|
|
76
|
+
payload,
|
|
55
77
|
});
|
|
56
78
|
return await classifyGatedAction(action, config, classifierOptionsForRepo(config, repoRoot));
|
|
57
79
|
}
|
|
@@ -69,14 +91,22 @@ export async function diffReclassification(record, config, repoRoot) {
|
|
|
69
91
|
if (previousVerdict === next.verdict && previousReason === next.reason) {
|
|
70
92
|
return null;
|
|
71
93
|
}
|
|
94
|
+
const snapshot = parseAuditActionSnapshot(record);
|
|
95
|
+
const replay = parseAuditReplayContext(record);
|
|
72
96
|
return {
|
|
73
97
|
timestamp: record.timestamp,
|
|
74
98
|
event: record.event,
|
|
75
99
|
summary: record.summary,
|
|
76
100
|
fingerprint: record.fingerprint,
|
|
101
|
+
...(snapshot || replay
|
|
102
|
+
? { replayCwd: snapshot?.cwd ?? replay?.cwd, replayKind: snapshot?.kind ?? replay?.kind }
|
|
103
|
+
: {}),
|
|
77
104
|
previousVerdict,
|
|
78
105
|
previousReason,
|
|
79
106
|
nextVerdict: next.verdict,
|
|
80
107
|
nextReason: next.reason,
|
|
81
108
|
};
|
|
82
109
|
}
|
|
110
|
+
export function countMissingActionSnapshots(records) {
|
|
111
|
+
return records.filter((record) => record.event && GATE_EVENTS.has(record.event) && !parseAuditActionSnapshot(record)).length;
|
|
112
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { GatedActionKind } from './gate-contract.js';
|
|
2
|
+
import type { ScrubOptions } from './types.js';
|
|
3
|
+
/** Subagent fingerprint input — must match classify-subagent `fingerprintSource`. */
|
|
4
|
+
export declare function subagentFingerprintSource(payload: Record<string, unknown>, scrubOptions: ScrubOptions): unknown;
|
|
5
|
+
/**
|
|
6
|
+
* Scrubbed payload used for replay envelope hashing — aligned with classifier fingerprints.
|
|
7
|
+
* Tool: scrubbed `tool_input`. Subagent: description/prompt subset. Other: full payload.
|
|
8
|
+
*/
|
|
9
|
+
export declare function fingerprintReplayPayload(kind: GatedActionKind, payload: Record<string, unknown> | undefined, scrubOptions: ScrubOptions): Record<string, unknown> | undefined;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { scrubValue } from './scrub.js';
|
|
2
|
+
/** Subagent fingerprint input — must match classify-subagent `fingerprintSource`. */
|
|
3
|
+
export function subagentFingerprintSource(payload, scrubOptions) {
|
|
4
|
+
const toolInput = payload.tool_input;
|
|
5
|
+
if (toolInput && typeof toolInput === 'object') {
|
|
6
|
+
const input = toolInput;
|
|
7
|
+
return scrubValue({
|
|
8
|
+
description: input.description ?? '',
|
|
9
|
+
prompt: input.prompt ?? '',
|
|
10
|
+
}, scrubOptions);
|
|
11
|
+
}
|
|
12
|
+
const task = payload.task;
|
|
13
|
+
if (typeof task === 'string') {
|
|
14
|
+
return scrubValue({ task }, scrubOptions);
|
|
15
|
+
}
|
|
16
|
+
if (task && typeof task === 'object') {
|
|
17
|
+
const taskObj = task;
|
|
18
|
+
return scrubValue({
|
|
19
|
+
description: taskObj.description ?? '',
|
|
20
|
+
prompt: taskObj.prompt ?? '',
|
|
21
|
+
}, scrubOptions);
|
|
22
|
+
}
|
|
23
|
+
return scrubValue(payload, scrubOptions);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Scrubbed payload used for replay envelope hashing — aligned with classifier fingerprints.
|
|
27
|
+
* Tool: scrubbed `tool_input`. Subagent: description/prompt subset. Other: full payload.
|
|
28
|
+
*/
|
|
29
|
+
export function fingerprintReplayPayload(kind, payload, scrubOptions) {
|
|
30
|
+
if (!payload) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
if (kind === 'tool') {
|
|
34
|
+
const toolInput = payload.tool_input;
|
|
35
|
+
if (toolInput && typeof toolInput === 'object') {
|
|
36
|
+
return scrubValue(toolInput, scrubOptions);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (kind === 'subagent') {
|
|
40
|
+
return subagentFingerprintSource(payload, scrubOptions);
|
|
41
|
+
}
|
|
42
|
+
return scrubValue(payload, scrubOptions);
|
|
43
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export declare function isRedirectOperator(token: string): boolean;
|
|
2
|
+
export declare function isFdDuplication(token: string): boolean;
|
|
1
3
|
export declare function tokenizeShell(input: string): string[];
|
|
2
4
|
export declare function normalizeShellCommand(command: string, repoRoot: string, normalizeToken: (t: string, r: string) => string): string;
|
|
3
5
|
export declare function splitTopLevelSegments(tokens: string[]): string[][];
|
|
@@ -1,4 +1,72 @@
|
|
|
1
1
|
const ENV_PREFIX_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|"[^"]*"|\S+)$/;
|
|
2
|
+
const FD_DUPLICATION_PATTERN = /^\d+[<>]&(?:\d+|-)$/;
|
|
3
|
+
const FD_REDIRECT_PATTERN = /^\d+(?:>>?|<)$/;
|
|
4
|
+
function readDigits(input, index) {
|
|
5
|
+
let end = index;
|
|
6
|
+
while (end < input.length && /[0-9]/.test(input[end] ?? '')) {
|
|
7
|
+
end += 1;
|
|
8
|
+
}
|
|
9
|
+
return input.slice(index, end);
|
|
10
|
+
}
|
|
11
|
+
// This tokenizer only recognizes FD redirects when the digit run is immediately
|
|
12
|
+
// followed by `>` or `<` (for example `2>&1`, `3>file`, `12>>log`, `3<file`,
|
|
13
|
+
// `3<&1`). It does not try to recover bash's full word-boundary rules for cases
|
|
14
|
+
// like `foo12>>bar` or `foo12<bar`.
|
|
15
|
+
function readShellOperator(input, index) {
|
|
16
|
+
const char = input[index];
|
|
17
|
+
const next = input[index + 1] ?? '';
|
|
18
|
+
const digits = /[0-9]/.test(char) ? readDigits(input, index) : '';
|
|
19
|
+
const digitsLength = digits.length;
|
|
20
|
+
const afterDigits = digitsLength > 0 ? (input[index + digitsLength] ?? '') : '';
|
|
21
|
+
const afterDigitsNext = digitsLength > 0 ? (input[index + digitsLength + 1] ?? '') : '';
|
|
22
|
+
if (char === '&' && next === '&') {
|
|
23
|
+
return { token: '&&', length: 2 };
|
|
24
|
+
}
|
|
25
|
+
if (char === '|' && next === '|') {
|
|
26
|
+
return { token: '||', length: 2 };
|
|
27
|
+
}
|
|
28
|
+
if (char === '|' && next === '&') {
|
|
29
|
+
return { token: '|&', length: 2 };
|
|
30
|
+
}
|
|
31
|
+
if (char === '&' && next === '>') {
|
|
32
|
+
return { token: '&>', length: 2 };
|
|
33
|
+
}
|
|
34
|
+
if (digitsLength > 0 && (afterDigits === '>' || afterDigits === '<')) {
|
|
35
|
+
if (afterDigitsNext === '&') {
|
|
36
|
+
let end = index + digitsLength + 2;
|
|
37
|
+
while (end < input.length && /[0-9-]/.test(input[end] ?? '')) {
|
|
38
|
+
end += 1;
|
|
39
|
+
}
|
|
40
|
+
if (end > index + digitsLength + 2) {
|
|
41
|
+
return { token: input.slice(index, end), length: end - index };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (afterDigits === '>' && afterDigitsNext === '>') {
|
|
45
|
+
return { token: `${digits}>>`, length: digitsLength + 2 };
|
|
46
|
+
}
|
|
47
|
+
return { token: `${digits}${afterDigits}`, length: digitsLength + 1 };
|
|
48
|
+
}
|
|
49
|
+
if (char === '>' && next === '>') {
|
|
50
|
+
return { token: '>>', length: 2 };
|
|
51
|
+
}
|
|
52
|
+
if (char === '&') {
|
|
53
|
+
return { token: '&', length: 1 };
|
|
54
|
+
}
|
|
55
|
+
if (char === '|' || char === ';' || char === '>' || char === '<') {
|
|
56
|
+
return { token: char, length: 1 };
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
export function isRedirectOperator(token) {
|
|
61
|
+
return (token === '>' ||
|
|
62
|
+
token === '>>' ||
|
|
63
|
+
token === '<' ||
|
|
64
|
+
token === '&>' ||
|
|
65
|
+
FD_REDIRECT_PATTERN.test(token));
|
|
66
|
+
}
|
|
67
|
+
export function isFdDuplication(token) {
|
|
68
|
+
return FD_DUPLICATION_PATTERN.test(token);
|
|
69
|
+
}
|
|
2
70
|
export function tokenizeShell(input) {
|
|
3
71
|
const tokens = [];
|
|
4
72
|
let buffer = '';
|
|
@@ -12,7 +80,6 @@ export function tokenizeShell(input) {
|
|
|
12
80
|
};
|
|
13
81
|
for (let index = 0; index < input.length; index += 1) {
|
|
14
82
|
const char = input[index];
|
|
15
|
-
const next = input[index + 1] ?? '';
|
|
16
83
|
if (escaping) {
|
|
17
84
|
buffer += char;
|
|
18
85
|
escaping = false;
|
|
@@ -35,27 +102,11 @@ export function tokenizeShell(input) {
|
|
|
35
102
|
quote = char;
|
|
36
103
|
continue;
|
|
37
104
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
tokens.push('&&');
|
|
41
|
-
index += 1;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (char === '|' && next === '|') {
|
|
45
|
-
flush();
|
|
46
|
-
tokens.push('||');
|
|
47
|
-
index += 1;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (char === '>' && next === '>') {
|
|
105
|
+
const operator = readShellOperator(input, index);
|
|
106
|
+
if (operator) {
|
|
51
107
|
flush();
|
|
52
|
-
tokens.push(
|
|
53
|
-
index += 1;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (char === '|' || char === ';' || char === '>' || char === '<') {
|
|
57
|
-
flush();
|
|
58
|
-
tokens.push(char);
|
|
108
|
+
tokens.push(operator.token);
|
|
109
|
+
index += operator.length - 1;
|
|
59
110
|
continue;
|
|
60
111
|
}
|
|
61
112
|
if (char === '\n' || char === '\r') {
|
|
@@ -84,7 +135,12 @@ export function splitTopLevelSegments(tokens) {
|
|
|
84
135
|
const segments = [];
|
|
85
136
|
let current = [];
|
|
86
137
|
for (const token of tokens) {
|
|
87
|
-
if (token === '&&' ||
|
|
138
|
+
if (token === '&&' ||
|
|
139
|
+
token === '||' ||
|
|
140
|
+
token === ';' ||
|
|
141
|
+
token === '|' ||
|
|
142
|
+
token === '&' ||
|
|
143
|
+
token === '|&') {
|
|
88
144
|
if (current.length > 0) {
|
|
89
145
|
segments.push(current);
|
|
90
146
|
}
|
|
@@ -118,7 +174,10 @@ export function extractRedirectTargets(tokens) {
|
|
|
118
174
|
const targets = [];
|
|
119
175
|
for (let index = 0; index < tokens.length; index += 1) {
|
|
120
176
|
const token = tokens[index];
|
|
121
|
-
if (token
|
|
177
|
+
if (isFdDuplication(token)) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (isRedirectOperator(token)) {
|
|
122
181
|
const next = tokens[index + 1];
|
|
123
182
|
if (next) {
|
|
124
183
|
targets.push(next);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { BelayConfigV4 } from './config.js';
|
|
2
|
+
import type { GatedActionKind } from './gate-contract.js';
|
|
3
|
+
import type { ClassifyResult } from './types.js';
|
|
4
|
+
export type StandingAllowSource = 'provably-benign-corpus' | 'must-allow-catalog' | 'operator' | 'availability-reconfirmed';
|
|
5
|
+
export interface StandingAllowEntry {
|
|
6
|
+
kind: GatedActionKind;
|
|
7
|
+
fingerprint: string;
|
|
8
|
+
source: StandingAllowSource;
|
|
9
|
+
reason: string;
|
|
10
|
+
summary?: string;
|
|
11
|
+
createdAt: string;
|
|
12
|
+
expiresAt: string;
|
|
13
|
+
repoRoot?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface StandingAllowFile {
|
|
16
|
+
version: 1;
|
|
17
|
+
entries: StandingAllowEntry[];
|
|
18
|
+
}
|
|
19
|
+
export interface StandingAllowMatch {
|
|
20
|
+
source: StandingAllowSource;
|
|
21
|
+
catalogCommand?: string;
|
|
22
|
+
entryId?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Default TTL for operator / availability-reconfirmed standing-allow entries. */
|
|
25
|
+
export declare const DEFAULT_STANDING_ALLOW_TTL_MS: number;
|
|
26
|
+
export declare function standingAllowFile(config: BelayConfigV4, repoLocalStateDir: string): string;
|
|
27
|
+
export declare function isTier0StandingAllowBlocked(result: ClassifyResult): boolean;
|
|
28
|
+
export declare function compactStandingAllow(state: StandingAllowFile, now?: number): StandingAllowFile;
|
|
29
|
+
export declare function loadStandingAllow(filePath: string): Promise<StandingAllowFile>;
|
|
30
|
+
export declare function saveStandingAllow(filePath: string, state: StandingAllowFile): Promise<void>;
|
|
31
|
+
export declare function resolveStandingAllowMatch(params: {
|
|
32
|
+
kind: GatedActionKind;
|
|
33
|
+
result: ClassifyResult;
|
|
34
|
+
repoRoot: string;
|
|
35
|
+
state: StandingAllowFile;
|
|
36
|
+
now?: number;
|
|
37
|
+
}): StandingAllowMatch | null;
|
|
38
|
+
export declare function addStandingAllowEntry(state: StandingAllowFile, entry: Omit<StandingAllowEntry, 'createdAt' | 'expiresAt'> & {
|
|
39
|
+
createdAt?: string;
|
|
40
|
+
expiresAt?: string;
|
|
41
|
+
ttlMs?: number;
|
|
42
|
+
}): StandingAllowFile;
|
|
43
|
+
export declare function revokeStandingAllowEntry(state: StandingAllowFile, params: {
|
|
44
|
+
kind: GatedActionKind;
|
|
45
|
+
fingerprint: string;
|
|
46
|
+
repoRoot?: string;
|
|
47
|
+
}): {
|
|
48
|
+
state: StandingAllowFile;
|
|
49
|
+
removed: boolean;
|
|
50
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { STANDING_ALLOW_CATALOG } from '../corpus/standing-allow-catalog.generated.js';
|
|
5
|
+
import { belayStateDir } from './config.js';
|
|
6
|
+
const EMPTY_STANDING_ALLOW = {
|
|
7
|
+
version: 1,
|
|
8
|
+
entries: [],
|
|
9
|
+
};
|
|
10
|
+
/** Default TTL for operator / availability-reconfirmed standing-allow entries. */
|
|
11
|
+
export const DEFAULT_STANDING_ALLOW_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
12
|
+
const PROVABLY_BENIGN_COMMANDS = new Set(STANDING_ALLOW_CATALOG.shell.provablyBenign.map((entry) => entry.normalizedCommand));
|
|
13
|
+
const MUST_ALLOW_COMMANDS = new Set(STANDING_ALLOW_CATALOG.shell.mustAllow.map((entry) => entry.normalizedCommand));
|
|
14
|
+
/** Reasons that must never be silenced via standing-allow (defense in depth with signal checks). */
|
|
15
|
+
const STANDING_ALLOW_BLOCKED_REASONS = new Set([
|
|
16
|
+
'external_effect',
|
|
17
|
+
'tier1_catastrophic',
|
|
18
|
+
'protected_artifact',
|
|
19
|
+
'pipe_to_shell',
|
|
20
|
+
'command_substitution',
|
|
21
|
+
]);
|
|
22
|
+
export function standingAllowFile(config, repoLocalStateDir) {
|
|
23
|
+
return `${belayStateDir(config, repoLocalStateDir)}/standing-allow.json`;
|
|
24
|
+
}
|
|
25
|
+
export function isTier0StandingAllowBlocked(result) {
|
|
26
|
+
if (result.reason.startsWith('tier0_') || STANDING_ALLOW_BLOCKED_REASONS.has(result.reason)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const signals = result.assessment?.signals ?? [];
|
|
30
|
+
return signals.some((signal) => signal === 'tier0_external' || signal === 'tier1_catastrophic');
|
|
31
|
+
}
|
|
32
|
+
function isExpired(iso, now = Date.now()) {
|
|
33
|
+
const parsed = Date.parse(iso);
|
|
34
|
+
return Number.isNaN(parsed) || parsed <= now;
|
|
35
|
+
}
|
|
36
|
+
export function compactStandingAllow(state, now = Date.now()) {
|
|
37
|
+
return {
|
|
38
|
+
version: 1,
|
|
39
|
+
entries: state.entries.filter((entry) => !isExpired(entry.expiresAt, now)),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function sanitizeStandingAllowEntries(input) {
|
|
43
|
+
if (!Array.isArray(input)) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
return input.flatMap((entry) => {
|
|
47
|
+
if (!entry || typeof entry !== 'object') {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
const record = entry;
|
|
51
|
+
if ((record.kind !== 'shell' && record.kind !== 'tool' && record.kind !== 'subagent') ||
|
|
52
|
+
typeof record.fingerprint !== 'string' ||
|
|
53
|
+
!record.fingerprint.trim() ||
|
|
54
|
+
typeof record.expiresAt !== 'string' ||
|
|
55
|
+
typeof record.createdAt !== 'string' ||
|
|
56
|
+
typeof record.reason !== 'string') {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const source = record.source;
|
|
60
|
+
if (source !== 'operator' && source !== 'availability-reconfirmed') {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
kind: record.kind,
|
|
66
|
+
fingerprint: record.fingerprint,
|
|
67
|
+
source,
|
|
68
|
+
reason: record.reason,
|
|
69
|
+
createdAt: record.createdAt,
|
|
70
|
+
expiresAt: record.expiresAt,
|
|
71
|
+
...(typeof record.summary === 'string' ? { summary: record.summary } : {}),
|
|
72
|
+
...(typeof record.repoRoot === 'string' ? { repoRoot: record.repoRoot } : {}),
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export async function loadStandingAllow(filePath) {
|
|
78
|
+
if (!existsSync(filePath)) {
|
|
79
|
+
return { ...EMPTY_STANDING_ALLOW };
|
|
80
|
+
}
|
|
81
|
+
const raw = JSON.parse(await readFile(filePath, 'utf8'));
|
|
82
|
+
const rawCount = Array.isArray(raw.entries) ? raw.entries.length : 0;
|
|
83
|
+
const entries = sanitizeStandingAllowEntries(raw.entries);
|
|
84
|
+
const compacted = compactStandingAllow({ version: 1, entries });
|
|
85
|
+
if (rawCount !== compacted.entries.length || entries.length !== compacted.entries.length) {
|
|
86
|
+
await saveStandingAllow(filePath, compacted);
|
|
87
|
+
}
|
|
88
|
+
return compacted;
|
|
89
|
+
}
|
|
90
|
+
export async function saveStandingAllow(filePath, state) {
|
|
91
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
92
|
+
const compacted = compactStandingAllow(state);
|
|
93
|
+
await writeFile(filePath, `${JSON.stringify(compacted, null, 2)}\n`, 'utf8');
|
|
94
|
+
}
|
|
95
|
+
function matchBundledCatalog(kind, normalizedCommand) {
|
|
96
|
+
if (kind !== 'shell' || !normalizedCommand) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (PROVABLY_BENIGN_COMMANDS.has(normalizedCommand)) {
|
|
100
|
+
return { source: 'provably-benign-corpus', catalogCommand: normalizedCommand };
|
|
101
|
+
}
|
|
102
|
+
if (MUST_ALLOW_COMMANDS.has(normalizedCommand)) {
|
|
103
|
+
return { source: 'must-allow-catalog', catalogCommand: normalizedCommand };
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
function matchStateEntry(params) {
|
|
108
|
+
const now = params.now ?? Date.now();
|
|
109
|
+
const match = params.state.entries.find((entry) => entry.kind === params.kind &&
|
|
110
|
+
entry.fingerprint === params.fingerprint &&
|
|
111
|
+
!isExpired(entry.expiresAt, now) &&
|
|
112
|
+
(entry.repoRoot === undefined || entry.repoRoot === params.repoRoot));
|
|
113
|
+
if (!match) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return { source: match.source, entryId: match.fingerprint };
|
|
117
|
+
}
|
|
118
|
+
export function resolveStandingAllowMatch(params) {
|
|
119
|
+
if (params.result.verdict !== 'deny_pending_approval') {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
if (isTier0StandingAllowBlocked(params.result)) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const normalizedCommand = params.result.normalizedCommand ?? params.result.summary ?? '';
|
|
126
|
+
const catalogMatch = matchBundledCatalog(params.kind, normalizedCommand);
|
|
127
|
+
if (catalogMatch) {
|
|
128
|
+
return catalogMatch;
|
|
129
|
+
}
|
|
130
|
+
return matchStateEntry({
|
|
131
|
+
kind: params.kind,
|
|
132
|
+
fingerprint: params.result.fingerprint,
|
|
133
|
+
repoRoot: params.repoRoot,
|
|
134
|
+
state: params.state,
|
|
135
|
+
now: params.now,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
export function addStandingAllowEntry(state, entry) {
|
|
139
|
+
const createdAt = entry.createdAt ?? new Date().toISOString();
|
|
140
|
+
const ttlMs = entry.ttlMs ?? DEFAULT_STANDING_ALLOW_TTL_MS;
|
|
141
|
+
const expiresAt = entry.expiresAt ?? new Date(Date.parse(createdAt) + ttlMs).toISOString();
|
|
142
|
+
const next = {
|
|
143
|
+
kind: entry.kind,
|
|
144
|
+
fingerprint: entry.fingerprint,
|
|
145
|
+
source: entry.source,
|
|
146
|
+
reason: entry.reason,
|
|
147
|
+
createdAt,
|
|
148
|
+
expiresAt,
|
|
149
|
+
...(entry.summary ? { summary: entry.summary } : {}),
|
|
150
|
+
...(entry.repoRoot ? { repoRoot: entry.repoRoot } : {}),
|
|
151
|
+
};
|
|
152
|
+
const filtered = state.entries.filter((existing) => !(existing.kind === next.kind &&
|
|
153
|
+
existing.fingerprint === next.fingerprint &&
|
|
154
|
+
existing.repoRoot === next.repoRoot));
|
|
155
|
+
return compactStandingAllow({
|
|
156
|
+
version: 1,
|
|
157
|
+
entries: [...filtered, next],
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
export function revokeStandingAllowEntry(state, params) {
|
|
161
|
+
const before = state.entries.length;
|
|
162
|
+
const entries = state.entries.filter((entry) => {
|
|
163
|
+
if (entry.kind !== params.kind || entry.fingerprint !== params.fingerprint) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
if (params.repoRoot && entry.repoRoot && entry.repoRoot !== params.repoRoot) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
state: { version: 1, entries },
|
|
173
|
+
removed: entries.length < before,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -24,21 +24,6 @@ function observedAssessment(evaluation) {
|
|
|
24
24
|
if (evaluation.deletedCount > 0) {
|
|
25
25
|
signals.push('observed_deletions');
|
|
26
26
|
}
|
|
27
|
-
if (evaluation.categories.includes('repo_outside') ||
|
|
28
|
-
evaluation.categories.includes('control_plane') ||
|
|
29
|
-
evaluation.categories.includes('sensitive_path')) {
|
|
30
|
-
return {
|
|
31
|
-
reversibility: 'irreversible',
|
|
32
|
-
external: evaluation.categories.includes('repo_outside'),
|
|
33
|
-
blastRadius: evaluation.categories.includes('control_plane')
|
|
34
|
-
? 'agent-belay control plane'
|
|
35
|
-
: evaluation.categories.includes('repo_outside')
|
|
36
|
-
? 'outside the repository'
|
|
37
|
-
: 'sensitive path',
|
|
38
|
-
confidence: 1,
|
|
39
|
-
signals,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
27
|
if (evaluation.categories.includes('large_deletion')) {
|
|
43
28
|
return {
|
|
44
29
|
reversibility: 'irreversible',
|
|
@@ -66,10 +51,7 @@ export function evaluateTransactionalDiff(changes, ctx) {
|
|
|
66
51
|
categories.add('large_deletion');
|
|
67
52
|
}
|
|
68
53
|
const categoryList = [...categories];
|
|
69
|
-
const dangerous = categoryList.includes('
|
|
70
|
-
categoryList.includes('control_plane') ||
|
|
71
|
-
categoryList.includes('sensitive_path') ||
|
|
72
|
-
categoryList.includes('large_deletion');
|
|
54
|
+
const dangerous = categoryList.includes('large_deletion');
|
|
73
55
|
const base = {
|
|
74
56
|
categories: categoryList,
|
|
75
57
|
changes,
|