@guilz-dev/belay 0.6.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 (143) hide show
  1. package/README.md +20 -1
  2. package/dist/adapters/claude/runtime-entry.js +54 -25
  3. package/dist/adapters/shared/gate-runtime.d.ts +7 -0
  4. package/dist/adapters/shared/gate-runtime.js +420 -220
  5. package/dist/bundle/claude-runtime.mjs +13215 -8864
  6. package/dist/bundle/codex-runtime.mjs +10771 -6453
  7. package/dist/bundle/cursor-runtime.mjs +10797 -6484
  8. package/dist/cli.js +37 -1
  9. package/dist/commands/approve.js +34 -10
  10. package/dist/commands/doctor.js +51 -0
  11. package/dist/commands/recovery-checkpoints.d.ts +164 -0
  12. package/dist/commands/recovery-checkpoints.js +347 -0
  13. package/dist/commands/revoke.js +19 -7
  14. package/dist/commands/status.js +7 -0
  15. package/dist/config-io.js +49 -18
  16. package/dist/conformance/guarantee-table.js +6 -0
  17. package/dist/conformance/types.d.ts +1 -0
  18. package/dist/core/approval-replay.d.ts +1 -1
  19. package/dist/core/approval-replay.js +4 -1
  20. package/dist/core/approval-service.d.ts +6 -1
  21. package/dist/core/approval-service.js +86 -27
  22. package/dist/core/approval.d.ts +2 -0
  23. package/dist/core/approval.js +6 -3
  24. package/dist/core/audit-types.d.ts +13 -0
  25. package/dist/core/capability/approval-state-mutation.d.ts +19 -0
  26. package/dist/core/capability/approval-state-mutation.js +76 -3
  27. package/dist/core/capability/approval-v3.d.ts +18 -1
  28. package/dist/core/capability/approval-v3.js +63 -23
  29. package/dist/core/capability/attestation.d.ts +3 -0
  30. package/dist/core/capability/attestation.js +7 -0
  31. package/dist/core/capability/boundary-driver-container.d.ts +12 -0
  32. package/dist/core/capability/boundary-driver-container.js +101 -34
  33. package/dist/core/capability/boundary-driver.js +1 -0
  34. package/dist/core/capability/boundary-grant-materialize.d.ts +1 -0
  35. package/dist/core/capability/boundary-grant-materialize.js +15 -0
  36. package/dist/core/capability/boundary-run.d.ts +25 -0
  37. package/dist/core/capability/boundary-run.js +37 -9
  38. package/dist/core/capability/boundary-session.d.ts +4 -0
  39. package/dist/core/capability/boundary-session.js +9 -0
  40. package/dist/core/capability/boundary-workspace-mount.d.ts +5 -0
  41. package/dist/core/capability/boundary-workspace-mount.js +68 -0
  42. package/dist/core/capability/gate-policy-shadow.js +23 -1
  43. package/dist/core/capability/grant-consumption.d.ts +4 -0
  44. package/dist/core/capability/grant-consumption.js +11 -0
  45. package/dist/core/capability/grant-lease.d.ts +36 -1
  46. package/dist/core/capability/grant-lease.js +213 -16
  47. package/dist/core/capability/grant-match.js +3 -0
  48. package/dist/core/capability/index.d.ts +1 -1
  49. package/dist/core/capability/index.js +1 -1
  50. package/dist/core/capability/policy-engine.d.ts +7 -0
  51. package/dist/core/capability/policy-engine.js +202 -15
  52. package/dist/core/capability/request.d.ts +3 -0
  53. package/dist/core/config.d.ts +20 -0
  54. package/dist/core/config.js +62 -0
  55. package/dist/core/effect-ir/audit.d.ts +9 -0
  56. package/dist/core/effect-ir/audit.js +86 -0
  57. package/dist/core/effect-ir/build.d.ts +31 -0
  58. package/dist/core/effect-ir/build.js +277 -0
  59. package/dist/core/effect-ir/index.d.ts +8 -0
  60. package/dist/core/effect-ir/index.js +7 -0
  61. package/dist/core/effect-ir/normalize.d.ts +4 -0
  62. package/dist/core/effect-ir/normalize.js +104 -0
  63. package/dist/core/effect-ir/package-exec-eval.d.ts +8 -0
  64. package/dist/core/effect-ir/package-exec-eval.js +152 -0
  65. package/dist/core/effect-ir/package-exec.d.ts +29 -0
  66. package/dist/core/effect-ir/package-exec.js +292 -0
  67. package/dist/core/effect-ir/policy.d.ts +13 -0
  68. package/dist/core/effect-ir/policy.js +77 -0
  69. package/dist/core/effect-ir/types.d.ts +55 -0
  70. package/dist/core/effect-ir/types.js +1 -0
  71. package/dist/core/egress/allowlist.d.ts +1 -0
  72. package/dist/core/egress/allowlist.js +8 -0
  73. package/dist/core/egress-approval.js +45 -51
  74. package/dist/core/gate-contract.d.ts +2 -0
  75. package/dist/core/gate-contract.js +2 -0
  76. package/dist/core/gate-engine.js +26 -9
  77. package/dist/core/index.d.ts +3 -1
  78. package/dist/core/index.js +2 -1
  79. package/dist/core/judge-cloud-consent.js +29 -15
  80. package/dist/core/network-endpoint.d.ts +10 -0
  81. package/dist/core/network-endpoint.js +51 -0
  82. package/dist/core/process-runner.d.ts +10 -0
  83. package/dist/core/process-runner.js +124 -0
  84. package/dist/core/recover-advice.js +2 -1
  85. package/dist/core/recovery/artifact-store.d.ts +29 -0
  86. package/dist/core/recovery/artifact-store.js +336 -0
  87. package/dist/core/recovery/checkpoint.d.ts +25 -0
  88. package/dist/core/recovery/checkpoint.js +260 -0
  89. package/dist/core/recovery/index.d.ts +3 -1
  90. package/dist/core/recovery/index.js +2 -0
  91. package/dist/core/recovery/operator-guidance.d.ts +7 -0
  92. package/dist/core/recovery/operator-guidance.js +39 -0
  93. package/dist/core/recovery/reconcile.d.ts +3 -0
  94. package/dist/core/recovery/reconcile.js +48 -0
  95. package/dist/core/recovery/resource-identity.d.ts +4 -0
  96. package/dist/core/recovery/resource-identity.js +34 -0
  97. package/dist/core/recovery/restore.d.ts +13 -0
  98. package/dist/core/recovery/restore.js +114 -0
  99. package/dist/core/recovery/snapshot-node.d.ts +27 -0
  100. package/dist/core/recovery/snapshot-node.js +261 -0
  101. package/dist/core/recovery/types.d.ts +85 -1
  102. package/dist/core/transactional/apply-observed-changes.d.ts +13 -0
  103. package/dist/core/transactional/apply-observed-changes.js +275 -0
  104. package/dist/core/transactional/backend-selector.d.ts +8 -0
  105. package/dist/core/transactional/backend-selector.js +118 -0
  106. package/dist/core/transactional/backend.d.ts +55 -0
  107. package/dist/core/transactional/backend.js +1 -0
  108. package/dist/core/transactional/file-checkpoint-backend.d.ts +3 -0
  109. package/dist/core/transactional/file-checkpoint-backend.js +278 -0
  110. package/dist/core/transactional/file-checkpoint-git.d.ts +11 -0
  111. package/dist/core/transactional/file-checkpoint-git.js +217 -0
  112. package/dist/core/transactional/file-checkpoint-staging.d.ts +13 -0
  113. package/dist/core/transactional/file-checkpoint-staging.js +54 -0
  114. package/dist/core/transactional/file-clone.d.ts +15 -0
  115. package/dist/core/transactional/file-clone.js +139 -0
  116. package/dist/core/transactional/file-tree-path.d.ts +7 -0
  117. package/dist/core/transactional/file-tree-path.js +50 -0
  118. package/dist/core/transactional/file-tree.d.ts +41 -0
  119. package/dist/core/transactional/file-tree.js +186 -0
  120. package/dist/core/transactional/git-worktree-backend.d.ts +2 -0
  121. package/dist/core/transactional/git-worktree-backend.js +50 -0
  122. package/dist/core/transactional/git-worktree.d.ts +9 -11
  123. package/dist/core/transactional/git-worktree.js +24 -119
  124. package/dist/core/transactional/index.d.ts +11 -0
  125. package/dist/core/transactional/index.js +10 -0
  126. package/dist/core/transactional/reasons.js +1 -0
  127. package/dist/core/transactional/runner.js +157 -25
  128. package/dist/core/transactional/snapshot-node.d.ts +26 -0
  129. package/dist/core/transactional/snapshot-node.js +87 -0
  130. package/dist/core/transactional/types.d.ts +17 -1
  131. package/dist/core/types.d.ts +10 -1
  132. package/dist/core/verdict/adapter.js +8 -0
  133. package/dist/core/verdict/launcher-resolve.js +2 -20
  134. package/dist/core/verdict/shell-policy.d.ts +1 -0
  135. package/dist/core/verdict/shell-policy.js +1 -1
  136. package/dist/core/verdict/types.d.ts +5 -0
  137. package/dist/core/verdict/verdict.js +37 -20
  138. package/dist/types.d.ts +11 -0
  139. package/dist/version.d.ts +1 -1
  140. package/dist/version.js +1 -1
  141. package/package.json +1 -1
  142. package/skills/belay/SKILL.md +8 -3
  143. package/skills/belay/belay-approve.md +10 -7
@@ -3,6 +3,7 @@ import type { CapabilityGrantV1 } from './capability/grant.js';
3
3
  import type { PolicyDecision } from './capability/policy-types.js';
4
4
  import type { CapabilityRequestV1 } from './capability/request.js';
5
5
  import type { FsScopeAllowlistFile } from './capability/types.js';
6
+ import type { EffectPlan } from './effect-ir/types.js';
6
7
  export type BelayMode = 'enforce' | 'audit';
7
8
  export type HookVerdict = 'allow' | 'allow_flagged' | 'deny_pending_approval';
8
9
  export type Reversibility = 'reversible' | 'recoverable_with_cost' | 'irreversible';
@@ -40,6 +41,8 @@ export interface ClassifyResult {
40
41
  axes?: VerdictAxes;
41
42
  capabilityRequests?: CapabilityRequestV1[];
42
43
  authorizationDecision?: PolicyDecision;
44
+ effectPlan?: EffectPlan;
45
+ effectPlanPolicyDecisions?: PolicyDecision[];
43
46
  boundaryProfile?: string;
44
47
  }
45
48
  export type UnknownLocalEffectPolicy = 'allow_flagged' | 'deny';
@@ -116,8 +119,14 @@ export interface ApprovalRecord {
116
119
  capabilityRequests?: CapabilityRequestV1[];
117
120
  /** Approval state v3: hash of capabilityRequests for replay binding. */
118
121
  capabilityRequestHash?: string;
119
- /** Approval state v3: minted grant after human approval. */
122
+ /** Approval state v3: hash of normalized EffectPlan for composite replay binding. */
123
+ effectPlanHash?: string;
124
+ /** Approval state v3: minted grant after human approval (legacy single grant; first of bundle). */
120
125
  grant?: CapabilityGrantV1;
126
+ /** Approval state v3: all grants for composite capability requests (atomic bundle). */
127
+ grants?: CapabilityGrantV1[];
128
+ /** Exact one-to-one grant bundle contract. Missing means legacy fingerprint authorization. */
129
+ grantBundleVersion?: 1;
121
130
  }
122
131
  export interface ApprovalStateFile {
123
132
  version: 1 | 2 | 3;
@@ -1,4 +1,5 @@
1
1
  import { BOUNDARY_PROFILE_L3_L4_ONLY, resolveBoundaryProfile, } from '../capability/boundary-profile.js';
2
+ import { effectPlanAuditFields } from '../effect-ir/audit.js';
2
3
  import { judgeTraceAuditFields } from './judge-audit.js';
3
4
  import { recordJudgeLatency } from './judge-baseline.js';
4
5
  import { verdict } from './verdict.js';
@@ -120,6 +121,8 @@ export function verdictToClassifyResult(result, config, options = {}) {
120
121
  },
121
122
  capabilityRequests: result.capabilityRequests,
122
123
  authorizationDecision: result.authorizationDecision,
124
+ effectPlan: result.effectPlan,
125
+ effectPlanPolicyDecisions: result.effectPlanPolicyDecisions,
123
126
  boundaryProfile: options.boundaryProfile ??
124
127
  (config
125
128
  ? resolveBoundaryProfile({ config, attestation: options.attestation })
@@ -138,6 +141,11 @@ export function verdictAuditFields(result) {
138
141
  would: result.permission,
139
142
  by: 'verdict',
140
143
  signals: result.signals,
144
+ ...effectPlanAuditFields(result.effectPlan, {
145
+ capabilityRequests: result.capabilityRequests,
146
+ decisions: result.effectPlanPolicyDecisions,
147
+ authorizationDecision: result.authorizationDecision,
148
+ }),
141
149
  ...judgeTraceAuditFields(result.judgeTrace),
142
150
  };
143
151
  }
@@ -34,17 +34,6 @@ const PNPM_BUILTIN_COMMANDS = new Set([
34
34
  'up',
35
35
  'why',
36
36
  ]);
37
- const PNPM_EXEC_LIKE_HEADS = new Set([
38
- 'vitest',
39
- 'vite',
40
- 'biome',
41
- 'eslint',
42
- 'jest',
43
- 'mocha',
44
- 'tsc',
45
- 'tsx',
46
- 'node',
47
- ]);
48
37
  function readPackageJson(dir) {
49
38
  const packagePath = path.join(dir, 'package.json');
50
39
  if (!existsSync(packagePath)) {
@@ -232,12 +221,12 @@ export function resolveLauncherRecipe(params) {
232
221
  const resolution = resolveNpmRecipe(params.cwd, params.repoRoot, scriptName, forwardedArgs(tokens));
233
222
  if (tokens[0] === 'pnpm' &&
234
223
  tokens[1] &&
235
- PNPM_EXEC_LIKE_HEADS.has(tokens[1]) &&
224
+ !PNPM_BUILTIN_COMMANDS.has(tokens[1]) &&
236
225
  resolution.reason === 'npm_script_undefined') {
237
226
  return {
238
227
  recipes: [tokens.slice(1).join(' ')],
239
228
  opaque: false,
240
- reason: 'pnpm_exec_like',
229
+ reason: 'pnpm_exec_shorthand',
241
230
  };
242
231
  }
243
232
  return resolution;
@@ -252,13 +241,6 @@ export function resolveLauncherRecipe(params) {
252
241
  reason: 'pnpm_exec',
253
242
  };
254
243
  }
255
- if (tokens[0] === 'pnpm' && tokens[1] && PNPM_EXEC_LIKE_HEADS.has(tokens[1])) {
256
- return {
257
- recipes: [tokens.slice(1).join(' ')],
258
- opaque: false,
259
- reason: 'pnpm_exec_like',
260
- };
261
- }
262
244
  return null;
263
245
  }
264
246
  const READ_ONLY_LAUNCHER_SUFFIXES = new Set(['--version', '-v', '--help', '-h']);
@@ -16,6 +16,7 @@ export interface SegmentShellPolicyInput {
16
16
  }
17
17
  export declare function evaluateSegmentShellPolicy(input: SegmentShellPolicyInput): {
18
18
  request: CapabilityRequestV1;
19
+ requests: CapabilityRequestV1[];
19
20
  decision: PolicyDecision;
20
21
  };
21
22
  /** Attach capability metadata without influencing the verdict (audit / shadow only). */
@@ -34,7 +34,7 @@ export function evaluateSegmentShellPolicy(input) {
34
34
  export function shellPolicyMetadata(input) {
35
35
  const result = evaluateSegmentShellPolicy(input);
36
36
  return {
37
- capabilityRequests: [result.request],
37
+ capabilityRequests: result.requests,
38
38
  authorizationDecision: result.decision,
39
39
  };
40
40
  }
@@ -1,6 +1,7 @@
1
1
  import type { PolicyDecision } from '../capability/policy-types.js';
2
2
  import type { CapabilityRequestV1 } from '../capability/request.js';
3
3
  import type { BelayConfigV4 } from '../config.js';
4
+ import type { EffectPlan } from '../effect-ir/types.js';
4
5
  export type VerdictPermission = 'allow' | 'ask';
5
6
  export type VerdictLocation = 'repo_local' | 'repo_outside' | 'external' | 'mixed' | 'unknown';
6
7
  export type VerdictOpacity = 'transparent' | 'recursive' | 'opaque' | 'unparseable';
@@ -19,6 +20,8 @@ export interface VerdictResult {
19
20
  judgeTrace?: JudgeTrace;
20
21
  capabilityRequests?: CapabilityRequestV1[];
21
22
  authorizationDecision?: PolicyDecision;
23
+ effectPlan?: EffectPlan;
24
+ effectPlanPolicyDecisions?: PolicyDecision[];
22
25
  }
23
26
  export interface Tier1Verdict {
24
27
  /** true = undoable via git/fs snapshot or trivial local revert (ADR-002 local-recoverable). */
@@ -93,4 +96,6 @@ export interface InternalSegmentVerdict {
93
96
  judgeTrace?: JudgeTrace;
94
97
  capabilityRequests?: CapabilityRequestV1[];
95
98
  authorizationDecision?: PolicyDecision;
99
+ effectPlan?: EffectPlan;
100
+ effectPlanPolicyDecisions?: PolicyDecision[];
96
101
  }
@@ -1,6 +1,9 @@
1
1
  import path from 'node:path';
2
2
  import { policyDecisionToLegacyReason } from '../capability/policy-bridge.js';
3
- import { policyDecisionRequiresAsk } from '../capability/policy-engine.js';
3
+ import { combinePolicyDecisions, policyDecisionRequiresAsk } from '../capability/policy-engine.js';
4
+ import { mergeEffectPlans } from '../effect-ir/normalize.js';
5
+ import { isPackageExecLauncher } from '../effect-ir/package-exec.js';
6
+ import { evaluatePackageExecSegment } from '../effect-ir/package-exec-eval.js';
4
7
  import { canonicalPath, resolveMutationTarget } from '../path-utils.js';
5
8
  import { extractRedirectTargets, isFdDuplication, isRedirectOperator, tokenizeShell, } from '../shell-tokenizer.js';
6
9
  import { analyzePathTargets, cwdRelative, isDestructiveHighStakesMutation, resolveTrustedPath, touchesProtectedRoot, } from './containment.js';
@@ -94,6 +97,12 @@ function mergeLocation(left, right) {
94
97
  return 'mixed';
95
98
  }
96
99
  function combineInternal(left, right) {
100
+ const policyDecisions = [
101
+ ...(left.effectPlanPolicyDecisions ??
102
+ (left.authorizationDecision ? [left.authorizationDecision] : [])),
103
+ ...(right.effectPlanPolicyDecisions ??
104
+ (right.authorizationDecision ? [right.authorizationDecision] : [])),
105
+ ];
97
106
  return {
98
107
  permission: worsePermission(left.permission, right.permission),
99
108
  location: mergeLocation(left.location, right.location),
@@ -122,11 +131,9 @@ function combineInternal(left, right) {
122
131
  signals: [...new Set([...left.signals, ...right.signals])],
123
132
  judgeTrace: right.judgeTrace ?? left.judgeTrace,
124
133
  capabilityRequests: [...(left.capabilityRequests ?? []), ...(right.capabilityRequests ?? [])],
125
- authorizationDecision: worsePermission(left.permission, right.permission) === 'ask'
126
- ? right.permission === 'ask'
127
- ? (right.authorizationDecision ?? left.authorizationDecision)
128
- : left.authorizationDecision
129
- : (right.authorizationDecision ?? left.authorizationDecision),
134
+ authorizationDecision: policyDecisions.length > 0 ? combinePolicyDecisions(policyDecisions) : undefined,
135
+ effectPlan: mergeEffectPlans(left.effectPlan, right.effectPlan),
136
+ effectPlanPolicyDecisions: policyDecisions,
130
137
  };
131
138
  }
132
139
  function askVerdict(params) {
@@ -327,7 +334,7 @@ function tier0HighStakesRm(tokens, context) {
327
334
  return null;
328
335
  }
329
336
  function verdictFromOpacityPolicy(params) {
330
- const { request, decision } = evaluateSegmentShellPolicy({
337
+ const { requests, decision } = evaluateSegmentShellPolicy({
331
338
  command: params.command,
332
339
  segmentHead: params.segmentHead,
333
340
  effect: params.effect,
@@ -343,7 +350,7 @@ function verdictFromOpacityPolicy(params) {
343
350
  effect: params.effect,
344
351
  confidence: 'deterministic',
345
352
  signals: [...params.signals, ...decision.signals],
346
- capabilityRequests: [request],
353
+ capabilityRequests: requests,
347
354
  authorizationDecision: decision,
348
355
  };
349
356
  if (policyDecisionRequiresAsk(decision)) {
@@ -428,7 +435,7 @@ async function evaluateSegment(command, context, depth) {
428
435
  if (recursiveScript) {
429
436
  const prescan = prescanInterpreterCode(recursiveScript);
430
437
  if (prescan && tier1RequiresAsk(prescan)) {
431
- const { request, decision } = evaluateSegmentShellPolicy({
438
+ const { requests, decision } = evaluateSegmentShellPolicy({
432
439
  command: recursiveScript,
433
440
  segmentHead: segment.head,
434
441
  effect: 'unknown',
@@ -448,7 +455,7 @@ async function evaluateSegment(command, context, depth) {
448
455
  confidence: 'deterministic',
449
456
  reason: legacyReason,
450
457
  signals: ['interpreter_secret_prescan', prescan.reason, ...decision.signals],
451
- capabilityRequests: [request],
458
+ capabilityRequests: requests,
452
459
  authorizationDecision: decision,
453
460
  });
454
461
  }
@@ -465,6 +472,14 @@ async function evaluateSegment(command, context, depth) {
465
472
  signals: [...innerVerdict.signals, 'recursive_wrapper'],
466
473
  };
467
474
  }
475
+ if (isPackageExecLauncher(peeled)) {
476
+ return evaluatePackageExecSegment({
477
+ command,
478
+ peeled,
479
+ context,
480
+ evaluateInner: (innerRecipe) => evaluateSegment(innerRecipe, context, depth + 1),
481
+ });
482
+ }
468
483
  if (isRoutineLauncher(peeled)) {
469
484
  const resolution = resolveLauncherRecipe({
470
485
  tokens: peeled,
@@ -518,7 +533,7 @@ async function evaluateSegment(command, context, depth) {
518
533
  const semantics = analyzeShellCommandSemantics(peeled, segment, context.cwd);
519
534
  const pathContext = pathContextFromSemantics(context, semantics);
520
535
  if (semantics.requiresAsk) {
521
- const { request, decision } = evaluateSegmentShellPolicy({
536
+ const { requests, decision } = evaluateSegmentShellPolicy({
522
537
  command,
523
538
  segmentHead: segment.head,
524
539
  effect: 'local_mutation',
@@ -535,13 +550,13 @@ async function evaluateSegment(command, context, depth) {
535
550
  confidence: 'deterministic',
536
551
  reason: semantics.requiresAsk.reason,
537
552
  signals: semantics.requiresAsk.signals,
538
- capabilityRequests: [request],
553
+ capabilityRequests: requests,
539
554
  authorizationDecision: decision,
540
555
  });
541
556
  }
542
557
  const egressClass = semantics.egressClass ?? classifyEgressTool(segment.head, peeled);
543
558
  if (egressClass === 'destructive') {
544
- const { request, decision } = evaluateSegmentShellPolicy({
559
+ const { requests, decision } = evaluateSegmentShellPolicy({
545
560
  command,
546
561
  segmentHead: segment.head,
547
562
  effect: 'remote_mutation',
@@ -559,13 +574,13 @@ async function evaluateSegment(command, context, depth) {
559
574
  confidence: 'deterministic',
560
575
  reason: 'tier0_external',
561
576
  signals: ['tier0_external', segment.head, ...decision.signals],
562
- capabilityRequests: [request],
577
+ capabilityRequests: requests,
563
578
  authorizationDecision: decision,
564
579
  });
565
580
  }
566
581
  if (tier0ExternalMatch(semantics.normalizedKey ?? segment.key, segment.head, peeled, semantics.signals)) {
567
582
  const tier0Key = semantics.normalizedKey ?? segment.key;
568
- const { request, decision } = evaluateSegmentShellPolicy({
583
+ const { requests, decision } = evaluateSegmentShellPolicy({
569
584
  command,
570
585
  segmentHead: segment.head,
571
586
  effect: 'remote_mutation',
@@ -582,7 +597,7 @@ async function evaluateSegment(command, context, depth) {
582
597
  confidence: 'deterministic',
583
598
  reason: 'tier0_external',
584
599
  signals: ['tier0_external', tier0Key, ...decision.signals],
585
- capabilityRequests: [request],
600
+ capabilityRequests: requests,
586
601
  authorizationDecision: decision,
587
602
  });
588
603
  }
@@ -743,7 +758,7 @@ async function evaluateSegment(command, context, depth) {
743
758
  })
744
759
  : null;
745
760
  if (mutationPrescan) {
746
- const { request, decision } = evaluateSegmentShellPolicy(buildPolicyInput({
761
+ const { requests, decision } = evaluateSegmentShellPolicy(buildPolicyInput({
747
762
  effect: 'local_mutation',
748
763
  location: pathAnalysis.location === 'unknown'
749
764
  ? 'unknown'
@@ -766,7 +781,7 @@ async function evaluateSegment(command, context, depth) {
766
781
  confidence: 'deterministic',
767
782
  reason: legacyReason,
768
783
  signals: ['tier1_catastrophic', mutationPrescan.reason, ...decision.signals],
769
- capabilityRequests: [request],
784
+ capabilityRequests: requests,
770
785
  authorizationDecision: decision,
771
786
  });
772
787
  }
@@ -777,12 +792,12 @@ async function evaluateSegment(command, context, depth) {
777
792
  (outsideMutation && effect !== 'read_only');
778
793
  let policyMetadata = {};
779
794
  if (needsPolicy) {
780
- const { request, decision } = evaluateSegmentShellPolicy(buildPolicyInput({
795
+ const { requests, decision } = evaluateSegmentShellPolicy(buildPolicyInput({
781
796
  effect,
782
797
  location: pathAnalysis.location,
783
798
  }));
784
799
  policyMetadata = {
785
- capabilityRequests: [request],
800
+ capabilityRequests: requests,
786
801
  authorizationDecision: decision,
787
802
  };
788
803
  if (policyDecisionRequiresAsk(decision)) {
@@ -908,6 +923,8 @@ function toVerdictResult(internal, command, context, fingerprintCwd = context.cw
908
923
  judgeTrace: internal.judgeTrace,
909
924
  capabilityRequests: internal.capabilityRequests,
910
925
  authorizationDecision: internal.authorizationDecision,
926
+ effectPlan: internal.effectPlan,
927
+ effectPlanPolicyDecisions: internal.effectPlanPolicyDecisions,
911
928
  };
912
929
  }
913
930
  export async function verdict(command, context) {
package/dist/types.d.ts CHANGED
@@ -138,6 +138,17 @@ export interface StatusReport {
138
138
  dogfood: DogfoodStatus;
139
139
  health: HealthSnapshot;
140
140
  visibility: AuditVisibilityReport;
141
+ fileCheckpoint: {
142
+ enabled: boolean;
143
+ allowNonGit: boolean;
144
+ transactionalEnabled: boolean;
145
+ durableCheckpointEnabled: boolean;
146
+ maxFiles: number;
147
+ maxSourceBytes: number;
148
+ maxWorkspaceBytes: number;
149
+ prepareTimeoutMs: number;
150
+ copyConcurrency: number;
151
+ };
141
152
  }
142
153
  export interface ReportOptions {
143
154
  targetDir?: string;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const PACKAGE_VERSION = "0.6.0";
1
+ export declare const PACKAGE_VERSION = "0.7.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by scripts/sync-version.mjs — do not edit.
2
- export const PACKAGE_VERSION = '0.6.0';
2
+ export const PACKAGE_VERSION = '0.7.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guilz-dev/belay",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Belay-style approval and audit gating for agent runtimes.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,9 +37,14 @@ setup (or `npx @guilz-dev/belay init` for non-interactive install). Run
37
37
  ## When belay blocks an action
38
38
 
39
39
  1. Read the approval ID in the deny message.
40
- 2. Approve once with `/belay-approve <approval-id>` or `belay approve <approval-id>`.
41
- 3. Retry the original action unchanged (default `approval.flow` is `one_step` for shell the
42
- approval response includes a replay hint when auto-replay is enabled).
40
+ 2. In the editor, approve once with `/belay-approve <approval-id>`.
41
+ 3. With default `approval.flow: one_step`, editor approval atomically claims the one-shot grant and
42
+ replays the exact denied shell action through the configured boundary driver. No follow-up prompt
43
+ is required. Failed or timed-out replay requires fresh approval. Tool and subagent actions still
44
+ require retrying the original action unchanged.
45
+
46
+ An instruction may follow the approval line in the same prompt. Belay runs the approved shell action
47
+ first and continues the remaining prompt only after replay succeeds.
43
48
 
44
49
  For shell-only CLI replay after approval: `belay approve <approval-id> --replay`.
45
50
 
@@ -1,21 +1,24 @@
1
1
  Run after belay denies a high-risk action and returns an approval ID.
2
2
 
3
- ```bash
4
- belay approve <approval-id>
3
+ ```text
4
+ /belay-approve <approval-id>
5
5
  ```
6
6
 
7
- Then retry the original action unchanged.
7
+ With default `approval.flow: one_step`, the editor hook atomically claims the one-shot grant and
8
+ immediately replays the exact denied shell action through the configured boundary driver. No
9
+ follow-up prompt is required. Failed or timed-out replay requires fresh approval. Tool and subagent
10
+ approvals still require a manual retry of the original action unchanged.
8
11
 
9
- With default `approval.flow: one_step`, shell approvals return a replay hint from editor
10
- hooks. Tool and subagent approvals still require a manual retry.
12
+ An instruction may follow the approval line in the same prompt. Belay runs the approved shell action
13
+ first and continues the remaining prompt only after replay succeeds.
11
14
 
12
- To run a denied shell command from the CLI after approval (explicit opt-in; do not also retry via hooks):
15
+ For CLI approval, replay remains explicit:
13
16
 
14
17
  ```bash
15
18
  belay approve <approval-id> --replay
16
19
  ```
17
20
 
18
- Successful CLI replay consumes the one-shot grant. On failure, the approval stays active for one hook retry.
21
+ CLI replay claims the one-shot grant before execution. Failure or timeout requires fresh approval.
19
22
 
20
23
  Restore legacy two-step UX in `belay.config.json`:
21
24