@guilz-dev/belay 0.4.0 → 0.7.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 +27 -2
- package/dist/adapters/claude/runtime-entry.js +54 -25
- package/dist/adapters/layouts/scope.js +3 -2
- package/dist/adapters/shared/gate-runtime.d.ts +8 -2
- package/dist/adapters/shared/gate-runtime.js +579 -181
- package/dist/bundle/claude-runtime.mjs +13574 -6334
- package/dist/bundle/codex-runtime.mjs +13108 -5903
- package/dist/bundle/cursor-runtime.mjs +12676 -5476
- package/dist/cli.js +80 -2
- package/dist/commands/approve.js +34 -10
- package/dist/commands/classify-for-report.js +1 -1
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +134 -11
- package/dist/commands/doctor.js +87 -1
- package/dist/commands/explain.js +11 -1
- package/dist/commands/judge.js +3 -2
- package/dist/commands/recovery-checkpoints.d.ts +164 -0
- package/dist/commands/recovery-checkpoints.js +347 -0
- package/dist/commands/revoke.js +19 -7
- package/dist/commands/session.d.ts +20 -0
- package/dist/commands/session.js +51 -0
- package/dist/commands/status.js +7 -0
- package/dist/config-io.js +91 -23
- 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 +20 -9
- package/dist/conformance/types.d.ts +1 -0
- package/dist/core/approval-replay.d.ts +1 -1
- package/dist/core/approval-replay.js +4 -1
- package/dist/core/approval-service.d.ts +6 -1
- package/dist/core/approval-service.js +87 -24
- package/dist/core/approval.d.ts +4 -0
- package/dist/core/approval.js +19 -5
- package/dist/core/audit-types.d.ts +13 -0
- package/dist/core/capability/approval-state-mutation.d.ts +32 -0
- package/dist/core/capability/approval-state-mutation.js +165 -0
- package/dist/core/capability/approval-v3.d.ts +32 -0
- package/dist/core/capability/approval-v3.js +114 -0
- package/dist/core/capability/attestation.d.ts +16 -0
- package/dist/core/capability/attestation.js +62 -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 +19 -0
- package/dist/core/capability/boundary-driver-container.js +189 -0
- package/dist/core/capability/boundary-driver.d.ts +29 -0
- package/dist/core/capability/boundary-driver.js +44 -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 +14 -0
- package/dist/core/capability/boundary-grant-materialize.js +93 -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 +48 -0
- package/dist/core/capability/boundary-run.js +51 -0
- package/dist/core/capability/boundary-session.d.ts +61 -0
- package/dist/core/capability/boundary-session.js +138 -0
- package/dist/core/capability/boundary-workspace-mount.d.ts +5 -0
- package/dist/core/capability/boundary-workspace-mount.js +68 -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 +130 -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-consumption.d.ts +4 -0
- package/dist/core/capability/grant-consumption.js +11 -0
- package/dist/core/capability/grant-lease.d.ts +44 -0
- package/dist/core/capability/grant-lease.js +243 -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 +96 -0
- package/dist/core/capability/grant.d.ts +16 -0
- package/dist/core/capability/grant.js +16 -0
- package/dist/core/capability/index.d.ts +8 -0
- package/dist/core/capability/index.js +7 -0
- package/dist/core/capability/limits.d.ts +7 -0
- package/dist/core/capability/limits.js +55 -0
- package/dist/core/capability/policy-bridge.d.ts +18 -0
- package/dist/core/capability/policy-bridge.js +51 -0
- package/dist/core/capability/policy-engine.d.ts +77 -0
- package/dist/core/capability/policy-engine.js +663 -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 +48 -0
- package/dist/core/capability/request.js +1 -0
- package/dist/core/capability/resolver.d.ts +4 -0
- package/dist/core/capability/resolver.js +4 -0
- package/dist/core/classify-subagent.d.ts +2 -1
- package/dist/core/classify-subagent.js +38 -2
- package/dist/core/classify-tool.js +126 -62
- package/dist/core/config.d.ts +34 -3
- package/dist/core/config.js +118 -3
- package/dist/core/effect-ir/audit.d.ts +9 -0
- package/dist/core/effect-ir/audit.js +86 -0
- package/dist/core/effect-ir/build.d.ts +31 -0
- package/dist/core/effect-ir/build.js +277 -0
- package/dist/core/effect-ir/index.d.ts +8 -0
- package/dist/core/effect-ir/index.js +7 -0
- package/dist/core/effect-ir/normalize.d.ts +4 -0
- package/dist/core/effect-ir/normalize.js +104 -0
- package/dist/core/effect-ir/package-exec-eval.d.ts +8 -0
- package/dist/core/effect-ir/package-exec-eval.js +152 -0
- package/dist/core/effect-ir/package-exec.d.ts +29 -0
- package/dist/core/effect-ir/package-exec.js +292 -0
- package/dist/core/effect-ir/policy.d.ts +13 -0
- package/dist/core/effect-ir/policy.js +77 -0
- package/dist/core/effect-ir/types.d.ts +55 -0
- package/dist/core/effect-ir/types.js +1 -0
- package/dist/core/egress/allowlist.d.ts +1 -0
- package/dist/core/egress/allowlist.js +8 -0
- package/dist/core/egress-approval.js +45 -51
- package/dist/core/gate-contract.d.ts +5 -0
- package/dist/core/gate-contract.js +5 -0
- package/dist/core/gate-engine.js +30 -8
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.js +2 -1
- package/dist/core/judge-cloud-consent.js +29 -15
- package/dist/core/judge-doctor.js +39 -14
- package/dist/core/judge-fallback-hints.d.ts +13 -0
- package/dist/core/judge-fallback-hints.js +108 -0
- package/dist/core/network-endpoint.d.ts +10 -0
- package/dist/core/network-endpoint.js +51 -0
- package/dist/core/path-utils.d.ts +1 -0
- package/dist/core/path-utils.js +5 -2
- package/dist/core/process-runner.d.ts +10 -0
- package/dist/core/process-runner.js +124 -0
- package/dist/core/recover-advice.js +2 -1
- package/dist/core/recovery/artifact-store.d.ts +29 -0
- package/dist/core/recovery/artifact-store.js +336 -0
- package/dist/core/recovery/capability.d.ts +3 -0
- package/dist/core/recovery/capability.js +22 -0
- package/dist/core/recovery/checkpoint.d.ts +25 -0
- package/dist/core/recovery/checkpoint.js +260 -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 +5 -0
- package/dist/core/recovery/index.js +4 -0
- package/dist/core/recovery/operator-guidance.d.ts +7 -0
- package/dist/core/recovery/operator-guidance.js +39 -0
- package/dist/core/recovery/reconcile.d.ts +3 -0
- package/dist/core/recovery/reconcile.js +48 -0
- package/dist/core/recovery/resource-identity.d.ts +4 -0
- package/dist/core/recovery/resource-identity.js +34 -0
- package/dist/core/recovery/restore.d.ts +13 -0
- package/dist/core/recovery/restore.js +114 -0
- package/dist/core/recovery/snapshot-node.d.ts +27 -0
- package/dist/core/recovery/snapshot-node.js +261 -0
- package/dist/core/recovery/types.d.ts +104 -0
- package/dist/core/recovery/types.js +1 -0
- package/dist/core/standing-allow.js +3 -0
- package/dist/core/transactional/apply-observed-changes.d.ts +13 -0
- package/dist/core/transactional/apply-observed-changes.js +275 -0
- package/dist/core/transactional/backend-selector.d.ts +8 -0
- package/dist/core/transactional/backend-selector.js +118 -0
- package/dist/core/transactional/backend.d.ts +55 -0
- package/dist/core/transactional/backend.js +1 -0
- package/dist/core/transactional/diff-evaluator.js +4 -1
- package/dist/core/transactional/eligibility.js +4 -0
- package/dist/core/transactional/file-checkpoint-backend.d.ts +3 -0
- package/dist/core/transactional/file-checkpoint-backend.js +278 -0
- package/dist/core/transactional/file-checkpoint-git.d.ts +11 -0
- package/dist/core/transactional/file-checkpoint-git.js +217 -0
- package/dist/core/transactional/file-checkpoint-staging.d.ts +13 -0
- package/dist/core/transactional/file-checkpoint-staging.js +54 -0
- package/dist/core/transactional/file-clone.d.ts +15 -0
- package/dist/core/transactional/file-clone.js +139 -0
- package/dist/core/transactional/file-tree-path.d.ts +7 -0
- package/dist/core/transactional/file-tree-path.js +50 -0
- package/dist/core/transactional/file-tree.d.ts +41 -0
- package/dist/core/transactional/file-tree.js +186 -0
- package/dist/core/transactional/git-worktree-backend.d.ts +2 -0
- package/dist/core/transactional/git-worktree-backend.js +50 -0
- package/dist/core/transactional/git-worktree.d.ts +14 -9
- package/dist/core/transactional/git-worktree.js +62 -87
- package/dist/core/transactional/index.d.ts +11 -0
- package/dist/core/transactional/index.js +10 -0
- package/dist/core/transactional/reasons.d.ts +1 -0
- package/dist/core/transactional/reasons.js +5 -0
- package/dist/core/transactional/runner.js +171 -23
- package/dist/core/transactional/snapshot-node.d.ts +26 -0
- package/dist/core/transactional/snapshot-node.js +87 -0
- package/dist/core/transactional/types.d.ts +21 -1
- package/dist/core/types.d.ts +30 -3
- package/dist/core/verdict/adapter.d.ts +3 -4
- package/dist/core/verdict/adapter.js +24 -12
- package/dist/core/verdict/cursor-acp-client.d.ts +67 -0
- package/dist/core/verdict/cursor-acp-client.js +345 -0
- package/dist/core/verdict/egress-classify.d.ts +2 -2
- package/dist/core/verdict/egress-classify.js +2 -2
- package/dist/core/verdict/git-classifier.d.ts +26 -0
- package/dist/core/verdict/git-classifier.js +533 -0
- package/dist/core/verdict/judge-audit.js +1 -0
- package/dist/core/verdict/judge-baseline.d.ts +1 -1
- package/dist/core/verdict/judge-baseline.js +1 -0
- package/dist/core/verdict/judge-broker-service.js +12 -0
- package/dist/core/verdict/judge-catalog.js +1 -0
- package/dist/core/verdict/judge-cli-fingerprint.d.ts +1 -0
- package/dist/core/verdict/judge-cli-fingerprint.js +6 -0
- package/dist/core/verdict/judge-cli.js +5 -3
- package/dist/core/verdict/judge-provider-matrix.js +1 -1
- package/dist/core/verdict/judge-runtime-config.d.ts +2 -0
- package/dist/core/verdict/judge-runtime-config.js +10 -6
- package/dist/core/verdict/judge-session-broker.d.ts +1 -1
- package/dist/core/verdict/judge-session-broker.js +10 -5
- package/dist/core/verdict/judge-transport.d.ts +5 -1
- package/dist/core/verdict/judge-transport.js +24 -14
- package/dist/core/verdict/judge.d.ts +2 -15
- package/dist/core/verdict/judge.js +5 -68
- package/dist/core/verdict/launcher-resolve.d.ts +2 -0
- package/dist/core/verdict/launcher-resolve.js +14 -16
- 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 +26 -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 +18 -1
- package/dist/core/verdict/verdict.js +313 -178
- package/dist/corpus/evaluate.js +3 -7
- package/dist/corpus/gate-latency-budget.d.ts +39 -0
- package/dist/corpus/gate-latency-budget.js +33 -0
- package/dist/corpus/mutators.d.ts +1 -1
- package/dist/corpus/mutators.js +11 -0
- package/dist/corpus/runtime-match.js +1 -3
- package/dist/corpus/standing-allow-catalog.generated.js +29 -9
- package/dist/judge-broker-daemon.js +15 -1
- package/dist/services/sandbox-service.d.ts +4 -0
- package/dist/services/sandbox-service.js +28 -2
- package/dist/types.d.ts +13 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/belay/SKILL.md +8 -3
- package/skills/belay/belay-approve.md +10 -7
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { CapabilityAction, CapabilityEvidenceLevel, CapabilityResource } from '../capability/request.js';
|
|
2
|
+
import type { VerdictOpacity } from '../verdict/types.js';
|
|
3
|
+
export declare const EFFECT_PLAN_VERSION: 1;
|
|
4
|
+
export type EffectTag = 'fs.read' | 'fs.write' | 'process.exec' | 'network.connect' | 'network.acquire' | 'git.ref.write' | 'secret.read' | 'control_plane.write' | 'read_only' | 'indeterminate';
|
|
5
|
+
export type LauncherPhase = 'invoke' | 'resolve' | 'acquire' | 'cache_read' | 'cache_write' | 'delegate';
|
|
6
|
+
export type PackageExecLauncher = 'npx' | 'npm' | 'pnpm';
|
|
7
|
+
export interface EffectEvidence {
|
|
8
|
+
level: CapabilityEvidenceLevel;
|
|
9
|
+
signals: readonly string[];
|
|
10
|
+
basis: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export interface EffectProvenance {
|
|
13
|
+
segment?: string;
|
|
14
|
+
launcher?: PackageExecLauncher;
|
|
15
|
+
phase?: LauncherPhase;
|
|
16
|
+
innerCommand?: string;
|
|
17
|
+
innerArgv?: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
export interface EffectRequirement {
|
|
20
|
+
tag: EffectTag;
|
|
21
|
+
action: CapabilityAction;
|
|
22
|
+
resource: CapabilityResource;
|
|
23
|
+
evidence: EffectEvidence;
|
|
24
|
+
provenance: EffectProvenance;
|
|
25
|
+
/** All contributing sources after requirement de-duplication. */
|
|
26
|
+
provenances?: readonly EffectProvenance[];
|
|
27
|
+
}
|
|
28
|
+
export interface ExecEffectNode {
|
|
29
|
+
kind: 'exec';
|
|
30
|
+
commandRedacted: string;
|
|
31
|
+
segmentHead: string;
|
|
32
|
+
requirements: readonly EffectRequirement[];
|
|
33
|
+
}
|
|
34
|
+
export interface LauncherEffectNode {
|
|
35
|
+
kind: 'launcher';
|
|
36
|
+
launcher: PackageExecLauncher | 'npm' | 'pnpm' | 'make';
|
|
37
|
+
phase: LauncherPhase;
|
|
38
|
+
opaque: boolean;
|
|
39
|
+
reason: string;
|
|
40
|
+
children: readonly EffectNode[];
|
|
41
|
+
}
|
|
42
|
+
export interface MergeEffectNode {
|
|
43
|
+
kind: 'merge';
|
|
44
|
+
children: readonly EffectNode[];
|
|
45
|
+
}
|
|
46
|
+
export type EffectNode = ExecEffectNode | LauncherEffectNode | MergeEffectNode;
|
|
47
|
+
export interface EffectPlan {
|
|
48
|
+
version: typeof EFFECT_PLAN_VERSION;
|
|
49
|
+
root: EffectNode;
|
|
50
|
+
inputFingerprint: string;
|
|
51
|
+
opacity: VerdictOpacity;
|
|
52
|
+
disposition: 'effects' | 'effect_free';
|
|
53
|
+
completeness: 'complete' | 'partial';
|
|
54
|
+
signals: readonly string[];
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const EFFECT_PLAN_VERSION = 1;
|
|
@@ -3,5 +3,6 @@ import type { EgressAllowlistEntry, EgressAllowlistFile } from './types.js';
|
|
|
3
3
|
export declare function egressAllowlistPath(config: BelayConfigV3, repoLocalStateDir: string): string;
|
|
4
4
|
export declare function loadEgressAllowlist(filePath: string): Promise<EgressAllowlistFile>;
|
|
5
5
|
export declare function saveEgressAllowlist(filePath: string, state: EgressAllowlistFile): Promise<void>;
|
|
6
|
+
export declare function mutateEgressAllowlist(filePath: string, mutate: (state: EgressAllowlistFile) => EgressAllowlistFile): Promise<EgressAllowlistFile>;
|
|
6
7
|
export declare function isHostAllowlisted(host: string, allowlist: EgressAllowlistFile): boolean;
|
|
7
8
|
export declare function addDomainToAllowlist(allowlist: EgressAllowlistFile, entry: EgressAllowlistEntry): EgressAllowlistFile;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { withStateFileLock } from '../capability/approval-state-mutation.js';
|
|
4
5
|
import { belayStateDir } from '../config.js';
|
|
5
6
|
export function egressAllowlistPath(config, repoLocalStateDir) {
|
|
6
7
|
return path.join(belayStateDir(config, repoLocalStateDir), 'egress-allowlist.json');
|
|
@@ -19,6 +20,13 @@ export async function saveEgressAllowlist(filePath, state) {
|
|
|
19
20
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
20
21
|
await writeFile(filePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
21
22
|
}
|
|
23
|
+
export async function mutateEgressAllowlist(filePath, mutate) {
|
|
24
|
+
return withStateFileLock(filePath, async () => {
|
|
25
|
+
const updated = mutate(await loadEgressAllowlist(filePath));
|
|
26
|
+
await saveEgressAllowlist(filePath, updated);
|
|
27
|
+
return updated;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
22
30
|
export function isHostAllowlisted(host, allowlist) {
|
|
23
31
|
const normalized = host.toLowerCase();
|
|
24
32
|
return allowlist.domains.some((entry) => entry.host.toLowerCase() === normalized);
|
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { compactApprovals, createApprovalRecord } from './approval.js';
|
|
3
3
|
import { issueApprovalToken } from './approval-token.js';
|
|
4
|
+
import { mutateApprovalStateWithRetry } from './capability/approval-state-mutation.js';
|
|
4
5
|
import { configuredControlPlaneDir } from './config.js';
|
|
5
|
-
import { addDomainToAllowlist,
|
|
6
|
+
import { addDomainToAllowlist, mutateEgressAllowlist } from './egress/allowlist.js';
|
|
6
7
|
import { parseHostFromSummary } from './egress/fingerprint.js';
|
|
7
8
|
import { notifyDeny } from './notify.js';
|
|
8
9
|
export async function ensurePendingEgressApproval(params) {
|
|
9
10
|
const { config, repoRoot, policyResult, store } = params;
|
|
10
|
-
const pending = await store.loadPending();
|
|
11
|
-
pending.state = compactApprovals(pending.state);
|
|
12
|
-
const existing = pending.state.approvals.find((approval) => approval.kind === 'egress' &&
|
|
13
|
-
approval.fingerprint === policyResult.fingerprint &&
|
|
14
|
-
approval.repoRoot === repoRoot);
|
|
15
|
-
if (existing) {
|
|
16
|
-
await store.writePending(pending.filePath, pending.state);
|
|
17
|
-
return { approvalId: existing.approvalId, approval: existing, created: false };
|
|
18
|
-
}
|
|
19
11
|
const approvalId = `belay_${randomUUID().replaceAll('-', '').slice(0, 12)}`;
|
|
20
|
-
const
|
|
12
|
+
const candidate = createApprovalRecord({
|
|
21
13
|
kind: 'egress',
|
|
22
14
|
fingerprint: policyResult.fingerprint,
|
|
23
15
|
repoRoot,
|
|
@@ -26,44 +18,47 @@ export async function ensurePendingEgressApproval(params) {
|
|
|
26
18
|
approvalTtlMinutes: config.approvalTtlMinutes,
|
|
27
19
|
approvalId,
|
|
28
20
|
});
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
const outcome = await mutateApprovalStateWithRetry({
|
|
22
|
+
load: store.loadPending,
|
|
23
|
+
write: store.writePending,
|
|
24
|
+
mutate: (state) => {
|
|
25
|
+
const compacted = compactApprovals(state);
|
|
26
|
+
const existing = compacted.approvals.find((approval) => approval.kind === 'egress' &&
|
|
27
|
+
approval.fingerprint === policyResult.fingerprint &&
|
|
28
|
+
approval.repoRoot === repoRoot);
|
|
29
|
+
if (existing) {
|
|
30
|
+
return {
|
|
31
|
+
state: compacted,
|
|
32
|
+
result: { approvalId: existing.approvalId, approval: existing, created: false },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
compacted.approvals.push(candidate);
|
|
36
|
+
return {
|
|
37
|
+
state: compacted,
|
|
38
|
+
result: { approvalId, approval: candidate, created: true },
|
|
39
|
+
};
|
|
40
|
+
},
|
|
39
41
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
return await fn();
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
release();
|
|
47
|
-
if (consumeLocks.get(key) === gate) {
|
|
48
|
-
consumeLocks.delete(key);
|
|
49
|
-
}
|
|
42
|
+
if (!outcome) {
|
|
43
|
+
throw new Error('Failed to persist pending egress approval');
|
|
50
44
|
}
|
|
45
|
+
return outcome;
|
|
51
46
|
}
|
|
52
47
|
export async function consumeApprovedEgress(params) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
approval.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
48
|
+
return mutateApprovalStateWithRetry({
|
|
49
|
+
load: params.store.loadApproved,
|
|
50
|
+
write: params.store.writeApproved,
|
|
51
|
+
mutate: (state) => {
|
|
52
|
+
const compacted = compactApprovals(state);
|
|
53
|
+
const index = compacted.approvals.findIndex((approval) => approval.kind === 'egress' &&
|
|
54
|
+
approval.fingerprint === params.fingerprint &&
|
|
55
|
+
approval.repoRoot === params.repoRoot);
|
|
56
|
+
if (index === -1) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const [approval] = compacted.approvals.splice(index, 1);
|
|
60
|
+
return { state: compacted, result: approval ?? null };
|
|
61
|
+
},
|
|
67
62
|
});
|
|
68
63
|
}
|
|
69
64
|
export async function notifyEgressDeny(params) {
|
|
@@ -98,8 +93,8 @@ export async function recordEgressApproval(params) {
|
|
|
98
93
|
const { recordApproval } = await import('./approval-service.js');
|
|
99
94
|
const pending = await params.store.loadPending();
|
|
100
95
|
const match = pending.state.approvals.find((approval) => approval.approvalId === params.approvalId);
|
|
101
|
-
const
|
|
102
|
-
if (params.scope === 'domain' && match && !
|
|
96
|
+
const pendingHost = match ? parseHostFromSummary(match.summary) : null;
|
|
97
|
+
if (params.scope === 'domain' && match && !pendingHost) {
|
|
103
98
|
return {
|
|
104
99
|
ok: false,
|
|
105
100
|
message: `Cannot add domain to egress allowlist: could not parse host from summary "${match.summary}".`,
|
|
@@ -112,16 +107,15 @@ export async function recordEgressApproval(params) {
|
|
|
112
107
|
requireSignedToken: params.requireSignedToken ?? false,
|
|
113
108
|
store: params.store,
|
|
114
109
|
});
|
|
110
|
+
const host = result.approval ? parseHostFromSummary(result.approval.summary) : pendingHost;
|
|
115
111
|
if (!result.ok || params.scope !== 'domain' || !host) {
|
|
116
112
|
return { ok: result.ok, message: result.message };
|
|
117
113
|
}
|
|
118
|
-
|
|
119
|
-
const updated = addDomainToAllowlist(allowlist, {
|
|
114
|
+
await mutateEgressAllowlist(params.store.allowlistPath, (allowlist) => addDomainToAllowlist(allowlist, {
|
|
120
115
|
host,
|
|
121
116
|
approvedAt: new Date().toISOString(),
|
|
122
117
|
approvalId: params.approvalId,
|
|
123
|
-
});
|
|
124
|
-
await saveEgressAllowlist(params.store.allowlistPath, updated);
|
|
118
|
+
}));
|
|
125
119
|
return {
|
|
126
120
|
ok: true,
|
|
127
121
|
message: `${result.message} Domain ${host} added to egress allowlist.`,
|
|
@@ -29,6 +29,11 @@ export interface GateVerdict extends GatePermissionResponse {
|
|
|
29
29
|
wouldBlock: boolean;
|
|
30
30
|
mode: 'enforce' | 'audit';
|
|
31
31
|
axes?: ClassifyResult['axes'];
|
|
32
|
+
capabilityRequests?: ClassifyResult['capabilityRequests'];
|
|
33
|
+
authorizationDecision?: ClassifyResult['authorizationDecision'];
|
|
34
|
+
effectPlan?: ClassifyResult['effectPlan'];
|
|
35
|
+
effectPlanPolicyDecisions?: ClassifyResult['effectPlanPolicyDecisions'];
|
|
36
|
+
boundaryProfile?: string;
|
|
32
37
|
}
|
|
33
38
|
export declare function isGatedAction(value: unknown): value is GatedAction;
|
|
34
39
|
export declare function classifyResultToGateVerdict(params: {
|
|
@@ -26,6 +26,11 @@ export function classifyResultToGateVerdict(params) {
|
|
|
26
26
|
user_message,
|
|
27
27
|
agent_message,
|
|
28
28
|
axes: result.axes,
|
|
29
|
+
capabilityRequests: result.capabilityRequests,
|
|
30
|
+
authorizationDecision: result.authorizationDecision,
|
|
31
|
+
effectPlan: result.effectPlan,
|
|
32
|
+
effectPlanPolicyDecisions: result.effectPlanPolicyDecisions,
|
|
33
|
+
boundaryProfile: result.boundaryProfile,
|
|
29
34
|
};
|
|
30
35
|
}
|
|
31
36
|
export function unnormalizedGateVerdict(params) {
|
package/dist/core/gate-engine.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
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';
|
|
5
6
|
import { classifierOptionsFromConfig } from './config.js';
|
|
7
|
+
import { buildCapabilityEffectPlan } from './effect-ir/build.js';
|
|
6
8
|
import { GATE_CONTRACT_VERSION } from './gate-contract.js';
|
|
7
9
|
import { mergeAgentAssessment } from './judgment.js';
|
|
8
10
|
import { classifyShell } from './verdict/adapter.js';
|
|
@@ -197,6 +199,10 @@ function applyToolSandboxPolicies(action, result, options) {
|
|
|
197
199
|
return next;
|
|
198
200
|
}
|
|
199
201
|
export async function classifyGatedAction(action, config, extraOptions = {}) {
|
|
202
|
+
const limitResult = checkGatedActionLimits(action);
|
|
203
|
+
if (limitResult) {
|
|
204
|
+
return attachEffectPlan(action, limitResult);
|
|
205
|
+
}
|
|
200
206
|
const options = { ...classifierOptionsFromConfig(config), ...extraOptions };
|
|
201
207
|
if (action.kind === 'shell') {
|
|
202
208
|
const command = action.command ?? shellCommandFromPayload(action.payload ?? {});
|
|
@@ -207,36 +213,52 @@ export async function classifyGatedAction(action, config, extraOptions = {}) {
|
|
|
207
213
|
result = applySandboxOutsideBoundary(command, action, result, options);
|
|
208
214
|
result = applyShellPeripheralPolicy(command, action, result, options);
|
|
209
215
|
if (!action.agentAssessment) {
|
|
210
|
-
return result;
|
|
216
|
+
return attachEffectPlan(action, result);
|
|
211
217
|
}
|
|
212
218
|
const merged = mergeAgentAssessment(result.assessment, action.agentAssessment);
|
|
213
219
|
if (!merged.mismatch) {
|
|
214
|
-
return { ...result, assessment: merged.assessment };
|
|
220
|
+
return attachEffectPlan(action, { ...result, assessment: merged.assessment });
|
|
215
221
|
}
|
|
216
|
-
return {
|
|
222
|
+
return attachEffectPlan(action, {
|
|
217
223
|
...result,
|
|
218
224
|
verdict: 'deny_pending_approval',
|
|
219
225
|
reason: 'agent_assessment_mismatch',
|
|
220
226
|
assessment: merged.assessment,
|
|
221
|
-
};
|
|
227
|
+
});
|
|
222
228
|
}
|
|
223
229
|
if (action.kind === 'subagent') {
|
|
224
|
-
return classifySubagent(action.payload ?? {}, action.repoRoot, options);
|
|
230
|
+
return attachEffectPlan(action, classifySubagent(action.payload ?? {}, action.repoRoot, options, config));
|
|
225
231
|
}
|
|
226
232
|
let result = await classifyToolUse(action.payload ?? {}, action.repoRoot, action.cwd, config, options);
|
|
227
233
|
result = applyToolSandboxPolicies(action, result, options);
|
|
228
234
|
if (!action.agentAssessment) {
|
|
229
|
-
return result;
|
|
235
|
+
return attachEffectPlan(action, result);
|
|
230
236
|
}
|
|
231
237
|
const merged = mergeAgentAssessment(result.assessment, action.agentAssessment);
|
|
232
238
|
if (!merged.mismatch) {
|
|
233
|
-
return { ...result, assessment: merged.assessment };
|
|
239
|
+
return attachEffectPlan(action, { ...result, assessment: merged.assessment });
|
|
234
240
|
}
|
|
235
|
-
return {
|
|
241
|
+
return attachEffectPlan(action, {
|
|
236
242
|
...result,
|
|
237
243
|
verdict: 'deny_pending_approval',
|
|
238
244
|
reason: 'agent_assessment_mismatch',
|
|
239
245
|
assessment: merged.assessment,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function attachEffectPlan(action, result) {
|
|
249
|
+
if (result.effectPlan) {
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
const summary = result.normalizedCommand ?? result.summary ?? action.command ?? action.kind;
|
|
253
|
+
return {
|
|
254
|
+
...result,
|
|
255
|
+
effectPlan: buildCapabilityEffectPlan({
|
|
256
|
+
actionKind: action.kind,
|
|
257
|
+
summary,
|
|
258
|
+
inputFingerprint: result.fingerprint,
|
|
259
|
+
requests: result.capabilityRequests ?? [],
|
|
260
|
+
effectFree: result.reason === 'read_only',
|
|
261
|
+
}),
|
|
240
262
|
};
|
|
241
263
|
}
|
|
242
264
|
export async function classifyGatedActionAsync(action, config, extraOptions = {}) {
|
package/dist/core/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type { ApprovalReplayHint, ReplayActionContext, ReplayAdapterId, } from '
|
|
|
3
3
|
/** @deprecated Use `buildRetryInstructionForConfig` */
|
|
4
4
|
export { approvalFlow, buildApprovalRecordedMessage, buildReplayEnvelopeFields, buildReplayHint, buildRetryInstructionForConfig, buildRetryInstructionForConfig as buildRetryInstruction, canAutoReplay, getExecutionLeaseMs, replayShellCommand, validateReplayEnvelope, } from './approval-replay.js';
|
|
5
5
|
export type { ApprovalStore } from './approval-service.js';
|
|
6
|
-
export { consumeApprovedAfterCliReplay, createGateApprovalStore, gateApprovalStoreFromDeps, recordApproval, } from './approval-service.js';
|
|
6
|
+
export { claimApprovedForReplay, consumeApprovedAfterCliReplay, createGateApprovalStore, gateApprovalStoreFromDeps, recordApproval, } from './approval-service.js';
|
|
7
7
|
export type { AuditMetricsReport } from './audit-metrics.js';
|
|
8
8
|
export { computeAuditMetrics, parseAuditNdjson } from './audit-metrics.js';
|
|
9
9
|
export { classifySubagent } from './classify-subagent.js';
|
|
@@ -16,6 +16,8 @@ export { classifyResultToGateVerdict, GATE_CONTRACT_VERSION, isGatedAction, unno
|
|
|
16
16
|
export { classifyGatedAction, GateNormalizationError, gateEnabledForAction, normalizeGatedAction, } from './gate-engine.js';
|
|
17
17
|
export { matchesSensitivePath } from './glob.js';
|
|
18
18
|
export { canonicalPath, hasOutsideRepoPath, normalizeToken, pathWithinRoot, relativeWithinRepo, resolveMutationTarget, } from './path-utils.js';
|
|
19
|
+
export type { RecoveryBackend, RecoveryCheckpointEntry, RecoveryCheckpointEntryV1, RecoveryCheckpointEntryV2, RecoveryCheckpointManifest, RecoveryCheckpointManifestV1, RecoveryCheckpointManifestV2, RecoveryCheckpointState, RecoveryCheckpointSummary, RecoveryFileSnapshotV1, RecoveryFileSnapshotV2, RecoveryProofV1, RecoveryReceiptV1, } from './recovery/index.js';
|
|
20
|
+
export { listRecoveryCheckpoints, RECOVERY_CHECKPOINT_CORRUPT, RECOVERY_CHECKPOINT_QUOTA, RECOVERY_RESTORE_CONFLICT, recoveryCheckpointStorageBytes, recoveryRestoreBinding, restoreRecoveryCheckpoint, showRecoveryCheckpoint, } from './recovery/index.js';
|
|
19
21
|
export { fingerprintReplayPayload, subagentFingerprintSource } from './replay-scrub.js';
|
|
20
22
|
export { scrubString, scrubValue } from './scrub.js';
|
|
21
23
|
export { findCommandSubstitutions, MAX_SUBSTITUTION_DEPTH } from './shell-substitution.js';
|
package/dist/core/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { APPROVAL_EXECUTION_LEASE_MS, approvalCommandMatch, compactApprovals, createApprovalRecord, createApprovalRecordWithEnvelope, isExecutionLeaseExpired, isExpired, mergeApprovalStates, nowIso, } from './approval.js';
|
|
2
2
|
/** @deprecated Use `buildRetryInstructionForConfig` */
|
|
3
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';
|
|
4
|
+
export { claimApprovedForReplay, consumeApprovedAfterCliReplay, createGateApprovalStore, gateApprovalStoreFromDeps, recordApproval, } from './approval-service.js';
|
|
5
5
|
export { computeAuditMetrics, parseAuditNdjson } from './audit-metrics.js';
|
|
6
6
|
export { classifySubagent } from './classify-subagent.js';
|
|
7
7
|
export { classifyToolUse } from './classify-tool.js';
|
|
@@ -12,6 +12,7 @@ export { classifyResultToGateVerdict, GATE_CONTRACT_VERSION, isGatedAction, unno
|
|
|
12
12
|
export { classifyGatedAction, GateNormalizationError, gateEnabledForAction, normalizeGatedAction, } from './gate-engine.js';
|
|
13
13
|
export { matchesSensitivePath } from './glob.js';
|
|
14
14
|
export { canonicalPath, hasOutsideRepoPath, normalizeToken, pathWithinRoot, relativeWithinRepo, resolveMutationTarget, } from './path-utils.js';
|
|
15
|
+
export { listRecoveryCheckpoints, RECOVERY_CHECKPOINT_CORRUPT, RECOVERY_CHECKPOINT_QUOTA, RECOVERY_RESTORE_CONFLICT, recoveryCheckpointStorageBytes, recoveryRestoreBinding, restoreRecoveryCheckpoint, showRecoveryCheckpoint, } from './recovery/index.js';
|
|
15
16
|
export { fingerprintReplayPayload, subagentFingerprintSource } from './replay-scrub.js';
|
|
16
17
|
export { scrubString, scrubValue } from './scrub.js';
|
|
17
18
|
export { findCommandSubstitutions, MAX_SUBSTITUTION_DEPTH } from './shell-substitution.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { loadApprovalState, saveApprovalState } from '../config-io.js';
|
|
2
|
+
import { loadApprovalState, pendingApprovalsPath, saveApprovalState } from '../config-io.js';
|
|
3
3
|
import { compactApprovals, createApprovalRecord } from './approval.js';
|
|
4
|
+
import { mutateApprovalStateWithRetry } from './capability/approval-state-mutation.js';
|
|
4
5
|
import { JUDGE_CLOUD_CONSENT_REASON } from './capability/reasons.js';
|
|
5
6
|
export { JUDGE_CLOUD_CONSENT_REASON };
|
|
6
7
|
export function judgeCloudConsentFingerprint(providerId, endpoint) {
|
|
@@ -10,18 +11,8 @@ export async function ensurePendingJudgeCloudConsentApproval(params) {
|
|
|
10
11
|
const { repoRoot, config, providerId, endpoint } = params;
|
|
11
12
|
const normalizedEndpoint = endpoint.trim();
|
|
12
13
|
const fingerprint = judgeCloudConsentFingerprint(providerId, normalizedEndpoint);
|
|
13
|
-
const pending = await loadApprovalState(repoRoot, 'pending-approvals.json', config);
|
|
14
|
-
const compacted = compactApprovals(pending);
|
|
15
|
-
const existing = compacted.approvals.find((approval) => approval.kind === 'capability' &&
|
|
16
|
-
approval.reason === JUDGE_CLOUD_CONSENT_REASON &&
|
|
17
|
-
approval.fingerprint === fingerprint &&
|
|
18
|
-
approval.repoRoot === repoRoot);
|
|
19
|
-
if (existing) {
|
|
20
|
-
await saveApprovalState(repoRoot, 'pending-approvals.json', compacted, config);
|
|
21
|
-
return { approvalId: existing.approvalId, created: false };
|
|
22
|
-
}
|
|
23
14
|
const approvalId = `belay_${randomUUID().replaceAll('-', '').slice(0, 12)}`;
|
|
24
|
-
const
|
|
15
|
+
const candidate = createApprovalRecord({
|
|
25
16
|
kind: 'capability',
|
|
26
17
|
fingerprint,
|
|
27
18
|
repoRoot,
|
|
@@ -32,7 +23,30 @@ export async function ensurePendingJudgeCloudConsentApproval(params) {
|
|
|
32
23
|
input: `belay judge use ${providerId} --endpoint ${normalizedEndpoint}`,
|
|
33
24
|
inputKind: 'shell',
|
|
34
25
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
const outcome = await mutateApprovalStateWithRetry({
|
|
27
|
+
load: async () => ({
|
|
28
|
+
filePath: pendingApprovalsPath(repoRoot, config),
|
|
29
|
+
state: await loadApprovalState(repoRoot, 'pending-approvals.json', config),
|
|
30
|
+
}),
|
|
31
|
+
write: async (_filePath, state) => saveApprovalState(repoRoot, 'pending-approvals.json', state, config),
|
|
32
|
+
mutate: (state) => {
|
|
33
|
+
const compacted = compactApprovals(state);
|
|
34
|
+
const existing = compacted.approvals.find((approval) => approval.kind === 'capability' &&
|
|
35
|
+
approval.reason === JUDGE_CLOUD_CONSENT_REASON &&
|
|
36
|
+
approval.fingerprint === fingerprint &&
|
|
37
|
+
approval.repoRoot === repoRoot);
|
|
38
|
+
if (existing) {
|
|
39
|
+
return {
|
|
40
|
+
state: compacted,
|
|
41
|
+
result: { approvalId: existing.approvalId, created: false },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
compacted.approvals.push(candidate);
|
|
45
|
+
return { state: compacted, result: { approvalId, created: true } };
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
if (!outcome) {
|
|
49
|
+
throw new Error('Failed to persist cloud judge consent approval');
|
|
50
|
+
}
|
|
51
|
+
return outcome;
|
|
38
52
|
}
|
|
@@ -2,20 +2,30 @@ import { repoLocalStateDirFor } from '../config-io.js';
|
|
|
2
2
|
import { normalizeJudgeProvider, scrubOptionsFromConfig } from './config.js';
|
|
3
3
|
import { resolveJudgeCredential } from './judge-api-key.js';
|
|
4
4
|
import { hasValidCloudConsent } from './judge-config.js';
|
|
5
|
+
import { formatJudgeRecoveryHint, inferProviderIdFromFallbackReason, } from './judge-fallback-hints.js';
|
|
5
6
|
import { discoverJudgeModels, modelPresenceFromDiscovery, } from './judge-model-discovery.js';
|
|
6
7
|
import { detectJudgeRuntimeCapabilities, resolveJudgeTransport } from './judge-runtime-detection.js';
|
|
7
8
|
import { createOllamaJudge, createOpenAiCompatibleJudge } from './verdict/judge.js';
|
|
8
9
|
import { stopJudgeBrokerDaemon } from './verdict/judge-broker-service.js';
|
|
9
10
|
import { getJudgeProviderCapabilities, getJudgeProviderSpec, isRemovedProviderId, normalizeLegacyProviderId, } from './verdict/judge-catalog.js';
|
|
10
11
|
import { createJudgeFromConfig, resolveJudgeModel } from './verdict/judge-factory.js';
|
|
12
|
+
import { resolveJudgeSmokeProbeTimeoutMs } from './verdict/judge-runtime-config.js';
|
|
11
13
|
import { listRepoJudgeSessionBrokers, stopRepoJudgeSessionBroker, } from './verdict/judge-session-broker.js';
|
|
12
14
|
import { clearJudgeSessionKillSwitch } from './verdict/judge-session-kill-switch.js';
|
|
15
|
+
function judgeShadowAdvisory(warnings, message) {
|
|
16
|
+
warnings.push(`[judge shadow advisory] ${message}`);
|
|
17
|
+
}
|
|
13
18
|
export async function diagnoseJudge(config, repoRoot = process.cwd(), options = {}) {
|
|
14
19
|
const shouldRunLiveProbe = options.liveProbe === true && !process.env.VITEST && !process.env.VITEST_WORKER_ID;
|
|
15
20
|
const issues = [];
|
|
16
21
|
const warnings = [];
|
|
17
22
|
const notes = [];
|
|
18
23
|
const judge = config.judge;
|
|
24
|
+
const gateMode = judge.mode ?? 'shadow';
|
|
25
|
+
notes.push(`Judge gate mode: ${gateMode} (sync Tier1 removed from hook gate; judge is shadow/async only)`);
|
|
26
|
+
if (gateMode === 'off') {
|
|
27
|
+
notes.push('Judge shadow is disabled (judge.mode=off).');
|
|
28
|
+
}
|
|
19
29
|
const provider = normalizeJudgeProvider(judge.provider);
|
|
20
30
|
const rawProviderId = judge.providerId ? String(judge.providerId) : undefined;
|
|
21
31
|
if (rawProviderId && isRemovedProviderId(rawProviderId)) {
|
|
@@ -36,15 +46,19 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
36
46
|
const capabilities = getJudgeProviderCapabilities(providerId);
|
|
37
47
|
const transport = resolveJudgeTransport(judge);
|
|
38
48
|
const runtime = detectJudgeRuntimeCapabilities(providerId);
|
|
49
|
+
const cursorAcpEnabled = transport === 'cursor-cli' &&
|
|
50
|
+
providerId === 'cursor' &&
|
|
51
|
+
config.judge.runtime?.session.enabled === true;
|
|
52
|
+
const effectiveTransport = cursorAcpEnabled ? 'cursor-acp' : transport;
|
|
39
53
|
notes.push(`Judge providerId: ${providerId}`);
|
|
40
54
|
notes.push(`Judge driver: ${provider}`);
|
|
41
55
|
notes.push(`Judge model requested: ${judge.model}`);
|
|
42
|
-
notes.push(`Judge transport: ${
|
|
56
|
+
notes.push(`Judge transport: ${effectiveTransport}`);
|
|
43
57
|
if (config.policy.modelAssist.enabled) {
|
|
44
58
|
warnings.push('policy.modelAssist is enabled but is not wired to v2 Tier1. Use top-level judge instead.');
|
|
45
59
|
}
|
|
46
60
|
if (capabilities?.requiresConsent && !hasValidCloudConsent(judge) && transport === 'http') {
|
|
47
|
-
|
|
61
|
+
judgeShadowAdvisory(warnings, 'Cloud judge consent is not recorded. Shadow cloud judge will fail until consent is granted.');
|
|
48
62
|
}
|
|
49
63
|
else if (judge.cloudConsent?.accepted) {
|
|
50
64
|
notes.push(`Cloud consent: accepted ${judge.cloudConsent.at} by ${judge.cloudConsent.by}`);
|
|
@@ -74,12 +88,12 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
74
88
|
if (providerId === 'ollama') {
|
|
75
89
|
notes.push(`Ollama endpoint: ${endpoint}`);
|
|
76
90
|
if (discovery.modelIds.length === 0) {
|
|
77
|
-
|
|
91
|
+
judgeShadowAdvisory(warnings, 'Ollama endpoint unreachable or returned no models. Shadow judge will not run.');
|
|
78
92
|
}
|
|
79
93
|
else {
|
|
80
94
|
const hasModel = modelCheck.status === 'found';
|
|
81
95
|
if (!hasModel) {
|
|
82
|
-
|
|
96
|
+
judgeShadowAdvisory(warnings, `Ollama model "${judge.model}" is not present. Pull it before enabling shadow comparisons.`);
|
|
83
97
|
}
|
|
84
98
|
else {
|
|
85
99
|
notes.push(`Ollama model "${judge.model}" is available.`);
|
|
@@ -103,7 +117,7 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
103
117
|
context: { cwd: process.cwd(), repoRoot: process.cwd() },
|
|
104
118
|
});
|
|
105
119
|
if (warmResult.reason === 'ollama_unavailable' || warmResult.reason === 'ollama_parse_error') {
|
|
106
|
-
|
|
120
|
+
judgeShadowAdvisory(warnings, `Ollama warm call failed: ${warmResult.reason}`);
|
|
107
121
|
}
|
|
108
122
|
else {
|
|
109
123
|
notes.push('Ollama warm call succeeded.');
|
|
@@ -111,24 +125,29 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
111
125
|
return { issues, warnings, notes, modelCheck };
|
|
112
126
|
}
|
|
113
127
|
if (transport === 'unavailable') {
|
|
114
|
-
|
|
128
|
+
judgeShadowAdvisory(warnings, 'No judge transport is available (configure endpoint or install native CLI). Shadow judge will not run.');
|
|
115
129
|
return { issues, warnings, notes, modelCheck };
|
|
116
130
|
}
|
|
117
131
|
if (transport.endsWith('-cli')) {
|
|
118
132
|
if (!runtime.cliTransport) {
|
|
119
|
-
|
|
133
|
+
judgeShadowAdvisory(warnings, `Native CLI transport (${transport}) is not available. Shadow judge will not run.`);
|
|
120
134
|
}
|
|
121
135
|
else if (!keyInfo.key && keyInfo.sourceKind !== 'host-session') {
|
|
122
|
-
|
|
136
|
+
judgeShadowAdvisory(warnings, 'Judge API key is not set for the configured credential mode. Shadow cloud judge will not run.');
|
|
123
137
|
}
|
|
124
138
|
else {
|
|
125
|
-
notes.push(
|
|
139
|
+
notes.push(cursorAcpEnabled
|
|
140
|
+
? 'Cursor ACP transport available.'
|
|
141
|
+
: `Native CLI transport available: ${transport}`);
|
|
142
|
+
if (cursorAcpEnabled) {
|
|
143
|
+
notes.push(`Cursor ACP session transport enabled (max ${config.judge.runtime?.session.maxTurns ?? 'unknown'} turns).`);
|
|
144
|
+
}
|
|
126
145
|
if (shouldRunLiveProbe) {
|
|
127
146
|
const smokeConfig = {
|
|
128
147
|
...config,
|
|
129
148
|
judge: {
|
|
130
149
|
...judge,
|
|
131
|
-
timeoutMs:
|
|
150
|
+
timeoutMs: resolveJudgeSmokeProbeTimeoutMs(judge.timeoutMs),
|
|
132
151
|
},
|
|
133
152
|
};
|
|
134
153
|
const smokeJudge = createJudgeFromConfig(smokeConfig, { repoRoot });
|
|
@@ -140,10 +159,16 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
140
159
|
smokeJudge.lastTrace?.fallbackReason ??
|
|
141
160
|
smokeResult.reason;
|
|
142
161
|
if (smokeJudge.lastTrace?.provider === 'fallback') {
|
|
143
|
-
|
|
162
|
+
const recoveryHint = formatJudgeRecoveryHint(inferProviderIdFromFallbackReason(smokeFallback, providerId), smokeFallback);
|
|
163
|
+
const smokeLabel = cursorAcpEnabled ? 'Cursor ACP' : 'Native CLI transport';
|
|
164
|
+
judgeShadowAdvisory(warnings, recoveryHint
|
|
165
|
+
? `${smokeLabel} smoke probe failed (${smokeFallback}). ${recoveryHint}`
|
|
166
|
+
: `${smokeLabel} smoke probe failed (${smokeFallback}).`);
|
|
144
167
|
}
|
|
145
168
|
else {
|
|
146
|
-
notes.push(
|
|
169
|
+
notes.push(cursorAcpEnabled
|
|
170
|
+
? 'Cursor ACP smoke probe succeeded.'
|
|
171
|
+
: 'Native CLI transport smoke probe succeeded.');
|
|
147
172
|
}
|
|
148
173
|
}
|
|
149
174
|
}
|
|
@@ -153,7 +178,7 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
153
178
|
notes.push(`HTTP endpoint: ${judge.endpoint}`);
|
|
154
179
|
}
|
|
155
180
|
if (!keyInfo.key) {
|
|
156
|
-
|
|
181
|
+
judgeShadowAdvisory(warnings, 'Judge API key is not set for the configured credential mode. Shadow cloud judge will not run.');
|
|
157
182
|
}
|
|
158
183
|
else {
|
|
159
184
|
notes.push(`Credential source: ${keyInfo.source ?? keyInfo.sourceKind}`);
|
|
@@ -188,7 +213,7 @@ export async function diagnoseJudge(config, repoRoot = process.cwd(), options =
|
|
|
188
213
|
});
|
|
189
214
|
if (dryRun.reason.startsWith('openai_compatible_') ||
|
|
190
215
|
dryRun.reason === 'outbound_scrub_failed') {
|
|
191
|
-
|
|
216
|
+
judgeShadowAdvisory(warnings, `HTTP judge dry-run failed: ${dryRun.reason}`);
|
|
192
217
|
}
|
|
193
218
|
else {
|
|
194
219
|
notes.push('HTTP judge dry-run succeeded.');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ClassifyResult } from './types.js';
|
|
2
|
+
export declare function extractJudgeFallbackReason(result: Pick<ClassifyResult, 'axes' | 'assessment'>): string | undefined;
|
|
3
|
+
export declare function isJudgeInfrastructureFailure(result: Pick<ClassifyResult, 'axes' | 'assessment' | 'reason'>): boolean;
|
|
4
|
+
export declare function inferProviderIdFromFallbackReason(fallbackReason: string | undefined, fallbackProviderId?: string): string;
|
|
5
|
+
export declare function formatJudgeRecoveryHint(providerId: string, fallbackReason?: string): string | null;
|
|
6
|
+
export declare function formatJudgeInfrastructureDenyMessage(params: {
|
|
7
|
+
providerId: string;
|
|
8
|
+
fallbackReason?: string;
|
|
9
|
+
command?: string;
|
|
10
|
+
}): {
|
|
11
|
+
user_message: string;
|
|
12
|
+
agent_message: string;
|
|
13
|
+
};
|