@guilz-dev/belay 0.7.0 → 0.8.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 +19 -19
- package/dist/adapters/shared/gate-runtime.js +23 -5
- package/dist/bundle/claude-runtime.mjs +4909 -3504
- package/dist/bundle/codex-runtime.mjs +5050 -3645
- package/dist/bundle/cursor-runtime.mjs +5050 -3645
- package/dist/commands/doctor.js +7 -0
- package/dist/commands/dogfood.js +1 -1
- package/dist/commands/explain.js +2 -2
- package/dist/commands/harvest.d.ts +0 -1
- package/dist/commands/harvest.js +1 -4
- package/dist/commands/metrics.js +6 -0
- package/dist/commands/quality.js +1 -3
- package/dist/conformance/guarantee-table.js +133 -11
- package/dist/conformance/types.d.ts +1 -0
- package/dist/core/audit-metrics.d.ts +1 -0
- package/dist/core/audit-metrics.js +12 -4
- package/dist/core/audit-types.d.ts +3 -0
- package/dist/core/capability/index.d.ts +1 -1
- package/dist/core/capability/policy-engine.d.ts +31 -1
- package/dist/core/capability/policy-engine.js +186 -0
- package/dist/core/capability/request.d.ts +9 -0
- package/dist/core/capability/resolver.d.ts +0 -1
- package/dist/core/capability/resolver.js +0 -1
- package/dist/core/config.js +0 -2
- package/dist/core/effect-ir/audit.js +1 -0
- package/dist/core/effect-ir/build.js +5 -1
- package/dist/core/effect-ir/index.d.ts +6 -1
- package/dist/core/effect-ir/index.js +2 -0
- package/dist/core/effect-ir/normalize.js +9 -3
- package/dist/core/effect-ir/policy.d.ts +2 -1
- package/dist/core/effect-ir/policy.js +93 -6
- package/dist/core/effect-ir/shell-build.d.ts +43 -0
- package/dist/core/effect-ir/shell-build.js +77 -0
- package/dist/core/effect-ir/shell-lower.d.ts +14 -0
- package/dist/core/effect-ir/shell-lower.js +1509 -0
- package/dist/core/effect-ir/types.d.ts +9 -2
- package/dist/core/gate-engine.js +63 -23
- package/dist/core/git-resource-identity.d.ts +26 -0
- package/dist/core/git-resource-identity.js +284 -0
- package/dist/core/harvest.d.ts +3 -7
- package/dist/core/harvest.js +4 -27
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +1 -0
- package/dist/core/path-utils.js +42 -23
- package/dist/core/shell-substitution.d.ts +1 -0
- package/dist/core/shell-substitution.js +61 -0
- package/dist/core/standing-allow.d.ts +1 -2
- package/dist/core/standing-allow.js +3 -20
- package/dist/core/types.d.ts +4 -1
- package/dist/core/verdict/adapter.js +15 -15
- package/dist/core/verdict/containment.js +4 -0
- package/dist/core/verdict/egress-classify.d.ts +12 -0
- package/dist/core/verdict/egress-classify.js +746 -0
- package/dist/core/verdict/git-classifier.d.ts +12 -0
- package/dist/core/verdict/git-classifier.js +320 -0
- package/dist/core/verdict/launcher-resolve.js +77 -17
- package/dist/core/verdict/parser.d.ts +2 -0
- package/dist/core/verdict/parser.js +95 -1
- package/dist/core/verdict/types.d.ts +2 -3
- package/dist/core/verdict/verdict.js +103 -952
- package/dist/corpus/must-allow-commands.d.ts +2 -1
- package/dist/corpus/must-allow-commands.js +2 -1
- package/dist/corpus/runtime-match.d.ts +2 -1
- package/dist/corpus/runtime-match.js +2 -1
- package/dist/corpus/types.d.ts +2 -2
- package/dist/templates.js +7 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/dist/core/verdict/overrides.d.ts +0 -7
- package/dist/core/verdict/overrides.js +0 -37
- package/dist/core/verdict/shell-policy.d.ts +0 -26
- package/dist/core/verdict/shell-policy.js +0 -40
- package/dist/corpus/standing-allow-catalog.generated.d.ts +0 -13
- package/dist/corpus/standing-allow-catalog.generated.js +0 -99
package/README.md
CHANGED
|
@@ -86,7 +86,8 @@ npx @guilz-dev/belay status
|
|
|
86
86
|
|
|
87
87
|
Fresh installs default to **fail-closed** shell policy: unknown or unparseable
|
|
88
88
|
shell commands are denied until approved. Use `belay explain` to inspect a
|
|
89
|
-
verdict
|
|
89
|
+
verdict. Correct inaccurate EffectPlan semantics or resource scope; otherwise
|
|
90
|
+
approve the exact request for one-shot, resource-scoped authorization.
|
|
90
91
|
|
|
91
92
|
## How it works
|
|
92
93
|
|
|
@@ -138,12 +139,12 @@ Belay is a layered hook gate, not a static denylist. Higher layers are opt-in.
|
|
|
138
139
|
| **L3** Prediction | Policy rules + command heuristics | default |
|
|
139
140
|
| **L4** Approval | Human one-shot / scoped approvals | default |
|
|
140
141
|
|
|
141
|
-
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
- Normalized shell authorization uses only canonical `EffectPlan` requirements.
|
|
143
|
+
Command lists, legacy overrides, corpus labels, and shell standing allows are inert.
|
|
144
|
+
- Payload-free reads are `allow`; reversible repository-local writes (including implicit
|
|
145
|
+
download output) are `allow_flagged`. Outside-repository writes, external mutation,
|
|
146
|
+
explicit payload/file/secret sends, high-stakes effects, and partial/indeterminate plans
|
|
147
|
+
require approval. See [ADR-004](./docs/adr/ADR-004-effectplan-shell-authority.md).
|
|
147
148
|
- Adversarial resistance requires the full L1 stack:
|
|
148
149
|
`belay init --preset l1-full-recommended`, verified with `belay sandbox status`.
|
|
149
150
|
|
|
@@ -188,18 +189,18 @@ npx @guilz-dev/belay dogfood # mode: audit, unknownLocalEffect: deny
|
|
|
188
189
|
# ...run normal agent work...
|
|
189
190
|
npx @guilz-dev/belay metrics # review what would have been blocked
|
|
190
191
|
npx @guilz-dev/belay status # check readiness
|
|
191
|
-
#
|
|
192
|
+
# inspect EffectPlan semantics and resource scope with `belay explain`, then:
|
|
192
193
|
npx @guilz-dev/belay dogfood --enforce
|
|
193
194
|
```
|
|
194
195
|
|
|
195
196
|
## Configuration
|
|
196
197
|
|
|
197
|
-
`belay.config.json` uses `version:
|
|
198
|
+
`belay.config.json` uses `version: 4`. v1/v2/v3 configs migrate automatically on
|
|
198
199
|
load.
|
|
199
200
|
|
|
200
201
|
```json
|
|
201
202
|
{
|
|
202
|
-
"version":
|
|
203
|
+
"version": 4,
|
|
203
204
|
"mode": "enforce",
|
|
204
205
|
"gates": {
|
|
205
206
|
"shell": true,
|
|
@@ -214,10 +215,6 @@ load.
|
|
|
214
215
|
"policy": {
|
|
215
216
|
"unknownLocalEffect": "allow_flagged"
|
|
216
217
|
},
|
|
217
|
-
"overrides": {
|
|
218
|
-
"allow": ["pnpm release:staging"],
|
|
219
|
-
"external": ["./scripts/release.sh"]
|
|
220
|
-
},
|
|
221
218
|
"redaction": {
|
|
222
219
|
"maskApprovalIds": true,
|
|
223
220
|
"maskBearerTokens": true,
|
|
@@ -238,12 +235,15 @@ load.
|
|
|
238
235
|
|
|
239
236
|
Notable settings:
|
|
240
237
|
|
|
241
|
-
- **`policy.unknownLocalEffect: "allow_flagged"`** (fresh default) —
|
|
242
|
-
|
|
243
|
-
|
|
238
|
+
- **`policy.unknownLocalEffect: "allow_flagged"`** (fresh default) — compatibility fallback
|
|
239
|
+
for legacy, non-EffectPlan classification paths. It cannot loosen a normalized shell
|
|
240
|
+
EffectPlan: partial/indeterminate plans and outside-repository writes still ask. Use
|
|
241
|
+
`"deny"` (via `belay dogfood`) for a stricter fallback.
|
|
244
242
|
- **`classifier.strictChains: true`** (default) — scans every `&&`, `|`, and `;`
|
|
245
|
-
segment and keeps the strictest
|
|
246
|
-
|
|
243
|
+
segment into the EffectPlan and keeps the strictest policy projection. Legacy
|
|
244
|
+
`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.
|
|
247
247
|
- **`controlPlane.enabled: true`** — stores approval state under
|
|
248
248
|
`~/.config/belay/` (or `XDG_CONFIG_HOME/belay`), shared across repos for the
|
|
249
249
|
current OS user. `upgrade` migrates repo-local approvals in; disabling merges
|
|
@@ -32,11 +32,25 @@ import { fingerprintReplayPayload } from '../../core/replay-scrub.js';
|
|
|
32
32
|
import { loadStandingAllow, resolveStandingAllowMatch, standingAllowFile, } from '../../core/standing-allow.js';
|
|
33
33
|
import { FILE_CHECKPOINT_ISOLATION_UNAVAILABLE, isTransactionalEligible, runTransactionalExecution, TRANSACTIONAL_ALREADY_APPLIED, TRANSACTIONAL_APPROVAL_BYPASS_REASONS, } from '../../core/transactional/index.js';
|
|
34
34
|
import { egressStatus } from '../../services/egress-service.js';
|
|
35
|
+
import { PACKAGE_VERSION } from '../../version.js';
|
|
35
36
|
import { protectedArtifactRoots } from '../layouts/protected-paths.js';
|
|
36
37
|
const EMPTY_APPROVALS = {
|
|
37
38
|
version: 1,
|
|
38
39
|
approvals: [],
|
|
39
40
|
};
|
|
41
|
+
const RUNTIME_PROVENANCE_KEY = Symbol.for('agent-belay.runtime-provenance');
|
|
42
|
+
function auditProvenance(config) {
|
|
43
|
+
const runtime = globalThis[RUNTIME_PROVENANCE_KEY];
|
|
44
|
+
const runtimeVersion = typeof runtime?.runtimeVersion === 'string' ? runtime.runtimeVersion : PACKAGE_VERSION;
|
|
45
|
+
const runtimeBuildStamp = typeof runtime?.runtimeBuildStamp === 'string'
|
|
46
|
+
? runtime.runtimeBuildStamp
|
|
47
|
+
: `${PACKAGE_VERSION}@source`;
|
|
48
|
+
return {
|
|
49
|
+
runtimeVersion,
|
|
50
|
+
runtimeBuildStamp,
|
|
51
|
+
configFingerprint: hashValue(canonicalStringify(config)),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
40
54
|
function adapterIdFromContext(ctx) {
|
|
41
55
|
if (ctx.config.adapter === 'cursor' ||
|
|
42
56
|
ctx.config.adapter === 'claude' ||
|
|
@@ -75,11 +89,17 @@ export function createDefaultGateRuntimeDeps() {
|
|
|
75
89
|
async appendAudit(ctx, event) {
|
|
76
90
|
const auditPath = path.join(ctx.repoRoot, ctx.config.audit.logPath);
|
|
77
91
|
await mkdir(path.dirname(auditPath), { recursive: true });
|
|
78
|
-
const
|
|
92
|
+
const provenance = auditProvenance(ctx.config);
|
|
93
|
+
const record = {
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
...event,
|
|
96
|
+
...provenance,
|
|
97
|
+
};
|
|
79
98
|
if (!ctx.config.audit.includeAssessment) {
|
|
80
99
|
delete record.assessment;
|
|
81
100
|
}
|
|
82
101
|
const scrubbed = scrubValue(record, scrubOptionsFromConfig(ctx.config));
|
|
102
|
+
Object.assign(scrubbed, provenance);
|
|
83
103
|
await writeFile(auditPath, `${JSON.stringify(scrubbed)}\n`, {
|
|
84
104
|
encoding: 'utf8',
|
|
85
105
|
flag: 'a',
|
|
@@ -767,7 +787,8 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
767
787
|
agent_message: agentMessage,
|
|
768
788
|
});
|
|
769
789
|
}
|
|
770
|
-
if (
|
|
790
|
+
if (kind !== 'shell' &&
|
|
791
|
+
result.verdict === 'deny_pending_approval' &&
|
|
771
792
|
ctx.config.mode === 'enforce' &&
|
|
772
793
|
!TRANSACTIONAL_APPROVAL_BYPASS_REASONS.has(result.reason)) {
|
|
773
794
|
const standingAllowPath = standingAllowFile(ctx.config, ctx.layout.repoLocalStateDir(ctx.repoRoot));
|
|
@@ -784,9 +805,6 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
784
805
|
verdict: 'allow',
|
|
785
806
|
reason: 'standing_allow',
|
|
786
807
|
standingAllowSource: standingMatch.source,
|
|
787
|
-
...(standingMatch.catalogCommand
|
|
788
|
-
? { standingAllowCatalogCommand: standingMatch.catalogCommand }
|
|
789
|
-
: {}),
|
|
790
808
|
wouldBlock: false,
|
|
791
809
|
permission: 'allow',
|
|
792
810
|
});
|