@guilz-dev/belay 0.1.1 → 0.3.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 +55 -15
- package/dist/adapters/shared/gate-runtime.js +25 -9
- package/dist/bundle/claude-runtime.mjs +1786 -379
- package/dist/bundle/codex-runtime.mjs +1785 -379
- package/dist/bundle/cursor-runtime.mjs +1786 -379
- package/dist/cli.js +201 -11
- package/dist/commands/approve.js +11 -0
- package/dist/commands/classify-for-report.js +3 -3
- package/dist/commands/config.d.ts +70 -0
- package/dist/commands/config.js +494 -0
- package/dist/commands/doctor.js +3 -2
- package/dist/commands/explain.js +14 -14
- package/dist/commands/health-snapshot.d.ts +6 -0
- package/dist/commands/health-snapshot.js +34 -0
- package/dist/commands/judge.d.ts +90 -0
- package/dist/commands/judge.js +285 -0
- package/dist/commands/recover.js +2 -2
- package/dist/commands/status.js +2 -2
- package/dist/commands/stdin-key.d.ts +1 -0
- package/dist/commands/stdin-key.js +8 -0
- package/dist/conformance/guarantee-table.js +12 -0
- package/dist/conformance/types.d.ts +2 -0
- package/dist/core/approval.d.ts +3 -0
- package/dist/core/approval.js +18 -3
- package/dist/core/audit-io.d.ts +2 -0
- package/dist/core/audit-io.js +14 -0
- package/dist/core/audit-query.js +5 -1
- package/dist/core/capability/index.d.ts +2 -2
- package/dist/core/capability/index.js +2 -2
- package/dist/core/capability/paths.d.ts +1 -0
- package/dist/core/capability/paths.js +74 -6
- package/dist/core/capability/reasons.d.ts +3 -0
- package/dist/core/capability/reasons.js +8 -0
- package/dist/core/classify-tool.js +97 -27
- package/dist/core/config-layers.js +2 -1
- package/dist/core/config.d.ts +23 -3
- package/dist/core/config.js +112 -13
- package/dist/core/credential-store.d.ts +11 -0
- package/dist/core/credential-store.js +60 -0
- package/dist/core/gate-contract.d.ts +1 -1
- package/dist/core/gate-contract.js +1 -1
- package/dist/core/gate-engine.js +105 -14
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/core/integrity.d.ts +2 -0
- package/dist/core/integrity.js +13 -0
- package/dist/core/judge-api-key.d.ts +19 -0
- package/dist/core/judge-api-key.js +74 -0
- package/dist/core/judge-cloud-consent.d.ts +13 -0
- package/dist/core/judge-cloud-consent.js +38 -0
- package/dist/core/judge-config.d.ts +45 -4
- package/dist/core/judge-config.js +266 -44
- package/dist/core/judge-doctor.d.ts +6 -1
- package/dist/core/judge-doctor.js +148 -97
- package/dist/core/judge-model-discovery.d.ts +24 -0
- package/dist/core/judge-model-discovery.js +168 -0
- package/dist/core/judge-model-policy.d.ts +5 -0
- package/dist/core/judge-model-policy.js +21 -0
- package/dist/core/judge-runtime-detection.d.ts +9 -0
- package/dist/core/judge-runtime-detection.js +68 -0
- package/dist/core/transactional/diff-evaluator.js +1 -19
- package/dist/core/types.d.ts +7 -3
- package/dist/core/{v2 → verdict}/adapter.d.ts +1 -0
- package/dist/core/{v2 → verdict}/adapter.js +16 -4
- package/dist/core/{v2 → verdict}/containment.d.ts +5 -0
- package/dist/core/{v2 → verdict}/containment.js +32 -2
- package/dist/core/{v2 → verdict}/egress-classify.js +3 -0
- package/dist/core/verdict/judge-catalog.d.ts +40 -0
- package/dist/core/verdict/judge-catalog.js +148 -0
- package/dist/core/verdict/judge-cli.d.ts +23 -0
- package/dist/core/verdict/judge-cli.js +280 -0
- package/dist/core/{v2 → verdict}/judge-factory.d.ts +15 -4
- package/dist/core/verdict/judge-factory.js +178 -0
- package/dist/core/{v2 → verdict}/judge.d.ts +20 -1
- package/dist/core/{v2 → verdict}/judge.js +85 -18
- package/dist/core/{v2 → verdict}/launcher-resolve.js +72 -1
- package/dist/core/verdict/persistent-paths.d.ts +8 -0
- package/dist/core/verdict/persistent-paths.js +52 -0
- package/dist/core/{v2 → verdict}/types.d.ts +6 -2
- package/dist/core/{v2 → verdict}/verdict.js +176 -46
- package/dist/corpus/evaluate.js +2 -2
- package/dist/installer.js +66 -14
- package/dist/types.d.ts +8 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
- package/skills/belay/SKILL.md +8 -7
- package/dist/commands/init-wizard.d.ts +0 -16
- package/dist/commands/init-wizard.js +0 -50
- package/dist/core/v2/judge-factory.js +0 -75
- /package/dist/core/{v2 → verdict}/egress-classify.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/fingerprint.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/fingerprint.js +0 -0
- /package/dist/core/{v2 → verdict}/index.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/index.js +0 -0
- /package/dist/core/{v2 → verdict}/judge-audit.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/judge-audit.js +0 -0
- /package/dist/core/{v2 → verdict}/judge-outbound.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/judge-outbound.js +0 -0
- /package/dist/core/{v2 → verdict}/launcher-resolve.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/overrides.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/overrides.js +0 -0
- /package/dist/core/{v2 → verdict}/parser.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/parser.js +0 -0
- /package/dist/core/{v2 → verdict}/types.js +0 -0
- /package/dist/core/{v2 → verdict}/verdict.d.ts +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Belay
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@guilz-dev/belay)
|
|
4
|
+
[](https://skills.sh/guilz-dev/belay)
|
|
4
5
|
[](https://github.com/guilz-dev/belay/actions/workflows/ci.yml)
|
|
5
6
|
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
@@ -33,8 +34,8 @@ policy to maintain.
|
|
|
33
34
|
| **Claude Code** | Supported | `.claude/settings.json` | `.claude/belay.config.json` |
|
|
34
35
|
| **Codex** | Experimental | `.codex/config.toml` | `.codex/belay.config.json` |
|
|
35
36
|
|
|
36
|
-
Pick the adapter at install time with `--adapter cursor|claude|codex` (or
|
|
37
|
-
`
|
|
37
|
+
Pick the adapter at install time with `--adapter cursor|claude|codex` (or use
|
|
38
|
+
`belay config` interactively). Hosts use different hook event names, but Belay registers
|
|
38
39
|
the same runners (`belay-tool-gate`, `belay-before-submit`, `belay-audit`) at
|
|
39
40
|
equivalent lifecycle points:
|
|
40
41
|
|
|
@@ -67,8 +68,8 @@ audit instead of guessing.
|
|
|
67
68
|
## Quick start
|
|
68
69
|
|
|
69
70
|
```bash
|
|
70
|
-
# Interactive setup (
|
|
71
|
-
|
|
71
|
+
# Interactive setup (adapter, scope, skill, judge provider, credentials)
|
|
72
|
+
belay config
|
|
72
73
|
|
|
73
74
|
# Or non-interactive
|
|
74
75
|
npx @guilz-dev/belay init --adapter claude # Claude Code
|
|
@@ -130,6 +131,9 @@ Belay is a layered hook gate, not a static denylist. Higher layers are opt-in.
|
|
|
130
131
|
- L3 command lists are **not security boundaries** by themselves — see
|
|
131
132
|
[docs/ops/semver-policy.md](./docs/ops/semver-policy.md) and
|
|
132
133
|
[docs/guarantee-table.md](./docs/guarantee-table.md).
|
|
134
|
+
- At default L3, **local-recoverable mutations outside the repo** pass after Tier1
|
|
135
|
+
(e.g. IDE plan files). To deny repo-external writes at the OS boundary, enable L1-full
|
|
136
|
+
(`sandbox.runtime` ≠ `none`). Tier1 requires a working judge (local Ollama by default).
|
|
133
137
|
- Adversarial resistance requires the full L1 stack:
|
|
134
138
|
`belay init --preset l1-full-recommended`, verified with `belay sandbox status`.
|
|
135
139
|
|
|
@@ -148,12 +152,23 @@ and skill under `~/.cursor/`, so the gate is user-wide while `belay.config.json`
|
|
|
148
152
|
approvals, and audit stay repo-local.
|
|
149
153
|
|
|
150
154
|
**Skill-only.** The skill is just a UX layer (slash commands + guidance) and does
|
|
151
|
-
**not** enable gating on its own
|
|
155
|
+
**not** enable gating on its own. Install from [skills.sh](https://skills.sh/guilz-dev/belay)
|
|
156
|
+
or GitHub:
|
|
152
157
|
|
|
153
158
|
```bash
|
|
159
|
+
# Cursor
|
|
154
160
|
npx skills add guilz-dev/belay --skill belay -a cursor -y
|
|
161
|
+
|
|
162
|
+
# Claude Code
|
|
163
|
+
npx skills add guilz-dev/belay --skill belay -a claude-code -y
|
|
164
|
+
|
|
165
|
+
# Codex
|
|
166
|
+
npx skills add guilz-dev/belay --skill belay -a codex -y
|
|
155
167
|
```
|
|
156
168
|
|
|
169
|
+
Running `npx skills add` also registers anonymous install telemetry on skills.sh,
|
|
170
|
+
which is how the skill appears in the directory leaderboard.
|
|
171
|
+
|
|
157
172
|
Runtime enforcement still requires `belay init` in the target repository.
|
|
158
173
|
|
|
159
174
|
## Dogfood → enforce
|
|
@@ -213,8 +228,9 @@ load.
|
|
|
213
228
|
|
|
214
229
|
Notable settings:
|
|
215
230
|
|
|
216
|
-
- **`policy.unknownLocalEffect: "
|
|
217
|
-
|
|
231
|
+
- **`policy.unknownLocalEffect: "allow_flagged"`** (fresh default) — after Tier1
|
|
232
|
+
says recoverable, structurally unknown local commands run with an audit flag. Use
|
|
233
|
+
`"deny"` (via `belay dogfood`) to ask on those commands instead.
|
|
218
234
|
- **`classifier.strictChains: true`** (default) — scans every `&&`, `|`, and `;`
|
|
219
235
|
segment and keeps the strictest verdict. Override lists match exact command or
|
|
220
236
|
segment keys only.
|
|
@@ -223,20 +239,37 @@ Notable settings:
|
|
|
223
239
|
current OS user. `upgrade` migrates repo-local approvals in; disabling merges
|
|
224
240
|
them back. File-mutation tools and shell redirects cannot write control-plane
|
|
225
241
|
paths while it is enabled.
|
|
226
|
-
- **Cloud judge** —
|
|
227
|
-
`
|
|
228
|
-
|
|
229
|
-
`
|
|
230
|
-
|
|
242
|
+
- **Cloud judge** — configure with `belay config` (interactive) or `belay config set judge.providerId <id>`.
|
|
243
|
+
Providers: `ollama`, `codex`, `claude`, `cursor`. **Provider** is the vendor/service
|
|
244
|
+
(`judge.providerId`); **driver** is the API compatibility layer (`judge.provider`:
|
|
245
|
+
`ollama`, `openai-compatible`, or `anthropic`); **host** is where hooks install
|
|
246
|
+
(`config.adapter`: `cursor`, `claude`, `codex`). Set `judge.endpoint` when needed;
|
|
247
|
+
credentials via `belay config credential mode project|apiKey` or env vars. Record egress
|
|
248
|
+
consent during `belay config` or via `belay judge consent` → `belay approve` →
|
|
249
|
+
`belay judge use … --cloud-consent-approval-id`. Cloud providers can use native CLI
|
|
250
|
+
transport without `judge.endpoint` when the host CLI is available (`codex-cli`,
|
|
251
|
+
`cursor-cli`, `claude-cli`); HTTP transport still requires endpoint and recorded consent.
|
|
252
|
+
Fresh installs default to the
|
|
253
|
+
host-matched provider (`cursor` → `cursor`, etc.). `belay judge use` remains available
|
|
254
|
+
as a secondary path.
|
|
255
|
+
|
|
256
|
+
Legacy `judge.model: auto` in config files is normalized to the provider catalog default on load
|
|
257
|
+
(with a warning); new `auto` values are rejected on CLI, `belay config set`, and `belay judge use`.
|
|
258
|
+
On an installed repo, interactive `belay config` defaults to judge-only setup without re-running
|
|
259
|
+
`init`. Model discovery is covered by unit tests with mocks; set `BELAY_LIVE_CLI_DISCOVERY=1` locally
|
|
260
|
+
for optional live CLI probes. Cloud egress consent is enforced for HTTP transport; native CLI
|
|
261
|
+
transport uses the host session and does not require `judge.endpoint`. `BELAY_JUDGE_MODEL_RESOLVED`
|
|
262
|
+
applies only under Vitest (test overrides).
|
|
231
263
|
|
|
232
264
|
## Command reference
|
|
233
265
|
|
|
234
266
|
```bash
|
|
235
267
|
belay init [--adapter cursor|claude|codex] [--scope project|global]
|
|
236
268
|
[--preset strict|standard|audit-first|l1-full-recommended]
|
|
237
|
-
[--with-skill] [--dogfood]
|
|
238
|
-
belay
|
|
239
|
-
belay
|
|
269
|
+
[--migrate-judge-default] [--with-skill] [--dogfood]
|
|
270
|
+
belay config # interactive setup (primary)
|
|
271
|
+
belay config list|get|set|unset|judge|credential …
|
|
272
|
+
belay upgrade [--migrate-judge-default] # refresh hooks + runtime, migrate config
|
|
240
273
|
belay dogfood [--enforce] # toggle audit / enforce mode
|
|
241
274
|
belay doctor [--fix] # check (and repair) floor health
|
|
242
275
|
belay status # show install scope / skill-only state
|
|
@@ -250,6 +283,13 @@ belay egress <start|stop|status|env>
|
|
|
250
283
|
belay sandbox status
|
|
251
284
|
belay approve <approval-id> [--scope once|domain|path]
|
|
252
285
|
belay revoke <approval-id>
|
|
286
|
+
belay judge status
|
|
287
|
+
belay judge list
|
|
288
|
+
belay judge use <ollama|codex|claude|cursor> [--model <id>] [--endpoint <url>]
|
|
289
|
+
[--accept-cloud] [--cloud-consent-approval-id <id>]
|
|
290
|
+
[--credential project|apiKey] [--key-stdin] [--key-env <NAME>]
|
|
291
|
+
belay judge test
|
|
292
|
+
belay judge consent <provider-id> [--endpoint <url>]
|
|
253
293
|
```
|
|
254
294
|
|
|
255
295
|
## Coexisting with existing hooks
|
|
@@ -4,11 +4,11 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { recordApproval } from '../../core/approval-service.js';
|
|
6
6
|
import { issueApprovalToken } from '../../core/approval-token.js';
|
|
7
|
-
import { fsScopeAllowlistPath, isCapabilityBrokerDemotionActive, loadFsScopeAllowlistSync, shouldSkipBrokerApprovedOnce, } from '../../core/capability/index.js';
|
|
7
|
+
import { fsScopeAllowlistPath, isCapabilityBrokerDemotionActive, loadFsScopeAllowlistSync, shouldSkipBrokerApprovedOnce, shouldSkipBrokerApprovedRecord, } from '../../core/capability/index.js';
|
|
8
8
|
import { resolveLayeredConfig, teamConfigPath } from '../../core/config-layers.js';
|
|
9
9
|
import { classifyResultToGateVerdict, unnormalizedGateVerdict, } from '../../core/gate-contract.js';
|
|
10
10
|
import { classifyGatedActionAsync, extractAgentAssessment, GateNormalizationError, gateEnabledForAction, normalizeGatedAction, } from '../../core/gate-engine.js';
|
|
11
|
-
import { approvalCommandMatch, approvedApprovalsFile, buildRetryInstruction, canonicalStringify, classifierOptionsFromConfig, compactApprovals, configuredControlPlaneDir, createApprovalRecord, pendingApprovalsFile, resolveControlPlaneDir, scrubOptionsFromConfig, scrubValue, toolFingerprint, } from '../../core/index.js';
|
|
11
|
+
import { APPROVAL_EXECUTION_LEASE_MS, approvalCommandMatch, approvedApprovalsFile, buildRetryInstruction, canonicalStringify, classifierOptionsFromConfig, compactApprovals, configuredControlPlaneDir, createApprovalRecord, pendingApprovalsFile, resolveControlPlaneDir, scrubOptionsFromConfig, scrubValue, toolFingerprint, } from '../../core/index.js';
|
|
12
12
|
import { notifyDeny } from '../../core/notify.js';
|
|
13
13
|
import { isTransactionalEligible, runTransactionalExecution, TRANSACTIONAL_ALREADY_APPLIED, TRANSACTIONAL_APPROVAL_BYPASS_REASONS, } from '../../core/transactional/index.js';
|
|
14
14
|
import { protectedArtifactRoots } from '../layouts/protected-paths.js';
|
|
@@ -142,7 +142,15 @@ async function consumeApprovedApproval(ctx, deps, kind, fingerprint) {
|
|
|
142
142
|
await deps.writeApprovals(approved.filePath, approved.state);
|
|
143
143
|
return null;
|
|
144
144
|
}
|
|
145
|
-
const
|
|
145
|
+
const approval = approved.state.approvals[index];
|
|
146
|
+
if (approval.executionLeaseExpiresAt) {
|
|
147
|
+
await deps.writeApprovals(approved.filePath, approved.state);
|
|
148
|
+
return approval;
|
|
149
|
+
}
|
|
150
|
+
approved.state.approvals[index] = {
|
|
151
|
+
...approval,
|
|
152
|
+
executionLeaseExpiresAt: new Date(Date.now() + APPROVAL_EXECUTION_LEASE_MS).toISOString(),
|
|
153
|
+
};
|
|
146
154
|
await deps.writeApprovals(approved.filePath, approved.state);
|
|
147
155
|
return approval;
|
|
148
156
|
}
|
|
@@ -290,8 +298,8 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
290
298
|
predictedAssessment: auditExtras.predictedAssessment,
|
|
291
299
|
observedAssessment: auditExtras.observedAssessment,
|
|
292
300
|
mode: ctx.config.mode,
|
|
293
|
-
schemaVersion: result.
|
|
294
|
-
...(result.
|
|
301
|
+
schemaVersion: result.axes ? 2 : 1,
|
|
302
|
+
...(result.axes ?? {}),
|
|
295
303
|
...auditExtras.transactionalLayer,
|
|
296
304
|
};
|
|
297
305
|
if (result.reason === TRANSACTIONAL_ALREADY_APPLIED) {
|
|
@@ -314,10 +322,18 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
314
322
|
});
|
|
315
323
|
}
|
|
316
324
|
const brokerActive = isCapabilityBrokerDemotionActive(ctx.config);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
325
|
+
let approved = null;
|
|
326
|
+
if (!TRANSACTIONAL_APPROVAL_BYPASS_REASONS.has(result.reason) &&
|
|
327
|
+
!shouldSkipBrokerApprovedOnce(brokerActive, result.reason)) {
|
|
328
|
+
const approvedState = await deps.loadApprovals(ctx, 'approved-approvals.json');
|
|
329
|
+
approvedState.state = compactApprovals(approvedState.state);
|
|
330
|
+
const matchedApproval = approvedState.state.approvals.find((entry) => entry.kind === kind &&
|
|
331
|
+
entry.fingerprint === result.fingerprint &&
|
|
332
|
+
entry.repoRoot === ctx.repoRoot);
|
|
333
|
+
if (!shouldSkipBrokerApprovedRecord(brokerActive, matchedApproval?.reason)) {
|
|
334
|
+
approved = await consumeApprovedApproval(ctx, deps, kind, result.fingerprint);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
321
337
|
if (approved) {
|
|
322
338
|
await deps.appendAudit(ctx, {
|
|
323
339
|
...gateBase,
|