@guilz-dev/belay 0.9.3 → 0.9.4

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 (83) hide show
  1. package/README.md +35 -1
  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 +7 -0
  7. package/dist/adapters/cursor/hook-dispatch-entry.js +126 -0
  8. package/dist/adapters/cursor/hook-router.d.ts +23 -0
  9. package/dist/adapters/cursor/hook-router.js +266 -0
  10. package/dist/adapters/cursor/hooks.d.ts +4 -0
  11. package/dist/adapters/cursor/hooks.js +99 -8
  12. package/dist/adapters/cursor/routing-layout.d.ts +5 -0
  13. package/dist/adapters/cursor/routing-layout.js +29 -0
  14. package/dist/adapters/cursor/runtime-entry.d.ts +17 -0
  15. package/dist/adapters/cursor/runtime-entry.js +39 -39
  16. package/dist/adapters/layouts/scope.d.ts +5 -0
  17. package/dist/adapters/layouts/scope.js +71 -0
  18. package/dist/adapters/shared/gate-runtime.js +1 -0
  19. package/dist/bundle/claude-runtime.mjs +1932 -1740
  20. package/dist/bundle/codex-runtime.mjs +1934 -1742
  21. package/dist/bundle/cursor-dispatcher.mjs +443 -0
  22. package/dist/bundle/cursor-runtime.mjs +2017 -1821
  23. package/dist/commands/doctor.js +191 -15
  24. package/dist/commands/health-snapshot.js +9 -4
  25. package/dist/commands/recovery-checkpoints.d.ts +3 -3
  26. package/dist/config-io.js +11 -2
  27. package/dist/core/audit-legacy-archive.js +43 -25
  28. package/dist/core/classify-tool.js +16 -8
  29. package/dist/core/effect-ir/argv-delegate.d.ts +9 -0
  30. package/dist/core/effect-ir/argv-delegate.js +42 -0
  31. package/dist/core/effect-ir/shell-lower/argv-delegate-gate.d.ts +3 -0
  32. package/dist/core/effect-ir/shell-lower/argv-delegate-gate.js +40 -0
  33. package/dist/core/effect-ir/shell-lower/augment.d.ts +4 -0
  34. package/dist/core/effect-ir/shell-lower/augment.js +97 -0
  35. package/dist/core/effect-ir/shell-lower/context.d.ts +10 -0
  36. package/dist/core/effect-ir/shell-lower/context.js +1 -0
  37. package/dist/core/effect-ir/shell-lower/decode-process.d.ts +9 -0
  38. package/dist/core/effect-ir/shell-lower/decode-process.js +201 -0
  39. package/dist/core/effect-ir/shell-lower/decoders/belay.d.ts +2 -0
  40. package/dist/core/effect-ir/shell-lower/decoders/belay.js +29 -0
  41. package/dist/core/effect-ir/shell-lower/decoders/builtins.d.ts +7 -0
  42. package/dist/core/effect-ir/shell-lower/decoders/builtins.js +194 -0
  43. package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.d.ts +4 -0
  44. package/dist/core/effect-ir/shell-lower/decoders/decode-package-exec.js +68 -0
  45. package/dist/core/effect-ir/shell-lower/decoders/docker.d.ts +3 -0
  46. package/dist/core/effect-ir/shell-lower/decoders/docker.js +43 -0
  47. package/dist/core/effect-ir/shell-lower/decoders/filesystem.d.ts +9 -0
  48. package/dist/core/effect-ir/shell-lower/decoders/filesystem.js +217 -0
  49. package/dist/core/effect-ir/shell-lower/decoders/prisma.d.ts +2 -0
  50. package/dist/core/effect-ir/shell-lower/decoders/prisma.js +54 -0
  51. package/dist/core/effect-ir/shell-lower/decoders/ruby.d.ts +11 -0
  52. package/dist/core/effect-ir/shell-lower/decoders/ruby.js +147 -0
  53. package/dist/core/effect-ir/shell-lower/decoders/toolchain.d.ts +7 -0
  54. package/dist/core/effect-ir/shell-lower/decoders/toolchain.js +155 -0
  55. package/dist/core/effect-ir/shell-lower/requirement.d.ts +12 -0
  56. package/dist/core/effect-ir/shell-lower/requirement.js +65 -0
  57. package/dist/core/effect-ir/shell-lower/segment.d.ts +11 -0
  58. package/dist/core/effect-ir/shell-lower/segment.js +82 -0
  59. package/dist/core/effect-ir/shell-lower/tokens.d.ts +26 -0
  60. package/dist/core/effect-ir/shell-lower/tokens.js +183 -0
  61. package/dist/core/effect-ir/shell-lower.d.ts +2 -7
  62. package/dist/core/effect-ir/shell-lower.js +60 -1502
  63. package/dist/core/glob.js +32 -16
  64. package/dist/core/integrity.d.ts +2 -2
  65. package/dist/core/integrity.js +52 -12
  66. package/dist/core/replay-scrub.d.ts +2 -0
  67. package/dist/core/replay-scrub.js +8 -0
  68. package/dist/core/shell-substitution.js +2 -2
  69. package/dist/core/shell-unparseable.js +3 -1
  70. package/dist/defaults.d.ts +7 -3
  71. package/dist/defaults.js +29 -22
  72. package/dist/installer/bootstrap.d.ts +1 -0
  73. package/dist/installer/bootstrap.js +2 -2
  74. package/dist/installer/runtime-artifacts.js +43 -13
  75. package/dist/installer.js +44 -9
  76. package/dist/node-resolution.d.ts +1 -0
  77. package/dist/node-resolution.js +61 -0
  78. package/dist/templates.d.ts +7 -4
  79. package/dist/templates.js +42 -4
  80. package/dist/types.d.ts +1 -0
  81. package/dist/version.d.ts +1 -1
  82. package/dist/version.js +1 -1
  83. package/package.json +1 -1
@@ -1,21 +1,22 @@
1
- import { lstatSync, realpathSync } from 'node:fs';
2
1
  import path from 'node:path';
3
- import { inspectGitResourceIdentity } from '../git-resource-identity.js';
4
- import { canonicalPath, pathWithinRoot } from '../path-utils.js';
5
- import { isFdDuplication, isRedirectOperator, lexShell, tokenizeShell, } from '../shell-tokenizer.js';
2
+ import { lexShell } from '../shell-tokenizer.js';
6
3
  import { decodeDockerComposeRun as decodeStructuredDockerComposeRun } from '../verdict/docker-compose-run.js';
7
4
  import { decodeEgressEffects } from '../verdict/egress-classify.js';
8
5
  import { decodeGitEffects } from '../verdict/git-classifier.js';
9
6
  import { resolveLauncherRecipe } from '../verdict/launcher-resolve.js';
10
- import { decodeRecursiveInvocationTokens, isCommandInspection, parseSegment, redactCommand, segmentOpacity, splitStructuralShellSegments, structuralSubstitutionInners, } from '../verdict/parser.js';
7
+ import { decodeRecursiveInvocationTokens, parseSegment, redactCommand, segmentOpacity, splitStructuralShellSegments, structuralSubstitutionInners, } from '../verdict/parser.js';
8
+ import { innerRecipeFromArgvDelegate, peelArgvDelegateArgv } from './argv-delegate.js';
11
9
  import { joinEffectOpacity } from './normalize.js';
12
- import { classifyPackageAcquisitionSpec, innerRecipeFromPeel, peelPackageExecArgv, resolveLocalBin, } from './package-exec.js';
10
+ import { innerRecipeFromPeel, peelPackageExecArgv } from './package-exec.js';
13
11
  import { buildShellEffectPlan, } from './shell-build.js';
12
+ import { isGrammarUnknownOnly, shouldApplyArgvDelegate } from './shell-lower/argv-delegate-gate.js';
13
+ import { addRedirectEffects, effectChangingEnvironmentSignals } from './shell-lower/augment.js';
14
+ import { decodeProcessOrFilesystem } from './shell-lower/decode-process.js';
15
+ import { decodePackageExec, packageExecInnerIsMetadata, } from './shell-lower/decoders/decode-package-exec.js';
16
+ import { ensureRequirement, processRequirement, requirement, withInnerProvenance, withProvenances, } from './shell-lower/requirement.js';
17
+ import { joinNestedOpacity, requiresKnownCwd, resolveCdTransition, shellSegment, startsLocalPostgresService, } from './shell-lower/segment.js';
18
+ import { alignStructuredTokens, ENV_PREFIX_PATTERN, expandKnownVariables, extractEnvironment, isShellHead, pipeToShell, stripRedirects, stripStructuredRedirects, } from './shell-lower/tokens.js';
14
19
  const MAX_LOWER_DEPTH = 8;
15
- const ENV_PREFIX_PATTERN = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
16
- const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '0:0:0:0:0:0:0:1']);
17
- const SECRET_PATH_PATTERN = /(?:^|[/\\])(?:\.env(?:\..*)?|credentials?|secrets?|id_rsa)(?:$|[/\\])/i;
18
- const CONTROL_PLANE_PATH_PATTERN = /^(?:\/etc(?:\/|$)|\/var\/run(?:\/|$)|.*(?:^|[/\\])\.(?:git|ssh|cursor|claude)(?:[/\\]|$))/;
19
20
  /**
20
21
  * Parse and lower a general shell command into the canonical Task 2 model.
21
22
  * This function only produces effects; it does not evaluate policy or return a
@@ -92,40 +93,6 @@ function lowerTopLevelSegments(command, context) {
92
93
  }
93
94
  return lowered;
94
95
  }
95
- function startsLocalPostgresService(command) {
96
- const tokens = tokenizeShell(command);
97
- return (path.basename(tokens[0] ?? '') === 'docker' &&
98
- tokens[1] === 'compose' &&
99
- ['up', 'start', 'restart'].includes(tokens[2] ?? '') &&
100
- tokens.includes('postgres'));
101
- }
102
- function resolveCdTransition(command, currentCwd) {
103
- const tokens = tokenizeShell(command);
104
- if (path.basename(tokens[0] ?? '') !== 'cd') {
105
- return null;
106
- }
107
- const target = tokens[1] ?? '~';
108
- if (!target || target === '-' || target.includes('$') || target.includes('`')) {
109
- return { cwd: currentCwd, known: false };
110
- }
111
- return { cwd: resolvePathOperand(target, currentCwd), known: true };
112
- }
113
- function requiresKnownCwd(requirementValue) {
114
- if (requirementValue.action === 'fs.write' ||
115
- requirementValue.action === 'git.ref.write' ||
116
- requirementValue.action === 'control_plane.write') {
117
- return true;
118
- }
119
- return (requirementValue.action === 'process.exec' &&
120
- requirementValue.resource.kind === 'executable' &&
121
- requirementValue.resource.operation === 'spawn');
122
- }
123
- function joinNestedOpacity(outer, nested) {
124
- const nestedOpacity = nested.completeness === 'partial' && nested.opacity === 'transparent'
125
- ? 'recursive'
126
- : nested.opacity;
127
- return joinEffectOpacity(outer, nestedOpacity);
128
- }
129
96
  function lowerSegment(command, context) {
130
97
  const commandRedacted = redactCommand(command);
131
98
  const lexed = lexShell(command);
@@ -321,14 +288,61 @@ function lowerSegment(command, context) {
321
288
  }
322
289
  }
323
290
  else {
324
- requirements.push(...decodeProcessOrFilesystem({
291
+ const processRequirements = decodeProcessOrFilesystem({
325
292
  tokens,
326
293
  head,
327
294
  env,
328
295
  cwd: context.cwd,
329
296
  repoRoot: context.repoRoot,
330
297
  segment: commandRedacted,
331
- }));
298
+ });
299
+ if (isGrammarUnknownOnly(processRequirements, head)) {
300
+ const argvDelegate = peelArgvDelegateArgv(tokens);
301
+ if (argvDelegate &&
302
+ !argvDelegate.opaque &&
303
+ shouldApplyArgvDelegate(head, argvDelegate.innerTokens, context.depth)) {
304
+ const innerRecipe = innerRecipeFromArgvDelegate(argvDelegate);
305
+ if (innerRecipe) {
306
+ requirements.push(processRequirement(head, 'inspect', commandRedacted, [
307
+ 'process.argv_delegate',
308
+ ...argvDelegate.signals,
309
+ ]));
310
+ for (const signal of argvDelegate.signals) {
311
+ signals.add(signal);
312
+ }
313
+ const nested = lowerTopLevelSegments(innerRecipe, {
314
+ ...context,
315
+ command: innerRecipe,
316
+ env,
317
+ depth: context.depth + 1,
318
+ });
319
+ for (const nestedSegment of nested) {
320
+ requirements.push(...nestedSegment.requirements.map((entry) => withInnerProvenance(entry, innerRecipe, head, commandRedacted)));
321
+ for (const signal of nestedSegment.signals) {
322
+ signals.add(signal);
323
+ }
324
+ opacity = joinNestedOpacity(opacity, nestedSegment);
325
+ }
326
+ return shellSegment(commandRedacted, head, requirements, opacity, signals);
327
+ }
328
+ }
329
+ if (argvDelegate?.opaque) {
330
+ requirements.push(processRequirement(head, 'spawn', commandRedacted, [
331
+ 'process.argv_delegate',
332
+ argvDelegate.reason,
333
+ ...argvDelegate.signals,
334
+ ]), requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, commandRedacted, [
335
+ argvDelegate.reason,
336
+ 'process.argv_delegate_opaque',
337
+ ]));
338
+ for (const signal of argvDelegate.signals) {
339
+ signals.add(signal);
340
+ }
341
+ signals.add('process.argv_delegate_opaque');
342
+ return shellSegment(commandRedacted, head, requirements, joinEffectOpacity(opacity, 'opaque'), signals);
343
+ }
344
+ }
345
+ requirements.push(...processRequirements);
332
346
  }
333
347
  }
334
348
  const environmentSignals = effectChangingEnvironmentSignals(head, env, environment.changedNames);
@@ -354,885 +368,6 @@ function lowerSegment(command, context) {
354
368
  }
355
369
  return shellSegment(commandRedacted, head, requirements, opacity, signals);
356
370
  }
357
- function decodePackageExec(peel, cwd, repoRoot, segment) {
358
- if (peel.opaque) {
359
- return [
360
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
361
- peel.reason,
362
- ...peel.signals,
363
- ]),
364
- ];
365
- }
366
- if (peel.reason === 'npx_wrapper_readonly') {
367
- return [];
368
- }
369
- const binHead = peel.innerTokens[0] ?? '';
370
- const metadataOnly = packageExecInnerIsMetadata(peel);
371
- const local = !peel.forceAcquire ? resolveLocalBin(binHead, cwd, repoRoot) : null;
372
- if (local) {
373
- return metadataOnly
374
- ? [
375
- requirement('process.exec', 'process.exec', { kind: 'executable', command: local.path, operation: 'inspect' }, segment, ['package_exec.delegate', 'package_exec.local_bin_resolved']),
376
- ]
377
- : [];
378
- }
379
- const networkResources = peel.acquisitionSpecs
380
- .map((spec) => classifyPackageAcquisitionSpec(spec))
381
- .filter((source) => source.kind !== 'local')
382
- .map((source) => source.kind === 'registry'
383
- ? {
384
- kind: 'network',
385
- host: 'registry.npmjs.org',
386
- protocol: 'registry',
387
- mode: 'read',
388
- payload: 'none',
389
- }
390
- : {
391
- kind: 'network',
392
- host: source.host,
393
- ...(source.port ? { port: source.port } : {}),
394
- protocol: source.protocol,
395
- mode: 'read',
396
- payload: 'none',
397
- });
398
- if (networkResources.length === 0) {
399
- networkResources.push({
400
- kind: 'network',
401
- host: 'registry.npmjs.org',
402
- protocol: 'registry',
403
- mode: 'read',
404
- payload: 'none',
405
- });
406
- }
407
- return [
408
- ...networkResources.map((resource) => requirement('network.acquire', 'network.connect', resource, segment, [
409
- 'package_acquire_possible',
410
- ])),
411
- requirement('fs.write', 'fs.write', { kind: 'package-cache', manager: peel.launcher === 'pnpm' ? 'pnpm' : 'npm' }, segment, ['package_cache_write']),
412
- ...(metadataOnly
413
- ? [
414
- requirement('process.exec', 'process.exec', { kind: 'executable', command: binHead || peel.launcher, operation: 'inspect' }, segment, ['package_exec.delegate']),
415
- ]
416
- : []),
417
- ];
418
- }
419
- function packageExecInnerIsMetadata(peel) {
420
- const args = peel.innerTokens.slice(1);
421
- return (args.length > 0 && args.every((arg) => ['--help', '--version', '-V', '-h', '-v'].includes(arg)));
422
- }
423
- const METADATA_ONLY_FLAGS = new Set(['--version', '-v', '-V', '--help', '-h']);
424
- function isMetadataOnlyArgv(argv) {
425
- return argv.length > 0 && argv.every((token) => METADATA_ONLY_FLAGS.has(token));
426
- }
427
- function executableBaseName(head) {
428
- return path.basename(head);
429
- }
430
- const RAILS_READ_ONLY_SUBCOMMANDS = new Set(['routes', 'middleware', 'stats', 'about', 'version']);
431
- function railsReadOnlySubcommand(args) {
432
- const subcommand = args[0];
433
- if (!subcommand || subcommand.startsWith('-')) {
434
- return false;
435
- }
436
- return RAILS_READ_ONLY_SUBCOMMANDS.has(subcommand);
437
- }
438
- function isRubyTestScript(scriptPath) {
439
- const base = path.basename(scriptPath);
440
- return base.endsWith('_test.rb') || base.endsWith('_spec.rb');
441
- }
442
- function parseRubyTestInvocation(args) {
443
- const includePaths = [];
444
- for (let index = 0; index < args.length; index += 1) {
445
- const arg = args[index] ?? '';
446
- if (arg === '-e' || arg === '-r') {
447
- return null;
448
- }
449
- if (arg === '-I') {
450
- const includePath = args[index + 1];
451
- if (!includePath) {
452
- return null;
453
- }
454
- includePaths.push(includePath);
455
- index += 1;
456
- continue;
457
- }
458
- if (arg.startsWith('-I') && arg.length > 2) {
459
- includePaths.push(arg.slice(2));
460
- continue;
461
- }
462
- if (arg.startsWith('-')) {
463
- if (arg === '-n') {
464
- if (!args[index + 1]) {
465
- return null;
466
- }
467
- index += 1;
468
- continue;
469
- }
470
- if (arg.startsWith('-n')) {
471
- continue;
472
- }
473
- return null;
474
- }
475
- if (isRubyTestScript(arg)) {
476
- return { includePaths, scriptPath: arg };
477
- }
478
- return null;
479
- }
480
- return null;
481
- }
482
- function isRubocopMutating(args) {
483
- return args.some((arg) => arg === '-A' ||
484
- arg === '-a' ||
485
- arg === '--auto-correct' ||
486
- arg === '--autocorrect' ||
487
- arg.startsWith('--auto-correct-all') ||
488
- arg.startsWith('--autocorrect-all'));
489
- }
490
- function decodeBundleExecInner(innerHead, innerArgs, segment) {
491
- const innerBase = executableBaseName(innerHead);
492
- if (innerBase === 'rubocop') {
493
- const mutating = isRubocopMutating(innerArgs);
494
- return [
495
- processRequirement(innerHead, mutating ? 'spawn' : 'inspect', segment, mutating ? ['process.linter.mutating'] : ['process.inspect.linter']),
496
- ];
497
- }
498
- if (innerBase === 'rspec') {
499
- const targetArgs = innerArgs.filter((arg) => !arg.startsWith('-'));
500
- if (targetArgs.length === 0) {
501
- return null;
502
- }
503
- return [processRequirement(innerHead, 'spawn', segment, ['process.test_runner.rspec'])];
504
- }
505
- return null;
506
- }
507
- function decodeRuby(args, cwd, repoRoot, segment) {
508
- const parsed = parseRubyTestInvocation(args);
509
- if (!parsed) {
510
- return unsupportedProcess('ruby', segment, 'process.ruby_grammar_incomplete');
511
- }
512
- const scriptPath = resolvePathOperand(parsed.scriptPath, cwd);
513
- if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(scriptPath))) {
514
- return unsupportedProcess('ruby', segment, 'process.ruby_outside_repo');
515
- }
516
- for (const includePath of parsed.includePaths) {
517
- const resolvedInclude = resolvePathOperand(includePath, cwd);
518
- if (!pathWithinRoot(canonicalPath(repoRoot), canonicalPath(resolvedInclude))) {
519
- return unsupportedProcess('ruby', segment, 'process.ruby_outside_repo');
520
- }
521
- }
522
- const lowered = [
523
- processRequirement('ruby', 'spawn', segment, ['process.test_runner.minitest']),
524
- requirement('fs.read', 'fs.read', { kind: 'path', path: scriptPath }, segment, [
525
- 'ruby.minitest_script_read',
526
- ]),
527
- ];
528
- for (const includePath of parsed.includePaths) {
529
- lowered.push(requirement('fs.read', 'fs.read', { kind: 'path', path: resolvePathOperand(includePath, cwd) }, segment, ['ruby.minitest_load_path_read']));
530
- }
531
- return lowered;
532
- }
533
- function decodeRuntimeMetadataProcess(head, args, segment) {
534
- if (head === 'bundle') {
535
- if (args.length === 1 && isMetadataOnlyArgv(args)) {
536
- return [processRequirement(head, 'inspect', segment, ['process.inspect.runtime_metadata'])];
537
- }
538
- if (args[0] === 'exec' && args.length >= 2) {
539
- const innerHead = args[1] ?? '';
540
- const innerArgs = args.slice(2);
541
- const innerBase = executableBaseName(innerHead);
542
- if ((innerBase === 'rails' || innerBase === 'rake') && railsReadOnlySubcommand(innerArgs)) {
543
- return [
544
- processRequirement(innerHead, 'inspect', segment, ['process.inspect.rails_read_only']),
545
- ];
546
- }
547
- if (isMetadataOnlyArgv(innerArgs)) {
548
- return [
549
- processRequirement(innerHead, 'inspect', segment, [
550
- 'process.inspect.bundle_exec_metadata',
551
- ]),
552
- ];
553
- }
554
- const bundleExecInner = decodeBundleExecInner(innerHead, innerArgs, segment);
555
- if (bundleExecInner) {
556
- return bundleExecInner;
557
- }
558
- }
559
- return null;
560
- }
561
- if ((head === 'ruby' || head === 'yarn') && args.length >= 1 && isMetadataOnlyArgv(args)) {
562
- return [processRequirement(head, 'inspect', segment, ['process.inspect.runtime_metadata'])];
563
- }
564
- if (head === 'make' && args.includes('-n')) {
565
- return [processRequirement(head, 'inspect', segment, ['process.inspect.make_dry_run'])];
566
- }
567
- const base = executableBaseName(head);
568
- if ((base === 'rails' || base === 'rake') && railsReadOnlySubcommand(args)) {
569
- return [processRequirement(head, 'inspect', segment, ['process.inspect.rails_read_only'])];
570
- }
571
- return null;
572
- }
573
- function decodeSetBuiltin(args) {
574
- let index = 0;
575
- while (index < args.length) {
576
- const arg = args[index] ?? '';
577
- if (arg === '--') {
578
- index += 1;
579
- continue;
580
- }
581
- if (arg === '-o' || arg === '+o') {
582
- if (!args[index + 1]) {
583
- return false;
584
- }
585
- index += 2;
586
- continue;
587
- }
588
- if (/^[-+][A-Za-z0-9]+$/.test(arg)) {
589
- index += 1;
590
- continue;
591
- }
592
- return false;
593
- }
594
- return true;
595
- }
596
- function decodeShellControlBuiltin(head, args) {
597
- if (head === 'set') {
598
- return decodeSetBuiltin(args) ? [] : null;
599
- }
600
- if (head === 'wait') {
601
- if (args.length === 0 || args.every((arg) => /^\d+$/.test(arg))) {
602
- return [];
603
- }
604
- return null;
605
- }
606
- if (head === 'exit') {
607
- if (args.length === 0 || (args.length === 1 && /^-?\d+$/.test(args[0] ?? ''))) {
608
- return [];
609
- }
610
- return null;
611
- }
612
- return null;
613
- }
614
- function decodeDockerComposeRun(head, args, segment) {
615
- let composeArgs = null;
616
- let command = head;
617
- if (head === 'docker-compose') {
618
- composeArgs = args;
619
- }
620
- else if (head === 'docker' && args[0] === 'compose') {
621
- composeArgs = args.slice(1);
622
- command = 'docker';
623
- }
624
- if (!composeArgs) {
625
- return null;
626
- }
627
- if (composeArgs.includes('run')) {
628
- return [processRequirement(command, 'spawn', segment, ['process.docker_compose_run'])];
629
- }
630
- return unsupportedProcess(command, segment, 'process.docker_compose_grammar_incomplete');
631
- }
632
- function decodeProcessOrFilesystem(params) {
633
- const { tokens, head, env, cwd, repoRoot, segment } = params;
634
- const args = tokens.slice(1);
635
- if (tokens.length > 0 && tokens.every((token) => ENV_PREFIX_PATTERN.test(token))) {
636
- return [];
637
- }
638
- const shellControl = decodeShellControlBuiltin(head, args);
639
- if (shellControl) {
640
- return shellControl;
641
- }
642
- if (isCommandInspection(tokens)) {
643
- return [processRequirement(head, 'inspect', segment, ['process.inspect.command_lookup'])];
644
- }
645
- if (head === 'lsof') {
646
- return validLsof(args)
647
- ? [processRequirement(head, 'inspect', segment, ['process.inspect.lsof'])]
648
- : unsupportedProcess(head, segment, 'process.lsof_grammar_incomplete');
649
- }
650
- if (head === 'ps') {
651
- return validPs(args)
652
- ? [processRequirement(head, 'inspect', segment, ['process.inspect.ps'])]
653
- : unsupportedProcess(head, segment, 'process.ps_grammar_incomplete');
654
- }
655
- if (head === 'test' || head === '[') {
656
- return validFilesystemTest(head, args)
657
- ? [processRequirement(head, 'inspect', segment, ['process.inspect.filesystem_test'])]
658
- : unsupportedProcess(head, segment, 'process.filesystem_test_grammar_incomplete');
659
- }
660
- if (head === 'docker') {
661
- if (args[0] === 'info') {
662
- return validDockerInfo(args.slice(1))
663
- ? [processRequirement(head, 'inspect', segment, ['process.inspect.docker_info'])]
664
- : unsupportedProcess(head, segment, 'process.docker_info_grammar_incomplete');
665
- }
666
- if (args[0] === 'compose' &&
667
- ['up', 'down', 'start', 'stop', 'restart', 'create', 'rm'].includes(args[1] ?? '')) {
668
- return [processRequirement(head, 'spawn', segment, ['service.local_mutation'])];
669
- }
670
- if (args[0] === 'push' ||
671
- (args[0] === 'build' && args.includes('--push')) ||
672
- (args[0] === 'buildx' &&
673
- args[1] === 'build' &&
674
- (args.includes('--push') ||
675
- args.some((arg) => arg.startsWith('--output=type=registry') || arg.startsWith('-o=type=registry'))))) {
676
- return [
677
- processRequirement(head, 'spawn', segment, ['tier0_external', 'docker.remote_mutation']),
678
- requirement('network.connect', 'network.connect', {
679
- kind: 'network',
680
- host: 'registry',
681
- protocol: 'container-registry',
682
- mode: 'mutate',
683
- payload: 'present',
684
- }, segment, ['tier0_external', 'docker.remote_mutation']),
685
- ];
686
- }
687
- if (args[0] === 'run' ||
688
- args[0] === 'create' ||
689
- args[0] === 'pull' ||
690
- args[0] === 'build' ||
691
- (args[0] === 'buildx' && args[1] === 'build')) {
692
- return [
693
- processRequirement(head, 'spawn', segment, ['process.docker_spawn']),
694
- requirement('network.acquire', 'network.connect', {
695
- kind: 'network',
696
- host: 'unknown',
697
- protocol: 'container-registry',
698
- mode: 'ambiguous',
699
- payload: 'none',
700
- }, segment, ['docker.image_acquisition_possible']),
701
- ];
702
- }
703
- return [processRequirement(head, 'spawn', segment, ['process.docker_spawn'])];
704
- }
705
- if (isShellHead(head)) {
706
- const syntax = bashSyntaxTarget(args);
707
- if (syntax) {
708
- return [
709
- processRequirement(head, 'inspect', segment, ['process.inspect.shell_syntax']),
710
- requirement('fs.read', 'fs.read', { kind: 'path', path: path.resolve(cwd, syntax) }, segment, ['shell.syntax_source_read']),
711
- ];
712
- }
713
- return unsupportedProcess(head, segment, 'process.shell_grammar_incomplete');
714
- }
715
- if (head === 'prisma') {
716
- return decodePrisma(args, env, repoRoot, segment);
717
- }
718
- const runtimeMetadata = decodeRuntimeMetadataProcess(head, args, segment);
719
- if (runtimeMetadata) {
720
- return runtimeMetadata;
721
- }
722
- if (head === 'ruby') {
723
- return decodeRuby(args, cwd, repoRoot, segment);
724
- }
725
- if (head === 'rubocop' || head === 'rspec') {
726
- const decoded = decodeBundleExecInner(head, args, segment);
727
- if (decoded) {
728
- return decoded;
729
- }
730
- }
731
- const dockerCompose = decodeDockerComposeRun(head, args, segment);
732
- if (dockerCompose) {
733
- return dockerCompose;
734
- }
735
- if ((head === 'npm' || head === 'pnpm') && args.length === 1 && isMetadataOnlyArgv(args)) {
736
- return [processRequirement(head, 'inspect', segment, ['process.inspect.package_manager'])];
737
- }
738
- if (head === 'belay') {
739
- return decodeBelay(args, repoRoot, segment);
740
- }
741
- if (head === 'tsc') {
742
- return decodeTsc(args, cwd, segment);
743
- }
744
- if (head === 'go') {
745
- return decodeGo(args, segment);
746
- }
747
- if (head === 'rsync') {
748
- return decodeRsync(args, cwd, segment);
749
- }
750
- if (head === 'sed') {
751
- return decodeSed(args, cwd, segment);
752
- }
753
- if (head === 'base64' && args.length === 1 && ['-d', '-D', '--decode'].includes(args[0] ?? '')) {
754
- return [processRequirement(head, 'inspect', segment, ['process.inspect.base64_stdin'])];
755
- }
756
- if (head === 'node') {
757
- return decodeNode(args, cwd, segment);
758
- }
759
- if (head === 'vite' || head === 'vite-node') {
760
- return [processRequirement(head, 'spawn', segment, ['process.local_dev_spawn'])];
761
- }
762
- if (((head === 'npm' || head === 'pnpm') && args[0] === 'publish') ||
763
- (head === 'terraform' && args[0] === 'apply')) {
764
- return [
765
- processRequirement(head, 'spawn', segment, ['tier0_external']),
766
- requirement('network.connect', 'network.connect', {
767
- kind: 'network',
768
- host: 'control-plane',
769
- protocol: head,
770
- mode: 'mutate',
771
- payload: 'present',
772
- }, segment, ['tier0_external']),
773
- ];
774
- }
775
- if (head === 'cd') {
776
- return [];
777
- }
778
- if (head === 'cp' || head === 'mv') {
779
- return decodeCopyMove(head, args, cwd, segment);
780
- }
781
- if (head === 'rm') {
782
- return decodeRm(args, cwd, repoRoot, segment);
783
- }
784
- if (head === 'vitest' || head === 'eslint' || head === 'biome' || head === 'belay') {
785
- return [processRequirement(head, 'spawn', segment, ['process.known_local_spawn'])];
786
- }
787
- if (head === 'pwd' || head === 'which' || head === 'whoami') {
788
- return [processRequirement(head, 'inspect', segment, ['process.pure_inspection'])];
789
- }
790
- const readOperands = filesystemReadOperands(head, args);
791
- if (readOperands !== null) {
792
- const lowered = [processRequirement(head, 'inspect', segment, ['process.filesystem_inspect'])];
793
- for (const operand of readOperands) {
794
- const resolved = resolvePathOperand(operand, cwd);
795
- lowered.push(requirement('fs.read', 'fs.read', { kind: 'path', path: resolved }, segment, [
796
- 'filesystem.read',
797
- ]));
798
- addSecretRead(lowered, resolved, segment);
799
- }
800
- return lowered;
801
- }
802
- const writeOperands = filesystemWriteOperands(head, args);
803
- if (writeOperands !== null) {
804
- const lowered = [processRequirement(head, 'spawn', segment, ['process.filesystem_mutation'])];
805
- for (const operand of writeOperands) {
806
- addWriteEffects(lowered, resolvePathOperand(operand, cwd), segment, ['filesystem.write']);
807
- }
808
- return lowered;
809
- }
810
- if (head === 'printf' || head === 'echo' || head === 'true' || head === 'false' || head === ':') {
811
- return [];
812
- }
813
- if (!head) {
814
- return [
815
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
816
- 'shell.segment_empty',
817
- ]),
818
- ];
819
- }
820
- return unsupportedProcess(head, segment, 'process.grammar_unknown');
821
- }
822
- function decodeBelay(args, repoRoot, segment) {
823
- const [section, operation, key] = args;
824
- const judgeCommand = section === 'judge' &&
825
- operation !== undefined &&
826
- ['consent', 'list', 'status', 'test', 'use'].includes(operation);
827
- const configRead = section === 'config' &&
828
- (operation === undefined ||
829
- operation === 'list' ||
830
- (operation === 'get' && key?.startsWith('judge.')));
831
- const configJudgeMutation = section === 'config' &&
832
- ((['set', 'unset'].includes(operation ?? '') && key?.startsWith('judge.')) ||
833
- (operation === 'credential' && key === 'mode'));
834
- if (judgeCommand || configRead || configJudgeMutation) {
835
- return [
836
- processRequirement('belay', 'inspect', segment, [
837
- 'belay_control_plane_command',
838
- configJudgeMutation ? 'belay.config_judge_mutation' : 'belay.config_read',
839
- ]),
840
- ];
841
- }
842
- if (section === 'config' && ['set', 'unset', 'credential'].includes(operation ?? '')) {
843
- return [
844
- requirement('control_plane.write', 'control_plane.write', { kind: 'path', path: path.join(repoRoot, '.belay-control-plane') }, segment, ['belay.config_non_judge_mutation']),
845
- ];
846
- }
847
- return [processRequirement('belay', 'spawn', segment, ['process.known_local_spawn'])];
848
- }
849
- function decodeTsc(args, cwd, segment) {
850
- const metadataOnly = args.length > 0 && args.every((arg) => ['--help', '--version', '-h', '-v'].includes(arg));
851
- const requirements = [];
852
- let hasOutput = false;
853
- for (let index = 0; index < args.length; index += 1) {
854
- const arg = args[index] ?? '';
855
- const inline = arg.match(/^--(?:outDir|outFile)=(.+)$/)?.[1];
856
- const separate = (arg === '--outDir' || arg === '--outFile') && args[index + 1] ? args[index + 1] : null;
857
- const output = inline ?? separate;
858
- if (!output) {
859
- continue;
860
- }
861
- hasOutput = true;
862
- addWriteEffects(requirements, resolvePathOperand(output, cwd), segment, ['tsc.output']);
863
- if (separate) {
864
- index += 1;
865
- }
866
- }
867
- const typecheckOnly = args.includes('--noEmit') && !hasOutput;
868
- const inspect = metadataOnly || typecheckOnly;
869
- requirements.unshift(processRequirement('tsc', inspect ? 'inspect' : 'spawn', segment, [
870
- metadataOnly
871
- ? 'process.inspect.tsc_metadata'
872
- : typecheckOnly
873
- ? 'process.inspect.tsc_typecheck'
874
- : 'process.known_local_spawn',
875
- ]));
876
- return requirements;
877
- }
878
- function decodeCopyMove(head, args, cwd, segment) {
879
- const requirements = [processRequirement(head, 'spawn', segment, ['process.filesystem_mutation'])];
880
- const operands = [];
881
- let targetDirectory = null;
882
- let recursive = false;
883
- let incomplete = false;
884
- let optionsEnded = false;
885
- const neutralFlags = new Set([
886
- '-f',
887
- '-i',
888
- '-n',
889
- '-p',
890
- '-v',
891
- '--force',
892
- '--interactive',
893
- '--no-clobber',
894
- ]);
895
- for (let index = 0; index < args.length; index += 1) {
896
- const arg = args[index] ?? '';
897
- if (!optionsEnded && arg === '--') {
898
- optionsEnded = true;
899
- continue;
900
- }
901
- if (!optionsEnded && (arg === '-r' || arg === '-R' || arg === '--recursive')) {
902
- recursive = true;
903
- continue;
904
- }
905
- if (!optionsEnded && (arg === '-t' || arg === '--target-directory')) {
906
- const value = args[index + 1];
907
- if (!value || value.startsWith('-')) {
908
- incomplete = true;
909
- }
910
- else {
911
- targetDirectory = value;
912
- index += 1;
913
- }
914
- continue;
915
- }
916
- if (!optionsEnded && arg.startsWith('--target-directory=')) {
917
- targetDirectory = arg.slice('--target-directory='.length) || null;
918
- incomplete ||= !targetDirectory;
919
- continue;
920
- }
921
- if (!optionsEnded && arg.startsWith('-')) {
922
- if (!neutralFlags.has(arg)) {
923
- incomplete = true;
924
- }
925
- continue;
926
- }
927
- operands.push(arg);
928
- }
929
- const sources = targetDirectory ? operands : operands.slice(0, -1);
930
- const destination = targetDirectory ?? (operands.length >= 2 ? (operands.at(-1) ?? null) : null);
931
- if (sources.length === 0 || !destination) {
932
- incomplete = true;
933
- }
934
- for (const source of sources) {
935
- const resolved = resolvePathOperand(source, cwd);
936
- requirements.push(requirement('fs.read', 'fs.read', { kind: 'path', path: resolved }, segment, [
937
- `${head}.source_read`,
938
- ]));
939
- addSecretRead(requirements, resolved, segment);
940
- if (head === 'mv') {
941
- addWriteEffects(requirements, resolved, segment, ['mv.source_remove']);
942
- }
943
- }
944
- if (destination) {
945
- addWriteEffects(requirements, resolvePathOperand(destination, cwd), segment, [
946
- `${head}.destination_write`,
947
- ]);
948
- }
949
- if (recursive || incomplete) {
950
- requirements.push(requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
951
- recursive ? `${head}.recursive_source` : `${head}.grammar_incomplete`,
952
- ]));
953
- }
954
- return requirements;
955
- }
956
- function decodeRm(args, cwd, repoRoot, segment) {
957
- const requirements = [processRequirement('rm', 'spawn', segment, ['process.filesystem_mutation'])];
958
- const recursive = args.some((arg) => arg === '-r' || arg === '-R' || arg === '--recursive' || /^-[^-]*[rR]/.test(arg));
959
- const operands = args.filter((arg) => arg && !arg.startsWith('-'));
960
- if (operands.length === 0) {
961
- return [
962
- ...requirements,
963
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
964
- 'rm.operand_missing',
965
- ]),
966
- ];
967
- }
968
- const identity = inspectGitResourceIdentity(repoRoot);
969
- for (const operand of operands) {
970
- const resolved = resolvePathOperand(operand, cwd);
971
- addWriteEffects(requirements, resolved, segment, ['filesystem.write', 'rm.remove']);
972
- const finalOperandIsSymlink = isSymbolicLink(resolved);
973
- const canonicalResolved = canonicalRmOperand(resolved, finalOperandIsSymlink);
974
- const targetIdentity = recursive && !finalOperandIsSymlink
975
- ? inspectGitResourceIdentity(resolved)
976
- : { status: 'absent' };
977
- const sameIdentityTargetRoot = identity.status === 'resolved' &&
978
- targetIdentity.status === 'resolved' &&
979
- identity.identity.commonDir === targetIdentity.identity.commonDir &&
980
- pathContains(canonicalResolved, targetIdentity.identity.repositoryRoot);
981
- if (recursive &&
982
- identity.status === 'resolved' &&
983
- (sameIdentityTargetRoot ||
984
- [
985
- repoRoot,
986
- identity.identity.repositoryRoot,
987
- identity.identity.gitDir,
988
- identity.identity.commonDir,
989
- identity.identity.gitEntryPath,
990
- ].some((metadataPath) => pathContains(canonicalResolved, metadataPath)))) {
991
- const gitEntryPath = targetIdentity.status === 'resolved'
992
- ? targetIdentity.identity.gitEntryPath
993
- : identity.identity.gitEntryPath;
994
- requirements.push(requirement('control_plane.write', 'control_plane.write', { kind: 'path', path: gitEntryPath }, segment, ['rm.recursive_git_boundary', 'tier1_catastrophic']));
995
- }
996
- }
997
- return requirements;
998
- }
999
- function canonicalRmOperand(targetPath, finalOperandIsSymlink) {
1000
- try {
1001
- if (finalOperandIsSymlink) {
1002
- return path.join(realpathSync.native(path.dirname(targetPath)), path.basename(targetPath));
1003
- }
1004
- return realpathSync.native(targetPath);
1005
- }
1006
- catch {
1007
- return path.resolve(targetPath);
1008
- }
1009
- }
1010
- function isSymbolicLink(targetPath) {
1011
- try {
1012
- return lstatSync(targetPath).isSymbolicLink();
1013
- }
1014
- catch {
1015
- return false;
1016
- }
1017
- }
1018
- function pathContains(ancestor, candidate) {
1019
- const relative = path.relative(path.resolve(ancestor), path.resolve(candidate));
1020
- return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
1021
- }
1022
- function decodeGo(args, segment) {
1023
- if (['test', 'list', 'vet'].includes(args[0] ?? '')) {
1024
- return [processRequirement('go', 'spawn', segment, ['process.go_local_spawn'])];
1025
- }
1026
- if (args[0] === 'install' || (args[0] === 'mod' && args[1] === 'download')) {
1027
- return [
1028
- processRequirement('go', 'spawn', segment, ['process.go_acquire']),
1029
- requirement('network.acquire', 'network.connect', {
1030
- kind: 'network',
1031
- host: 'unknown',
1032
- protocol: 'go-module',
1033
- mode: 'ambiguous',
1034
- payload: 'none',
1035
- }, segment, ['go.network_acquisition_possible']),
1036
- ];
1037
- }
1038
- return unsupportedProcess('go', segment, 'process.go_grammar_incomplete');
1039
- }
1040
- function decodeRsync(args, cwd, segment) {
1041
- const destructive = args.some((arg) => arg === '--delete' || arg === '--del' || arg.startsWith('--delete-'));
1042
- if (destructive) {
1043
- return [
1044
- processRequirement('rsync', 'spawn', segment, ['rsync_destructive']),
1045
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
1046
- 'rsync_destructive',
1047
- ]),
1048
- ];
1049
- }
1050
- const allowedFlags = new Set([
1051
- '-a',
1052
- '-r',
1053
- '-v',
1054
- '-z',
1055
- '--archive',
1056
- '--delay-updates',
1057
- '--recursive',
1058
- '--verbose',
1059
- ]);
1060
- if (args.some((arg) => arg.startsWith('-') && !allowedFlags.has(arg))) {
1061
- return unsupportedProcess('rsync', segment, 'process.rsync_grammar_incomplete');
1062
- }
1063
- const operands = args.filter((arg) => !arg.startsWith('-'));
1064
- if (operands.length < 2) {
1065
- return unsupportedProcess('rsync', segment, 'process.rsync_operands_incomplete');
1066
- }
1067
- if (operands.some(isRemoteRsyncOperand)) {
1068
- return [
1069
- processRequirement('rsync', 'spawn', segment, ['rsync.remote']),
1070
- requirement('network.connect', 'network.connect', {
1071
- kind: 'network',
1072
- host: 'remote',
1073
- protocol: 'rsync',
1074
- mode: 'mutate',
1075
- payload: 'present',
1076
- }, segment, ['rsync.remote']),
1077
- ];
1078
- }
1079
- const destination = operands.at(-1) ?? '';
1080
- return [
1081
- processRequirement('rsync', 'spawn', segment, ['process.rsync_local']),
1082
- ...operands
1083
- .slice(0, -1)
1084
- .map((operand) => requirement('fs.read', 'fs.read', { kind: 'path', path: resolvePathOperand(operand, cwd) }, segment, ['rsync.local_source'])),
1085
- requirement('fs.write', 'fs.write', { kind: 'path', path: resolvePathOperand(destination, cwd) }, segment, ['rsync.local_destination']),
1086
- ];
1087
- }
1088
- function isRemoteRsyncOperand(operand) {
1089
- return (/^rsync:\/\//i.test(operand) ||
1090
- /^[^/:\s]+::/.test(operand) ||
1091
- /^(?:[^@/:\s]+@)?[^/:\s]+:/.test(operand));
1092
- }
1093
- function decodeSed(args, cwd, segment) {
1094
- let inline = false;
1095
- let scriptConsumed = false;
1096
- const files = [];
1097
- for (let index = 0; index < args.length; index += 1) {
1098
- const arg = args[index] ?? '';
1099
- if (arg === '-n' || arg === '--quiet' || arg === '--silent') {
1100
- continue;
1101
- }
1102
- if (arg === '-i' || arg === '--in-place' || arg.startsWith('-i')) {
1103
- inline = true;
1104
- continue;
1105
- }
1106
- if (arg === '-e' || arg === '--expression') {
1107
- if (!args[index + 1]) {
1108
- return unsupportedProcess('sed', segment, 'process.sed_expression_missing');
1109
- }
1110
- scriptConsumed = true;
1111
- index += 1;
1112
- continue;
1113
- }
1114
- if (arg.startsWith('--expression=')) {
1115
- scriptConsumed = true;
1116
- continue;
1117
- }
1118
- if (arg.startsWith('-')) {
1119
- return unsupportedProcess('sed', segment, 'process.sed_grammar_incomplete');
1120
- }
1121
- if (!scriptConsumed) {
1122
- scriptConsumed = true;
1123
- continue;
1124
- }
1125
- files.push(arg);
1126
- }
1127
- const operation = inline ? 'spawn' : 'inspect';
1128
- const lowered = [processRequirement('sed', operation, segment, ['process.sed'])];
1129
- for (const file of files) {
1130
- lowered.push(requirement(inline ? 'fs.write' : 'fs.read', inline ? 'fs.write' : 'fs.read', { kind: 'path', path: resolvePathOperand(file, cwd) }, segment, [inline ? 'sed.in_place_write' : 'sed.file_read']));
1131
- }
1132
- return lowered;
1133
- }
1134
- function decodeNode(args, cwd, segment) {
1135
- if (args.length > 0 && args.every((arg) => ['--help', '--version', '-h', '-v'].includes(arg))) {
1136
- return [processRequirement('node', 'inspect', segment, ['process.inspect.node_metadata'])];
1137
- }
1138
- if ((args[0] === '--check' || args[0] === '-c') && args.length === 2 && args[1]) {
1139
- return [
1140
- processRequirement('node', 'inspect', segment, ['process.inspect.node_syntax']),
1141
- requirement('fs.read', 'fs.read', { kind: 'path', path: resolvePathOperand(args[1], cwd) }, segment, ['node.syntax_source_read']),
1142
- ];
1143
- }
1144
- return unsupportedProcess('node', segment, 'process.node_grammar_incomplete');
1145
- }
1146
- function decodePrisma(args, env, repoRoot, segment) {
1147
- const processEffect = processRequirement('prisma', 'spawn', segment, ['process.prisma']);
1148
- if (args[0] === 'generate') {
1149
- return [
1150
- processEffect,
1151
- requirement('fs.write', 'fs.write', { kind: 'path', path: repoRoot }, segment, [
1152
- 'prisma.generate.repo_local',
1153
- ]),
1154
- ];
1155
- }
1156
- const databaseMutation = args[0] === 'migrate' || (args[0] === 'db' && args[1] === 'seed') || args[0] === 'seed';
1157
- if (!databaseMutation) {
1158
- return [
1159
- processEffect,
1160
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
1161
- 'prisma.grammar_incomplete',
1162
- ]),
1163
- ];
1164
- }
1165
- const endpoint = databaseEndpoint(env.DATABASE_URL);
1166
- if (!endpoint) {
1167
- return [
1168
- processEffect,
1169
- requirement('network.connect', 'network.connect', {
1170
- kind: 'network',
1171
- host: 'unknown',
1172
- protocol: 'database',
1173
- mode: 'ambiguous',
1174
- payload: 'present',
1175
- }, segment, ['prisma.database_endpoint_unknown']),
1176
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
1177
- 'prisma.database_endpoint_unknown',
1178
- ]),
1179
- ];
1180
- }
1181
- const network = requirement('network.connect', 'network.connect', {
1182
- kind: 'network',
1183
- ...endpoint,
1184
- mode: 'mutate',
1185
- payload: 'present',
1186
- }, segment, [LOOPBACK_HOSTS.has(endpoint.host) ? 'prisma.database_local' : 'prisma.database_remote']);
1187
- if (LOOPBACK_HOSTS.has(endpoint.host)) {
1188
- return [processEffect, network];
1189
- }
1190
- return [
1191
- processEffect,
1192
- network,
1193
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
1194
- 'prisma.remote_database_mutation',
1195
- ]),
1196
- ];
1197
- }
1198
- function addRedirectEffects(requirements, tokens, env, context, segment) {
1199
- for (let index = 0; index < tokens.length; index += 1) {
1200
- const operator = tokens[index] ?? '';
1201
- if (!isRedirectOperator(operator) || isFdDuplication(operator)) {
1202
- continue;
1203
- }
1204
- const rawTarget = tokens[index + 1];
1205
- if (!rawTarget) {
1206
- requirements.push(requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
1207
- 'shell.redirect_target_missing',
1208
- ]));
1209
- continue;
1210
- }
1211
- if (operator.includes('<<')) {
1212
- requirements.push(requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [
1213
- 'shell.heredoc_opaque',
1214
- ]));
1215
- index += 1;
1216
- continue;
1217
- }
1218
- const target = expandKnownVariables(rawTarget, env);
1219
- const resolved = resolvePathOperand(target, context.cwd);
1220
- if (resolved === '/dev/null') {
1221
- index += 1;
1222
- continue;
1223
- }
1224
- if (operator.includes('<')) {
1225
- requirements.push(requirement('fs.read', 'fs.read', { kind: 'path', path: resolved }, segment, [
1226
- 'shell.input_redirect',
1227
- ]));
1228
- addSecretRead(requirements, resolved, segment);
1229
- }
1230
- if (operator.includes('>')) {
1231
- addWriteEffects(requirements, resolved, segment, ['shell.output_redirect']);
1232
- }
1233
- index += 1;
1234
- }
1235
- }
1236
371
  function addSubstitutionEffects(requirements, command, context, segment, signals) {
1237
372
  const inners = structuralSubstitutionInners(command);
1238
373
  for (const inner of inners) {
@@ -1264,580 +399,3 @@ function addSubstitutionEffects(requirements, command, context, segment, signals
1264
399
  }
1265
400
  }
1266
401
  }
1267
- function extractEnvironment(tokens, inherited) {
1268
- const env = { ...inherited };
1269
- const changedNames = new Set();
1270
- if (tokens[0] === 'env') {
1271
- let index = 1;
1272
- let malformed = false;
1273
- while (index < tokens.length) {
1274
- const option = tokens[index] ?? '';
1275
- if (option === '-u' || option === '--unset') {
1276
- const name = tokens[index + 1] ?? '';
1277
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
1278
- malformed = true;
1279
- index += 1;
1280
- break;
1281
- }
1282
- delete env[name];
1283
- changedNames.add(name);
1284
- index += 2;
1285
- continue;
1286
- }
1287
- if (option.startsWith('--unset=')) {
1288
- const name = option.slice('--unset='.length);
1289
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
1290
- malformed = true;
1291
- index += 1;
1292
- break;
1293
- }
1294
- delete env[name];
1295
- changedNames.add(name);
1296
- index += 1;
1297
- continue;
1298
- }
1299
- if (option === '-i' || option === '--ignore-environment') {
1300
- for (const name of Object.keys(env)) {
1301
- delete env[name];
1302
- }
1303
- changedNames.add('*');
1304
- index += 1;
1305
- continue;
1306
- }
1307
- if (option === '--') {
1308
- index += 1;
1309
- break;
1310
- }
1311
- if (option.startsWith('-')) {
1312
- malformed = true;
1313
- index += 1;
1314
- break;
1315
- }
1316
- break;
1317
- }
1318
- while (index < tokens.length) {
1319
- const token = tokens[index] ?? '';
1320
- const match = ENV_PREFIX_PATTERN.exec(token);
1321
- if (!match) {
1322
- break;
1323
- }
1324
- const [, name, value] = match;
1325
- if (name) {
1326
- env[name] = expandKnownVariables(value ?? '', env);
1327
- changedNames.add(name);
1328
- }
1329
- index += 1;
1330
- }
1331
- const commandTokens = tokens.slice(index);
1332
- return {
1333
- env,
1334
- commandTokens,
1335
- malformed: malformed || commandTokens.length === 0,
1336
- changedNames,
1337
- };
1338
- }
1339
- for (const token of tokens) {
1340
- const match = ENV_PREFIX_PATTERN.exec(token);
1341
- if (!match) {
1342
- break;
1343
- }
1344
- const [, name, value] = match;
1345
- if (name) {
1346
- env[name] = expandKnownVariables(value ?? '', env);
1347
- changedNames.add(name);
1348
- }
1349
- }
1350
- return { env, malformed: false, changedNames };
1351
- }
1352
- function expandKnownVariables(token, env) {
1353
- return token.replace(/\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g, (raw, a, b) => {
1354
- const value = env[a ?? b];
1355
- return value === undefined ? raw : value;
1356
- });
1357
- }
1358
- function stripRedirects(tokens) {
1359
- const stripped = [];
1360
- for (let index = 0; index < tokens.length; index += 1) {
1361
- const token = tokens[index] ?? '';
1362
- if (isFdDuplication(token)) {
1363
- continue;
1364
- }
1365
- if (!isRedirectOperator(token)) {
1366
- stripped.push(token);
1367
- continue;
1368
- }
1369
- index += 1;
1370
- }
1371
- return stripped;
1372
- }
1373
- function stripStructuredRedirects(tokens) {
1374
- const stripped = [];
1375
- for (let index = 0; index < tokens.length; index += 1) {
1376
- const token = tokens[index];
1377
- if (!token)
1378
- continue;
1379
- if (isFdDuplication(token.value)) {
1380
- continue;
1381
- }
1382
- if (!isRedirectOperator(token.value)) {
1383
- stripped.push(token);
1384
- continue;
1385
- }
1386
- index += 1;
1387
- }
1388
- return stripped;
1389
- }
1390
- function alignStructuredTokens(tokens, values) {
1391
- if (values.length === 0)
1392
- return [];
1393
- for (let start = tokens.length - values.length; start >= 0; start -= 1) {
1394
- const candidate = tokens.slice(start);
1395
- if (candidate.length === values.length &&
1396
- candidate.every((token, index) => token.value === values[index])) {
1397
- return candidate;
1398
- }
1399
- }
1400
- return [];
1401
- }
1402
- function shellSegment(commandRedacted, segmentHead, requirements, opacity, signals) {
1403
- const normalizedRequirements = requirements.flatMap((entry) => {
1404
- const dynamicSignal = dynamicResourceSignal(entry.resource);
1405
- if (dynamicSignal) {
1406
- return [
1407
- { ...entry, resource: { kind: 'unknown' } },
1408
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, entry.provenance.segment ?? commandRedacted, [...entry.evidence.signals, dynamicSignal]),
1409
- ];
1410
- }
1411
- return [entry];
1412
- });
1413
- const partial = normalizedRequirements.some((entry) => entry.tag === 'indeterminate' || entry.action === 'indeterminate');
1414
- return {
1415
- commandRedacted,
1416
- segmentHead,
1417
- requirements: normalizedRequirements,
1418
- completeness: partial ? 'partial' : 'complete',
1419
- opacity,
1420
- signals: [...signals].sort(),
1421
- };
1422
- }
1423
- const DYNAMIC_SHELL_VALUE_PATTERN = /(?:\$\(|`|\$(?:\d+|[@*#?$!-]|\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*))/;
1424
- const SHELL_GLOB_PATTERN = /[*?[]/;
1425
- function effectChangingEnvironmentSignals(head, env, changedNames) {
1426
- if (head === 'curl' &&
1427
- (env.CURL_HOME ||
1428
- changedNames.has('CURL_HOME') ||
1429
- changedNames.has('HOME') ||
1430
- changedNames.has('XDG_CONFIG_HOME'))) {
1431
- return ['egress.curl.environment_config_override'];
1432
- }
1433
- if (head === 'wget' && (env.WGETRC || changedNames.has('WGETRC') || changedNames.has('HOME'))) {
1434
- return ['egress.wget.environment_config_override'];
1435
- }
1436
- if (head !== 'git') {
1437
- return [];
1438
- }
1439
- const hazardousNames = new Set([
1440
- 'GIT_ASKPASS',
1441
- 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
1442
- 'GIT_CEILING_DIRECTORIES',
1443
- 'GIT_COMMON_DIR',
1444
- 'GIT_CONFIG',
1445
- 'GIT_CONFIG_GLOBAL',
1446
- 'GIT_CONFIG_SYSTEM',
1447
- 'GIT_DIR',
1448
- 'GIT_DISCOVERY_ACROSS_FILESYSTEM',
1449
- 'GIT_EDITOR',
1450
- 'GIT_EXEC_PATH',
1451
- 'GIT_EXTERNAL_DIFF',
1452
- 'GIT_INDEX_FILE',
1453
- 'GIT_NAMESPACE',
1454
- 'GIT_OBJECT_DIRECTORY',
1455
- 'GIT_PAGER',
1456
- 'GIT_PROXY_COMMAND',
1457
- 'GIT_QUARANTINE_PATH',
1458
- 'GIT_SEQUENCE_EDITOR',
1459
- 'GIT_SHALLOW_FILE',
1460
- 'GIT_SSH',
1461
- 'GIT_SSH_COMMAND',
1462
- 'GIT_TEMPLATE_DIR',
1463
- 'GIT_WORK_TREE',
1464
- 'SSH_ASKPASS',
1465
- ]);
1466
- const overridden = changedNames.has('HOME') ||
1467
- changedNames.has('XDG_CONFIG_HOME') ||
1468
- Object.entries(env).some(([name, value]) => {
1469
- if (!value) {
1470
- return false;
1471
- }
1472
- if (hazardousNames.has(name) ||
1473
- /^GIT_CONFIG_(?:KEY|VALUE)_\d+$/.test(name) ||
1474
- /^GIT_TRACE/.test(name)) {
1475
- return true;
1476
- }
1477
- return name === 'GIT_CONFIG_COUNT' && value !== '0';
1478
- });
1479
- return overridden ? ['git.environment_execution_or_config_override'] : [];
1480
- }
1481
- function dynamicResourceSignal(resource) {
1482
- const expanded = (value) => Boolean(value && DYNAMIC_SHELL_VALUE_PATTERN.test(value));
1483
- const pathLike = (value, allowSemanticWildcard = false) => Boolean(value && (expanded(value) || (!allowSemanticWildcard && SHELL_GLOB_PATTERN.test(value))));
1484
- switch (resource.kind) {
1485
- case 'path':
1486
- return pathLike(resource.path) ? 'shell.path_unresolved' : null;
1487
- case 'network':
1488
- return expanded(resource.host) || expanded(resource.protocol)
1489
- ? 'shell.network_resource_unresolved'
1490
- : null;
1491
- case 'git-ref':
1492
- return pathLike(resource.repoPath) || pathLike(resource.ref, resource.ref.endsWith('/*'))
1493
- ? 'shell.git_resource_unresolved'
1494
- : null;
1495
- case 'executable':
1496
- return expanded(resource.command) ? 'shell.executable_unresolved' : null;
1497
- default:
1498
- return null;
1499
- }
1500
- }
1501
- function processRequirement(command, operation, segment, signals) {
1502
- return requirement('process.exec', 'process.exec', { kind: 'executable', command, operation }, segment, signals);
1503
- }
1504
- function unsupportedProcess(command, segment, signal) {
1505
- return [
1506
- processRequirement(command, 'spawn', segment, [signal]),
1507
- requirement('indeterminate', 'indeterminate', { kind: 'unknown' }, segment, [signal]),
1508
- ];
1509
- }
1510
- function addWriteEffects(requirements, resolved, segment, signals) {
1511
- requirements.push(requirement('fs.write', 'fs.write', { kind: 'path', path: resolved }, segment, signals));
1512
- if (CONTROL_PLANE_PATH_PATTERN.test(resolved)) {
1513
- requirements.push(requirement('control_plane.write', 'control_plane.write', { kind: 'path', path: resolved }, segment, [...signals, 'protected_path']));
1514
- }
1515
- }
1516
- function addSecretRead(requirements, resolved, segment) {
1517
- if (SECRET_PATH_PATTERN.test(resolved)) {
1518
- requirements.push(requirement('secret.read', 'secret.read', { kind: 'path', path: resolved }, segment, [
1519
- 'secret_path_read',
1520
- ]));
1521
- }
1522
- }
1523
- function filesystemReadOperands(head, args) {
1524
- switch (head) {
1525
- case 'ls': {
1526
- const operands = args.filter((arg) => arg && !arg.startsWith('-'));
1527
- return operands.length > 0 ? operands : ['.'];
1528
- }
1529
- case 'find':
1530
- return findReadOperands(args);
1531
- case 'cat':
1532
- case 'head':
1533
- case 'tail':
1534
- case 'less':
1535
- case 'stat':
1536
- case 'file':
1537
- case 'du':
1538
- case 'wc':
1539
- return args.filter((arg) => arg && !arg.startsWith('-'));
1540
- case 'grep':
1541
- case 'rg':
1542
- return args.slice(1).filter((arg) => arg && !arg.startsWith('-'));
1543
- case 'jq':
1544
- return args.slice(1).filter((arg) => arg && !arg.startsWith('-'));
1545
- default:
1546
- return null;
1547
- }
1548
- }
1549
- function findReadOperands(args) {
1550
- const mutatingPrimaries = new Set([
1551
- '-delete',
1552
- '-exec',
1553
- '-execdir',
1554
- '-fls',
1555
- '-fprint',
1556
- '-fprintf',
1557
- '-ok',
1558
- '-okdir',
1559
- ]);
1560
- if (args.some((arg) => mutatingPrimaries.has(arg) ||
1561
- [...mutatingPrimaries].some((primary) => arg.startsWith(`${primary}=`)))) {
1562
- return null;
1563
- }
1564
- const paths = [];
1565
- for (const arg of args) {
1566
- if (arg.startsWith('-') || arg === '!' || arg === '(') {
1567
- break;
1568
- }
1569
- paths.push(arg);
1570
- }
1571
- return paths.length > 0 ? paths : ['.'];
1572
- }
1573
- function filesystemWriteOperands(head, args) {
1574
- const positional = args.filter((arg) => arg && !arg.startsWith('-'));
1575
- switch (head) {
1576
- case 'touch':
1577
- case 'mkdir':
1578
- case 'rm':
1579
- case 'truncate':
1580
- case 'chmod':
1581
- return positional;
1582
- case 'cp':
1583
- case 'mv':
1584
- return positional.length > 0 ? [positional[positional.length - 1] ?? ''] : [];
1585
- case 'tee':
1586
- return positional;
1587
- default:
1588
- return null;
1589
- }
1590
- }
1591
- function validLsof(args) {
1592
- const flagsWithoutValues = new Set(['-a', '-b', '-l', '-n', '-P', '-R', '-t', '-V']);
1593
- const flagsWithValues = new Set(['-c', '-d', '-F', '-g', '-p', '-T', '-u', '+d', '+D']);
1594
- for (let index = 0; index < args.length; index += 1) {
1595
- const arg = args[index] ?? '';
1596
- if (flagsWithoutValues.has(arg) || arg === '-i') {
1597
- continue;
1598
- }
1599
- if (flagsWithValues.has(arg)) {
1600
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
1601
- return false;
1602
- }
1603
- index += 1;
1604
- continue;
1605
- }
1606
- if ((/^-[ablnPRtV]+$/.test(arg) &&
1607
- [...arg.slice(1)].every((flag) => 'ablnPRtV'.includes(flag))) ||
1608
- /^-i(?:TCP|UDP)?(?::\d+(?:-\d+)?)?$/.test(arg) ||
1609
- /^-s(?:TCP|UDP):[A-Za-z]+$/.test(arg) ||
1610
- /^-(?:c|d|F|g|p|T|u).+$/.test(arg) ||
1611
- /^\+(?:d|D).+$/.test(arg)) {
1612
- continue;
1613
- }
1614
- return false;
1615
- }
1616
- return true;
1617
- }
1618
- function validPs(args) {
1619
- const bareForms = new Set(['aux', 'ax', 'x', 'u']);
1620
- const flagsWithoutValues = new Set(['-A', '-a', '-d', '-e', '-f', '-j', '-l', '-T', '-x']);
1621
- const flagsWithValues = new Set([
1622
- '--format',
1623
- '--group',
1624
- '--pid',
1625
- '--ppid',
1626
- '--sort',
1627
- '--tty',
1628
- '--user',
1629
- ]);
1630
- for (let index = 0; index < args.length; index += 1) {
1631
- const arg = args[index] ?? '';
1632
- if (bareForms.has(arg) || flagsWithoutValues.has(arg)) {
1633
- continue;
1634
- }
1635
- if (flagsWithValues.has(arg)) {
1636
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
1637
- return false;
1638
- }
1639
- index += 1;
1640
- continue;
1641
- }
1642
- if (/^(?:--format|--group|--pid|--ppid|--sort|--tty|--user)=.+$/.test(arg)) {
1643
- continue;
1644
- }
1645
- if (/^-[^-]/.test(arg)) {
1646
- const noValue = new Set(['A', 'a', 'd', 'e', 'f', 'j', 'l', 'T', 'x']);
1647
- const withValue = new Set(['G', 'g', 'N', 'o', 'p', 't', 'U', 'u']);
1648
- const characters = arg.slice(1);
1649
- let valid = true;
1650
- for (let optionIndex = 0; optionIndex < characters.length; optionIndex += 1) {
1651
- const option = characters[optionIndex] ?? '';
1652
- if (noValue.has(option)) {
1653
- continue;
1654
- }
1655
- if (withValue.has(option)) {
1656
- if (optionIndex === characters.length - 1) {
1657
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
1658
- return false;
1659
- }
1660
- index += 1;
1661
- }
1662
- break;
1663
- }
1664
- valid = false;
1665
- break;
1666
- }
1667
- if (valid) {
1668
- continue;
1669
- }
1670
- }
1671
- return false;
1672
- }
1673
- return true;
1674
- }
1675
- function validFilesystemTest(head, args) {
1676
- const operands = head === '[' && args.at(-1) === ']' ? args.slice(0, -1) : args;
1677
- if (head === '[' && args.at(-1) !== ']') {
1678
- return false;
1679
- }
1680
- return (operands.length === 2 &&
1681
- new Set(['-b', '-c', '-d', '-e', '-f', '-h', '-L', '-r', '-s', '-w', '-x']).has(operands[0] ?? '') &&
1682
- Boolean(operands[1]));
1683
- }
1684
- function validDockerInfo(args) {
1685
- for (let index = 0; index < args.length; index += 1) {
1686
- const arg = args[index] ?? '';
1687
- if (arg === '--help') {
1688
- continue;
1689
- }
1690
- if (arg === '--format' || arg === '-f') {
1691
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
1692
- return false;
1693
- }
1694
- index += 1;
1695
- continue;
1696
- }
1697
- if (arg.startsWith('--format=')) {
1698
- if (arg.slice('--format='.length).length === 0) {
1699
- return false;
1700
- }
1701
- continue;
1702
- }
1703
- return false;
1704
- }
1705
- return true;
1706
- }
1707
- function bashSyntaxTarget(args) {
1708
- let syntaxOnly = false;
1709
- let script = null;
1710
- const shortFlags = new Set('abefhkmnptuvxBCEHPT'.split(''));
1711
- const longFlags = new Set([
1712
- '--debugger',
1713
- '--dump-po-strings',
1714
- '--dump-strings',
1715
- '--help',
1716
- '--login',
1717
- '--noediting',
1718
- '--noprofile',
1719
- '--norc',
1720
- '--posix',
1721
- '--restricted',
1722
- '--verbose',
1723
- '--version',
1724
- ]);
1725
- for (let index = 0; index < args.length; index += 1) {
1726
- const arg = args[index] ?? '';
1727
- if (arg === '-c' || arg === '-lc') {
1728
- return null;
1729
- }
1730
- if (arg === '-O' || arg === '+O' || arg === '--rcfile' || arg === '--init-file') {
1731
- if (!args[index + 1] || isOptionToken(args[index + 1] ?? '')) {
1732
- return null;
1733
- }
1734
- index += 1;
1735
- continue;
1736
- }
1737
- if ((arg.startsWith('-O') && arg.length > 2) ||
1738
- (arg.startsWith('+O') && arg.length > 2) ||
1739
- (arg.startsWith('--rcfile=') && arg.length > '--rcfile='.length) ||
1740
- (arg.startsWith('--init-file=') && arg.length > '--init-file='.length)) {
1741
- continue;
1742
- }
1743
- if (longFlags.has(arg)) {
1744
- continue;
1745
- }
1746
- if (/^[+-][A-Za-z]+$/.test(arg)) {
1747
- const options = arg.slice(1);
1748
- if ([...options].some((option) => !shortFlags.has(option))) {
1749
- return null;
1750
- }
1751
- syntaxOnly ||= options.includes('n');
1752
- continue;
1753
- }
1754
- if (arg === '--') {
1755
- script = args[index + 1] ?? null;
1756
- break;
1757
- }
1758
- if (arg.startsWith('-') || arg.startsWith('+')) {
1759
- return null;
1760
- }
1761
- script = arg;
1762
- break;
1763
- }
1764
- return syntaxOnly ? script : null;
1765
- }
1766
- function isOptionToken(value) {
1767
- return value.startsWith('-') || value.startsWith('+');
1768
- }
1769
- function databaseEndpoint(raw) {
1770
- if (!raw || raw.includes('$')) {
1771
- return null;
1772
- }
1773
- try {
1774
- const url = new URL(raw);
1775
- if (!url.hostname || !url.protocol) {
1776
- return null;
1777
- }
1778
- return {
1779
- host: url.hostname.toLowerCase(),
1780
- protocol: url.protocol.slice(0, -1),
1781
- ...(url.port ? { port: Number(url.port) } : {}),
1782
- };
1783
- }
1784
- catch {
1785
- return null;
1786
- }
1787
- }
1788
- function resolvePathOperand(operand, cwd) {
1789
- if (operand === '~') {
1790
- return process.env.HOME ?? operand;
1791
- }
1792
- if (operand.startsWith('~/')) {
1793
- return path.join(process.env.HOME ?? '~', operand.slice(2));
1794
- }
1795
- return path.resolve(cwd, operand);
1796
- }
1797
- function isShellHead(head) {
1798
- return head === 'bash' || head === 'sh' || head === 'zsh' || head === 'dash' || head === 'fish';
1799
- }
1800
- function pipeToShell(command) {
1801
- return /(?:^|[|;&]\s*)(?:bash|sh|zsh|dash|fish)(?:\s|$)/.test(command) && /\|/.test(command);
1802
- }
1803
- function withInnerProvenance(requirementValue, innerCommand, launcher, outerSegment) {
1804
- const provenance = {
1805
- ...requirementValue.provenance,
1806
- segment: outerSegment,
1807
- innerCommand: requirementValue.provenance.innerCommand ?? innerCommand,
1808
- ...(launcher === 'npm' || launcher === 'pnpm' ? { launcher, phase: 'delegate' } : {}),
1809
- };
1810
- const launcherProvenance = {
1811
- segment: outerSegment,
1812
- innerCommand,
1813
- ...(launcher === 'npm' || launcher === 'pnpm' ? { launcher, phase: 'delegate' } : {}),
1814
- };
1815
- return withProvenances(requirementValue, provenance, launcherProvenance);
1816
- }
1817
- function withProvenances(requirementValue, provenance, contributing) {
1818
- const provenances = [
1819
- ...(requirementValue.provenances ?? [requirementValue.provenance]),
1820
- contributing,
1821
- provenance,
1822
- ].filter((candidate, index, all) => all.findIndex((entry) => JSON.stringify(entry) === JSON.stringify(candidate)) === index);
1823
- return { ...requirementValue, provenance, provenances };
1824
- }
1825
- function ensureRequirement(requirements, candidate) {
1826
- if (!requirements.some((entry) => entry.action === candidate.action &&
1827
- JSON.stringify(entry.resource) === JSON.stringify(candidate.resource))) {
1828
- requirements.push(candidate);
1829
- }
1830
- }
1831
- function requirement(tag, action, resource, segment, signals) {
1832
- return {
1833
- tag,
1834
- action,
1835
- resource,
1836
- evidence: {
1837
- level: tag === 'indeterminate' ? 'indeterminate' : 'certain',
1838
- signals: [...new Set(signals)].sort(),
1839
- basis: ['shell_semantic_lowering'],
1840
- },
1841
- provenance: { segment },
1842
- };
1843
- }