@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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M3 catalog: paths that survive reboot or change agent startup.
|
|
3
|
+
* @see docs/adr/tier0-retention-ledger.md
|
|
4
|
+
*/
|
|
5
|
+
export const PERSISTENT_AGENT_PATH_MARKERS = [
|
|
6
|
+
'.ssh/authorized_keys',
|
|
7
|
+
'.zshrc',
|
|
8
|
+
'.bashrc',
|
|
9
|
+
'.bash_profile',
|
|
10
|
+
'.profile',
|
|
11
|
+
'.config/',
|
|
12
|
+
'/crontab',
|
|
13
|
+
'LaunchAgents/',
|
|
14
|
+
'/Library/LaunchDaemons/',
|
|
15
|
+
];
|
|
16
|
+
export const OUTSIDE_REPO_SECRET_CREDENTIAL_MARKERS = [
|
|
17
|
+
'.env',
|
|
18
|
+
'.env.*',
|
|
19
|
+
'*.pem',
|
|
20
|
+
'.ssh/id_*',
|
|
21
|
+
'.aws/',
|
|
22
|
+
'.npmrc',
|
|
23
|
+
'.git-credentials',
|
|
24
|
+
'.netrc',
|
|
25
|
+
'.kube/config',
|
|
26
|
+
'.docker/config.json',
|
|
27
|
+
'.gnupg/',
|
|
28
|
+
'.pypirc',
|
|
29
|
+
];
|
|
30
|
+
export function isPersistentAgentPath(resolvedPath) {
|
|
31
|
+
const normalized = resolvedPath.replaceAll('\\', '/');
|
|
32
|
+
return (/\.ssh\/authorized_keys$/i.test(normalized) ||
|
|
33
|
+
/\/\.(zshrc|bashrc|bash_profile|profile)$/i.test(normalized) ||
|
|
34
|
+
/\/\.config\//i.test(normalized) ||
|
|
35
|
+
/\/crontab$/i.test(normalized) ||
|
|
36
|
+
/LaunchAgents\//i.test(normalized) ||
|
|
37
|
+
/\/Library\/LaunchDaemons\//i.test(normalized));
|
|
38
|
+
}
|
|
39
|
+
export function isOutsideRepoSecretCredentialPath(resolvedPath) {
|
|
40
|
+
const normalized = resolvedPath.replaceAll('\\', '/');
|
|
41
|
+
return (/\/\.env(?:\.[^/]+)?$/i.test(normalized) ||
|
|
42
|
+
/\/[^/]+\.pem$/i.test(normalized) ||
|
|
43
|
+
/\/\.ssh\/id_[^/]+$/i.test(normalized) ||
|
|
44
|
+
/\/\.aws\//i.test(normalized) ||
|
|
45
|
+
/\/\.npmrc$/i.test(normalized) ||
|
|
46
|
+
/\/\.git-credentials$/i.test(normalized) ||
|
|
47
|
+
/\/\.netrc$/i.test(normalized) ||
|
|
48
|
+
/\/\.kube\/config$/i.test(normalized) ||
|
|
49
|
+
/\/\.docker\/config\.json$/i.test(normalized) ||
|
|
50
|
+
/\/\.gnupg\//i.test(normalized) ||
|
|
51
|
+
/\/\.pypirc$/i.test(normalized));
|
|
52
|
+
}
|
|
@@ -16,10 +16,14 @@ export interface VerdictResult {
|
|
|
16
16
|
judgeTrace?: JudgeTrace;
|
|
17
17
|
}
|
|
18
18
|
export interface Tier1Verdict {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/** true = undoable via git/fs snapshot or trivial local revert (ADR-002 local-recoverable). */
|
|
20
|
+
local_recoverable: boolean;
|
|
21
21
|
destroys_history_or_secrets: boolean;
|
|
22
22
|
reason: string;
|
|
23
|
+
/** @deprecated Legacy LLM field; parsed only for migration. */
|
|
24
|
+
external_change?: boolean;
|
|
25
|
+
/** @deprecated Unused in decision logic. */
|
|
26
|
+
destroys_outside_repo?: boolean;
|
|
23
27
|
}
|
|
24
28
|
export interface Tier1EvaluateInput {
|
|
25
29
|
text: string;
|
|
@@ -39,12 +43,25 @@ export interface JudgeTrace {
|
|
|
39
43
|
latencyMs: number;
|
|
40
44
|
outboundRedacted?: boolean;
|
|
41
45
|
fallbackReason?: string;
|
|
46
|
+
judgeSessionUsed?: boolean;
|
|
47
|
+
judgeSessionReused?: boolean;
|
|
48
|
+
judgeSessionRefHash?: string;
|
|
49
|
+
judgeSessionResetReason?: string;
|
|
50
|
+
judgeFallbackReason?: string;
|
|
51
|
+
judgeConnectMs?: number;
|
|
52
|
+
judgeEvalMs?: number;
|
|
53
|
+
judgeParseMs?: number;
|
|
54
|
+
judgeShadowCompared?: boolean;
|
|
55
|
+
judgeShadowMismatch?: boolean;
|
|
56
|
+
judgeShadowMismatchRateWindow?: number;
|
|
57
|
+
judgeKillSwitchTriggered?: boolean;
|
|
42
58
|
}
|
|
43
59
|
export type VerdictMode = 'enforce' | 'audit';
|
|
44
60
|
export interface VerdictContext {
|
|
45
61
|
cwd: string;
|
|
46
62
|
repoRoot: string;
|
|
47
63
|
trustedCwd: boolean;
|
|
64
|
+
trustedWorkspaceRoots?: string[];
|
|
48
65
|
sensitivePaths: string[];
|
|
49
66
|
protectedArtifactRoots?: string[];
|
|
50
67
|
customAllowCommands?: string[];
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { analyzePathTargets, cwdRelative, resolveTrustedPath } from './containment.js';
|
|
1
|
+
import { resolveMutationTarget } from '../path-utils.js';
|
|
2
|
+
import { extractRedirectTargets, isFdDuplication, isRedirectOperator, tokenizeShell, } from '../shell-tokenizer.js';
|
|
3
|
+
import { analyzePathTargets, cwdRelative, isDestructiveHighStakesMutation, resolveTrustedPath, touchesProtectedRoot, } from './containment.js';
|
|
5
4
|
import { classifyEgressTool } from './egress-classify.js';
|
|
6
5
|
import { verdictFingerprint } from './fingerprint.js';
|
|
7
|
-
import { prescanInterpreterCode, tier1RequiresAsk } from './judge.js';
|
|
6
|
+
import { mutationPrescanRequiresAsk, prescanInterpreterCode, tier1RequiresAsk } from './judge.js';
|
|
8
7
|
import { isRoutineLauncher, resolveLauncherRecipe } from './launcher-resolve.js';
|
|
9
8
|
import { allowFromCustomOverride, askFromCustomExternal, customAllowMatch, customExternalMatch, } from './overrides.js';
|
|
10
9
|
import { extractRecursiveScript, isBareInterpreter, isVariableIndirectHead, parseSegment, peelTransparentWrappers, redactCommand, segmentOpacity, splitTopLevelSegments, substitutionInners, } from './parser.js';
|
|
@@ -197,7 +196,7 @@ function extractPathArgs(tokens) {
|
|
|
197
196
|
const args = [...redirects];
|
|
198
197
|
for (let index = 1; index < tokens.length; index += 1) {
|
|
199
198
|
const token = tokens[index];
|
|
200
|
-
if (!token || token.startsWith('-') || token
|
|
199
|
+
if (!token || token.startsWith('-') || isRedirectOperator(token) || isFdDuplication(token)) {
|
|
201
200
|
continue;
|
|
202
201
|
}
|
|
203
202
|
if (redirects.includes(token)) {
|
|
@@ -257,13 +256,45 @@ function tier0ExternalMatch(key, head, tokens) {
|
|
|
257
256
|
}
|
|
258
257
|
return false;
|
|
259
258
|
}
|
|
259
|
+
function isBelayJudgeSelfCommand(tokens) {
|
|
260
|
+
return (tokens[0] === 'belay' &&
|
|
261
|
+
tokens[1] === 'judge' &&
|
|
262
|
+
(tokens[2] === 'use' ||
|
|
263
|
+
tokens[2] === 'status' ||
|
|
264
|
+
tokens[2] === 'list' ||
|
|
265
|
+
tokens[2] === 'test' ||
|
|
266
|
+
tokens[2] === 'consent'));
|
|
267
|
+
}
|
|
268
|
+
function isBelayConfigSelfCommand(tokens) {
|
|
269
|
+
if (tokens[0] !== 'belay' || tokens[1] !== 'config') {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
const sub = tokens[2];
|
|
273
|
+
if (!sub) {
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
if (sub === 'list' || sub === 'get' || sub === 'judge') {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
if (sub === 'credential') {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
if (sub === 'set' || sub === 'unset') {
|
|
283
|
+
const pathKey = tokens[3] ?? '';
|
|
284
|
+
return pathKey.startsWith('judge.');
|
|
285
|
+
}
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
260
288
|
function isBelaySelfCommand(tokens) {
|
|
261
289
|
const head = tokens[0] ?? '';
|
|
262
290
|
const subcommand = tokens[1] ?? '';
|
|
263
|
-
return head === 'belay' &&
|
|
291
|
+
return (head === 'belay' &&
|
|
292
|
+
(BELAY_SELF_COMMANDS.has(subcommand) ||
|
|
293
|
+
isBelayJudgeSelfCommand(tokens) ||
|
|
294
|
+
isBelayConfigSelfCommand(tokens)));
|
|
264
295
|
}
|
|
265
296
|
function tier0HighStakesRm(tokens, context) {
|
|
266
|
-
const head = tokens[0] ?? '';
|
|
297
|
+
const head = (tokens[0] ?? '').split('/').pop() ?? '';
|
|
267
298
|
if (head !== 'rm') {
|
|
268
299
|
return null;
|
|
269
300
|
}
|
|
@@ -273,6 +304,7 @@ function tier0HighStakesRm(tokens, context) {
|
|
|
273
304
|
cwd: context.cwd,
|
|
274
305
|
repoRoot: context.repoRoot,
|
|
275
306
|
trustedCwd: context.trustedCwd,
|
|
307
|
+
trustedWorkspaceRoots: context.trustedWorkspaceRoots,
|
|
276
308
|
sensitivePaths: context.sensitivePaths,
|
|
277
309
|
protectedArtifactRoots: context.protectedArtifactRoots,
|
|
278
310
|
});
|
|
@@ -286,31 +318,40 @@ function tier0HighStakesRm(tokens, context) {
|
|
|
286
318
|
signals: ['missing_trusted_cwd', ...analysis.signals],
|
|
287
319
|
});
|
|
288
320
|
}
|
|
289
|
-
if (analysis.isHighStakes) {
|
|
290
|
-
return askVerdict({
|
|
291
|
-
location: analysis.location,
|
|
292
|
-
opacity: 'transparent',
|
|
293
|
-
effect: 'local_mutation',
|
|
294
|
-
confidence: 'deterministic',
|
|
295
|
-
reason: 'high_stakes_path',
|
|
296
|
-
signals: ['high_stakes_path', ...analysis.signals],
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
321
|
for (const target of targets) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
322
|
+
const normalized = target.replace(/^['"]|['"]$/g, '');
|
|
323
|
+
if (normalized === '~' || normalized === '$HOME' || normalized.startsWith('~/')) {
|
|
324
|
+
return askVerdict({
|
|
325
|
+
location: 'repo_outside',
|
|
326
|
+
opacity: 'transparent',
|
|
327
|
+
effect: 'local_mutation',
|
|
328
|
+
confidence: 'deterministic',
|
|
329
|
+
reason: 'high_stakes_path',
|
|
330
|
+
signals: ['catastrophic_home_deletion', ...analysis.signals],
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (analysis.isHighStakes && analysis.signals.includes('high_stakes_path')) {
|
|
335
|
+
let destructiveHighStakes = false;
|
|
336
|
+
for (const target of targets) {
|
|
337
|
+
const resolved = resolveTrustedPath(target, context.cwd, context.trustedCwd) ??
|
|
338
|
+
resolveMutationTarget(target, context.cwd);
|
|
339
|
+
if (resolved &&
|
|
340
|
+
isDestructiveHighStakesMutation(head, resolved, context.repoRoot, context.sensitivePaths, context.protectedArtifactRoots, context.trustedWorkspaceRoots)) {
|
|
341
|
+
destructiveHighStakes = true;
|
|
342
|
+
break;
|
|
312
343
|
}
|
|
313
344
|
}
|
|
345
|
+
if (destructiveHighStakes) {
|
|
346
|
+
return askVerdict({
|
|
347
|
+
location: analysis.location,
|
|
348
|
+
opacity: 'transparent',
|
|
349
|
+
effect: 'local_mutation',
|
|
350
|
+
confidence: 'deterministic',
|
|
351
|
+
reason: 'high_stakes_path',
|
|
352
|
+
signals: ['high_stakes_path', ...analysis.signals],
|
|
353
|
+
});
|
|
354
|
+
}
|
|
314
355
|
}
|
|
315
356
|
return null;
|
|
316
357
|
}
|
|
@@ -515,11 +556,15 @@ async function evaluateSegment(command, context, depth) {
|
|
|
515
556
|
effect = 'local_mutation';
|
|
516
557
|
}
|
|
517
558
|
const pathArgs = extractPathArgs(peeled);
|
|
559
|
+
if (extractRedirectTargets(peeled).length > 0 && effect === 'read_only') {
|
|
560
|
+
effect = 'local_mutation';
|
|
561
|
+
}
|
|
518
562
|
const pathAnalysis = analyzePathTargets({
|
|
519
563
|
targets: pathArgs,
|
|
520
564
|
cwd: context.cwd,
|
|
521
565
|
repoRoot: context.repoRoot,
|
|
522
566
|
trustedCwd: context.trustedCwd,
|
|
567
|
+
trustedWorkspaceRoots: context.trustedWorkspaceRoots,
|
|
523
568
|
sensitivePaths: context.sensitivePaths,
|
|
524
569
|
protectedArtifactRoots: context.protectedArtifactRoots,
|
|
525
570
|
});
|
|
@@ -545,15 +590,45 @@ async function evaluateSegment(command, context, depth) {
|
|
|
545
590
|
});
|
|
546
591
|
}
|
|
547
592
|
}
|
|
548
|
-
if (
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
593
|
+
if ((effect === 'local_mutation' || effect === 'unknown') &&
|
|
594
|
+
context.protectedArtifactRoots &&
|
|
595
|
+
context.protectedArtifactRoots.length > 0) {
|
|
596
|
+
for (const target of pathArgs) {
|
|
597
|
+
const resolved = resolveTrustedPath(target, context.cwd, context.trustedCwd) ??
|
|
598
|
+
resolveMutationTarget(target, context.cwd);
|
|
599
|
+
if (resolved && touchesProtectedRoot(resolved, context.protectedArtifactRoots)) {
|
|
600
|
+
return askVerdict({
|
|
601
|
+
location: pathAnalysis.location,
|
|
602
|
+
opacity: 'transparent',
|
|
603
|
+
effect: 'local_mutation',
|
|
604
|
+
confidence: 'deterministic',
|
|
605
|
+
reason: 'high_stakes_path',
|
|
606
|
+
signals: ['high_stakes_path', 'control_plane_path'],
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (effect === 'local_mutation' || effect === 'unknown') {
|
|
612
|
+
let destructiveHighStakes = false;
|
|
613
|
+
for (const target of pathArgs) {
|
|
614
|
+
const resolved = resolveTrustedPath(target, context.cwd, context.trustedCwd) ??
|
|
615
|
+
resolveMutationTarget(target, context.cwd);
|
|
616
|
+
if (resolved &&
|
|
617
|
+
isDestructiveHighStakesMutation(segment.head, resolved, context.repoRoot, context.sensitivePaths, context.protectedArtifactRoots, context.trustedWorkspaceRoots)) {
|
|
618
|
+
destructiveHighStakes = true;
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (destructiveHighStakes) {
|
|
623
|
+
return askVerdict({
|
|
624
|
+
location: pathAnalysis.location,
|
|
625
|
+
opacity: 'transparent',
|
|
626
|
+
effect: 'local_mutation',
|
|
627
|
+
confidence: 'deterministic',
|
|
628
|
+
reason: 'high_stakes_path',
|
|
629
|
+
signals: pathAnalysis.signals,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
557
632
|
}
|
|
558
633
|
if (segment.head === 'find' && isFindDangerous(peeled)) {
|
|
559
634
|
return askVerdict({
|
|
@@ -565,15 +640,15 @@ async function evaluateSegment(command, context, depth) {
|
|
|
565
640
|
signals: ['find_dangerous_action'],
|
|
566
641
|
});
|
|
567
642
|
}
|
|
568
|
-
if (
|
|
569
|
-
|
|
570
|
-
return
|
|
643
|
+
if (effect === 'read_only' &&
|
|
644
|
+
(pathAnalysis.location === 'repo_outside' || pathAnalysis.location === 'mixed')) {
|
|
645
|
+
return allowVerdict({
|
|
571
646
|
location: pathAnalysis.location,
|
|
572
|
-
opacity
|
|
573
|
-
effect:
|
|
647
|
+
opacity,
|
|
648
|
+
effect: 'read_only',
|
|
574
649
|
confidence: 'deterministic',
|
|
575
|
-
reason: '
|
|
576
|
-
signals: ['
|
|
650
|
+
reason: 'read_only',
|
|
651
|
+
signals: ['read_only', ...pathAnalysis.signals],
|
|
577
652
|
});
|
|
578
653
|
}
|
|
579
654
|
if (pathAnalysis.location === 'unknown' &&
|
|
@@ -588,7 +663,35 @@ async function evaluateSegment(command, context, depth) {
|
|
|
588
663
|
signals: ['unknown_location_mutation'],
|
|
589
664
|
});
|
|
590
665
|
}
|
|
591
|
-
const
|
|
666
|
+
const outsideMutation = pathAnalysis.location === 'repo_outside' || pathAnalysis.location === 'mixed';
|
|
667
|
+
const mutationPrescan = (effect === 'local_mutation' || effect === 'unknown') && pathArgs.length > 0
|
|
668
|
+
? mutationPrescanRequiresAsk({
|
|
669
|
+
targets: pathArgs,
|
|
670
|
+
cwd: context.cwd,
|
|
671
|
+
repoRoot: context.repoRoot,
|
|
672
|
+
trustedCwd: context.trustedCwd,
|
|
673
|
+
trustedWorkspaceRoots: context.trustedWorkspaceRoots,
|
|
674
|
+
sensitivePaths: context.sensitivePaths,
|
|
675
|
+
})
|
|
676
|
+
: null;
|
|
677
|
+
if (mutationPrescan) {
|
|
678
|
+
return askVerdict({
|
|
679
|
+
location: pathAnalysis.location === 'unknown'
|
|
680
|
+
? 'unknown'
|
|
681
|
+
: pathAnalysis.location === 'repo_outside' || pathAnalysis.location === 'mixed'
|
|
682
|
+
? pathAnalysis.location
|
|
683
|
+
: 'repo_local',
|
|
684
|
+
opacity,
|
|
685
|
+
effect: 'local_mutation',
|
|
686
|
+
confidence: 'deterministic',
|
|
687
|
+
reason: 'tier1_catastrophic',
|
|
688
|
+
signals: ['tier1_catastrophic', mutationPrescan.reason],
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
const needsTier1 = effect === 'unknown' ||
|
|
692
|
+
TIER0_EXTERNAL_HEADS.has(segment.head) ||
|
|
693
|
+
egressClass === 'ambiguous' ||
|
|
694
|
+
(outsideMutation && effect !== 'read_only');
|
|
592
695
|
let tier1Trace;
|
|
593
696
|
if (needsTier1) {
|
|
594
697
|
const tier1Text = recursiveScript ?? command;
|
|
@@ -600,9 +703,13 @@ async function evaluateSegment(command, context, depth) {
|
|
|
600
703
|
tier1Trace = context.judge.lastTrace;
|
|
601
704
|
if (tier1RequiresAsk(tier1)) {
|
|
602
705
|
return askVerdict({
|
|
603
|
-
location: pathAnalysis.location === 'unknown'
|
|
706
|
+
location: pathAnalysis.location === 'unknown'
|
|
707
|
+
? 'unknown'
|
|
708
|
+
: pathAnalysis.location === 'repo_outside' || pathAnalysis.location === 'mixed'
|
|
709
|
+
? pathAnalysis.location
|
|
710
|
+
: 'repo_local',
|
|
604
711
|
opacity,
|
|
605
|
-
effect: tier1.
|
|
712
|
+
effect: !tier1.local_recoverable ? 'remote_mutation' : effect,
|
|
606
713
|
confidence: 'llm',
|
|
607
714
|
reason: 'tier1_catastrophic',
|
|
608
715
|
signals: ['tier1_catastrophic', tier1.reason],
|
|
@@ -610,6 +717,16 @@ async function evaluateSegment(command, context, depth) {
|
|
|
610
717
|
});
|
|
611
718
|
}
|
|
612
719
|
}
|
|
720
|
+
if (outsideMutation && effect !== 'read_only') {
|
|
721
|
+
return withJudgeTrace(allowVerdict({
|
|
722
|
+
location: pathAnalysis.location,
|
|
723
|
+
opacity,
|
|
724
|
+
effect: 'local_mutation',
|
|
725
|
+
confidence: tier1Trace ? 'llm' : 'deterministic',
|
|
726
|
+
reason: 'repo_outside_local_mutation',
|
|
727
|
+
signals: ['repo_outside_local_mutation', ...pathAnalysis.signals],
|
|
728
|
+
}), tier1Trace);
|
|
729
|
+
}
|
|
613
730
|
if (pathAnalysis.location === 'repo_local' &&
|
|
614
731
|
(effect === 'read_only' || effect === 'local_mutation') &&
|
|
615
732
|
opacity !== 'opaque') {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type MutatedCase } from './mutators.js';
|
|
2
|
+
declare const MUST_ASK_VERDICT: "deny_pending_approval";
|
|
3
|
+
export declare function probeCaseKey(entry: {
|
|
4
|
+
core: string;
|
|
5
|
+
mutatorId: string;
|
|
6
|
+
command: string;
|
|
7
|
+
}): string;
|
|
8
|
+
export interface ProbePassedCase {
|
|
9
|
+
core: string;
|
|
10
|
+
mutatorId: string;
|
|
11
|
+
command: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AdversarialProbeFailure {
|
|
14
|
+
core: string;
|
|
15
|
+
mutatorId: string;
|
|
16
|
+
command: string;
|
|
17
|
+
expected: typeof MUST_ASK_VERDICT;
|
|
18
|
+
actual: string;
|
|
19
|
+
reason: string;
|
|
20
|
+
}
|
|
21
|
+
export interface AdversarialProbeFpFailure {
|
|
22
|
+
core: string;
|
|
23
|
+
mutatorId: string;
|
|
24
|
+
command: string;
|
|
25
|
+
actual: string;
|
|
26
|
+
reason: string;
|
|
27
|
+
}
|
|
28
|
+
export interface AdversarialProbeReport {
|
|
29
|
+
iteration: number;
|
|
30
|
+
generatedAt: string;
|
|
31
|
+
seed: number;
|
|
32
|
+
batchId: string;
|
|
33
|
+
sourceCommands: string[];
|
|
34
|
+
selectedMutators: string[];
|
|
35
|
+
maxCases?: number;
|
|
36
|
+
holdoutRatio: number;
|
|
37
|
+
totalCases: number;
|
|
38
|
+
fixSetSize: number;
|
|
39
|
+
holdoutSetSize: number;
|
|
40
|
+
fixSetFnRate: number;
|
|
41
|
+
firstPassFnRate: number;
|
|
42
|
+
/** null when no FP probe cases were evaluated. */
|
|
43
|
+
firstPassFpRate: number | null;
|
|
44
|
+
fpProbeSize: number;
|
|
45
|
+
/** null when holdout set is empty, or fix set FN rate is 0 but holdout FN > 0. */
|
|
46
|
+
holdoutFnRate: number | null;
|
|
47
|
+
/** holdoutFnRate / fixSetFnRate; 1.0 when both are 0. null when holdout empty. */
|
|
48
|
+
holdoutFixFnRateRatio: number | null;
|
|
49
|
+
passedCases: ProbePassedCase[];
|
|
50
|
+
failures: AdversarialProbeFailure[];
|
|
51
|
+
fpFailures: AdversarialProbeFpFailure[];
|
|
52
|
+
/** Populated by fix sessions / CI integration; empty when probe-only. */
|
|
53
|
+
filesChanged: string[];
|
|
54
|
+
}
|
|
55
|
+
export interface AdversarialProbeOptions {
|
|
56
|
+
seed?: number;
|
|
57
|
+
holdoutRatio?: number;
|
|
58
|
+
maxCases?: number;
|
|
59
|
+
outputDir?: string;
|
|
60
|
+
repoRoot?: string;
|
|
61
|
+
iteration?: number;
|
|
62
|
+
}
|
|
63
|
+
/** Mulberry32 PRNG for reproducible shuffles. */
|
|
64
|
+
export declare function createSeededRandom(seed: number): () => number;
|
|
65
|
+
export declare function shuffleWithSeed<T>(items: readonly T[], seed: number): T[];
|
|
66
|
+
export declare function splitFixHoldout<T>(items: readonly T[], seed: number, holdoutRatio?: number): {
|
|
67
|
+
fix: T[];
|
|
68
|
+
holdout: T[];
|
|
69
|
+
};
|
|
70
|
+
export declare function generateProbeCases(seed: number, maxCases?: number): MutatedCase[];
|
|
71
|
+
/** Benign must-allow cores × AUTO_LABEL mutators — FP over-stop probe (not for corpus auto-add). */
|
|
72
|
+
export declare function generateFpProbeCases(seed: number): MutatedCase[];
|
|
73
|
+
/** Ratio for overfitting detection; null when holdout unavailable or fix-perfect but holdout not. */
|
|
74
|
+
export declare function holdoutFixFnRateRatio(fixSetFnRate: number, holdoutFnRate: number | null): number | null;
|
|
75
|
+
export declare function runAdversarialProbe(options?: AdversarialProbeOptions): Promise<AdversarialProbeReport>;
|
|
76
|
+
export declare function defaultProbeOutputDir(): string;
|
|
77
|
+
export declare function formatProbeSummary(report: AdversarialProbeReport): string;
|
|
78
|
+
export declare function parseProbeCliArgs(argv: string[]): {
|
|
79
|
+
seed: number;
|
|
80
|
+
holdoutRatio: number;
|
|
81
|
+
maxCases?: number;
|
|
82
|
+
strict: boolean;
|
|
83
|
+
outputDir: string;
|
|
84
|
+
};
|
|
85
|
+
export declare function main(argv?: string[]): Promise<number>;
|
|
86
|
+
export {};
|