@guilz-dev/belay 0.8.1 → 0.9.1

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 (127) hide show
  1. package/README.md +67 -11
  2. package/dist/adapters/claude/runtime-entry.js +2 -0
  3. package/dist/adapters/codex/runtime-entry.js +2 -0
  4. package/dist/adapters/cursor/hooks.d.ts +5 -1
  5. package/dist/adapters/cursor/hooks.js +37 -2
  6. package/dist/adapters/cursor/runtime-entry.js +5 -0
  7. package/dist/adapters/shared/gate-runtime.d.ts +6 -0
  8. package/dist/adapters/shared/gate-runtime.js +260 -59
  9. package/dist/bundle/claude-runtime.mjs +7068 -3966
  10. package/dist/bundle/codex-runtime.mjs +7001 -3899
  11. package/dist/bundle/cursor-runtime.mjs +7004 -3899
  12. package/dist/commands/classify-for-report.js +5 -0
  13. package/dist/commands/doctor.js +88 -9
  14. package/dist/commands/explain.js +3 -2
  15. package/dist/commands/metrics.js +42 -0
  16. package/dist/commands/recovery-checkpoints.d.ts +8 -1
  17. package/dist/commands/recovery-checkpoints.js +44 -11
  18. package/dist/commands/session.d.ts +5 -0
  19. package/dist/commands/session.js +8 -1
  20. package/dist/config-io.js +2 -2
  21. package/dist/conformance/contained-execution-guarantee.d.ts +121 -0
  22. package/dist/conformance/contained-execution-guarantee.js +121 -0
  23. package/dist/conformance/guarantee-table.js +7 -0
  24. package/dist/core/audit-analysis.js +7 -4
  25. package/dist/core/audit-io.d.ts +1 -0
  26. package/dist/core/audit-io.js +16 -6
  27. package/dist/core/audit-legacy-archive.d.ts +6 -0
  28. package/dist/core/audit-legacy-archive.js +51 -0
  29. package/dist/core/audit-metrics.d.ts +15 -0
  30. package/dist/core/audit-metrics.js +16 -2
  31. package/dist/core/audit-query.d.ts +2 -0
  32. package/dist/core/audit-query.js +33 -9
  33. package/dist/core/audit-recovery-metrics.d.ts +33 -0
  34. package/dist/core/audit-recovery-metrics.js +146 -0
  35. package/dist/core/audit-serialize.d.ts +9 -0
  36. package/dist/core/audit-serialize.js +179 -0
  37. package/dist/core/audit-types.d.ts +8 -1
  38. package/dist/core/audit-types.js +1 -1
  39. package/dist/core/bounded-output.d.ts +11 -0
  40. package/dist/core/bounded-output.js +26 -0
  41. package/dist/core/capability/attestation.d.ts +56 -0
  42. package/dist/core/capability/attestation.js +110 -3
  43. package/dist/core/capability/boundary-session.d.ts +5 -0
  44. package/dist/core/capability/boundary-session.js +38 -1
  45. package/dist/core/capability/index.d.ts +1 -1
  46. package/dist/core/capability/index.js +1 -1
  47. package/dist/core/capability/policy-engine.js +32 -13
  48. package/dist/core/config.d.ts +18 -0
  49. package/dist/core/config.js +89 -10
  50. package/dist/core/contained-execution/docker-policy.d.ts +18 -0
  51. package/dist/core/contained-execution/docker-policy.js +97 -0
  52. package/dist/core/contained-execution/docker.d.ts +223 -0
  53. package/dist/core/contained-execution/docker.js +846 -0
  54. package/dist/core/contained-execution/eligibility.d.ts +9 -0
  55. package/dist/core/contained-execution/eligibility.js +103 -0
  56. package/dist/core/contained-execution/failure.d.ts +18 -0
  57. package/dist/core/contained-execution/failure.js +105 -0
  58. package/dist/core/contained-execution/mirror.d.ts +54 -0
  59. package/dist/core/contained-execution/mirror.js +587 -0
  60. package/dist/core/contained-execution/policy.d.ts +10 -0
  61. package/dist/core/contained-execution/policy.js +20 -0
  62. package/dist/core/decision-config-fingerprint.d.ts +3 -0
  63. package/dist/core/decision-config-fingerprint.js +6 -0
  64. package/dist/core/effect-ir/index.d.ts +1 -1
  65. package/dist/core/effect-ir/index.js +1 -1
  66. package/dist/core/effect-ir/normalize.d.ts +5 -0
  67. package/dist/core/effect-ir/normalize.js +17 -7
  68. package/dist/core/effect-ir/shell-build.js +2 -14
  69. package/dist/core/effect-ir/shell-lower.js +103 -25
  70. package/dist/core/gate-contract.d.ts +4 -1
  71. package/dist/core/gate-contract.js +2 -0
  72. package/dist/core/process-runner.d.ts +10 -3
  73. package/dist/core/process-runner.js +66 -10
  74. package/dist/core/recovery/checkpoint.d.ts +3 -0
  75. package/dist/core/recovery/checkpoint.js +33 -4
  76. package/dist/core/recovery/types.d.ts +1 -0
  77. package/dist/core/scrub.d.ts +17 -0
  78. package/dist/core/scrub.js +499 -5
  79. package/dist/core/standing-allow.d.ts +0 -20
  80. package/dist/core/standing-allow.js +0 -71
  81. package/dist/core/transactional/apply-observed-changes.d.ts +1 -0
  82. package/dist/core/transactional/apply-observed-changes.js +11 -0
  83. package/dist/core/transactional/backend-selector.d.ts +2 -1
  84. package/dist/core/transactional/backend-selector.js +32 -29
  85. package/dist/core/transactional/backend.d.ts +3 -0
  86. package/dist/core/transactional/file-checkpoint-backend.js +242 -54
  87. package/dist/core/transactional/file-checkpoint-git.d.ts +3 -0
  88. package/dist/core/transactional/file-checkpoint-git.js +53 -0
  89. package/dist/core/transactional/file-checkpoint-isolation.d.ts +3 -0
  90. package/dist/core/transactional/file-checkpoint-isolation.js +16 -0
  91. package/dist/core/transactional/git-worktree-backend.js +3 -0
  92. package/dist/core/transactional/git-worktree.d.ts +1 -0
  93. package/dist/core/transactional/git-worktree.js +1 -0
  94. package/dist/core/transactional/runner.js +59 -13
  95. package/dist/core/types.d.ts +13 -0
  96. package/dist/core/verdict/egress-classify.js +1 -0
  97. package/dist/core/verdict/parser.d.ts +10 -0
  98. package/dist/core/verdict/parser.js +271 -40
  99. package/dist/core/verdict/shell-semantics.js +21 -0
  100. package/dist/corpus/adversarial-probe.d.ts +1 -1
  101. package/dist/corpus/adversarial-probe.js +3 -3
  102. package/dist/corpus/benign-probe-cores.d.ts +6 -0
  103. package/dist/corpus/{must-allow-commands.js → benign-probe-cores.js} +8 -2
  104. package/dist/corpus/coverage-compare.d.ts +36 -0
  105. package/dist/corpus/coverage-compare.js +197 -0
  106. package/dist/corpus/coverage-contexts.d.ts +17 -0
  107. package/dist/corpus/coverage-contexts.js +88 -0
  108. package/dist/corpus/coverage-matrix.d.ts +37 -0
  109. package/dist/corpus/coverage-matrix.js +159 -0
  110. package/dist/corpus/coverage-probe.d.ts +91 -0
  111. package/dist/corpus/coverage-probe.js +341 -0
  112. package/dist/corpus/evaluate.d.ts +1 -1
  113. package/dist/corpus/evaluate.js +1 -1
  114. package/dist/corpus/structural-fixture-root.d.ts +2 -0
  115. package/dist/corpus/structural-fixture-root.js +5 -0
  116. package/dist/defaults.js +0 -8
  117. package/dist/egress-daemon.js +20 -4
  118. package/dist/installer.js +3 -0
  119. package/dist/runtime-provenance.d.ts +10 -0
  120. package/dist/runtime-provenance.js +63 -0
  121. package/dist/templates.js +12 -7
  122. package/dist/version.d.ts +1 -1
  123. package/dist/version.js +1 -1
  124. package/package.json +4 -1
  125. package/skills/belay/SKILL.md +19 -0
  126. package/skills/belay/belay-status.md +5 -0
  127. package/dist/corpus/must-allow-commands.d.ts +0 -6
package/README.md CHANGED
@@ -122,7 +122,13 @@ approved shell action first, then lets the remaining prompt continue. CLI replay
122
122
 
123
123
  Approvals are one-shot and expire after 15 minutes by default. Every decision is
124
124
  written to `.cursor/belay/audit.ndjson`, `.claude/belay/audit.ndjson`, or
125
- `.codex/belay/audit.ndjson` (depending on the adapter).
125
+ `.codex/belay/audit.ndjson` (depending on the adapter). Schema v3 preserves ISO
126
+ timestamps, fingerprints, and `approvalCorrelationId` for metrics joins. Dogfood
127
+ readiness counts only the active cohort (runtime bundle + authorization-relevant
128
+ config + boundary profile); legacy placeholder logs should be archived via
129
+ `belay upgrade` before trusting readiness. See
130
+ [config schema — audit log](./docs/config-schema.md#audit-log-ndjson-schema-v3) and
131
+ [dogfood audit remediation](./docs/dogfood-audit-remediation-2026-08-22.ja.md).
126
132
 
127
133
  In **audit mode** (`mode: "audit"`), would-be denials are recorded
128
134
  (`wouldBlock: true`) but execution still continues, and no approval IDs are
@@ -135,12 +141,13 @@ Belay is a layered hook gate, not a static denylist. Higher layers are opt-in.
135
141
  | Layer | Role | Enabled by |
136
142
  |-------|------|------------|
137
143
  | **L1** Containment | Egress proxy, sandbox capability broker | `egress` / `sandbox` config |
138
- | **L2** Observation | Transactional git-worktree diff | `policy.transactional` |
144
+ | **L2** Observation | Transactional mirror and durable checkpoint observation | `policy.transactional` |
139
145
  | **L3** Prediction | Policy rules + command heuristics | default |
140
146
  | **L4** Approval | Human one-shot / scoped approvals | default |
141
147
 
142
148
  - Normalized shell authorization uses only canonical `EffectPlan` requirements.
143
- Command lists, legacy overrides, corpus labels, and shell standing allows are inert.
149
+ Command lists, legacy overrides, corpus labels, and legacy standing-allow records for shell,
150
+ tool, and subagent actions are inert at runtime.
144
151
  - Payload-free reads are `allow`; reversible repository-local writes (including implicit
145
152
  download output) are `allow_flagged`. Outside-repository writes, external mutation,
146
153
  explicit payload/file/secret sends, high-stakes effects, and partial/indeterminate plans
@@ -148,6 +155,52 @@ Belay is a layered hook gate, not a static denylist. Higher layers are opt-in.
148
155
  - Adversarial resistance requires the full L1 stack:
149
156
  `belay init --preset l1-full-recommended`, verified with `belay sandbox status`.
150
157
 
158
+ ### Contained unknown execution (opt-in)
159
+
160
+ For an eligible repository-local `unknown_local_effect`, an operator may opt into one disposable
161
+ Docker run instead of taking the ordinary approval path. No command allowlist is involved:
162
+ EffectPlan remains the shell authority, and executable names, prefixes, fingerprints, corpus
163
+ membership, Rails, RSpec, and fictional command names all use the same effect-based eligibility
164
+ route. The original host command is blocked after mediation.
165
+
166
+ Enable it only with an operator-provisioned image and explicit local Docker substrate:
167
+
168
+ ```json
169
+ {
170
+ "sandbox": {
171
+ "enabled": true,
172
+ "runtime": "container",
173
+ "containedExecution": {
174
+ "enabled": true,
175
+ "image": "registry.example/contained-runner:2026-08-18",
176
+ "dockerExecutable": "/usr/local/bin/docker",
177
+ "dockerHost": "unix:///var/run/docker.sock"
178
+ }
179
+ }
180
+ }
181
+ ```
182
+
183
+ Run `belay session start` after changing this configuration. It binds a fresh signed capability to
184
+ the configured Docker executable, local Unix daemon, and immutable image ID; image-tag drift,
185
+ tampering, stale proof, or config changes require another session start. v1 never builds or pulls
186
+ an image automatically and always uses network `none`.
187
+
188
+ Belay copies the current workspace to a bounded metadata-free mirror, mounts only that mirror in
189
+ the container at the original guest path, and discards it after one run. Workspace changes are
190
+ discarded; there is no diff/apply or host replay. Guest output is scrubbed and retained only as
191
+ 16 KiB tails. This boundary scrub is mandatory even when ordinary audit `redaction.*` options are
192
+ disabled. A nonzero guest exit is reported as contained failure and does not create an approval.
193
+
194
+ In audit mode Belay's contained route reports `wouldMediate` and performs no contained execution:
195
+ it reads no attestation and runs neither a mirror nor a container. The gate returns `allow`, so the
196
+ host hook delegates the original invocation as ordinary audit pass-through; it is not a contained
197
+ route host replay.
198
+ In enforce mode, only pre-execution Docker substrate or daemon unavailability returns to the normal
199
+ approval path. Missing, stale, or tampered attestation/capability; missing or mismatched image;
200
+ mirror/lease failure; create/inspect/start failure; timeout; and cleanup uncertainty fail closed
201
+ without approval. This route is not L1-full;
202
+ see the [guarantee table](./docs/guarantee-table.md) for its exact boundary and residual risks.
203
+
151
204
  ## Install options
152
205
 
153
206
  ```bash
@@ -242,18 +295,21 @@ Notable settings:
242
295
  - **`classifier.strictChains: true`** (default) — scans every `&&`, `|`, and `;`
243
296
  segment into the EffectPlan and keeps the strictest policy projection. Legacy
244
297
  `overrides.allow` / `overrides.external` lists are accepted only for config
245
- compatibility; shell authorization ignores them and `belay doctor` warns when
246
- either list is non-empty.
298
+ compatibility; shell authorization ignores them. **`belay doctor` fails** when either
299
+ list is non-empty (ADR-005). Remove legacy lists; improve EffectPlan semantics or use
300
+ one-shot approval instead.
247
301
  - **`controlPlane.enabled: true`** — stores approval state under
248
302
  `~/.config/belay/` (or `XDG_CONFIG_HOME/belay`), shared across repos for the
249
303
  current OS user. `upgrade` migrates repo-local approvals in; disabling merges
250
304
  them back. File-mutation tools and shell redirects cannot write control-plane
251
305
  paths while it is enabled.
252
- - **`policy.transactional.checkpoint.enabled: true`** — after enabling the
253
- transactional git-worktree runner, persist repo-local pre-images and expose them through
254
- `belay recover list`. Restore is conflict-checked and always requires a signed
255
- out-of-band, exact one-shot approval. Network, remote Git, databases, processes, and
256
- repo-external effects are outside this guarantee.
306
+ - **`policy.transactional.checkpoint.enabled: true`** — after enabling the transactional
307
+ runner, persist repo-local pre-images and expose them through `belay recover list`. Clean Git
308
+ uses `git_worktree`; dirty Git and non-Git directories use `file_checkpoint` when separately
309
+ enabled (`policy.transactional.fileCheckpoint.enabled` and, for non-Git roots,
310
+ `allowNonGit: true`) with an attested workspace-isolating boundary. Restore is conflict-checked
311
+ and always requires a signed out-of-band, exact one-shot approval. Network, remote Git,
312
+ databases, processes, and repo-external effects are outside this guarantee.
257
313
  - **Cloud judge** — configure with `belay config` (interactive) or `belay config set judge.providerId <id>`.
258
314
  Providers: `ollama`, `codex`, `claude`, `cursor`. **Provider** is the vendor/service
259
315
  (`judge.providerId`); **driver** is the API compatibility layer (`judge.provider`:
@@ -297,7 +353,7 @@ belay status # show install scope / skill-only state
297
353
  belay metrics # would-block / verdict summary
298
354
  belay report # audit log report
299
355
  belay recover [advice] [--command "rm important.ts"] # advisory candidates only
300
- belay recover status # checkpoint backend and state counts
356
+ belay recover status # checkpoint backend, eligibility, and state counts
301
357
  belay recover list # proven repo-local recovery points
302
358
  belay recover show <checkpoint-id>
303
359
  belay recover apply <checkpoint-id> # signed OOB exact one-shot approval required
@@ -162,6 +162,7 @@ export async function runShellGateHook() {
162
162
  command,
163
163
  payload,
164
164
  toolName: 'Bash',
165
+ sourceEvent: 'PreToolUse',
165
166
  });
166
167
  jsonResponse(gateVerdictToClaudePreToolUseResponse(verdict));
167
168
  }
@@ -261,6 +262,7 @@ export async function runToolGateHook(_eventName) {
261
262
  cwd,
262
263
  payload: normalizedPayload,
263
264
  toolName,
265
+ sourceEvent: 'PreToolUse',
264
266
  });
265
267
  jsonResponse(gateVerdictToClaudePreToolUseResponse(verdict));
266
268
  }
@@ -180,6 +180,7 @@ export async function runToolGateHook(eventName) {
180
180
  command: kind === 'shell' ? extractString(normalizedPayload.tool_input, 'command') : undefined,
181
181
  payload: normalizedPayload,
182
182
  toolName,
183
+ sourceEvent: eventName,
183
184
  });
184
185
  jsonResponse(gateVerdictToCodexPreToolUseResponse(verdict));
185
186
  }
@@ -209,6 +210,7 @@ export async function runShellGateHook() {
209
210
  command,
210
211
  payload,
211
212
  toolName: 'Shell',
213
+ sourceEvent: 'PreToolUse',
212
214
  });
213
215
  jsonResponse(gateVerdictToCodexPreToolUseResponse(verdict));
214
216
  }
@@ -1,2 +1,6 @@
1
- import type { HooksFile } from '../../types.js';
1
+ import type { HookEntry, HooksFile } from '../../types.js';
2
+ /** Legacy Belay-managed Shell preToolUse gate duplicated beforeShellExecution. */
3
+ export declare function legacyManagedShellPreToolUseEntry(platform: NodeJS.Platform, hooksDir: string, repoRoot: string): HookEntry;
4
+ export declare function removeLegacyManagedShellPreToolUse(hooks: HooksFile, platform: NodeJS.Platform, hooksDir: string, repoRoot: string): HooksFile;
2
5
  export declare function mergeCursorHooksFile(current: HooksFile, platform: NodeJS.Platform, hooksDir: string, repoRoot: string): HooksFile;
6
+ export declare function hasDuplicateCursorShellGates(hooks: HooksFile, platform: NodeJS.Platform, hooksDir: string, repoRoot: string): boolean;
@@ -10,10 +10,37 @@ function mergeHookEntry(current, expected, placement) {
10
10
  }
11
11
  return [...filtered, expected];
12
12
  }
13
+ /** Legacy Belay-managed Shell preToolUse gate duplicated beforeShellExecution. */
14
+ export function legacyManagedShellPreToolUseEntry(platform, hooksDir, repoRoot) {
15
+ const managed = getManagedHookEntries(platform, hooksDir, repoRoot);
16
+ const referencePreToolUse = managed.find((entry) => entry.event === 'preToolUse' && entry.definition.matcher === 'Write')?.definition;
17
+ if (!referencePreToolUse) {
18
+ throw new Error('managed hook definitions missing for legacy Shell migration');
19
+ }
20
+ return {
21
+ command: referencePreToolUse.command,
22
+ matcher: 'Shell',
23
+ };
24
+ }
25
+ export function removeLegacyManagedShellPreToolUse(hooks, platform, hooksDir, repoRoot) {
26
+ const legacy = legacyManagedShellPreToolUseEntry(platform, hooksDir, repoRoot);
27
+ const preToolUse = hooks.hooks.preToolUse;
28
+ if (!Array.isArray(preToolUse)) {
29
+ return hooks;
30
+ }
31
+ return {
32
+ ...hooks,
33
+ hooks: {
34
+ ...hooks.hooks,
35
+ preToolUse: preToolUse.filter((entry) => !entryMatches(entry, legacy)),
36
+ },
37
+ };
38
+ }
13
39
  export function mergeCursorHooksFile(current, platform, hooksDir, repoRoot) {
40
+ const withoutLegacyShell = removeLegacyManagedShellPreToolUse(current, platform, hooksDir, repoRoot);
14
41
  const next = {
15
- version: current.version || 1,
16
- hooks: { ...current.hooks },
42
+ version: withoutLegacyShell.version || 1,
43
+ hooks: { ...withoutLegacyShell.hooks },
17
44
  };
18
45
  const managedEntries = getManagedHookEntries(platform, hooksDir, repoRoot);
19
46
  for (const { event, definition } of managedEntries) {
@@ -24,3 +51,11 @@ export function mergeCursorHooksFile(current, platform, hooksDir, repoRoot) {
24
51
  }
25
52
  return next;
26
53
  }
54
+ export function hasDuplicateCursorShellGates(hooks, platform, hooksDir, repoRoot) {
55
+ const legacy = legacyManagedShellPreToolUseEntry(platform, hooksDir, repoRoot);
56
+ const preToolUse = hooks.hooks.preToolUse;
57
+ if (!Array.isArray(preToolUse)) {
58
+ return false;
59
+ }
60
+ return preToolUse.some((entry) => entryMatches(entry, legacy));
61
+ }
@@ -65,6 +65,7 @@ export async function runShellGateHook() {
65
65
  kind: 'shell',
66
66
  cwd,
67
67
  command,
68
+ sourceEvent: 'beforeShellExecution',
68
69
  });
69
70
  jsonResponse(gateVerdictToCursorResponse(verdict));
70
71
  }
@@ -87,6 +88,7 @@ export async function runToolGateHook(eventName) {
87
88
  kind: 'subagent',
88
89
  cwd,
89
90
  payload,
91
+ sourceEvent: eventName,
90
92
  });
91
93
  jsonResponse(gateVerdictToCursorResponse(verdict));
92
94
  return;
@@ -97,6 +99,7 @@ export async function runToolGateHook(eventName) {
97
99
  cwd,
98
100
  payload,
99
101
  toolName,
102
+ sourceEvent: eventName,
100
103
  });
101
104
  jsonResponse(gateVerdictToCursorResponse(verdict));
102
105
  return;
@@ -107,6 +110,7 @@ export async function runToolGateHook(eventName) {
107
110
  cwd,
108
111
  payload,
109
112
  toolName,
113
+ sourceEvent: eventName,
110
114
  });
111
115
  jsonResponse(gateVerdictToCursorResponse(verdict));
112
116
  return;
@@ -116,6 +120,7 @@ export async function runToolGateHook(eventName) {
116
120
  kind: 'subagent',
117
121
  cwd,
118
122
  payload,
123
+ sourceEvent: eventName,
119
124
  });
120
125
  jsonResponse(gateVerdictToCursorResponse(verdict));
121
126
  return;
@@ -1,4 +1,6 @@
1
1
  import { type ApprovalReplayHint } from '../../core/approval-replay.js';
2
+ import { type ExecuteContainedDockerParams, executeContainedDocker } from '../../core/contained-execution/docker.js';
3
+ import { type ContainedExecutionMirrorHandle, type ContainedExecutionMirrorOptions } from '../../core/contained-execution/mirror.js';
2
4
  import { type GatedActionKind, type GatePermissionResponse, type GateVerdict } from '../../core/gate-contract.js';
3
5
  import { GateNormalizationError } from '../../core/gate-engine.js';
4
6
  import { type BelayConfigV3 } from '../../core/index.js';
@@ -30,6 +32,9 @@ export interface GateRuntimeDeps {
30
32
  stdout?: string;
31
33
  stderr?: string;
32
34
  }>;
35
+ readSignedAttestation: (filePath: string) => Promise<unknown>;
36
+ withContainedExecutionMirror: <T>(options: ContainedExecutionMirrorOptions, operation: (mirror: ContainedExecutionMirrorHandle) => Promise<T>) => Promise<T>;
37
+ executeContainedDocker: (params: ExecuteContainedDockerParams) => ReturnType<typeof executeContainedDocker>;
33
38
  }
34
39
  export declare function createDefaultGateRuntimeDeps(): GateRuntimeDeps;
35
40
  export declare function resolveGateConfig(ctx: Pick<GateRuntimeContext, 'layout' | 'repoRoot' | 'configPath'>, deps: GateRuntimeDeps): Promise<BelayConfigV3>;
@@ -41,6 +46,7 @@ export declare function evaluateGatedAction(ctx: GateRuntimeContext, deps: GateR
41
46
  command?: string;
42
47
  payload?: Record<string, unknown>;
43
48
  toolName?: string;
49
+ sourceEvent?: string;
44
50
  }): Promise<GateVerdict>;
45
51
  /** R39: unmapped Codex tools ask via pending approval — not hard deny without approval path. */
46
52
  export declare function gateUnmappedToolVerdict(ctx: GateRuntimeContext, deps: GateRuntimeDeps, toolName: string, payload: Record<string, unknown>): Promise<GateVerdict>;