@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,24 @@
|
|
|
1
|
+
import type { BelayJudgeShadowConfig } from './judge-runtime-config.js';
|
|
2
|
+
import type { Tier1Verdict } from './types.js';
|
|
3
|
+
export interface JudgeShadowState {
|
|
4
|
+
compared: number;
|
|
5
|
+
mismatches: number;
|
|
6
|
+
dailyCount: number;
|
|
7
|
+
dailyKey: string;
|
|
8
|
+
killSwitchTriggered: boolean;
|
|
9
|
+
mismatchWindow: boolean[];
|
|
10
|
+
}
|
|
11
|
+
export declare function setJudgeShadowClockForTests(next: () => Date): void;
|
|
12
|
+
export declare function setJudgeShadowRandomForTests(next: () => number): void;
|
|
13
|
+
export declare function resetJudgeShadowState(repoRoot?: string): void;
|
|
14
|
+
export declare function isJudgeSessionKillSwitchActive(repoRoot: string, stateDir?: string): Promise<boolean>;
|
|
15
|
+
export declare function triggerJudgeSessionKillSwitch(repoRoot: string, stateDir?: string, reason?: string): Promise<void>;
|
|
16
|
+
export declare function shouldRunShadowComparison(repoRoot: string, providerId: string, config: BelayJudgeShadowConfig): boolean;
|
|
17
|
+
export declare function verdictsEquivalent(a: Tier1Verdict, b: Tier1Verdict): boolean;
|
|
18
|
+
export declare function recordShadowComparison(repoRoot: string, config: BelayJudgeShadowConfig, mismatch: boolean): {
|
|
19
|
+
compared: boolean;
|
|
20
|
+
mismatch: boolean;
|
|
21
|
+
mismatchRateWindow: number;
|
|
22
|
+
killSwitchTriggered: boolean;
|
|
23
|
+
};
|
|
24
|
+
export declare function judgeShadowAuditFields(repoRoot: string): Record<string, unknown>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { isJudgeSessionKillSwitchPersisted, persistJudgeSessionKillSwitch, } from './judge-session-kill-switch.js';
|
|
2
|
+
const shadowStates = new Map();
|
|
3
|
+
let clock = () => new Date();
|
|
4
|
+
let random = Math.random;
|
|
5
|
+
export function setJudgeShadowClockForTests(next) {
|
|
6
|
+
clock = next;
|
|
7
|
+
}
|
|
8
|
+
export function setJudgeShadowRandomForTests(next) {
|
|
9
|
+
random = next;
|
|
10
|
+
}
|
|
11
|
+
export function resetJudgeShadowState(repoRoot) {
|
|
12
|
+
if (repoRoot) {
|
|
13
|
+
shadowStates.delete(repoRoot);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
shadowStates.clear();
|
|
17
|
+
}
|
|
18
|
+
function dailyKeyFor(date) {
|
|
19
|
+
return date.toISOString().slice(0, 10);
|
|
20
|
+
}
|
|
21
|
+
function getShadowState(repoRoot) {
|
|
22
|
+
const existing = shadowStates.get(repoRoot);
|
|
23
|
+
if (existing) {
|
|
24
|
+
const key = dailyKeyFor(clock());
|
|
25
|
+
if (existing.dailyKey !== key) {
|
|
26
|
+
existing.dailyCount = 0;
|
|
27
|
+
existing.dailyKey = key;
|
|
28
|
+
}
|
|
29
|
+
return existing;
|
|
30
|
+
}
|
|
31
|
+
const created = {
|
|
32
|
+
compared: 0,
|
|
33
|
+
mismatches: 0,
|
|
34
|
+
dailyCount: 0,
|
|
35
|
+
dailyKey: dailyKeyFor(clock()),
|
|
36
|
+
killSwitchTriggered: false,
|
|
37
|
+
mismatchWindow: [],
|
|
38
|
+
};
|
|
39
|
+
shadowStates.set(repoRoot, created);
|
|
40
|
+
return created;
|
|
41
|
+
}
|
|
42
|
+
export async function isJudgeSessionKillSwitchActive(repoRoot, stateDir) {
|
|
43
|
+
if (getShadowState(repoRoot).killSwitchTriggered) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (stateDir) {
|
|
47
|
+
return isJudgeSessionKillSwitchPersisted(stateDir);
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
export async function triggerJudgeSessionKillSwitch(repoRoot, stateDir, reason = 'shadow_mismatch') {
|
|
52
|
+
getShadowState(repoRoot).killSwitchTriggered = true;
|
|
53
|
+
if (stateDir) {
|
|
54
|
+
await persistJudgeSessionKillSwitch(stateDir, reason);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function shouldRunShadowComparison(repoRoot, providerId, config) {
|
|
58
|
+
if (!config.enabled) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (!config.providerAllowlist.includes(providerId)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const state = getShadowState(repoRoot);
|
|
65
|
+
if (state.killSwitchTriggered) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (state.dailyCount >= config.dailyRequestCap) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const rate = Math.min(config.sampleRate, config.sampleRateMax);
|
|
72
|
+
return random() < rate;
|
|
73
|
+
}
|
|
74
|
+
export function verdictsEquivalent(a, b) {
|
|
75
|
+
return (a.local_recoverable === b.local_recoverable &&
|
|
76
|
+
a.destroys_history_or_secrets === b.destroys_history_or_secrets);
|
|
77
|
+
}
|
|
78
|
+
export function recordShadowComparison(repoRoot, config, mismatch) {
|
|
79
|
+
const state = getShadowState(repoRoot);
|
|
80
|
+
state.compared += 1;
|
|
81
|
+
state.dailyCount += 1;
|
|
82
|
+
if (mismatch) {
|
|
83
|
+
state.mismatches += 1;
|
|
84
|
+
}
|
|
85
|
+
state.mismatchWindow.push(mismatch);
|
|
86
|
+
if (state.mismatchWindow.length > config.windowSize) {
|
|
87
|
+
state.mismatchWindow.shift();
|
|
88
|
+
}
|
|
89
|
+
const windowComparisons = state.mismatchWindow.length;
|
|
90
|
+
const windowMismatches = state.mismatchWindow.filter(Boolean).length;
|
|
91
|
+
const mismatchRateWindow = windowComparisons === 0 ? 0 : windowMismatches / windowComparisons;
|
|
92
|
+
if (windowComparisons >= Math.min(10, config.windowSize) &&
|
|
93
|
+
mismatchRateWindow > config.mismatchRateThreshold) {
|
|
94
|
+
state.killSwitchTriggered = true;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
compared: true,
|
|
98
|
+
mismatch,
|
|
99
|
+
mismatchRateWindow,
|
|
100
|
+
killSwitchTriggered: state.killSwitchTriggered,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function judgeShadowAuditFields(repoRoot) {
|
|
104
|
+
const state = shadowStates.get(repoRoot);
|
|
105
|
+
if (!state) {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
const windowComparisons = state.mismatchWindow.length;
|
|
109
|
+
const windowMismatches = state.mismatchWindow.filter(Boolean).length;
|
|
110
|
+
return {
|
|
111
|
+
judgeShadowCompared: state.compared > 0,
|
|
112
|
+
judgeShadowMismatch: state.mismatches > 0,
|
|
113
|
+
judgeShadowMismatchRateWindow: windowComparisons === 0 ? 0 : windowMismatches / windowComparisons,
|
|
114
|
+
judgeKillSwitchTriggered: state.killSwitchTriggered,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { JudgeProviderId } from './judge-catalog.js';
|
|
2
|
+
import { type CliInvocation, type CliJudgeRunCommand } from './judge-cli.js';
|
|
3
|
+
import type { BelayJudgeRuntimeConfig } from './judge-runtime-config.js';
|
|
4
|
+
import { type JudgeFallbackReason, type JudgeSessionResetReason } from './judge-session-guard.js';
|
|
5
|
+
import type { Tier1Verdict } from './types.js';
|
|
6
|
+
export interface JudgeTransportContext {
|
|
7
|
+
providerId: Exclude<JudgeProviderId, 'ollama'>;
|
|
8
|
+
model: string;
|
|
9
|
+
repoRoot: string;
|
|
10
|
+
stateDir: string;
|
|
11
|
+
judgeMode: string;
|
|
12
|
+
runtime: BelayJudgeRuntimeConfig;
|
|
13
|
+
judgeTimeoutMs: number;
|
|
14
|
+
}
|
|
15
|
+
export interface JudgeTransportEvaluateRequest {
|
|
16
|
+
prompt: string;
|
|
17
|
+
context: JudgeTransportContext;
|
|
18
|
+
}
|
|
19
|
+
export interface JudgeTransportEvaluateResult {
|
|
20
|
+
raw: string;
|
|
21
|
+
verdict: Tier1Verdict | null;
|
|
22
|
+
transport: 'spawn' | 'session';
|
|
23
|
+
sessionUsed: boolean;
|
|
24
|
+
sessionReused: boolean;
|
|
25
|
+
fallbackReason?: JudgeFallbackReason;
|
|
26
|
+
sessionResetReason?: JudgeSessionResetReason;
|
|
27
|
+
shadowCompared?: boolean;
|
|
28
|
+
shadowMismatch?: boolean;
|
|
29
|
+
shadowMismatchRateWindow?: number;
|
|
30
|
+
killSwitchTriggered?: boolean;
|
|
31
|
+
sessionRefHash?: string;
|
|
32
|
+
connectMs: number;
|
|
33
|
+
evalMs: number;
|
|
34
|
+
parseMs: number;
|
|
35
|
+
}
|
|
36
|
+
export interface JudgeTransportBridgeOptions {
|
|
37
|
+
runCommand?: CliJudgeRunCommand;
|
|
38
|
+
}
|
|
39
|
+
export declare function evaluateWithJudgeTransport(request: JudgeTransportEvaluateRequest, options?: JudgeTransportBridgeOptions): Promise<JudgeTransportEvaluateResult>;
|
|
40
|
+
export declare function buildTransportInvocation(providerId: Exclude<JudgeProviderId, 'ollama'>, prompt: string, model: string): CliInvocation;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { hashJudgeSessionRef } from './judge-audit.js';
|
|
2
|
+
import { recordJudgeLatency } from './judge-baseline.js';
|
|
3
|
+
import { evaluateViaJudgeBroker, invalidateJudgeSession } from './judge-broker-service.js';
|
|
4
|
+
import { buildCliInvocation, CliRunError, parseCliJudgeOutput, runCliJsonWithTimeouts, } from './judge-cli.js';
|
|
5
|
+
import { resolveCliVersionFingerprint } from './judge-cli-fingerprint.js';
|
|
6
|
+
import { assertReadOnlyInvocationArgs, providerSupportsSession } from './judge-provider-matrix.js';
|
|
7
|
+
import { resolveSessionEvalTimeoutMs } from './judge-runtime-config.js';
|
|
8
|
+
import { buildJudgeSessionKey, exceedsPromptBudget, guardFailClosedFallbackReason, } from './judge-session-guard.js';
|
|
9
|
+
import { isJudgeSessionKillSwitchActive, recordShadowComparison, shouldRunShadowComparison, triggerJudgeSessionKillSwitch, verdictsEquivalent, } from './judge-shadow.js';
|
|
10
|
+
function isEvalTimeoutError(error) {
|
|
11
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12
|
+
return /timed out/i.test(message);
|
|
13
|
+
}
|
|
14
|
+
function classifyParseFailure(providerId, raw) {
|
|
15
|
+
const trimmed = raw.trim();
|
|
16
|
+
if (!trimmed) {
|
|
17
|
+
return 'non_json_response';
|
|
18
|
+
}
|
|
19
|
+
if (parseCliJudgeOutput(providerId, trimmed)) {
|
|
20
|
+
return 'parse_error';
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
JSON.parse(trimmed);
|
|
24
|
+
return 'parse_error';
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return 'non_json_response';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function spawnFallbackReason(providerId, error) {
|
|
31
|
+
if (error instanceof CliRunError) {
|
|
32
|
+
if (error.kind === 'timeout') {
|
|
33
|
+
return 'eval_timeout';
|
|
34
|
+
}
|
|
35
|
+
if (error.kind === 'exit_nonzero') {
|
|
36
|
+
return `${providerId}_cli_nonzero`;
|
|
37
|
+
}
|
|
38
|
+
if (error.kind === 'spawn_error') {
|
|
39
|
+
return `${providerId}_cli_spawn_error`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (isEvalTimeoutError(error)) {
|
|
43
|
+
return 'eval_timeout';
|
|
44
|
+
}
|
|
45
|
+
return `${providerId}_cli_unavailable`;
|
|
46
|
+
}
|
|
47
|
+
function parseWithBudget(providerId, raw) {
|
|
48
|
+
const started = Date.now();
|
|
49
|
+
const verdict = parseCliJudgeOutput(providerId, raw);
|
|
50
|
+
const parseMs = Date.now() - started;
|
|
51
|
+
recordJudgeLatency('tier1_parse', parseMs);
|
|
52
|
+
return { verdict, parseMs };
|
|
53
|
+
}
|
|
54
|
+
async function runSpawnTransport(request, runCommand) {
|
|
55
|
+
const { context, prompt } = request;
|
|
56
|
+
const sessionConfig = context.runtime.session;
|
|
57
|
+
const invocation = buildCliInvocation(context.providerId, prompt, context.model);
|
|
58
|
+
const readOnly = assertReadOnlyInvocationArgs(context.providerId, invocation.args);
|
|
59
|
+
if (!readOnly.ok) {
|
|
60
|
+
return {
|
|
61
|
+
raw: '',
|
|
62
|
+
verdict: null,
|
|
63
|
+
transport: 'spawn',
|
|
64
|
+
sessionUsed: false,
|
|
65
|
+
sessionReused: false,
|
|
66
|
+
fallbackReason: 'unsafe_option_rejected',
|
|
67
|
+
connectMs: 0,
|
|
68
|
+
evalMs: 0,
|
|
69
|
+
parseMs: 0,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const evalTimeoutMs = resolveSessionEvalTimeoutMs(sessionConfig, context.judgeTimeoutMs);
|
|
73
|
+
let raw = '';
|
|
74
|
+
let evalMs = 0;
|
|
75
|
+
const evalStarted = Date.now();
|
|
76
|
+
try {
|
|
77
|
+
raw = await runCommand(invocation, evalTimeoutMs);
|
|
78
|
+
evalMs = Date.now() - evalStarted;
|
|
79
|
+
recordJudgeLatency('tier1_spawn', evalMs);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
evalMs = Date.now() - evalStarted;
|
|
83
|
+
return {
|
|
84
|
+
raw: '',
|
|
85
|
+
verdict: null,
|
|
86
|
+
transport: 'spawn',
|
|
87
|
+
sessionUsed: false,
|
|
88
|
+
sessionReused: false,
|
|
89
|
+
fallbackReason: spawnFallbackReason(context.providerId, error),
|
|
90
|
+
connectMs: 0,
|
|
91
|
+
evalMs,
|
|
92
|
+
parseMs: 0,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const parsed = parseWithBudget(context.providerId, raw);
|
|
96
|
+
return {
|
|
97
|
+
raw,
|
|
98
|
+
verdict: parsed.verdict,
|
|
99
|
+
transport: 'spawn',
|
|
100
|
+
sessionUsed: false,
|
|
101
|
+
sessionReused: false,
|
|
102
|
+
fallbackReason: parsed.verdict ? undefined : classifyParseFailure(context.providerId, raw),
|
|
103
|
+
connectMs: 0,
|
|
104
|
+
evalMs,
|
|
105
|
+
parseMs: parsed.parseMs,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function runSessionTransport(request, runCommand) {
|
|
109
|
+
const { context, prompt } = request;
|
|
110
|
+
const sessionConfig = context.runtime.session;
|
|
111
|
+
const connectStarted = Date.now();
|
|
112
|
+
if (!sessionConfig.enabled ||
|
|
113
|
+
!providerSupportsSession(context.providerId, sessionConfig.providerAllowlist) ||
|
|
114
|
+
(await isJudgeSessionKillSwitchActive(context.repoRoot, context.stateDir))) {
|
|
115
|
+
return runSpawnTransport(request, runCommand);
|
|
116
|
+
}
|
|
117
|
+
let cliVersion = 'unknown';
|
|
118
|
+
try {
|
|
119
|
+
cliVersion = await resolveCliVersionFingerprint(context.providerId, sessionConfig.connectTimeoutMs);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return {
|
|
123
|
+
...(await runSpawnTransport(request, runCommand)),
|
|
124
|
+
fallbackReason: 'connect_timeout',
|
|
125
|
+
sessionUsed: false,
|
|
126
|
+
sessionReused: false,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const connectMs = Date.now() - connectStarted;
|
|
130
|
+
recordJudgeLatency('tier1_connect', connectMs);
|
|
131
|
+
const promptBytes = Buffer.byteLength(prompt, 'utf8');
|
|
132
|
+
if (exceedsPromptBudget(promptBytes, sessionConfig)) {
|
|
133
|
+
const spawnResult = await runSpawnTransport(request, runCommand);
|
|
134
|
+
return {
|
|
135
|
+
...spawnResult,
|
|
136
|
+
sessionUsed: false,
|
|
137
|
+
sessionReused: false,
|
|
138
|
+
sessionResetReason: 'max_prompt_bytes_exceeded',
|
|
139
|
+
fallbackReason: 'guard_reset',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const keyParts = {
|
|
143
|
+
providerId: context.providerId,
|
|
144
|
+
model: context.model,
|
|
145
|
+
repoRoot: context.repoRoot,
|
|
146
|
+
judgeMode: context.judgeMode,
|
|
147
|
+
cliVersion,
|
|
148
|
+
};
|
|
149
|
+
const sessionKey = buildJudgeSessionKey(keyParts);
|
|
150
|
+
const sessionRefHash = hashJudgeSessionRef(sessionKey);
|
|
151
|
+
const invocation = buildCliInvocation(context.providerId, prompt, context.model);
|
|
152
|
+
const readOnly = assertReadOnlyInvocationArgs(context.providerId, invocation.args);
|
|
153
|
+
if (!readOnly.ok) {
|
|
154
|
+
return runSpawnTransport(request, runCommand);
|
|
155
|
+
}
|
|
156
|
+
const evalTimeoutMs = resolveSessionEvalTimeoutMs(sessionConfig, context.judgeTimeoutMs);
|
|
157
|
+
const evalStarted = Date.now();
|
|
158
|
+
let brokerResult;
|
|
159
|
+
try {
|
|
160
|
+
brokerResult = await evaluateViaJudgeBroker({
|
|
161
|
+
stateDir: context.stateDir,
|
|
162
|
+
repoRoot: context.repoRoot,
|
|
163
|
+
sessionConfig,
|
|
164
|
+
request: {
|
|
165
|
+
keyParts,
|
|
166
|
+
invocation,
|
|
167
|
+
promptBytes,
|
|
168
|
+
},
|
|
169
|
+
timeoutMs: evalTimeoutMs,
|
|
170
|
+
runCommand,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
const spawnResult = await runSpawnTransport(request, runCommand);
|
|
175
|
+
return {
|
|
176
|
+
...spawnResult,
|
|
177
|
+
transport: 'session',
|
|
178
|
+
sessionUsed: true,
|
|
179
|
+
sessionReused: false,
|
|
180
|
+
fallbackReason: spawnFallbackReason(context.providerId, error),
|
|
181
|
+
sessionResetReason: 'cli_error',
|
|
182
|
+
sessionRefHash,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const evalMs = Date.now() - evalStarted;
|
|
186
|
+
recordJudgeLatency('tier1_session', evalMs);
|
|
187
|
+
recordJudgeLatency('tier1_eval', evalMs);
|
|
188
|
+
const parsed = parseWithBudget(context.providerId, brokerResult.raw);
|
|
189
|
+
if (!parsed.verdict) {
|
|
190
|
+
const parseReason = classifyParseFailure(context.providerId, brokerResult.raw);
|
|
191
|
+
await invalidateJudgeSession({
|
|
192
|
+
stateDir: context.stateDir,
|
|
193
|
+
repoRoot: context.repoRoot,
|
|
194
|
+
sessionConfig,
|
|
195
|
+
sessionKey,
|
|
196
|
+
reason: parseReason === 'non_json_response' ? 'non_json_response' : 'parse_failure',
|
|
197
|
+
});
|
|
198
|
+
const spawnResult = await runSpawnTransport(request, runCommand);
|
|
199
|
+
return {
|
|
200
|
+
...spawnResult,
|
|
201
|
+
transport: 'session',
|
|
202
|
+
sessionUsed: true,
|
|
203
|
+
sessionReused: brokerResult.reused,
|
|
204
|
+
fallbackReason: guardFailClosedFallbackReason(parseReason === 'non_json_response' ? 'non_json_response' : 'parse_failure'),
|
|
205
|
+
sessionResetReason: parseReason === 'non_json_response' ? 'non_json_response' : 'parse_failure',
|
|
206
|
+
sessionRefHash,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
raw: brokerResult.raw,
|
|
211
|
+
verdict: parsed.verdict,
|
|
212
|
+
transport: 'session',
|
|
213
|
+
sessionUsed: true,
|
|
214
|
+
sessionReused: brokerResult.reused,
|
|
215
|
+
sessionResetReason: brokerResult.resetReason,
|
|
216
|
+
sessionRefHash,
|
|
217
|
+
connectMs,
|
|
218
|
+
evalMs,
|
|
219
|
+
parseMs: parsed.parseMs,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
export async function evaluateWithJudgeTransport(request, options = {}) {
|
|
223
|
+
const runCommand = options.runCommand ??
|
|
224
|
+
((invocation, timeoutMs) => runCliJsonWithTimeouts(invocation, { evalTimeoutMs: timeoutMs }));
|
|
225
|
+
const { context } = request;
|
|
226
|
+
const useSession = context.runtime.session.enabled &&
|
|
227
|
+
providerSupportsSession(context.providerId, context.runtime.session.providerAllowlist) &&
|
|
228
|
+
!(await isJudgeSessionKillSwitchActive(context.repoRoot, context.stateDir));
|
|
229
|
+
const primary = useSession
|
|
230
|
+
? await runSessionTransport(request, runCommand)
|
|
231
|
+
: await runSpawnTransport(request, runCommand);
|
|
232
|
+
if (!shouldRunShadowComparison(context.repoRoot, context.providerId, context.runtime.shadow)) {
|
|
233
|
+
return primary;
|
|
234
|
+
}
|
|
235
|
+
const shadowSpawn = primary.transport === 'session' ? await runSpawnTransport(request, runCommand) : primary;
|
|
236
|
+
if (!primary.verdict || !shadowSpawn.verdict) {
|
|
237
|
+
return primary;
|
|
238
|
+
}
|
|
239
|
+
const shadow = recordShadowComparison(context.repoRoot, context.runtime.shadow, !verdictsEquivalent(primary.verdict, shadowSpawn.verdict));
|
|
240
|
+
if (shadow.killSwitchTriggered) {
|
|
241
|
+
await triggerJudgeSessionKillSwitch(context.repoRoot, context.stateDir);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
...primary,
|
|
245
|
+
shadowCompared: shadow.compared,
|
|
246
|
+
shadowMismatch: shadow.mismatch,
|
|
247
|
+
shadowMismatchRateWindow: shadow.mismatchRateWindow,
|
|
248
|
+
killSwitchTriggered: shadow.killSwitchTriggered,
|
|
249
|
+
verdict: shadow.mismatch ? shadowSpawn.verdict : primary.verdict,
|
|
250
|
+
fallbackReason: shadow.mismatch ? 'shadow_forced_spawn' : primary.fallbackReason,
|
|
251
|
+
transport: shadow.mismatch ? 'spawn' : primary.transport,
|
|
252
|
+
sessionUsed: shadow.mismatch ? false : primary.sessionUsed,
|
|
253
|
+
sessionReused: shadow.mismatch ? false : primary.sessionReused,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
export function buildTransportInvocation(providerId, prompt, model) {
|
|
257
|
+
return buildCliInvocation(providerId, prompt, model);
|
|
258
|
+
}
|
|
@@ -1,17 +1,45 @@
|
|
|
1
1
|
import type { ScrubOptions } from '../types.js';
|
|
2
2
|
import type { Tier1Judge, Tier1Verdict } from './types.js';
|
|
3
|
+
export type Tier1JudgeTransport = 'http' | 'ollama-http' | 'codex-cli' | 'cursor-cli' | 'claude-cli' | 'codex-cli-session' | 'cursor-cli-session' | 'claude-cli-session' | 'unavailable';
|
|
3
4
|
export interface Tier1JudgeTrace {
|
|
4
|
-
provider: 'openai-compatible' | 'ollama' | 'fallback';
|
|
5
|
+
provider: 'openai-compatible' | 'ollama' | 'anthropic' | 'fallback';
|
|
5
6
|
modelRequested: string;
|
|
6
7
|
modelResolved: string;
|
|
7
8
|
latencyMs: number;
|
|
9
|
+
transport?: Tier1JudgeTransport;
|
|
8
10
|
outboundRedacted?: boolean;
|
|
9
11
|
fallbackReason?: string;
|
|
12
|
+
judgeSessionUsed?: boolean;
|
|
13
|
+
judgeSessionReused?: boolean;
|
|
14
|
+
judgeSessionRefHash?: string;
|
|
15
|
+
judgeSessionResetReason?: string;
|
|
16
|
+
judgeFallbackReason?: string;
|
|
17
|
+
judgeConnectMs?: number;
|
|
18
|
+
judgeEvalMs?: number;
|
|
19
|
+
judgeParseMs?: number;
|
|
20
|
+
judgeShadowCompared?: boolean;
|
|
21
|
+
judgeShadowMismatch?: boolean;
|
|
22
|
+
judgeShadowMismatchRateWindow?: number;
|
|
23
|
+
judgeKillSwitchTriggered?: boolean;
|
|
10
24
|
}
|
|
11
25
|
export interface TracedTier1Judge extends Tier1Judge {
|
|
12
26
|
lastTrace?: Tier1JudgeTrace;
|
|
13
27
|
}
|
|
28
|
+
export declare function buildTier1Prompt(text: string): string;
|
|
29
|
+
export declare function parseTier1Json(raw: string): Tier1Verdict | null;
|
|
14
30
|
export declare function prescanInterpreterCode(code: string): Tier1Verdict | null;
|
|
31
|
+
export interface MutationPrescanParams {
|
|
32
|
+
targets: string[];
|
|
33
|
+
cwd: string;
|
|
34
|
+
repoRoot: string;
|
|
35
|
+
trustedCwd: boolean;
|
|
36
|
+
trustedWorkspaceRoots?: string[];
|
|
37
|
+
sensitivePaths: string[];
|
|
38
|
+
}
|
|
39
|
+
/** ADR-002 M3: structural prescan for sensitive / persistent mutation targets (shell redirects, etc.). */
|
|
40
|
+
export declare function prescanMutationTargets(params: MutationPrescanParams): Tier1Verdict | null;
|
|
41
|
+
/** Returns prescan verdict when structural M3 rules require ask (before Tier1 LLM). */
|
|
42
|
+
export declare function mutationPrescanRequiresAsk(params: MutationPrescanParams): Tier1Verdict | null;
|
|
15
43
|
/** Conservative stub: Tier1 defers to Tier0; returns safe negatives for structural suite. */
|
|
16
44
|
export declare function createDeterministicJudgeStub(): TracedTier1Judge;
|
|
17
45
|
/** Fail-closed judge for when Tier1 is required but unavailable. */
|
|
@@ -35,6 +63,10 @@ export interface OpenAiCompatibleJudgeOptions {
|
|
|
35
63
|
modelResolved: string;
|
|
36
64
|
timeoutMs: number;
|
|
37
65
|
apiKey?: string;
|
|
66
|
+
resolveApiKey?: () => Promise<{
|
|
67
|
+
key: string | null;
|
|
68
|
+
source: string | null;
|
|
69
|
+
}>;
|
|
38
70
|
sensitivePaths: string[];
|
|
39
71
|
scrubOptions: ScrubOptions;
|
|
40
72
|
fetchImpl?: typeof fetch;
|
|
@@ -1,34 +1,60 @@
|
|
|
1
|
+
import { matchesSensitivePath } from '../glob.js';
|
|
2
|
+
import { resolveMutationTarget, resolveWorkspaceRootMatch } from '../path-utils.js';
|
|
3
|
+
import { resolveTrustedPath } from './containment.js';
|
|
1
4
|
import { scrubOutboundForJudge } from './judge-outbound.js';
|
|
5
|
+
import { isOutsideRepoSecretCredentialPath, isPersistentAgentPath } from './persistent-paths.js';
|
|
2
6
|
const SECRET_PATTERNS = [/\.env\b/i, /\.pem\b/i, /id_rsa\b/i, /credentials/i, /secrets?\b/i];
|
|
3
7
|
const DESTRUCTIVE_VERBS = /\b(rm|rmtree|unlink|delete|truncate|shred|destroy|drop)\b/i;
|
|
4
8
|
const GIT_PATTERNS = /\.git\b/i;
|
|
5
|
-
const TIER1_PROMPT = `Answer ONLY with JSON: {"
|
|
9
|
+
const TIER1_PROMPT = `Answer ONLY with JSON: {"local_recoverable":boolean,"destroys_history_or_secrets":boolean,"reason":string}
|
|
6
10
|
reason should be a short snake_case label.
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
local_recoverable: true if a mistaken operation can be undone locally (git revert, fs snapshot, trivial manual revert).
|
|
13
|
+
local_recoverable: false if remote/destructive (git push --force, aws s3 rm, secret send) OR persistent local harm (~/.ssh/authorized_keys, shell rc, crontab, ~/.config persistence).
|
|
14
|
+
|
|
15
|
+
destroys_history_or_secrets: true if the operation deletes or corrupts git history (.git), overwrites secrets/credentials, or modifies persistent agent startup (shell rc, crontab, launchd, authorized_keys).
|
|
16
|
+
|
|
17
|
+
Examples local_recoverable=true, destroys_history_or_secrets=false: IDE plan files under the user home, /tmp files, in-repo edits, READ/QUERY/GET.
|
|
18
|
+
Examples local_recoverable=false OR destroys_history_or_secrets=true: remote mutations, credential paths, agent startup persistence, rm -rf .git.
|
|
19
|
+
If genuinely ambiguous about recoverability or persistent harm, answer false.
|
|
11
20
|
|
|
12
21
|
Command/code:
|
|
13
22
|
`;
|
|
14
23
|
function failClosedVerdict(reason) {
|
|
15
24
|
return {
|
|
16
|
-
|
|
25
|
+
local_recoverable: false,
|
|
17
26
|
destroys_outside_repo: true,
|
|
18
27
|
destroys_history_or_secrets: true,
|
|
28
|
+
external_change: true,
|
|
19
29
|
reason,
|
|
20
30
|
};
|
|
21
31
|
}
|
|
22
|
-
function
|
|
32
|
+
function parseLocalRecoverable(parsed) {
|
|
33
|
+
if (typeof parsed.local_recoverable === 'boolean') {
|
|
34
|
+
return parsed.local_recoverable;
|
|
35
|
+
}
|
|
36
|
+
if (typeof parsed.external_change === 'boolean') {
|
|
37
|
+
return !parsed.external_change;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
export function buildTier1Prompt(text) {
|
|
42
|
+
return `${TIER1_PROMPT}${text}`;
|
|
43
|
+
}
|
|
44
|
+
export function parseTier1Json(raw) {
|
|
23
45
|
try {
|
|
24
46
|
const parsed = JSON.parse(raw);
|
|
25
|
-
|
|
47
|
+
const localRecoverable = parseLocalRecoverable(parsed);
|
|
48
|
+
if (localRecoverable === null) {
|
|
26
49
|
return null;
|
|
27
50
|
}
|
|
28
51
|
return {
|
|
52
|
+
local_recoverable: localRecoverable,
|
|
53
|
+
destroys_outside_repo: typeof parsed.destroys_outside_repo === 'boolean' ? parsed.destroys_outside_repo : false,
|
|
54
|
+
destroys_history_or_secrets: typeof parsed.destroys_history_or_secrets === 'boolean'
|
|
55
|
+
? parsed.destroys_history_or_secrets
|
|
56
|
+
: false,
|
|
29
57
|
external_change: parsed.external_change,
|
|
30
|
-
destroys_outside_repo: false,
|
|
31
|
-
destroys_history_or_secrets: false,
|
|
32
58
|
reason: typeof parsed.reason === 'string' ? parsed.reason : 'tier1_llm',
|
|
33
59
|
};
|
|
34
60
|
}
|
|
@@ -43,7 +69,7 @@ export function prescanInterpreterCode(code) {
|
|
|
43
69
|
const hitsDestructive = DESTRUCTIVE_VERBS.test(normalized);
|
|
44
70
|
if ((hitsSecret || hitsGit) && hitsDestructive) {
|
|
45
71
|
return {
|
|
46
|
-
|
|
72
|
+
local_recoverable: true,
|
|
47
73
|
destroys_outside_repo: false,
|
|
48
74
|
destroys_history_or_secrets: true,
|
|
49
75
|
reason: 'prescan_destructive_secret',
|
|
@@ -51,12 +77,55 @@ export function prescanInterpreterCode(code) {
|
|
|
51
77
|
}
|
|
52
78
|
return null;
|
|
53
79
|
}
|
|
80
|
+
/** ADR-002 M3: structural prescan for sensitive / persistent mutation targets (shell redirects, etc.). */
|
|
81
|
+
export function prescanMutationTargets(params) {
|
|
82
|
+
for (const target of params.targets) {
|
|
83
|
+
const resolved = resolveTrustedPath(target, params.cwd, params.trustedCwd) ??
|
|
84
|
+
resolveMutationTarget(target, params.cwd);
|
|
85
|
+
if (!resolved) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const workspaceMatch = resolveWorkspaceRootMatch(params.repoRoot, params.trustedWorkspaceRoots, resolved);
|
|
89
|
+
if (workspaceMatch !== null &&
|
|
90
|
+
matchesSensitivePath(workspaceMatch.relativePath.replaceAll('\\', '/'), params.sensitivePaths)) {
|
|
91
|
+
return {
|
|
92
|
+
local_recoverable: false,
|
|
93
|
+
destroys_outside_repo: false,
|
|
94
|
+
destroys_history_or_secrets: true,
|
|
95
|
+
reason: 'sensitive_path_mutation',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if ((workspaceMatch === null || workspaceMatch.kind === 'trusted') &&
|
|
99
|
+
isOutsideRepoSecretCredentialPath(resolved)) {
|
|
100
|
+
return {
|
|
101
|
+
local_recoverable: false,
|
|
102
|
+
destroys_outside_repo: false,
|
|
103
|
+
destroys_history_or_secrets: true,
|
|
104
|
+
reason: 'outside_repo_secret_credential_path',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (isPersistentAgentPath(resolved)) {
|
|
108
|
+
return {
|
|
109
|
+
local_recoverable: false,
|
|
110
|
+
destroys_outside_repo: false,
|
|
111
|
+
destroys_history_or_secrets: true,
|
|
112
|
+
reason: 'persistent_agent_path',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
/** Returns prescan verdict when structural M3 rules require ask (before Tier1 LLM). */
|
|
119
|
+
export function mutationPrescanRequiresAsk(params) {
|
|
120
|
+
const prescan = prescanMutationTargets(params);
|
|
121
|
+
return prescan && tier1RequiresAsk(prescan) ? prescan : null;
|
|
122
|
+
}
|
|
54
123
|
/** Conservative stub: Tier1 defers to Tier0; returns safe negatives for structural suite. */
|
|
55
124
|
export function createDeterministicJudgeStub() {
|
|
56
125
|
return {
|
|
57
126
|
evaluate() {
|
|
58
127
|
return Promise.resolve({
|
|
59
|
-
|
|
128
|
+
local_recoverable: true,
|
|
60
129
|
destroys_outside_repo: false,
|
|
61
130
|
destroys_history_or_secrets: false,
|
|
62
131
|
reason: 'deterministic_stub',
|
|
@@ -190,8 +259,9 @@ export function createOpenAiCompatibleJudge(options) {
|
|
|
190
259
|
};
|
|
191
260
|
return failClosedVerdict('outbound_scrub_failed');
|
|
192
261
|
}
|
|
193
|
-
const
|
|
194
|
-
|
|
262
|
+
const resolvedKey = options.resolveApiKey
|
|
263
|
+
? await options.resolveApiKey()
|
|
264
|
+
: { key: options.apiKey ?? null, source: null };
|
|
195
265
|
const apiKey = options.apiKey ?? resolvedKey.key;
|
|
196
266
|
if (!apiKey) {
|
|
197
267
|
judge.lastTrace = {
|
|
@@ -258,5 +328,5 @@ export function createOpenAiCompatibleJudge(options) {
|
|
|
258
328
|
/** @deprecated Use createOpenAiCompatibleJudge */
|
|
259
329
|
export const createCursorJudge = createOpenAiCompatibleJudge;
|
|
260
330
|
export function tier1RequiresAsk(verdict) {
|
|
261
|
-
return verdict.
|
|
331
|
+
return !verdict.local_recoverable || verdict.destroys_history_or_secrets;
|
|
262
332
|
}
|
|
@@ -160,7 +160,12 @@ export function splitTopLevelSegments(command) {
|
|
|
160
160
|
}
|
|
161
161
|
};
|
|
162
162
|
for (const token of tokens) {
|
|
163
|
-
if (token === '&&' ||
|
|
163
|
+
if (token === '&&' ||
|
|
164
|
+
token === '||' ||
|
|
165
|
+
token === ';' ||
|
|
166
|
+
token === '|' ||
|
|
167
|
+
token === '&' ||
|
|
168
|
+
token === '|&') {
|
|
164
169
|
flush();
|
|
165
170
|
continue;
|
|
166
171
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M3 catalog: paths that survive reboot or change agent startup.
|
|
3
|
+
* @see docs/adr/tier0-retention-ledger.md
|
|
4
|
+
*/
|
|
5
|
+
export declare const PERSISTENT_AGENT_PATH_MARKERS: readonly [".ssh/authorized_keys", ".zshrc", ".bashrc", ".bash_profile", ".profile", ".config/", "/crontab", "LaunchAgents/", "/Library/LaunchDaemons/"];
|
|
6
|
+
export declare const OUTSIDE_REPO_SECRET_CREDENTIAL_MARKERS: readonly [".env", ".env.*", "*.pem", ".ssh/id_*", ".aws/", ".npmrc", ".git-credentials", ".netrc", ".kube/config", ".docker/config.json", ".gnupg/", ".pypirc"];
|
|
7
|
+
export declare function isPersistentAgentPath(resolvedPath: string): boolean;
|
|
8
|
+
export declare function isOutsideRepoSecretCredentialPath(resolvedPath: string): boolean;
|