@guilz-dev/belay 0.4.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.
Files changed (158) hide show
  1. package/README.md +7 -1
  2. package/dist/adapters/layouts/scope.js +3 -2
  3. package/dist/adapters/shared/gate-runtime.d.ts +1 -2
  4. package/dist/adapters/shared/gate-runtime.js +226 -28
  5. package/dist/bundle/claude-runtime.mjs +6967 -4078
  6. package/dist/bundle/codex-runtime.mjs +5951 -3064
  7. package/dist/bundle/cursor-runtime.mjs +5951 -3064
  8. package/dist/cli.js +43 -1
  9. package/dist/commands/classify-for-report.js +1 -1
  10. package/dist/commands/config.d.ts +8 -0
  11. package/dist/commands/config.js +134 -11
  12. package/dist/commands/doctor.js +36 -1
  13. package/dist/commands/explain.js +11 -1
  14. package/dist/commands/judge.js +3 -2
  15. package/dist/commands/session.d.ts +20 -0
  16. package/dist/commands/session.js +51 -0
  17. package/dist/config-io.js +49 -12
  18. package/dist/conformance/guarantee-posture.d.ts +21 -0
  19. package/dist/conformance/guarantee-posture.js +46 -0
  20. package/dist/conformance/guarantee-table.d.ts +1 -0
  21. package/dist/conformance/guarantee-table.js +14 -9
  22. package/dist/core/approval-service.js +6 -2
  23. package/dist/core/approval.d.ts +2 -0
  24. package/dist/core/approval.js +14 -3
  25. package/dist/core/capability/approval-state-mutation.d.ts +13 -0
  26. package/dist/core/capability/approval-state-mutation.js +92 -0
  27. package/dist/core/capability/approval-v3.d.ts +15 -0
  28. package/dist/core/capability/approval-v3.js +74 -0
  29. package/dist/core/capability/attestation.d.ts +13 -0
  30. package/dist/core/capability/attestation.js +55 -0
  31. package/dist/core/capability/boundary-attestation-sign.d.ts +21 -0
  32. package/dist/core/capability/boundary-attestation-sign.js +84 -0
  33. package/dist/core/capability/boundary-driver-container.d.ts +7 -0
  34. package/dist/core/capability/boundary-driver-container.js +122 -0
  35. package/dist/core/capability/boundary-driver.d.ts +29 -0
  36. package/dist/core/capability/boundary-driver.js +43 -0
  37. package/dist/core/capability/boundary-egress.d.ts +21 -0
  38. package/dist/core/capability/boundary-egress.js +109 -0
  39. package/dist/core/capability/boundary-grant-materialize.d.ts +13 -0
  40. package/dist/core/capability/boundary-grant-materialize.js +78 -0
  41. package/dist/core/capability/boundary-profile.d.ts +15 -0
  42. package/dist/core/capability/boundary-profile.js +25 -0
  43. package/dist/core/capability/boundary-run.d.ts +23 -0
  44. package/dist/core/capability/boundary-run.js +23 -0
  45. package/dist/core/capability/boundary-session.d.ts +57 -0
  46. package/dist/core/capability/boundary-session.js +129 -0
  47. package/dist/core/capability/capability-request-hash.d.ts +2 -0
  48. package/dist/core/capability/capability-request-hash.js +8 -0
  49. package/dist/core/capability/gate-latency-ratchet.d.ts +7 -0
  50. package/dist/core/capability/gate-latency-ratchet.js +18 -0
  51. package/dist/core/capability/gate-policy-shadow.d.ts +25 -0
  52. package/dist/core/capability/gate-policy-shadow.js +108 -0
  53. package/dist/core/capability/gate-shadow-audit.d.ts +1 -0
  54. package/dist/core/capability/gate-shadow-audit.js +1 -0
  55. package/dist/core/capability/gate-shadow-ratchet.d.ts +14 -0
  56. package/dist/core/capability/gate-shadow-ratchet.js +81 -0
  57. package/dist/core/capability/grant-lease.d.ts +9 -0
  58. package/dist/core/capability/grant-lease.js +46 -0
  59. package/dist/core/capability/grant-loader.d.ts +12 -0
  60. package/dist/core/capability/grant-loader.js +23 -0
  61. package/dist/core/capability/grant-match.d.ts +6 -0
  62. package/dist/core/capability/grant-match.js +93 -0
  63. package/dist/core/capability/grant.d.ts +16 -0
  64. package/dist/core/capability/grant.js +16 -0
  65. package/dist/core/capability/index.d.ts +8 -0
  66. package/dist/core/capability/index.js +7 -0
  67. package/dist/core/capability/limits.d.ts +7 -0
  68. package/dist/core/capability/limits.js +55 -0
  69. package/dist/core/capability/policy-bridge.d.ts +18 -0
  70. package/dist/core/capability/policy-bridge.js +51 -0
  71. package/dist/core/capability/policy-engine.d.ts +70 -0
  72. package/dist/core/capability/policy-engine.js +476 -0
  73. package/dist/core/capability/policy-types.d.ts +20 -0
  74. package/dist/core/capability/policy-types.js +1 -0
  75. package/dist/core/capability/request.d.ts +45 -0
  76. package/dist/core/capability/request.js +1 -0
  77. package/dist/core/capability/resolver.d.ts +4 -0
  78. package/dist/core/capability/resolver.js +4 -0
  79. package/dist/core/classify-subagent.d.ts +2 -1
  80. package/dist/core/classify-subagent.js +38 -2
  81. package/dist/core/classify-tool.js +126 -62
  82. package/dist/core/config.d.ts +14 -3
  83. package/dist/core/config.js +56 -3
  84. package/dist/core/gate-contract.d.ts +3 -0
  85. package/dist/core/gate-contract.js +3 -0
  86. package/dist/core/gate-engine.js +6 -1
  87. package/dist/core/judge-doctor.js +39 -14
  88. package/dist/core/judge-fallback-hints.d.ts +13 -0
  89. package/dist/core/judge-fallback-hints.js +108 -0
  90. package/dist/core/path-utils.d.ts +1 -0
  91. package/dist/core/path-utils.js +5 -2
  92. package/dist/core/recovery/capability.d.ts +3 -0
  93. package/dist/core/recovery/capability.js +22 -0
  94. package/dist/core/recovery/fail-closed.d.ts +6 -0
  95. package/dist/core/recovery/fail-closed.js +25 -0
  96. package/dist/core/recovery/index.d.ts +3 -0
  97. package/dist/core/recovery/index.js +2 -0
  98. package/dist/core/recovery/types.d.ts +20 -0
  99. package/dist/core/recovery/types.js +1 -0
  100. package/dist/core/standing-allow.js +3 -0
  101. package/dist/core/transactional/diff-evaluator.js +4 -1
  102. package/dist/core/transactional/eligibility.js +4 -0
  103. package/dist/core/transactional/git-worktree.d.ts +10 -3
  104. package/dist/core/transactional/git-worktree.js +81 -11
  105. package/dist/core/transactional/reasons.d.ts +1 -0
  106. package/dist/core/transactional/reasons.js +4 -0
  107. package/dist/core/transactional/runner.js +22 -6
  108. package/dist/core/transactional/types.d.ts +4 -0
  109. package/dist/core/types.d.ts +21 -3
  110. package/dist/core/verdict/adapter.d.ts +3 -4
  111. package/dist/core/verdict/adapter.js +16 -12
  112. package/dist/core/verdict/cursor-acp-client.d.ts +67 -0
  113. package/dist/core/verdict/cursor-acp-client.js +345 -0
  114. package/dist/core/verdict/egress-classify.d.ts +2 -2
  115. package/dist/core/verdict/egress-classify.js +2 -2
  116. package/dist/core/verdict/git-classifier.d.ts +26 -0
  117. package/dist/core/verdict/git-classifier.js +533 -0
  118. package/dist/core/verdict/judge-audit.js +1 -0
  119. package/dist/core/verdict/judge-baseline.d.ts +1 -1
  120. package/dist/core/verdict/judge-baseline.js +1 -0
  121. package/dist/core/verdict/judge-broker-service.js +12 -0
  122. package/dist/core/verdict/judge-catalog.js +1 -0
  123. package/dist/core/verdict/judge-cli-fingerprint.d.ts +1 -0
  124. package/dist/core/verdict/judge-cli-fingerprint.js +6 -0
  125. package/dist/core/verdict/judge-cli.js +5 -3
  126. package/dist/core/verdict/judge-provider-matrix.js +1 -1
  127. package/dist/core/verdict/judge-runtime-config.d.ts +2 -0
  128. package/dist/core/verdict/judge-runtime-config.js +10 -6
  129. package/dist/core/verdict/judge-session-broker.d.ts +1 -1
  130. package/dist/core/verdict/judge-session-broker.js +10 -5
  131. package/dist/core/verdict/judge-transport.d.ts +5 -1
  132. package/dist/core/verdict/judge-transport.js +24 -14
  133. package/dist/core/verdict/judge.d.ts +2 -15
  134. package/dist/core/verdict/judge.js +5 -68
  135. package/dist/core/verdict/launcher-resolve.d.ts +2 -0
  136. package/dist/core/verdict/launcher-resolve.js +16 -0
  137. package/dist/core/verdict/prescan.d.ts +15 -0
  138. package/dist/core/verdict/prescan.js +68 -0
  139. package/dist/core/verdict/shell-policy.d.ts +25 -0
  140. package/dist/core/verdict/shell-policy.js +40 -0
  141. package/dist/core/verdict/shell-semantics.d.ts +24 -0
  142. package/dist/core/verdict/shell-semantics.js +225 -0
  143. package/dist/core/verdict/types.d.ts +13 -1
  144. package/dist/core/verdict/verdict.js +296 -178
  145. package/dist/corpus/evaluate.js +3 -7
  146. package/dist/corpus/gate-latency-budget.d.ts +39 -0
  147. package/dist/corpus/gate-latency-budget.js +33 -0
  148. package/dist/corpus/mutators.d.ts +1 -1
  149. package/dist/corpus/mutators.js +11 -0
  150. package/dist/corpus/runtime-match.js +1 -3
  151. package/dist/corpus/standing-allow-catalog.generated.js +29 -9
  152. package/dist/judge-broker-daemon.js +15 -1
  153. package/dist/services/sandbox-service.d.ts +4 -0
  154. package/dist/services/sandbox-service.js +28 -2
  155. package/dist/types.d.ts +2 -2
  156. package/dist/version.d.ts +1 -1
  157. package/dist/version.js +1 -1
  158. package/package.json +1 -1
@@ -0,0 +1,29 @@
1
+ import { type ShellRunResult } from '../transactional/git-worktree.js';
2
+ import type { BoundaryAttestation, BoundaryDriverId } from './attestation.js';
3
+ import type { BoundaryPrepareContext, BoundaryRunOptions } from './boundary-run.js';
4
+ import type { CapabilityGrantV1 } from './grant.js';
5
+ import type { CapabilityRequestV1 } from './request.js';
6
+ export type { ShellRunResult as BoundaryRunResult };
7
+ export interface BoundaryMaterializeContext {
8
+ attestation: BoundaryAttestation;
9
+ mountRoot: string;
10
+ egressProxyActive: boolean;
11
+ existingGrants?: CapabilityGrantV1[];
12
+ sensitivePaths?: string[];
13
+ }
14
+ export type { BoundaryRunOptions } from './boundary-run.js';
15
+ export { boundaryMountReadOnlyFromPrediction } from './boundary-run.js';
16
+ export interface BoundaryDriver {
17
+ id: BoundaryDriverId;
18
+ probe(): Promise<BoundaryAttestation>;
19
+ prepare?(context: BoundaryPrepareContext): Promise<void>;
20
+ run(command: string, cwd: string, timeoutMs: number, options?: BoundaryRunOptions): Promise<ShellRunResult>;
21
+ materializeGrant(request: CapabilityRequestV1, context: BoundaryMaterializeContext): CapabilityGrantV1 | null;
22
+ }
23
+ export interface BoundaryDriverOptions {
24
+ egressProxyEnv?: Record<string, string>;
25
+ image?: string;
26
+ repoRoot?: string;
27
+ }
28
+ export declare function createHostIntegrationDriver(): BoundaryDriver;
29
+ export declare function getDefaultBoundaryDriver(driverId?: BoundaryDriverId, options?: BoundaryDriverOptions): BoundaryDriver;
@@ -0,0 +1,43 @@
1
+ import { runShellCommand } from '../transactional/git-worktree.js';
2
+ import { createContainerBoundaryDriver } from './boundary-driver-container.js';
3
+ export { boundaryMountReadOnlyFromPrediction } from './boundary-run.js';
4
+ const ATTESTATION_TTL_MS = 15 * 60_000;
5
+ function hostIntegrationAttestation(driver) {
6
+ const probedAt = new Date().toISOString();
7
+ return {
8
+ version: 1,
9
+ driver,
10
+ probedAt,
11
+ expiresAt: new Date(Date.now() + ATTESTATION_TTL_MS).toISOString(),
12
+ deniesUngrantedEffects: false,
13
+ materializesGrants: false,
14
+ probeSignals: ['host-integration', 'l3-policy-only'],
15
+ };
16
+ }
17
+ export function createHostIntegrationDriver() {
18
+ return {
19
+ id: 'host-integration',
20
+ async probe() {
21
+ return hostIntegrationAttestation('host-integration');
22
+ },
23
+ async prepare() {
24
+ // L3 host path has no runtime preparation.
25
+ },
26
+ run(command, cwd, timeoutMs) {
27
+ return runShellCommand(command, cwd, timeoutMs);
28
+ },
29
+ materializeGrant() {
30
+ return null;
31
+ },
32
+ };
33
+ }
34
+ export function getDefaultBoundaryDriver(driverId = 'host-integration', options = {}) {
35
+ if (driverId === 'container') {
36
+ return createContainerBoundaryDriver({
37
+ image: options.image,
38
+ egressProxyEnv: options.egressProxyEnv,
39
+ repoRoot: options.repoRoot,
40
+ });
41
+ }
42
+ return createHostIntegrationDriver();
43
+ }
@@ -0,0 +1,21 @@
1
+ import type { BelayConfigV4 } from '../config.js';
2
+ import type { BoundaryDriverId } from './attestation.js';
3
+ export declare function isEgressProxyActive(params: {
4
+ config: BelayConfigV4;
5
+ running: boolean;
6
+ foreignProxy?: boolean;
7
+ repoRootMismatch?: boolean;
8
+ }): boolean;
9
+ export declare function egressProxyEnvFromConfig(config: BelayConfigV4, proxyActive: boolean): Record<string, string>;
10
+ /** Container workloads cannot reach 127.0.0.1 on the host; route via host.docker.internal. */
11
+ export declare function egressProxyEnvForContainer(config: BelayConfigV4, proxyActive: boolean): Record<string, string>;
12
+ export declare function resolveBoundaryEgressProxyEnv(params: {
13
+ driverId: BoundaryDriverId;
14
+ config: BelayConfigV4;
15
+ proxyActive: boolean;
16
+ }): Record<string, string>;
17
+ export declare function belayContainerNetworkName(repoRoot: string): string;
18
+ export declare function isBelayContainerNetworkReady(repoRoot: string): Promise<boolean>;
19
+ export declare function ensureBelayContainerNetwork(repoRoot: string): Promise<string>;
20
+ export declare function dockerNetworkArgs(proxyActive: boolean, repoRoot?: string): string[];
21
+ export declare function dockerEnvArgs(proxyEnv: Record<string, string>): string[];
@@ -0,0 +1,109 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { recommendedProxyEnv } from '../egress/env.js';
4
+ import { canonicalPath } from '../path-utils.js';
5
+ export function isEgressProxyActive(params) {
6
+ if (!params.config.egress.enabled) {
7
+ return false;
8
+ }
9
+ return params.running && !params.foreignProxy && !params.repoRootMismatch;
10
+ }
11
+ function proxyHostForContainer(listenHost) {
12
+ if (listenHost === '127.0.0.1' || listenHost === 'localhost' || listenHost === '::1') {
13
+ return 'host.docker.internal';
14
+ }
15
+ return listenHost;
16
+ }
17
+ export function egressProxyEnvFromConfig(config, proxyActive) {
18
+ if (!proxyActive || !config.egress.enabled) {
19
+ return {};
20
+ }
21
+ return recommendedProxyEnv(config.egress);
22
+ }
23
+ /** Container workloads cannot reach 127.0.0.1 on the host; route via host.docker.internal. */
24
+ export function egressProxyEnvForContainer(config, proxyActive) {
25
+ if (!proxyActive || !config.egress.enabled) {
26
+ return {};
27
+ }
28
+ const host = proxyHostForContainer(config.egress.listenHost);
29
+ const proxyUrl = `http://${host}:${config.egress.listenPort}`;
30
+ return {
31
+ HTTP_PROXY: proxyUrl,
32
+ HTTPS_PROXY: proxyUrl,
33
+ http_proxy: proxyUrl,
34
+ https_proxy: proxyUrl,
35
+ NO_PROXY: '127.0.0.1,localhost',
36
+ no_proxy: '127.0.0.1,localhost',
37
+ };
38
+ }
39
+ export function resolveBoundaryEgressProxyEnv(params) {
40
+ if (params.driverId === 'container') {
41
+ return egressProxyEnvForContainer(params.config, params.proxyActive);
42
+ }
43
+ return egressProxyEnvFromConfig(params.config, params.proxyActive);
44
+ }
45
+ export function belayContainerNetworkName(repoRoot) {
46
+ const hash = createHash('sha256').update(canonicalPath(repoRoot)).digest('hex').slice(0, 12);
47
+ return `belay-int-${hash}`;
48
+ }
49
+ function dockerNetworkInspect(name) {
50
+ return new Promise((resolve) => {
51
+ const child = spawn('docker', ['network', 'inspect', name], { stdio: 'ignore' });
52
+ child.on('error', () => resolve(false));
53
+ child.on('close', (code) => resolve(code === 0));
54
+ });
55
+ }
56
+ export async function isBelayContainerNetworkReady(repoRoot) {
57
+ return dockerNetworkInspect(belayContainerNetworkName(repoRoot));
58
+ }
59
+ function dockerNetworkCreateInternal(name) {
60
+ return new Promise((resolve, reject) => {
61
+ const child = spawn('docker', ['network', 'create', '--internal', '--label', 'belay=1', name], {
62
+ stdio: 'ignore',
63
+ });
64
+ child.on('error', reject);
65
+ child.on('close', (code) => {
66
+ if (code === 0) {
67
+ resolve();
68
+ return;
69
+ }
70
+ reject(new Error(`Failed to create internal docker network ${name}`));
71
+ });
72
+ });
73
+ }
74
+ export async function ensureBelayContainerNetwork(repoRoot) {
75
+ const name = belayContainerNetworkName(repoRoot);
76
+ if (await dockerNetworkInspect(name)) {
77
+ return name;
78
+ }
79
+ try {
80
+ await dockerNetworkCreateInternal(name);
81
+ }
82
+ catch (error) {
83
+ if (await dockerNetworkInspect(name)) {
84
+ return name;
85
+ }
86
+ throw error;
87
+ }
88
+ return name;
89
+ }
90
+ export function dockerNetworkArgs(proxyActive, repoRoot) {
91
+ if (!proxyActive) {
92
+ return ['--network', 'none'];
93
+ }
94
+ if (!repoRoot) {
95
+ return ['--network', 'none'];
96
+ }
97
+ return [
98
+ '--add-host=host.docker.internal:host-gateway',
99
+ '--network',
100
+ belayContainerNetworkName(repoRoot),
101
+ ];
102
+ }
103
+ export function dockerEnvArgs(proxyEnv) {
104
+ const args = [];
105
+ for (const [key, value] of Object.entries(proxyEnv)) {
106
+ args.push('-e', `${key}=${value}`);
107
+ }
108
+ return args;
109
+ }
@@ -0,0 +1,13 @@
1
+ import type { BoundaryAttestation } from './attestation.js';
2
+ import { type CapabilityGrantV1 } from './grant.js';
3
+ import type { CapabilityRequestV1 } from './request.js';
4
+ export declare const BOUNDARY_GRANT_ISSUER_CONTAINER: "boundary:container";
5
+ export declare function isPathWithinBoundaryMount(request: CapabilityRequestV1): boolean;
6
+ export interface MaterializeBoundaryGrantParams {
7
+ attestation: BoundaryAttestation;
8
+ mountRoot: string;
9
+ egressProxyActive: boolean;
10
+ existingGrants?: CapabilityGrantV1[];
11
+ sensitivePaths?: string[];
12
+ }
13
+ export declare function materializeContainerBoundaryGrant(request: CapabilityRequestV1, params: MaterializeBoundaryGrantParams): CapabilityGrantV1 | null;
@@ -0,0 +1,78 @@
1
+ import path from 'node:path';
2
+ import { matchesSensitivePath } from '../glob.js';
3
+ import { canonicalPath, pathWithinRoot } from '../path-utils.js';
4
+ import { newGrantId } from './approval-v3.js';
5
+ import { isAttestationFresh } from './attestation.js';
6
+ import { CAPABILITY_GRANT_VERSION } from './grant.js';
7
+ import { grantMatchesRequest } from './grant-match.js';
8
+ const BOUNDARY_GRANT_TTL_MS = 15 * 60_000;
9
+ export const BOUNDARY_GRANT_ISSUER_CONTAINER = 'boundary:container';
10
+ function resolveCapabilityPath(targetPath, cwd) {
11
+ const joined = path.isAbsolute(targetPath) ? targetPath : path.join(cwd, targetPath);
12
+ return canonicalPath(joined);
13
+ }
14
+ export function isPathWithinBoundaryMount(request) {
15
+ if (request.resource.kind !== 'path') {
16
+ return true;
17
+ }
18
+ const mountRoot = canonicalPath(request.context.cwd);
19
+ const resolved = resolveCapabilityPath(request.resource.path, request.context.cwd);
20
+ return pathWithinRoot(mountRoot, resolved);
21
+ }
22
+ function mintBoundaryGrant(request, attestation, issuer) {
23
+ const now = Date.now();
24
+ const attestationExpiry = Date.parse(attestation.expiresAt);
25
+ const grantExpiry = now + BOUNDARY_GRANT_TTL_MS;
26
+ const expiresAt = new Date(Number.isFinite(attestationExpiry) ? Math.min(attestationExpiry, grantExpiry) : grantExpiry).toISOString();
27
+ return {
28
+ version: CAPABILITY_GRANT_VERSION,
29
+ grantId: newGrantId(),
30
+ principal: request.principal,
31
+ action: request.action,
32
+ resource: request.resource,
33
+ inputFingerprint: request.context.inputFingerprint,
34
+ issuedAt: new Date(now).toISOString(),
35
+ expiresAt,
36
+ maxUses: 1,
37
+ usesRemaining: 1,
38
+ issuer,
39
+ };
40
+ }
41
+ export function materializeContainerBoundaryGrant(request, params) {
42
+ if (!isAttestationFresh(params.attestation)) {
43
+ return null;
44
+ }
45
+ if (!params.attestation.materializesGrants || params.attestation.driver !== 'container') {
46
+ return null;
47
+ }
48
+ if (request.evidence.level !== 'certain') {
49
+ return null;
50
+ }
51
+ const repoRoot = canonicalPath(request.principal.repoRoot);
52
+ const mountRoot = canonicalPath(params.mountRoot);
53
+ if (request.action === 'fs.read' || request.action === 'fs.write') {
54
+ if (request.resource.kind !== 'path') {
55
+ return null;
56
+ }
57
+ const resolved = resolveCapabilityPath(request.resource.path, request.context.cwd);
58
+ if (!pathWithinRoot(repoRoot, resolved) || !pathWithinRoot(mountRoot, resolved)) {
59
+ return null;
60
+ }
61
+ const relative = resolved.slice(repoRoot.length).replace(/^[/\\]/, '');
62
+ if (params.sensitivePaths?.length && matchesSensitivePath(relative, params.sensitivePaths)) {
63
+ return null;
64
+ }
65
+ return mintBoundaryGrant(request, params.attestation, BOUNDARY_GRANT_ISSUER_CONTAINER);
66
+ }
67
+ if (request.action === 'network.connect' && request.resource.kind === 'network') {
68
+ if (!params.egressProxyActive) {
69
+ return null;
70
+ }
71
+ const approved = params.existingGrants?.some((grant) => grant.issuer !== BOUNDARY_GRANT_ISSUER_CONTAINER && grantMatchesRequest(grant, request));
72
+ if (!approved) {
73
+ return null;
74
+ }
75
+ return mintBoundaryGrant(request, params.attestation, BOUNDARY_GRANT_ISSUER_CONTAINER);
76
+ }
77
+ return null;
78
+ }
@@ -0,0 +1,15 @@
1
+ import type { BelayConfigV4 } from '../config.js';
2
+ import type { BoundaryAttestation } from './attestation.js';
3
+ /** Policy-only boundary (L3/L4). No attested L1 enforcement. */
4
+ export declare const BOUNDARY_PROFILE_L3_POLICY: "l3-policy-engine";
5
+ /** Editor-facing label when only prediction + approval layers are active. */
6
+ export declare const BOUNDARY_PROFILE_L3_L4_ONLY: "l3-l4-only";
7
+ /** Attested container / sandbox boundary with grant materialization. */
8
+ export declare const BOUNDARY_PROFILE_L1_ATTESTED: "l1-attested-boundary";
9
+ export type BoundaryProfileId = typeof BOUNDARY_PROFILE_L3_POLICY | typeof BOUNDARY_PROFILE_L3_L4_ONLY | typeof BOUNDARY_PROFILE_L1_ATTESTED;
10
+ export declare function isAttestedBoundary(attestation: BoundaryAttestation | null | undefined): boolean;
11
+ export declare function resolveBoundaryProfile(params: {
12
+ config: BelayConfigV4;
13
+ attestation?: BoundaryAttestation | null;
14
+ }): BoundaryProfileId;
15
+ export declare function boundaryVerifiedAllowEnabled(attestation: BoundaryAttestation | null | undefined): boolean;
@@ -0,0 +1,25 @@
1
+ import { isAttestationFresh } from './attestation.js';
2
+ /** Policy-only boundary (L3/L4). No attested L1 enforcement. */
3
+ export const BOUNDARY_PROFILE_L3_POLICY = 'l3-policy-engine';
4
+ /** Editor-facing label when only prediction + approval layers are active. */
5
+ export const BOUNDARY_PROFILE_L3_L4_ONLY = 'l3-l4-only';
6
+ /** Attested container / sandbox boundary with grant materialization. */
7
+ export const BOUNDARY_PROFILE_L1_ATTESTED = 'l1-attested-boundary';
8
+ export function isAttestedBoundary(attestation) {
9
+ if (!attestation || !isAttestationFresh(attestation)) {
10
+ return false;
11
+ }
12
+ return attestation.deniesUngrantedEffects && attestation.materializesGrants;
13
+ }
14
+ export function resolveBoundaryProfile(params) {
15
+ if (!isAttestedBoundary(params.attestation)) {
16
+ return BOUNDARY_PROFILE_L3_L4_ONLY;
17
+ }
18
+ if (params.attestation?.driver === 'container') {
19
+ return BOUNDARY_PROFILE_L1_ATTESTED;
20
+ }
21
+ return BOUNDARY_PROFILE_L3_POLICY;
22
+ }
23
+ export function boundaryVerifiedAllowEnabled(attestation) {
24
+ return isAttestedBoundary(attestation);
25
+ }
@@ -0,0 +1,23 @@
1
+ import type { ShellRunResult } from '../transactional/git-worktree.js';
2
+ import type { ClassifyResult } from '../types.js';
3
+ export interface BoundaryRunOptions {
4
+ /** When true, container driver mounts the working directory read-only. */
5
+ mountReadOnly?: boolean;
6
+ }
7
+ export interface BoundaryPrepareContext {
8
+ repoRoot?: string;
9
+ egressProxyActive: boolean;
10
+ proxyEnv: Record<string, string>;
11
+ }
12
+ export interface BoundaryRunnable {
13
+ prepare?(context: BoundaryPrepareContext): Promise<void>;
14
+ run(command: string, cwd: string, timeoutMs: number, options?: BoundaryRunOptions): Promise<ShellRunResult>;
15
+ }
16
+ export declare function boundaryMountReadOnlyFromPrediction(predicted: ClassifyResult): boolean;
17
+ export declare function runWithBoundaryRunnable(target: BoundaryRunnable, params: {
18
+ prepareContext: BoundaryPrepareContext;
19
+ command: string;
20
+ cwd: string;
21
+ timeoutMs: number;
22
+ runOptions?: BoundaryRunOptions;
23
+ }): Promise<ShellRunResult>;
@@ -0,0 +1,23 @@
1
+ export function boundaryMountReadOnlyFromPrediction(predicted) {
2
+ const effect = predicted.axes?.effect;
3
+ if (effect === 'local_mutation' || effect === 'remote_mutation') {
4
+ return false;
5
+ }
6
+ const action = predicted.capabilityRequests?.[0]?.action;
7
+ if (action === 'fs.write' || action === 'git.ref.write' || action === 'control_plane.write') {
8
+ return false;
9
+ }
10
+ if (action === 'fs.read') {
11
+ return true;
12
+ }
13
+ if (action === 'network.connect' || action === 'process.exec' || action === 'indeterminate') {
14
+ return true;
15
+ }
16
+ return true;
17
+ }
18
+ export async function runWithBoundaryRunnable(target, params) {
19
+ if (target.prepare) {
20
+ await target.prepare(params.prepareContext);
21
+ }
22
+ return target.run(params.command, params.cwd, params.timeoutMs, params.runOptions);
23
+ }
@@ -0,0 +1,57 @@
1
+ import type { BelayConfigV4 } from '../config.js';
2
+ import type { BoundaryAttestation, BoundaryDriverId } from './attestation.js';
3
+ import { type BoundaryDriver } from './boundary-driver.js';
4
+ import { dockerNetworkArgs } from './boundary-egress.js';
5
+ import { type BoundaryPrepareContext, type BoundaryRunOptions } from './boundary-run.js';
6
+ export interface BoundarySessionOptions {
7
+ egressProxyRunning?: boolean;
8
+ }
9
+ export interface ResolvedBoundaryDriverContext {
10
+ driver: BoundaryDriver;
11
+ driverId: BoundaryDriverId;
12
+ proxyActive: boolean;
13
+ proxyEnv: Record<string, string>;
14
+ prepareContext: BoundaryPrepareContext;
15
+ }
16
+ export declare function boundaryAttestationPath(repoRoot: string, config: BelayConfigV4): string;
17
+ export declare function loadBoundaryAttestation(filePath: string, expectedRepoRoot?: string, controlPlaneDir?: string): Promise<BoundaryAttestation | null>;
18
+ export declare function saveBoundaryAttestation(filePath: string, attestation: BoundaryAttestation, repoRoot: string, controlPlaneDir: string): Promise<void>;
19
+ export declare function hostIntegrationBoundaryContext(repoRoot: string): ResolvedBoundaryDriverContext;
20
+ export declare function resolveBoundaryDriverContext(params: {
21
+ repoRoot: string;
22
+ config: BelayConfigV4;
23
+ driverId?: BoundaryDriverId;
24
+ egressProxyRunning?: boolean;
25
+ }): Promise<ResolvedBoundaryDriverContext>;
26
+ export declare function runWithBoundaryDriver(params: {
27
+ context: ResolvedBoundaryDriverContext;
28
+ command: string;
29
+ cwd: string;
30
+ timeoutMs: number;
31
+ runOptions?: BoundaryRunOptions;
32
+ }): Promise<Awaited<ReturnType<BoundaryDriver['run']>>>;
33
+ export declare function startBoundarySession(params: {
34
+ repoRoot: string;
35
+ config: BelayConfigV4;
36
+ driverId?: BoundaryDriverId;
37
+ egressProxyRunning?: boolean;
38
+ }): Promise<{
39
+ attestation: BoundaryAttestation;
40
+ attestationPath: string;
41
+ }>;
42
+ export declare function boundarySessionStatus(params: {
43
+ repoRoot: string;
44
+ config: BelayConfigV4;
45
+ }): Promise<{
46
+ attestationPath: string;
47
+ attestation: BoundaryAttestation | null;
48
+ fresh: boolean;
49
+ }>;
50
+ export declare function runBoundaryAgentCommand(params: {
51
+ repoRoot: string;
52
+ config: BelayConfigV4;
53
+ command: string;
54
+ cwd?: string;
55
+ timeoutMs?: number;
56
+ }): Promise<Awaited<ReturnType<BoundaryDriver['run']>>>;
57
+ export { dockerNetworkArgs };
@@ -0,0 +1,129 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { egressStatus } from '../../services/egress-service.js';
4
+ import { configuredControlPlaneDir } from '../config.js';
5
+ import { isAttestationFresh } from './attestation.js';
6
+ import { readSignedAttestationFile, signBoundaryAttestation, verifySignedBoundaryAttestation, } from './boundary-attestation-sign.js';
7
+ import { getDefaultBoundaryDriver } from './boundary-driver.js';
8
+ import { dockerNetworkArgs, isEgressProxyActive, resolveBoundaryEgressProxyEnv, } from './boundary-egress.js';
9
+ import { runWithBoundaryRunnable, } from './boundary-run.js';
10
+ export function boundaryAttestationPath(repoRoot, config) {
11
+ const rel = config.capability?.attestationRelPath ?? '.belay/attestation.json';
12
+ return path.isAbsolute(rel) ? rel : path.join(repoRoot, rel);
13
+ }
14
+ export async function loadBoundaryAttestation(filePath, expectedRepoRoot, controlPlaneDir) {
15
+ try {
16
+ const raw = await readSignedAttestationFile(filePath);
17
+ if (expectedRepoRoot && controlPlaneDir) {
18
+ return verifySignedBoundaryAttestation({
19
+ file: raw,
20
+ expectedRepoRoot,
21
+ controlPlaneDir,
22
+ });
23
+ }
24
+ return null;
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ export async function saveBoundaryAttestation(filePath, attestation, repoRoot, controlPlaneDir) {
31
+ const signed = await signBoundaryAttestation({
32
+ repoRoot,
33
+ attestation,
34
+ controlPlaneDir,
35
+ });
36
+ await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
37
+ await writeFile(filePath, `${JSON.stringify(signed, null, 2)}\n`, {
38
+ encoding: 'utf8',
39
+ mode: 0o600,
40
+ });
41
+ }
42
+ export function hostIntegrationBoundaryContext(repoRoot) {
43
+ const driver = getDefaultBoundaryDriver('host-integration', { repoRoot });
44
+ return {
45
+ driver,
46
+ driverId: 'host-integration',
47
+ proxyActive: false,
48
+ proxyEnv: {},
49
+ prepareContext: {
50
+ repoRoot,
51
+ egressProxyActive: false,
52
+ proxyEnv: {},
53
+ },
54
+ };
55
+ }
56
+ export async function resolveBoundaryDriverContext(params) {
57
+ const driverId = params.driverId ?? params.config.capability?.boundaryDriver ?? 'host-integration';
58
+ let proxyActive = params.egressProxyRunning === true;
59
+ if (params.egressProxyRunning === undefined) {
60
+ const egress = await egressStatus({ targetDir: params.repoRoot });
61
+ proxyActive = isEgressProxyActive({
62
+ config: params.config,
63
+ running: egress.running,
64
+ foreignProxy: egress.foreignProxy,
65
+ repoRootMismatch: egress.repoRootMismatch,
66
+ });
67
+ }
68
+ const proxyEnv = resolveBoundaryEgressProxyEnv({
69
+ driverId,
70
+ config: params.config,
71
+ proxyActive,
72
+ });
73
+ const driver = getDefaultBoundaryDriver(driverId, {
74
+ egressProxyEnv: proxyEnv,
75
+ repoRoot: params.repoRoot,
76
+ });
77
+ return {
78
+ driver,
79
+ driverId,
80
+ proxyActive,
81
+ proxyEnv,
82
+ prepareContext: {
83
+ repoRoot: params.repoRoot,
84
+ egressProxyActive: proxyActive,
85
+ proxyEnv,
86
+ },
87
+ };
88
+ }
89
+ export async function runWithBoundaryDriver(params) {
90
+ return runWithBoundaryRunnable(params.context.driver, {
91
+ prepareContext: params.context.prepareContext,
92
+ command: params.command,
93
+ cwd: params.cwd,
94
+ timeoutMs: params.timeoutMs,
95
+ runOptions: params.runOptions,
96
+ });
97
+ }
98
+ export async function startBoundarySession(params) {
99
+ const resolved = await resolveBoundaryDriverContext(params);
100
+ const attestation = await resolved.driver.probe();
101
+ if (resolved.driver.prepare) {
102
+ await resolved.driver.prepare(resolved.prepareContext);
103
+ }
104
+ const attestationPath = boundaryAttestationPath(params.repoRoot, params.config);
105
+ await saveBoundaryAttestation(attestationPath, attestation, params.repoRoot, configuredControlPlaneDir(params.config));
106
+ return { attestation, attestationPath };
107
+ }
108
+ export async function boundarySessionStatus(params) {
109
+ const attestationPath = boundaryAttestationPath(params.repoRoot, params.config);
110
+ const attestation = await loadBoundaryAttestation(attestationPath, params.repoRoot, configuredControlPlaneDir(params.config));
111
+ return {
112
+ attestationPath,
113
+ attestation,
114
+ fresh: attestation ? isAttestationFresh(attestation) : false,
115
+ };
116
+ }
117
+ export async function runBoundaryAgentCommand(params) {
118
+ const context = await resolveBoundaryDriverContext({
119
+ repoRoot: params.repoRoot,
120
+ config: params.config,
121
+ });
122
+ return runWithBoundaryDriver({
123
+ context,
124
+ command: params.command,
125
+ cwd: params.cwd ?? params.repoRoot,
126
+ timeoutMs: params.timeoutMs ?? 30 * 60_000,
127
+ });
128
+ }
129
+ export { dockerNetworkArgs };
@@ -0,0 +1,2 @@
1
+ import type { CapabilityRequestV1 } from './request.js';
2
+ export declare function hashCapabilityRequests(requests: CapabilityRequestV1[]): string;
@@ -0,0 +1,8 @@
1
+ import { canonicalStringify, hashValue } from '../fingerprint.js';
2
+ export function hashCapabilityRequests(requests) {
3
+ if (requests.length === 0) {
4
+ return '';
5
+ }
6
+ const normalized = [...requests].sort((left, right) => canonicalStringify(left).localeCompare(canonicalStringify(right)));
7
+ return hashValue(`capability-requests:v1:${canonicalStringify(normalized)}`);
8
+ }
@@ -0,0 +1,7 @@
1
+ import { GATE_LATENCY_BUDGET } from '../../corpus/gate-latency-budget.js';
2
+ export type GateLatencyKind = keyof typeof GATE_LATENCY_BUDGET;
3
+ /**
4
+ * Advisory warnings when CI budgets still exceed PLAN acceptance targets.
5
+ * Surfaced via sandbox status `advisories` and doctor warnings — not `issues` or gate exit codes.
6
+ */
7
+ export declare function evaluateGateLatencyRatchetAdvisories(): string[];
@@ -0,0 +1,18 @@
1
+ import { GATE_LATENCY_BUDGET, GATE_LATENCY_PLAN_TARGET } from '../../corpus/gate-latency-budget.js';
2
+ /**
3
+ * Advisory warnings when CI budgets still exceed PLAN acceptance targets.
4
+ * Surfaced via sandbox status `advisories` and doctor warnings — not `issues` or gate exit codes.
5
+ */
6
+ export function evaluateGateLatencyRatchetAdvisories() {
7
+ const warnings = [];
8
+ for (const kind of Object.keys(GATE_LATENCY_BUDGET)) {
9
+ const budget = GATE_LATENCY_BUDGET[kind];
10
+ if (budget.p95Ms > GATE_LATENCY_PLAN_TARGET.p95Ms) {
11
+ warnings.push(`Gate ${kind} p95 CI budget ${budget.p95Ms}ms exceeds PLAN target ${GATE_LATENCY_PLAN_TARGET.p95Ms}ms — tighten via quality-loop ratchet.`);
12
+ }
13
+ if (budget.maxMs > GATE_LATENCY_PLAN_TARGET.maxMs) {
14
+ warnings.push(`Gate ${kind} max CI budget ${budget.maxMs}ms exceeds PLAN target ${GATE_LATENCY_PLAN_TARGET.maxMs}ms — tighten via quality-loop ratchet.`);
15
+ }
16
+ }
17
+ return warnings;
18
+ }
@@ -0,0 +1,25 @@
1
+ import type { BelayConfigV4 } from '../config.js';
2
+ import type { ClassifyResult } from '../types.js';
3
+ import type { JudgeProviderId } from '../verdict/judge-catalog.js';
4
+ import type { Tier1Verdict } from '../verdict/types.js';
5
+ export { recordGateApprovalAsk } from './gate-shadow-ratchet.js';
6
+ export declare function capabilityDecisionAuditFields(result: ClassifyResult): Record<string, unknown>;
7
+ export declare function policyWouldBlock(result: ClassifyResult): boolean;
8
+ export declare function judgeWouldBlock(verdict: Tier1Verdict): boolean;
9
+ export declare function policyJudgeMismatch(result: ClassifyResult, judgeVerdict: Tier1Verdict): boolean;
10
+ export declare function scheduleGateShadowAudit(params: {
11
+ repoRoot: string;
12
+ config: BelayConfigV4;
13
+ providerId: string;
14
+ result: ClassifyResult;
15
+ command?: string;
16
+ stateDir?: string;
17
+ }): Record<string, unknown>;
18
+ export declare function runGatePolicyShadowComparison(params: {
19
+ repoRoot: string;
20
+ config: BelayConfigV4;
21
+ providerId: Exclude<JudgeProviderId, 'ollama'>;
22
+ command: string;
23
+ result: ClassifyResult;
24
+ stateDir?: string;
25
+ }): Promise<void>;