@guilz-dev/belay 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/adapters/layouts/scope.js +3 -2
- package/dist/adapters/shared/gate-runtime.d.ts +1 -2
- package/dist/adapters/shared/gate-runtime.js +226 -28
- package/dist/bundle/claude-runtime.mjs +6967 -4078
- package/dist/bundle/codex-runtime.mjs +5951 -3064
- package/dist/bundle/cursor-runtime.mjs +5951 -3064
- package/dist/cli.js +43 -1
- package/dist/commands/classify-for-report.js +1 -1
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +134 -11
- package/dist/commands/doctor.js +36 -1
- package/dist/commands/explain.js +11 -1
- package/dist/commands/judge.js +3 -2
- package/dist/commands/session.d.ts +20 -0
- package/dist/commands/session.js +51 -0
- package/dist/config-io.js +49 -12
- package/dist/conformance/guarantee-posture.d.ts +21 -0
- package/dist/conformance/guarantee-posture.js +46 -0
- package/dist/conformance/guarantee-table.d.ts +1 -0
- package/dist/conformance/guarantee-table.js +14 -9
- package/dist/core/approval-service.js +6 -2
- package/dist/core/approval.d.ts +2 -0
- package/dist/core/approval.js +14 -3
- package/dist/core/capability/approval-state-mutation.d.ts +13 -0
- package/dist/core/capability/approval-state-mutation.js +92 -0
- package/dist/core/capability/approval-v3.d.ts +15 -0
- package/dist/core/capability/approval-v3.js +74 -0
- package/dist/core/capability/attestation.d.ts +13 -0
- package/dist/core/capability/attestation.js +55 -0
- package/dist/core/capability/boundary-attestation-sign.d.ts +21 -0
- package/dist/core/capability/boundary-attestation-sign.js +84 -0
- package/dist/core/capability/boundary-driver-container.d.ts +7 -0
- package/dist/core/capability/boundary-driver-container.js +122 -0
- package/dist/core/capability/boundary-driver.d.ts +29 -0
- package/dist/core/capability/boundary-driver.js +43 -0
- package/dist/core/capability/boundary-egress.d.ts +21 -0
- package/dist/core/capability/boundary-egress.js +109 -0
- package/dist/core/capability/boundary-grant-materialize.d.ts +13 -0
- package/dist/core/capability/boundary-grant-materialize.js +78 -0
- package/dist/core/capability/boundary-profile.d.ts +15 -0
- package/dist/core/capability/boundary-profile.js +25 -0
- package/dist/core/capability/boundary-run.d.ts +23 -0
- package/dist/core/capability/boundary-run.js +23 -0
- package/dist/core/capability/boundary-session.d.ts +57 -0
- package/dist/core/capability/boundary-session.js +129 -0
- package/dist/core/capability/capability-request-hash.d.ts +2 -0
- package/dist/core/capability/capability-request-hash.js +8 -0
- package/dist/core/capability/gate-latency-ratchet.d.ts +7 -0
- package/dist/core/capability/gate-latency-ratchet.js +18 -0
- package/dist/core/capability/gate-policy-shadow.d.ts +25 -0
- package/dist/core/capability/gate-policy-shadow.js +108 -0
- package/dist/core/capability/gate-shadow-audit.d.ts +1 -0
- package/dist/core/capability/gate-shadow-audit.js +1 -0
- package/dist/core/capability/gate-shadow-ratchet.d.ts +14 -0
- package/dist/core/capability/gate-shadow-ratchet.js +81 -0
- package/dist/core/capability/grant-lease.d.ts +9 -0
- package/dist/core/capability/grant-lease.js +46 -0
- package/dist/core/capability/grant-loader.d.ts +12 -0
- package/dist/core/capability/grant-loader.js +23 -0
- package/dist/core/capability/grant-match.d.ts +6 -0
- package/dist/core/capability/grant-match.js +93 -0
- package/dist/core/capability/grant.d.ts +16 -0
- package/dist/core/capability/grant.js +16 -0
- package/dist/core/capability/index.d.ts +8 -0
- package/dist/core/capability/index.js +7 -0
- package/dist/core/capability/limits.d.ts +7 -0
- package/dist/core/capability/limits.js +55 -0
- package/dist/core/capability/policy-bridge.d.ts +18 -0
- package/dist/core/capability/policy-bridge.js +51 -0
- package/dist/core/capability/policy-engine.d.ts +70 -0
- package/dist/core/capability/policy-engine.js +476 -0
- package/dist/core/capability/policy-types.d.ts +20 -0
- package/dist/core/capability/policy-types.js +1 -0
- package/dist/core/capability/request.d.ts +45 -0
- package/dist/core/capability/request.js +1 -0
- package/dist/core/capability/resolver.d.ts +4 -0
- package/dist/core/capability/resolver.js +4 -0
- package/dist/core/classify-subagent.d.ts +2 -1
- package/dist/core/classify-subagent.js +38 -2
- package/dist/core/classify-tool.js +126 -62
- package/dist/core/config.d.ts +14 -3
- package/dist/core/config.js +56 -3
- package/dist/core/gate-contract.d.ts +3 -0
- package/dist/core/gate-contract.js +3 -0
- package/dist/core/gate-engine.js +6 -1
- package/dist/core/judge-doctor.js +39 -14
- package/dist/core/judge-fallback-hints.d.ts +13 -0
- package/dist/core/judge-fallback-hints.js +108 -0
- package/dist/core/path-utils.d.ts +1 -0
- package/dist/core/path-utils.js +5 -2
- package/dist/core/recovery/capability.d.ts +3 -0
- package/dist/core/recovery/capability.js +22 -0
- package/dist/core/recovery/fail-closed.d.ts +6 -0
- package/dist/core/recovery/fail-closed.js +25 -0
- package/dist/core/recovery/index.d.ts +3 -0
- package/dist/core/recovery/index.js +2 -0
- package/dist/core/recovery/types.d.ts +20 -0
- package/dist/core/recovery/types.js +1 -0
- package/dist/core/standing-allow.js +3 -0
- package/dist/core/transactional/diff-evaluator.js +4 -1
- package/dist/core/transactional/eligibility.js +4 -0
- package/dist/core/transactional/git-worktree.d.ts +10 -3
- package/dist/core/transactional/git-worktree.js +81 -11
- package/dist/core/transactional/reasons.d.ts +1 -0
- package/dist/core/transactional/reasons.js +4 -0
- package/dist/core/transactional/runner.js +22 -6
- package/dist/core/transactional/types.d.ts +4 -0
- package/dist/core/types.d.ts +21 -3
- package/dist/core/verdict/adapter.d.ts +3 -4
- package/dist/core/verdict/adapter.js +16 -12
- package/dist/core/verdict/cursor-acp-client.d.ts +67 -0
- package/dist/core/verdict/cursor-acp-client.js +345 -0
- package/dist/core/verdict/egress-classify.d.ts +2 -2
- package/dist/core/verdict/egress-classify.js +2 -2
- package/dist/core/verdict/git-classifier.d.ts +26 -0
- package/dist/core/verdict/git-classifier.js +533 -0
- package/dist/core/verdict/judge-audit.js +1 -0
- package/dist/core/verdict/judge-baseline.d.ts +1 -1
- package/dist/core/verdict/judge-baseline.js +1 -0
- package/dist/core/verdict/judge-broker-service.js +12 -0
- package/dist/core/verdict/judge-catalog.js +1 -0
- package/dist/core/verdict/judge-cli-fingerprint.d.ts +1 -0
- package/dist/core/verdict/judge-cli-fingerprint.js +6 -0
- package/dist/core/verdict/judge-cli.js +5 -3
- package/dist/core/verdict/judge-provider-matrix.js +1 -1
- package/dist/core/verdict/judge-runtime-config.d.ts +2 -0
- package/dist/core/verdict/judge-runtime-config.js +10 -6
- package/dist/core/verdict/judge-session-broker.d.ts +1 -1
- package/dist/core/verdict/judge-session-broker.js +10 -5
- package/dist/core/verdict/judge-transport.d.ts +5 -1
- package/dist/core/verdict/judge-transport.js +24 -14
- package/dist/core/verdict/judge.d.ts +2 -15
- package/dist/core/verdict/judge.js +5 -68
- package/dist/core/verdict/launcher-resolve.d.ts +2 -0
- package/dist/core/verdict/launcher-resolve.js +16 -0
- package/dist/core/verdict/prescan.d.ts +15 -0
- package/dist/core/verdict/prescan.js +68 -0
- package/dist/core/verdict/shell-policy.d.ts +25 -0
- package/dist/core/verdict/shell-policy.js +40 -0
- package/dist/core/verdict/shell-semantics.d.ts +24 -0
- package/dist/core/verdict/shell-semantics.js +225 -0
- package/dist/core/verdict/types.d.ts +13 -1
- package/dist/core/verdict/verdict.js +296 -178
- package/dist/corpus/evaluate.js +3 -7
- package/dist/corpus/gate-latency-budget.d.ts +39 -0
- package/dist/corpus/gate-latency-budget.js +33 -0
- package/dist/corpus/mutators.d.ts +1 -1
- package/dist/corpus/mutators.js +11 -0
- package/dist/corpus/runtime-match.js +1 -3
- package/dist/corpus/standing-allow-catalog.generated.js +29 -9
- package/dist/judge-broker-daemon.js +15 -1
- package/dist/services/sandbox-service.d.ts +4 -0
- package/dist/services/sandbox-service.js +28 -2
- package/dist/types.d.ts +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -2,20 +2,30 @@ import { repoLocalStateDirFor } from '../config-io.js';
|
|
|
2
2
|
import { normalizeJudgeProvider, scrubOptionsFromConfig } from './config.js';
|
|
3
3
|
import { resolveJudgeCredential } from './judge-api-key.js';
|
|
4
4
|
import { hasValidCloudConsent } from './judge-config.js';
|
|
5
|
+
import { formatJudgeRecoveryHint, inferProviderIdFromFallbackReason, } from './judge-fallback-hints.js';
|
|
5
6
|
import { discoverJudgeModels, modelPresenceFromDiscovery, } from './judge-model-discovery.js';
|
|
6
7
|
import { detectJudgeRuntimeCapabilities, resolveJudgeTransport } from './judge-runtime-detection.js';
|
|
7
8
|
import { createOllamaJudge, createOpenAiCompatibleJudge } from './verdict/judge.js';
|
|
8
9
|
import { stopJudgeBrokerDaemon } from './verdict/judge-broker-service.js';
|
|
9
10
|
import { getJudgeProviderCapabilities, getJudgeProviderSpec, isRemovedProviderId, normalizeLegacyProviderId, } from './verdict/judge-catalog.js';
|
|
10
11
|
import { createJudgeFromConfig, resolveJudgeModel } from './verdict/judge-factory.js';
|
|
12
|
+
import { resolveJudgeSmokeProbeTimeoutMs } from './verdict/judge-runtime-config.js';
|
|
11
13
|
import { listRepoJudgeSessionBrokers, stopRepoJudgeSessionBroker, } from './verdict/judge-session-broker.js';
|
|
12
14
|
import { clearJudgeSessionKillSwitch } from './verdict/judge-session-kill-switch.js';
|
|
15
|
+
function judgeShadowAdvisory(warnings, message) {
|
|
16
|
+
warnings.push(`[judge shadow advisory] ${message}`);
|
|
17
|
+
}
|
|
13
18
|
export async function diagnoseJudge(config, repoRoot = process.cwd(), options = {}) {
|
|
14
19
|
const shouldRunLiveProbe = options.liveProbe === true && !process.env.VITEST && !process.env.VITEST_WORKER_ID;
|
|
15
20
|
const issues = [];
|
|
16
21
|
const warnings = [];
|
|
17
22
|
const notes = [];
|
|
18
23
|
const judge = config.judge;
|
|
24
|
+
const gateMode = judge.mode ?? 'shadow';
|
|
25
|
+
notes.push(`Judge gate mode: ${gateMode} (sync Tier1 removed from hook gate; judge is shadow/async only)`);
|
|
26
|
+
if (gateMode === 'off') {
|
|
27
|
+
notes.push('Judge shadow is disabled (judge.mode=off).');
|
|
28
|
+
}
|
|
19
29
|
const provider = normalizeJudgeProvider(judge.provider);
|
|
20
30
|
const rawProviderId = judge.providerId ? String(judge.providerId) : undefined;
|
|
21
31
|
if (rawProviderId && isRemovedProviderId(rawProviderId)) {
|
|
@@ -36,15 +46,19 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
36
46
|
const capabilities = getJudgeProviderCapabilities(providerId);
|
|
37
47
|
const transport = resolveJudgeTransport(judge);
|
|
38
48
|
const runtime = detectJudgeRuntimeCapabilities(providerId);
|
|
49
|
+
const cursorAcpEnabled = transport === 'cursor-cli' &&
|
|
50
|
+
providerId === 'cursor' &&
|
|
51
|
+
config.judge.runtime?.session.enabled === true;
|
|
52
|
+
const effectiveTransport = cursorAcpEnabled ? 'cursor-acp' : transport;
|
|
39
53
|
notes.push(`Judge providerId: ${providerId}`);
|
|
40
54
|
notes.push(`Judge driver: ${provider}`);
|
|
41
55
|
notes.push(`Judge model requested: ${judge.model}`);
|
|
42
|
-
notes.push(`Judge transport: ${
|
|
56
|
+
notes.push(`Judge transport: ${effectiveTransport}`);
|
|
43
57
|
if (config.policy.modelAssist.enabled) {
|
|
44
58
|
warnings.push('policy.modelAssist is enabled but is not wired to v2 Tier1. Use top-level judge instead.');
|
|
45
59
|
}
|
|
46
60
|
if (capabilities?.requiresConsent && !hasValidCloudConsent(judge) && transport === 'http') {
|
|
47
|
-
|
|
61
|
+
judgeShadowAdvisory(warnings, 'Cloud judge consent is not recorded. Shadow cloud judge will fail until consent is granted.');
|
|
48
62
|
}
|
|
49
63
|
else if (judge.cloudConsent?.accepted) {
|
|
50
64
|
notes.push(`Cloud consent: accepted ${judge.cloudConsent.at} by ${judge.cloudConsent.by}`);
|
|
@@ -74,12 +88,12 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
74
88
|
if (providerId === 'ollama') {
|
|
75
89
|
notes.push(`Ollama endpoint: ${endpoint}`);
|
|
76
90
|
if (discovery.modelIds.length === 0) {
|
|
77
|
-
|
|
91
|
+
judgeShadowAdvisory(warnings, 'Ollama endpoint unreachable or returned no models. Shadow judge will not run.');
|
|
78
92
|
}
|
|
79
93
|
else {
|
|
80
94
|
const hasModel = modelCheck.status === 'found';
|
|
81
95
|
if (!hasModel) {
|
|
82
|
-
|
|
96
|
+
judgeShadowAdvisory(warnings, `Ollama model "${judge.model}" is not present. Pull it before enabling shadow comparisons.`);
|
|
83
97
|
}
|
|
84
98
|
else {
|
|
85
99
|
notes.push(`Ollama model "${judge.model}" is available.`);
|
|
@@ -103,7 +117,7 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
103
117
|
context: { cwd: process.cwd(), repoRoot: process.cwd() },
|
|
104
118
|
});
|
|
105
119
|
if (warmResult.reason === 'ollama_unavailable' || warmResult.reason === 'ollama_parse_error') {
|
|
106
|
-
|
|
120
|
+
judgeShadowAdvisory(warnings, `Ollama warm call failed: ${warmResult.reason}`);
|
|
107
121
|
}
|
|
108
122
|
else {
|
|
109
123
|
notes.push('Ollama warm call succeeded.');
|
|
@@ -111,24 +125,29 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
111
125
|
return { issues, warnings, notes, modelCheck };
|
|
112
126
|
}
|
|
113
127
|
if (transport === 'unavailable') {
|
|
114
|
-
|
|
128
|
+
judgeShadowAdvisory(warnings, 'No judge transport is available (configure endpoint or install native CLI). Shadow judge will not run.');
|
|
115
129
|
return { issues, warnings, notes, modelCheck };
|
|
116
130
|
}
|
|
117
131
|
if (transport.endsWith('-cli')) {
|
|
118
132
|
if (!runtime.cliTransport) {
|
|
119
|
-
|
|
133
|
+
judgeShadowAdvisory(warnings, `Native CLI transport (${transport}) is not available. Shadow judge will not run.`);
|
|
120
134
|
}
|
|
121
135
|
else if (!keyInfo.key && keyInfo.sourceKind !== 'host-session') {
|
|
122
|
-
|
|
136
|
+
judgeShadowAdvisory(warnings, 'Judge API key is not set for the configured credential mode. Shadow cloud judge will not run.');
|
|
123
137
|
}
|
|
124
138
|
else {
|
|
125
|
-
notes.push(
|
|
139
|
+
notes.push(cursorAcpEnabled
|
|
140
|
+
? 'Cursor ACP transport available.'
|
|
141
|
+
: `Native CLI transport available: ${transport}`);
|
|
142
|
+
if (cursorAcpEnabled) {
|
|
143
|
+
notes.push(`Cursor ACP session transport enabled (max ${config.judge.runtime?.session.maxTurns ?? 'unknown'} turns).`);
|
|
144
|
+
}
|
|
126
145
|
if (shouldRunLiveProbe) {
|
|
127
146
|
const smokeConfig = {
|
|
128
147
|
...config,
|
|
129
148
|
judge: {
|
|
130
149
|
...judge,
|
|
131
|
-
timeoutMs:
|
|
150
|
+
timeoutMs: resolveJudgeSmokeProbeTimeoutMs(judge.timeoutMs),
|
|
132
151
|
},
|
|
133
152
|
};
|
|
134
153
|
const smokeJudge = createJudgeFromConfig(smokeConfig, { repoRoot });
|
|
@@ -140,10 +159,16 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
140
159
|
smokeJudge.lastTrace?.fallbackReason ??
|
|
141
160
|
smokeResult.reason;
|
|
142
161
|
if (smokeJudge.lastTrace?.provider === 'fallback') {
|
|
143
|
-
|
|
162
|
+
const recoveryHint = formatJudgeRecoveryHint(inferProviderIdFromFallbackReason(smokeFallback, providerId), smokeFallback);
|
|
163
|
+
const smokeLabel = cursorAcpEnabled ? 'Cursor ACP' : 'Native CLI transport';
|
|
164
|
+
judgeShadowAdvisory(warnings, recoveryHint
|
|
165
|
+
? `${smokeLabel} smoke probe failed (${smokeFallback}). ${recoveryHint}`
|
|
166
|
+
: `${smokeLabel} smoke probe failed (${smokeFallback}).`);
|
|
144
167
|
}
|
|
145
168
|
else {
|
|
146
|
-
notes.push(
|
|
169
|
+
notes.push(cursorAcpEnabled
|
|
170
|
+
? 'Cursor ACP smoke probe succeeded.'
|
|
171
|
+
: 'Native CLI transport smoke probe succeeded.');
|
|
147
172
|
}
|
|
148
173
|
}
|
|
149
174
|
}
|
|
@@ -153,7 +178,7 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
153
178
|
notes.push(`HTTP endpoint: ${judge.endpoint}`);
|
|
154
179
|
}
|
|
155
180
|
if (!keyInfo.key) {
|
|
156
|
-
|
|
181
|
+
judgeShadowAdvisory(warnings, 'Judge API key is not set for the configured credential mode. Shadow cloud judge will not run.');
|
|
157
182
|
}
|
|
158
183
|
else {
|
|
159
184
|
notes.push(`Credential source: ${keyInfo.source ?? keyInfo.sourceKind}`);
|
|
@@ -188,7 +213,7 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
188
213
|
});
|
|
189
214
|
if (dryRun.reason.startsWith('openai_compatible_') ||
|
|
190
215
|
dryRun.reason === 'outbound_scrub_failed') {
|
|
191
|
-
|
|
216
|
+
judgeShadowAdvisory(warnings, `HTTP judge dry-run failed: ${dryRun.reason}`);
|
|
192
217
|
}
|
|
193
218
|
else {
|
|
194
219
|
notes.push('HTTP judge dry-run succeeded.');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ClassifyResult } from './types.js';
|
|
2
|
+
export declare function extractJudgeFallbackReason(result: Pick<ClassifyResult, 'axes' | 'assessment'>): string | undefined;
|
|
3
|
+
export declare function isJudgeInfrastructureFailure(result: Pick<ClassifyResult, 'axes' | 'assessment' | 'reason'>): boolean;
|
|
4
|
+
export declare function inferProviderIdFromFallbackReason(fallbackReason: string | undefined, fallbackProviderId?: string): string;
|
|
5
|
+
export declare function formatJudgeRecoveryHint(providerId: string, fallbackReason?: string): string | null;
|
|
6
|
+
export declare function formatJudgeInfrastructureDenyMessage(params: {
|
|
7
|
+
providerId: string;
|
|
8
|
+
fallbackReason?: string;
|
|
9
|
+
command?: string;
|
|
10
|
+
}): {
|
|
11
|
+
user_message: string;
|
|
12
|
+
agent_message: string;
|
|
13
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const INFRASTRUCTURE_FALLBACK_SUFFIXES = [
|
|
2
|
+
'_cli_nonzero',
|
|
3
|
+
'_cli_unavailable',
|
|
4
|
+
'_cli_parse_error',
|
|
5
|
+
'_cli_spawn_error',
|
|
6
|
+
];
|
|
7
|
+
const INFRASTRUCTURE_FALLBACK_REASONS = new Set([
|
|
8
|
+
'eval_timeout',
|
|
9
|
+
'connect_timeout',
|
|
10
|
+
'parse_timeout',
|
|
11
|
+
'session_unavailable',
|
|
12
|
+
'session_disabled',
|
|
13
|
+
'provider_not_allowlisted',
|
|
14
|
+
'guard_reset',
|
|
15
|
+
'kill_switch',
|
|
16
|
+
'non_json_response',
|
|
17
|
+
'parse_error',
|
|
18
|
+
'cli_error',
|
|
19
|
+
'timeout',
|
|
20
|
+
'version_mismatch',
|
|
21
|
+
'shadow_forced_spawn',
|
|
22
|
+
'unsafe_option_rejected',
|
|
23
|
+
'broker_busy_reset',
|
|
24
|
+
]);
|
|
25
|
+
function isInfrastructureFallbackReason(reason) {
|
|
26
|
+
if (INFRASTRUCTURE_FALLBACK_REASONS.has(reason)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return INFRASTRUCTURE_FALLBACK_SUFFIXES.some((suffix) => reason.endsWith(suffix));
|
|
30
|
+
}
|
|
31
|
+
function infrastructureSignal(signal) {
|
|
32
|
+
return isInfrastructureFallbackReason(signal);
|
|
33
|
+
}
|
|
34
|
+
export function extractJudgeFallbackReason(result) {
|
|
35
|
+
if (result.axes?.judgeFallbackReason) {
|
|
36
|
+
return result.axes.judgeFallbackReason;
|
|
37
|
+
}
|
|
38
|
+
return result.assessment.signals.find((signal) => isInfrastructureFallbackReason(signal));
|
|
39
|
+
}
|
|
40
|
+
export function isJudgeInfrastructureFailure(result) {
|
|
41
|
+
if (result.axes?.judgeProvider === 'fallback') {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
const fallbackReason = extractJudgeFallbackReason(result);
|
|
45
|
+
if (fallbackReason && isInfrastructureFallbackReason(fallbackReason)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return result.assessment.signals.some(infrastructureSignal);
|
|
49
|
+
}
|
|
50
|
+
export function inferProviderIdFromFallbackReason(fallbackReason, fallbackProviderId = 'cursor') {
|
|
51
|
+
if (!fallbackReason) {
|
|
52
|
+
return fallbackProviderId;
|
|
53
|
+
}
|
|
54
|
+
const match = fallbackReason.match(/^(cursor|claude|codex)_/);
|
|
55
|
+
return match?.[1] ?? fallbackProviderId;
|
|
56
|
+
}
|
|
57
|
+
export function formatJudgeRecoveryHint(providerId, fallbackReason) {
|
|
58
|
+
if (!fallbackReason) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
if (fallbackReason.endsWith('_cli_nonzero') || fallbackReason.endsWith('_cli_unavailable')) {
|
|
62
|
+
switch (providerId) {
|
|
63
|
+
case 'cursor':
|
|
64
|
+
return 'Judge CLI is not authenticated. Run: agent login';
|
|
65
|
+
case 'claude':
|
|
66
|
+
return 'Claude CLI failed. Run: claude auth login';
|
|
67
|
+
case 'codex':
|
|
68
|
+
return 'Codex CLI failed. Check codex auth';
|
|
69
|
+
default:
|
|
70
|
+
return `Judge CLI transport failed (${fallbackReason}). Check ${providerId} CLI auth`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (fallbackReason.endsWith('_cli_parse_error')) {
|
|
74
|
+
return `Judge CLI returned an invalid response (${fallbackReason}). Retry after updating the ${providerId} CLI`;
|
|
75
|
+
}
|
|
76
|
+
if (fallbackReason.endsWith('_cli_spawn_error')) {
|
|
77
|
+
return `Judge CLI could not be started (${fallbackReason}). Install or repair the ${providerId} CLI`;
|
|
78
|
+
}
|
|
79
|
+
switch (fallbackReason) {
|
|
80
|
+
case 'eval_timeout':
|
|
81
|
+
case 'connect_timeout':
|
|
82
|
+
case 'parse_timeout':
|
|
83
|
+
case 'timeout':
|
|
84
|
+
return 'Judge timed out. Check network or increase judge.timeoutMs';
|
|
85
|
+
case 'session_unavailable':
|
|
86
|
+
case 'session_disabled':
|
|
87
|
+
return 'Judge session transport is unavailable. Run: belay doctor';
|
|
88
|
+
case 'kill_switch':
|
|
89
|
+
return 'Judge session kill switch is active. Run: belay judge status';
|
|
90
|
+
default:
|
|
91
|
+
if (isInfrastructureFallbackReason(fallbackReason)) {
|
|
92
|
+
return `Judge transport failed (${fallbackReason}). Run: belay doctor`;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function formatJudgeInfrastructureDenyMessage(params) {
|
|
98
|
+
const fallbackReason = params.fallbackReason ?? 'judge_transport_unavailable';
|
|
99
|
+
const recoveryHint = formatJudgeRecoveryHint(params.providerId, fallbackReason) ??
|
|
100
|
+
'Judge transport is unavailable. Run: belay doctor';
|
|
101
|
+
const commandHint = params.command
|
|
102
|
+
? ` For details: belay explain --command "${params.command}"`
|
|
103
|
+
: ' For details: belay explain';
|
|
104
|
+
return {
|
|
105
|
+
user_message: `Belay could not reach the Tier1 judge (${fallbackReason}). ${recoveryHint}. Then retry.${commandHint}`,
|
|
106
|
+
agent_message: `Belay denied this action because the Tier1 judge transport failed (${fallbackReason}). Repair judge infrastructure, then retry the exact same action.`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* fs-scope allowlist matching).
|
|
6
6
|
*/
|
|
7
7
|
export declare function canonicalPath(targetPath: string): string;
|
|
8
|
+
export declare function isPathOutsideRoot(relativePath: string): boolean;
|
|
8
9
|
export declare function pathWithinRoot(root: string, targetPath: string): boolean;
|
|
9
10
|
export declare function relativeWithinRepo(repoRoot: string, targetPath: string): string | null;
|
|
10
11
|
export type WorkspaceRootMatch = {
|
package/dist/core/path-utils.js
CHANGED
|
@@ -36,6 +36,9 @@ export function canonicalPath(targetPath) {
|
|
|
36
36
|
}
|
|
37
37
|
return current;
|
|
38
38
|
}
|
|
39
|
+
export function isPathOutsideRoot(relativePath) {
|
|
40
|
+
return relativePath === '..' || relativePath.startsWith(`..${path.sep}`);
|
|
41
|
+
}
|
|
39
42
|
export function pathWithinRoot(root, targetPath) {
|
|
40
43
|
const resolvedRoot = canonicalPath(root);
|
|
41
44
|
const resolvedTarget = canonicalPath(targetPath);
|
|
@@ -43,7 +46,7 @@ export function pathWithinRoot(root, targetPath) {
|
|
|
43
46
|
if (relativePath === '') {
|
|
44
47
|
return true;
|
|
45
48
|
}
|
|
46
|
-
return !relativePath
|
|
49
|
+
return !isPathOutsideRoot(relativePath) && !path.isAbsolute(relativePath);
|
|
47
50
|
}
|
|
48
51
|
function relativeWithinRoot(root, targetPath, options = {}) {
|
|
49
52
|
const resolvedRoot = options.canonicalizeRoot === false ? path.resolve(root) : canonicalPath(root);
|
|
@@ -52,7 +55,7 @@ function relativeWithinRoot(root, targetPath, options = {}) {
|
|
|
52
55
|
if (relativePath === '') {
|
|
53
56
|
return '.';
|
|
54
57
|
}
|
|
55
|
-
if (relativePath
|
|
58
|
+
if (isPathOutsideRoot(relativePath) || path.isAbsolute(relativePath)) {
|
|
56
59
|
return null;
|
|
57
60
|
}
|
|
58
61
|
return relativePath;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const NON_RECOVERABLE_ACTIONS = new Set([
|
|
2
|
+
'network.connect',
|
|
3
|
+
'git.ref.write',
|
|
4
|
+
'control_plane.write',
|
|
5
|
+
'secret.read',
|
|
6
|
+
'indeterminate',
|
|
7
|
+
]);
|
|
8
|
+
/** Actions that cannot be closed by local git-worktree / file checkpoint recovery. */
|
|
9
|
+
export function capabilityRequestsBlockRecovery(requests) {
|
|
10
|
+
if (!requests?.length) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
for (const request of requests) {
|
|
14
|
+
if (NON_RECOVERABLE_ACTIONS.has(request.action)) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (request.evidence.level === 'indeterminate') {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ClassifyResult } from '../types.js';
|
|
2
|
+
export declare const RECOVERY_SUBSTRATE_UNAVAILABLE = "recovery_substrate_unavailable";
|
|
3
|
+
export declare const RECOVERY_EXECUTION_FAILED = "recovery_execution_failed";
|
|
4
|
+
export declare const RECOVERY_DIRTY_WORKTREE = "recovery_dirty_worktree";
|
|
5
|
+
export declare function recoveryFailReasonFromSkip(skipReason: string): string;
|
|
6
|
+
export declare function recoveryFailClosedResult(predicted: ClassifyResult, reason: string, signals?: string[]): ClassifyResult;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const RECOVERY_SUBSTRATE_UNAVAILABLE = 'recovery_substrate_unavailable';
|
|
2
|
+
export const RECOVERY_EXECUTION_FAILED = 'recovery_execution_failed';
|
|
3
|
+
export const RECOVERY_DIRTY_WORKTREE = 'recovery_dirty_worktree';
|
|
4
|
+
export function recoveryFailReasonFromSkip(skipReason) {
|
|
5
|
+
if (skipReason === 'transactional_command_failed' || skipReason === 'transactional_timed_out') {
|
|
6
|
+
return RECOVERY_EXECUTION_FAILED;
|
|
7
|
+
}
|
|
8
|
+
if (skipReason === 'dirty_worktree') {
|
|
9
|
+
return RECOVERY_DIRTY_WORKTREE;
|
|
10
|
+
}
|
|
11
|
+
return RECOVERY_SUBSTRATE_UNAVAILABLE;
|
|
12
|
+
}
|
|
13
|
+
export function recoveryFailClosedResult(predicted, reason, signals = []) {
|
|
14
|
+
return {
|
|
15
|
+
...predicted,
|
|
16
|
+
verdict: 'deny_pending_approval',
|
|
17
|
+
reason,
|
|
18
|
+
assessment: {
|
|
19
|
+
...predicted.assessment,
|
|
20
|
+
reversibility: 'irreversible',
|
|
21
|
+
confidence: 1,
|
|
22
|
+
signals: [...predicted.assessment.signals, 'recovery_fail_closed', ...signals],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { capabilityRequestsBlockRecovery } from './capability.js';
|
|
2
|
+
export { RECOVERY_DIRTY_WORKTREE, RECOVERY_EXECUTION_FAILED, RECOVERY_SUBSTRATE_UNAVAILABLE, recoveryFailClosedResult, recoveryFailReasonFromSkip, } from './fail-closed.js';
|
|
3
|
+
export type { RecoveryBackend, RecoveryProofContext, RecoveryProofV1 } from './types.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CapabilityRequestV1 } from '../capability/request.js';
|
|
2
|
+
/** Backends that can mint a verifiable RecoveryProof (ADR: proof-only execution). */
|
|
3
|
+
export type RecoveryBackend = 'git_worktree' | 'file_checkpoint';
|
|
4
|
+
export interface RecoveryProofV1 {
|
|
5
|
+
version: 1;
|
|
6
|
+
backend: RecoveryBackend;
|
|
7
|
+
inputFingerprint: string;
|
|
8
|
+
resourceScope: string;
|
|
9
|
+
baseStateHash: string;
|
|
10
|
+
effectClosure: 'repo_local_fs';
|
|
11
|
+
issuedAt: string;
|
|
12
|
+
expiresAt: string;
|
|
13
|
+
probeSignals: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface RecoveryProofContext {
|
|
16
|
+
repoRoot: string;
|
|
17
|
+
cwd: string;
|
|
18
|
+
command: string;
|
|
19
|
+
requests: CapabilityRequestV1[];
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -18,6 +18,9 @@ const STANDING_ALLOW_BLOCKED_REASONS = new Set([
|
|
|
18
18
|
'protected_artifact',
|
|
19
19
|
'pipe_to_shell',
|
|
20
20
|
'command_substitution',
|
|
21
|
+
'outside_repo_mutation',
|
|
22
|
+
'outside_repo_redirect',
|
|
23
|
+
'control_plane_mutation',
|
|
21
24
|
]);
|
|
22
25
|
export function standingAllowFile(config, repoLocalStateDir) {
|
|
23
26
|
return `${belayStateDir(config, repoLocalStateDir)}/standing-allow.json`;
|
|
@@ -51,7 +51,10 @@ export function evaluateTransactionalDiff(changes, ctx) {
|
|
|
51
51
|
categories.add('large_deletion');
|
|
52
52
|
}
|
|
53
53
|
const categoryList = [...categories];
|
|
54
|
-
const dangerous = categoryList.includes('large_deletion')
|
|
54
|
+
const dangerous = categoryList.includes('large_deletion') ||
|
|
55
|
+
categoryList.includes('repo_outside') ||
|
|
56
|
+
categoryList.includes('sensitive_path') ||
|
|
57
|
+
categoryList.includes('control_plane');
|
|
55
58
|
const base = {
|
|
56
59
|
categories: categoryList,
|
|
57
60
|
changes,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { capabilityRequestsBlockRecovery } from '../recovery/capability.js';
|
|
1
2
|
const EXCLUDED_REASONS = new Set([
|
|
2
3
|
'unparseable_shell',
|
|
3
4
|
'external_effect',
|
|
@@ -26,6 +27,9 @@ export function isTransactionalEligible(config, kind, result) {
|
|
|
26
27
|
if (EXCLUDED_REASONS.has(result.reason)) {
|
|
27
28
|
return false;
|
|
28
29
|
}
|
|
30
|
+
if (capabilityRequestsBlockRecovery(result.capabilityRequests)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
29
33
|
const { assessment } = result;
|
|
30
34
|
if (assessment.external) {
|
|
31
35
|
return false;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { TransactionalFileChange, TransactionalSnapshot } from './types.js';
|
|
2
|
+
export declare const TRANSACTIONAL_APPLY_TOCTOU = "transactional_apply_toctou";
|
|
3
|
+
export declare function hashRepoFile(filePath: string): Promise<string>;
|
|
4
|
+
export declare function captureRepoFileHashes(repoRoot: string, changes: TransactionalFileChange[]): Promise<Map<string, string | null>>;
|
|
2
5
|
export declare function isGitWorktreeAvailable(repoRoot: string): Promise<boolean>;
|
|
3
|
-
export declare function isDirtyWorktree(repoRoot: string
|
|
4
|
-
|
|
6
|
+
export declare function isDirtyWorktree(repoRoot: string, options?: {
|
|
7
|
+
ignoreRoots?: string[];
|
|
8
|
+
}): Promise<boolean>;
|
|
9
|
+
export declare function createGitWorktreeSnapshot(repoRoot: string, _stateDir: string): Promise<TransactionalSnapshot>;
|
|
5
10
|
export declare function resolveWorktreeCwd(repoRoot: string, worktreePath: string, cwd: string): string;
|
|
6
11
|
export interface ShellRunResult {
|
|
7
12
|
exitCode: number | null;
|
|
@@ -10,4 +15,6 @@ export interface ShellRunResult {
|
|
|
10
15
|
}
|
|
11
16
|
export declare function runShellCommand(command: string, cwd: string, timeoutMs: number): Promise<ShellRunResult>;
|
|
12
17
|
export declare function collectWorktreeChanges(worktreePath: string): Promise<TransactionalFileChange[]>;
|
|
13
|
-
export declare function applyWorktreeChanges(worktreePath: string, repoRoot: string, changes: TransactionalFileChange[]
|
|
18
|
+
export declare function applyWorktreeChanges(worktreePath: string, repoRoot: string, changes: TransactionalFileChange[], options?: {
|
|
19
|
+
baseHashes?: Map<string, string | null>;
|
|
20
|
+
}): Promise<void>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
|
-
import { copyFile, mkdir, mkdtemp, rm } from 'node:fs/promises';
|
|
4
|
+
import { copyFile, mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
-
import { canonicalPath } from '../path-utils.js';
|
|
7
|
+
import { canonicalPath, isPathOutsideRoot, pathWithinRoot } from '../path-utils.js';
|
|
8
|
+
export const TRANSACTIONAL_APPLY_TOCTOU = 'transactional_apply_toctou';
|
|
8
9
|
function execGit(repoRoot, args) {
|
|
9
10
|
return new Promise((resolve, reject) => {
|
|
10
11
|
const child = spawn('git', ['-C', repoRoot, ...args], {
|
|
@@ -24,6 +25,38 @@ function execGit(repoRoot, args) {
|
|
|
24
25
|
});
|
|
25
26
|
});
|
|
26
27
|
}
|
|
28
|
+
function porcelainRelativePath(line) {
|
|
29
|
+
if (line.length < 4) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const relativePath = line.slice(3).trim();
|
|
33
|
+
return relativePath || null;
|
|
34
|
+
}
|
|
35
|
+
function isIgnoredDirtyPath(repoRoot, relativePath, ignoreRoots) {
|
|
36
|
+
if (ignoreRoots.length === 0) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const absolutePath = canonicalPath(path.join(repoRoot, relativePath));
|
|
40
|
+
return ignoreRoots.some((root) => pathWithinRoot(root, absolutePath) ||
|
|
41
|
+
root === absolutePath ||
|
|
42
|
+
pathWithinRoot(absolutePath, root));
|
|
43
|
+
}
|
|
44
|
+
export async function hashRepoFile(filePath) {
|
|
45
|
+
const content = await readFile(filePath);
|
|
46
|
+
return createHash('sha256').update(content).digest('hex');
|
|
47
|
+
}
|
|
48
|
+
export async function captureRepoFileHashes(repoRoot, changes) {
|
|
49
|
+
const hashes = new Map();
|
|
50
|
+
for (const change of changes) {
|
|
51
|
+
const target = path.join(repoRoot, change.relativePath);
|
|
52
|
+
if (!existsSync(target)) {
|
|
53
|
+
hashes.set(change.relativePath, null);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
hashes.set(change.relativePath, await hashRepoFile(target));
|
|
57
|
+
}
|
|
58
|
+
return hashes;
|
|
59
|
+
}
|
|
27
60
|
export async function isGitWorktreeAvailable(repoRoot) {
|
|
28
61
|
try {
|
|
29
62
|
await execGit(repoRoot, ['rev-parse', '--git-dir']);
|
|
@@ -33,18 +66,32 @@ export async function isGitWorktreeAvailable(repoRoot) {
|
|
|
33
66
|
return false;
|
|
34
67
|
}
|
|
35
68
|
}
|
|
36
|
-
export async function isDirtyWorktree(repoRoot) {
|
|
69
|
+
export async function isDirtyWorktree(repoRoot, options) {
|
|
37
70
|
try {
|
|
38
|
-
const status = await execGit(repoRoot, ['status', '--porcelain'
|
|
39
|
-
|
|
71
|
+
const status = await execGit(repoRoot, ['status', '--porcelain']);
|
|
72
|
+
const ignoreRoots = (options?.ignoreRoots ?? []).map((root) => canonicalPath(root));
|
|
73
|
+
const repoRootCanonical = canonicalPath(repoRoot);
|
|
74
|
+
for (const line of status.split('\n')) {
|
|
75
|
+
if (!line.trim()) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const relativePath = porcelainRelativePath(line);
|
|
79
|
+
if (!relativePath) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (isIgnoredDirtyPath(repoRootCanonical, relativePath, ignoreRoots)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
40
88
|
}
|
|
41
89
|
catch {
|
|
42
90
|
return true;
|
|
43
91
|
}
|
|
44
92
|
}
|
|
45
|
-
export async function createGitWorktreeSnapshot(repoRoot,
|
|
46
|
-
const worktreePath = path.join(
|
|
47
|
-
await mkdir(stateDir, { recursive: true });
|
|
93
|
+
export async function createGitWorktreeSnapshot(repoRoot, _stateDir) {
|
|
94
|
+
const worktreePath = await mkdtemp(path.join(os.tmpdir(), 'belay-tx-'));
|
|
48
95
|
await execGit(repoRoot, ['worktree', 'add', '--detach', worktreePath, 'HEAD']);
|
|
49
96
|
return {
|
|
50
97
|
worktreePath,
|
|
@@ -67,7 +114,7 @@ export async function createGitWorktreeSnapshot(repoRoot, stateDir) {
|
|
|
67
114
|
export function resolveWorktreeCwd(repoRoot, worktreePath, cwd) {
|
|
68
115
|
const resolvedCwd = canonicalPath(cwd);
|
|
69
116
|
const relative = path.relative(canonicalPath(repoRoot), resolvedCwd);
|
|
70
|
-
if (relative
|
|
117
|
+
if (isPathOutsideRoot(relative) || path.isAbsolute(relative)) {
|
|
71
118
|
return worktreePath;
|
|
72
119
|
}
|
|
73
120
|
if (relative === '') {
|
|
@@ -155,7 +202,30 @@ async function rollbackAppliedChanges(actions) {
|
|
|
155
202
|
}
|
|
156
203
|
}
|
|
157
204
|
}
|
|
158
|
-
|
|
205
|
+
async function assertRepoFilesUnchanged(repoRoot, changes, baseHashes) {
|
|
206
|
+
for (const change of changes) {
|
|
207
|
+
const target = path.join(repoRoot, change.relativePath);
|
|
208
|
+
const expected = baseHashes.get(change.relativePath);
|
|
209
|
+
const exists = existsSync(target);
|
|
210
|
+
if (expected === null) {
|
|
211
|
+
if (exists) {
|
|
212
|
+
throw new Error(TRANSACTIONAL_APPLY_TOCTOU);
|
|
213
|
+
}
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (!exists) {
|
|
217
|
+
throw new Error(TRANSACTIONAL_APPLY_TOCTOU);
|
|
218
|
+
}
|
|
219
|
+
const current = await hashRepoFile(target);
|
|
220
|
+
if (current !== expected) {
|
|
221
|
+
throw new Error(TRANSACTIONAL_APPLY_TOCTOU);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
export async function applyWorktreeChanges(worktreePath, repoRoot, changes, options) {
|
|
226
|
+
if (options?.baseHashes) {
|
|
227
|
+
await assertRepoFilesUnchanged(repoRoot, changes, options.baseHashes);
|
|
228
|
+
}
|
|
159
229
|
const backupRoot = await mkdtemp(path.join(os.tmpdir(), 'belay-tx-rollback-'));
|
|
160
230
|
const rollbackActions = [];
|
|
161
231
|
try {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const TRANSACTIONAL_ALREADY_APPLIED = "transactional_already_applied";
|
|
2
2
|
export declare const TRANSACTIONAL_OBSERVED_RISK = "transactional_observed_risk";
|
|
3
3
|
export declare const TRANSACTIONAL_APPLY_FAILED = "transactional_apply_failed";
|
|
4
|
+
export { RECOVERY_DIRTY_WORKTREE, RECOVERY_EXECUTION_FAILED, RECOVERY_SUBSTRATE_UNAVAILABLE, } from '../recovery/fail-closed.js';
|
|
4
5
|
export declare const TRANSACTIONAL_APPROVAL_BYPASS_REASONS: Set<string>;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export const TRANSACTIONAL_ALREADY_APPLIED = 'transactional_already_applied';
|
|
2
2
|
export const TRANSACTIONAL_OBSERVED_RISK = 'transactional_observed_risk';
|
|
3
3
|
export const TRANSACTIONAL_APPLY_FAILED = 'transactional_apply_failed';
|
|
4
|
+
export { RECOVERY_DIRTY_WORKTREE, RECOVERY_EXECUTION_FAILED, RECOVERY_SUBSTRATE_UNAVAILABLE, } from '../recovery/fail-closed.js';
|
|
4
5
|
export const TRANSACTIONAL_APPROVAL_BYPASS_REASONS = new Set([
|
|
5
6
|
TRANSACTIONAL_OBSERVED_RISK,
|
|
6
7
|
TRANSACTIONAL_ALREADY_APPLIED,
|
|
7
8
|
TRANSACTIONAL_APPLY_FAILED,
|
|
9
|
+
'recovery_substrate_unavailable',
|
|
10
|
+
'recovery_execution_failed',
|
|
11
|
+
'recovery_dirty_worktree',
|
|
8
12
|
]);
|