@guilz-dev/belay 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/adapters/cursor/runtime-entry.js +1 -0
  2. package/dist/adapters/shared/gate-runtime.d.ts +11 -14
  3. package/dist/adapters/shared/gate-runtime.js +277 -19
  4. package/dist/bundle/claude-runtime.mjs +3138 -699
  5. package/dist/bundle/codex-runtime.mjs +3162 -711
  6. package/dist/bundle/cursor-runtime.mjs +3140 -701
  7. package/dist/cli.js +137 -5
  8. package/dist/commands/approve.d.ts +1 -0
  9. package/dist/commands/approve.js +45 -26
  10. package/dist/commands/config.js +135 -52
  11. package/dist/commands/doctor.js +11 -1
  12. package/dist/commands/explain.js +6 -1
  13. package/dist/commands/harvest.d.ts +29 -0
  14. package/dist/commands/harvest.js +72 -0
  15. package/dist/commands/judge.d.ts +11 -1
  16. package/dist/commands/judge.js +21 -2
  17. package/dist/commands/metrics.js +34 -2
  18. package/dist/commands/quality.d.ts +38 -0
  19. package/dist/commands/quality.js +105 -0
  20. package/dist/commands/simulate.d.ts +1 -0
  21. package/dist/commands/simulate.js +7 -2
  22. package/dist/commands/standing-allow.d.ts +10 -0
  23. package/dist/commands/standing-allow.js +26 -0
  24. package/dist/commands/tui.d.ts +48 -0
  25. package/dist/commands/tui.js +150 -0
  26. package/dist/config-io.js +10 -5
  27. package/dist/core/approval-replay-cli.d.ts +7 -0
  28. package/dist/core/approval-replay-cli.js +36 -0
  29. package/dist/core/approval-replay.d.ts +45 -0
  30. package/dist/core/approval-replay.js +141 -0
  31. package/dist/core/approval-service.d.ts +15 -0
  32. package/dist/core/approval-service.js +47 -2
  33. package/dist/core/approval.d.ts +24 -3
  34. package/dist/core/approval.js +47 -4
  35. package/dist/core/audit-analysis.d.ts +7 -1
  36. package/dist/core/audit-analysis.js +111 -0
  37. package/dist/core/audit-metrics.d.ts +7 -0
  38. package/dist/core/audit-metrics.js +24 -4
  39. package/dist/core/audit-query.d.ts +1 -0
  40. package/dist/core/audit-query.js +3 -0
  41. package/dist/core/audit-replay-context.d.ts +35 -0
  42. package/dist/core/audit-replay-context.js +88 -0
  43. package/dist/core/audit-types.d.ts +24 -1
  44. package/dist/core/audit-types.js +1 -1
  45. package/dist/core/capability/index.d.ts +2 -1
  46. package/dist/core/capability/index.js +1 -0
  47. package/dist/core/capability/paths.d.ts +2 -2
  48. package/dist/core/capability/paths.js +9 -9
  49. package/dist/core/capability/trusted-workspace-roots.d.ts +25 -0
  50. package/dist/core/capability/trusted-workspace-roots.js +149 -0
  51. package/dist/core/capability/types.d.ts +11 -1
  52. package/dist/core/capability-approval.d.ts +2 -1
  53. package/dist/core/capability-approval.js +100 -2
  54. package/dist/core/classify-subagent.js +2 -20
  55. package/dist/core/classify-tool.js +32 -3
  56. package/dist/core/config.d.ts +18 -0
  57. package/dist/core/config.js +49 -0
  58. package/dist/core/gate-engine.js +6 -6
  59. package/dist/core/harvest.d.ts +53 -0
  60. package/dist/core/harvest.js +276 -0
  61. package/dist/core/index.d.ts +9 -3
  62. package/dist/core/index.js +6 -2
  63. package/dist/core/judge-doctor.d.ts +3 -0
  64. package/dist/core/judge-doctor.js +54 -0
  65. package/dist/core/path-utils.d.ts +11 -0
  66. package/dist/core/path-utils.js +56 -6
  67. package/dist/core/reclassify.d.ts +3 -0
  68. package/dist/core/reclassify.js +44 -14
  69. package/dist/core/replay-scrub.d.ts +9 -0
  70. package/dist/core/replay-scrub.js +43 -0
  71. package/dist/core/shell-tokenizer.d.ts +2 -0
  72. package/dist/core/shell-tokenizer.js +82 -23
  73. package/dist/core/standing-allow.d.ts +50 -0
  74. package/dist/core/standing-allow.js +175 -0
  75. package/dist/core/types.d.ts +15 -0
  76. package/dist/core/verdict/adapter.js +10 -1
  77. package/dist/core/verdict/containment.d.ts +4 -3
  78. package/dist/core/verdict/containment.js +17 -12
  79. package/dist/core/verdict/judge-audit.d.ts +1 -0
  80. package/dist/core/verdict/judge-audit.js +32 -1
  81. package/dist/core/verdict/judge-baseline.d.ts +20 -0
  82. package/dist/core/verdict/judge-baseline.js +48 -0
  83. package/dist/core/verdict/judge-broker-service.d.ts +42 -0
  84. package/dist/core/verdict/judge-broker-service.js +279 -0
  85. package/dist/core/verdict/judge-cli-fingerprint.d.ts +5 -0
  86. package/dist/core/verdict/judge-cli-fingerprint.js +61 -0
  87. package/dist/core/verdict/judge-cli.d.ts +23 -0
  88. package/dist/core/verdict/judge-cli.js +137 -16
  89. package/dist/core/verdict/judge-factory.js +13 -4
  90. package/dist/core/verdict/judge-provider-matrix.d.ts +20 -0
  91. package/dist/core/verdict/judge-provider-matrix.js +66 -0
  92. package/dist/core/verdict/judge-runtime-config.d.ts +42 -0
  93. package/dist/core/verdict/judge-runtime-config.js +92 -0
  94. package/dist/core/verdict/judge-session-broker.d.ts +48 -0
  95. package/dist/core/verdict/judge-session-broker.js +195 -0
  96. package/dist/core/verdict/judge-session-guard.d.ts +27 -0
  97. package/dist/core/verdict/judge-session-guard.js +91 -0
  98. package/dist/core/verdict/judge-session-kill-switch.d.ts +11 -0
  99. package/dist/core/verdict/judge-session-kill-switch.js +43 -0
  100. package/dist/core/verdict/judge-session-mutex.d.ts +9 -0
  101. package/dist/core/verdict/judge-session-mutex.js +23 -0
  102. package/dist/core/verdict/judge-shadow.d.ts +24 -0
  103. package/dist/core/verdict/judge-shadow.js +116 -0
  104. package/dist/core/verdict/judge-transport.d.ts +40 -0
  105. package/dist/core/verdict/judge-transport.js +258 -0
  106. package/dist/core/verdict/judge.d.ts +14 -1
  107. package/dist/core/verdict/judge.js +6 -5
  108. package/dist/core/verdict/parser.js +6 -1
  109. package/dist/core/verdict/types.d.ts +13 -0
  110. package/dist/core/verdict/verdict.js +7 -4
  111. package/dist/corpus/adversarial-probe.d.ts +86 -0
  112. package/dist/corpus/adversarial-probe.js +220 -0
  113. package/dist/corpus/evaluate.d.ts +45 -12
  114. package/dist/corpus/evaluate.js +62 -5
  115. package/dist/corpus/gates.d.ts +35 -0
  116. package/dist/corpus/gates.js +81 -0
  117. package/dist/corpus/judge-accuracy.d.ts +8 -0
  118. package/dist/corpus/judge-accuracy.js +39 -0
  119. package/dist/corpus/must-allow-commands.d.ts +5 -0
  120. package/dist/corpus/must-allow-commands.js +13 -0
  121. package/dist/corpus/mutators.d.ts +28 -0
  122. package/dist/corpus/mutators.js +151 -0
  123. package/dist/corpus/ratchet.d.ts +42 -0
  124. package/dist/corpus/ratchet.js +116 -0
  125. package/dist/corpus/runtime-match.d.ts +19 -0
  126. package/dist/corpus/runtime-match.js +45 -0
  127. package/dist/corpus/standing-allow-catalog.generated.d.ts +13 -0
  128. package/dist/corpus/standing-allow-catalog.generated.js +79 -0
  129. package/dist/corpus/types.d.ts +47 -0
  130. package/dist/corpus/types.js +157 -0
  131. package/dist/installer.js +4 -1
  132. package/dist/judge-broker-daemon.d.ts +1 -0
  133. package/dist/judge-broker-daemon.js +123 -0
  134. package/dist/services/sandbox-service.d.ts +1 -0
  135. package/dist/services/sandbox-service.js +2 -0
  136. package/dist/version.d.ts +1 -1
  137. package/dist/version.js +1 -1
  138. package/package.json +7 -3
  139. package/skills/belay/SKILL.md +7 -1
  140. package/skills/belay/belay-approve.md +17 -0
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) && !hasValidCloudConsent(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,123 @@
1
+ import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
2
+ import { createServer } from 'node:net';
3
+ import path from 'node:path';
4
+ import { cleanupJudgeBrokerArtifacts, JUDGE_BROKER_SESSION, judgeBrokerPaths, } from './core/verdict/judge-broker-service.js';
5
+ import { runCliJsonWithTimeouts } from './core/verdict/judge-cli.js';
6
+ import { DEFAULT_JUDGE_SESSION_CONFIG, normalizeJudgeSessionConfig, resolveSessionEvalTimeoutMs, } from './core/verdict/judge-runtime-config.js';
7
+ import { JudgeSessionBroker, } from './core/verdict/judge-session-broker.js';
8
+ async function loadBrokerSessionConfig(stateDir) {
9
+ const sessionConfigPath = path.join(stateDir, JUDGE_BROKER_SESSION);
10
+ try {
11
+ const raw = JSON.parse(await readFile(sessionConfigPath, 'utf8'));
12
+ return normalizeJudgeSessionConfig({ ...raw, enabled: true });
13
+ }
14
+ catch {
15
+ return normalizeJudgeSessionConfig({ ...DEFAULT_JUDGE_SESSION_CONFIG, enabled: true });
16
+ }
17
+ }
18
+ async function main() {
19
+ const repoRoot = process.env.BELAY_JUDGE_BROKER_REPO_ROOT;
20
+ const stateDir = process.env.BELAY_JUDGE_BROKER_STATE_DIR;
21
+ if (!repoRoot || !stateDir) {
22
+ process.stderr.write('BELAY_JUDGE_BROKER_REPO_ROOT and BELAY_JUDGE_BROKER_STATE_DIR are required.\n');
23
+ process.exitCode = 1;
24
+ return;
25
+ }
26
+ const paths = judgeBrokerPaths(stateDir);
27
+ await mkdir(stateDir, { recursive: true, mode: 0o700 });
28
+ const sessionConfig = await loadBrokerSessionConfig(stateDir);
29
+ const defaultEvalTimeoutMs = resolveSessionEvalTimeoutMs(sessionConfig, 25_000);
30
+ const broker = new JudgeSessionBroker({
31
+ config: sessionConfig,
32
+ runCommand: (invocation, timeoutMs) => runCliJsonWithTimeouts(invocation, { evalTimeoutMs: timeoutMs }),
33
+ });
34
+ let idleTimer = null;
35
+ let server;
36
+ const scheduleIdleShutdown = () => {
37
+ if (idleTimer) {
38
+ clearTimeout(idleTimer);
39
+ }
40
+ idleTimer = setTimeout(() => {
41
+ void shutdown('idle_timeout');
42
+ }, sessionConfig.maxIdleMs);
43
+ };
44
+ const shutdown = async (reason) => {
45
+ broker.stopAll('manual_stop');
46
+ server?.close();
47
+ await cleanupJudgeBrokerArtifacts(paths);
48
+ process.stderr.write(`judge broker stopped (${reason})\n`);
49
+ process.exit(0);
50
+ };
51
+ server = createServer((socket) => {
52
+ scheduleIdleShutdown();
53
+ let buffer = '';
54
+ socket.on('data', (chunk) => {
55
+ buffer += String(chunk);
56
+ const lines = buffer.split('\n');
57
+ buffer = lines.pop() ?? '';
58
+ for (const line of lines) {
59
+ if (!line.trim()) {
60
+ continue;
61
+ }
62
+ void handleLine(line, socket, broker, defaultEvalTimeoutMs).catch((error) => {
63
+ const id = safeMessageId(line);
64
+ socket.write(`${JSON.stringify({
65
+ type: 'error',
66
+ id,
67
+ error: error instanceof Error ? error.message : 'judge broker handler failed',
68
+ })}\n`);
69
+ });
70
+ }
71
+ });
72
+ });
73
+ await unlink(paths.socketPath).catch(() => undefined);
74
+ await new Promise((resolve, reject) => {
75
+ server.once('error', reject);
76
+ server.listen(paths.socketPath, () => resolve());
77
+ });
78
+ const status = {
79
+ pid: process.pid,
80
+ repoRoot: path.resolve(repoRoot),
81
+ socketPath: paths.socketPath,
82
+ startedAt: new Date().toISOString(),
83
+ };
84
+ await writeFile(paths.statusPath, JSON.stringify(status), { encoding: 'utf8', mode: 0o600 });
85
+ await writeFile(paths.pidPath, String(process.pid), { encoding: 'utf8', mode: 0o600 });
86
+ process.on('SIGTERM', () => {
87
+ void shutdown('sigterm');
88
+ });
89
+ process.on('SIGINT', () => {
90
+ void shutdown('sigint');
91
+ });
92
+ scheduleIdleShutdown();
93
+ process.stdout.write(`judge broker listening on ${paths.socketPath} for ${repoRoot}\n`);
94
+ }
95
+ function safeMessageId(line) {
96
+ try {
97
+ const parsed = JSON.parse(line);
98
+ return parsed.id ?? 'unknown';
99
+ }
100
+ catch {
101
+ return 'unknown';
102
+ }
103
+ }
104
+ async function handleLine(line, socket, broker, defaultEvalTimeoutMs) {
105
+ const message = JSON.parse(line);
106
+ if (message.type === 'stop') {
107
+ socket.write(`${JSON.stringify({ type: 'stopped', id: message.id })}\n`);
108
+ broker.stopAll('manual_stop');
109
+ return;
110
+ }
111
+ if (message.type === 'invalidate' && typeof message.sessionKey === 'string') {
112
+ broker.invalidateSession(message.sessionKey, message.reason ?? 'parse_failure');
113
+ socket.write(`${JSON.stringify({ type: 'invalidated', id: message.id })}\n`);
114
+ return;
115
+ }
116
+ if (message.type !== 'evaluate' || !message.request) {
117
+ socket.write(`${JSON.stringify({ type: 'error', id: message.id, error: 'unsupported message' })}\n`);
118
+ return;
119
+ }
120
+ const result = await broker.evaluate(message.request, message.timeoutMs ?? defaultEvalTimeoutMs);
121
+ socket.write(`${JSON.stringify({ type: 'result', id: message.id, result })}\n`);
122
+ }
123
+ await main();
@@ -24,6 +24,7 @@ export interface SandboxStatusReport {
24
24
  export declare function sandboxStatus(options?: SandboxServiceOptions): Promise<SandboxStatusReport>;
25
25
  export declare function createCapabilityApprovalStore(repoRoot: string, config: Awaited<ReturnType<typeof loadConfigFile>>): {
26
26
  allowlistPath: string;
27
+ trustedRootsPath: string;
27
28
  loadPending(): Promise<{
28
29
  filePath: string;
29
30
  state: import("../types.js").ApprovalStateFile;
@@ -2,6 +2,7 @@ import path from 'node:path';
2
2
  import { loadConfigFile, repoLocalStateDirFor } from '../config-io.js';
3
3
  import { fsScopeAllowlistPath, loadFsScopeAllowlist } from '../core/capability/allowlist.js';
4
4
  import { evaluateL1FullStatus, isCapabilityBrokerDemotionActive, } from '../core/capability/broker.js';
5
+ import { trustedWorkspaceRootsPath } from '../core/capability/trusted-workspace-roots.js';
5
6
  import { configuredControlPlaneDir } from '../core/config.js';
6
7
  import { verifyControlPlaneIsolation } from '../core/control-plane-isolation.js';
7
8
  import { egressStatus } from './egress-service.js';
@@ -48,6 +49,7 @@ export function createCapabilityApprovalStore(repoRoot, config) {
48
49
  const repoLocalDir = repoLocalStateDirFor(repoRoot, config);
49
50
  return {
50
51
  allowlistPath: fsScopeAllowlistPath(config, repoLocalDir),
52
+ trustedRootsPath: trustedWorkspaceRootsPath(config, repoLocalDir),
51
53
  async loadPending() {
52
54
  const { loadApprovalState, pendingApprovalsPath } = await import('../config-io.js');
53
55
  const filePath = pendingApprovalsPath(repoRoot, config);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const PACKAGE_VERSION = "0.3.0";
1
+ export declare const PACKAGE_VERSION = "0.4.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.3.0';
2
+ export const PACKAGE_VERSION = '0.4.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guilz-dev/belay",
3
- "version": "0.3.0",
3
+ "version": "0.4.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",
@@ -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
+ ```