@guilz-dev/belay 0.9.3 → 0.10.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 (110) hide show
  1. package/README.md +38 -3
  2. package/dist/adapters/codex/hooks.d.ts +1 -0
  3. package/dist/adapters/codex/hooks.js +3 -0
  4. package/dist/adapters/cursor/dispatcher-generation.d.ts +3 -0
  5. package/dist/adapters/cursor/dispatcher-generation.js +5 -0
  6. package/dist/adapters/cursor/hook-dispatch-entry.d.ts +10 -0
  7. package/dist/adapters/cursor/hook-dispatch-entry.js +137 -0
  8. package/dist/adapters/cursor/hook-router.d.ts +23 -0
  9. package/dist/adapters/cursor/hook-router.js +273 -0
  10. package/dist/adapters/cursor/hooks.d.ts +5 -1
  11. package/dist/adapters/cursor/hooks.js +112 -10
  12. package/dist/adapters/cursor/routing-config-trust.d.ts +1 -0
  13. package/dist/adapters/cursor/routing-config-trust.js +67 -0
  14. package/dist/adapters/cursor/routing-layout.d.ts +5 -0
  15. package/dist/adapters/cursor/routing-layout.js +29 -0
  16. package/dist/adapters/cursor/runtime-entry.d.ts +17 -0
  17. package/dist/adapters/cursor/runtime-entry.js +74 -49
  18. package/dist/adapters/layouts/scope.d.ts +5 -0
  19. package/dist/adapters/layouts/scope.js +71 -0
  20. package/dist/adapters/shared/gate-runtime.js +29 -17
  21. package/dist/bundle/claude-runtime.mjs +2428 -2072
  22. package/dist/bundle/codex-runtime.mjs +2440 -2084
  23. package/dist/bundle/cursor-dispatcher.mjs +517 -0
  24. package/dist/bundle/cursor-runtime.mjs +2521 -2133
  25. package/dist/cli.js +53 -4
  26. package/dist/commands/approval-token.d.ts +10 -0
  27. package/dist/commands/approval-token.js +26 -0
  28. package/dist/commands/audit.d.ts +2 -1
  29. package/dist/commands/audit.js +2 -2
  30. package/dist/commands/config.d.ts +1 -1
  31. package/dist/commands/config.js +28 -2
  32. package/dist/commands/doctor.js +149 -17
  33. package/dist/commands/dogfood-check.d.ts +3 -0
  34. package/dist/commands/dogfood-check.js +116 -0
  35. package/dist/commands/dogfood.d.ts +1 -0
  36. package/dist/commands/dogfood.js +4 -3
  37. package/dist/commands/health-snapshot.js +9 -4
  38. package/dist/commands/judge.js +2 -2
  39. package/dist/commands/recovery-checkpoints.d.ts +3 -3
  40. package/dist/commands/recovery-checkpoints.js +2 -11
  41. package/dist/config-io.d.ts +1 -0
  42. package/dist/config-io.js +19 -3
  43. package/dist/core/audit-legacy-archive.js +43 -25
  44. package/dist/core/classify-tool.js +16 -8
  45. package/dist/core/dogfood-environment.d.ts +8 -0
  46. package/dist/core/dogfood-environment.js +55 -0
  47. package/dist/core/effect-ir/argv-delegate.d.ts +9 -0
  48. package/dist/core/effect-ir/argv-delegate.js +42 -0
  49. package/dist/core/effect-ir/shell-lower/argv-delegate-gate.d.ts +3 -0
  50. package/dist/core/effect-ir/shell-lower/argv-delegate-gate.js +40 -0
  51. package/dist/core/effect-ir/shell-lower/augment.d.ts +4 -0
  52. package/dist/core/effect-ir/shell-lower/augment.js +97 -0
  53. package/dist/core/effect-ir/shell-lower/context.d.ts +10 -0
  54. package/dist/core/effect-ir/shell-lower/context.js +1 -0
  55. package/dist/core/effect-ir/shell-lower/decode-process.d.ts +9 -0
  56. package/dist/core/effect-ir/shell-lower/decode-process.js +201 -0
  57. package/dist/core/effect-ir/shell-lower/decoders/belay.d.ts +2 -0
  58. package/dist/core/effect-ir/shell-lower/decoders/belay.js +41 -0
  59. package/dist/core/effect-ir/shell-lower/decoders/builtins.d.ts +7 -0
  60. package/dist/core/effect-ir/shell-lower/decoders/builtins.js +194 -0
  61. package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.d.ts +4 -0
  62. package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.js +68 -0
  63. package/dist/core/effect-ir/shell-lower/decoders/docker.d.ts +3 -0
  64. package/dist/core/effect-ir/shell-lower/decoders/docker.js +43 -0
  65. package/dist/core/effect-ir/shell-lower/decoders/filesystem.d.ts +9 -0
  66. package/dist/core/effect-ir/shell-lower/decoders/filesystem.js +217 -0
  67. package/dist/core/effect-ir/shell-lower/decoders/prisma.d.ts +2 -0
  68. package/dist/core/effect-ir/shell-lower/decoders/prisma.js +54 -0
  69. package/dist/core/effect-ir/shell-lower/decoders/ruby.d.ts +11 -0
  70. package/dist/core/effect-ir/shell-lower/decoders/ruby.js +147 -0
  71. package/dist/core/effect-ir/shell-lower/decoders/toolchain.d.ts +7 -0
  72. package/dist/core/effect-ir/shell-lower/decoders/toolchain.js +155 -0
  73. package/dist/core/effect-ir/shell-lower/requirement.d.ts +12 -0
  74. package/dist/core/effect-ir/shell-lower/requirement.js +65 -0
  75. package/dist/core/effect-ir/shell-lower/segment.d.ts +11 -0
  76. package/dist/core/effect-ir/shell-lower/segment.js +82 -0
  77. package/dist/core/effect-ir/shell-lower/tokens.d.ts +26 -0
  78. package/dist/core/effect-ir/shell-lower/tokens.js +183 -0
  79. package/dist/core/effect-ir/shell-lower.d.ts +2 -7
  80. package/dist/core/effect-ir/shell-lower.js +60 -1502
  81. package/dist/core/egress-approval.js +0 -18
  82. package/dist/core/glob.js +32 -16
  83. package/dist/core/integrity.d.ts +2 -2
  84. package/dist/core/integrity.js +52 -12
  85. package/dist/core/notify.d.ts +8 -2
  86. package/dist/core/notify.js +63 -7
  87. package/dist/core/recovery/operator-guidance.js +4 -4
  88. package/dist/core/replay-scrub.d.ts +2 -0
  89. package/dist/core/replay-scrub.js +8 -0
  90. package/dist/core/repo-config-trust.d.ts +31 -0
  91. package/dist/core/repo-config-trust.js +152 -0
  92. package/dist/core/shell-substitution.js +2 -2
  93. package/dist/core/shell-unparseable.js +3 -1
  94. package/dist/corpus/benign-probe-cores.d.ts +1 -1
  95. package/dist/corpus/benign-probe-cores.js +0 -1
  96. package/dist/defaults.d.ts +7 -3
  97. package/dist/defaults.js +29 -47
  98. package/dist/installer/bootstrap.d.ts +1 -0
  99. package/dist/installer/bootstrap.js +2 -2
  100. package/dist/installer/runtime-artifacts.js +43 -13
  101. package/dist/installer/scope-config.js +2 -2
  102. package/dist/installer.js +48 -13
  103. package/dist/node-resolution.d.ts +1 -0
  104. package/dist/node-resolution.js +61 -0
  105. package/dist/templates.d.ts +7 -4
  106. package/dist/templates.js +42 -4
  107. package/dist/types.d.ts +20 -0
  108. package/dist/version.d.ts +1 -1
  109. package/dist/version.js +1 -1
  110. package/package.json +5 -1
@@ -0,0 +1,40 @@
1
+ import path from 'node:path';
2
+ import { isEgressToolHead } from '../../verdict/egress-classify.js';
3
+ const ARGV_DELEGATE_INNER_BLOCKLIST = new Set([
4
+ 'sudo',
5
+ 'env',
6
+ 'command',
7
+ 'builtin',
8
+ 'exec',
9
+ 'time',
10
+ 'nice',
11
+ 'nohup',
12
+ 'stdbuf',
13
+ 'setsid',
14
+ '(',
15
+ ]);
16
+ export function shouldApplyArgvDelegate(head, innerTokens, depth) {
17
+ if (depth > 0 || isEgressToolHead(head) || head === 'bundle') {
18
+ return false;
19
+ }
20
+ if (ARGV_DELEGATE_INNER_BLOCKLIST.has(head)) {
21
+ return false;
22
+ }
23
+ const innerHead = path.basename(innerTokens[0] ?? '');
24
+ if (ARGV_DELEGATE_INNER_BLOCKLIST.has(innerHead)) {
25
+ return false;
26
+ }
27
+ return innerTokens.length >= 2;
28
+ }
29
+ export function isGrammarUnknownOnly(requirements, head) {
30
+ if (requirements.length !== 2) {
31
+ return false;
32
+ }
33
+ const [spawn, indeterminate] = requirements;
34
+ return (spawn.action === 'process.exec' &&
35
+ spawn.resource.kind === 'executable' &&
36
+ spawn.resource.command === head &&
37
+ spawn.resource.operation === 'spawn' &&
38
+ indeterminate.action === 'indeterminate' &&
39
+ indeterminate.evidence.signals.includes('process.grammar_unknown'));
40
+ }
@@ -0,0 +1,4 @@
1
+ import type { ShellEffectRequirement } from '../shell-build.js';
2
+ import type { LowerContext } from './context.js';
3
+ export declare function addRedirectEffects(requirements: ShellEffectRequirement[], tokens: string[], env: Readonly<Record<string, string | undefined>>, context: LowerContext, segment: string): void;
4
+ export declare function effectChangingEnvironmentSignals(head: string, env: Readonly<Record<string, string | undefined>>, changedNames: ReadonlySet<string>): string[];
@@ -0,0 +1,97 @@
1
+ import { isFdDuplication, isRedirectOperator } from '../../shell-tokenizer.js';
2
+ import { addSecretRead, addWriteEffects, requirement } from './requirement.js';
3
+ import { expandKnownVariables, resolvePathOperand } from './tokens.js';
4
+ export function addRedirectEffects(requirements, tokens, env, context, segment) {
5
+ for (let index = 0; index < tokens.length; index += 1) {
6
+ const operator = tokens[index] ?? '';
7
+ if (!isRedirectOperator(operator) || isFdDuplication(operator)) {
8
+ continue;
9
+ }
10
+ const rawTarget = tokens[index + 1];
11
+ if (!rawTarget) {
12
+ requirements.push(requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
13
+ 'shell.redirect_target_missing',
14
+ ]));
15
+ continue;
16
+ }
17
+ if (operator.includes('<<')) {
18
+ requirements.push(requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
19
+ 'shell.heredoc_opaque',
20
+ ]));
21
+ index += 1;
22
+ continue;
23
+ }
24
+ const target = expandKnownVariables(rawTarget, env);
25
+ const resolved = resolvePathOperand(target, context.cwd);
26
+ if (resolved === '/dev/null') {
27
+ index += 1;
28
+ continue;
29
+ }
30
+ if (operator.includes('<')) {
31
+ requirements.push(requirement('fs.read', 'fs.read', { kind: 'path', path: resolved }, segment, [
32
+ 'shell.input_redirect',
33
+ ]));
34
+ addSecretRead(requirements, resolved, segment);
35
+ }
36
+ if (operator.includes('>')) {
37
+ addWriteEffects(requirements, resolved, segment, ['shell.output_redirect']);
38
+ }
39
+ index += 1;
40
+ }
41
+ }
42
+ export function effectChangingEnvironmentSignals(head, env, changedNames) {
43
+ if (head === 'curl' &&
44
+ (env.CURL_HOME ||
45
+ changedNames.has('CURL_HOME') ||
46
+ changedNames.has('HOME') ||
47
+ changedNames.has('XDG_CONFIG_HOME'))) {
48
+ return ['egress.curl.environment_config_override'];
49
+ }
50
+ if (head === 'wget' && (env.WGETRC || changedNames.has('WGETRC') || changedNames.has('HOME'))) {
51
+ return ['egress.wget.environment_config_override'];
52
+ }
53
+ if (head !== 'git') {
54
+ return [];
55
+ }
56
+ const hazardousNames = new Set([
57
+ 'GIT_ASKPASS',
58
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
59
+ 'GIT_CEILING_DIRECTORIES',
60
+ 'GIT_COMMON_DIR',
61
+ 'GIT_CONFIG',
62
+ 'GIT_CONFIG_GLOBAL',
63
+ 'GIT_CONFIG_SYSTEM',
64
+ 'GIT_DIR',
65
+ 'GIT_DISCOVERY_ACROSS_FILESYSTEM',
66
+ 'GIT_EDITOR',
67
+ 'GIT_EXEC_PATH',
68
+ 'GIT_EXTERNAL_DIFF',
69
+ 'GIT_INDEX_FILE',
70
+ 'GIT_NAMESPACE',
71
+ 'GIT_OBJECT_DIRECTORY',
72
+ 'GIT_PAGER',
73
+ 'GIT_PROXY_COMMAND',
74
+ 'GIT_QUARANTINE_PATH',
75
+ 'GIT_SEQUENCE_EDITOR',
76
+ 'GIT_SHALLOW_FILE',
77
+ 'GIT_SSH',
78
+ 'GIT_SSH_COMMAND',
79
+ 'GIT_TEMPLATE_DIR',
80
+ 'GIT_WORK_TREE',
81
+ 'SSH_ASKPASS',
82
+ ]);
83
+ const overridden = changedNames.has('HOME') ||
84
+ changedNames.has('XDG_CONFIG_HOME') ||
85
+ Object.entries(env).some(([name, value]) => {
86
+ if (!value) {
87
+ return false;
88
+ }
89
+ if (hazardousNames.has(name) ||
90
+ /^GIT_CONFIG_(?:KEY|VALUE)_\d+$/.test(name) ||
91
+ /^GIT_TRACE/.test(name)) {
92
+ return true;
93
+ }
94
+ return name === 'GIT_CONFIG_COUNT' && value !== '0';
95
+ });
96
+ return overridden ? ['git.environment_execution_or_config_override'] : [];
97
+ }
@@ -0,0 +1,10 @@
1
+ export interface LowerShellEffectPlanParams {
2
+ command: string;
3
+ cwd: string;
4
+ repoRoot: string;
5
+ inputFingerprint: string;
6
+ env?: Readonly<Record<string, string | undefined>>;
7
+ }
8
+ export interface LowerContext extends LowerShellEffectPlanParams {
9
+ depth: number;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { ShellEffectRequirement } from '../shell-build.js';
2
+ export declare function decodeProcessOrFilesystem(params: {
3
+ tokens: string[];
4
+ head: string;
5
+ env: Readonly<Record<string, string | undefined>>;
6
+ cwd: string;
7
+ repoRoot: string;
8
+ segment: string;
9
+ }): ShellEffectRequirement[];
@@ -0,0 +1,201 @@
1
+ import path from 'node:path';
2
+ import { isCommandInspection } from '../../verdict/parser.js';
3
+ import { decodeBelay } from './decoders/belay.js';
4
+ import { bashSyntaxTarget, decodeShellControlBuiltin, validFilesystemTest, validLsof, validPs, } from './decoders/builtins.js';
5
+ import { decodeDockerComposeRun, validDockerInfo } from './decoders/docker.js';
6
+ import { decodeCopyMove, decodeRm, filesystemReadOperands, filesystemWriteOperands, } from './decoders/filesystem.js';
7
+ import { decodePrisma } from './decoders/prisma.js';
8
+ import { decodeBundleExecInner, decodeRuby, decodeRuntimeMetadataProcess } from './decoders/ruby.js';
9
+ import { decodeGo, decodeNode, decodeRsync, decodeSed, decodeTsc } from './decoders/toolchain.js';
10
+ import { addSecretRead, addWriteEffects, processRequirement, requirement, unsupportedProcess, } from './requirement.js';
11
+ import { ENV_PREFIX_PATTERN, isMetadataOnlyArgv, isShellHead, resolvePathOperand, } from './tokens.js';
12
+ export function decodeProcessOrFilesystem(params) {
13
+ const { tokens, head, env, cwd, repoRoot, segment } = params;
14
+ const args = tokens.slice(1);
15
+ if (tokens.length > 0 && tokens.every((token) => ENV_PREFIX_PATTERN.test(token))) {
16
+ return [];
17
+ }
18
+ const shellControl = decodeShellControlBuiltin(head, args);
19
+ if (shellControl) {
20
+ return shellControl;
21
+ }
22
+ if (isCommandInspection(tokens)) {
23
+ return [processRequirement(head, 'inspect', segment, ['process.inspect.command_lookup'])];
24
+ }
25
+ if (head === 'lsof') {
26
+ return validLsof(args)
27
+ ? [processRequirement(head, 'inspect', segment, ['process.inspect.lsof'])]
28
+ : unsupportedProcess(head, segment, 'process.lsof_grammar_incomplete');
29
+ }
30
+ if (head === 'ps') {
31
+ return validPs(args)
32
+ ? [processRequirement(head, 'inspect', segment, ['process.inspect.ps'])]
33
+ : unsupportedProcess(head, segment, 'process.ps_grammar_incomplete');
34
+ }
35
+ if (head === 'test' || head === '[') {
36
+ return validFilesystemTest(head, args)
37
+ ? [processRequirement(head, 'inspect', segment, ['process.inspect.filesystem_test'])]
38
+ : unsupportedProcess(head, segment, 'process.filesystem_test_grammar_incomplete');
39
+ }
40
+ if (head === 'docker') {
41
+ if (args[0] === 'info') {
42
+ return validDockerInfo(args.slice(1))
43
+ ? [processRequirement(head, 'inspect', segment, ['process.inspect.docker_info'])]
44
+ : unsupportedProcess(head, segment, 'process.docker_info_grammar_incomplete');
45
+ }
46
+ if (args[0] === 'compose' &&
47
+ ['up', 'down', 'start', 'stop', 'restart', 'create', 'rm'].includes(args[1] ?? '')) {
48
+ return [processRequirement(head, 'spawn', segment, ['service.local_mutation'])];
49
+ }
50
+ if (args[0] === 'push' ||
51
+ (args[0] === 'build' && args.includes('--push')) ||
52
+ (args[0] === 'buildx' &&
53
+ args[1] === 'build' &&
54
+ (args.includes('--push') ||
55
+ args.some((arg) => arg.startsWith('--output=type=registry') || arg.startsWith('-o=type=registry'))))) {
56
+ return [
57
+ processRequirement(head, 'spawn', segment, ['tier0_external', 'docker.remote_mutation']),
58
+ requirement('network.connect', 'network.connect', {
59
+ kind: 'network',
60
+ host: 'registry',
61
+ protocol: 'container-registry',
62
+ mode: 'mutate',
63
+ payload: 'present',
64
+ }, segment, ['tier0_external', 'docker.remote_mutation']),
65
+ ];
66
+ }
67
+ if (args[0] === 'run' ||
68
+ args[0] === 'create' ||
69
+ args[0] === 'pull' ||
70
+ args[0] === 'build' ||
71
+ (args[0] === 'buildx' && args[1] === 'build')) {
72
+ return [
73
+ processRequirement(head, 'spawn', segment, ['process.docker_spawn']),
74
+ requirement('network.acquire', 'network.connect', {
75
+ kind: 'network',
76
+ host: 'unknown',
77
+ protocol: 'container-registry',
78
+ mode: 'ambiguous',
79
+ payload: 'none',
80
+ }, segment, ['docker.image_acquisition_possible']),
81
+ ];
82
+ }
83
+ return [processRequirement(head, 'spawn', segment, ['process.docker_spawn'])];
84
+ }
85
+ if (isShellHead(head)) {
86
+ const syntax = bashSyntaxTarget(args);
87
+ if (syntax) {
88
+ return [
89
+ processRequirement(head, 'inspect', segment, ['process.inspect.shell_syntax']),
90
+ requirement('fs.read', 'fs.read', { kind: 'path', path: path.resolve(cwd, syntax) }, segment, ['shell.syntax_source_read']),
91
+ ];
92
+ }
93
+ return unsupportedProcess(head, segment, 'process.shell_grammar_incomplete');
94
+ }
95
+ if (head === 'prisma') {
96
+ return decodePrisma(args, env, repoRoot, segment);
97
+ }
98
+ const runtimeMetadata = decodeRuntimeMetadataProcess(head, args, segment);
99
+ if (runtimeMetadata) {
100
+ return runtimeMetadata;
101
+ }
102
+ if (head === 'ruby') {
103
+ return decodeRuby(args, cwd, repoRoot, segment);
104
+ }
105
+ if (head === 'rubocop' || head === 'rspec') {
106
+ const decoded = decodeBundleExecInner(head, args, segment);
107
+ if (decoded) {
108
+ return decoded;
109
+ }
110
+ }
111
+ const dockerCompose = decodeDockerComposeRun(head, args, segment);
112
+ if (dockerCompose) {
113
+ return dockerCompose;
114
+ }
115
+ if ((head === 'npm' || head === 'pnpm') && args.length === 1 && isMetadataOnlyArgv(args)) {
116
+ return [processRequirement(head, 'inspect', segment, ['process.inspect.package_manager'])];
117
+ }
118
+ if (head === 'belay') {
119
+ return decodeBelay(args, repoRoot, segment);
120
+ }
121
+ if (head === 'tsc') {
122
+ return decodeTsc(args, cwd, segment);
123
+ }
124
+ if (head === 'go') {
125
+ return decodeGo(args, segment);
126
+ }
127
+ if (head === 'rsync') {
128
+ return decodeRsync(args, cwd, segment);
129
+ }
130
+ if (head === 'sed') {
131
+ return decodeSed(args, cwd, segment);
132
+ }
133
+ if (head === 'base64' && args.length === 1 && ['-d', '-D', '--decode'].includes(args[0] ?? '')) {
134
+ return [processRequirement(head, 'inspect', segment, ['process.inspect.base64_stdin'])];
135
+ }
136
+ if (head === 'node') {
137
+ return decodeNode(args, cwd, segment);
138
+ }
139
+ if (head === 'vite' || head === 'vite-node') {
140
+ return [processRequirement(head, 'spawn', segment, ['process.local_dev_spawn'])];
141
+ }
142
+ if (((head === 'npm' || head === 'pnpm') && args[0] === 'publish') ||
143
+ (head === 'terraform' && args[0] === 'apply')) {
144
+ return [
145
+ processRequirement(head, 'spawn', segment, ['tier0_external']),
146
+ requirement('network.connect', 'network.connect', {
147
+ kind: 'network',
148
+ host: 'control-plane',
149
+ protocol: head,
150
+ mode: 'mutate',
151
+ payload: 'present',
152
+ }, segment, ['tier0_external']),
153
+ ];
154
+ }
155
+ if (head === 'cd') {
156
+ return [];
157
+ }
158
+ if (head === 'cp' || head === 'mv') {
159
+ return decodeCopyMove(head, args, cwd, segment);
160
+ }
161
+ if (head === 'rm') {
162
+ return decodeRm(args, cwd, repoRoot, segment);
163
+ }
164
+ if (head === 'vitest' || head === 'eslint' || head === 'biome' || head === 'belay') {
165
+ return [processRequirement(head, 'spawn', segment, ['process.known_local_spawn'])];
166
+ }
167
+ if (head === 'pwd' || head === 'which' || head === 'whoami') {
168
+ return [processRequirement(head, 'inspect', segment, ['process.pure_inspection'])];
169
+ }
170
+ const readOperands = filesystemReadOperands(head, args);
171
+ if (readOperands !== null) {
172
+ const lowered = [processRequirement(head, 'inspect', segment, ['process.filesystem_inspect'])];
173
+ for (const operand of readOperands) {
174
+ const resolved = resolvePathOperand(operand, cwd);
175
+ lowered.push(requirement('fs.read', 'fs.read', { kind: 'path', path: resolved }, segment, [
176
+ 'filesystem.read',
177
+ ]));
178
+ addSecretRead(lowered, resolved, segment);
179
+ }
180
+ return lowered;
181
+ }
182
+ const writeOperands = filesystemWriteOperands(head, args);
183
+ if (writeOperands !== null) {
184
+ const lowered = [processRequirement(head, 'spawn', segment, ['process.filesystem_mutation'])];
185
+ for (const operand of writeOperands) {
186
+ addWriteEffects(lowered, resolvePathOperand(operand, cwd), segment, ['filesystem.write']);
187
+ }
188
+ return lowered;
189
+ }
190
+ if (head === 'printf' || head === 'echo' || head === 'true' || head === 'false' || head === ':') {
191
+ return [];
192
+ }
193
+ if (!head) {
194
+ return [
195
+ requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
196
+ 'shell.segment_empty',
197
+ ]),
198
+ ];
199
+ }
200
+ return unsupportedProcess(head, segment, 'process.grammar_unknown');
201
+ }
@@ -0,0 +1,2 @@
1
+ import type { ShellEffectRequirement } from '../../shell-build.js';
2
+ export declare function decodeBelay(args: string[], repoRoot: string, segment: string): ShellEffectRequirement[];
@@ -0,0 +1,41 @@
1
+ import path from 'node:path';
2
+ import { processRequirement, requirement } from '../requirement.js';
3
+ export function decodeBelay(args, repoRoot, segment) {
4
+ const [section, operation, key] = args;
5
+ const judgeCommand = section === 'judge' &&
6
+ operation !== undefined &&
7
+ ['consent', 'list', 'status', 'test', 'use'].includes(operation);
8
+ const configRead = section === 'config' &&
9
+ (operation === undefined ||
10
+ operation === 'list' ||
11
+ (operation === 'get' && key?.startsWith('judge.')));
12
+ const configJudgeMutation = section === 'config' &&
13
+ ((['set', 'unset'].includes(operation ?? '') && key?.startsWith('judge.')) ||
14
+ (operation === 'credential' && key === 'mode'));
15
+ const approvalAuthorityCommand = [
16
+ 'approval-token',
17
+ 'approve',
18
+ 'revoke',
19
+ 'standing-allow',
20
+ ].includes(section ?? '');
21
+ const configTrustMutation = section === 'config' && operation === 'trust';
22
+ if (judgeCommand || configRead || configJudgeMutation) {
23
+ return [
24
+ processRequirement('belay', 'inspect', segment, [
25
+ 'belay_control_plane_command',
26
+ configJudgeMutation ? 'belay.config_judge_mutation' : 'belay.config_read',
27
+ ]),
28
+ ];
29
+ }
30
+ if (approvalAuthorityCommand || configTrustMutation) {
31
+ return [
32
+ requirement('control_plane.write', 'control_plane.write', { kind: 'path', path: path.join(repoRoot, '.belay-control-plane') }, segment, [approvalAuthorityCommand ? 'belay.approval_authority' : 'belay.config_trust']),
33
+ ];
34
+ }
35
+ if (section === 'config' && ['set', 'unset', 'credential'].includes(operation ?? '')) {
36
+ return [
37
+ requirement('control_plane.write', 'control_plane.write', { kind: 'path', path: path.join(repoRoot, '.belay-control-plane') }, segment, ['belay.config_non_judge_mutation']),
38
+ ];
39
+ }
40
+ return [processRequirement('belay', 'spawn', segment, ['process.known_local_spawn'])];
41
+ }
@@ -0,0 +1,7 @@
1
+ import type { ShellEffectRequirement } from '../../shell-build.js';
2
+ export declare function decodeSetBuiltin(args: string[]): boolean;
3
+ export declare function decodeShellControlBuiltin(head: string, args: string[]): ShellEffectRequirement[] | null;
4
+ export declare function validLsof(args: string[]): boolean;
5
+ export declare function validPs(args: string[]): boolean;
6
+ export declare function validFilesystemTest(head: string, args: string[]): boolean;
7
+ export declare function bashSyntaxTarget(args: string[]): string | null;
@@ -0,0 +1,194 @@
1
+ import { isOptionToken } from '../tokens.js';
2
+ export function decodeSetBuiltin(args) {
3
+ let index = 0;
4
+ while (index < args.length) {
5
+ const arg = args[index] ?? '';
6
+ if (arg === '--') {
7
+ index += 1;
8
+ continue;
9
+ }
10
+ if (arg === '-o' || arg === '+o') {
11
+ if (!args[index + 1]) {
12
+ return false;
13
+ }
14
+ index += 2;
15
+ continue;
16
+ }
17
+ if (/^[-+][A-Za-z0-9]+$/.test(arg)) {
18
+ index += 1;
19
+ continue;
20
+ }
21
+ return false;
22
+ }
23
+ return true;
24
+ }
25
+ export function decodeShellControlBuiltin(head, args) {
26
+ if (head === 'set') {
27
+ return decodeSetBuiltin(args) ? [] : null;
28
+ }
29
+ if (head === 'wait') {
30
+ if (args.length === 0 || args.every((arg) => /^\d+$/.test(arg))) {
31
+ return [];
32
+ }
33
+ return null;
34
+ }
35
+ if (head === 'exit') {
36
+ if (args.length === 0 || (args.length === 1 && /^-?\d+$/.test(args[0] ?? ''))) {
37
+ return [];
38
+ }
39
+ return null;
40
+ }
41
+ return null;
42
+ }
43
+ export function validLsof(args) {
44
+ const flagsWithoutValues = new Set(['-a', '-b', '-l', '-n', '-P', '-R', '-t', '-V']);
45
+ const flagsWithValues = new Set(['-c', '-d', '-F', '-g', '-p', '-T', '-u', '+d', '+D']);
46
+ for (let index = 0; index < args.length; index += 1) {
47
+ const arg = args[index] ?? '';
48
+ if (flagsWithoutValues.has(arg) || arg === '-i') {
49
+ continue;
50
+ }
51
+ if (flagsWithValues.has(arg)) {
52
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
53
+ return false;
54
+ }
55
+ index += 1;
56
+ continue;
57
+ }
58
+ if ((/^-[ablnPRtV]+$/.test(arg) &&
59
+ [...arg.slice(1)].every((flag) => 'ablnPRtV'.includes(flag))) ||
60
+ /^-i(?:TCP|UDP)?(?::\d+(?:-\d+)?)?$/.test(arg) ||
61
+ /^-s(?:TCP|UDP):[A-Za-z]+$/.test(arg) ||
62
+ /^-(?:c|d|F|g|p|T|u).+$/.test(arg) ||
63
+ /^\+(?:d|D).+$/.test(arg)) {
64
+ continue;
65
+ }
66
+ return false;
67
+ }
68
+ return true;
69
+ }
70
+ export function validPs(args) {
71
+ const bareForms = new Set(['aux', 'ax', 'x', 'u']);
72
+ const flagsWithoutValues = new Set(['-A', '-a', '-d', '-e', '-f', '-j', '-l', '-T', '-x']);
73
+ const flagsWithValues = new Set([
74
+ '--format',
75
+ '--group',
76
+ '--pid',
77
+ '--ppid',
78
+ '--sort',
79
+ '--tty',
80
+ '--user',
81
+ ]);
82
+ for (let index = 0; index < args.length; index += 1) {
83
+ const arg = args[index] ?? '';
84
+ if (bareForms.has(arg) || flagsWithoutValues.has(arg)) {
85
+ continue;
86
+ }
87
+ if (flagsWithValues.has(arg)) {
88
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
89
+ return false;
90
+ }
91
+ index += 1;
92
+ continue;
93
+ }
94
+ if (/^(?:--format|--group|--pid|--ppid|--sort|--tty|--user)=.+$/.test(arg)) {
95
+ continue;
96
+ }
97
+ if (/^-[^-]/.test(arg)) {
98
+ const noValue = new Set(['A', 'a', 'd', 'e', 'f', 'j', 'l', 'T', 'x']);
99
+ const withValue = new Set(['G', 'g', 'N', 'o', 'p', 't', 'U', 'u']);
100
+ const characters = arg.slice(1);
101
+ let valid = true;
102
+ for (let optionIndex = 0; optionIndex < characters.length; optionIndex += 1) {
103
+ const option = characters[optionIndex] ?? '';
104
+ if (noValue.has(option)) {
105
+ continue;
106
+ }
107
+ if (withValue.has(option)) {
108
+ if (optionIndex === characters.length - 1) {
109
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
110
+ return false;
111
+ }
112
+ index += 1;
113
+ }
114
+ break;
115
+ }
116
+ valid = false;
117
+ break;
118
+ }
119
+ if (valid) {
120
+ continue;
121
+ }
122
+ }
123
+ return false;
124
+ }
125
+ return true;
126
+ }
127
+ export function validFilesystemTest(head, args) {
128
+ const operands = head === '[' && args.at(-1) === ']' ? args.slice(0, -1) : args;
129
+ if (head === '[' && args.at(-1) !== ']') {
130
+ return false;
131
+ }
132
+ return (operands.length === 2 &&
133
+ new Set(['-b', '-c', '-d', '-e', '-f', '-h', '-L', '-r', '-s', '-w', '-x']).has(operands[0] ?? '') &&
134
+ Boolean(operands[1]));
135
+ }
136
+ export function bashSyntaxTarget(args) {
137
+ let syntaxOnly = false;
138
+ let script = null;
139
+ const shortFlags = new Set('abefhkmnptuvxBCEHPT'.split(''));
140
+ const longFlags = new Set([
141
+ '--debugger',
142
+ '--dump-po-strings',
143
+ '--dump-strings',
144
+ '--help',
145
+ '--login',
146
+ '--noediting',
147
+ '--noprofile',
148
+ '--norc',
149
+ '--posix',
150
+ '--restricted',
151
+ '--verbose',
152
+ '--version',
153
+ ]);
154
+ for (let index = 0; index < args.length; index += 1) {
155
+ const arg = args[index] ?? '';
156
+ if (arg === '-c' || arg === '-lc') {
157
+ return null;
158
+ }
159
+ if (arg === '-O' || arg === '+O' || arg === '--rcfile' || arg === '--init-file') {
160
+ if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
161
+ return null;
162
+ }
163
+ index += 1;
164
+ continue;
165
+ }
166
+ if ((arg.startsWith('-O') && arg.length > 2) ||
167
+ (arg.startsWith('+O') && arg.length > 2) ||
168
+ (arg.startsWith('--rcfile=') && arg.length > '--rcfile='.length) ||
169
+ (arg.startsWith('--init-file=') && arg.length > '--init-file='.length)) {
170
+ continue;
171
+ }
172
+ if (longFlags.has(arg)) {
173
+ continue;
174
+ }
175
+ if (/^[+-][A-Za-z]+$/.test(arg)) {
176
+ const options = arg.slice(1);
177
+ if ([...options].some((option) => !shortFlags.has(option))) {
178
+ return null;
179
+ }
180
+ syntaxOnly ||= options.includes('n');
181
+ continue;
182
+ }
183
+ if (arg === '--') {
184
+ script = args[index + 1] ?? null;
185
+ break;
186
+ }
187
+ if (arg.startsWith('-') || arg.startsWith('+')) {
188
+ return null;
189
+ }
190
+ script = arg;
191
+ break;
192
+ }
193
+ return syntaxOnly ? script : null;
194
+ }
@@ -0,0 +1,4 @@
1
+ import { type peelPackageExecArgv } from '../../package-exec.js';
2
+ import type { ShellEffectRequirement } from '../../shell-build.js';
3
+ export declare function decodePackageExec(peel: NonNullable<ReturnType<typeof peelPackageExecArgv>>, cwd: string, repoRoot: string, segment: string): ShellEffectRequirement[];
4
+ export declare function packageExecInnerIsMetadata(peel: NonNullable<ReturnType<typeof peelPackageExecArgv>>): boolean;