@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.
- package/README.md +7 -1
- package/dist/adapters/cursor/runtime-entry.js +1 -0
- package/dist/adapters/layouts/scope.js +3 -2
- package/dist/adapters/shared/gate-runtime.d.ts +12 -16
- package/dist/adapters/shared/gate-runtime.js +499 -43
- package/dist/bundle/claude-runtime.mjs +8004 -2676
- package/dist/bundle/codex-runtime.mjs +8021 -2683
- package/dist/bundle/cursor-runtime.mjs +7993 -2667
- package/dist/cli.js +180 -6
- package/dist/commands/approve.d.ts +1 -0
- package/dist/commands/approve.js +45 -26
- package/dist/commands/classify-for-report.js +1 -1
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +264 -58
- package/dist/commands/doctor.js +47 -2
- package/dist/commands/explain.js +17 -2
- package/dist/commands/harvest.d.ts +29 -0
- package/dist/commands/harvest.js +72 -0
- package/dist/commands/judge.d.ts +11 -1
- package/dist/commands/judge.js +24 -4
- 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/session.d.ts +20 -0
- package/dist/commands/session.js +51 -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/tui.d.ts +48 -0
- package/dist/commands/tui.js +150 -0
- package/dist/config-io.js +59 -17
- 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-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 +53 -4
- package/dist/core/approval.d.ts +26 -3
- package/dist/core/approval.js +61 -7
- package/dist/core/audit-analysis.d.ts +7 -1
- package/dist/core/audit-analysis.js +111 -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/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 +10 -1
- package/dist/core/capability/index.js +8 -0
- package/dist/core/capability/limits.d.ts +7 -0
- package/dist/core/capability/limits.js +55 -0
- package/dist/core/capability/paths.d.ts +2 -2
- package/dist/core/capability/paths.js +9 -9
- 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/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.d.ts +2 -1
- package/dist/core/classify-subagent.js +40 -22
- package/dist/core/classify-tool.js +157 -64
- package/dist/core/config.d.ts +32 -3
- package/dist/core/config.js +105 -3
- package/dist/core/gate-contract.d.ts +3 -0
- package/dist/core/gate-contract.js +3 -0
- package/dist/core/gate-engine.js +12 -7
- 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/judge-doctor.d.ts +3 -0
- package/dist/core/judge-doctor.js +90 -11
- 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 +12 -0
- package/dist/core/path-utils.js +60 -7
- package/dist/core/reclassify.d.ts +3 -0
- package/dist/core/reclassify.js +44 -14
- 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/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 +178 -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 +36 -3
- package/dist/core/verdict/adapter.d.ts +3 -4
- package/dist/core/verdict/adapter.js +19 -6
- package/dist/core/verdict/containment.d.ts +4 -3
- package/dist/core/verdict/containment.js +17 -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.d.ts +1 -0
- package/dist/core/verdict/judge-audit.js +33 -1
- package/dist/core/verdict/judge-baseline.d.ts +20 -0
- package/dist/core/verdict/judge-baseline.js +49 -0
- package/dist/core/verdict/judge-broker-service.d.ts +42 -0
- package/dist/core/verdict/judge-broker-service.js +291 -0
- package/dist/core/verdict/judge-catalog.js +1 -0
- package/dist/core/verdict/judge-cli-fingerprint.d.ts +6 -0
- package/dist/core/verdict/judge-cli-fingerprint.js +67 -0
- package/dist/core/verdict/judge-cli.d.ts +23 -0
- package/dist/core/verdict/judge-cli.js +139 -16
- package/dist/core/verdict/judge-factory.js +13 -4
- 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 +44 -0
- package/dist/core/verdict/judge-runtime-config.js +96 -0
- package/dist/core/verdict/judge-session-broker.d.ts +48 -0
- package/dist/core/verdict/judge-session-broker.js +200 -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 +44 -0
- package/dist/core/verdict/judge-transport.js +268 -0
- package/dist/core/verdict/judge.d.ts +14 -14
- package/dist/core/verdict/judge.js +5 -67
- package/dist/core/verdict/launcher-resolve.d.ts +2 -0
- package/dist/core/verdict/launcher-resolve.js +16 -0
- package/dist/core/verdict/parser.js +6 -1
- 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 +26 -1
- package/dist/core/verdict/verdict.js +300 -179
- 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 +61 -8
- package/dist/corpus/gate-latency-budget.d.ts +39 -0
- package/dist/corpus/gate-latency-budget.js +33 -0
- 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 +162 -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 +43 -0
- package/dist/corpus/standing-allow-catalog.generated.d.ts +13 -0
- package/dist/corpus/standing-allow-catalog.generated.js +99 -0
- package/dist/corpus/types.d.ts +47 -0
- package/dist/corpus/types.js +157 -0
- package/dist/installer.js +4 -1
- package/dist/judge-broker-daemon.d.ts +1 -0
- package/dist/judge-broker-daemon.js +137 -0
- package/dist/services/sandbox-service.d.ts +5 -0
- package/dist/services/sandbox-service.js +30 -2
- package/dist/types.d.ts +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -3
- package/skills/belay/SKILL.md +7 -1
- package/skills/belay/belay-approve.md +17 -0
package/dist/core/gate-engine.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { allPathsAllowlisted } from './capability/allowlist.js';
|
|
2
|
+
import { checkGatedActionLimits } from './capability/limits.js';
|
|
2
3
|
import { collectOutsideRepoPaths, collectOutsideRepoPathsFromToolPayload, } from './capability/paths.js';
|
|
3
4
|
import { classifySubagent } from './classify-subagent.js';
|
|
4
5
|
import { classifyToolUse } from './classify-tool.js';
|
|
@@ -167,28 +168,28 @@ function applyFsScopePeripheralPolicy(outsideRepoPaths, result, options) {
|
|
|
167
168
|
return result;
|
|
168
169
|
}
|
|
169
170
|
function applySandboxOutsideBoundary(command, action, result, options) {
|
|
170
|
-
const outsideRepoPaths = collectOutsideRepoPaths(command, action.cwd, action.repoRoot);
|
|
171
|
+
const outsideRepoPaths = collectOutsideRepoPaths(command, action.cwd, action.repoRoot, options.trustedWorkspaceRoots);
|
|
171
172
|
return applySandboxFsScopeBoundary(outsideRepoPaths, result, options, {
|
|
172
173
|
redirect: command.includes('>'),
|
|
173
174
|
});
|
|
174
175
|
}
|
|
175
176
|
function applyShellPeripheralPolicy(command, action, result, options) {
|
|
176
|
-
const outsideRepoPaths = collectOutsideRepoPaths(command, action.cwd, action.repoRoot);
|
|
177
|
+
const outsideRepoPaths = collectOutsideRepoPaths(command, action.cwd, action.repoRoot, options.trustedWorkspaceRoots);
|
|
177
178
|
return applyFsScopePeripheralPolicy(outsideRepoPaths, result, options);
|
|
178
179
|
}
|
|
179
|
-
function outsideRepoPathsForToolAction(action) {
|
|
180
|
+
function outsideRepoPathsForToolAction(action, options) {
|
|
180
181
|
const payload = action.payload ?? {};
|
|
181
|
-
const paths = new Set(collectOutsideRepoPathsFromToolPayload(payload, action.cwd, action.repoRoot));
|
|
182
|
+
const paths = new Set(collectOutsideRepoPathsFromToolPayload(payload, action.cwd, action.repoRoot, options.trustedWorkspaceRoots));
|
|
182
183
|
const command = shellCommandFromPayload(payload);
|
|
183
184
|
if (command) {
|
|
184
|
-
for (const resolved of collectOutsideRepoPaths(command, action.cwd, action.repoRoot)) {
|
|
185
|
+
for (const resolved of collectOutsideRepoPaths(command, action.cwd, action.repoRoot, options.trustedWorkspaceRoots)) {
|
|
185
186
|
paths.add(resolved);
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
return [...paths];
|
|
189
190
|
}
|
|
190
191
|
function applyToolSandboxPolicies(action, result, options) {
|
|
191
|
-
const outsideRepoPaths = outsideRepoPathsForToolAction(action);
|
|
192
|
+
const outsideRepoPaths = outsideRepoPathsForToolAction(action, options);
|
|
192
193
|
const command = shellCommandFromPayload(action.payload ?? {});
|
|
193
194
|
let next = applySandboxFsScopeBoundary(outsideRepoPaths, result, options, {
|
|
194
195
|
redirect: command.includes('>'),
|
|
@@ -197,6 +198,10 @@ function applyToolSandboxPolicies(action, result, options) {
|
|
|
197
198
|
return next;
|
|
198
199
|
}
|
|
199
200
|
export async function classifyGatedAction(action, config, extraOptions = {}) {
|
|
201
|
+
const limitResult = checkGatedActionLimits(action);
|
|
202
|
+
if (limitResult) {
|
|
203
|
+
return limitResult;
|
|
204
|
+
}
|
|
200
205
|
const options = { ...classifierOptionsFromConfig(config), ...extraOptions };
|
|
201
206
|
if (action.kind === 'shell') {
|
|
202
207
|
const command = action.command ?? shellCommandFromPayload(action.payload ?? {});
|
|
@@ -221,7 +226,7 @@ export async function classifyGatedAction(action, config, extraOptions = {}) {
|
|
|
221
226
|
};
|
|
222
227
|
}
|
|
223
228
|
if (action.kind === 'subagent') {
|
|
224
|
-
return classifySubagent(action.payload ?? {}, action.repoRoot, options);
|
|
229
|
+
return classifySubagent(action.payload ?? {}, action.repoRoot, options, config);
|
|
225
230
|
}
|
|
226
231
|
let result = await classifyToolUse(action.payload ?? {}, action.repoRoot, action.cwd, config, options);
|
|
227
232
|
result = applyToolSandboxPolicies(action, result, options);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { CorpusCase } from '../corpus/types.js';
|
|
2
|
+
import type { AuditRecord } from './audit-types.js';
|
|
3
|
+
export declare const HARVEST_REPORT_SCHEMA_VERSION = 1;
|
|
4
|
+
export type HarvestCandidateSource = 'deny_then_approve' | 'repeated_ask' | 'read_style_signal' | 'overrides_allow';
|
|
5
|
+
export type HarvestReviewOutcome = 'provably-benign' | 'accepted-benign' | 'reject';
|
|
6
|
+
export interface HarvestCandidate {
|
|
7
|
+
kind: 'shell';
|
|
8
|
+
command: string;
|
|
9
|
+
fingerprint: string;
|
|
10
|
+
reason: string;
|
|
11
|
+
sources: HarvestCandidateSource[];
|
|
12
|
+
askCount: number;
|
|
13
|
+
approvedAfterDeny: boolean;
|
|
14
|
+
}
|
|
15
|
+
export type AvailabilitySignal = 'missing_trusted_cwd' | 'judge_timeout' | 'judge_fallback';
|
|
16
|
+
export interface AvailabilityQueueItem {
|
|
17
|
+
kind: 'shell';
|
|
18
|
+
command: string;
|
|
19
|
+
fingerprint: string;
|
|
20
|
+
reason: string;
|
|
21
|
+
availabilitySignal: AvailabilitySignal;
|
|
22
|
+
judgeFallbackReason?: string;
|
|
23
|
+
askCount: number;
|
|
24
|
+
}
|
|
25
|
+
export interface HarvestReport {
|
|
26
|
+
schemaVersion: typeof HARVEST_REPORT_SCHEMA_VERSION;
|
|
27
|
+
/** Initial harvest scope — shell audit traces only. */
|
|
28
|
+
scope: 'shell';
|
|
29
|
+
candidates: HarvestCandidate[];
|
|
30
|
+
availabilityQueue: AvailabilityQueueItem[];
|
|
31
|
+
}
|
|
32
|
+
export declare function extractAvailabilityQueue(records: AuditRecord[]): AvailabilityQueueItem[];
|
|
33
|
+
/** Shell gate rows plus approval events — matches what `belay harvest list` needs. */
|
|
34
|
+
export declare function filterRecordsForHarvest(records: AuditRecord[], options?: {
|
|
35
|
+
since?: string;
|
|
36
|
+
until?: string;
|
|
37
|
+
}): AuditRecord[];
|
|
38
|
+
export declare function extractHarvestCandidates(records: AuditRecord[], options?: {
|
|
39
|
+
allowPatterns?: string[];
|
|
40
|
+
}): HarvestCandidate[];
|
|
41
|
+
export declare function buildHarvestReport(records: AuditRecord[], options?: {
|
|
42
|
+
allowPatterns?: string[];
|
|
43
|
+
}): HarvestReport;
|
|
44
|
+
export declare function applyHarvestReview(cases: CorpusCase[], params: {
|
|
45
|
+
command: string;
|
|
46
|
+
outcome: HarvestReviewOutcome;
|
|
47
|
+
reason?: string;
|
|
48
|
+
}): {
|
|
49
|
+
cases: CorpusCase[];
|
|
50
|
+
applied: boolean;
|
|
51
|
+
ok: boolean;
|
|
52
|
+
message: string;
|
|
53
|
+
};
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { computeRepeatedFingerprintAsks, isAvailabilityCausedAsk } from './audit-analysis.js';
|
|
2
|
+
import { buildApprovalRoundTrips, filterAuditRecords, inferWouldBlock, isApprovalRecorded, isShellGateRecord, parseTimestamp, } from './audit-query.js';
|
|
3
|
+
import { matchesCustomCommand } from './custom-command-match.js';
|
|
4
|
+
export const HARVEST_REPORT_SCHEMA_VERSION = 1;
|
|
5
|
+
const READ_STYLE_COMMAND_PATTERN = /^\s*(git\s+status|git\s+diff|git\s+log|git\s+show|ls\b|cat\b|head\b|tail\b|find\b|rg\b|grep\b|fd\b|bat\b|less\b|more\b|pwd\b|whoami\b|env\b|printenv\b|npm\s+(test|run\s+test)|pnpm\s+(test|run\s+test)|yarn\s+test)/;
|
|
6
|
+
function shellRecords(records) {
|
|
7
|
+
return records.filter(isShellGateRecord);
|
|
8
|
+
}
|
|
9
|
+
function availabilitySignal(record) {
|
|
10
|
+
if (!isAvailabilityCausedAsk(record)) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
if (record.reason === 'missing_trusted_cwd') {
|
|
14
|
+
return 'missing_trusted_cwd';
|
|
15
|
+
}
|
|
16
|
+
const fallback = typeof record.judgeFallbackReason === 'string' ? record.judgeFallbackReason : '';
|
|
17
|
+
if (fallback.includes('timeout')) {
|
|
18
|
+
return 'judge_timeout';
|
|
19
|
+
}
|
|
20
|
+
return 'judge_fallback';
|
|
21
|
+
}
|
|
22
|
+
function commandFromRecord(record) {
|
|
23
|
+
return (record.summary ?? '').trim();
|
|
24
|
+
}
|
|
25
|
+
function hasReadStyleSignal(summary) {
|
|
26
|
+
return READ_STYLE_COMMAND_PATTERN.test(summary);
|
|
27
|
+
}
|
|
28
|
+
function upsertCandidate(map, params) {
|
|
29
|
+
const existing = map.get(params.fingerprint);
|
|
30
|
+
if (existing) {
|
|
31
|
+
if (!existing.sources.includes(params.source)) {
|
|
32
|
+
existing.sources.push(params.source);
|
|
33
|
+
}
|
|
34
|
+
existing.askCount = Math.max(existing.askCount, params.askCount ?? 1);
|
|
35
|
+
existing.approvedAfterDeny = existing.approvedAfterDeny || Boolean(params.approvedAfterDeny);
|
|
36
|
+
if (params.command) {
|
|
37
|
+
existing.command = params.command;
|
|
38
|
+
}
|
|
39
|
+
if (params.reason) {
|
|
40
|
+
existing.reason = params.reason;
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
map.set(params.fingerprint, {
|
|
45
|
+
kind: 'shell',
|
|
46
|
+
command: params.command,
|
|
47
|
+
fingerprint: params.fingerprint,
|
|
48
|
+
reason: params.reason,
|
|
49
|
+
sources: [params.source],
|
|
50
|
+
askCount: params.askCount ?? 1,
|
|
51
|
+
approvedAfterDeny: Boolean(params.approvedAfterDeny),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export function extractAvailabilityQueue(records) {
|
|
55
|
+
const grouped = new Map();
|
|
56
|
+
for (const record of shellRecords(records)) {
|
|
57
|
+
if (!inferWouldBlock(record) || !record.fingerprint) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const signal = availabilitySignal(record);
|
|
61
|
+
if (!signal) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const fingerprint = record.fingerprint;
|
|
65
|
+
const existing = grouped.get(fingerprint);
|
|
66
|
+
if (existing) {
|
|
67
|
+
existing.askCount += 1;
|
|
68
|
+
existing.command = commandFromRecord(record) || existing.command;
|
|
69
|
+
existing.reason = record.reason ?? existing.reason;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
grouped.set(fingerprint, {
|
|
73
|
+
kind: 'shell',
|
|
74
|
+
command: commandFromRecord(record),
|
|
75
|
+
fingerprint,
|
|
76
|
+
reason: record.reason ?? 'unknown',
|
|
77
|
+
availabilitySignal: signal,
|
|
78
|
+
...(typeof record.judgeFallbackReason === 'string'
|
|
79
|
+
? { judgeFallbackReason: record.judgeFallbackReason }
|
|
80
|
+
: {}),
|
|
81
|
+
askCount: 1,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return [...grouped.values()].sort((left, right) => right.askCount - left.askCount);
|
|
85
|
+
}
|
|
86
|
+
function recordsForShellRoundTrips(records) {
|
|
87
|
+
return records.filter((record) => isApprovalRecorded(record) ||
|
|
88
|
+
(isShellGateRecord(record) && inferWouldBlock(record) && !isAvailabilityCausedAsk(record)));
|
|
89
|
+
}
|
|
90
|
+
/** Pair deny/approval rows split by a time window so round-trip harvest still works. */
|
|
91
|
+
function augmentHarvestRoundTripPairs(scoped, timeFiltered) {
|
|
92
|
+
if (timeFiltered.length === 0) {
|
|
93
|
+
return timeFiltered;
|
|
94
|
+
}
|
|
95
|
+
const included = new Set(timeFiltered);
|
|
96
|
+
const augmented = [...timeFiltered];
|
|
97
|
+
const approvalById = new Map();
|
|
98
|
+
const denyByApprovalId = new Map();
|
|
99
|
+
for (const record of scoped) {
|
|
100
|
+
if (!record.approvalId) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (isApprovalRecorded(record)) {
|
|
104
|
+
approvalById.set(record.approvalId, record);
|
|
105
|
+
}
|
|
106
|
+
else if (isShellGateRecord(record) && inferWouldBlock(record)) {
|
|
107
|
+
denyByApprovalId.set(record.approvalId, record);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const record of timeFiltered) {
|
|
111
|
+
if (!record.approvalId) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (isApprovalRecorded(record)) {
|
|
115
|
+
const deny = denyByApprovalId.get(record.approvalId);
|
|
116
|
+
if (deny && !included.has(deny)) {
|
|
117
|
+
included.add(deny);
|
|
118
|
+
augmented.push(deny);
|
|
119
|
+
}
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (isShellGateRecord(record)) {
|
|
123
|
+
const approval = approvalById.get(record.approvalId);
|
|
124
|
+
if (approval && !included.has(approval)) {
|
|
125
|
+
included.add(approval);
|
|
126
|
+
augmented.push(approval);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return augmented.sort((left, right) => {
|
|
131
|
+
const leftMs = parseTimestamp(left.timestamp) ?? 0;
|
|
132
|
+
const rightMs = parseTimestamp(right.timestamp) ?? 0;
|
|
133
|
+
return leftMs - rightMs;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/** Shell gate rows plus approval events — matches what `belay harvest list` needs. */
|
|
137
|
+
export function filterRecordsForHarvest(records, options = {}) {
|
|
138
|
+
const scoped = records.filter((record) => isShellGateRecord(record) || isApprovalRecorded(record));
|
|
139
|
+
if (!options.since && !options.until) {
|
|
140
|
+
return scoped;
|
|
141
|
+
}
|
|
142
|
+
const timeFiltered = filterAuditRecords(scoped, {
|
|
143
|
+
since: options.since,
|
|
144
|
+
until: options.until,
|
|
145
|
+
});
|
|
146
|
+
return augmentHarvestRoundTripPairs(scoped, timeFiltered);
|
|
147
|
+
}
|
|
148
|
+
export function extractHarvestCandidates(records, options = {}) {
|
|
149
|
+
const shellOnly = shellRecords(records);
|
|
150
|
+
const classifierAsks = shellOnly.filter((record) => inferWouldBlock(record) && !isAvailabilityCausedAsk(record));
|
|
151
|
+
const map = new Map();
|
|
152
|
+
// `unknown` covers legacy shell rows missing `kind`.
|
|
153
|
+
const roundTrips = buildApprovalRoundTrips(recordsForShellRoundTrips(records)).filter((trip) => trip.kind === 'shell' || trip.kind === 'unknown');
|
|
154
|
+
for (const trip of roundTrips) {
|
|
155
|
+
if (!trip.fingerprint || !trip.approvalTimestamp) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
upsertCandidate(map, {
|
|
159
|
+
fingerprint: trip.fingerprint,
|
|
160
|
+
command: trip.summary,
|
|
161
|
+
reason: trip.reason,
|
|
162
|
+
source: 'deny_then_approve',
|
|
163
|
+
approvedAfterDeny: true,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
for (const entry of computeRepeatedFingerprintAsks(classifierAsks, 2, 50)) {
|
|
167
|
+
upsertCandidate(map, {
|
|
168
|
+
fingerprint: entry.fingerprint,
|
|
169
|
+
command: entry.summary,
|
|
170
|
+
reason: entry.reason,
|
|
171
|
+
source: 'repeated_ask',
|
|
172
|
+
askCount: entry.askCount,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
for (const record of classifierAsks) {
|
|
176
|
+
if (!record.fingerprint) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const command = commandFromRecord(record);
|
|
180
|
+
if (!hasReadStyleSignal(command)) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
upsertCandidate(map, {
|
|
184
|
+
fingerprint: record.fingerprint,
|
|
185
|
+
command,
|
|
186
|
+
reason: record.reason ?? 'unknown',
|
|
187
|
+
source: 'read_style_signal',
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
// Current overrides.allow match on past would-blocks — not a changelog of new additions.
|
|
191
|
+
const allowPatterns = options.allowPatterns ?? [];
|
|
192
|
+
for (const record of classifierAsks) {
|
|
193
|
+
if (!record.fingerprint) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const command = commandFromRecord(record);
|
|
197
|
+
if (!command) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
for (const pattern of allowPatterns) {
|
|
201
|
+
if (matchesCustomCommand(command, command, pattern)) {
|
|
202
|
+
upsertCandidate(map, {
|
|
203
|
+
fingerprint: record.fingerprint,
|
|
204
|
+
command,
|
|
205
|
+
reason: record.reason ?? 'unknown',
|
|
206
|
+
source: 'overrides_allow',
|
|
207
|
+
});
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return [...map.values()].sort((left, right) => {
|
|
213
|
+
if (right.askCount !== left.askCount) {
|
|
214
|
+
return right.askCount - left.askCount;
|
|
215
|
+
}
|
|
216
|
+
return left.command.localeCompare(right.command);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
export function buildHarvestReport(records, options = {}) {
|
|
220
|
+
return {
|
|
221
|
+
schemaVersion: HARVEST_REPORT_SCHEMA_VERSION,
|
|
222
|
+
scope: 'shell',
|
|
223
|
+
candidates: extractHarvestCandidates(records, options),
|
|
224
|
+
availabilityQueue: extractAvailabilityQueue(records),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
export function applyHarvestReview(cases, params) {
|
|
228
|
+
const command = params.command.trim();
|
|
229
|
+
if (!command) {
|
|
230
|
+
return { cases, applied: false, ok: false, message: 'Command must be non-empty.' };
|
|
231
|
+
}
|
|
232
|
+
if (params.outcome === 'reject') {
|
|
233
|
+
return {
|
|
234
|
+
cases,
|
|
235
|
+
applied: false,
|
|
236
|
+
ok: true,
|
|
237
|
+
message: `Reviewed and rejected candidate ${JSON.stringify(command)}.`,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const category = params.outcome;
|
|
241
|
+
const verdict = category === 'provably-benign' ? 'allow' : 'allow_flagged';
|
|
242
|
+
const reason = params.reason?.trim();
|
|
243
|
+
const duplicate = cases.find((entry) => entry.command === command);
|
|
244
|
+
if (duplicate) {
|
|
245
|
+
if (duplicate.category === category) {
|
|
246
|
+
return {
|
|
247
|
+
cases,
|
|
248
|
+
applied: false,
|
|
249
|
+
ok: false,
|
|
250
|
+
message: `Corpus already contains ${JSON.stringify(command)} as ${category}.`,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
cases,
|
|
255
|
+
applied: false,
|
|
256
|
+
ok: false,
|
|
257
|
+
message: `Corpus already contains ${JSON.stringify(command)} as ${duplicate.category}; resolve manually.`,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const nextCase = {
|
|
261
|
+
kind: 'shell',
|
|
262
|
+
category,
|
|
263
|
+
command,
|
|
264
|
+
verdict,
|
|
265
|
+
...(reason ? { reason } : {}),
|
|
266
|
+
};
|
|
267
|
+
const followUp = category === 'provably-benign'
|
|
268
|
+
? 'Next: run `pnpm corpus` and confirm hard gates pass before `pnpm build` refreshes the standing-allow catalog.'
|
|
269
|
+
: 'Next: run `pnpm corpus` to verify corpus evaluation (accepted-benign is soft-gated).';
|
|
270
|
+
return {
|
|
271
|
+
cases: [...cases, nextCase],
|
|
272
|
+
applied: true,
|
|
273
|
+
ok: true,
|
|
274
|
+
message: `Added ${JSON.stringify(command)} to corpus as ${category}. ${followUp}`,
|
|
275
|
+
};
|
|
276
|
+
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
export { APPROVAL_EXECUTION_LEASE_MS, approvalCommandMatch,
|
|
1
|
+
export { APPROVAL_EXECUTION_LEASE_MS, approvalCommandMatch, compactApprovals, createApprovalRecord, createApprovalRecordWithEnvelope, isExecutionLeaseExpired, isExpired, mergeApprovalStates, nowIso, } from './approval.js';
|
|
2
|
+
export type { ApprovalReplayHint, ReplayActionContext, ReplayAdapterId, } from './approval-replay.js';
|
|
3
|
+
/** @deprecated Use `buildRetryInstructionForConfig` */
|
|
4
|
+
export { approvalFlow, buildApprovalRecordedMessage, buildReplayEnvelopeFields, buildReplayHint, buildRetryInstructionForConfig, buildRetryInstructionForConfig as buildRetryInstruction, canAutoReplay, getExecutionLeaseMs, replayShellCommand, validateReplayEnvelope, } from './approval-replay.js';
|
|
5
|
+
export type { ApprovalStore } from './approval-service.js';
|
|
6
|
+
export { consumeApprovedAfterCliReplay, createGateApprovalStore, gateApprovalStoreFromDeps, recordApproval, } from './approval-service.js';
|
|
2
7
|
export type { AuditMetricsReport } from './audit-metrics.js';
|
|
3
8
|
export { computeAuditMetrics, parseAuditNdjson } from './audit-metrics.js';
|
|
4
9
|
export { classifySubagent } from './classify-subagent.js';
|
|
5
10
|
export { classifyToolUse } from './classify-tool.js';
|
|
6
|
-
export { approvedApprovalsFile, type BelayConfig, type BelayConfigV1, type BelayConfigV2, type BelayConfigV3, type BelayConfigV4, type BelayControlPlaneConfig, type BelayJudgeConfig, type BelayOverridesConfig, type BelayPolicyConfig, type BelayRedactionConfig, belayStateDir, classifierOptionsFromConfig, configuredControlPlaneDir, DEFAULT_CONFIG_V2, DEFAULT_CONFIG_V3, DEFAULT_CONFIG_V4, DEFAULT_JUDGE_CURSOR_COMPOSER, DEFAULT_JUDGE_LOCAL_OLLAMA, defaultControlPlaneDir, isConfigV4, isFreshConfigInput, LEGACY_POLICY_V3, mapLegacyClassifierToOverrides, mergeConfig, migrateConfig, migrateV2ToV3, normalizeConfig, normalizeJudgeConfig, pendingApprovalsFile, resolveControlPlaneDir, scrubOptionsFromConfig, } from './config.js';
|
|
11
|
+
export { type ApprovalFlow, approvedApprovalsFile, type BelayApprovalAutoReplayScopes, type BelayApprovalConfig, type BelayConfig, type BelayConfigV1, type BelayConfigV2, type BelayConfigV3, type BelayConfigV4, type BelayControlPlaneConfig, type BelayJudgeConfig, type BelayOverridesConfig, type BelayPolicyConfig, type BelayRedactionConfig, belayStateDir, classifierOptionsFromConfig, configuredControlPlaneDir, DEFAULT_APPROVAL_CONFIG, DEFAULT_CONFIG_V2, DEFAULT_CONFIG_V3, DEFAULT_CONFIG_V4, DEFAULT_JUDGE_CURSOR_COMPOSER, DEFAULT_JUDGE_LOCAL_OLLAMA, defaultControlPlaneDir, isConfigV4, isFreshConfigInput, LEGACY_POLICY_V3, mapLegacyClassifierToOverrides, mergeConfig, migrateConfig, migrateV2ToV3, normalizeConfig, normalizeJudgeConfig, pendingApprovalsFile, resolveControlPlaneDir, scrubOptionsFromConfig, } from './config.js';
|
|
7
12
|
export { matchesCustomCommand } from './custom-command-match.js';
|
|
8
13
|
export { canonicalStringify, hashValue, shellFingerprint, subagentFingerprint, toolFingerprint, } from './fingerprint.js';
|
|
9
14
|
export type { GatedAction, GatedActionKind, GatePermissionResponse, GateVerdict, } from './gate-contract.js';
|
|
@@ -11,9 +16,10 @@ export { classifyResultToGateVerdict, GATE_CONTRACT_VERSION, isGatedAction, unno
|
|
|
11
16
|
export { classifyGatedAction, GateNormalizationError, gateEnabledForAction, normalizeGatedAction, } from './gate-engine.js';
|
|
12
17
|
export { matchesSensitivePath } from './glob.js';
|
|
13
18
|
export { canonicalPath, hasOutsideRepoPath, normalizeToken, pathWithinRoot, relativeWithinRepo, resolveMutationTarget, } from './path-utils.js';
|
|
19
|
+
export { fingerprintReplayPayload, subagentFingerprintSource } from './replay-scrub.js';
|
|
14
20
|
export { scrubString, scrubValue } from './scrub.js';
|
|
15
21
|
export { findCommandSubstitutions, MAX_SUBSTITUTION_DEPTH } from './shell-substitution.js';
|
|
16
22
|
export type { TransactionalDiffEvaluation, TransactionalExecutionResult, } from './transactional/index.js';
|
|
17
23
|
export { isTransactionalEligible, runTransactionalExecution } from './transactional/index.js';
|
|
18
|
-
export type { ApprovalRecord, ApprovalStateFile, Assessment, ClassifierOptions, ClassifyResult, HookVerdict, Reversibility, ScrubOptions, UnknownLocalEffectPolicy, } from './types.js';
|
|
24
|
+
export type { ApprovalRecord, ApprovalScopeHint, ApprovalStateFile, Assessment, ClassifierOptions, ClassifyResult, HookVerdict, Reversibility, ScrubOptions, UnknownLocalEffectPolicy, } from './types.js';
|
|
19
25
|
export { buildVerdictContext, classifyShell, verdict, verdictToClassifyResult, } from './verdict/index.js';
|
package/dist/core/index.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
export { APPROVAL_EXECUTION_LEASE_MS, approvalCommandMatch,
|
|
1
|
+
export { APPROVAL_EXECUTION_LEASE_MS, approvalCommandMatch, compactApprovals, createApprovalRecord, createApprovalRecordWithEnvelope, isExecutionLeaseExpired, isExpired, mergeApprovalStates, nowIso, } from './approval.js';
|
|
2
|
+
/** @deprecated Use `buildRetryInstructionForConfig` */
|
|
3
|
+
export { approvalFlow, buildApprovalRecordedMessage, buildReplayEnvelopeFields, buildReplayHint, buildRetryInstructionForConfig, buildRetryInstructionForConfig as buildRetryInstruction, canAutoReplay, getExecutionLeaseMs, replayShellCommand, validateReplayEnvelope, } from './approval-replay.js';
|
|
4
|
+
export { consumeApprovedAfterCliReplay, createGateApprovalStore, gateApprovalStoreFromDeps, recordApproval, } from './approval-service.js';
|
|
2
5
|
export { computeAuditMetrics, parseAuditNdjson } from './audit-metrics.js';
|
|
3
6
|
export { classifySubagent } from './classify-subagent.js';
|
|
4
7
|
export { classifyToolUse } from './classify-tool.js';
|
|
5
|
-
export { approvedApprovalsFile, belayStateDir, classifierOptionsFromConfig, configuredControlPlaneDir, DEFAULT_CONFIG_V2, DEFAULT_CONFIG_V3, DEFAULT_CONFIG_V4, DEFAULT_JUDGE_CURSOR_COMPOSER, DEFAULT_JUDGE_LOCAL_OLLAMA, defaultControlPlaneDir, isConfigV4, isFreshConfigInput, LEGACY_POLICY_V3, mapLegacyClassifierToOverrides, mergeConfig, migrateConfig, migrateV2ToV3, normalizeConfig, normalizeJudgeConfig, pendingApprovalsFile, resolveControlPlaneDir, scrubOptionsFromConfig, } from './config.js';
|
|
8
|
+
export { approvedApprovalsFile, belayStateDir, classifierOptionsFromConfig, configuredControlPlaneDir, DEFAULT_APPROVAL_CONFIG, DEFAULT_CONFIG_V2, DEFAULT_CONFIG_V3, DEFAULT_CONFIG_V4, DEFAULT_JUDGE_CURSOR_COMPOSER, DEFAULT_JUDGE_LOCAL_OLLAMA, defaultControlPlaneDir, isConfigV4, isFreshConfigInput, LEGACY_POLICY_V3, mapLegacyClassifierToOverrides, mergeConfig, migrateConfig, migrateV2ToV3, normalizeConfig, normalizeJudgeConfig, pendingApprovalsFile, resolveControlPlaneDir, scrubOptionsFromConfig, } from './config.js';
|
|
6
9
|
export { matchesCustomCommand } from './custom-command-match.js';
|
|
7
10
|
export { canonicalStringify, hashValue, shellFingerprint, subagentFingerprint, toolFingerprint, } from './fingerprint.js';
|
|
8
11
|
export { classifyResultToGateVerdict, GATE_CONTRACT_VERSION, isGatedAction, unnormalizedGateVerdict, } from './gate-contract.js';
|
|
9
12
|
export { classifyGatedAction, GateNormalizationError, gateEnabledForAction, normalizeGatedAction, } from './gate-engine.js';
|
|
10
13
|
export { matchesSensitivePath } from './glob.js';
|
|
11
14
|
export { canonicalPath, hasOutsideRepoPath, normalizeToken, pathWithinRoot, relativeWithinRepo, resolveMutationTarget, } from './path-utils.js';
|
|
15
|
+
export { fingerprintReplayPayload, subagentFingerprintSource } from './replay-scrub.js';
|
|
12
16
|
export { scrubString, scrubValue } from './scrub.js';
|
|
13
17
|
export { findCommandSubstitutions, MAX_SUBSTITUTION_DEPTH } from './shell-substitution.js';
|
|
14
18
|
export { isTransactionalEligible, runTransactionalExecution } from './transactional/index.js';
|
|
@@ -8,5 +8,8 @@ export interface JudgeDoctorResult {
|
|
|
8
8
|
}
|
|
9
9
|
export interface DiagnoseJudgeOptions {
|
|
10
10
|
discoveryDeps?: JudgeModelDiscoveryDeps;
|
|
11
|
+
liveProbe?: boolean;
|
|
11
12
|
}
|
|
12
13
|
export declare function diagnoseJudge(config: BelayConfigV4, repoRoot?: string, options?: DiagnoseJudgeOptions): Promise<JudgeDoctorResult>;
|
|
14
|
+
export declare function stopJudgeSessionBrokers(repoRoot: string, stateDir: string): Promise<number>;
|
|
15
|
+
export declare function stopInProcessJudgeSessionBrokers(repoRoot?: string): number;
|
|
@@ -2,16 +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';
|
|
9
|
+
import { stopJudgeBrokerDaemon } from './verdict/judge-broker-service.js';
|
|
8
10
|
import { getJudgeProviderCapabilities, getJudgeProviderSpec, isRemovedProviderId, normalizeLegacyProviderId, } from './verdict/judge-catalog.js';
|
|
9
11
|
import { createJudgeFromConfig, resolveJudgeModel } from './verdict/judge-factory.js';
|
|
12
|
+
import { resolveJudgeSmokeProbeTimeoutMs } from './verdict/judge-runtime-config.js';
|
|
13
|
+
import { listRepoJudgeSessionBrokers, stopRepoJudgeSessionBroker, } from './verdict/judge-session-broker.js';
|
|
14
|
+
import { clearJudgeSessionKillSwitch } from './verdict/judge-session-kill-switch.js';
|
|
15
|
+
function judgeShadowAdvisory(warnings, message) {
|
|
16
|
+
warnings.push(`[judge shadow advisory] ${message}`);
|
|
17
|
+
}
|
|
10
18
|
export async function diagnoseJudge(config, repoRoot = process.cwd(), options = {}) {
|
|
19
|
+
const shouldRunLiveProbe = options.liveProbe === true && !process.env.VITEST && !process.env.VITEST_WORKER_ID;
|
|
11
20
|
const issues = [];
|
|
12
21
|
const warnings = [];
|
|
13
22
|
const notes = [];
|
|
14
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
|
+
}
|
|
15
29
|
const provider = normalizeJudgeProvider(judge.provider);
|
|
16
30
|
const rawProviderId = judge.providerId ? String(judge.providerId) : undefined;
|
|
17
31
|
if (rawProviderId && isRemovedProviderId(rawProviderId)) {
|
|
@@ -32,15 +46,19 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
32
46
|
const capabilities = getJudgeProviderCapabilities(providerId);
|
|
33
47
|
const transport = resolveJudgeTransport(judge);
|
|
34
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;
|
|
35
53
|
notes.push(`Judge providerId: ${providerId}`);
|
|
36
54
|
notes.push(`Judge driver: ${provider}`);
|
|
37
55
|
notes.push(`Judge model requested: ${judge.model}`);
|
|
38
|
-
notes.push(`Judge transport: ${
|
|
56
|
+
notes.push(`Judge transport: ${effectiveTransport}`);
|
|
39
57
|
if (config.policy.modelAssist.enabled) {
|
|
40
58
|
warnings.push('policy.modelAssist is enabled but is not wired to v2 Tier1. Use top-level judge instead.');
|
|
41
59
|
}
|
|
42
60
|
if (capabilities?.requiresConsent && !hasValidCloudConsent(judge) && transport === 'http') {
|
|
43
|
-
|
|
61
|
+
judgeShadowAdvisory(warnings, 'Cloud judge consent is not recorded. Shadow cloud judge will fail until consent is granted.');
|
|
44
62
|
}
|
|
45
63
|
else if (judge.cloudConsent?.accepted) {
|
|
46
64
|
notes.push(`Cloud consent: accepted ${judge.cloudConsent.at} by ${judge.cloudConsent.by}`);
|
|
@@ -70,12 +88,12 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
70
88
|
if (providerId === 'ollama') {
|
|
71
89
|
notes.push(`Ollama endpoint: ${endpoint}`);
|
|
72
90
|
if (discovery.modelIds.length === 0) {
|
|
73
|
-
|
|
91
|
+
judgeShadowAdvisory(warnings, 'Ollama endpoint unreachable or returned no models. Shadow judge will not run.');
|
|
74
92
|
}
|
|
75
93
|
else {
|
|
76
94
|
const hasModel = modelCheck.status === 'found';
|
|
77
95
|
if (!hasModel) {
|
|
78
|
-
|
|
96
|
+
judgeShadowAdvisory(warnings, `Ollama model "${judge.model}" is not present. Pull it before enabling shadow comparisons.`);
|
|
79
97
|
}
|
|
80
98
|
else {
|
|
81
99
|
notes.push(`Ollama model "${judge.model}" is available.`);
|
|
@@ -99,7 +117,7 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
99
117
|
context: { cwd: process.cwd(), repoRoot: process.cwd() },
|
|
100
118
|
});
|
|
101
119
|
if (warmResult.reason === 'ollama_unavailable' || warmResult.reason === 'ollama_parse_error') {
|
|
102
|
-
|
|
120
|
+
judgeShadowAdvisory(warnings, `Ollama warm call failed: ${warmResult.reason}`);
|
|
103
121
|
}
|
|
104
122
|
else {
|
|
105
123
|
notes.push('Ollama warm call succeeded.');
|
|
@@ -107,18 +125,52 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
107
125
|
return { issues, warnings, notes, modelCheck };
|
|
108
126
|
}
|
|
109
127
|
if (transport === 'unavailable') {
|
|
110
|
-
|
|
128
|
+
judgeShadowAdvisory(warnings, 'No judge transport is available (configure endpoint or install native CLI). Shadow judge will not run.');
|
|
111
129
|
return { issues, warnings, notes, modelCheck };
|
|
112
130
|
}
|
|
113
131
|
if (transport.endsWith('-cli')) {
|
|
114
132
|
if (!runtime.cliTransport) {
|
|
115
|
-
|
|
133
|
+
judgeShadowAdvisory(warnings, `Native CLI transport (${transport}) is not available. Shadow judge will not run.`);
|
|
116
134
|
}
|
|
117
135
|
else if (!keyInfo.key && keyInfo.sourceKind !== 'host-session') {
|
|
118
|
-
|
|
136
|
+
judgeShadowAdvisory(warnings, 'Judge API key is not set for the configured credential mode. Shadow cloud judge will not run.');
|
|
119
137
|
}
|
|
120
138
|
else {
|
|
121
|
-
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
|
+
}
|
|
145
|
+
if (shouldRunLiveProbe) {
|
|
146
|
+
const smokeConfig = {
|
|
147
|
+
...config,
|
|
148
|
+
judge: {
|
|
149
|
+
...judge,
|
|
150
|
+
timeoutMs: resolveJudgeSmokeProbeTimeoutMs(judge.timeoutMs),
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
const smokeJudge = createJudgeFromConfig(smokeConfig, { repoRoot });
|
|
154
|
+
const smokeResult = await smokeJudge.evaluate({
|
|
155
|
+
text: 'git status',
|
|
156
|
+
context: { cwd: repoRoot, repoRoot },
|
|
157
|
+
});
|
|
158
|
+
const smokeFallback = smokeJudge.lastTrace?.judgeFallbackReason ??
|
|
159
|
+
smokeJudge.lastTrace?.fallbackReason ??
|
|
160
|
+
smokeResult.reason;
|
|
161
|
+
if (smokeJudge.lastTrace?.provider === 'fallback') {
|
|
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}).`);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
notes.push(cursorAcpEnabled
|
|
170
|
+
? 'Cursor ACP smoke probe succeeded.'
|
|
171
|
+
: 'Native CLI transport smoke probe succeeded.');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
122
174
|
}
|
|
123
175
|
return { issues, warnings, notes, modelCheck };
|
|
124
176
|
}
|
|
@@ -126,7 +178,7 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
126
178
|
notes.push(`HTTP endpoint: ${judge.endpoint}`);
|
|
127
179
|
}
|
|
128
180
|
if (!keyInfo.key) {
|
|
129
|
-
|
|
181
|
+
judgeShadowAdvisory(warnings, 'Judge API key is not set for the configured credential mode. Shadow cloud judge will not run.');
|
|
130
182
|
}
|
|
131
183
|
else {
|
|
132
184
|
notes.push(`Credential source: ${keyInfo.source ?? keyInfo.sourceKind}`);
|
|
@@ -161,7 +213,7 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
161
213
|
});
|
|
162
214
|
if (dryRun.reason.startsWith('openai_compatible_') ||
|
|
163
215
|
dryRun.reason === 'outbound_scrub_failed') {
|
|
164
|
-
|
|
216
|
+
judgeShadowAdvisory(warnings, `HTTP judge dry-run failed: ${dryRun.reason}`);
|
|
165
217
|
}
|
|
166
218
|
else {
|
|
167
219
|
notes.push('HTTP judge dry-run succeeded.');
|
|
@@ -171,5 +223,32 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
171
223
|
if (factoryJudge.lastTrace?.transport) {
|
|
172
224
|
notes.push(`Factory transport: ${factoryJudge.lastTrace.transport}`);
|
|
173
225
|
}
|
|
226
|
+
if (config.judge.runtime?.session.enabled) {
|
|
227
|
+
notes.push('Judge session transport: enabled');
|
|
228
|
+
const activeBrokers = listRepoJudgeSessionBrokers();
|
|
229
|
+
if (activeBrokers.includes(repoRoot)) {
|
|
230
|
+
notes.push(`Active session broker for repo: ${repoRoot}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
notes.push('Judge session transport: disabled (default)');
|
|
235
|
+
}
|
|
174
236
|
return { issues, warnings, notes, modelCheck };
|
|
175
237
|
}
|
|
238
|
+
export async function stopJudgeSessionBrokers(repoRoot, stateDir) {
|
|
239
|
+
let stopped = 0;
|
|
240
|
+
stopped += stopRepoJudgeSessionBroker(repoRoot, 'manual_stop');
|
|
241
|
+
stopped += await stopJudgeBrokerDaemon(stateDir);
|
|
242
|
+
await clearJudgeSessionKillSwitch(stateDir);
|
|
243
|
+
return stopped;
|
|
244
|
+
}
|
|
245
|
+
export function stopInProcessJudgeSessionBrokers(repoRoot) {
|
|
246
|
+
if (repoRoot) {
|
|
247
|
+
return stopRepoJudgeSessionBroker(repoRoot, 'manual_stop');
|
|
248
|
+
}
|
|
249
|
+
let stopped = 0;
|
|
250
|
+
for (const root of listRepoJudgeSessionBrokers()) {
|
|
251
|
+
stopped += stopRepoJudgeSessionBroker(root, 'manual_stop');
|
|
252
|
+
}
|
|
253
|
+
return stopped;
|
|
254
|
+
}
|
|
@@ -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
|
+
};
|