@guilz-dev/belay 0.6.0 → 0.8.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 (183) hide show
  1. package/README.md +39 -20
  2. package/dist/adapters/claude/runtime-entry.js +54 -25
  3. package/dist/adapters/shared/gate-runtime.d.ts +7 -0
  4. package/dist/adapters/shared/gate-runtime.js +443 -225
  5. package/dist/bundle/claude-runtime.mjs +13027 -7271
  6. package/dist/bundle/codex-runtime.mjs +12700 -6977
  7. package/dist/bundle/cursor-runtime.mjs +12695 -6977
  8. package/dist/cli.js +37 -1
  9. package/dist/commands/approve.js +34 -10
  10. package/dist/commands/doctor.js +58 -0
  11. package/dist/commands/dogfood.js +1 -1
  12. package/dist/commands/explain.js +2 -2
  13. package/dist/commands/harvest.d.ts +0 -1
  14. package/dist/commands/harvest.js +1 -4
  15. package/dist/commands/metrics.js +6 -0
  16. package/dist/commands/quality.js +1 -3
  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/status.js +7 -0
  21. package/dist/config-io.js +49 -18
  22. package/dist/conformance/guarantee-table.js +137 -9
  23. package/dist/conformance/types.d.ts +2 -0
  24. package/dist/core/approval-replay.d.ts +1 -1
  25. package/dist/core/approval-replay.js +4 -1
  26. package/dist/core/approval-service.d.ts +6 -1
  27. package/dist/core/approval-service.js +86 -27
  28. package/dist/core/approval.d.ts +2 -0
  29. package/dist/core/approval.js +6 -3
  30. package/dist/core/audit-metrics.d.ts +1 -0
  31. package/dist/core/audit-metrics.js +12 -4
  32. package/dist/core/audit-types.d.ts +16 -0
  33. package/dist/core/capability/approval-state-mutation.d.ts +19 -0
  34. package/dist/core/capability/approval-state-mutation.js +76 -3
  35. package/dist/core/capability/approval-v3.d.ts +18 -1
  36. package/dist/core/capability/approval-v3.js +63 -23
  37. package/dist/core/capability/attestation.d.ts +3 -0
  38. package/dist/core/capability/attestation.js +7 -0
  39. package/dist/core/capability/boundary-driver-container.d.ts +12 -0
  40. package/dist/core/capability/boundary-driver-container.js +101 -34
  41. package/dist/core/capability/boundary-driver.js +1 -0
  42. package/dist/core/capability/boundary-grant-materialize.d.ts +1 -0
  43. package/dist/core/capability/boundary-grant-materialize.js +15 -0
  44. package/dist/core/capability/boundary-run.d.ts +25 -0
  45. package/dist/core/capability/boundary-run.js +37 -9
  46. package/dist/core/capability/boundary-session.d.ts +4 -0
  47. package/dist/core/capability/boundary-session.js +9 -0
  48. package/dist/core/capability/boundary-workspace-mount.d.ts +5 -0
  49. package/dist/core/capability/boundary-workspace-mount.js +68 -0
  50. package/dist/core/capability/gate-policy-shadow.js +23 -1
  51. package/dist/core/capability/grant-consumption.d.ts +4 -0
  52. package/dist/core/capability/grant-consumption.js +11 -0
  53. package/dist/core/capability/grant-lease.d.ts +36 -1
  54. package/dist/core/capability/grant-lease.js +213 -16
  55. package/dist/core/capability/grant-match.js +3 -0
  56. package/dist/core/capability/index.d.ts +2 -2
  57. package/dist/core/capability/index.js +1 -1
  58. package/dist/core/capability/policy-engine.d.ts +38 -1
  59. package/dist/core/capability/policy-engine.js +388 -15
  60. package/dist/core/capability/request.d.ts +12 -0
  61. package/dist/core/capability/resolver.d.ts +0 -1
  62. package/dist/core/capability/resolver.js +0 -1
  63. package/dist/core/config.d.ts +20 -0
  64. package/dist/core/config.js +62 -2
  65. package/dist/core/effect-ir/audit.d.ts +9 -0
  66. package/dist/core/effect-ir/audit.js +87 -0
  67. package/dist/core/effect-ir/build.d.ts +31 -0
  68. package/dist/core/effect-ir/build.js +281 -0
  69. package/dist/core/effect-ir/index.d.ts +13 -0
  70. package/dist/core/effect-ir/index.js +9 -0
  71. package/dist/core/effect-ir/normalize.d.ts +4 -0
  72. package/dist/core/effect-ir/normalize.js +110 -0
  73. package/dist/core/effect-ir/package-exec-eval.d.ts +8 -0
  74. package/dist/core/effect-ir/package-exec-eval.js +152 -0
  75. package/dist/core/effect-ir/package-exec.d.ts +29 -0
  76. package/dist/core/effect-ir/package-exec.js +292 -0
  77. package/dist/core/effect-ir/policy.d.ts +14 -0
  78. package/dist/core/effect-ir/policy.js +164 -0
  79. package/dist/core/effect-ir/shell-build.d.ts +43 -0
  80. package/dist/core/effect-ir/shell-build.js +77 -0
  81. package/dist/core/effect-ir/shell-lower.d.ts +14 -0
  82. package/dist/core/effect-ir/shell-lower.js +1509 -0
  83. package/dist/core/effect-ir/types.d.ts +62 -0
  84. package/dist/core/effect-ir/types.js +1 -0
  85. package/dist/core/egress/allowlist.d.ts +1 -0
  86. package/dist/core/egress/allowlist.js +8 -0
  87. package/dist/core/egress-approval.js +45 -51
  88. package/dist/core/gate-contract.d.ts +2 -0
  89. package/dist/core/gate-contract.js +2 -0
  90. package/dist/core/gate-engine.js +82 -25
  91. package/dist/core/git-resource-identity.d.ts +26 -0
  92. package/dist/core/git-resource-identity.js +284 -0
  93. package/dist/core/harvest.d.ts +3 -7
  94. package/dist/core/harvest.js +4 -27
  95. package/dist/core/index.d.ts +5 -1
  96. package/dist/core/index.js +3 -1
  97. package/dist/core/judge-cloud-consent.js +29 -15
  98. package/dist/core/network-endpoint.d.ts +10 -0
  99. package/dist/core/network-endpoint.js +51 -0
  100. package/dist/core/path-utils.js +42 -23
  101. package/dist/core/process-runner.d.ts +10 -0
  102. package/dist/core/process-runner.js +124 -0
  103. package/dist/core/recover-advice.js +2 -1
  104. package/dist/core/recovery/artifact-store.d.ts +29 -0
  105. package/dist/core/recovery/artifact-store.js +336 -0
  106. package/dist/core/recovery/checkpoint.d.ts +25 -0
  107. package/dist/core/recovery/checkpoint.js +260 -0
  108. package/dist/core/recovery/index.d.ts +3 -1
  109. package/dist/core/recovery/index.js +2 -0
  110. package/dist/core/recovery/operator-guidance.d.ts +7 -0
  111. package/dist/core/recovery/operator-guidance.js +39 -0
  112. package/dist/core/recovery/reconcile.d.ts +3 -0
  113. package/dist/core/recovery/reconcile.js +48 -0
  114. package/dist/core/recovery/resource-identity.d.ts +4 -0
  115. package/dist/core/recovery/resource-identity.js +34 -0
  116. package/dist/core/recovery/restore.d.ts +13 -0
  117. package/dist/core/recovery/restore.js +114 -0
  118. package/dist/core/recovery/snapshot-node.d.ts +27 -0
  119. package/dist/core/recovery/snapshot-node.js +261 -0
  120. package/dist/core/recovery/types.d.ts +85 -1
  121. package/dist/core/shell-substitution.d.ts +1 -0
  122. package/dist/core/shell-substitution.js +61 -0
  123. package/dist/core/standing-allow.d.ts +1 -2
  124. package/dist/core/standing-allow.js +3 -20
  125. package/dist/core/transactional/apply-observed-changes.d.ts +13 -0
  126. package/dist/core/transactional/apply-observed-changes.js +275 -0
  127. package/dist/core/transactional/backend-selector.d.ts +8 -0
  128. package/dist/core/transactional/backend-selector.js +118 -0
  129. package/dist/core/transactional/backend.d.ts +55 -0
  130. package/dist/core/transactional/backend.js +1 -0
  131. package/dist/core/transactional/file-checkpoint-backend.d.ts +3 -0
  132. package/dist/core/transactional/file-checkpoint-backend.js +278 -0
  133. package/dist/core/transactional/file-checkpoint-git.d.ts +11 -0
  134. package/dist/core/transactional/file-checkpoint-git.js +217 -0
  135. package/dist/core/transactional/file-checkpoint-staging.d.ts +13 -0
  136. package/dist/core/transactional/file-checkpoint-staging.js +54 -0
  137. package/dist/core/transactional/file-clone.d.ts +15 -0
  138. package/dist/core/transactional/file-clone.js +139 -0
  139. package/dist/core/transactional/file-tree-path.d.ts +7 -0
  140. package/dist/core/transactional/file-tree-path.js +50 -0
  141. package/dist/core/transactional/file-tree.d.ts +41 -0
  142. package/dist/core/transactional/file-tree.js +186 -0
  143. package/dist/core/transactional/git-worktree-backend.d.ts +2 -0
  144. package/dist/core/transactional/git-worktree-backend.js +50 -0
  145. package/dist/core/transactional/git-worktree.d.ts +9 -11
  146. package/dist/core/transactional/git-worktree.js +24 -119
  147. package/dist/core/transactional/index.d.ts +11 -0
  148. package/dist/core/transactional/index.js +10 -0
  149. package/dist/core/transactional/reasons.js +1 -0
  150. package/dist/core/transactional/runner.js +157 -25
  151. package/dist/core/transactional/snapshot-node.d.ts +26 -0
  152. package/dist/core/transactional/snapshot-node.js +87 -0
  153. package/dist/core/transactional/types.d.ts +17 -1
  154. package/dist/core/types.d.ts +13 -1
  155. package/dist/core/verdict/adapter.js +23 -15
  156. package/dist/core/verdict/containment.js +4 -0
  157. package/dist/core/verdict/egress-classify.d.ts +12 -0
  158. package/dist/core/verdict/egress-classify.js +746 -0
  159. package/dist/core/verdict/git-classifier.d.ts +12 -0
  160. package/dist/core/verdict/git-classifier.js +320 -0
  161. package/dist/core/verdict/launcher-resolve.js +79 -37
  162. package/dist/core/verdict/parser.d.ts +2 -0
  163. package/dist/core/verdict/parser.js +95 -1
  164. package/dist/core/verdict/types.d.ts +6 -2
  165. package/dist/core/verdict/verdict.js +103 -935
  166. package/dist/corpus/must-allow-commands.d.ts +2 -1
  167. package/dist/corpus/must-allow-commands.js +2 -1
  168. package/dist/corpus/runtime-match.d.ts +2 -1
  169. package/dist/corpus/runtime-match.js +2 -1
  170. package/dist/corpus/types.d.ts +2 -2
  171. package/dist/templates.js +7 -2
  172. package/dist/types.d.ts +11 -0
  173. package/dist/version.d.ts +1 -1
  174. package/dist/version.js +1 -1
  175. package/package.json +2 -2
  176. package/skills/belay/SKILL.md +8 -3
  177. package/skills/belay/belay-approve.md +10 -7
  178. package/dist/core/verdict/overrides.d.ts +0 -7
  179. package/dist/core/verdict/overrides.js +0 -37
  180. package/dist/core/verdict/shell-policy.d.ts +0 -25
  181. package/dist/core/verdict/shell-policy.js +0 -40
  182. package/dist/corpus/standing-allow-catalog.generated.d.ts +0 -13
  183. package/dist/corpus/standing-allow-catalog.generated.js +0 -99
@@ -0,0 +1,87 @@
1
+ import { canonicalStringify, hashValue } from '../fingerprint.js';
2
+ import { collectRequirements } from './build.js';
3
+ function auditableProvenance(provenance) {
4
+ return {
5
+ ...(provenance.segment ? { segment: provenance.segment } : {}),
6
+ ...(provenance.launcher ? { launcher: provenance.launcher } : {}),
7
+ ...(provenance.phase ? { phase: provenance.phase } : {}),
8
+ };
9
+ }
10
+ function auditableResource(resource) {
11
+ if (resource.kind === 'executable') {
12
+ return {
13
+ kind: 'executable',
14
+ commandHash: hashValue(resource.command),
15
+ ...(resource.operation ? { operation: resource.operation } : {}),
16
+ };
17
+ }
18
+ return resource;
19
+ }
20
+ export function hashEffectPlan(plan) {
21
+ const requirements = collectRequirements(plan.root)
22
+ .map((requirement) => ({
23
+ tag: requirement.tag,
24
+ action: requirement.action,
25
+ resource: auditableResource(requirement.resource),
26
+ evidence: {
27
+ level: requirement.evidence.level,
28
+ signals: [...requirement.evidence.signals].sort(),
29
+ basis: [...requirement.evidence.basis].sort(),
30
+ },
31
+ provenance: requirement.provenance,
32
+ provenances: [...(requirement.provenances ?? [requirement.provenance])].sort((left, right) => canonicalStringify(left).localeCompare(canonicalStringify(right))),
33
+ }))
34
+ .sort((left, right) => canonicalStringify(left).localeCompare(canonicalStringify(right)));
35
+ return hashValue(`effect-plan:v1:${canonicalStringify({
36
+ version: plan.version,
37
+ inputFingerprint: plan.inputFingerprint,
38
+ opacity: plan.opacity,
39
+ disposition: plan.disposition,
40
+ completeness: plan.completeness,
41
+ signals: [...plan.signals].sort(),
42
+ requirements,
43
+ })}`);
44
+ }
45
+ export function effectPlanAuditFields(plan, policy) {
46
+ if (!plan) {
47
+ return {};
48
+ }
49
+ const fields = {
50
+ effectPlanVersion: plan.version,
51
+ effectIRHash: hashEffectPlan(plan),
52
+ effectPlanSignals: [...plan.signals],
53
+ effectPlanOpacity: plan.opacity,
54
+ effectPlanDisposition: plan.disposition,
55
+ effectPlanCompleteness: plan.completeness,
56
+ effectPlanRequirements: collectRequirements(plan.root).map((requirement) => ({
57
+ tag: requirement.tag,
58
+ action: requirement.action,
59
+ resource: auditableResource(requirement.resource),
60
+ evidence: requirement.evidence,
61
+ provenance: auditableProvenance(requirement.provenance),
62
+ provenances: (requirement.provenances ?? [requirement.provenance]).map((provenance) => auditableProvenance(provenance)),
63
+ })),
64
+ };
65
+ if (policy?.authorizationDecision) {
66
+ fields.effectPlanPolicyOutcome = policy.authorizationDecision.outcome;
67
+ fields.effectPlanPolicyReason = policy.authorizationDecision.reason;
68
+ if (policy.authorizationDecision.matchedRule) {
69
+ fields.effectPlanPolicyMatchedRule = policy.authorizationDecision.matchedRule;
70
+ }
71
+ }
72
+ if (policy?.capabilityRequests?.length) {
73
+ fields.effectPlanRequestActions = policy.capabilityRequests.map((request) => request.action);
74
+ fields.effectPlanRequestCount = policy.capabilityRequests.length;
75
+ if (policy.capabilityRequests.some((request) => request.evidence.signals.includes('package_cache_write'))) {
76
+ fields.effectPlanCacheWriteProjected = true;
77
+ }
78
+ }
79
+ if (policy?.decisions?.length) {
80
+ fields.effectPlanRequestDecisions = policy.decisions.map((decision) => ({
81
+ outcome: decision.outcome,
82
+ reason: decision.reason,
83
+ matchedRule: decision.matchedRule,
84
+ }));
85
+ }
86
+ return fields;
87
+ }
@@ -0,0 +1,31 @@
1
+ import type { CapabilityPrincipal, CapabilityRequestV1 } from '../capability/request.js';
2
+ import type { LauncherResolution } from '../verdict/launcher-resolve.js';
3
+ import type { EffectNode, EffectPlan, EffectRequirement, PackageExecLauncher } from './types.js';
4
+ export interface BuildEffectPlanParams {
5
+ tokens: string[];
6
+ cwd: string;
7
+ repoRoot: string;
8
+ inputFingerprint: string;
9
+ launcherResolution?: LauncherResolution | null;
10
+ innerRequirements?: readonly EffectRequirement[];
11
+ }
12
+ export interface BuildCapabilityEffectPlanParams {
13
+ actionKind: 'shell' | 'tool' | 'subagent';
14
+ summary: string;
15
+ inputFingerprint: string;
16
+ requests: readonly CapabilityRequestV1[];
17
+ effectFree: boolean;
18
+ }
19
+ export declare function buildPackageExecEffectNode(params: BuildEffectPlanParams): EffectNode | null;
20
+ export declare function collectRequirements(node: EffectNode): EffectRequirement[];
21
+ export declare function buildEffectPlan(params: BuildEffectPlanParams): EffectPlan | null;
22
+ /** Build the canonical plan envelope for non-package-exec gated actions. */
23
+ export declare function buildCapabilityEffectPlan(params: BuildCapabilityEffectPlanParams): EffectPlan;
24
+ export declare function flattenRequirementsToCapabilityRequests(requirements: readonly EffectRequirement[], opts: {
25
+ principal: CapabilityPrincipal;
26
+ cwd: string;
27
+ repoRoot: string;
28
+ hookKind: 'shell';
29
+ inputFingerprint: string;
30
+ }): CapabilityRequestV1[];
31
+ export type { PackageExecLauncher };
@@ -0,0 +1,281 @@
1
+ import { mergeRequirements } from './normalize.js';
2
+ import { classifyPackageAcquisitionSpec, innerRecipeFromPeel, peelPackageExecArgv, resolveLocalBin, } from './package-exec.js';
3
+ export function buildPackageExecEffectNode(params) {
4
+ const peel = peelPackageExecArgv(params.tokens);
5
+ if (!peel) {
6
+ return null;
7
+ }
8
+ const children = [];
9
+ const invokeNode = {
10
+ kind: 'launcher',
11
+ launcher: peel.launcher,
12
+ phase: 'invoke',
13
+ opaque: peel.opaque,
14
+ reason: peel.reason,
15
+ children: [],
16
+ };
17
+ children.push(invokeNode);
18
+ if (peel.opaque) {
19
+ children.push(acquireRequirement(peel, 'indeterminate'));
20
+ return { kind: 'merge', children };
21
+ }
22
+ if (peel.reason === 'npx_wrapper_readonly') {
23
+ return { kind: 'merge', children };
24
+ }
25
+ const innerRecipe = innerRecipeFromPeel(peel);
26
+ const binHead = peel.innerTokens[0] ?? '';
27
+ const local = !peel.forceAcquire ? resolveLocalBin(binHead, params.cwd, params.repoRoot) : null;
28
+ if (local) {
29
+ children.push({
30
+ kind: 'launcher',
31
+ launcher: peel.launcher,
32
+ phase: 'resolve',
33
+ opaque: false,
34
+ reason: 'npx_local_bin_resolved',
35
+ children: [],
36
+ });
37
+ if (innerRecipe) {
38
+ children.push(delegateExecNode(peel, innerRecipe, binHead, local.path));
39
+ }
40
+ if (innerRecipe && params.innerRequirements !== undefined) {
41
+ if (params.innerRequirements.length > 0) {
42
+ children.push({
43
+ kind: 'exec',
44
+ commandRedacted: innerRecipe,
45
+ segmentHead: binHead,
46
+ requirements: [...params.innerRequirements],
47
+ });
48
+ }
49
+ }
50
+ }
51
+ else {
52
+ children.push(acquireRequirement(peel, 'possible'));
53
+ if (innerRecipe) {
54
+ children.push(delegateExecNode(peel, innerRecipe, binHead));
55
+ }
56
+ }
57
+ return { kind: 'merge', children };
58
+ }
59
+ function delegateExecNode(peel, innerRecipe, segmentHead, resolvedLocalPath) {
60
+ return {
61
+ kind: 'launcher',
62
+ launcher: peel.launcher,
63
+ phase: 'delegate',
64
+ opaque: false,
65
+ reason: 'package_exec_delegate',
66
+ children: [
67
+ {
68
+ kind: 'exec',
69
+ commandRedacted: innerRecipe,
70
+ segmentHead,
71
+ requirements: [
72
+ {
73
+ tag: 'process.exec',
74
+ action: 'process.exec',
75
+ resource: {
76
+ kind: 'executable',
77
+ command: resolvedLocalPath ?? segmentHead,
78
+ operation: 'spawn',
79
+ },
80
+ evidence: {
81
+ level: resolvedLocalPath ? 'certain' : peel.forceAcquire ? 'possible' : 'certain',
82
+ signals: [
83
+ 'package_exec.delegate',
84
+ ...(resolvedLocalPath ? ['package_exec.local_bin_resolved'] : []),
85
+ ],
86
+ basis: [`launcher:${peel.launcher}`],
87
+ },
88
+ provenance: {
89
+ launcher: peel.launcher,
90
+ phase: 'delegate',
91
+ innerCommand: innerRecipe,
92
+ innerArgv: [...peel.innerTokens],
93
+ },
94
+ },
95
+ ],
96
+ },
97
+ ],
98
+ };
99
+ }
100
+ function cacheWriteRequirement(peel) {
101
+ return {
102
+ tag: 'fs.write',
103
+ action: 'fs.write',
104
+ resource: { kind: 'package-cache', manager: peel.launcher === 'pnpm' ? 'pnpm' : 'npm' },
105
+ evidence: {
106
+ level: 'possible',
107
+ signals: [...peel.signals, 'package_cache_write'],
108
+ basis: [`launcher:${peel.launcher}:cache_write`],
109
+ },
110
+ provenance: { launcher: peel.launcher, phase: 'cache_write' },
111
+ };
112
+ }
113
+ function acquireRequirement(peel, level) {
114
+ const knownResources = packageAcquisitionResources(peel.acquisitionSpecs);
115
+ const networkRequirements = knownResources.map((resource) => acquireNetworkRequirement(peel, resource, 'possible'));
116
+ if (level === 'indeterminate') {
117
+ networkRequirements.push(acquireNetworkRequirement(peel, { kind: 'unknown' }, level));
118
+ }
119
+ else if (networkRequirements.length === 0 && peel.acquisitionSpecs.length === 0) {
120
+ networkRequirements.push(acquireNetworkRequirement(peel, { kind: 'network', host: 'registry.npmjs.org', protocol: 'registry' }, level));
121
+ }
122
+ return {
123
+ kind: 'launcher',
124
+ launcher: peel.launcher,
125
+ phase: 'acquire',
126
+ opaque: level === 'indeterminate',
127
+ reason: peel.reason,
128
+ children: [
129
+ {
130
+ kind: 'exec',
131
+ commandRedacted: peel.innerTokens.join(' ') || peel.launcher,
132
+ segmentHead: peel.launcher,
133
+ requirements: [
134
+ ...networkRequirements,
135
+ ...(level === 'possible' || peel.acquisitionSpecs.length > 0
136
+ ? [cacheWriteRequirement(peel)]
137
+ : []),
138
+ ],
139
+ },
140
+ ],
141
+ };
142
+ }
143
+ function acquireNetworkRequirement(peel, resource, level) {
144
+ return {
145
+ tag: level === 'indeterminate' ? 'indeterminate' : 'network.acquire',
146
+ action: level === 'indeterminate' ? 'indeterminate' : 'network.connect',
147
+ resource,
148
+ evidence: {
149
+ level,
150
+ signals: [...peel.signals, 'package_acquire_possible'],
151
+ basis: [`launcher:${peel.launcher}:acquire`],
152
+ },
153
+ provenance: {
154
+ launcher: peel.launcher,
155
+ phase: 'acquire',
156
+ innerArgv: [...peel.acquisitionSpecs],
157
+ },
158
+ };
159
+ }
160
+ function packageAcquisitionResources(specs) {
161
+ const resources = new Map();
162
+ for (const spec of specs) {
163
+ const source = classifyPackageAcquisitionSpec(spec);
164
+ if (source.kind === 'local') {
165
+ continue;
166
+ }
167
+ const resource = source.kind === 'registry'
168
+ ? { kind: 'network', host: 'registry.npmjs.org', protocol: 'registry' }
169
+ : {
170
+ kind: 'network',
171
+ host: source.host,
172
+ ...(source.port ? { port: source.port } : {}),
173
+ protocol: source.protocol,
174
+ };
175
+ resources.set(`${resource.host}:${resource.port ?? ''}:${resource.protocol ?? ''}`, resource);
176
+ }
177
+ return [...resources.values()];
178
+ }
179
+ export function collectRequirements(node) {
180
+ switch (node.kind) {
181
+ case 'exec':
182
+ return [...node.requirements];
183
+ case 'launcher':
184
+ return node.children.flatMap((child) => collectRequirements(child));
185
+ case 'merge':
186
+ return mergeRequirements(...node.children.map((child) => collectRequirements(child)));
187
+ default:
188
+ return [];
189
+ }
190
+ }
191
+ export function buildEffectPlan(params) {
192
+ const root = buildPackageExecEffectNode(params);
193
+ if (!root) {
194
+ return null;
195
+ }
196
+ const requirements = collectRequirements(root);
197
+ const signals = [
198
+ ...new Set([
199
+ ...(params.launcherResolution?.reason ? [params.launcherResolution.reason] : []),
200
+ ...requirements.flatMap((r) => r.evidence.signals),
201
+ ]),
202
+ ];
203
+ const opacity = peelPackageExecArgv(params.tokens)?.opaque || params.launcherResolution?.opaque
204
+ ? 'opaque'
205
+ : 'recursive';
206
+ return {
207
+ version: 1,
208
+ root,
209
+ inputFingerprint: params.inputFingerprint,
210
+ opacity,
211
+ disposition: requirements.length > 0 ? 'effects' : 'effect_free',
212
+ completeness: opacity === 'recursive' ? 'complete' : 'partial',
213
+ signals,
214
+ };
215
+ }
216
+ function effectTagForAction(action) {
217
+ return action === 'indeterminate' ? 'indeterminate' : action;
218
+ }
219
+ /** Build the canonical plan envelope for non-package-exec gated actions. */
220
+ export function buildCapabilityEffectPlan(params) {
221
+ const provenance = { segment: params.actionKind };
222
+ const requirements = params.requests.map((request) => ({
223
+ tag: effectTagForAction(request.action),
224
+ action: request.action,
225
+ resource: request.resource,
226
+ evidence: {
227
+ level: request.evidence.level,
228
+ signals: [...request.evidence.signals],
229
+ basis: [...request.context.analysisBasis],
230
+ },
231
+ provenance,
232
+ provenances: [provenance],
233
+ }));
234
+ if (!params.effectFree && requirements.length === 0) {
235
+ requirements.push({
236
+ tag: 'indeterminate',
237
+ action: 'indeterminate',
238
+ resource: { kind: 'unknown' },
239
+ evidence: {
240
+ level: 'indeterminate',
241
+ signals: ['effect_plan.incomplete'],
242
+ basis: [`gated_action:${params.actionKind}`],
243
+ },
244
+ provenance,
245
+ provenances: [provenance],
246
+ });
247
+ }
248
+ const partial = requirements.some((requirement) => requirement.evidence.level === 'indeterminate');
249
+ return {
250
+ version: 1,
251
+ root: {
252
+ kind: 'exec',
253
+ commandRedacted: params.summary,
254
+ segmentHead: params.actionKind,
255
+ requirements,
256
+ },
257
+ inputFingerprint: params.inputFingerprint,
258
+ opacity: partial ? 'opaque' : 'transparent',
259
+ disposition: params.effectFree ? 'effect_free' : 'effects',
260
+ completeness: partial ? 'partial' : 'complete',
261
+ signals: [...new Set(requirements.flatMap((requirement) => requirement.evidence.signals))],
262
+ };
263
+ }
264
+ export function flattenRequirementsToCapabilityRequests(requirements, opts) {
265
+ return requirements.map((req) => ({
266
+ version: 1,
267
+ principal: opts.principal,
268
+ action: req.action,
269
+ resource: req.resource,
270
+ context: {
271
+ hookKind: opts.hookKind,
272
+ cwd: opts.cwd,
273
+ inputFingerprint: opts.inputFingerprint,
274
+ analysisBasis: [...req.evidence.basis],
275
+ },
276
+ evidence: {
277
+ level: req.evidence.level,
278
+ signals: [...req.evidence.signals],
279
+ },
280
+ }));
281
+ }
@@ -0,0 +1,13 @@
1
+ export type { GitRefScope, NetworkMode, NetworkPayload, ProcessOperation, } from '../capability/request.js';
2
+ export { effectPlanAuditFields, hashEffectPlan } from './audit.js';
3
+ export { buildCapabilityEffectPlan, buildEffectPlan, buildPackageExecEffectNode, collectRequirements, flattenRequirementsToCapabilityRequests, } from './build.js';
4
+ export { mergeEffectPlans, mergeRequirements, normalizeEffectTags } from './normalize.js';
5
+ export { innerRecipeFromPeel, isPackageExecLauncher, peelPackageExecArgv, resolveLocalBin, } from './package-exec.js';
6
+ export { evaluatePackageExecSegment } from './package-exec-eval.js';
7
+ export { capabilityRequestsFromEffectPlan, capabilityRequestsToEffectRequirements, effectPlanPolicyLegacyReason, effectPlanPolicyRequiresAsk, evaluateEffectPlanPolicy, } from './policy.js';
8
+ export type { BuildShellEffectPlanParams, ShellEffectRequirement, ShellEffectResource, ShellEffectSegment, } from './shell-build.js';
9
+ export { buildShellEffectPlan } from './shell-build.js';
10
+ export type { LowerShellEffectPlanParams } from './shell-lower.js';
11
+ export { lowerShellEffectPlan } from './shell-lower.js';
12
+ export type { AnalysisCompleteness, EffectEvidence, EffectNode, EffectPlan, EffectPlanDisposition, EffectPlanPolicyProjection, EffectProvenance, EffectRequirement, EffectTag, ExecEffectNode, LauncherEffectNode, LauncherPhase, MergeEffectNode, PackageExecLauncher, } from './types.js';
13
+ export { EFFECT_PLAN_VERSION } from './types.js';
@@ -0,0 +1,9 @@
1
+ export { effectPlanAuditFields, hashEffectPlan } from './audit.js';
2
+ export { buildCapabilityEffectPlan, buildEffectPlan, buildPackageExecEffectNode, collectRequirements, flattenRequirementsToCapabilityRequests, } from './build.js';
3
+ export { mergeEffectPlans, mergeRequirements, normalizeEffectTags } from './normalize.js';
4
+ export { innerRecipeFromPeel, isPackageExecLauncher, peelPackageExecArgv, resolveLocalBin, } from './package-exec.js';
5
+ export { evaluatePackageExecSegment } from './package-exec-eval.js';
6
+ export { capabilityRequestsFromEffectPlan, capabilityRequestsToEffectRequirements, effectPlanPolicyLegacyReason, effectPlanPolicyRequiresAsk, evaluateEffectPlanPolicy, } from './policy.js';
7
+ export { buildShellEffectPlan } from './shell-build.js';
8
+ export { lowerShellEffectPlan } from './shell-lower.js';
9
+ export { EFFECT_PLAN_VERSION } from './types.js';
@@ -0,0 +1,4 @@
1
+ import type { EffectPlan, EffectRequirement, EffectTag } from './types.js';
2
+ export declare function normalizeEffectTags(tags: Iterable<EffectTag>): readonly EffectTag[];
3
+ export declare function mergeRequirements(...groups: readonly (readonly EffectRequirement[])[]): EffectRequirement[];
4
+ export declare function mergeEffectPlans(...plans: readonly (EffectPlan | null | undefined)[]): EffectPlan | undefined;
@@ -0,0 +1,110 @@
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 [
81
+ resource.host,
82
+ resource.port ?? '',
83
+ resource.protocol ?? '',
84
+ resource.mode ?? '',
85
+ resource.payload ?? '',
86
+ ].join(':');
87
+ case 'executable':
88
+ return `${resource.command}:${resource.operation ?? ''}`;
89
+ case 'package-cache':
90
+ return resource.manager;
91
+ case 'git-ref':
92
+ return `${resource.ref}:${resource.scope ?? ''}:${resource.repoPath ?? ''}`;
93
+ case 'unknown':
94
+ return 'unknown';
95
+ default:
96
+ return 'unknown';
97
+ }
98
+ }
99
+ function evidenceRank(level) {
100
+ switch (level) {
101
+ case 'certain':
102
+ return 3;
103
+ case 'possible':
104
+ return 2;
105
+ case 'indeterminate':
106
+ return 1;
107
+ default:
108
+ return 0;
109
+ }
110
+ }
@@ -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>;