@guilz-dev/belay 0.3.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/README.md +7 -1
  2. package/dist/adapters/cursor/runtime-entry.js +1 -0
  3. package/dist/adapters/layouts/scope.js +3 -2
  4. package/dist/adapters/shared/gate-runtime.d.ts +12 -16
  5. package/dist/adapters/shared/gate-runtime.js +499 -43
  6. package/dist/bundle/claude-runtime.mjs +8004 -2676
  7. package/dist/bundle/codex-runtime.mjs +8021 -2683
  8. package/dist/bundle/cursor-runtime.mjs +7993 -2667
  9. package/dist/cli.js +180 -6
  10. package/dist/commands/approve.d.ts +1 -0
  11. package/dist/commands/approve.js +45 -26
  12. package/dist/commands/classify-for-report.js +1 -1
  13. package/dist/commands/config.d.ts +8 -0
  14. package/dist/commands/config.js +264 -58
  15. package/dist/commands/doctor.js +47 -2
  16. package/dist/commands/explain.js +17 -2
  17. package/dist/commands/harvest.d.ts +29 -0
  18. package/dist/commands/harvest.js +72 -0
  19. package/dist/commands/judge.d.ts +11 -1
  20. package/dist/commands/judge.js +24 -4
  21. package/dist/commands/metrics.js +34 -2
  22. package/dist/commands/quality.d.ts +38 -0
  23. package/dist/commands/quality.js +105 -0
  24. package/dist/commands/session.d.ts +20 -0
  25. package/dist/commands/session.js +51 -0
  26. package/dist/commands/simulate.d.ts +1 -0
  27. package/dist/commands/simulate.js +7 -2
  28. package/dist/commands/standing-allow.d.ts +10 -0
  29. package/dist/commands/standing-allow.js +26 -0
  30. package/dist/commands/tui.d.ts +48 -0
  31. package/dist/commands/tui.js +150 -0
  32. package/dist/config-io.js +59 -17
  33. package/dist/conformance/guarantee-posture.d.ts +21 -0
  34. package/dist/conformance/guarantee-posture.js +46 -0
  35. package/dist/conformance/guarantee-table.d.ts +1 -0
  36. package/dist/conformance/guarantee-table.js +14 -9
  37. package/dist/core/approval-replay-cli.d.ts +7 -0
  38. package/dist/core/approval-replay-cli.js +36 -0
  39. package/dist/core/approval-replay.d.ts +45 -0
  40. package/dist/core/approval-replay.js +141 -0
  41. package/dist/core/approval-service.d.ts +15 -0
  42. package/dist/core/approval-service.js +53 -4
  43. package/dist/core/approval.d.ts +26 -3
  44. package/dist/core/approval.js +61 -7
  45. package/dist/core/audit-analysis.d.ts +7 -1
  46. package/dist/core/audit-analysis.js +111 -0
  47. package/dist/core/audit-metrics.d.ts +7 -0
  48. package/dist/core/audit-metrics.js +24 -4
  49. package/dist/core/audit-query.d.ts +1 -0
  50. package/dist/core/audit-query.js +3 -0
  51. package/dist/core/audit-replay-context.d.ts +35 -0
  52. package/dist/core/audit-replay-context.js +88 -0
  53. package/dist/core/audit-types.d.ts +24 -1
  54. package/dist/core/audit-types.js +1 -1
  55. package/dist/core/capability/approval-state-mutation.d.ts +13 -0
  56. package/dist/core/capability/approval-state-mutation.js +92 -0
  57. package/dist/core/capability/approval-v3.d.ts +15 -0
  58. package/dist/core/capability/approval-v3.js +74 -0
  59. package/dist/core/capability/attestation.d.ts +13 -0
  60. package/dist/core/capability/attestation.js +55 -0
  61. package/dist/core/capability/boundary-attestation-sign.d.ts +21 -0
  62. package/dist/core/capability/boundary-attestation-sign.js +84 -0
  63. package/dist/core/capability/boundary-driver-container.d.ts +7 -0
  64. package/dist/core/capability/boundary-driver-container.js +122 -0
  65. package/dist/core/capability/boundary-driver.d.ts +29 -0
  66. package/dist/core/capability/boundary-driver.js +43 -0
  67. package/dist/core/capability/boundary-egress.d.ts +21 -0
  68. package/dist/core/capability/boundary-egress.js +109 -0
  69. package/dist/core/capability/boundary-grant-materialize.d.ts +13 -0
  70. package/dist/core/capability/boundary-grant-materialize.js +78 -0
  71. package/dist/core/capability/boundary-profile.d.ts +15 -0
  72. package/dist/core/capability/boundary-profile.js +25 -0
  73. package/dist/core/capability/boundary-run.d.ts +23 -0
  74. package/dist/core/capability/boundary-run.js +23 -0
  75. package/dist/core/capability/boundary-session.d.ts +57 -0
  76. package/dist/core/capability/boundary-session.js +129 -0
  77. package/dist/core/capability/capability-request-hash.d.ts +2 -0
  78. package/dist/core/capability/capability-request-hash.js +8 -0
  79. package/dist/core/capability/gate-latency-ratchet.d.ts +7 -0
  80. package/dist/core/capability/gate-latency-ratchet.js +18 -0
  81. package/dist/core/capability/gate-policy-shadow.d.ts +25 -0
  82. package/dist/core/capability/gate-policy-shadow.js +108 -0
  83. package/dist/core/capability/gate-shadow-audit.d.ts +1 -0
  84. package/dist/core/capability/gate-shadow-audit.js +1 -0
  85. package/dist/core/capability/gate-shadow-ratchet.d.ts +14 -0
  86. package/dist/core/capability/gate-shadow-ratchet.js +81 -0
  87. package/dist/core/capability/grant-lease.d.ts +9 -0
  88. package/dist/core/capability/grant-lease.js +46 -0
  89. package/dist/core/capability/grant-loader.d.ts +12 -0
  90. package/dist/core/capability/grant-loader.js +23 -0
  91. package/dist/core/capability/grant-match.d.ts +6 -0
  92. package/dist/core/capability/grant-match.js +93 -0
  93. package/dist/core/capability/grant.d.ts +16 -0
  94. package/dist/core/capability/grant.js +16 -0
  95. package/dist/core/capability/index.d.ts +10 -1
  96. package/dist/core/capability/index.js +8 -0
  97. package/dist/core/capability/limits.d.ts +7 -0
  98. package/dist/core/capability/limits.js +55 -0
  99. package/dist/core/capability/paths.d.ts +2 -2
  100. package/dist/core/capability/paths.js +9 -9
  101. package/dist/core/capability/policy-bridge.d.ts +18 -0
  102. package/dist/core/capability/policy-bridge.js +51 -0
  103. package/dist/core/capability/policy-engine.d.ts +70 -0
  104. package/dist/core/capability/policy-engine.js +476 -0
  105. package/dist/core/capability/policy-types.d.ts +20 -0
  106. package/dist/core/capability/policy-types.js +1 -0
  107. package/dist/core/capability/request.d.ts +45 -0
  108. package/dist/core/capability/request.js +1 -0
  109. package/dist/core/capability/resolver.d.ts +4 -0
  110. package/dist/core/capability/resolver.js +4 -0
  111. package/dist/core/capability/trusted-workspace-roots.d.ts +25 -0
  112. package/dist/core/capability/trusted-workspace-roots.js +149 -0
  113. package/dist/core/capability/types.d.ts +11 -1
  114. package/dist/core/capability-approval.d.ts +2 -1
  115. package/dist/core/capability-approval.js +100 -2
  116. package/dist/core/classify-subagent.d.ts +2 -1
  117. package/dist/core/classify-subagent.js +40 -22
  118. package/dist/core/classify-tool.js +157 -64
  119. package/dist/core/config.d.ts +32 -3
  120. package/dist/core/config.js +105 -3
  121. package/dist/core/gate-contract.d.ts +3 -0
  122. package/dist/core/gate-contract.js +3 -0
  123. package/dist/core/gate-engine.js +12 -7
  124. package/dist/core/harvest.d.ts +53 -0
  125. package/dist/core/harvest.js +276 -0
  126. package/dist/core/index.d.ts +9 -3
  127. package/dist/core/index.js +6 -2
  128. package/dist/core/judge-doctor.d.ts +3 -0
  129. package/dist/core/judge-doctor.js +90 -11
  130. package/dist/core/judge-fallback-hints.d.ts +13 -0
  131. package/dist/core/judge-fallback-hints.js +108 -0
  132. package/dist/core/path-utils.d.ts +12 -0
  133. package/dist/core/path-utils.js +60 -7
  134. package/dist/core/reclassify.d.ts +3 -0
  135. package/dist/core/reclassify.js +44 -14
  136. package/dist/core/recovery/capability.d.ts +3 -0
  137. package/dist/core/recovery/capability.js +22 -0
  138. package/dist/core/recovery/fail-closed.d.ts +6 -0
  139. package/dist/core/recovery/fail-closed.js +25 -0
  140. package/dist/core/recovery/index.d.ts +3 -0
  141. package/dist/core/recovery/index.js +2 -0
  142. package/dist/core/recovery/types.d.ts +20 -0
  143. package/dist/core/recovery/types.js +1 -0
  144. package/dist/core/replay-scrub.d.ts +9 -0
  145. package/dist/core/replay-scrub.js +43 -0
  146. package/dist/core/shell-tokenizer.d.ts +2 -0
  147. package/dist/core/shell-tokenizer.js +82 -23
  148. package/dist/core/standing-allow.d.ts +50 -0
  149. package/dist/core/standing-allow.js +178 -0
  150. package/dist/core/transactional/diff-evaluator.js +4 -1
  151. package/dist/core/transactional/eligibility.js +4 -0
  152. package/dist/core/transactional/git-worktree.d.ts +10 -3
  153. package/dist/core/transactional/git-worktree.js +81 -11
  154. package/dist/core/transactional/reasons.d.ts +1 -0
  155. package/dist/core/transactional/reasons.js +4 -0
  156. package/dist/core/transactional/runner.js +22 -6
  157. package/dist/core/transactional/types.d.ts +4 -0
  158. package/dist/core/types.d.ts +36 -3
  159. package/dist/core/verdict/adapter.d.ts +3 -4
  160. package/dist/core/verdict/adapter.js +19 -6
  161. package/dist/core/verdict/containment.d.ts +4 -3
  162. package/dist/core/verdict/containment.js +17 -12
  163. package/dist/core/verdict/cursor-acp-client.d.ts +67 -0
  164. package/dist/core/verdict/cursor-acp-client.js +345 -0
  165. package/dist/core/verdict/egress-classify.d.ts +2 -2
  166. package/dist/core/verdict/egress-classify.js +2 -2
  167. package/dist/core/verdict/git-classifier.d.ts +26 -0
  168. package/dist/core/verdict/git-classifier.js +533 -0
  169. package/dist/core/verdict/judge-audit.d.ts +1 -0
  170. package/dist/core/verdict/judge-audit.js +33 -1
  171. package/dist/core/verdict/judge-baseline.d.ts +20 -0
  172. package/dist/core/verdict/judge-baseline.js +49 -0
  173. package/dist/core/verdict/judge-broker-service.d.ts +42 -0
  174. package/dist/core/verdict/judge-broker-service.js +291 -0
  175. package/dist/core/verdict/judge-catalog.js +1 -0
  176. package/dist/core/verdict/judge-cli-fingerprint.d.ts +6 -0
  177. package/dist/core/verdict/judge-cli-fingerprint.js +67 -0
  178. package/dist/core/verdict/judge-cli.d.ts +23 -0
  179. package/dist/core/verdict/judge-cli.js +139 -16
  180. package/dist/core/verdict/judge-factory.js +13 -4
  181. package/dist/core/verdict/judge-provider-matrix.d.ts +20 -0
  182. package/dist/core/verdict/judge-provider-matrix.js +66 -0
  183. package/dist/core/verdict/judge-runtime-config.d.ts +44 -0
  184. package/dist/core/verdict/judge-runtime-config.js +96 -0
  185. package/dist/core/verdict/judge-session-broker.d.ts +48 -0
  186. package/dist/core/verdict/judge-session-broker.js +200 -0
  187. package/dist/core/verdict/judge-session-guard.d.ts +27 -0
  188. package/dist/core/verdict/judge-session-guard.js +91 -0
  189. package/dist/core/verdict/judge-session-kill-switch.d.ts +11 -0
  190. package/dist/core/verdict/judge-session-kill-switch.js +43 -0
  191. package/dist/core/verdict/judge-session-mutex.d.ts +9 -0
  192. package/dist/core/verdict/judge-session-mutex.js +23 -0
  193. package/dist/core/verdict/judge-shadow.d.ts +24 -0
  194. package/dist/core/verdict/judge-shadow.js +116 -0
  195. package/dist/core/verdict/judge-transport.d.ts +44 -0
  196. package/dist/core/verdict/judge-transport.js +268 -0
  197. package/dist/core/verdict/judge.d.ts +14 -14
  198. package/dist/core/verdict/judge.js +5 -67
  199. package/dist/core/verdict/launcher-resolve.d.ts +2 -0
  200. package/dist/core/verdict/launcher-resolve.js +16 -0
  201. package/dist/core/verdict/parser.js +6 -1
  202. package/dist/core/verdict/prescan.d.ts +15 -0
  203. package/dist/core/verdict/prescan.js +68 -0
  204. package/dist/core/verdict/shell-policy.d.ts +25 -0
  205. package/dist/core/verdict/shell-policy.js +40 -0
  206. package/dist/core/verdict/shell-semantics.d.ts +24 -0
  207. package/dist/core/verdict/shell-semantics.js +225 -0
  208. package/dist/core/verdict/types.d.ts +26 -1
  209. package/dist/core/verdict/verdict.js +300 -179
  210. package/dist/corpus/adversarial-probe.d.ts +86 -0
  211. package/dist/corpus/adversarial-probe.js +220 -0
  212. package/dist/corpus/evaluate.d.ts +45 -12
  213. package/dist/corpus/evaluate.js +61 -8
  214. package/dist/corpus/gate-latency-budget.d.ts +39 -0
  215. package/dist/corpus/gate-latency-budget.js +33 -0
  216. package/dist/corpus/gates.d.ts +35 -0
  217. package/dist/corpus/gates.js +81 -0
  218. package/dist/corpus/judge-accuracy.d.ts +8 -0
  219. package/dist/corpus/judge-accuracy.js +39 -0
  220. package/dist/corpus/must-allow-commands.d.ts +5 -0
  221. package/dist/corpus/must-allow-commands.js +13 -0
  222. package/dist/corpus/mutators.d.ts +28 -0
  223. package/dist/corpus/mutators.js +162 -0
  224. package/dist/corpus/ratchet.d.ts +42 -0
  225. package/dist/corpus/ratchet.js +116 -0
  226. package/dist/corpus/runtime-match.d.ts +19 -0
  227. package/dist/corpus/runtime-match.js +43 -0
  228. package/dist/corpus/standing-allow-catalog.generated.d.ts +13 -0
  229. package/dist/corpus/standing-allow-catalog.generated.js +99 -0
  230. package/dist/corpus/types.d.ts +47 -0
  231. package/dist/corpus/types.js +157 -0
  232. package/dist/installer.js +4 -1
  233. package/dist/judge-broker-daemon.d.ts +1 -0
  234. package/dist/judge-broker-daemon.js +137 -0
  235. package/dist/services/sandbox-service.d.ts +5 -0
  236. package/dist/services/sandbox-service.js +30 -2
  237. package/dist/types.d.ts +2 -2
  238. package/dist/version.d.ts +1 -1
  239. package/dist/version.js +1 -1
  240. package/package.json +7 -3
  241. package/skills/belay/SKILL.md +7 -1
  242. package/skills/belay/belay-approve.md +17 -0
@@ -1,7 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { repoLocalStateDirFor } from '../../config-io.js';
4
- import { normalizeJudgeProvider, scrubOptionsFromConfig } from '../config.js';
4
+ import { belayStateDir, normalizeJudgeProvider, scrubOptionsFromConfig } from '../config.js';
5
5
  import { resolveJudgeCredential } from '../judge-api-key.js';
6
6
  import { hasValidCloudConsent, isCloudJudgeConfig } from '../judge-config.js';
7
7
  import { rejectDeprecatedJudgeModelAuto } from '../judge-model-policy.js';
@@ -63,7 +63,8 @@ function providerIdFromJudge(judge) {
63
63
  }
64
64
  return inferProviderIdFromConfig(judge);
65
65
  }
66
- function createCliJudgeForProvider(providerId, judgeConfig, config) {
66
+ function createCliJudgeForProvider(providerId, judgeConfig, config, repoRoot) {
67
+ const resolvedRepoRoot = repoRoot ?? process.cwd();
67
68
  const { resolved } = resolveJudgeModel(judgeConfig);
68
69
  const options = {
69
70
  modelRequested: judgeConfig.model,
@@ -71,6 +72,10 @@ function createCliJudgeForProvider(providerId, judgeConfig, config) {
71
72
  timeoutMs: judgeConfig.timeoutMs,
72
73
  sensitivePaths: config.classifier.sensitivePaths,
73
74
  scrubOptions: scrubOptionsFromConfig(config),
75
+ runtime: judgeConfig.runtime,
76
+ repoRoot: resolvedRepoRoot,
77
+ stateDir: belayStateDir(config, repoLocalStateDirFor(resolvedRepoRoot, config)),
78
+ judgeMode: config.mode,
74
79
  };
75
80
  if (providerId === 'codex') {
76
81
  return createCodexCliJudge(options);
@@ -99,11 +104,12 @@ export function createJudgeFromConfig(config, options = {}) {
99
104
  const catalogSpec = getJudgeProviderSpec(providerId);
100
105
  const { resolved } = resolveJudgeModel(judgeConfig);
101
106
  const runtime = detectJudgeRuntimeCapabilities(providerId);
107
+ const repoRoot = options.repoRoot ?? process.cwd();
102
108
  if (provider === 'openai-compatible') {
103
109
  const endpoint = judgeConfig.endpoint?.trim();
104
110
  if (!endpoint && runtime.cliTransport) {
105
111
  if (providerId === 'codex' || providerId === 'cursor') {
106
- return createCliJudgeForProvider(providerId, judgeConfig, config);
112
+ return createCliJudgeForProvider(providerId, judgeConfig, config, repoRoot);
107
113
  }
108
114
  }
109
115
  if (!endpoint) {
@@ -122,7 +128,6 @@ export function createJudgeFromConfig(config, options = {}) {
122
128
  modelResolved: resolved,
123
129
  });
124
130
  }
125
- const repoRoot = options.repoRoot ?? process.cwd();
126
131
  const repoLocalStateDir = repoLocalStateDirFor(repoRoot, config);
127
132
  return createOpenAiCompatibleJudge({
128
133
  endpoint,
@@ -156,6 +161,10 @@ export function createJudgeFromConfig(config, options = {}) {
156
161
  timeoutMs: judgeConfig.timeoutMs,
157
162
  sensitivePaths: config.classifier.sensitivePaths,
158
163
  scrubOptions: scrubOptionsFromConfig(config),
164
+ runtime: judgeConfig.runtime,
165
+ repoRoot,
166
+ stateDir: belayStateDir(config, repoLocalStateDirFor(repoRoot, config)),
167
+ judgeMode: config.mode,
159
168
  });
160
169
  }
161
170
  return createFailClosedJudge({
@@ -0,0 +1,20 @@
1
+ import type { JudgeProviderId } from './judge-catalog.js';
2
+ export type JudgeSessionFailureMode = 'parse_error' | 'non_json_response' | 'timeout' | 'cli_nonzero_exit' | 'io_error' | 'version_mismatch' | 'unsupported_resume' | 'unsafe_option_rejected';
3
+ export interface JudgeProviderSessionCapability {
4
+ providerId: Exclude<JudgeProviderId, 'ollama'>;
5
+ supportsSession: boolean;
6
+ resumeFlag: string | null;
7
+ createChatFlag: string | null;
8
+ readOnlyArgs: string[];
9
+ rejectedUnsafeOptions: string[];
10
+ failureModes: JudgeSessionFailureMode[];
11
+ notes: string;
12
+ }
13
+ export declare const JUDGE_PROVIDER_SESSION_MATRIX: Record<Exclude<JudgeProviderId, 'ollama'>, JudgeProviderSessionCapability>;
14
+ export declare function providerSupportsSession(providerId: Exclude<JudgeProviderId, 'ollama'>, allowlist: JudgeProviderId[]): boolean;
15
+ export declare function assertReadOnlyInvocationArgs(providerId: Exclude<JudgeProviderId, 'ollama'>, args: string[]): {
16
+ ok: true;
17
+ } | {
18
+ ok: false;
19
+ reason: JudgeSessionFailureMode;
20
+ };
@@ -0,0 +1,66 @@
1
+ export const JUDGE_PROVIDER_SESSION_MATRIX = {
2
+ cursor: {
3
+ providerId: 'cursor',
4
+ supportsSession: true,
5
+ resumeFlag: '--resume',
6
+ createChatFlag: null,
7
+ readOnlyArgs: ['--mode', 'ask', '--sandbox', 'enabled', '--trust'],
8
+ rejectedUnsafeOptions: ['--force', '--yolo', '--dangerously-skip-permissions'],
9
+ failureModes: [
10
+ 'parse_error',
11
+ 'non_json_response',
12
+ 'timeout',
13
+ 'cli_nonzero_exit',
14
+ 'io_error',
15
+ 'version_mismatch',
16
+ ],
17
+ notes: 'Pilot provider: persistent ACP in ask mode; bounded conversation reuse is broker-controlled.',
18
+ },
19
+ codex: {
20
+ providerId: 'codex',
21
+ supportsSession: false,
22
+ resumeFlag: null,
23
+ createChatFlag: null,
24
+ readOnlyArgs: ['--sandbox', 'read-only'],
25
+ rejectedUnsafeOptions: ['--dangerously-bypass-approvals-and-sandbox'],
26
+ failureModes: ['unsupported_resume', 'parse_error', 'timeout'],
27
+ notes: 'Session transport disabled by default; spawn read-only exec remains canonical.',
28
+ },
29
+ claude: {
30
+ providerId: 'claude',
31
+ supportsSession: false,
32
+ resumeFlag: '--continue',
33
+ createChatFlag: null,
34
+ readOnlyArgs: ['--permission-mode', 'plan', '--tools', '', '--bare'],
35
+ rejectedUnsafeOptions: ['--dangerously-skip-permissions'],
36
+ failureModes: ['unsupported_resume', 'parse_error', 'timeout'],
37
+ notes: 'Session transport gated behind allowlist after capability verification.',
38
+ },
39
+ };
40
+ export function providerSupportsSession(providerId, allowlist) {
41
+ const capability = JUDGE_PROVIDER_SESSION_MATRIX[providerId];
42
+ return capability.supportsSession && allowlist.includes(providerId);
43
+ }
44
+ export function assertReadOnlyInvocationArgs(providerId, args) {
45
+ const capability = JUDGE_PROVIDER_SESSION_MATRIX[providerId];
46
+ for (const unsafe of capability.rejectedUnsafeOptions) {
47
+ if (args.includes(unsafe)) {
48
+ return { ok: false, reason: 'unsafe_option_rejected' };
49
+ }
50
+ }
51
+ for (let index = 0; index < capability.readOnlyArgs.length; index += 2) {
52
+ const flag = capability.readOnlyArgs[index];
53
+ const value = capability.readOnlyArgs[index + 1];
54
+ if (!flag) {
55
+ continue;
56
+ }
57
+ const flagIndex = args.indexOf(flag);
58
+ if (flagIndex === -1) {
59
+ return { ok: false, reason: 'unsafe_option_rejected' };
60
+ }
61
+ if (value !== undefined && args[flagIndex + 1] !== value) {
62
+ return { ok: false, reason: 'unsafe_option_rejected' };
63
+ }
64
+ }
65
+ return { ok: true };
66
+ }
@@ -0,0 +1,44 @@
1
+ import type { JudgeProviderId } from './judge-catalog.js';
2
+ export interface BelayJudgeSessionConfig {
3
+ enabled: boolean;
4
+ maxTurns: number;
5
+ maxAgeMs: number;
6
+ maxIdleMs: number;
7
+ maxPromptBytes: number;
8
+ providerAllowlist: JudgeProviderId[];
9
+ connectTimeoutMs: number;
10
+ evalTimeoutMs: number | null;
11
+ parseTimeoutMs: number;
12
+ maxSessionsPerProvider: number;
13
+ }
14
+ export interface BelayJudgeShadowConfig {
15
+ enabled: boolean;
16
+ sampleRate: number;
17
+ sampleRateMax: number;
18
+ dailyRequestCap: number;
19
+ mismatchRateThreshold: number;
20
+ providerAllowlist: JudgeProviderId[];
21
+ windowSize: number;
22
+ }
23
+ export interface BelayJudgeRuntimeConfig {
24
+ session: BelayJudgeSessionConfig;
25
+ shadow: BelayJudgeShadowConfig;
26
+ }
27
+ export declare const JUDGE_LATENCY_SLO: {
28
+ /** Target: Tier1 p95 at least 40% below spawn baseline. */
29
+ readonly tier1P95ReductionTarget: 0.4;
30
+ /** Reference spawn baseline (ms) from dogfood measurement. */
31
+ readonly spawnBaselineP95Ms: 25000;
32
+ readonly spawnBaselineP50Ms: 1300;
33
+ readonly tier0BaselineP95Ms: 60;
34
+ /** Session path must not exceed spawn p95 when enabled. */
35
+ readonly sessionMaxP95Ms: 15000;
36
+ };
37
+ export declare const DEFAULT_JUDGE_SESSION_CONFIG: BelayJudgeSessionConfig;
38
+ export declare const DEFAULT_JUDGE_SHADOW_CONFIG: BelayJudgeShadowConfig;
39
+ export declare const DEFAULT_JUDGE_RUNTIME_CONFIG: BelayJudgeRuntimeConfig;
40
+ export declare function normalizeJudgeRuntimeConfig(runtime: Partial<BelayJudgeRuntimeConfig> | undefined): BelayJudgeRuntimeConfig;
41
+ export declare function normalizeJudgeSessionConfig(session: Partial<BelayJudgeSessionConfig> | undefined): BelayJudgeSessionConfig;
42
+ export declare function resolveSessionEvalTimeoutMs(sessionConfig: BelayJudgeSessionConfig, judgeTimeoutMs: number): number;
43
+ /** Live CLI smoke probes use the configured judge timeout without an artificial cap. */
44
+ export declare function resolveJudgeSmokeProbeTimeoutMs(judgeTimeoutMs: number): number;
@@ -0,0 +1,96 @@
1
+ export const JUDGE_LATENCY_SLO = {
2
+ /** Target: Tier1 p95 at least 40% below spawn baseline. */
3
+ tier1P95ReductionTarget: 0.4,
4
+ /** Reference spawn baseline (ms) from dogfood measurement. */
5
+ spawnBaselineP95Ms: 25_000,
6
+ spawnBaselineP50Ms: 1_300,
7
+ tier0BaselineP95Ms: 60,
8
+ /** Session path must not exceed spawn p95 when enabled. */
9
+ sessionMaxP95Ms: 15_000,
10
+ };
11
+ export const DEFAULT_JUDGE_SESSION_CONFIG = {
12
+ enabled: true,
13
+ maxTurns: 8,
14
+ maxAgeMs: 10 * 60 * 1000,
15
+ maxIdleMs: 2 * 60 * 1000,
16
+ maxPromptBytes: 32 * 1024,
17
+ providerAllowlist: ['cursor'],
18
+ connectTimeoutMs: 5_000,
19
+ evalTimeoutMs: null,
20
+ parseTimeoutMs: 2_000,
21
+ maxSessionsPerProvider: 1,
22
+ };
23
+ export const DEFAULT_JUDGE_SHADOW_CONFIG = {
24
+ enabled: false,
25
+ sampleRate: 0.01,
26
+ sampleRateMax: 0.05,
27
+ dailyRequestCap: 500,
28
+ mismatchRateThreshold: 0.02,
29
+ providerAllowlist: ['cursor'],
30
+ windowSize: 100,
31
+ };
32
+ export const DEFAULT_JUDGE_RUNTIME_CONFIG = {
33
+ session: { ...DEFAULT_JUDGE_SESSION_CONFIG },
34
+ shadow: { ...DEFAULT_JUDGE_SHADOW_CONFIG },
35
+ };
36
+ const CLI_PROVIDER_IDS = ['codex', 'cursor', 'claude'];
37
+ function normalizeProviderAllowlist(value, fallback) {
38
+ if (!Array.isArray(value)) {
39
+ return [...fallback];
40
+ }
41
+ const allowed = value
42
+ .map((entry) => String(entry).trim())
43
+ .filter((entry) => CLI_PROVIDER_IDS.includes(entry));
44
+ return allowed.length > 0 ? [...new Set(allowed)] : [...fallback];
45
+ }
46
+ function normalizePositiveInt(value, fallback) {
47
+ return typeof value === 'number' && Number.isFinite(value) && value > 0
48
+ ? Math.floor(value)
49
+ : fallback;
50
+ }
51
+ function normalizeRate(value, fallback, max) {
52
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
53
+ return fallback;
54
+ }
55
+ return Math.min(value, max);
56
+ }
57
+ export function normalizeJudgeRuntimeConfig(runtime) {
58
+ const session = runtime?.session ?? {};
59
+ const shadow = runtime?.shadow ?? {};
60
+ return {
61
+ session: normalizeJudgeSessionConfig(session),
62
+ shadow: {
63
+ enabled: shadow.enabled === true,
64
+ sampleRate: normalizeRate(shadow.sampleRate, DEFAULT_JUDGE_SHADOW_CONFIG.sampleRate, 1),
65
+ sampleRateMax: normalizeRate(shadow.sampleRateMax, DEFAULT_JUDGE_SHADOW_CONFIG.sampleRateMax, 1),
66
+ dailyRequestCap: normalizePositiveInt(shadow.dailyRequestCap, DEFAULT_JUDGE_SHADOW_CONFIG.dailyRequestCap),
67
+ mismatchRateThreshold: normalizeRate(shadow.mismatchRateThreshold, DEFAULT_JUDGE_SHADOW_CONFIG.mismatchRateThreshold, 1),
68
+ providerAllowlist: normalizeProviderAllowlist(shadow.providerAllowlist, DEFAULT_JUDGE_SHADOW_CONFIG.providerAllowlist),
69
+ windowSize: normalizePositiveInt(shadow.windowSize, DEFAULT_JUDGE_SHADOW_CONFIG.windowSize),
70
+ },
71
+ };
72
+ }
73
+ export function normalizeJudgeSessionConfig(session) {
74
+ const input = session ?? {};
75
+ return {
76
+ enabled: typeof input.enabled === 'boolean' ? input.enabled : DEFAULT_JUDGE_SESSION_CONFIG.enabled,
77
+ maxTurns: normalizePositiveInt(input.maxTurns, DEFAULT_JUDGE_SESSION_CONFIG.maxTurns),
78
+ maxAgeMs: normalizePositiveInt(input.maxAgeMs, DEFAULT_JUDGE_SESSION_CONFIG.maxAgeMs),
79
+ maxIdleMs: normalizePositiveInt(input.maxIdleMs, DEFAULT_JUDGE_SESSION_CONFIG.maxIdleMs),
80
+ maxPromptBytes: normalizePositiveInt(input.maxPromptBytes, DEFAULT_JUDGE_SESSION_CONFIG.maxPromptBytes),
81
+ providerAllowlist: normalizeProviderAllowlist(input.providerAllowlist, DEFAULT_JUDGE_SESSION_CONFIG.providerAllowlist),
82
+ connectTimeoutMs: normalizePositiveInt(input.connectTimeoutMs, DEFAULT_JUDGE_SESSION_CONFIG.connectTimeoutMs),
83
+ evalTimeoutMs: typeof input.evalTimeoutMs === 'number' && input.evalTimeoutMs > 0
84
+ ? Math.floor(input.evalTimeoutMs)
85
+ : null,
86
+ parseTimeoutMs: normalizePositiveInt(input.parseTimeoutMs, DEFAULT_JUDGE_SESSION_CONFIG.parseTimeoutMs),
87
+ maxSessionsPerProvider: normalizePositiveInt(input.maxSessionsPerProvider, DEFAULT_JUDGE_SESSION_CONFIG.maxSessionsPerProvider),
88
+ };
89
+ }
90
+ export function resolveSessionEvalTimeoutMs(sessionConfig, judgeTimeoutMs) {
91
+ return sessionConfig.evalTimeoutMs ?? judgeTimeoutMs;
92
+ }
93
+ /** Live CLI smoke probes use the configured judge timeout without an artificial cap. */
94
+ export function resolveJudgeSmokeProbeTimeoutMs(judgeTimeoutMs) {
95
+ return judgeTimeoutMs;
96
+ }
@@ -0,0 +1,48 @@
1
+ import type { JudgeProviderId } from './judge-catalog.js';
2
+ import type { CliInvocation, CliJudgeRunCommand } from './judge-cli.js';
3
+ import type { BelayJudgeSessionConfig } from './judge-runtime-config.js';
4
+ import { type JudgeSessionBudget, type JudgeSessionKeyParts, type JudgeSessionResetReason } from './judge-session-guard.js';
5
+ export type BrokerSessionState = 'idle' | 'busy';
6
+ export interface BrokerSessionRecord {
7
+ keyParts: JudgeSessionKeyParts;
8
+ budget: JudgeSessionBudget;
9
+ state: BrokerSessionState;
10
+ providerResumeId?: string;
11
+ lastResetReason?: JudgeSessionResetReason;
12
+ }
13
+ export interface BrokerEvaluateRequest {
14
+ keyParts: JudgeSessionKeyParts;
15
+ invocation: CliInvocation;
16
+ promptBytes: number;
17
+ }
18
+ export interface BrokerEvaluateResult {
19
+ raw: string;
20
+ reused: boolean;
21
+ resetReason?: JudgeSessionResetReason;
22
+ providerResumeId?: string;
23
+ }
24
+ type RunCommand = CliJudgeRunCommand;
25
+ export interface JudgeSessionBrokerOptions {
26
+ config: BelayJudgeSessionConfig;
27
+ runCommand: RunCommand;
28
+ extractResumeId?: (providerId: Exclude<JudgeProviderId, 'ollama'>, raw: string) => string | null;
29
+ }
30
+ export declare class JudgeSessionBroker {
31
+ private readonly sessions;
32
+ private readonly providerMutexes;
33
+ private readonly options;
34
+ constructor(options: JudgeSessionBrokerOptions);
35
+ listSessions(): BrokerSessionRecord[];
36
+ invalidateSession(sessionKey: string, reason: JudgeSessionResetReason): void;
37
+ stopAll(reason?: JudgeSessionResetReason): number;
38
+ evaluate(request: BrokerEvaluateRequest, timeoutMs: number): Promise<BrokerEvaluateResult>;
39
+ private evaluateExclusive;
40
+ private enforceProviderSessionCap;
41
+ private buildResumeInvocation;
42
+ }
43
+ export declare function extractProviderResumeId(providerId: Exclude<JudgeProviderId, 'ollama'>, raw: string): string | null;
44
+ export declare function getRepoJudgeSessionBroker(repoRoot: string, options: JudgeSessionBrokerOptions): JudgeSessionBroker;
45
+ export declare function stopRepoJudgeSessionBroker(repoRoot: string, reason?: JudgeSessionResetReason): number;
46
+ export declare function resetJudgeSessionBrokersForTests(): void;
47
+ export declare function listRepoJudgeSessionBrokers(): string[];
48
+ export {};
@@ -0,0 +1,200 @@
1
+ import { buildJudgeSessionKey, evaluateSessionReuse, } from './judge-session-guard.js';
2
+ import { MutexRegistry } from './judge-session-mutex.js';
3
+ export class JudgeSessionBroker {
4
+ sessions = new Map();
5
+ providerMutexes = new MutexRegistry();
6
+ options;
7
+ constructor(options) {
8
+ this.options = options;
9
+ }
10
+ listSessions() {
11
+ return [...this.sessions.values()];
12
+ }
13
+ invalidateSession(sessionKey, reason) {
14
+ const session = this.sessions.get(sessionKey);
15
+ if (session) {
16
+ session.lastResetReason = reason;
17
+ session.state = 'idle';
18
+ }
19
+ this.sessions.delete(sessionKey);
20
+ }
21
+ stopAll(reason = 'manual_stop') {
22
+ const count = this.sessions.size;
23
+ for (const session of this.sessions.values()) {
24
+ session.lastResetReason = reason;
25
+ session.state = 'idle';
26
+ }
27
+ this.sessions.clear();
28
+ this.providerMutexes.clear();
29
+ return count;
30
+ }
31
+ async evaluate(request, timeoutMs) {
32
+ // One persistent provider process may back several logical session keys. Serialize at the
33
+ // provider boundary so a key rotation cannot close a conversation that is still evaluating.
34
+ return this.providerMutexes
35
+ .forKey(request.keyParts.providerId)
36
+ .run(() => this.evaluateExclusive(request, timeoutMs));
37
+ }
38
+ async evaluateExclusive(request, timeoutMs) {
39
+ const sessionKey = buildJudgeSessionKey(request.keyParts);
40
+ const providerId = request.keyParts.providerId;
41
+ const existing = this.sessions.get(sessionKey);
42
+ const reuseDecision = evaluateSessionReuse(existing?.keyParts ?? null, request.keyParts, existing?.budget ?? null, this.options.config, Date.now(), request.promptBytes);
43
+ const resetReason = reuseDecision.canReuse ? undefined : reuseDecision.resetReason;
44
+ let session = existing;
45
+ if (!reuseDecision.canReuse || !session) {
46
+ session = {
47
+ keyParts: request.keyParts,
48
+ budget: {
49
+ turnCount: 0,
50
+ createdAtMs: Date.now(),
51
+ lastUsedAtMs: Date.now(),
52
+ promptBytes: 0,
53
+ },
54
+ state: 'busy',
55
+ lastResetReason: resetReason ?? 'initial',
56
+ };
57
+ this.enforceProviderSessionCap(providerId, sessionKey);
58
+ this.sessions.set(sessionKey, session);
59
+ }
60
+ else {
61
+ session.state = 'busy';
62
+ session.budget.lastUsedAtMs = Date.now();
63
+ }
64
+ const invocation = reuseDecision.canReuse && session.providerResumeId
65
+ ? this.buildResumeInvocation(request.invocation, providerId, session.providerResumeId)
66
+ : request.invocation;
67
+ const resumed = reuseDecision.canReuse && Boolean(session.providerResumeId);
68
+ const appliedResetReason = session.lastResetReason;
69
+ try {
70
+ const raw = await this.options.runCommand(invocation, timeoutMs);
71
+ session.budget.turnCount += 1;
72
+ session.budget.promptBytes += request.promptBytes;
73
+ session.budget.lastUsedAtMs = Date.now();
74
+ session.state = 'idle';
75
+ session.lastResetReason = undefined;
76
+ const extracted = this.options.extractResumeId?.(providerId, raw) ??
77
+ extractProviderResumeId(providerId, raw) ??
78
+ undefined;
79
+ if (extracted) {
80
+ session.providerResumeId = extracted;
81
+ }
82
+ return {
83
+ raw,
84
+ reused: resumed,
85
+ resetReason: appliedResetReason,
86
+ providerResumeId: session.providerResumeId,
87
+ };
88
+ }
89
+ catch (error) {
90
+ session.state = 'idle';
91
+ session.lastResetReason = 'cli_error';
92
+ this.sessions.delete(sessionKey);
93
+ throw error;
94
+ }
95
+ }
96
+ enforceProviderSessionCap(providerId, keepKey) {
97
+ const cap = this.options.config.maxSessionsPerProvider;
98
+ const sameProvider = [...this.sessions.entries()].filter(([, record]) => record.keyParts.providerId === providerId);
99
+ if (sameProvider.length < cap) {
100
+ return;
101
+ }
102
+ for (const [key] of sameProvider) {
103
+ if (key === keepKey) {
104
+ continue;
105
+ }
106
+ this.sessions.delete(key);
107
+ if (this.sessions.size < cap) {
108
+ break;
109
+ }
110
+ }
111
+ }
112
+ buildResumeInvocation(base, providerId, resumeId) {
113
+ if (providerId === 'cursor') {
114
+ const args = [...base.args];
115
+ const modelIndex = args.indexOf('--model');
116
+ const model = modelIndex >= 0 ? args[modelIndex + 1] : undefined;
117
+ const prompt = args[args.length - 1];
118
+ return {
119
+ binary: base.binary,
120
+ args: [
121
+ '--print',
122
+ '--output-format',
123
+ 'json',
124
+ '--mode',
125
+ 'ask',
126
+ '--sandbox',
127
+ 'enabled',
128
+ '--trust',
129
+ '--resume',
130
+ resumeId,
131
+ ...(model ? ['--model', model] : []),
132
+ prompt,
133
+ ],
134
+ };
135
+ }
136
+ if (providerId === 'claude') {
137
+ return {
138
+ ...base,
139
+ args: [...base.args, '--continue', resumeId],
140
+ };
141
+ }
142
+ return base;
143
+ }
144
+ }
145
+ export function extractProviderResumeId(providerId, raw) {
146
+ try {
147
+ const parsed = JSON.parse(raw);
148
+ const candidates = [
149
+ parsed.chat_id,
150
+ parsed.chatId,
151
+ parsed.session_id,
152
+ parsed.sessionId,
153
+ parsed.result?.chat_id,
154
+ parsed.result?.session_id,
155
+ ];
156
+ for (const candidate of candidates) {
157
+ if (typeof candidate === 'string' && candidate.trim()) {
158
+ return candidate.trim();
159
+ }
160
+ }
161
+ }
162
+ catch {
163
+ // fall through
164
+ }
165
+ if (providerId === 'cursor') {
166
+ const match = raw.match(/"chat_id"\s*:\s*"([^"]+)"/);
167
+ if (match?.[1]) {
168
+ return match[1];
169
+ }
170
+ }
171
+ return null;
172
+ }
173
+ const repoBrokers = new Map();
174
+ export function getRepoJudgeSessionBroker(repoRoot, options) {
175
+ const existing = repoBrokers.get(repoRoot);
176
+ if (existing) {
177
+ return existing;
178
+ }
179
+ const broker = new JudgeSessionBroker(options);
180
+ repoBrokers.set(repoRoot, broker);
181
+ return broker;
182
+ }
183
+ export function stopRepoJudgeSessionBroker(repoRoot, reason = 'manual_stop') {
184
+ const broker = repoBrokers.get(repoRoot);
185
+ if (!broker) {
186
+ return 0;
187
+ }
188
+ const count = broker.stopAll(reason);
189
+ repoBrokers.delete(repoRoot);
190
+ return count;
191
+ }
192
+ export function resetJudgeSessionBrokersForTests() {
193
+ for (const broker of repoBrokers.values()) {
194
+ broker.stopAll('manual_stop');
195
+ }
196
+ repoBrokers.clear();
197
+ }
198
+ export function listRepoJudgeSessionBrokers() {
199
+ return [...repoBrokers.keys()];
200
+ }
@@ -0,0 +1,27 @@
1
+ import type { JudgeProviderId } from './judge-catalog.js';
2
+ import type { BelayJudgeSessionConfig } from './judge-runtime-config.js';
3
+ export type JudgeSessionResetReason = 'provider_mismatch' | 'model_mismatch' | 'repo_mismatch' | 'mode_mismatch' | 'cli_version_mismatch' | 'max_turns_exceeded' | 'max_age_exceeded' | 'max_idle_exceeded' | 'max_prompt_bytes_exceeded' | 'parse_failure' | 'non_json_response' | 'cli_error' | 'timeout' | 'kill_switch' | 'manual_stop' | 'budget_exhausted' | 'session_disabled' | 'provider_not_allowlisted' | 'initial';
4
+ export type JudgeFallbackReason = 'session_disabled' | 'provider_not_allowlisted' | 'session_unavailable' | 'guard_reset' | 'parse_error' | 'non_json_response' | 'cli_error' | 'timeout' | 'connect_timeout' | 'eval_timeout' | 'parse_timeout' | 'version_mismatch' | 'kill_switch' | 'shadow_forced_spawn' | 'unsafe_option_rejected' | 'broker_busy_reset' | `${Exclude<JudgeProviderId, 'ollama'>}_cli_spawn_error` | `${Exclude<JudgeProviderId, 'ollama'>}_cli_nonzero` | `${Exclude<JudgeProviderId, 'ollama'>}_cli_unavailable` | `${Exclude<JudgeProviderId, 'ollama'>}_cli_parse_error`;
5
+ export interface JudgeSessionKeyParts {
6
+ providerId: JudgeProviderId;
7
+ model: string;
8
+ repoRoot: string;
9
+ judgeMode: string;
10
+ cliVersion: string;
11
+ }
12
+ export interface JudgeSessionBudget {
13
+ turnCount: number;
14
+ createdAtMs: number;
15
+ lastUsedAtMs: number;
16
+ promptBytes: number;
17
+ }
18
+ export interface JudgeSessionGuardDecision {
19
+ canReuse: boolean;
20
+ resetReason?: JudgeSessionResetReason;
21
+ sessionKey: string;
22
+ }
23
+ export declare function buildJudgeSessionKey(parts: JudgeSessionKeyParts): string;
24
+ export declare function evaluateSessionReuse(existing: JudgeSessionKeyParts | null, requested: JudgeSessionKeyParts, budget: JudgeSessionBudget | null, config: BelayJudgeSessionConfig, now?: number, incomingPromptBytes?: number): JudgeSessionGuardDecision;
25
+ export declare function exceedsPromptBudget(promptBytes: number, config: BelayJudgeSessionConfig, existingBudget?: JudgeSessionBudget | null): boolean;
26
+ export declare function guardFailClosedFallbackReason(resetReason: JudgeSessionResetReason): JudgeFallbackReason;
27
+ export declare function transportFallbackToFailClosedReason(providerId: Exclude<JudgeProviderId, 'ollama'>, fallback: JudgeFallbackReason | undefined): string;
@@ -0,0 +1,91 @@
1
+ export function buildJudgeSessionKey(parts) {
2
+ return [parts.providerId, parts.model, parts.repoRoot, parts.judgeMode, parts.cliVersion].join(':');
3
+ }
4
+ export function evaluateSessionReuse(existing, requested, budget, config, now = Date.now(), incomingPromptBytes = 0) {
5
+ const sessionKey = buildJudgeSessionKey(requested);
6
+ if (!config.enabled) {
7
+ return { canReuse: false, resetReason: 'session_disabled', sessionKey };
8
+ }
9
+ if (!config.providerAllowlist.includes(requested.providerId)) {
10
+ return { canReuse: false, resetReason: 'provider_not_allowlisted', sessionKey };
11
+ }
12
+ if (!existing || !budget) {
13
+ return { canReuse: false, resetReason: 'initial', sessionKey };
14
+ }
15
+ const existingKey = buildJudgeSessionKey(existing);
16
+ if (existingKey !== sessionKey) {
17
+ if (existing.providerId !== requested.providerId) {
18
+ return { canReuse: false, resetReason: 'provider_mismatch', sessionKey };
19
+ }
20
+ if (existing.model !== requested.model) {
21
+ return { canReuse: false, resetReason: 'model_mismatch', sessionKey };
22
+ }
23
+ if (existing.repoRoot !== requested.repoRoot) {
24
+ return { canReuse: false, resetReason: 'repo_mismatch', sessionKey };
25
+ }
26
+ if (existing.judgeMode !== requested.judgeMode) {
27
+ return { canReuse: false, resetReason: 'mode_mismatch', sessionKey };
28
+ }
29
+ if (existing.cliVersion !== requested.cliVersion) {
30
+ return { canReuse: false, resetReason: 'cli_version_mismatch', sessionKey };
31
+ }
32
+ return { canReuse: false, resetReason: 'provider_mismatch', sessionKey };
33
+ }
34
+ if (budget.turnCount >= config.maxTurns) {
35
+ return { canReuse: false, resetReason: 'max_turns_exceeded', sessionKey };
36
+ }
37
+ if (now - budget.createdAtMs >= config.maxAgeMs) {
38
+ return { canReuse: false, resetReason: 'max_age_exceeded', sessionKey };
39
+ }
40
+ if (now - budget.lastUsedAtMs >= config.maxIdleMs) {
41
+ return { canReuse: false, resetReason: 'max_idle_exceeded', sessionKey };
42
+ }
43
+ if (budget.promptBytes + incomingPromptBytes > config.maxPromptBytes) {
44
+ return { canReuse: false, resetReason: 'max_prompt_bytes_exceeded', sessionKey };
45
+ }
46
+ return { canReuse: true, sessionKey };
47
+ }
48
+ export function exceedsPromptBudget(promptBytes, config, existingBudget = null) {
49
+ const total = (existingBudget?.promptBytes ?? 0) + promptBytes;
50
+ return total > config.maxPromptBytes;
51
+ }
52
+ export function guardFailClosedFallbackReason(resetReason) {
53
+ switch (resetReason) {
54
+ case 'parse_failure':
55
+ return 'parse_error';
56
+ case 'non_json_response':
57
+ return 'non_json_response';
58
+ case 'cli_error':
59
+ return 'cli_error';
60
+ case 'timeout':
61
+ return 'timeout';
62
+ case 'cli_version_mismatch':
63
+ return 'version_mismatch';
64
+ case 'kill_switch':
65
+ return 'kill_switch';
66
+ case 'session_disabled':
67
+ return 'session_disabled';
68
+ case 'provider_not_allowlisted':
69
+ return 'provider_not_allowlisted';
70
+ case 'max_prompt_bytes_exceeded':
71
+ return 'guard_reset';
72
+ default:
73
+ return 'guard_reset';
74
+ }
75
+ }
76
+ export function transportFallbackToFailClosedReason(providerId, fallback) {
77
+ if (!fallback) {
78
+ return `${providerId}_cli_parse_error`;
79
+ }
80
+ if (fallback.endsWith('_cli_unavailable') || fallback.endsWith('_cli_parse_error')) {
81
+ return fallback;
82
+ }
83
+ switch (fallback) {
84
+ case 'non_json_response':
85
+ case 'parse_error':
86
+ case 'parse_timeout':
87
+ return `${providerId}_cli_parse_error`;
88
+ default:
89
+ return `${providerId}_cli_unavailable`;
90
+ }
91
+ }
@@ -0,0 +1,11 @@
1
+ export declare const JUDGE_SESSION_KILL_FILE = "judge-session-kill.json";
2
+ export interface JudgeSessionKillSwitchRecord {
3
+ triggered: boolean;
4
+ at: string;
5
+ reason?: string;
6
+ }
7
+ export declare function judgeSessionKillSwitchPath(stateDir: string): string;
8
+ export declare function readJudgeSessionKillSwitch(stateDir: string): Promise<JudgeSessionKillSwitchRecord | null>;
9
+ export declare function isJudgeSessionKillSwitchPersisted(stateDir: string): Promise<boolean>;
10
+ export declare function persistJudgeSessionKillSwitch(stateDir: string, reason?: string): Promise<void>;
11
+ export declare function clearJudgeSessionKillSwitch(stateDir: string): Promise<void>;