@guilz-dev/belay 0.4.0 → 0.7.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 (240) hide show
  1. package/README.md +27 -2
  2. package/dist/adapters/claude/runtime-entry.js +54 -25
  3. package/dist/adapters/layouts/scope.js +3 -2
  4. package/dist/adapters/shared/gate-runtime.d.ts +8 -2
  5. package/dist/adapters/shared/gate-runtime.js +579 -181
  6. package/dist/bundle/claude-runtime.mjs +13574 -6334
  7. package/dist/bundle/codex-runtime.mjs +13108 -5903
  8. package/dist/bundle/cursor-runtime.mjs +12676 -5476
  9. package/dist/cli.js +80 -2
  10. package/dist/commands/approve.js +34 -10
  11. package/dist/commands/classify-for-report.js +1 -1
  12. package/dist/commands/config.d.ts +8 -0
  13. package/dist/commands/config.js +134 -11
  14. package/dist/commands/doctor.js +87 -1
  15. package/dist/commands/explain.js +11 -1
  16. package/dist/commands/judge.js +3 -2
  17. package/dist/commands/recovery-checkpoints.d.ts +164 -0
  18. package/dist/commands/recovery-checkpoints.js +347 -0
  19. package/dist/commands/revoke.js +19 -7
  20. package/dist/commands/session.d.ts +20 -0
  21. package/dist/commands/session.js +51 -0
  22. package/dist/commands/status.js +7 -0
  23. package/dist/config-io.js +91 -23
  24. package/dist/conformance/guarantee-posture.d.ts +21 -0
  25. package/dist/conformance/guarantee-posture.js +46 -0
  26. package/dist/conformance/guarantee-table.d.ts +1 -0
  27. package/dist/conformance/guarantee-table.js +20 -9
  28. package/dist/conformance/types.d.ts +1 -0
  29. package/dist/core/approval-replay.d.ts +1 -1
  30. package/dist/core/approval-replay.js +4 -1
  31. package/dist/core/approval-service.d.ts +6 -1
  32. package/dist/core/approval-service.js +87 -24
  33. package/dist/core/approval.d.ts +4 -0
  34. package/dist/core/approval.js +19 -5
  35. package/dist/core/audit-types.d.ts +13 -0
  36. package/dist/core/capability/approval-state-mutation.d.ts +32 -0
  37. package/dist/core/capability/approval-state-mutation.js +165 -0
  38. package/dist/core/capability/approval-v3.d.ts +32 -0
  39. package/dist/core/capability/approval-v3.js +114 -0
  40. package/dist/core/capability/attestation.d.ts +16 -0
  41. package/dist/core/capability/attestation.js +62 -0
  42. package/dist/core/capability/boundary-attestation-sign.d.ts +21 -0
  43. package/dist/core/capability/boundary-attestation-sign.js +84 -0
  44. package/dist/core/capability/boundary-driver-container.d.ts +19 -0
  45. package/dist/core/capability/boundary-driver-container.js +189 -0
  46. package/dist/core/capability/boundary-driver.d.ts +29 -0
  47. package/dist/core/capability/boundary-driver.js +44 -0
  48. package/dist/core/capability/boundary-egress.d.ts +21 -0
  49. package/dist/core/capability/boundary-egress.js +109 -0
  50. package/dist/core/capability/boundary-grant-materialize.d.ts +14 -0
  51. package/dist/core/capability/boundary-grant-materialize.js +93 -0
  52. package/dist/core/capability/boundary-profile.d.ts +15 -0
  53. package/dist/core/capability/boundary-profile.js +25 -0
  54. package/dist/core/capability/boundary-run.d.ts +48 -0
  55. package/dist/core/capability/boundary-run.js +51 -0
  56. package/dist/core/capability/boundary-session.d.ts +61 -0
  57. package/dist/core/capability/boundary-session.js +138 -0
  58. package/dist/core/capability/boundary-workspace-mount.d.ts +5 -0
  59. package/dist/core/capability/boundary-workspace-mount.js +68 -0
  60. package/dist/core/capability/capability-request-hash.d.ts +2 -0
  61. package/dist/core/capability/capability-request-hash.js +8 -0
  62. package/dist/core/capability/gate-latency-ratchet.d.ts +7 -0
  63. package/dist/core/capability/gate-latency-ratchet.js +18 -0
  64. package/dist/core/capability/gate-policy-shadow.d.ts +25 -0
  65. package/dist/core/capability/gate-policy-shadow.js +130 -0
  66. package/dist/core/capability/gate-shadow-audit.d.ts +1 -0
  67. package/dist/core/capability/gate-shadow-audit.js +1 -0
  68. package/dist/core/capability/gate-shadow-ratchet.d.ts +14 -0
  69. package/dist/core/capability/gate-shadow-ratchet.js +81 -0
  70. package/dist/core/capability/grant-consumption.d.ts +4 -0
  71. package/dist/core/capability/grant-consumption.js +11 -0
  72. package/dist/core/capability/grant-lease.d.ts +44 -0
  73. package/dist/core/capability/grant-lease.js +243 -0
  74. package/dist/core/capability/grant-loader.d.ts +12 -0
  75. package/dist/core/capability/grant-loader.js +23 -0
  76. package/dist/core/capability/grant-match.d.ts +6 -0
  77. package/dist/core/capability/grant-match.js +96 -0
  78. package/dist/core/capability/grant.d.ts +16 -0
  79. package/dist/core/capability/grant.js +16 -0
  80. package/dist/core/capability/index.d.ts +8 -0
  81. package/dist/core/capability/index.js +7 -0
  82. package/dist/core/capability/limits.d.ts +7 -0
  83. package/dist/core/capability/limits.js +55 -0
  84. package/dist/core/capability/policy-bridge.d.ts +18 -0
  85. package/dist/core/capability/policy-bridge.js +51 -0
  86. package/dist/core/capability/policy-engine.d.ts +77 -0
  87. package/dist/core/capability/policy-engine.js +663 -0
  88. package/dist/core/capability/policy-types.d.ts +20 -0
  89. package/dist/core/capability/policy-types.js +1 -0
  90. package/dist/core/capability/request.d.ts +48 -0
  91. package/dist/core/capability/request.js +1 -0
  92. package/dist/core/capability/resolver.d.ts +4 -0
  93. package/dist/core/capability/resolver.js +4 -0
  94. package/dist/core/classify-subagent.d.ts +2 -1
  95. package/dist/core/classify-subagent.js +38 -2
  96. package/dist/core/classify-tool.js +126 -62
  97. package/dist/core/config.d.ts +34 -3
  98. package/dist/core/config.js +118 -3
  99. package/dist/core/effect-ir/audit.d.ts +9 -0
  100. package/dist/core/effect-ir/audit.js +86 -0
  101. package/dist/core/effect-ir/build.d.ts +31 -0
  102. package/dist/core/effect-ir/build.js +277 -0
  103. package/dist/core/effect-ir/index.d.ts +8 -0
  104. package/dist/core/effect-ir/index.js +7 -0
  105. package/dist/core/effect-ir/normalize.d.ts +4 -0
  106. package/dist/core/effect-ir/normalize.js +104 -0
  107. package/dist/core/effect-ir/package-exec-eval.d.ts +8 -0
  108. package/dist/core/effect-ir/package-exec-eval.js +152 -0
  109. package/dist/core/effect-ir/package-exec.d.ts +29 -0
  110. package/dist/core/effect-ir/package-exec.js +292 -0
  111. package/dist/core/effect-ir/policy.d.ts +13 -0
  112. package/dist/core/effect-ir/policy.js +77 -0
  113. package/dist/core/effect-ir/types.d.ts +55 -0
  114. package/dist/core/effect-ir/types.js +1 -0
  115. package/dist/core/egress/allowlist.d.ts +1 -0
  116. package/dist/core/egress/allowlist.js +8 -0
  117. package/dist/core/egress-approval.js +45 -51
  118. package/dist/core/gate-contract.d.ts +5 -0
  119. package/dist/core/gate-contract.js +5 -0
  120. package/dist/core/gate-engine.js +30 -8
  121. package/dist/core/index.d.ts +3 -1
  122. package/dist/core/index.js +2 -1
  123. package/dist/core/judge-cloud-consent.js +29 -15
  124. package/dist/core/judge-doctor.js +39 -14
  125. package/dist/core/judge-fallback-hints.d.ts +13 -0
  126. package/dist/core/judge-fallback-hints.js +108 -0
  127. package/dist/core/network-endpoint.d.ts +10 -0
  128. package/dist/core/network-endpoint.js +51 -0
  129. package/dist/core/path-utils.d.ts +1 -0
  130. package/dist/core/path-utils.js +5 -2
  131. package/dist/core/process-runner.d.ts +10 -0
  132. package/dist/core/process-runner.js +124 -0
  133. package/dist/core/recover-advice.js +2 -1
  134. package/dist/core/recovery/artifact-store.d.ts +29 -0
  135. package/dist/core/recovery/artifact-store.js +336 -0
  136. package/dist/core/recovery/capability.d.ts +3 -0
  137. package/dist/core/recovery/capability.js +22 -0
  138. package/dist/core/recovery/checkpoint.d.ts +25 -0
  139. package/dist/core/recovery/checkpoint.js +260 -0
  140. package/dist/core/recovery/fail-closed.d.ts +6 -0
  141. package/dist/core/recovery/fail-closed.js +25 -0
  142. package/dist/core/recovery/index.d.ts +5 -0
  143. package/dist/core/recovery/index.js +4 -0
  144. package/dist/core/recovery/operator-guidance.d.ts +7 -0
  145. package/dist/core/recovery/operator-guidance.js +39 -0
  146. package/dist/core/recovery/reconcile.d.ts +3 -0
  147. package/dist/core/recovery/reconcile.js +48 -0
  148. package/dist/core/recovery/resource-identity.d.ts +4 -0
  149. package/dist/core/recovery/resource-identity.js +34 -0
  150. package/dist/core/recovery/restore.d.ts +13 -0
  151. package/dist/core/recovery/restore.js +114 -0
  152. package/dist/core/recovery/snapshot-node.d.ts +27 -0
  153. package/dist/core/recovery/snapshot-node.js +261 -0
  154. package/dist/core/recovery/types.d.ts +104 -0
  155. package/dist/core/recovery/types.js +1 -0
  156. package/dist/core/standing-allow.js +3 -0
  157. package/dist/core/transactional/apply-observed-changes.d.ts +13 -0
  158. package/dist/core/transactional/apply-observed-changes.js +275 -0
  159. package/dist/core/transactional/backend-selector.d.ts +8 -0
  160. package/dist/core/transactional/backend-selector.js +118 -0
  161. package/dist/core/transactional/backend.d.ts +55 -0
  162. package/dist/core/transactional/backend.js +1 -0
  163. package/dist/core/transactional/diff-evaluator.js +4 -1
  164. package/dist/core/transactional/eligibility.js +4 -0
  165. package/dist/core/transactional/file-checkpoint-backend.d.ts +3 -0
  166. package/dist/core/transactional/file-checkpoint-backend.js +278 -0
  167. package/dist/core/transactional/file-checkpoint-git.d.ts +11 -0
  168. package/dist/core/transactional/file-checkpoint-git.js +217 -0
  169. package/dist/core/transactional/file-checkpoint-staging.d.ts +13 -0
  170. package/dist/core/transactional/file-checkpoint-staging.js +54 -0
  171. package/dist/core/transactional/file-clone.d.ts +15 -0
  172. package/dist/core/transactional/file-clone.js +139 -0
  173. package/dist/core/transactional/file-tree-path.d.ts +7 -0
  174. package/dist/core/transactional/file-tree-path.js +50 -0
  175. package/dist/core/transactional/file-tree.d.ts +41 -0
  176. package/dist/core/transactional/file-tree.js +186 -0
  177. package/dist/core/transactional/git-worktree-backend.d.ts +2 -0
  178. package/dist/core/transactional/git-worktree-backend.js +50 -0
  179. package/dist/core/transactional/git-worktree.d.ts +14 -9
  180. package/dist/core/transactional/git-worktree.js +62 -87
  181. package/dist/core/transactional/index.d.ts +11 -0
  182. package/dist/core/transactional/index.js +10 -0
  183. package/dist/core/transactional/reasons.d.ts +1 -0
  184. package/dist/core/transactional/reasons.js +5 -0
  185. package/dist/core/transactional/runner.js +171 -23
  186. package/dist/core/transactional/snapshot-node.d.ts +26 -0
  187. package/dist/core/transactional/snapshot-node.js +87 -0
  188. package/dist/core/transactional/types.d.ts +21 -1
  189. package/dist/core/types.d.ts +30 -3
  190. package/dist/core/verdict/adapter.d.ts +3 -4
  191. package/dist/core/verdict/adapter.js +24 -12
  192. package/dist/core/verdict/cursor-acp-client.d.ts +67 -0
  193. package/dist/core/verdict/cursor-acp-client.js +345 -0
  194. package/dist/core/verdict/egress-classify.d.ts +2 -2
  195. package/dist/core/verdict/egress-classify.js +2 -2
  196. package/dist/core/verdict/git-classifier.d.ts +26 -0
  197. package/dist/core/verdict/git-classifier.js +533 -0
  198. package/dist/core/verdict/judge-audit.js +1 -0
  199. package/dist/core/verdict/judge-baseline.d.ts +1 -1
  200. package/dist/core/verdict/judge-baseline.js +1 -0
  201. package/dist/core/verdict/judge-broker-service.js +12 -0
  202. package/dist/core/verdict/judge-catalog.js +1 -0
  203. package/dist/core/verdict/judge-cli-fingerprint.d.ts +1 -0
  204. package/dist/core/verdict/judge-cli-fingerprint.js +6 -0
  205. package/dist/core/verdict/judge-cli.js +5 -3
  206. package/dist/core/verdict/judge-provider-matrix.js +1 -1
  207. package/dist/core/verdict/judge-runtime-config.d.ts +2 -0
  208. package/dist/core/verdict/judge-runtime-config.js +10 -6
  209. package/dist/core/verdict/judge-session-broker.d.ts +1 -1
  210. package/dist/core/verdict/judge-session-broker.js +10 -5
  211. package/dist/core/verdict/judge-transport.d.ts +5 -1
  212. package/dist/core/verdict/judge-transport.js +24 -14
  213. package/dist/core/verdict/judge.d.ts +2 -15
  214. package/dist/core/verdict/judge.js +5 -68
  215. package/dist/core/verdict/launcher-resolve.d.ts +2 -0
  216. package/dist/core/verdict/launcher-resolve.js +14 -16
  217. package/dist/core/verdict/prescan.d.ts +15 -0
  218. package/dist/core/verdict/prescan.js +68 -0
  219. package/dist/core/verdict/shell-policy.d.ts +26 -0
  220. package/dist/core/verdict/shell-policy.js +40 -0
  221. package/dist/core/verdict/shell-semantics.d.ts +24 -0
  222. package/dist/core/verdict/shell-semantics.js +225 -0
  223. package/dist/core/verdict/types.d.ts +18 -1
  224. package/dist/core/verdict/verdict.js +313 -178
  225. package/dist/corpus/evaluate.js +3 -7
  226. package/dist/corpus/gate-latency-budget.d.ts +39 -0
  227. package/dist/corpus/gate-latency-budget.js +33 -0
  228. package/dist/corpus/mutators.d.ts +1 -1
  229. package/dist/corpus/mutators.js +11 -0
  230. package/dist/corpus/runtime-match.js +1 -3
  231. package/dist/corpus/standing-allow-catalog.generated.js +29 -9
  232. package/dist/judge-broker-daemon.js +15 -1
  233. package/dist/services/sandbox-service.d.ts +4 -0
  234. package/dist/services/sandbox-service.js +28 -2
  235. package/dist/types.d.ts +13 -2
  236. package/dist/version.d.ts +1 -1
  237. package/dist/version.js +1 -1
  238. package/package.json +1 -1
  239. package/skills/belay/SKILL.md +8 -3
  240. package/skills/belay/belay-approve.md +10 -7
@@ -0,0 +1,104 @@
1
+ import { canonicalStringify, hashValue } from '../fingerprint.js';
2
+ export function normalizeEffectTags(tags) {
3
+ const set = new Set(tags);
4
+ if (set.has('read_only') && set.size > 1) {
5
+ throw new Error('read_only must not co-occur with other effect tags');
6
+ }
7
+ return [...set].sort();
8
+ }
9
+ export function mergeRequirements(...groups) {
10
+ const seen = new Map();
11
+ for (const group of groups) {
12
+ for (const req of group) {
13
+ const key = `${req.action}:${req.resource.kind}:${resourceKey(req.resource)}`;
14
+ const existing = seen.get(key);
15
+ if (!existing) {
16
+ seen.set(key, {
17
+ ...req,
18
+ provenances: uniqueProvenances(req.provenances ?? [req.provenance]),
19
+ });
20
+ continue;
21
+ }
22
+ const strongest = evidenceRank(req.evidence.level) > evidenceRank(existing.evidence.level) ? req : existing;
23
+ seen.set(key, {
24
+ ...strongest,
25
+ evidence: {
26
+ level: strongest.evidence.level,
27
+ signals: [...new Set([...existing.evidence.signals, ...req.evidence.signals])],
28
+ basis: [...new Set([...existing.evidence.basis, ...req.evidence.basis])],
29
+ },
30
+ provenances: uniqueProvenances([
31
+ ...(existing.provenances ?? [existing.provenance]),
32
+ ...(req.provenances ?? [req.provenance]),
33
+ ]),
34
+ });
35
+ }
36
+ }
37
+ return [...seen.values()];
38
+ }
39
+ export function mergeEffectPlans(...plans) {
40
+ const present = plans.filter((plan) => plan !== null && plan !== undefined);
41
+ if (!present.length) {
42
+ return undefined;
43
+ }
44
+ if (present.length === 1) {
45
+ return present[0];
46
+ }
47
+ const children = present.flatMap((plan) => plan.root.kind === 'merge' ? [...plan.root.children] : [plan.root]);
48
+ const opacity = present.some((plan) => plan.opacity === 'unparseable')
49
+ ? 'unparseable'
50
+ : present.some((plan) => plan.opacity === 'opaque')
51
+ ? 'opaque'
52
+ : present.some((plan) => plan.opacity === 'recursive')
53
+ ? 'recursive'
54
+ : 'transparent';
55
+ const fingerprints = present.map((plan) => plan.inputFingerprint).sort();
56
+ return {
57
+ version: 1,
58
+ root: { kind: 'merge', children },
59
+ inputFingerprint: hashValue(`effect-plan-input:v1:${canonicalStringify(fingerprints)}`),
60
+ opacity,
61
+ disposition: present.some((plan) => plan.disposition === 'effects') ? 'effects' : 'effect_free',
62
+ completeness: present.every((plan) => plan.completeness === 'complete')
63
+ ? 'complete'
64
+ : 'partial',
65
+ signals: [...new Set(present.flatMap((plan) => [...plan.signals]))].sort(),
66
+ };
67
+ }
68
+ function uniqueProvenances(provenances) {
69
+ const seen = new Map();
70
+ for (const provenance of provenances) {
71
+ seen.set(canonicalStringify(provenance), provenance);
72
+ }
73
+ return [...seen.values()];
74
+ }
75
+ function resourceKey(resource) {
76
+ switch (resource.kind) {
77
+ case 'path':
78
+ return resource.path;
79
+ case 'network':
80
+ return `${resource.host}:${resource.port ?? ''}:${resource.protocol ?? ''}`;
81
+ case 'executable':
82
+ return resource.command;
83
+ case 'package-cache':
84
+ return resource.manager;
85
+ case 'git-ref':
86
+ return resource.ref;
87
+ case 'unknown':
88
+ return 'unknown';
89
+ default:
90
+ return 'unknown';
91
+ }
92
+ }
93
+ function evidenceRank(level) {
94
+ switch (level) {
95
+ case 'certain':
96
+ return 3;
97
+ case 'possible':
98
+ return 2;
99
+ case 'indeterminate':
100
+ return 1;
101
+ default:
102
+ return 0;
103
+ }
104
+ }
@@ -0,0 +1,8 @@
1
+ import type { InternalSegmentVerdict, VerdictContext } from '../verdict/types.js';
2
+ export interface PackageExecEvalParams {
3
+ command: string;
4
+ peeled: string[];
5
+ context: VerdictContext;
6
+ evaluateInner: (innerRecipe: string) => Promise<InternalSegmentVerdict>;
7
+ }
8
+ export declare function evaluatePackageExecSegment(params: PackageExecEvalParams): Promise<InternalSegmentVerdict>;
@@ -0,0 +1,152 @@
1
+ import { policyDecisionToLegacyReason } from '../capability/policy-bridge.js';
2
+ import { policyDecisionRequiresAsk } from '../capability/policy-engine.js';
3
+ import { cwdRelative } from '../verdict/containment.js';
4
+ import { verdictFingerprint } from '../verdict/fingerprint.js';
5
+ import { redactCommand } from '../verdict/parser.js';
6
+ import { buildEffectPlan } from './build.js';
7
+ import { innerRecipeFromPeel, peelPackageExecArgv } from './package-exec.js';
8
+ import { capabilityRequestsToEffectRequirements, evaluateEffectPlanPolicy } from './policy.js';
9
+ function askFromEffectPlan(params) {
10
+ const { capabilityRequests, authorizationDecision, decisions } = evaluateEffectPlanPolicy(params.plan, params.context);
11
+ const legacyReason = policyDecisionRequiresAsk(authorizationDecision)
12
+ ? policyDecisionToLegacyReason(authorizationDecision)
13
+ : params.reason;
14
+ return {
15
+ permission: 'ask',
16
+ location: params.inner?.location ?? 'unknown',
17
+ opacity: params.plan.opacity,
18
+ effect: params.inner?.effect ?? 'unknown',
19
+ confidence: 'deterministic',
20
+ reason: legacyReason,
21
+ signals: [...new Set([...params.signals, ...params.plan.signals])],
22
+ capabilityRequests,
23
+ authorizationDecision,
24
+ effectPlan: params.plan,
25
+ effectPlanPolicyDecisions: decisions,
26
+ };
27
+ }
28
+ function allowFromEffectPlan(params) {
29
+ const { capabilityRequests, authorizationDecision, decisions } = evaluateEffectPlanPolicy(params.plan, params.context);
30
+ return {
31
+ ...params.inner,
32
+ opacity: params.plan.opacity,
33
+ signals: [...new Set([...params.inner.signals, ...params.signals, ...params.plan.signals])],
34
+ capabilityRequests,
35
+ authorizationDecision,
36
+ effectPlan: params.plan,
37
+ effectPlanPolicyDecisions: decisions,
38
+ };
39
+ }
40
+ export async function evaluatePackageExecSegment(params) {
41
+ const peel = peelPackageExecArgv(params.peeled);
42
+ if (!peel) {
43
+ return {
44
+ permission: 'ask',
45
+ location: 'unknown',
46
+ opacity: 'opaque',
47
+ effect: 'unknown',
48
+ confidence: 'deterministic',
49
+ reason: 'launcher_unresolved',
50
+ signals: ['launcher_unresolved'],
51
+ };
52
+ }
53
+ const relative = cwdRelative(params.context.repoRoot, params.context.cwd);
54
+ const inputFingerprint = verdictFingerprint(relative, redactCommand(params.command));
55
+ if (peel.opaque) {
56
+ const plan = buildEffectPlan({
57
+ tokens: params.peeled,
58
+ cwd: params.context.cwd,
59
+ repoRoot: params.context.repoRoot,
60
+ inputFingerprint,
61
+ }) ??
62
+ {
63
+ version: 1,
64
+ root: { kind: 'merge', children: [] },
65
+ inputFingerprint,
66
+ opacity: 'opaque',
67
+ disposition: 'effects',
68
+ completeness: 'partial',
69
+ signals: [peel.reason],
70
+ };
71
+ return askFromEffectPlan({
72
+ plan,
73
+ context: params.context,
74
+ reason: peel.reason,
75
+ signals: [peel.reason, ...peel.signals],
76
+ });
77
+ }
78
+ const innerRecipe = innerRecipeFromPeel(peel);
79
+ let innerVerdict = null;
80
+ let innerRequirements;
81
+ if (innerRecipe) {
82
+ innerVerdict = await params.evaluateInner(innerRecipe);
83
+ if (innerVerdict.capabilityRequests?.length) {
84
+ innerRequirements = capabilityRequestsToEffectRequirements(innerVerdict.capabilityRequests);
85
+ }
86
+ else if (innerVerdict.permission === 'allow' && innerVerdict.effect === 'read_only') {
87
+ innerRequirements = [];
88
+ }
89
+ }
90
+ const plan = buildEffectPlan({
91
+ tokens: params.peeled,
92
+ cwd: params.context.cwd,
93
+ repoRoot: params.context.repoRoot,
94
+ inputFingerprint,
95
+ innerRequirements,
96
+ });
97
+ if (!plan) {
98
+ return {
99
+ permission: 'ask',
100
+ location: innerVerdict?.location ?? 'unknown',
101
+ opacity: 'opaque',
102
+ effect: innerVerdict?.effect ?? 'unknown',
103
+ confidence: 'deterministic',
104
+ reason: peel.reason,
105
+ signals: [peel.reason, ...peel.signals],
106
+ capabilityRequests: innerVerdict?.capabilityRequests,
107
+ authorizationDecision: innerVerdict?.authorizationDecision,
108
+ };
109
+ }
110
+ const { capabilityRequests, authorizationDecision, decisions } = evaluateEffectPlanPolicy(plan, params.context);
111
+ if (policyDecisionRequiresAsk(authorizationDecision)) {
112
+ return askFromEffectPlan({
113
+ plan,
114
+ context: params.context,
115
+ reason: policyDecisionToLegacyReason(authorizationDecision),
116
+ signals: [peel.reason, ...peel.signals],
117
+ inner: innerVerdict ?? undefined,
118
+ });
119
+ }
120
+ if (innerVerdict?.permission === 'ask') {
121
+ return {
122
+ ...innerVerdict,
123
+ opacity: plan.opacity,
124
+ signals: [...new Set([...innerVerdict.signals, peel.reason, ...plan.signals])],
125
+ capabilityRequests,
126
+ authorizationDecision,
127
+ effectPlan: plan,
128
+ effectPlanPolicyDecisions: decisions,
129
+ };
130
+ }
131
+ if (!innerVerdict) {
132
+ return {
133
+ permission: 'allow',
134
+ location: 'repo_local',
135
+ opacity: plan.opacity,
136
+ effect: 'read_only',
137
+ confidence: 'deterministic',
138
+ reason: peel.reason,
139
+ signals: [peel.reason, ...plan.signals],
140
+ capabilityRequests,
141
+ authorizationDecision,
142
+ effectPlan: plan,
143
+ effectPlanPolicyDecisions: decisions,
144
+ };
145
+ }
146
+ return allowFromEffectPlan({
147
+ plan,
148
+ context: params.context,
149
+ inner: innerVerdict,
150
+ signals: [peel.reason, ...peel.signals],
151
+ });
152
+ }
@@ -0,0 +1,29 @@
1
+ import type { PackageExecLauncher } from './types.js';
2
+ export interface PackageExecPeelResult {
3
+ launcher: PackageExecLauncher;
4
+ innerTokens: string[];
5
+ acquisitionSpecs: string[];
6
+ opaque: boolean;
7
+ reason: string;
8
+ forceAcquire: boolean;
9
+ signals: string[];
10
+ }
11
+ export type PackageAcquisitionSource = {
12
+ kind: 'local';
13
+ } | {
14
+ kind: 'registry';
15
+ } | {
16
+ kind: 'network';
17
+ host: string;
18
+ port?: number;
19
+ protocol: string;
20
+ };
21
+ export declare function isPackageExecLauncher(tokens: string[]): boolean;
22
+ export declare function peelPackageExecArgv(tokens: string[]): PackageExecPeelResult | null;
23
+ export declare function classifyPackageAcquisitionSpec(spec: string): PackageAcquisitionSource;
24
+ export declare function resolveLocalBin(binName: string, cwd: string, repoRoot: string): {
25
+ path: string;
26
+ proven: true;
27
+ } | null;
28
+ export declare function innerRecipeFromPeel(peel: PackageExecPeelResult): string | null;
29
+ export declare function isPackageExecHeadToken(head: string): boolean;
@@ -0,0 +1,292 @@
1
+ import { existsSync, realpathSync, statSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parseNetworkEndpoint } from '../network-endpoint.js';
4
+ import { pathWithinRoot } from '../path-utils.js';
5
+ const READ_ONLY_SUFFIXES = new Set(['--version', '-v', '--help', '-h']);
6
+ function isPackageExecHead(head) {
7
+ return head === 'npx' || head === 'npm' || head === 'pnpm';
8
+ }
9
+ export function isPackageExecLauncher(tokens) {
10
+ const head = tokens[0] ?? '';
11
+ if (head === 'npx') {
12
+ return true;
13
+ }
14
+ if (head === 'npm' && tokens[1] === 'exec') {
15
+ return true;
16
+ }
17
+ if (head === 'pnpm' && tokens[1] === 'dlx') {
18
+ return true;
19
+ }
20
+ return false;
21
+ }
22
+ export function peelPackageExecArgv(tokens) {
23
+ const head = tokens[0] ?? '';
24
+ if (head === 'npx') {
25
+ return peelNpx(tokens);
26
+ }
27
+ if (head === 'npm' && tokens[1] === 'exec') {
28
+ return peelNpmExec(tokens);
29
+ }
30
+ if (head === 'pnpm' && tokens[1] === 'dlx') {
31
+ return peelPnpmDlx(tokens);
32
+ }
33
+ return null;
34
+ }
35
+ function peelNpx(tokens) {
36
+ const signals = ['package_exec:npx'];
37
+ const acquisitionSpecs = [];
38
+ let index = 1;
39
+ let forceAcquire = false;
40
+ while (index < tokens.length) {
41
+ const token = tokens[index] ?? '';
42
+ if (index === 1 && tokens.length === 2 && READ_ONLY_SUFFIXES.has(token)) {
43
+ return {
44
+ launcher: 'npx',
45
+ innerTokens: [token],
46
+ acquisitionSpecs,
47
+ opaque: false,
48
+ reason: 'npx_wrapper_readonly',
49
+ forceAcquire: false,
50
+ signals: [...signals, 'npx_wrapper_readonly'],
51
+ };
52
+ }
53
+ if (token === '--') {
54
+ index += 1;
55
+ break;
56
+ }
57
+ if (token === '-y' || token === '--yes') {
58
+ forceAcquire = true;
59
+ signals.push('npx.force_yes');
60
+ index += 1;
61
+ continue;
62
+ }
63
+ if (token === '-p' || token === '--package') {
64
+ const packageSpec = tokens[index + 1];
65
+ if (!packageSpec) {
66
+ return opaquePackageExec('npx', 'npx_package_missing', signals, acquisitionSpecs);
67
+ }
68
+ acquisitionSpecs.push(packageSpec);
69
+ forceAcquire = true;
70
+ signals.push('npx.package_flag');
71
+ index += 2;
72
+ continue;
73
+ }
74
+ if (token.startsWith('--package=')) {
75
+ acquisitionSpecs.push(token.slice('--package='.length));
76
+ forceAcquire = true;
77
+ signals.push('npx.package_flag');
78
+ index += 1;
79
+ continue;
80
+ }
81
+ if (token === '-c' || token === '--call') {
82
+ return {
83
+ launcher: 'npx',
84
+ innerTokens: [],
85
+ acquisitionSpecs,
86
+ opaque: true,
87
+ reason: 'npx_call_script',
88
+ forceAcquire: true,
89
+ signals: [...signals, 'npx_call_script'],
90
+ };
91
+ }
92
+ if (token.startsWith('-')) {
93
+ return opaquePackageExec('npx', 'npx_unknown_option', [...signals, 'npx_unknown_option'], acquisitionSpecs);
94
+ }
95
+ break;
96
+ }
97
+ const innerTokens = tokens.slice(index);
98
+ if (innerTokens.length === 0) {
99
+ return {
100
+ launcher: 'npx',
101
+ innerTokens: [],
102
+ acquisitionSpecs,
103
+ opaque: true,
104
+ reason: 'npx_target_missing',
105
+ forceAcquire,
106
+ signals: [...signals, 'npx_target_missing'],
107
+ };
108
+ }
109
+ if (/^@[^/]+\/[^/]+/.test(innerTokens[0] ?? '')) {
110
+ if (acquisitionSpecs.length === 0 && innerTokens[0]) {
111
+ acquisitionSpecs.push(innerTokens[0]);
112
+ }
113
+ return opaquePackageExec('npx', 'npx_scoped_package_bin_indeterminate', [...signals, 'npx_scoped_package_bin_indeterminate'], acquisitionSpecs);
114
+ }
115
+ if (requiresExplicitPackageAcquisition(innerTokens[0] ?? '')) {
116
+ forceAcquire = true;
117
+ signals.push('npx.remote_spec');
118
+ }
119
+ if (acquisitionSpecs.length === 0 && innerTokens[0]) {
120
+ acquisitionSpecs.push(innerTokens[0]);
121
+ }
122
+ return {
123
+ launcher: 'npx',
124
+ innerTokens,
125
+ acquisitionSpecs,
126
+ opaque: false,
127
+ reason: 'npx_exec',
128
+ forceAcquire,
129
+ signals,
130
+ };
131
+ }
132
+ function peelNpmExec(tokens) {
133
+ return peelForcedPackageExec(tokens, 2, 'npm', 'npm_exec');
134
+ }
135
+ function peelPnpmDlx(tokens) {
136
+ return peelForcedPackageExec(tokens, 2, 'pnpm', 'pnpm_dlx');
137
+ }
138
+ function peelForcedPackageExec(tokens, startIndex, launcher, reason) {
139
+ const signals = [`package_exec:${reason}`, `${reason}_acquire`];
140
+ const acquisitionSpecs = [];
141
+ let index = startIndex;
142
+ while (index < tokens.length) {
143
+ const token = tokens[index] ?? '';
144
+ if (token === '--') {
145
+ index += 1;
146
+ break;
147
+ }
148
+ if (launcher === 'npm' && (token === '-y' || token === '--yes')) {
149
+ index += 1;
150
+ continue;
151
+ }
152
+ if (launcher === 'npm' && (token === '-p' || token === '--package')) {
153
+ const packageSpec = tokens[index + 1];
154
+ if (!packageSpec) {
155
+ return opaquePackageExec(launcher, `${reason}_package_missing`, signals, acquisitionSpecs);
156
+ }
157
+ acquisitionSpecs.push(packageSpec);
158
+ index += 2;
159
+ continue;
160
+ }
161
+ if (launcher === 'npm' && token.startsWith('--package=')) {
162
+ acquisitionSpecs.push(token.slice('--package='.length));
163
+ index += 1;
164
+ continue;
165
+ }
166
+ if (launcher === 'npm' && (token === '-c' || token === '--call')) {
167
+ return opaquePackageExec(launcher, `${reason}_call_script`, signals, acquisitionSpecs);
168
+ }
169
+ if (token.startsWith('-')) {
170
+ return opaquePackageExec(launcher, `${reason}_unknown_option`, [...signals, `${reason}_unknown_option`], acquisitionSpecs);
171
+ }
172
+ break;
173
+ }
174
+ const innerTokens = tokens.slice(index);
175
+ if (acquisitionSpecs.length === 0 && innerTokens[0]) {
176
+ acquisitionSpecs.push(innerTokens[0]);
177
+ }
178
+ return {
179
+ launcher,
180
+ innerTokens,
181
+ acquisitionSpecs,
182
+ opaque: innerTokens.length === 0,
183
+ reason: innerTokens.length === 0 ? `${reason}_missing` : reason,
184
+ forceAcquire: true,
185
+ signals,
186
+ };
187
+ }
188
+ function opaquePackageExec(launcher, reason, signals, acquisitionSpecs = []) {
189
+ return {
190
+ launcher,
191
+ innerTokens: [],
192
+ acquisitionSpecs,
193
+ opaque: true,
194
+ reason,
195
+ forceAcquire: true,
196
+ signals,
197
+ };
198
+ }
199
+ function requiresExplicitPackageAcquisition(token) {
200
+ if (!token || token.startsWith('-')) {
201
+ return false;
202
+ }
203
+ if (classifyPackageAcquisitionSpec(token).kind !== 'registry') {
204
+ return true;
205
+ }
206
+ if (token.includes('@') && !token.startsWith('@')) {
207
+ return true;
208
+ }
209
+ if (/^@[^/]+\/[^/]+@/.test(token)) {
210
+ return true;
211
+ }
212
+ if (token.startsWith('npm:')) {
213
+ return true;
214
+ }
215
+ return false;
216
+ }
217
+ export function classifyPackageAcquisitionSpec(spec) {
218
+ const normalized = spec.trim();
219
+ if (normalized.startsWith('file:') ||
220
+ normalized.startsWith('git+file:') ||
221
+ normalized.startsWith('link:') ||
222
+ normalized.startsWith('workspace:') ||
223
+ normalized.startsWith('./') ||
224
+ normalized.startsWith('../') ||
225
+ normalized.startsWith('/') ||
226
+ normalized.startsWith('~/') ||
227
+ /^[A-Za-z]:[\\/]/.test(normalized)) {
228
+ return { kind: 'local' };
229
+ }
230
+ const transportSpec = normalized.startsWith('git+') ? normalized.slice(4) : normalized;
231
+ const endpoint = parseNetworkEndpoint(transportSpec, {
232
+ allowHostedGitShorthand: true,
233
+ allowScpStyle: true,
234
+ });
235
+ if (endpoint) {
236
+ return { kind: 'network', ...endpoint };
237
+ }
238
+ if (/^[A-Za-z0-9][A-Za-z0-9._-]*\/[^/]+(?:#.*)?$/.test(normalized)) {
239
+ return { kind: 'network', host: 'github.com', protocol: 'git' };
240
+ }
241
+ if (/(?:\.tgz|\.tar|\.tar\.gz)$/i.test(normalized)) {
242
+ return { kind: 'local' };
243
+ }
244
+ if ((normalized.match(/\//g)?.length ?? 0) > 1) {
245
+ return { kind: 'local' };
246
+ }
247
+ return { kind: 'registry' };
248
+ }
249
+ export function resolveLocalBin(binName, cwd, repoRoot) {
250
+ const base = path.basename(binName);
251
+ if (!base || base === '.' || base === '..') {
252
+ return null;
253
+ }
254
+ let current = path.resolve(cwd);
255
+ const stop = path.resolve(repoRoot);
256
+ if (!pathWithinRoot(stop, current)) {
257
+ return null;
258
+ }
259
+ while (true) {
260
+ const candidate = path.join(current, 'node_modules', '.bin', base);
261
+ if (existsSync(candidate)) {
262
+ try {
263
+ const resolvedCandidate = realpathSync.native(candidate);
264
+ if (!pathWithinRoot(stop, resolvedCandidate) || !statSync(resolvedCandidate).isFile()) {
265
+ return null;
266
+ }
267
+ return { path: resolvedCandidate, proven: true };
268
+ }
269
+ catch {
270
+ return null;
271
+ }
272
+ }
273
+ if (current === stop) {
274
+ break;
275
+ }
276
+ const parent = path.dirname(current);
277
+ if (parent === current) {
278
+ break;
279
+ }
280
+ current = parent;
281
+ }
282
+ return null;
283
+ }
284
+ export function innerRecipeFromPeel(peel) {
285
+ if (peel.opaque || peel.reason === 'npx_wrapper_readonly' || peel.innerTokens.length === 0) {
286
+ return null;
287
+ }
288
+ return peel.innerTokens.join(' ');
289
+ }
290
+ export function isPackageExecHeadToken(head) {
291
+ return isPackageExecHead(head);
292
+ }
@@ -0,0 +1,13 @@
1
+ import type { PolicyDecision } from '../capability/policy-types.js';
2
+ import type { CapabilityRequestV1 } from '../capability/request.js';
3
+ import type { VerdictContext } from '../verdict/types.js';
4
+ import type { EffectPlan, EffectRequirement } from './types.js';
5
+ export declare function capabilityRequestsToEffectRequirements(requests: readonly CapabilityRequestV1[]): EffectRequirement[];
6
+ export declare function capabilityRequestsFromEffectPlan(plan: EffectPlan, context: VerdictContext): CapabilityRequestV1[];
7
+ export declare function evaluateEffectPlanPolicy(plan: EffectPlan, context: VerdictContext): {
8
+ capabilityRequests: CapabilityRequestV1[];
9
+ decisions: PolicyDecision[];
10
+ authorizationDecision: PolicyDecision;
11
+ };
12
+ export declare function effectPlanPolicyRequiresAsk(decision: PolicyDecision): boolean;
13
+ export declare function effectPlanPolicyLegacyReason(decision: PolicyDecision): string;
@@ -0,0 +1,77 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { policyDecisionToLegacyReason } from '../capability/policy-bridge.js';
3
+ import { evaluateCapabilityRequestsPolicy, policyDecisionRequiresAsk, } from '../capability/policy-engine.js';
4
+ import { collectRequirements, flattenRequirementsToCapabilityRequests } from './build.js';
5
+ function shellPrincipal(repoRoot, cwd) {
6
+ return {
7
+ repoRoot,
8
+ sessionHash: createHash('sha256').update(`${repoRoot}:${cwd}`).digest('hex').slice(0, 16),
9
+ };
10
+ }
11
+ function capabilityActionToEffectTag(action) {
12
+ switch (action) {
13
+ case 'fs.read':
14
+ return 'fs.read';
15
+ case 'fs.write':
16
+ return 'fs.write';
17
+ case 'process.exec':
18
+ return 'process.exec';
19
+ case 'network.connect':
20
+ return 'network.connect';
21
+ case 'secret.read':
22
+ return 'secret.read';
23
+ case 'git.ref.write':
24
+ return 'git.ref.write';
25
+ case 'control_plane.write':
26
+ return 'control_plane.write';
27
+ case 'indeterminate':
28
+ return 'indeterminate';
29
+ default:
30
+ return 'indeterminate';
31
+ }
32
+ }
33
+ export function capabilityRequestsToEffectRequirements(requests) {
34
+ return requests.map((request) => ({
35
+ tag: capabilityActionToEffectTag(request.action),
36
+ action: request.action,
37
+ resource: request.resource,
38
+ evidence: {
39
+ level: request.evidence.level,
40
+ signals: [...request.evidence.signals],
41
+ basis: [...request.context.analysisBasis],
42
+ },
43
+ provenance: {
44
+ innerCommand: request.resource.kind === 'executable' ? request.resource.command : undefined,
45
+ },
46
+ provenances: [
47
+ {
48
+ innerCommand: request.resource.kind === 'executable' ? request.resource.command : undefined,
49
+ },
50
+ ],
51
+ }));
52
+ }
53
+ export function capabilityRequestsFromEffectPlan(plan, context) {
54
+ const requirements = collectRequirements(plan.root);
55
+ return flattenRequirementsToCapabilityRequests(requirements, {
56
+ principal: shellPrincipal(context.repoRoot, context.cwd),
57
+ cwd: context.cwd,
58
+ repoRoot: context.repoRoot,
59
+ hookKind: 'shell',
60
+ inputFingerprint: plan.inputFingerprint,
61
+ });
62
+ }
63
+ export function evaluateEffectPlanPolicy(plan, context) {
64
+ const capabilityRequests = capabilityRequestsFromEffectPlan(plan, context);
65
+ const { decisions, decision } = evaluateCapabilityRequestsPolicy(capabilityRequests, context.config, {
66
+ grants: context.grants,
67
+ attestation: context.attestation,
68
+ egressProxyActive: context.egressProxyActive,
69
+ }, context.trustedWorkspaceRoots);
70
+ return { capabilityRequests, decisions, authorizationDecision: decision };
71
+ }
72
+ export function effectPlanPolicyRequiresAsk(decision) {
73
+ return policyDecisionRequiresAsk(decision);
74
+ }
75
+ export function effectPlanPolicyLegacyReason(decision) {
76
+ return policyDecisionToLegacyReason(decision);
77
+ }