@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.
Files changed (240) hide show
  1. package/README.md +27 -2
  2. package/dist/adapters/claude/runtime-entry.js +54 -25
  3. package/dist/adapters/layouts/scope.js +3 -2
  4. package/dist/adapters/shared/gate-runtime.d.ts +8 -2
  5. package/dist/adapters/shared/gate-runtime.js +579 -181
  6. package/dist/bundle/claude-runtime.mjs +13574 -6334
  7. package/dist/bundle/codex-runtime.mjs +13108 -5903
  8. package/dist/bundle/cursor-runtime.mjs +12676 -5476
  9. package/dist/cli.js +80 -2
  10. package/dist/commands/approve.js +34 -10
  11. package/dist/commands/classify-for-report.js +1 -1
  12. package/dist/commands/config.d.ts +8 -0
  13. package/dist/commands/config.js +134 -11
  14. package/dist/commands/doctor.js +87 -1
  15. package/dist/commands/explain.js +11 -1
  16. package/dist/commands/judge.js +3 -2
  17. package/dist/commands/recovery-checkpoints.d.ts +164 -0
  18. package/dist/commands/recovery-checkpoints.js +347 -0
  19. package/dist/commands/revoke.js +19 -7
  20. package/dist/commands/session.d.ts +20 -0
  21. package/dist/commands/session.js +51 -0
  22. package/dist/commands/status.js +7 -0
  23. package/dist/config-io.js +91 -23
  24. package/dist/conformance/guarantee-posture.d.ts +21 -0
  25. package/dist/conformance/guarantee-posture.js +46 -0
  26. package/dist/conformance/guarantee-table.d.ts +1 -0
  27. package/dist/conformance/guarantee-table.js +20 -9
  28. package/dist/conformance/types.d.ts +1 -0
  29. package/dist/core/approval-replay.d.ts +1 -1
  30. package/dist/core/approval-replay.js +4 -1
  31. package/dist/core/approval-service.d.ts +6 -1
  32. package/dist/core/approval-service.js +87 -24
  33. package/dist/core/approval.d.ts +4 -0
  34. package/dist/core/approval.js +19 -5
  35. package/dist/core/audit-types.d.ts +13 -0
  36. package/dist/core/capability/approval-state-mutation.d.ts +32 -0
  37. package/dist/core/capability/approval-state-mutation.js +165 -0
  38. package/dist/core/capability/approval-v3.d.ts +32 -0
  39. package/dist/core/capability/approval-v3.js +114 -0
  40. package/dist/core/capability/attestation.d.ts +16 -0
  41. package/dist/core/capability/attestation.js +62 -0
  42. package/dist/core/capability/boundary-attestation-sign.d.ts +21 -0
  43. package/dist/core/capability/boundary-attestation-sign.js +84 -0
  44. package/dist/core/capability/boundary-driver-container.d.ts +19 -0
  45. package/dist/core/capability/boundary-driver-container.js +189 -0
  46. package/dist/core/capability/boundary-driver.d.ts +29 -0
  47. package/dist/core/capability/boundary-driver.js +44 -0
  48. package/dist/core/capability/boundary-egress.d.ts +21 -0
  49. package/dist/core/capability/boundary-egress.js +109 -0
  50. package/dist/core/capability/boundary-grant-materialize.d.ts +14 -0
  51. package/dist/core/capability/boundary-grant-materialize.js +93 -0
  52. package/dist/core/capability/boundary-profile.d.ts +15 -0
  53. package/dist/core/capability/boundary-profile.js +25 -0
  54. package/dist/core/capability/boundary-run.d.ts +48 -0
  55. package/dist/core/capability/boundary-run.js +51 -0
  56. package/dist/core/capability/boundary-session.d.ts +61 -0
  57. package/dist/core/capability/boundary-session.js +138 -0
  58. package/dist/core/capability/boundary-workspace-mount.d.ts +5 -0
  59. package/dist/core/capability/boundary-workspace-mount.js +68 -0
  60. package/dist/core/capability/capability-request-hash.d.ts +2 -0
  61. package/dist/core/capability/capability-request-hash.js +8 -0
  62. package/dist/core/capability/gate-latency-ratchet.d.ts +7 -0
  63. package/dist/core/capability/gate-latency-ratchet.js +18 -0
  64. package/dist/core/capability/gate-policy-shadow.d.ts +25 -0
  65. package/dist/core/capability/gate-policy-shadow.js +130 -0
  66. package/dist/core/capability/gate-shadow-audit.d.ts +1 -0
  67. package/dist/core/capability/gate-shadow-audit.js +1 -0
  68. package/dist/core/capability/gate-shadow-ratchet.d.ts +14 -0
  69. package/dist/core/capability/gate-shadow-ratchet.js +81 -0
  70. package/dist/core/capability/grant-consumption.d.ts +4 -0
  71. package/dist/core/capability/grant-consumption.js +11 -0
  72. package/dist/core/capability/grant-lease.d.ts +44 -0
  73. package/dist/core/capability/grant-lease.js +243 -0
  74. package/dist/core/capability/grant-loader.d.ts +12 -0
  75. package/dist/core/capability/grant-loader.js +23 -0
  76. package/dist/core/capability/grant-match.d.ts +6 -0
  77. package/dist/core/capability/grant-match.js +96 -0
  78. package/dist/core/capability/grant.d.ts +16 -0
  79. package/dist/core/capability/grant.js +16 -0
  80. package/dist/core/capability/index.d.ts +8 -0
  81. package/dist/core/capability/index.js +7 -0
  82. package/dist/core/capability/limits.d.ts +7 -0
  83. package/dist/core/capability/limits.js +55 -0
  84. package/dist/core/capability/policy-bridge.d.ts +18 -0
  85. package/dist/core/capability/policy-bridge.js +51 -0
  86. package/dist/core/capability/policy-engine.d.ts +77 -0
  87. package/dist/core/capability/policy-engine.js +663 -0
  88. package/dist/core/capability/policy-types.d.ts +20 -0
  89. package/dist/core/capability/policy-types.js +1 -0
  90. package/dist/core/capability/request.d.ts +48 -0
  91. package/dist/core/capability/request.js +1 -0
  92. package/dist/core/capability/resolver.d.ts +4 -0
  93. package/dist/core/capability/resolver.js +4 -0
  94. package/dist/core/classify-subagent.d.ts +2 -1
  95. package/dist/core/classify-subagent.js +38 -2
  96. package/dist/core/classify-tool.js +126 -62
  97. package/dist/core/config.d.ts +34 -3
  98. package/dist/core/config.js +118 -3
  99. package/dist/core/effect-ir/audit.d.ts +9 -0
  100. package/dist/core/effect-ir/audit.js +86 -0
  101. package/dist/core/effect-ir/build.d.ts +31 -0
  102. package/dist/core/effect-ir/build.js +277 -0
  103. package/dist/core/effect-ir/index.d.ts +8 -0
  104. package/dist/core/effect-ir/index.js +7 -0
  105. package/dist/core/effect-ir/normalize.d.ts +4 -0
  106. package/dist/core/effect-ir/normalize.js +104 -0
  107. package/dist/core/effect-ir/package-exec-eval.d.ts +8 -0
  108. package/dist/core/effect-ir/package-exec-eval.js +152 -0
  109. package/dist/core/effect-ir/package-exec.d.ts +29 -0
  110. package/dist/core/effect-ir/package-exec.js +292 -0
  111. package/dist/core/effect-ir/policy.d.ts +13 -0
  112. package/dist/core/effect-ir/policy.js +77 -0
  113. package/dist/core/effect-ir/types.d.ts +55 -0
  114. package/dist/core/effect-ir/types.js +1 -0
  115. package/dist/core/egress/allowlist.d.ts +1 -0
  116. package/dist/core/egress/allowlist.js +8 -0
  117. package/dist/core/egress-approval.js +45 -51
  118. package/dist/core/gate-contract.d.ts +5 -0
  119. package/dist/core/gate-contract.js +5 -0
  120. package/dist/core/gate-engine.js +30 -8
  121. package/dist/core/index.d.ts +3 -1
  122. package/dist/core/index.js +2 -1
  123. package/dist/core/judge-cloud-consent.js +29 -15
  124. package/dist/core/judge-doctor.js +39 -14
  125. package/dist/core/judge-fallback-hints.d.ts +13 -0
  126. package/dist/core/judge-fallback-hints.js +108 -0
  127. package/dist/core/network-endpoint.d.ts +10 -0
  128. package/dist/core/network-endpoint.js +51 -0
  129. package/dist/core/path-utils.d.ts +1 -0
  130. package/dist/core/path-utils.js +5 -2
  131. package/dist/core/process-runner.d.ts +10 -0
  132. package/dist/core/process-runner.js +124 -0
  133. package/dist/core/recover-advice.js +2 -1
  134. package/dist/core/recovery/artifact-store.d.ts +29 -0
  135. package/dist/core/recovery/artifact-store.js +336 -0
  136. package/dist/core/recovery/capability.d.ts +3 -0
  137. package/dist/core/recovery/capability.js +22 -0
  138. package/dist/core/recovery/checkpoint.d.ts +25 -0
  139. package/dist/core/recovery/checkpoint.js +260 -0
  140. package/dist/core/recovery/fail-closed.d.ts +6 -0
  141. package/dist/core/recovery/fail-closed.js +25 -0
  142. package/dist/core/recovery/index.d.ts +5 -0
  143. package/dist/core/recovery/index.js +4 -0
  144. package/dist/core/recovery/operator-guidance.d.ts +7 -0
  145. package/dist/core/recovery/operator-guidance.js +39 -0
  146. package/dist/core/recovery/reconcile.d.ts +3 -0
  147. package/dist/core/recovery/reconcile.js +48 -0
  148. package/dist/core/recovery/resource-identity.d.ts +4 -0
  149. package/dist/core/recovery/resource-identity.js +34 -0
  150. package/dist/core/recovery/restore.d.ts +13 -0
  151. package/dist/core/recovery/restore.js +114 -0
  152. package/dist/core/recovery/snapshot-node.d.ts +27 -0
  153. package/dist/core/recovery/snapshot-node.js +261 -0
  154. package/dist/core/recovery/types.d.ts +104 -0
  155. package/dist/core/recovery/types.js +1 -0
  156. package/dist/core/standing-allow.js +3 -0
  157. package/dist/core/transactional/apply-observed-changes.d.ts +13 -0
  158. package/dist/core/transactional/apply-observed-changes.js +275 -0
  159. package/dist/core/transactional/backend-selector.d.ts +8 -0
  160. package/dist/core/transactional/backend-selector.js +118 -0
  161. package/dist/core/transactional/backend.d.ts +55 -0
  162. package/dist/core/transactional/backend.js +1 -0
  163. package/dist/core/transactional/diff-evaluator.js +4 -1
  164. package/dist/core/transactional/eligibility.js +4 -0
  165. package/dist/core/transactional/file-checkpoint-backend.d.ts +3 -0
  166. package/dist/core/transactional/file-checkpoint-backend.js +278 -0
  167. package/dist/core/transactional/file-checkpoint-git.d.ts +11 -0
  168. package/dist/core/transactional/file-checkpoint-git.js +217 -0
  169. package/dist/core/transactional/file-checkpoint-staging.d.ts +13 -0
  170. package/dist/core/transactional/file-checkpoint-staging.js +54 -0
  171. package/dist/core/transactional/file-clone.d.ts +15 -0
  172. package/dist/core/transactional/file-clone.js +139 -0
  173. package/dist/core/transactional/file-tree-path.d.ts +7 -0
  174. package/dist/core/transactional/file-tree-path.js +50 -0
  175. package/dist/core/transactional/file-tree.d.ts +41 -0
  176. package/dist/core/transactional/file-tree.js +186 -0
  177. package/dist/core/transactional/git-worktree-backend.d.ts +2 -0
  178. package/dist/core/transactional/git-worktree-backend.js +50 -0
  179. package/dist/core/transactional/git-worktree.d.ts +14 -9
  180. package/dist/core/transactional/git-worktree.js +62 -87
  181. package/dist/core/transactional/index.d.ts +11 -0
  182. package/dist/core/transactional/index.js +10 -0
  183. package/dist/core/transactional/reasons.d.ts +1 -0
  184. package/dist/core/transactional/reasons.js +5 -0
  185. package/dist/core/transactional/runner.js +171 -23
  186. package/dist/core/transactional/snapshot-node.d.ts +26 -0
  187. package/dist/core/transactional/snapshot-node.js +87 -0
  188. package/dist/core/transactional/types.d.ts +21 -1
  189. package/dist/core/types.d.ts +30 -3
  190. package/dist/core/verdict/adapter.d.ts +3 -4
  191. package/dist/core/verdict/adapter.js +24 -12
  192. package/dist/core/verdict/cursor-acp-client.d.ts +67 -0
  193. package/dist/core/verdict/cursor-acp-client.js +345 -0
  194. package/dist/core/verdict/egress-classify.d.ts +2 -2
  195. package/dist/core/verdict/egress-classify.js +2 -2
  196. package/dist/core/verdict/git-classifier.d.ts +26 -0
  197. package/dist/core/verdict/git-classifier.js +533 -0
  198. package/dist/core/verdict/judge-audit.js +1 -0
  199. package/dist/core/verdict/judge-baseline.d.ts +1 -1
  200. package/dist/core/verdict/judge-baseline.js +1 -0
  201. package/dist/core/verdict/judge-broker-service.js +12 -0
  202. package/dist/core/verdict/judge-catalog.js +1 -0
  203. package/dist/core/verdict/judge-cli-fingerprint.d.ts +1 -0
  204. package/dist/core/verdict/judge-cli-fingerprint.js +6 -0
  205. package/dist/core/verdict/judge-cli.js +5 -3
  206. package/dist/core/verdict/judge-provider-matrix.js +1 -1
  207. package/dist/core/verdict/judge-runtime-config.d.ts +2 -0
  208. package/dist/core/verdict/judge-runtime-config.js +10 -6
  209. package/dist/core/verdict/judge-session-broker.d.ts +1 -1
  210. package/dist/core/verdict/judge-session-broker.js +10 -5
  211. package/dist/core/verdict/judge-transport.d.ts +5 -1
  212. package/dist/core/verdict/judge-transport.js +24 -14
  213. package/dist/core/verdict/judge.d.ts +2 -15
  214. package/dist/core/verdict/judge.js +5 -68
  215. package/dist/core/verdict/launcher-resolve.d.ts +2 -0
  216. package/dist/core/verdict/launcher-resolve.js +14 -16
  217. package/dist/core/verdict/prescan.d.ts +15 -0
  218. package/dist/core/verdict/prescan.js +68 -0
  219. package/dist/core/verdict/shell-policy.d.ts +26 -0
  220. package/dist/core/verdict/shell-policy.js +40 -0
  221. package/dist/core/verdict/shell-semantics.d.ts +24 -0
  222. package/dist/core/verdict/shell-semantics.js +225 -0
  223. package/dist/core/verdict/types.d.ts +18 -1
  224. package/dist/core/verdict/verdict.js +313 -178
  225. package/dist/corpus/evaluate.js +3 -7
  226. package/dist/corpus/gate-latency-budget.d.ts +39 -0
  227. package/dist/corpus/gate-latency-budget.js +33 -0
  228. package/dist/corpus/mutators.d.ts +1 -1
  229. package/dist/corpus/mutators.js +11 -0
  230. package/dist/corpus/runtime-match.js +1 -3
  231. package/dist/corpus/standing-allow-catalog.generated.js +29 -9
  232. package/dist/judge-broker-daemon.js +15 -1
  233. package/dist/services/sandbox-service.d.ts +4 -0
  234. package/dist/services/sandbox-service.js +28 -2
  235. package/dist/types.d.ts +13 -2
  236. package/dist/version.d.ts +1 -1
  237. package/dist/version.js +1 -1
  238. package/package.json +1 -1
  239. package/skills/belay/SKILL.md +8 -3
  240. package/skills/belay/belay-approve.md +10 -7
@@ -0,0 +1,48 @@
1
+ export declare const CAPABILITY_REQUEST_VERSION: 1;
2
+ export type CapabilityAction = 'fs.read' | 'fs.write' | 'process.exec' | 'network.connect' | 'secret.read' | 'git.ref.write' | 'control_plane.write' | 'indeterminate';
3
+ export type CapabilityEvidenceLevel = 'certain' | 'possible' | 'indeterminate';
4
+ export type CapabilityHookKind = 'shell' | 'tool' | 'subagent';
5
+ export interface CapabilityPrincipal {
6
+ adapter?: string;
7
+ repoRoot: string;
8
+ sessionHash?: string;
9
+ }
10
+ export type CapabilityResource = {
11
+ kind: 'path';
12
+ path: string;
13
+ } | {
14
+ kind: 'network';
15
+ host: string;
16
+ port?: number;
17
+ protocol?: string;
18
+ } | {
19
+ kind: 'executable';
20
+ command: string;
21
+ } | {
22
+ kind: 'package-cache';
23
+ manager: 'npm' | 'pnpm';
24
+ } | {
25
+ kind: 'git-ref';
26
+ ref: string;
27
+ } | {
28
+ kind: 'unknown';
29
+ };
30
+ export interface CapabilityRequestContext {
31
+ cwd: string;
32
+ inputFingerprint: string;
33
+ hookKind: CapabilityHookKind;
34
+ analysisBasis: string[];
35
+ boundaryProfile?: string;
36
+ }
37
+ export interface CapabilityEvidence {
38
+ level: CapabilityEvidenceLevel;
39
+ signals: string[];
40
+ }
41
+ export interface CapabilityRequestV1 {
42
+ version: typeof CAPABILITY_REQUEST_VERSION;
43
+ principal: CapabilityPrincipal;
44
+ action: CapabilityAction;
45
+ resource: CapabilityResource;
46
+ context: CapabilityRequestContext;
47
+ evidence: CapabilityEvidence;
48
+ }
@@ -0,0 +1 @@
1
+ export const CAPABILITY_REQUEST_VERSION = 1;
@@ -0,0 +1,4 @@
1
+ export { evaluateSegmentShellPolicy, type SegmentShellPolicyInput, } from '../verdict/shell-policy.js';
2
+ export { BOUNDARY_PROFILE_L3_POLICY, checkGatedActionLimits } from './limits.js';
3
+ export { type CapabilityAuthorizationMetadata, policyReasonToLegacyReason, singleCapabilityMetadata, } from './policy-bridge.js';
4
+ export { buildFileMutationCapabilityRequest, buildShellCapabilityRequest, buildSubagentCapabilityRequest, evaluateFileMutationPolicy, evaluateShellPolicy, evaluateSubagentPolicy, type FileMutationCapabilityAnalysis, policyDecisionRequiresAsk, type ShellCapabilityAnalysis, type SubagentCapabilityAnalysis, } from './policy-engine.js';
@@ -0,0 +1,4 @@
1
+ export { evaluateSegmentShellPolicy, } from '../verdict/shell-policy.js';
2
+ export { BOUNDARY_PROFILE_L3_POLICY, checkGatedActionLimits } from './limits.js';
3
+ export { policyReasonToLegacyReason, singleCapabilityMetadata, } from './policy-bridge.js';
4
+ export { buildFileMutationCapabilityRequest, buildShellCapabilityRequest, buildSubagentCapabilityRequest, evaluateFileMutationPolicy, evaluateShellPolicy, evaluateSubagentPolicy, policyDecisionRequiresAsk, } from './policy-engine.js';
@@ -1,2 +1,3 @@
1
+ import type { BelayConfigV3 } from './config.js';
1
2
  import type { ClassifierOptions, ClassifyResult } from './types.js';
2
- export declare function classifySubagent(payload: Record<string, unknown>, repoRoot: string, options?: ClassifierOptions): ClassifyResult;
3
+ export declare function classifySubagent(payload: Record<string, unknown>, repoRoot: string, options: ClassifierOptions | undefined, config: BelayConfigV3): ClassifyResult;
@@ -1,3 +1,6 @@
1
+ import { BOUNDARY_PROFILE_L3_L4_ONLY } from './capability/boundary-profile.js';
2
+ import { policyReasonToLegacyReason } from './capability/policy-bridge.js';
3
+ import { evaluateSubagentPolicy, policyDecisionRequiresAsk } from './capability/policy-engine.js';
1
4
  import { canonicalStringify, subagentFingerprint } from './fingerprint.js';
2
5
  import { subagentFingerprintSource } from './replay-scrub.js';
3
6
  import { scrubValue } from './scrub.js';
@@ -25,7 +28,7 @@ function extractSubagentText(payload, options) {
25
28
  function fingerprintSource(payload, options) {
26
29
  return subagentFingerprintSource(payload, options.scrubOptions ?? {});
27
30
  }
28
- export function classifySubagent(payload, repoRoot, options = {}) {
31
+ export function classifySubagent(payload, repoRoot, options = {}, config) {
29
32
  const kind = payload.tool_name === 'Task' ? 'Task' : String(payload.subagent_type ?? 'generalPurpose');
30
33
  const scrubbed = fingerprintSource(payload, options);
31
34
  const summary = extractSubagentText(payload, options);
@@ -35,6 +38,36 @@ export function classifySubagent(payload, repoRoot, options = {}) {
35
38
  const pattern = new RegExp(`\\b${term}\\b`, 'i');
36
39
  return pattern.test(lowered);
37
40
  });
41
+ const signals = hasExternalTerm ? ['subagent_external_intent_hint'] : ['subagent_default_review'];
42
+ const { request, decision } = evaluateSubagentPolicy({
43
+ subagentType: kind,
44
+ summary,
45
+ repoRoot,
46
+ cwd: repoRoot,
47
+ inputFingerprint: fingerprint,
48
+ signals,
49
+ }, config, {
50
+ grants: options.grants,
51
+ attestation: options.attestation,
52
+ egressProxyActive: options.egressProxyActive,
53
+ });
54
+ if (policyDecisionRequiresAsk(decision)) {
55
+ return {
56
+ verdict: 'deny_pending_approval',
57
+ reason: policyReasonToLegacyReason(decision),
58
+ summary,
59
+ fingerprint,
60
+ assessment: {
61
+ reversibility: 'recoverable_with_cost',
62
+ external: hasExternalTerm,
63
+ blastRadius: 'subagent task scope',
64
+ confidence: hasExternalTerm ? 0.7 : 0.67,
65
+ signals: [...signals, ...decision.signals],
66
+ },
67
+ capabilityRequests: [request],
68
+ authorizationDecision: decision,
69
+ };
70
+ }
38
71
  return {
39
72
  verdict: 'allow_flagged',
40
73
  reason: 'subagent_review',
@@ -45,7 +78,10 @@ export function classifySubagent(payload, repoRoot, options = {}) {
45
78
  external: false,
46
79
  blastRadius: 'subagent task scope',
47
80
  confidence: hasExternalTerm ? 0.7 : 0.67,
48
- signals: hasExternalTerm ? ['subagent_external_intent_hint'] : ['subagent_default_review'],
81
+ signals,
49
82
  },
83
+ capabilityRequests: [request],
84
+ authorizationDecision: decision,
85
+ boundaryProfile: options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY,
50
86
  };
51
87
  }
@@ -1,11 +1,14 @@
1
1
  import path from 'node:path';
2
+ import { BOUNDARY_PROFILE_L3_L4_ONLY } from './capability/boundary-profile.js';
3
+ import { policyReasonToLegacyReason } from './capability/policy-bridge.js';
4
+ import { evaluateFileMutationPolicy, policyDecisionRequiresAsk, } from './capability/policy-engine.js';
2
5
  import { canonicalStringify, toolFingerprint } from './fingerprint.js';
3
6
  import { matchesSensitivePath } from './glob.js';
4
7
  import { pathWithinRoot, resolveWorkspaceRootMatch } from './path-utils.js';
5
8
  import { scrubValue } from './scrub.js';
6
9
  import { classifyShell, resolveClassifierTrustedCwd } from './verdict/adapter.js';
7
- import { mutationPrescanRequiresAsk, tier1RequiresAsk } from './verdict/judge.js';
8
- import { createJudgeFromConfig } from './verdict/judge-factory.js';
10
+ import { isGitPath } from './verdict/containment.js';
11
+ import { mutationPrescanRequiresAsk } from './verdict/prescan.js';
9
12
  const DEFAULT_SENSITIVE_PATHS = ['.env', '.env.*', '**/credentials/**'];
10
13
  const FILE_WRITE_TOOL_NAMES = new Set(['write']);
11
14
  const FILE_EDIT_TOOL_NAMES = new Set([
@@ -18,6 +21,19 @@ const FILE_EDIT_TOOL_NAMES = new Set([
18
21
  ]);
19
22
  const FILE_DELETE_TOOL_NAMES = new Set(['delete']);
20
23
  const APPLY_PATCH_TOOL_NAMES = new Set(['apply_patch', 'applypatch']);
24
+ function policyAuth(options) {
25
+ if (!options.grants &&
26
+ options.attestation === undefined &&
27
+ options.egressProxyActive === undefined) {
28
+ return undefined;
29
+ }
30
+ return {
31
+ grants: options.grants,
32
+ attestation: options.attestation,
33
+ egressProxyActive: options.egressProxyActive,
34
+ sensitivePaths: options.sensitivePaths,
35
+ };
36
+ }
21
37
  function scrubPayload(value, options) {
22
38
  return scrubValue(value, options.scrubOptions);
23
39
  }
@@ -76,11 +92,7 @@ function applyPatchTargets(patch) {
76
92
  function normalizedToolName(toolName) {
77
93
  return toolName.trim().toLowerCase();
78
94
  }
79
- function resolveFileTier1Judge(config, options, repoRoot) {
80
- return options.tier1Judge ?? createJudgeFromConfig(config, { repoRoot });
81
- }
82
- async function classifyFileMutationWithTier1(params) {
83
- const judge = resolveFileTier1Judge(params.config, params.options, params.repoRoot);
95
+ function classifyFileMutationWithPolicy(params) {
84
96
  const trustedCwd = resolveClassifierTrustedCwd(params.cwd, params.options);
85
97
  const prescan = mutationPrescanRequiresAsk({
86
98
  targets: [params.filePath],
@@ -91,11 +103,29 @@ async function classifyFileMutationWithTier1(params) {
91
103
  sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths,
92
104
  });
93
105
  if (prescan) {
106
+ const fingerprint = toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot);
107
+ const { request, decision } = evaluateFileMutationPolicy({
108
+ hookKind: 'tool',
109
+ toolKind: params.toolKind,
110
+ filePath: params.filePath,
111
+ resolvedPath: params.resolvedPath,
112
+ repoRoot: params.repoRoot,
113
+ cwd: params.cwd,
114
+ inputFingerprint: fingerprint,
115
+ signals: [...params.signals, 'tier1_catastrophic', prescan.reason],
116
+ isDelete: params.isDelete,
117
+ locationLabel: params.locationLabel,
118
+ trustedWorkspaceRoots: params.options.trustedWorkspaceRoots,
119
+ sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths,
120
+ }, params.config, policyAuth(params.options));
121
+ const legacyReason = policyDecisionRequiresAsk(decision)
122
+ ? policyReasonToLegacyReason(decision)
123
+ : 'tier1_catastrophic';
94
124
  return {
95
125
  verdict: 'deny_pending_approval',
96
- reason: 'tier1_catastrophic',
126
+ reason: legacyReason,
97
127
  summary: params.filePath,
98
- fingerprint: toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot),
128
+ fingerprint,
99
129
  assessment: {
100
130
  reversibility: 'irreversible',
101
131
  external: params.locationLabel === 'outside_repo',
@@ -103,20 +133,32 @@ async function classifyFileMutationWithTier1(params) {
103
133
  ? 'outside the repository'
104
134
  : 'sensitive repository file',
105
135
  confidence: 0.95,
106
- signals: [...params.signals, 'tier1_catastrophic', prescan.reason],
136
+ signals: [...params.signals, 'tier1_catastrophic', prescan.reason, ...decision.signals],
107
137
  },
138
+ capabilityRequests: [request],
139
+ authorizationDecision: decision,
140
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY,
108
141
  };
109
142
  }
110
- const tier1Text = `file_mutation: ${params.toolKind} path=${params.resolvedPath} location=${params.locationLabel}`;
111
- const tier1 = await judge.evaluate({
112
- text: tier1Text,
113
- context: { cwd: params.cwd, repoRoot: params.repoRoot },
114
- });
115
143
  const fingerprint = toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot);
116
- if (tier1RequiresAsk(tier1)) {
144
+ const { request, decision } = evaluateFileMutationPolicy({
145
+ hookKind: 'tool',
146
+ toolKind: params.toolKind,
147
+ filePath: params.filePath,
148
+ resolvedPath: params.resolvedPath,
149
+ repoRoot: params.repoRoot,
150
+ cwd: params.cwd,
151
+ inputFingerprint: fingerprint,
152
+ signals: params.signals,
153
+ isDelete: params.isDelete,
154
+ locationLabel: params.locationLabel,
155
+ trustedWorkspaceRoots: params.options.trustedWorkspaceRoots,
156
+ sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths,
157
+ }, params.config, policyAuth(params.options));
158
+ if (policyDecisionRequiresAsk(decision)) {
117
159
  return {
118
160
  verdict: 'deny_pending_approval',
119
- reason: 'tier1_catastrophic',
161
+ reason: policyReasonToLegacyReason(decision),
120
162
  summary: params.filePath,
121
163
  fingerprint,
122
164
  assessment: {
@@ -125,9 +167,12 @@ async function classifyFileMutationWithTier1(params) {
125
167
  blastRadius: params.locationLabel === 'outside_repo'
126
168
  ? 'outside the repository'
127
169
  : 'sensitive repository file',
128
- confidence: 0.82,
129
- signals: [...params.signals, 'tier1_catastrophic', tier1.reason],
170
+ confidence: 0.95,
171
+ signals: [...params.signals, ...decision.signals],
130
172
  },
173
+ capabilityRequests: [request],
174
+ authorizationDecision: decision,
175
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY,
131
176
  };
132
177
  }
133
178
  const reason = params.isDelete ? 'file_delete' : 'file_mutation';
@@ -141,8 +186,11 @@ async function classifyFileMutationWithTier1(params) {
141
186
  external: params.locationLabel === 'outside_repo',
142
187
  blastRadius: params.locationLabel === 'outside_repo' ? 'outside the repository' : 'this repository',
143
188
  confidence: 0.72,
144
- signals: [...params.signals, 'tier1_restorable', tier1.reason],
189
+ signals: [...params.signals, ...decision.signals],
145
190
  },
191
+ capabilityRequests: [request],
192
+ authorizationDecision: decision,
193
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY,
146
194
  };
147
195
  }
148
196
  export async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
@@ -230,24 +278,24 @@ export async function classifyToolUse(payload, repoRoot, cwd, config, options =
230
278
  const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
231
279
  if (hitsProtectedRoot) {
232
280
  signals.push('control_plane_path');
233
- return {
234
- verdict: 'deny_pending_approval',
235
- reason: 'control_plane_mutation',
236
- summary: filePath,
237
- fingerprint: toolFingerprint(toolName, { path: filePath }, repoRoot),
238
- assessment: {
239
- reversibility: 'irreversible',
240
- external: false,
241
- blastRadius: 'agent-belay control plane',
242
- confidence: 0.97,
243
- signals,
244
- },
245
- };
281
+ return classifyFileMutationWithPolicy({
282
+ toolName,
283
+ toolKind,
284
+ filePath,
285
+ resolvedPath,
286
+ repoRoot,
287
+ cwd,
288
+ config,
289
+ options,
290
+ signals,
291
+ isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
292
+ locationLabel: 'control_plane',
293
+ });
246
294
  }
247
295
  const workspaceMatch = resolveWorkspaceRootMatch(repoRoot, options.trustedWorkspaceRoots, resolvedPath);
248
296
  if (workspaceMatch === null) {
249
297
  signals.push('outside_repo_path');
250
- return classifyFileMutationWithTier1({
298
+ return classifyFileMutationWithPolicy({
251
299
  toolName,
252
300
  toolKind,
253
301
  filePath,
@@ -274,7 +322,7 @@ export async function classifyToolUse(payload, repoRoot, cwd, config, options =
274
322
  sensitivePaths,
275
323
  });
276
324
  if (workspacePrescan) {
277
- return classifyFileMutationWithTier1({
325
+ return classifyFileMutationWithPolicy({
278
326
  toolName,
279
327
  toolKind,
280
328
  filePath,
@@ -291,7 +339,7 @@ export async function classifyToolUse(payload, repoRoot, cwd, config, options =
291
339
  const relativePath = workspaceMatch.relativePath;
292
340
  if (matchesSensitivePath(relativePath, sensitivePaths)) {
293
341
  signals.push('sensitive_path');
294
- return classifyFileMutationWithTier1({
342
+ return classifyFileMutationWithPolicy({
295
343
  toolName,
296
344
  toolKind,
297
345
  filePath,
@@ -306,35 +354,51 @@ export async function classifyToolUse(payload, repoRoot, cwd, config, options =
306
354
  });
307
355
  }
308
356
  if (FILE_DELETE_TOOL_NAMES.has(toolKind)) {
309
- signals.push('file_delete');
310
- return {
311
- verdict: 'allow_flagged',
312
- reason: 'file_delete',
313
- summary: filePath,
314
- fingerprint: toolFingerprint(toolName, { path: filePath }, repoRoot),
315
- assessment: {
316
- reversibility: 'recoverable_with_cost',
317
- external: false,
318
- blastRadius: 'this repository',
319
- confidence: 0.7,
357
+ if (isGitPath(resolvedPath, repoRoot)) {
358
+ signals.push('protected_artifact');
359
+ return classifyFileMutationWithPolicy({
360
+ toolName,
361
+ toolKind,
362
+ filePath,
363
+ resolvedPath,
364
+ repoRoot,
365
+ cwd,
366
+ config,
367
+ options,
320
368
  signals,
321
- },
322
- };
369
+ isDelete: true,
370
+ locationLabel: 'sensitive_path',
371
+ });
372
+ }
373
+ signals.push('file_delete');
374
+ return classifyFileMutationWithPolicy({
375
+ toolName,
376
+ toolKind,
377
+ filePath,
378
+ resolvedPath,
379
+ repoRoot,
380
+ cwd,
381
+ config,
382
+ options,
383
+ signals,
384
+ isDelete: true,
385
+ locationLabel: 'repo_local',
386
+ });
323
387
  }
324
388
  signals.push('file_mutation');
325
- return {
326
- verdict: 'allow_flagged',
327
- reason: 'file_mutation',
328
- summary: filePath,
329
- fingerprint: toolFingerprint(toolName, { path: filePath }, repoRoot),
330
- assessment: {
331
- reversibility: 'recoverable_with_cost',
332
- external: false,
333
- blastRadius: 'this repository',
334
- confidence: 0.68,
335
- signals,
336
- },
337
- };
389
+ return classifyFileMutationWithPolicy({
390
+ toolName,
391
+ toolKind,
392
+ filePath,
393
+ resolvedPath,
394
+ repoRoot,
395
+ cwd,
396
+ config,
397
+ options,
398
+ signals,
399
+ isDelete: false,
400
+ locationLabel: 'repo_local',
401
+ });
338
402
  }
339
403
  if (APPLY_PATCH_TOOL_NAMES.has(toolKind)) {
340
404
  const patch = extractPatch(payload);
@@ -1,6 +1,6 @@
1
- import type { ClassifierOptions, ControlPlaneIntegrity, ScrubOptions, UnknownLocalEffectPolicy, UnparseableShellPolicy } from './types.js';
1
+ import type { BoundaryDriverId } from './capability/attestation.js';
2
+ import type { BelayMode, ClassifierOptions, ControlPlaneIntegrity, ScrubOptions, UnknownLocalEffectPolicy, UnparseableShellPolicy } from './types.js';
2
3
  import { type BelayJudgeRuntimeConfig } from './verdict/judge-runtime-config.js';
3
- export type BelayMode = 'enforce' | 'audit';
4
4
  export type { UnknownLocalEffectPolicy };
5
5
  export interface BelayConfigV1 {
6
6
  version: 1;
@@ -46,6 +46,15 @@ export interface BelayModelAssistConfig {
46
46
  model?: string;
47
47
  timeoutMs?: number;
48
48
  }
49
+ export interface BelayFileCheckpointConfig {
50
+ enabled: boolean;
51
+ allowNonGit: boolean;
52
+ maxFiles: number;
53
+ maxSourceBytes: number;
54
+ maxWorkspaceBytes: number;
55
+ prepareTimeoutMs: number;
56
+ copyConcurrency: number;
57
+ }
49
58
  export interface BelayTransactionalConfig {
50
59
  enabled: boolean;
51
60
  minConfidence: number;
@@ -55,6 +64,14 @@ export interface BelayTransactionalConfig {
55
64
  gates: {
56
65
  shell: boolean;
57
66
  };
67
+ fileCheckpoint: BelayFileCheckpointConfig;
68
+ checkpoint?: {
69
+ enabled: boolean;
70
+ appliedRetentionHours: number;
71
+ restoredRetentionHours: number;
72
+ maxCheckpoints: number;
73
+ maxBytes: number;
74
+ };
58
75
  }
59
76
  export interface BelayPolicyConfig {
60
77
  unknownLocalEffect: UnknownLocalEffectPolicy;
@@ -94,7 +111,10 @@ export interface JudgeCloudConsent {
94
111
  endpoint: string;
95
112
  by: string;
96
113
  }
114
+ export type BelayJudgeMode = 'shadow' | 'off';
97
115
  export interface BelayJudgeConfig {
116
+ /** Gate uses PolicyEngine; judge runs async shadow/compare only. */
117
+ mode?: BelayJudgeMode;
98
118
  provider: JudgeProvider;
99
119
  providerId?: JudgeProviderId | DeprecatedJudgeProviderId;
100
120
  model: string;
@@ -157,8 +177,14 @@ export interface BelayEgressConfig {
157
177
  /** When true with egress enabled, L3 external command lists become hints only. */
158
178
  demoteL3External: boolean;
159
179
  }
180
+ export interface BelayCapabilityConfig {
181
+ grantsEnabled?: boolean;
182
+ boundaryDriver?: BoundaryDriverId;
183
+ attestationRelPath?: string;
184
+ }
185
+ export declare const DEFAULT_CAPABILITY_V5: BelayCapabilityConfig;
160
186
  export interface BelayConfigV4 {
161
- version: 4;
187
+ version: 4 | 5;
162
188
  adapter?: 'cursor' | 'claude' | 'codex';
163
189
  /** Where hooks/runtime/skill artifacts are installed. Defaults to project. */
164
190
  installScope?: 'project' | 'global';
@@ -178,6 +204,7 @@ export interface BelayConfigV4 {
178
204
  sandbox: BelaySandboxConfig;
179
205
  audit: BelayConfigV2['audit'];
180
206
  judge: BelayJudgeConfig;
207
+ capability?: BelayCapabilityConfig;
181
208
  }
182
209
  /** @deprecated Use BelayConfigV4 */
183
210
  export type BelayConfigV3 = BelayConfigV4;
@@ -186,6 +213,9 @@ export type BelayConfig = BelayConfigV4;
186
213
  export declare const DEFAULT_FENCE_WARN_THRESHOLD = 0.5;
187
214
  export declare const DEFAULT_CONFIDENCE_THRESHOLDS: BelayConfidenceThresholds;
188
215
  export declare const DEFAULT_MODEL_ASSIST: BelayModelAssistConfig;
216
+ export declare const DEFAULT_FILE_CHECKPOINT: BelayFileCheckpointConfig;
217
+ export declare function normalizeFileCheckpointConfig(raw: Partial<BelayFileCheckpointConfig> | undefined): BelayFileCheckpointConfig;
218
+ export declare const DEFAULT_RECOVERY_CHECKPOINT: NonNullable<BelayTransactionalConfig['checkpoint']>;
189
219
  export declare const DEFAULT_TRANSACTIONAL_V3: BelayTransactionalConfig;
190
220
  export declare const LEGACY_POLICY_V3: BelayPolicyConfig;
191
221
  /** Fresh install defaults: recoverable-first with opaque/unparseable fail-closed. */
@@ -239,6 +269,7 @@ type RawConfigInput = Partial<{
239
269
  sandbox: Partial<BelaySandboxConfig>;
240
270
  audit: Partial<BelayConfigV2['audit']>;
241
271
  installScope: 'project' | 'global';
272
+ capability: Partial<BelayCapabilityConfig>;
242
273
  }>;
243
274
  export declare function migrateConfig(loaded: unknown): BelayConfigV4;
244
275
  export declare function normalizeConfigV2(config: BelayConfigV2): BelayConfigV2;