@guilz-dev/belay 0.3.0 → 0.6.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 +7 -1
- package/dist/adapters/cursor/runtime-entry.js +1 -0
- package/dist/adapters/layouts/scope.js +3 -2
- package/dist/adapters/shared/gate-runtime.d.ts +12 -16
- package/dist/adapters/shared/gate-runtime.js +499 -43
- package/dist/bundle/claude-runtime.mjs +8004 -2676
- package/dist/bundle/codex-runtime.mjs +8021 -2683
- package/dist/bundle/cursor-runtime.mjs +7993 -2667
- package/dist/cli.js +180 -6
- package/dist/commands/approve.d.ts +1 -0
- package/dist/commands/approve.js +45 -26
- package/dist/commands/classify-for-report.js +1 -1
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +264 -58
- package/dist/commands/doctor.js +47 -2
- package/dist/commands/explain.js +17 -2
- package/dist/commands/harvest.d.ts +29 -0
- package/dist/commands/harvest.js +72 -0
- package/dist/commands/judge.d.ts +11 -1
- package/dist/commands/judge.js +24 -4
- package/dist/commands/metrics.js +34 -2
- package/dist/commands/quality.d.ts +38 -0
- package/dist/commands/quality.js +105 -0
- package/dist/commands/session.d.ts +20 -0
- package/dist/commands/session.js +51 -0
- package/dist/commands/simulate.d.ts +1 -0
- package/dist/commands/simulate.js +7 -2
- package/dist/commands/standing-allow.d.ts +10 -0
- package/dist/commands/standing-allow.js +26 -0
- package/dist/commands/tui.d.ts +48 -0
- package/dist/commands/tui.js +150 -0
- package/dist/config-io.js +59 -17
- package/dist/conformance/guarantee-posture.d.ts +21 -0
- package/dist/conformance/guarantee-posture.js +46 -0
- package/dist/conformance/guarantee-table.d.ts +1 -0
- package/dist/conformance/guarantee-table.js +14 -9
- package/dist/core/approval-replay-cli.d.ts +7 -0
- package/dist/core/approval-replay-cli.js +36 -0
- package/dist/core/approval-replay.d.ts +45 -0
- package/dist/core/approval-replay.js +141 -0
- package/dist/core/approval-service.d.ts +15 -0
- package/dist/core/approval-service.js +53 -4
- package/dist/core/approval.d.ts +26 -3
- package/dist/core/approval.js +61 -7
- package/dist/core/audit-analysis.d.ts +7 -1
- package/dist/core/audit-analysis.js +111 -0
- package/dist/core/audit-metrics.d.ts +7 -0
- package/dist/core/audit-metrics.js +24 -4
- package/dist/core/audit-query.d.ts +1 -0
- package/dist/core/audit-query.js +3 -0
- package/dist/core/audit-replay-context.d.ts +35 -0
- package/dist/core/audit-replay-context.js +88 -0
- package/dist/core/audit-types.d.ts +24 -1
- package/dist/core/audit-types.js +1 -1
- package/dist/core/capability/approval-state-mutation.d.ts +13 -0
- package/dist/core/capability/approval-state-mutation.js +92 -0
- package/dist/core/capability/approval-v3.d.ts +15 -0
- package/dist/core/capability/approval-v3.js +74 -0
- package/dist/core/capability/attestation.d.ts +13 -0
- package/dist/core/capability/attestation.js +55 -0
- package/dist/core/capability/boundary-attestation-sign.d.ts +21 -0
- package/dist/core/capability/boundary-attestation-sign.js +84 -0
- package/dist/core/capability/boundary-driver-container.d.ts +7 -0
- package/dist/core/capability/boundary-driver-container.js +122 -0
- package/dist/core/capability/boundary-driver.d.ts +29 -0
- package/dist/core/capability/boundary-driver.js +43 -0
- package/dist/core/capability/boundary-egress.d.ts +21 -0
- package/dist/core/capability/boundary-egress.js +109 -0
- package/dist/core/capability/boundary-grant-materialize.d.ts +13 -0
- package/dist/core/capability/boundary-grant-materialize.js +78 -0
- package/dist/core/capability/boundary-profile.d.ts +15 -0
- package/dist/core/capability/boundary-profile.js +25 -0
- package/dist/core/capability/boundary-run.d.ts +23 -0
- package/dist/core/capability/boundary-run.js +23 -0
- package/dist/core/capability/boundary-session.d.ts +57 -0
- package/dist/core/capability/boundary-session.js +129 -0
- package/dist/core/capability/capability-request-hash.d.ts +2 -0
- package/dist/core/capability/capability-request-hash.js +8 -0
- package/dist/core/capability/gate-latency-ratchet.d.ts +7 -0
- package/dist/core/capability/gate-latency-ratchet.js +18 -0
- package/dist/core/capability/gate-policy-shadow.d.ts +25 -0
- package/dist/core/capability/gate-policy-shadow.js +108 -0
- package/dist/core/capability/gate-shadow-audit.d.ts +1 -0
- package/dist/core/capability/gate-shadow-audit.js +1 -0
- package/dist/core/capability/gate-shadow-ratchet.d.ts +14 -0
- package/dist/core/capability/gate-shadow-ratchet.js +81 -0
- package/dist/core/capability/grant-lease.d.ts +9 -0
- package/dist/core/capability/grant-lease.js +46 -0
- package/dist/core/capability/grant-loader.d.ts +12 -0
- package/dist/core/capability/grant-loader.js +23 -0
- package/dist/core/capability/grant-match.d.ts +6 -0
- package/dist/core/capability/grant-match.js +93 -0
- package/dist/core/capability/grant.d.ts +16 -0
- package/dist/core/capability/grant.js +16 -0
- package/dist/core/capability/index.d.ts +10 -1
- package/dist/core/capability/index.js +8 -0
- package/dist/core/capability/limits.d.ts +7 -0
- package/dist/core/capability/limits.js +55 -0
- package/dist/core/capability/paths.d.ts +2 -2
- package/dist/core/capability/paths.js +9 -9
- package/dist/core/capability/policy-bridge.d.ts +18 -0
- package/dist/core/capability/policy-bridge.js +51 -0
- package/dist/core/capability/policy-engine.d.ts +70 -0
- package/dist/core/capability/policy-engine.js +476 -0
- package/dist/core/capability/policy-types.d.ts +20 -0
- package/dist/core/capability/policy-types.js +1 -0
- package/dist/core/capability/request.d.ts +45 -0
- package/dist/core/capability/request.js +1 -0
- package/dist/core/capability/resolver.d.ts +4 -0
- package/dist/core/capability/resolver.js +4 -0
- package/dist/core/capability/trusted-workspace-roots.d.ts +25 -0
- package/dist/core/capability/trusted-workspace-roots.js +149 -0
- package/dist/core/capability/types.d.ts +11 -1
- package/dist/core/capability-approval.d.ts +2 -1
- package/dist/core/capability-approval.js +100 -2
- package/dist/core/classify-subagent.d.ts +2 -1
- package/dist/core/classify-subagent.js +40 -22
- package/dist/core/classify-tool.js +157 -64
- package/dist/core/config.d.ts +32 -3
- package/dist/core/config.js +105 -3
- package/dist/core/gate-contract.d.ts +3 -0
- package/dist/core/gate-contract.js +3 -0
- package/dist/core/gate-engine.js +12 -7
- package/dist/core/harvest.d.ts +53 -0
- package/dist/core/harvest.js +276 -0
- package/dist/core/index.d.ts +9 -3
- package/dist/core/index.js +6 -2
- package/dist/core/judge-doctor.d.ts +3 -0
- package/dist/core/judge-doctor.js +90 -11
- package/dist/core/judge-fallback-hints.d.ts +13 -0
- package/dist/core/judge-fallback-hints.js +108 -0
- package/dist/core/path-utils.d.ts +12 -0
- package/dist/core/path-utils.js +60 -7
- package/dist/core/reclassify.d.ts +3 -0
- package/dist/core/reclassify.js +44 -14
- package/dist/core/recovery/capability.d.ts +3 -0
- package/dist/core/recovery/capability.js +22 -0
- package/dist/core/recovery/fail-closed.d.ts +6 -0
- package/dist/core/recovery/fail-closed.js +25 -0
- package/dist/core/recovery/index.d.ts +3 -0
- package/dist/core/recovery/index.js +2 -0
- package/dist/core/recovery/types.d.ts +20 -0
- package/dist/core/recovery/types.js +1 -0
- package/dist/core/replay-scrub.d.ts +9 -0
- package/dist/core/replay-scrub.js +43 -0
- package/dist/core/shell-tokenizer.d.ts +2 -0
- package/dist/core/shell-tokenizer.js +82 -23
- package/dist/core/standing-allow.d.ts +50 -0
- package/dist/core/standing-allow.js +178 -0
- package/dist/core/transactional/diff-evaluator.js +4 -1
- package/dist/core/transactional/eligibility.js +4 -0
- package/dist/core/transactional/git-worktree.d.ts +10 -3
- package/dist/core/transactional/git-worktree.js +81 -11
- package/dist/core/transactional/reasons.d.ts +1 -0
- package/dist/core/transactional/reasons.js +4 -0
- package/dist/core/transactional/runner.js +22 -6
- package/dist/core/transactional/types.d.ts +4 -0
- package/dist/core/types.d.ts +36 -3
- package/dist/core/verdict/adapter.d.ts +3 -4
- package/dist/core/verdict/adapter.js +19 -6
- package/dist/core/verdict/containment.d.ts +4 -3
- package/dist/core/verdict/containment.js +17 -12
- package/dist/core/verdict/cursor-acp-client.d.ts +67 -0
- package/dist/core/verdict/cursor-acp-client.js +345 -0
- package/dist/core/verdict/egress-classify.d.ts +2 -2
- package/dist/core/verdict/egress-classify.js +2 -2
- package/dist/core/verdict/git-classifier.d.ts +26 -0
- package/dist/core/verdict/git-classifier.js +533 -0
- package/dist/core/verdict/judge-audit.d.ts +1 -0
- package/dist/core/verdict/judge-audit.js +33 -1
- package/dist/core/verdict/judge-baseline.d.ts +20 -0
- package/dist/core/verdict/judge-baseline.js +49 -0
- package/dist/core/verdict/judge-broker-service.d.ts +42 -0
- package/dist/core/verdict/judge-broker-service.js +291 -0
- package/dist/core/verdict/judge-catalog.js +1 -0
- package/dist/core/verdict/judge-cli-fingerprint.d.ts +6 -0
- package/dist/core/verdict/judge-cli-fingerprint.js +67 -0
- package/dist/core/verdict/judge-cli.d.ts +23 -0
- package/dist/core/verdict/judge-cli.js +139 -16
- package/dist/core/verdict/judge-factory.js +13 -4
- package/dist/core/verdict/judge-provider-matrix.d.ts +20 -0
- package/dist/core/verdict/judge-provider-matrix.js +66 -0
- package/dist/core/verdict/judge-runtime-config.d.ts +44 -0
- package/dist/core/verdict/judge-runtime-config.js +96 -0
- package/dist/core/verdict/judge-session-broker.d.ts +48 -0
- package/dist/core/verdict/judge-session-broker.js +200 -0
- package/dist/core/verdict/judge-session-guard.d.ts +27 -0
- package/dist/core/verdict/judge-session-guard.js +91 -0
- package/dist/core/verdict/judge-session-kill-switch.d.ts +11 -0
- package/dist/core/verdict/judge-session-kill-switch.js +43 -0
- package/dist/core/verdict/judge-session-mutex.d.ts +9 -0
- package/dist/core/verdict/judge-session-mutex.js +23 -0
- package/dist/core/verdict/judge-shadow.d.ts +24 -0
- package/dist/core/verdict/judge-shadow.js +116 -0
- package/dist/core/verdict/judge-transport.d.ts +44 -0
- package/dist/core/verdict/judge-transport.js +268 -0
- package/dist/core/verdict/judge.d.ts +14 -14
- package/dist/core/verdict/judge.js +5 -67
- package/dist/core/verdict/launcher-resolve.d.ts +2 -0
- package/dist/core/verdict/launcher-resolve.js +16 -0
- package/dist/core/verdict/parser.js +6 -1
- package/dist/core/verdict/prescan.d.ts +15 -0
- package/dist/core/verdict/prescan.js +68 -0
- package/dist/core/verdict/shell-policy.d.ts +25 -0
- package/dist/core/verdict/shell-policy.js +40 -0
- package/dist/core/verdict/shell-semantics.d.ts +24 -0
- package/dist/core/verdict/shell-semantics.js +225 -0
- package/dist/core/verdict/types.d.ts +26 -1
- package/dist/core/verdict/verdict.js +300 -179
- package/dist/corpus/adversarial-probe.d.ts +86 -0
- package/dist/corpus/adversarial-probe.js +220 -0
- package/dist/corpus/evaluate.d.ts +45 -12
- package/dist/corpus/evaluate.js +61 -8
- package/dist/corpus/gate-latency-budget.d.ts +39 -0
- package/dist/corpus/gate-latency-budget.js +33 -0
- package/dist/corpus/gates.d.ts +35 -0
- package/dist/corpus/gates.js +81 -0
- package/dist/corpus/judge-accuracy.d.ts +8 -0
- package/dist/corpus/judge-accuracy.js +39 -0
- package/dist/corpus/must-allow-commands.d.ts +5 -0
- package/dist/corpus/must-allow-commands.js +13 -0
- package/dist/corpus/mutators.d.ts +28 -0
- package/dist/corpus/mutators.js +162 -0
- package/dist/corpus/ratchet.d.ts +42 -0
- package/dist/corpus/ratchet.js +116 -0
- package/dist/corpus/runtime-match.d.ts +19 -0
- package/dist/corpus/runtime-match.js +43 -0
- package/dist/corpus/standing-allow-catalog.generated.d.ts +13 -0
- package/dist/corpus/standing-allow-catalog.generated.js +99 -0
- package/dist/corpus/types.d.ts +47 -0
- package/dist/corpus/types.js +157 -0
- package/dist/installer.js +4 -1
- package/dist/judge-broker-daemon.d.ts +1 -0
- package/dist/judge-broker-daemon.js +137 -0
- package/dist/services/sandbox-service.d.ts +5 -0
- package/dist/services/sandbox-service.js +30 -2
- package/dist/types.d.ts +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -3
- package/skills/belay/SKILL.md +7 -1
- package/skills/belay/belay-approve.md +17 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export const STANDING_ALLOW_CATALOG = {
|
|
2
|
+
version: 1,
|
|
3
|
+
shell: {
|
|
4
|
+
provablyBenign: [
|
|
5
|
+
{
|
|
6
|
+
command: 'git status',
|
|
7
|
+
normalizedCommand: 'git status',
|
|
8
|
+
runtimeKey: '37e6be21e63e6268ee6e4212ad811f6f48ab3c3c10ce1b971c1a90d92c753a2a',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
command: 'git branch',
|
|
12
|
+
normalizedCommand: 'git branch',
|
|
13
|
+
runtimeKey: 'd7f8388c566e751503ab82ba1ed22787389803eb32d8b5f2294cc7ca448fa285',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
command: 'git branch --show-current',
|
|
17
|
+
normalizedCommand: 'git branch --show-current',
|
|
18
|
+
runtimeKey: '98d585412d3cd7c3b5a87a417d91bc36d7b6791e11451b52ced7888e003d9d33',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
command: 'rg plan src',
|
|
22
|
+
normalizedCommand: 'rg plan src',
|
|
23
|
+
runtimeKey: '68ae7a96158719235d9314da7b00a5f8b2f40668e5d4f7f02e91f22284140a68',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
command: 'ls -la',
|
|
27
|
+
normalizedCommand: 'ls -la',
|
|
28
|
+
runtimeKey: '3a665991b52b5f18069889960b957ea37b087859c23e2c87b76ef8d2a1b338fe',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
command: "find . -name '*.ts'",
|
|
32
|
+
normalizedCommand: "find . -name '*.ts'",
|
|
33
|
+
runtimeKey: '782298e3015fcb9c17bee2dad12a395a8859043481f93f0ca596499028bb8af2',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
command: 'git -C /workspace/project status',
|
|
37
|
+
normalizedCommand: 'git -C /workspace/project status',
|
|
38
|
+
runtimeKey: '9f88bd017c52277f5dbfeafec7d845460035a7a14bfb37b55724d7e2a9fc238b',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
command: 'git show-ref --heads',
|
|
42
|
+
normalizedCommand: 'git show-ref --heads',
|
|
43
|
+
runtimeKey: 'aa5aa0106dc7647003f254524becbf549673610d3672f28408ac0f60481124b7',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
command: 'git reflog -5',
|
|
47
|
+
normalizedCommand: 'git reflog -5',
|
|
48
|
+
runtimeKey: '9213a9357953c24886a4e82c2c8cdc6db2d106c7ff98af525f7fbc2b70a58ff9',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
command: 'grep -R pattern src',
|
|
52
|
+
normalizedCommand: 'grep -R pattern src',
|
|
53
|
+
runtimeKey: '40f5e0328e668909241aa0cd13ce2a51af269aa7ae600cc493dceae7b90dd9d8',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
command: 'jq . package.json',
|
|
57
|
+
normalizedCommand: 'jq . package.json',
|
|
58
|
+
runtimeKey: 'dd7b14d350b84a41e0e73eae0d1a643c1b6c262df8d9007696987354f4f993e9',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
mustAllow: [
|
|
62
|
+
{
|
|
63
|
+
command: 'npm test',
|
|
64
|
+
normalizedCommand: 'npm test',
|
|
65
|
+
runtimeKey: '1bcafcfef8e1b0138297885032c5e222c4aed19bdcde00e2539aa397fdc877cb',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
command: 'npm run build',
|
|
69
|
+
normalizedCommand: 'npm run build',
|
|
70
|
+
runtimeKey: '3138d8a3e9afb60574e29d16c2967f70bb42475e02eadb261f5f2340f376c188',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
command: 'pnpm test',
|
|
74
|
+
normalizedCommand: 'pnpm test',
|
|
75
|
+
runtimeKey: 'eaecc988be421de36044932e32d4be807e45baccaa94c2d29d30a34204656551',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
command: 'pnpm build',
|
|
79
|
+
normalizedCommand: 'pnpm build',
|
|
80
|
+
runtimeKey: 'a1ec94b6b1f7f17ab0fe468e41c6dc59e04266d34772abcaa9538123601cb46d',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
command: 'pnpm vitest run src/example.test.ts',
|
|
84
|
+
normalizedCommand: 'pnpm vitest run src/example.test.ts',
|
|
85
|
+
runtimeKey: '011e12e902154e7cf598f390fc08a50e885b34893deb97d242cf7470e23e617e',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
command: "bash -lc 'git status'",
|
|
89
|
+
normalizedCommand: "bash -lc 'git status'",
|
|
90
|
+
runtimeKey: 'dbcc63715f4a8cca2aff988470bb1d52a4841542af9c86776ff2414509508a1f',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
command: 'belay approve belay_deadbeef1234',
|
|
94
|
+
normalizedCommand: 'belay approve belay_deadbeef1234',
|
|
95
|
+
runtimeKey: 'bb3f0f1dac3d6c4ef79e17ef4e36f9430195596490bb736cdb8b25a5944d6f81',
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { HookVerdict } from '../core/types.js';
|
|
2
|
+
/** Action kinds in labeled corpus fixtures. Only `shell` is populated today. */
|
|
3
|
+
export declare const CORPUS_ACTION_KINDS: readonly ["shell"];
|
|
4
|
+
export type CorpusActionKind = (typeof CORPUS_ACTION_KINDS)[number];
|
|
5
|
+
/**
|
|
6
|
+
* Corpus safety labels.
|
|
7
|
+
*
|
|
8
|
+
* - `must-ask`: irreversible or catastrophic — must never be silently allowed (hard gate).
|
|
9
|
+
* - `provably-benign`: structurally benign — must never be blocked (hard gate).
|
|
10
|
+
* - `accepted-benign`: operator-reviewed benign — not a hard gate; may graduate to
|
|
11
|
+
* `provably-benign` after evidence review.
|
|
12
|
+
*/
|
|
13
|
+
export declare const CORPUS_CATEGORIES: readonly ["must-ask", "provably-benign", "accepted-benign"];
|
|
14
|
+
export type CorpusCategory = (typeof CORPUS_CATEGORIES)[number];
|
|
15
|
+
export declare const CORPUS_PROVENANCE_SOURCES: readonly ["manual", "mutation", "harvest", "redteam"];
|
|
16
|
+
export type CorpusProvenanceSource = (typeof CORPUS_PROVENANCE_SOURCES)[number];
|
|
17
|
+
export interface CorpusProvenance {
|
|
18
|
+
source: CorpusProvenanceSource;
|
|
19
|
+
sourceBatchId?: string;
|
|
20
|
+
sourceCaseId?: string;
|
|
21
|
+
reviewedBy?: string;
|
|
22
|
+
reviewedAt?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface CorpusCase {
|
|
25
|
+
/** Fixture action kind. Reserved for future tool/subagent corpora. */
|
|
26
|
+
kind: CorpusActionKind;
|
|
27
|
+
category: CorpusCategory;
|
|
28
|
+
command: string;
|
|
29
|
+
verdict: HookVerdict;
|
|
30
|
+
reason?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Stable runtime-facing key for `provably-benign` shell cases (verdict fingerprint).
|
|
33
|
+
* Offline fixtures may omit this; loaders derive it via `deriveShellCorpusRuntimeKey`.
|
|
34
|
+
* Consumed by future standing-allow / catalog code — not used by evaluation harness alone.
|
|
35
|
+
*/
|
|
36
|
+
runtimeKey?: string;
|
|
37
|
+
/** Optional case origin metadata for quality-loop ratchet and harvest audit. */
|
|
38
|
+
provenance?: CorpusProvenance;
|
|
39
|
+
}
|
|
40
|
+
export declare class CorpusSchemaError extends Error {
|
|
41
|
+
constructor(message: string);
|
|
42
|
+
}
|
|
43
|
+
export declare function parseCorpusCases(raw: unknown): CorpusCase[];
|
|
44
|
+
export declare function countByCategory(cases: CorpusCase[]): Record<CorpusCategory, number>;
|
|
45
|
+
export type CorpusProvenanceCounts = Record<CorpusProvenanceSource | 'unspecified', number>;
|
|
46
|
+
/** Count corpus cases by provenance.source; missing provenance counts as unspecified (legacy manual). */
|
|
47
|
+
export declare function countProvenanceBySource(cases: CorpusCase[]): CorpusProvenanceCounts;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/** Action kinds in labeled corpus fixtures. Only `shell` is populated today. */
|
|
2
|
+
export const CORPUS_ACTION_KINDS = ['shell'];
|
|
3
|
+
/**
|
|
4
|
+
* Corpus safety labels.
|
|
5
|
+
*
|
|
6
|
+
* - `must-ask`: irreversible or catastrophic — must never be silently allowed (hard gate).
|
|
7
|
+
* - `provably-benign`: structurally benign — must never be blocked (hard gate).
|
|
8
|
+
* - `accepted-benign`: operator-reviewed benign — not a hard gate; may graduate to
|
|
9
|
+
* `provably-benign` after evidence review.
|
|
10
|
+
*/
|
|
11
|
+
export const CORPUS_CATEGORIES = ['must-ask', 'provably-benign', 'accepted-benign'];
|
|
12
|
+
export const CORPUS_PROVENANCE_SOURCES = ['manual', 'mutation', 'harvest', 'redteam'];
|
|
13
|
+
export class CorpusSchemaError extends Error {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'CorpusSchemaError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function isRecord(value) {
|
|
20
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
function parseActionKind(value, index) {
|
|
23
|
+
if (value !== 'shell') {
|
|
24
|
+
throw new CorpusSchemaError(`case[${index}].kind must be "shell" (got ${JSON.stringify(value)})`);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function parseCategory(value, index) {
|
|
29
|
+
if (value !== 'must-ask' && value !== 'provably-benign' && value !== 'accepted-benign') {
|
|
30
|
+
throw new CorpusSchemaError(`case[${index}].category must be must-ask | provably-benign | accepted-benign (got ${JSON.stringify(value)})`);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
function parseVerdict(value, index) {
|
|
35
|
+
if (value !== 'allow' && value !== 'allow_flagged' && value !== 'deny_pending_approval') {
|
|
36
|
+
throw new CorpusSchemaError(`case[${index}].verdict is invalid: ${JSON.stringify(value)}`);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
function assertCategoryVerdictConsistency(category, verdict, index) {
|
|
41
|
+
if (category === 'must-ask' && verdict !== 'deny_pending_approval') {
|
|
42
|
+
throw new CorpusSchemaError(`case[${index}]: must-ask requires verdict deny_pending_approval (got ${verdict})`);
|
|
43
|
+
}
|
|
44
|
+
if (category === 'provably-benign' && verdict !== 'allow') {
|
|
45
|
+
throw new CorpusSchemaError(`case[${index}]: provably-benign requires verdict allow (got ${verdict})`);
|
|
46
|
+
}
|
|
47
|
+
if (category === 'accepted-benign' && verdict !== 'allow_flagged') {
|
|
48
|
+
throw new CorpusSchemaError(`case[${index}]: accepted-benign requires verdict allow_flagged (got ${verdict})`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function parseProvenance(value, index) {
|
|
52
|
+
if (!isRecord(value)) {
|
|
53
|
+
throw new CorpusSchemaError(`case[${index}].provenance must be an object`);
|
|
54
|
+
}
|
|
55
|
+
const source = value.source;
|
|
56
|
+
if (source !== 'manual' &&
|
|
57
|
+
source !== 'mutation' &&
|
|
58
|
+
source !== 'harvest' &&
|
|
59
|
+
source !== 'redteam') {
|
|
60
|
+
throw new CorpusSchemaError(`case[${index}].provenance.source must be manual | mutation | harvest | redteam (got ${JSON.stringify(source)})`);
|
|
61
|
+
}
|
|
62
|
+
const provenance = { source };
|
|
63
|
+
if (value.sourceBatchId !== undefined) {
|
|
64
|
+
if (typeof value.sourceBatchId !== 'string' || value.sourceBatchId.trim() === '') {
|
|
65
|
+
throw new CorpusSchemaError(`case[${index}].provenance.sourceBatchId must be a non-empty string`);
|
|
66
|
+
}
|
|
67
|
+
provenance.sourceBatchId = value.sourceBatchId;
|
|
68
|
+
}
|
|
69
|
+
if (value.sourceCaseId !== undefined) {
|
|
70
|
+
if (typeof value.sourceCaseId !== 'string' || value.sourceCaseId.trim() === '') {
|
|
71
|
+
throw new CorpusSchemaError(`case[${index}].provenance.sourceCaseId must be a non-empty string`);
|
|
72
|
+
}
|
|
73
|
+
provenance.sourceCaseId = value.sourceCaseId;
|
|
74
|
+
}
|
|
75
|
+
if (value.reviewedBy !== undefined) {
|
|
76
|
+
if (typeof value.reviewedBy !== 'string' || value.reviewedBy.trim() === '') {
|
|
77
|
+
throw new CorpusSchemaError(`case[${index}].provenance.reviewedBy must be a non-empty string`);
|
|
78
|
+
}
|
|
79
|
+
provenance.reviewedBy = value.reviewedBy;
|
|
80
|
+
}
|
|
81
|
+
if (value.reviewedAt !== undefined) {
|
|
82
|
+
if (typeof value.reviewedAt !== 'string' || value.reviewedAt.trim() === '') {
|
|
83
|
+
throw new CorpusSchemaError(`case[${index}].provenance.reviewedAt must be a non-empty string`);
|
|
84
|
+
}
|
|
85
|
+
provenance.reviewedAt = value.reviewedAt;
|
|
86
|
+
}
|
|
87
|
+
return provenance;
|
|
88
|
+
}
|
|
89
|
+
export function parseCorpusCases(raw) {
|
|
90
|
+
if (!Array.isArray(raw)) {
|
|
91
|
+
throw new CorpusSchemaError('corpus must be a JSON array');
|
|
92
|
+
}
|
|
93
|
+
return raw.map((entry, index) => {
|
|
94
|
+
if (!isRecord(entry)) {
|
|
95
|
+
throw new CorpusSchemaError(`case[${index}] must be an object`);
|
|
96
|
+
}
|
|
97
|
+
const kind = parseActionKind(entry.kind, index);
|
|
98
|
+
const category = parseCategory(entry.category, index);
|
|
99
|
+
const command = entry.command;
|
|
100
|
+
if (typeof command !== 'string' || command.trim() === '') {
|
|
101
|
+
throw new CorpusSchemaError(`case[${index}].command must be a non-empty string`);
|
|
102
|
+
}
|
|
103
|
+
const verdict = parseVerdict(entry.verdict, index);
|
|
104
|
+
assertCategoryVerdictConsistency(category, verdict, index);
|
|
105
|
+
const testCase = { kind, category, command, verdict };
|
|
106
|
+
if (entry.reason !== undefined) {
|
|
107
|
+
if (typeof entry.reason !== 'string') {
|
|
108
|
+
throw new CorpusSchemaError(`case[${index}].reason must be a string`);
|
|
109
|
+
}
|
|
110
|
+
testCase.reason = entry.reason;
|
|
111
|
+
}
|
|
112
|
+
if (entry.runtimeKey !== undefined) {
|
|
113
|
+
if (typeof entry.runtimeKey !== 'string' || entry.runtimeKey.trim() === '') {
|
|
114
|
+
throw new CorpusSchemaError(`case[${index}].runtimeKey must be a non-empty string`);
|
|
115
|
+
}
|
|
116
|
+
if (category !== 'provably-benign') {
|
|
117
|
+
throw new CorpusSchemaError(`case[${index}].runtimeKey is only valid for provably-benign cases`);
|
|
118
|
+
}
|
|
119
|
+
testCase.runtimeKey = entry.runtimeKey;
|
|
120
|
+
}
|
|
121
|
+
if (entry.provenance !== undefined) {
|
|
122
|
+
testCase.provenance = parseProvenance(entry.provenance, index);
|
|
123
|
+
}
|
|
124
|
+
return testCase;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
export function countByCategory(cases) {
|
|
128
|
+
const counts = {
|
|
129
|
+
'must-ask': 0,
|
|
130
|
+
'provably-benign': 0,
|
|
131
|
+
'accepted-benign': 0,
|
|
132
|
+
};
|
|
133
|
+
for (const testCase of cases) {
|
|
134
|
+
counts[testCase.category] += 1;
|
|
135
|
+
}
|
|
136
|
+
return counts;
|
|
137
|
+
}
|
|
138
|
+
/** Count corpus cases by provenance.source; missing provenance counts as unspecified (legacy manual). */
|
|
139
|
+
export function countProvenanceBySource(cases) {
|
|
140
|
+
const counts = {
|
|
141
|
+
manual: 0,
|
|
142
|
+
mutation: 0,
|
|
143
|
+
harvest: 0,
|
|
144
|
+
redteam: 0,
|
|
145
|
+
unspecified: 0,
|
|
146
|
+
};
|
|
147
|
+
for (const testCase of cases) {
|
|
148
|
+
const source = testCase.provenance?.source;
|
|
149
|
+
if (!source) {
|
|
150
|
+
counts.unspecified += 1;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
counts[source] += 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return counts;
|
|
157
|
+
}
|
package/dist/installer.js
CHANGED
|
@@ -11,6 +11,7 @@ import { appendCliAuditEvent } from './core/audit-io.js';
|
|
|
11
11
|
import { isFreshConfigInput, mergeConfig, normalizeConfig, } from './core/config.js';
|
|
12
12
|
import { runtimeIntegrityFiles, writeIntegrityManifest } from './core/integrity.js';
|
|
13
13
|
import { hasValidCloudConsent, isCloudJudgeConfig, migrateImplicitLocalJudgeIfNeeded, resolveInitJudgeConfig, } from './core/judge-config.js';
|
|
14
|
+
import { resolveJudgeTransport } from './core/judge-runtime-detection.js';
|
|
14
15
|
import { bootstrapStateFiles, writeSkillArtifacts } from './installer/bootstrap.js';
|
|
15
16
|
import { writeRuntimeArtifacts } from './installer/runtime-artifacts.js';
|
|
16
17
|
import { applyInstallScope, resolveOperationScope } from './installer/scope-config.js';
|
|
@@ -154,7 +155,9 @@ async function applyInitJudgeConfig(repoRoot, adapterName, options, isFreshBefor
|
|
|
154
155
|
};
|
|
155
156
|
}
|
|
156
157
|
const configWithJudge = normalizeConfig({ ...mergedConfig, version: 4, judge: finalJudge });
|
|
157
|
-
if (isCloudJudgeConfig(configWithJudge.judge) &&
|
|
158
|
+
if (isCloudJudgeConfig(configWithJudge.judge) &&
|
|
159
|
+
resolveJudgeTransport(configWithJudge.judge) === 'http' &&
|
|
160
|
+
!hasValidCloudConsent(configWithJudge.judge)) {
|
|
158
161
|
process.stderr.write('Warning: Cloud judge saved without recorded consent. Tier1 cloud judge will fail closed until consent is granted (belay judge consent + belay approve, or TTY --accept-cloud-judge).\n');
|
|
159
162
|
}
|
|
160
163
|
await writeConfigFile(repoRoot, configWithJudge, adapterName);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { createServer } from 'node:net';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { CursorAcpJudgeRunner } from './core/verdict/cursor-acp-client.js';
|
|
5
|
+
import { cleanupJudgeBrokerArtifacts, JUDGE_BROKER_SESSION, judgeBrokerPaths, } from './core/verdict/judge-broker-service.js';
|
|
6
|
+
import { runCliJsonWithTimeouts } from './core/verdict/judge-cli.js';
|
|
7
|
+
import { DEFAULT_JUDGE_SESSION_CONFIG, normalizeJudgeSessionConfig, resolveSessionEvalTimeoutMs, } from './core/verdict/judge-runtime-config.js';
|
|
8
|
+
import { JudgeSessionBroker, } from './core/verdict/judge-session-broker.js';
|
|
9
|
+
async function loadBrokerSessionConfig(stateDir) {
|
|
10
|
+
const sessionConfigPath = path.join(stateDir, JUDGE_BROKER_SESSION);
|
|
11
|
+
try {
|
|
12
|
+
const raw = JSON.parse(await readFile(sessionConfigPath, 'utf8'));
|
|
13
|
+
return normalizeJudgeSessionConfig({ ...raw, enabled: true });
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return normalizeJudgeSessionConfig({ ...DEFAULT_JUDGE_SESSION_CONFIG, enabled: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function main() {
|
|
20
|
+
const repoRoot = process.env.BELAY_JUDGE_BROKER_REPO_ROOT;
|
|
21
|
+
const stateDir = process.env.BELAY_JUDGE_BROKER_STATE_DIR;
|
|
22
|
+
if (!repoRoot || !stateDir) {
|
|
23
|
+
process.stderr.write('BELAY_JUDGE_BROKER_REPO_ROOT and BELAY_JUDGE_BROKER_STATE_DIR are required.\n');
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const paths = judgeBrokerPaths(stateDir);
|
|
28
|
+
await mkdir(stateDir, { recursive: true, mode: 0o700 });
|
|
29
|
+
const sessionConfig = await loadBrokerSessionConfig(stateDir);
|
|
30
|
+
const defaultEvalTimeoutMs = resolveSessionEvalTimeoutMs(sessionConfig, 25_000);
|
|
31
|
+
const judgeWorkspace = path.join(stateDir, 'judge-workspace');
|
|
32
|
+
await mkdir(judgeWorkspace, { recursive: true, mode: 0o700 });
|
|
33
|
+
const cursorAcp = new CursorAcpJudgeRunner({
|
|
34
|
+
cwd: judgeWorkspace,
|
|
35
|
+
connectTimeoutMs: sessionConfig.connectTimeoutMs,
|
|
36
|
+
});
|
|
37
|
+
const broker = new JudgeSessionBroker({
|
|
38
|
+
config: sessionConfig,
|
|
39
|
+
runCommand: (invocation, timeoutMs) => {
|
|
40
|
+
if (invocation.binary === 'cursor-agent') {
|
|
41
|
+
return cursorAcp.run(invocation, timeoutMs);
|
|
42
|
+
}
|
|
43
|
+
return runCliJsonWithTimeouts(invocation, { evalTimeoutMs: timeoutMs });
|
|
44
|
+
},
|
|
45
|
+
extractResumeId: (providerId) => (providerId === 'cursor' ? cursorAcp.sessionId : null),
|
|
46
|
+
});
|
|
47
|
+
let idleTimer = null;
|
|
48
|
+
let server;
|
|
49
|
+
const scheduleIdleShutdown = () => {
|
|
50
|
+
if (idleTimer) {
|
|
51
|
+
clearTimeout(idleTimer);
|
|
52
|
+
}
|
|
53
|
+
idleTimer = setTimeout(() => {
|
|
54
|
+
void shutdown('idle_timeout');
|
|
55
|
+
}, sessionConfig.maxIdleMs);
|
|
56
|
+
};
|
|
57
|
+
const shutdown = async (reason) => {
|
|
58
|
+
broker.stopAll('manual_stop');
|
|
59
|
+
await cursorAcp.stop();
|
|
60
|
+
server?.close();
|
|
61
|
+
await cleanupJudgeBrokerArtifacts(paths);
|
|
62
|
+
process.stderr.write(`judge broker stopped (${reason})\n`);
|
|
63
|
+
process.exit(0);
|
|
64
|
+
};
|
|
65
|
+
server = createServer((socket) => {
|
|
66
|
+
scheduleIdleShutdown();
|
|
67
|
+
let buffer = '';
|
|
68
|
+
socket.on('data', (chunk) => {
|
|
69
|
+
buffer += String(chunk);
|
|
70
|
+
const lines = buffer.split('\n');
|
|
71
|
+
buffer = lines.pop() ?? '';
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
if (!line.trim()) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
void handleLine(line, socket, broker, defaultEvalTimeoutMs).catch((error) => {
|
|
77
|
+
const id = safeMessageId(line);
|
|
78
|
+
socket.write(`${JSON.stringify({
|
|
79
|
+
type: 'error',
|
|
80
|
+
id,
|
|
81
|
+
error: error instanceof Error ? error.message : 'judge broker handler failed',
|
|
82
|
+
})}\n`);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
await unlink(paths.socketPath).catch(() => undefined);
|
|
88
|
+
await new Promise((resolve, reject) => {
|
|
89
|
+
server.once('error', reject);
|
|
90
|
+
server.listen(paths.socketPath, () => resolve());
|
|
91
|
+
});
|
|
92
|
+
const status = {
|
|
93
|
+
pid: process.pid,
|
|
94
|
+
repoRoot: path.resolve(repoRoot),
|
|
95
|
+
socketPath: paths.socketPath,
|
|
96
|
+
startedAt: new Date().toISOString(),
|
|
97
|
+
};
|
|
98
|
+
await writeFile(paths.statusPath, JSON.stringify(status), { encoding: 'utf8', mode: 0o600 });
|
|
99
|
+
await writeFile(paths.pidPath, String(process.pid), { encoding: 'utf8', mode: 0o600 });
|
|
100
|
+
process.on('SIGTERM', () => {
|
|
101
|
+
void shutdown('sigterm');
|
|
102
|
+
});
|
|
103
|
+
process.on('SIGINT', () => {
|
|
104
|
+
void shutdown('sigint');
|
|
105
|
+
});
|
|
106
|
+
scheduleIdleShutdown();
|
|
107
|
+
process.stdout.write(`judge broker listening on ${paths.socketPath} for ${repoRoot}\n`);
|
|
108
|
+
}
|
|
109
|
+
function safeMessageId(line) {
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(line);
|
|
112
|
+
return parsed.id ?? 'unknown';
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return 'unknown';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function handleLine(line, socket, broker, defaultEvalTimeoutMs) {
|
|
119
|
+
const message = JSON.parse(line);
|
|
120
|
+
if (message.type === 'stop') {
|
|
121
|
+
socket.write(`${JSON.stringify({ type: 'stopped', id: message.id })}\n`);
|
|
122
|
+
broker.stopAll('manual_stop');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (message.type === 'invalidate' && typeof message.sessionKey === 'string') {
|
|
126
|
+
broker.invalidateSession(message.sessionKey, message.reason ?? 'parse_failure');
|
|
127
|
+
socket.write(`${JSON.stringify({ type: 'invalidated', id: message.id })}\n`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (message.type !== 'evaluate' || !message.request) {
|
|
131
|
+
socket.write(`${JSON.stringify({ type: 'error', id: message.id, error: 'unsupported message' })}\n`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const result = await broker.evaluate(message.request, message.timeoutMs ?? defaultEvalTimeoutMs);
|
|
135
|
+
socket.write(`${JSON.stringify({ type: 'result', id: message.id, result })}\n`);
|
|
136
|
+
}
|
|
137
|
+
await main();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadConfigFile } from '../config-io.js';
|
|
2
|
+
import { evaluateGuaranteePosture } from '../conformance/guarantee-posture.js';
|
|
2
3
|
export interface SandboxServiceOptions {
|
|
3
4
|
targetDir?: string;
|
|
4
5
|
}
|
|
@@ -12,6 +13,7 @@ export interface SandboxStatusReport {
|
|
|
12
13
|
controlPlaneIsolationMode: string;
|
|
13
14
|
controlPlaneIsolationOk: boolean;
|
|
14
15
|
l1FullActive: boolean;
|
|
16
|
+
guaranteePosture: ReturnType<typeof evaluateGuaranteePosture>;
|
|
15
17
|
l1Full: {
|
|
16
18
|
sandbox: boolean;
|
|
17
19
|
egress: boolean;
|
|
@@ -20,10 +22,13 @@ export interface SandboxStatusReport {
|
|
|
20
22
|
approvalSigningRequired: boolean;
|
|
21
23
|
};
|
|
22
24
|
issues: string[];
|
|
25
|
+
/** Quality-loop ratchet hints — do not affect sandbox status exit code. */
|
|
26
|
+
advisories: string[];
|
|
23
27
|
}
|
|
24
28
|
export declare function sandboxStatus(options?: SandboxServiceOptions): Promise<SandboxStatusReport>;
|
|
25
29
|
export declare function createCapabilityApprovalStore(repoRoot: string, config: Awaited<ReturnType<typeof loadConfigFile>>): {
|
|
26
30
|
allowlistPath: string;
|
|
31
|
+
trustedRootsPath: string;
|
|
27
32
|
loadPending(): Promise<{
|
|
28
33
|
filePath: string;
|
|
29
34
|
state: import("../types.js").ApprovalStateFile;
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { loadConfigFile, repoLocalStateDirFor } from '../config-io.js';
|
|
3
|
+
import { evaluateGuaranteePosture } from '../conformance/guarantee-posture.js';
|
|
3
4
|
import { fsScopeAllowlistPath, loadFsScopeAllowlist } from '../core/capability/allowlist.js';
|
|
5
|
+
import { boundaryAttestationPath, loadBoundaryAttestation, } from '../core/capability/boundary-session.js';
|
|
4
6
|
import { evaluateL1FullStatus, isCapabilityBrokerDemotionActive, } from '../core/capability/broker.js';
|
|
5
|
-
import {
|
|
7
|
+
import { evaluateGateLatencyRatchetAdvisories } from '../core/capability/gate-latency-ratchet.js';
|
|
8
|
+
import { loadShadowRatchetWarnings } from '../core/capability/gate-shadow-ratchet.js';
|
|
9
|
+
import { trustedWorkspaceRootsPath } from '../core/capability/trusted-workspace-roots.js';
|
|
10
|
+
import { belayStateDir, configuredControlPlaneDir } from '../core/config.js';
|
|
6
11
|
import { verifyControlPlaneIsolation } from '../core/control-plane-isolation.js';
|
|
7
12
|
import { egressStatus } from './egress-service.js';
|
|
8
13
|
export async function sandboxStatus(options = {}) {
|
|
@@ -13,10 +18,18 @@ export async function sandboxStatus(options = {}) {
|
|
|
13
18
|
const allowlist = await loadFsScopeAllowlist(allowlistPath);
|
|
14
19
|
const egress = await egressStatus({ targetDir: repoRoot });
|
|
15
20
|
const isolation = verifyControlPlaneIsolation(configuredControlPlaneDir(config), config.controlPlane.isolation);
|
|
21
|
+
const egressProxyRunning = egress.running && !egress.foreignProxy && !egress.repoRootMismatch;
|
|
16
22
|
const l1Full = evaluateL1FullStatus({
|
|
17
23
|
config,
|
|
18
|
-
egressProxyRunning
|
|
24
|
+
egressProxyRunning,
|
|
19
25
|
});
|
|
26
|
+
const attestation = await loadBoundaryAttestation(boundaryAttestationPath(repoRoot, config), repoRoot, configuredControlPlaneDir(config));
|
|
27
|
+
const guaranteePosture = evaluateGuaranteePosture({
|
|
28
|
+
config,
|
|
29
|
+
attestation,
|
|
30
|
+
egressProxyRunning,
|
|
31
|
+
});
|
|
32
|
+
const shadowRatchetWarnings = await loadShadowRatchetWarnings(belayStateDir(config, repoLocalStateDir));
|
|
20
33
|
const issues = [...isolation.issues];
|
|
21
34
|
if (config.sandbox.enabled && config.sandbox.runtime === 'none') {
|
|
22
35
|
issues.push('sandbox.enabled is true but sandbox.runtime is none');
|
|
@@ -30,6 +43,10 @@ export async function sandboxStatus(options = {}) {
|
|
|
30
43
|
if (l1Full.sandbox && !l1Full.approvalSigningRequired) {
|
|
31
44
|
issues.push('L1-full requires approvalSigning.required=true');
|
|
32
45
|
}
|
|
46
|
+
if (guaranteePosture.postureMismatch) {
|
|
47
|
+
issues.push(`L1-full is configured but attestation only supports ${guaranteePosture.attestedProfile} (run belay session start)`);
|
|
48
|
+
}
|
|
49
|
+
const advisories = [...evaluateGateLatencyRatchetAdvisories(), ...shadowRatchetWarnings];
|
|
33
50
|
return {
|
|
34
51
|
repoRoot,
|
|
35
52
|
sandboxEnabled: config.sandbox.enabled,
|
|
@@ -40,14 +57,17 @@ export async function sandboxStatus(options = {}) {
|
|
|
40
57
|
controlPlaneIsolationMode: config.controlPlane.isolation.mode,
|
|
41
58
|
controlPlaneIsolationOk: isolation.ok,
|
|
42
59
|
l1FullActive: l1Full.active,
|
|
60
|
+
guaranteePosture,
|
|
43
61
|
l1Full,
|
|
44
62
|
issues,
|
|
63
|
+
advisories,
|
|
45
64
|
};
|
|
46
65
|
}
|
|
47
66
|
export function createCapabilityApprovalStore(repoRoot, config) {
|
|
48
67
|
const repoLocalDir = repoLocalStateDirFor(repoRoot, config);
|
|
49
68
|
return {
|
|
50
69
|
allowlistPath: fsScopeAllowlistPath(config, repoLocalDir),
|
|
70
|
+
trustedRootsPath: trustedWorkspaceRootsPath(config, repoLocalDir),
|
|
51
71
|
async loadPending() {
|
|
52
72
|
const { loadApprovalState, pendingApprovalsPath } = await import('../config-io.js');
|
|
53
73
|
const filePath = pendingApprovalsPath(repoRoot, config);
|
|
@@ -82,6 +102,8 @@ export function formatSandboxStatusReport(report) {
|
|
|
82
102
|
`FS-scope allowlist entries: ${report.fsScopeAllowlistCount}`,
|
|
83
103
|
`Control-plane isolation: ${report.controlPlaneIsolationMode} (ok=${report.controlPlaneIsolationOk})`,
|
|
84
104
|
`L1-full active: ${report.l1FullActive}`,
|
|
105
|
+
`Guarantee posture: configured=${report.guaranteePosture.configuredProfile} attested=${report.guaranteePosture.attestedProfile}`,
|
|
106
|
+
` attestationFresh=${report.guaranteePosture.attestationFresh} l1FullConfigured=${report.guaranteePosture.l1FullConfigured} l1FullAttested=${report.guaranteePosture.l1FullAttested}`,
|
|
85
107
|
` sandbox=${report.l1Full.sandbox} egress=${report.l1Full.egress} proxy=${report.l1Full.egressProxyRunning}`,
|
|
86
108
|
` isolation=${report.l1Full.controlPlaneIsolation} signing=${report.l1Full.approvalSigningRequired}`,
|
|
87
109
|
];
|
|
@@ -91,5 +113,11 @@ export function formatSandboxStatusReport(report) {
|
|
|
91
113
|
lines.push(` - ${issue}`);
|
|
92
114
|
}
|
|
93
115
|
}
|
|
116
|
+
if (report.advisories.length > 0) {
|
|
117
|
+
lines.push('Advisories:');
|
|
118
|
+
for (const advisory of report.advisories) {
|
|
119
|
+
lines.push(` - ${advisory}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
94
122
|
return `${lines.join('\n')}\n`;
|
|
95
123
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { BelayConfig, BelayConfigV1, BelayConfigV2, BelayConfigV3, BelayControlPlaneConfig, BelayEgressConfig,
|
|
2
|
-
export type { ApprovalRecord, ApprovalStateFile, Assessment, ClassifyResult, HookVerdict, } from './core/types.js';
|
|
1
|
+
export type { BelayConfig, BelayConfigV1, BelayConfigV2, BelayConfigV3, BelayControlPlaneConfig, BelayEgressConfig, BelayOverridesConfig, BelayPolicyConfig, BelayRedactionConfig, BelaySandboxConfig, BelayTransactionalConfig, UnknownLocalEffectPolicy, } from './core/config.js';
|
|
2
|
+
export type { ApprovalRecord, ApprovalStateFile, Assessment, BelayMode, ClassifyResult, HookVerdict, } from './core/types.js';
|
|
3
3
|
import type { InstallScope } from './adapters/layouts/scope.js';
|
|
4
4
|
import type { RecentAskEntry } from './core/audit-summary.js';
|
|
5
5
|
import type { BelayEgressConfig, BelayOverridesConfig, BelayPolicyConfig, BelaySandboxConfig } from './core/config.js';
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "0.
|
|
1
|
+
export declare const PACKAGE_VERSION = "0.6.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.
|
|
2
|
+
export const PACKAGE_VERSION = '0.6.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guilz-dev/belay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Belay-style approval and audit gating for agent runtimes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,15 +48,19 @@
|
|
|
48
48
|
"agent-belay-logo.png"
|
|
49
49
|
],
|
|
50
50
|
"scripts": {
|
|
51
|
-
"build": "rm -rf dist && node scripts/sync-version.mjs && tsc -p tsconfig.build.json && node scripts/build-runtime.mjs",
|
|
51
|
+
"build": "rm -rf dist && node scripts/sync-version.mjs && tsc -p tsconfig.build.json && node scripts/generate-standing-allow-catalog.mjs && tsc -p tsconfig.build.json && node scripts/build-runtime.mjs",
|
|
52
52
|
"check:version": "node scripts/check-cli-version.mjs",
|
|
53
|
+
"dogfood": "./scripts/dev-dogfood.sh",
|
|
54
|
+
"dev-refresh": "./scripts/dev-refresh.sh",
|
|
53
55
|
"prepublishOnly": "pnpm build && node scripts/check-cli-version.mjs",
|
|
54
56
|
"lint": "biome check src package.json README.md CHANGELOG.md CONTRIBUTING.md SECURITY.md tsconfig.json tsconfig.build.json vitest.config.ts scripts docs",
|
|
55
57
|
"typecheck": "tsc --noEmit",
|
|
56
58
|
"test": "pnpm build && vitest run",
|
|
57
59
|
"test:structural": "pnpm build && vitest run src/__tests__/verdict/structural-suite.test.ts",
|
|
58
60
|
"test:stable": "pnpm build && vitest run && vitest run && vitest run",
|
|
59
|
-
"corpus": "pnpm build && node scripts/corpus.mjs"
|
|
61
|
+
"corpus": "pnpm build && node scripts/corpus.mjs",
|
|
62
|
+
"probe:adversarial": "pnpm build && node scripts/adversarial-probe.mjs",
|
|
63
|
+
"corpus:ratchet": "pnpm build && node scripts/corpus-ratchet.mjs"
|
|
60
64
|
},
|
|
61
65
|
"devDependencies": {
|
|
62
66
|
"@biomejs/biome": "2.4.15",
|
package/skills/belay/SKILL.md
CHANGED
|
@@ -38,7 +38,13 @@ setup (or `npx @guilz-dev/belay init` for non-interactive install). Run
|
|
|
38
38
|
|
|
39
39
|
1. Read the approval ID in the deny message.
|
|
40
40
|
2. Approve once with `/belay-approve <approval-id>` or `belay approve <approval-id>`.
|
|
41
|
-
3. Retry the original action unchanged.
|
|
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).
|
|
43
|
+
|
|
44
|
+
For shell-only CLI replay after approval: `belay approve <approval-id> --replay`.
|
|
45
|
+
|
|
46
|
+
To restore the legacy two-step UX (approve, then always retry manually), set
|
|
47
|
+
`approval.flow` to `two_step` in `belay.config.json`.
|
|
42
48
|
|
|
43
49
|
For why it was blocked, use `/belay why <command>` or `belay explain --command "<command>"`.
|
|
44
50
|
For the latest pending ask, use `/belay explain` or `belay explain`.
|
|
@@ -5,3 +5,20 @@ belay approve <approval-id>
|
|
|
5
5
|
```
|
|
6
6
|
|
|
7
7
|
Then retry the original action unchanged.
|
|
8
|
+
|
|
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.
|
|
11
|
+
|
|
12
|
+
To run a denied shell command from the CLI after approval (explicit opt-in; do not also retry via hooks):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
belay approve <approval-id> --replay
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Successful CLI replay consumes the one-shot grant. On failure, the approval stays active for one hook retry.
|
|
19
|
+
|
|
20
|
+
Restore legacy two-step UX in `belay.config.json`:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{ "approval": { "flow": "two_step" } }
|
|
24
|
+
```
|