@smthrs/kernel 0.0.0-stage → 1.0.0-rc.3

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 (267) hide show
  1. package/CHANGELOG.md +206 -0
  2. package/LICENSE +21 -0
  3. package/README.md +154 -2
  4. package/dist/cjs/CapabilitySet.d.ts +6 -0
  5. package/dist/cjs/CapabilitySet.d.ts.map +1 -0
  6. package/dist/cjs/CapabilitySet.js +19 -0
  7. package/dist/cjs/CapabilitySet.js.map +7 -0
  8. package/dist/cjs/ChildProcessEnvironment.d.ts +60 -0
  9. package/dist/cjs/ChildProcessEnvironment.d.ts.map +1 -0
  10. package/dist/cjs/ChildProcessEnvironment.js +61 -0
  11. package/dist/cjs/ChildProcessEnvironment.js.map +7 -0
  12. package/dist/cjs/ChildProcessSpawner.d.ts +100 -0
  13. package/dist/cjs/ChildProcessSpawner.d.ts.map +1 -0
  14. package/dist/cjs/ChildProcessSpawner.js +291 -0
  15. package/dist/cjs/ChildProcessSpawner.js.map +7 -0
  16. package/dist/cjs/CommandLine.d.ts +173 -0
  17. package/dist/cjs/CommandLine.d.ts.map +1 -0
  18. package/dist/cjs/CommandLine.js +54 -0
  19. package/dist/cjs/CommandLine.js.map +7 -0
  20. package/dist/cjs/ContainedSpawner.d.ts +135 -0
  21. package/dist/cjs/ContainedSpawner.d.ts.map +1 -0
  22. package/dist/cjs/ContainedSpawner.js +141 -0
  23. package/dist/cjs/ContainedSpawner.js.map +7 -0
  24. package/dist/cjs/FileSystem.d.ts +452 -0
  25. package/dist/cjs/FileSystem.d.ts.map +1 -0
  26. package/dist/cjs/FileSystem.js +890 -0
  27. package/dist/cjs/FileSystem.js.map +7 -0
  28. package/dist/cjs/FileSystemBatch.d.ts +94 -0
  29. package/dist/cjs/FileSystemBatch.d.ts.map +1 -0
  30. package/dist/cjs/FileSystemBatch.js +31 -0
  31. package/dist/cjs/FileSystemBatch.js.map +7 -0
  32. package/dist/cjs/GrantEvent.d.ts +240 -0
  33. package/dist/cjs/GrantEvent.d.ts.map +1 -0
  34. package/dist/cjs/GrantEvent.js +115 -0
  35. package/dist/cjs/GrantEvent.js.map +7 -0
  36. package/dist/cjs/GrantStore.d.ts +270 -0
  37. package/dist/cjs/GrantStore.d.ts.map +1 -0
  38. package/dist/cjs/GrantStore.js +857 -0
  39. package/dist/cjs/GrantStore.js.map +7 -0
  40. package/dist/cjs/HostError.d.ts +36 -0
  41. package/dist/cjs/HostError.d.ts.map +1 -0
  42. package/dist/cjs/HostError.js +17 -0
  43. package/dist/cjs/HostError.js.map +7 -0
  44. package/dist/cjs/HostServices.d.ts +101 -0
  45. package/dist/cjs/HostServices.d.ts.map +1 -0
  46. package/dist/cjs/HostServices.js +66 -0
  47. package/dist/cjs/HostServices.js.map +7 -0
  48. package/dist/cjs/HttpClient.d.ts +177 -0
  49. package/dist/cjs/HttpClient.d.ts.map +1 -0
  50. package/dist/cjs/HttpClient.js +217 -0
  51. package/dist/cjs/HttpClient.js.map +7 -0
  52. package/dist/cjs/Jj.d.ts +67 -0
  53. package/dist/cjs/Jj.d.ts.map +1 -0
  54. package/dist/cjs/Jj.js +118 -0
  55. package/dist/cjs/Jj.js.map +7 -0
  56. package/dist/cjs/JournalGrantStore.d.ts +65 -0
  57. package/dist/cjs/JournalGrantStore.d.ts.map +1 -0
  58. package/dist/cjs/JournalGrantStore.js +354 -0
  59. package/dist/cjs/JournalGrantStore.js.map +7 -0
  60. package/dist/cjs/Path.d.ts +37 -0
  61. package/dist/cjs/Path.d.ts.map +1 -0
  62. package/dist/cjs/Path.js +46 -0
  63. package/dist/cjs/Path.js.map +7 -0
  64. package/dist/cjs/ProcessConfinement.d.ts +55 -0
  65. package/dist/cjs/ProcessConfinement.d.ts.map +1 -0
  66. package/dist/cjs/ProcessConfinement.js +94 -0
  67. package/dist/cjs/ProcessConfinement.js.map +7 -0
  68. package/dist/cjs/ProcessLedger.d.ts +260 -0
  69. package/dist/cjs/ProcessLedger.d.ts.map +1 -0
  70. package/dist/cjs/ProcessLedger.js +190 -0
  71. package/dist/cjs/ProcessLedger.js.map +7 -0
  72. package/dist/cjs/Rooted.d.ts +53 -0
  73. package/dist/cjs/Rooted.d.ts.map +1 -0
  74. package/dist/cjs/Rooted.js +101 -0
  75. package/dist/cjs/Rooted.js.map +7 -0
  76. package/dist/cjs/Unreachable.d.ts +27 -0
  77. package/dist/cjs/Unreachable.d.ts.map +1 -0
  78. package/dist/cjs/Unreachable.js +43 -0
  79. package/dist/cjs/Unreachable.js.map +7 -0
  80. package/dist/cjs/Workspace.d.ts +62 -0
  81. package/dist/cjs/Workspace.d.ts.map +1 -0
  82. package/dist/cjs/Workspace.js +35 -0
  83. package/dist/cjs/Workspace.js.map +7 -0
  84. package/dist/cjs/index.d.ts +161 -0
  85. package/dist/cjs/index.d.ts.map +1 -0
  86. package/dist/cjs/index.js +73 -0
  87. package/dist/cjs/index.js.map +7 -0
  88. package/dist/cjs/internal/Containment.d.ts +24 -0
  89. package/dist/cjs/internal/Containment.d.ts.map +1 -0
  90. package/dist/cjs/internal/Containment.js +30 -0
  91. package/dist/cjs/internal/Containment.js.map +7 -0
  92. package/dist/cjs/internal/Pipeline.d.ts +18 -0
  93. package/dist/cjs/internal/Pipeline.d.ts.map +1 -0
  94. package/dist/cjs/internal/Pipeline.js +90 -0
  95. package/dist/cjs/internal/Pipeline.js.map +7 -0
  96. package/dist/cjs/internal/makeCapability.d.ts +16 -0
  97. package/dist/cjs/internal/makeCapability.d.ts.map +1 -0
  98. package/dist/cjs/internal/makeCapability.js +44 -0
  99. package/dist/cjs/internal/makeCapability.js.map +7 -0
  100. package/dist/cjs/package.json +1 -0
  101. package/dist/cjs/test/FileSystemBatchContract.d.ts +15 -0
  102. package/dist/cjs/test/FileSystemBatchContract.d.ts.map +1 -0
  103. package/dist/cjs/test/FileSystemBatchContract.js +76 -0
  104. package/dist/cjs/test/FileSystemBatchContract.js.map +7 -0
  105. package/dist/cjs/test/HostContract.d.ts +225 -0
  106. package/dist/cjs/test/HostContract.d.ts.map +1 -0
  107. package/dist/cjs/test/HostContract.js +489 -0
  108. package/dist/cjs/test/HostContract.js.map +7 -0
  109. package/dist/cjs/test/TestGrantStore.d.ts +31 -0
  110. package/dist/cjs/test/TestGrantStore.d.ts.map +1 -0
  111. package/dist/cjs/test/TestGrantStore.js +65 -0
  112. package/dist/cjs/test/TestGrantStore.js.map +7 -0
  113. package/dist/esm/CapabilitySet.d.ts +6 -0
  114. package/dist/esm/CapabilitySet.d.ts.map +1 -0
  115. package/dist/esm/CapabilitySet.js +6 -0
  116. package/dist/esm/CapabilitySet.js.map +1 -0
  117. package/dist/esm/ChildProcessEnvironment.d.ts +60 -0
  118. package/dist/esm/ChildProcessEnvironment.d.ts.map +1 -0
  119. package/dist/esm/ChildProcessEnvironment.js +91 -0
  120. package/dist/esm/ChildProcessEnvironment.js.map +1 -0
  121. package/dist/esm/ChildProcessSpawner.d.ts +100 -0
  122. package/dist/esm/ChildProcessSpawner.d.ts.map +1 -0
  123. package/dist/esm/ChildProcessSpawner.js +342 -0
  124. package/dist/esm/ChildProcessSpawner.js.map +1 -0
  125. package/dist/esm/CommandLine.d.ts +173 -0
  126. package/dist/esm/CommandLine.d.ts.map +1 -0
  127. package/dist/esm/CommandLine.js +195 -0
  128. package/dist/esm/CommandLine.js.map +1 -0
  129. package/dist/esm/ContainedSpawner.d.ts +135 -0
  130. package/dist/esm/ContainedSpawner.d.ts.map +1 -0
  131. package/dist/esm/ContainedSpawner.js +180 -0
  132. package/dist/esm/ContainedSpawner.js.map +1 -0
  133. package/dist/esm/FileSystem.d.ts +452 -0
  134. package/dist/esm/FileSystem.d.ts.map +1 -0
  135. package/dist/esm/FileSystem.js +989 -0
  136. package/dist/esm/FileSystem.js.map +1 -0
  137. package/dist/esm/FileSystemBatch.d.ts +94 -0
  138. package/dist/esm/FileSystemBatch.d.ts.map +1 -0
  139. package/dist/esm/FileSystemBatch.js +26 -0
  140. package/dist/esm/FileSystemBatch.js.map +1 -0
  141. package/dist/esm/GrantEvent.d.ts +240 -0
  142. package/dist/esm/GrantEvent.d.ts.map +1 -0
  143. package/dist/esm/GrantEvent.js +155 -0
  144. package/dist/esm/GrantEvent.js.map +1 -0
  145. package/dist/esm/GrantStore.d.ts +270 -0
  146. package/dist/esm/GrantStore.d.ts.map +1 -0
  147. package/dist/esm/GrantStore.js +951 -0
  148. package/dist/esm/GrantStore.js.map +1 -0
  149. package/dist/esm/HostError.d.ts +36 -0
  150. package/dist/esm/HostError.d.ts.map +1 -0
  151. package/dist/esm/HostError.js +20 -0
  152. package/dist/esm/HostError.js.map +1 -0
  153. package/dist/esm/HostServices.d.ts +101 -0
  154. package/dist/esm/HostServices.d.ts.map +1 -0
  155. package/dist/esm/HostServices.js +76 -0
  156. package/dist/esm/HostServices.js.map +1 -0
  157. package/dist/esm/HttpClient.d.ts +177 -0
  158. package/dist/esm/HttpClient.d.ts.map +1 -0
  159. package/dist/esm/HttpClient.js +299 -0
  160. package/dist/esm/HttpClient.js.map +1 -0
  161. package/dist/esm/Jj.d.ts +67 -0
  162. package/dist/esm/Jj.d.ts.map +1 -0
  163. package/dist/esm/Jj.js +111 -0
  164. package/dist/esm/Jj.js.map +1 -0
  165. package/dist/esm/JournalGrantStore.d.ts +65 -0
  166. package/dist/esm/JournalGrantStore.d.ts.map +1 -0
  167. package/dist/esm/JournalGrantStore.js +350 -0
  168. package/dist/esm/JournalGrantStore.js.map +1 -0
  169. package/dist/esm/Path.d.ts +37 -0
  170. package/dist/esm/Path.d.ts.map +1 -0
  171. package/dist/esm/Path.js +34 -0
  172. package/dist/esm/Path.js.map +1 -0
  173. package/dist/esm/ProcessConfinement.d.ts +55 -0
  174. package/dist/esm/ProcessConfinement.d.ts.map +1 -0
  175. package/dist/esm/ProcessConfinement.js +106 -0
  176. package/dist/esm/ProcessConfinement.js.map +1 -0
  177. package/dist/esm/ProcessLedger.d.ts +260 -0
  178. package/dist/esm/ProcessLedger.d.ts.map +1 -0
  179. package/dist/esm/ProcessLedger.js +283 -0
  180. package/dist/esm/ProcessLedger.js.map +1 -0
  181. package/dist/esm/Rooted.d.ts +53 -0
  182. package/dist/esm/Rooted.d.ts.map +1 -0
  183. package/dist/esm/Rooted.js +104 -0
  184. package/dist/esm/Rooted.js.map +1 -0
  185. package/dist/esm/Unreachable.d.ts +27 -0
  186. package/dist/esm/Unreachable.d.ts.map +1 -0
  187. package/dist/esm/Unreachable.js +26 -0
  188. package/dist/esm/Unreachable.js.map +1 -0
  189. package/dist/esm/Workspace.d.ts +62 -0
  190. package/dist/esm/Workspace.d.ts.map +1 -0
  191. package/dist/esm/Workspace.js +51 -0
  192. package/dist/esm/Workspace.js.map +1 -0
  193. package/dist/esm/index.d.ts +161 -0
  194. package/dist/esm/index.d.ts.map +1 -0
  195. package/dist/esm/index.js +151 -0
  196. package/dist/esm/index.js.map +1 -0
  197. package/dist/esm/internal/Containment.d.ts +24 -0
  198. package/dist/esm/internal/Containment.d.ts.map +1 -0
  199. package/dist/esm/internal/Containment.js +27 -0
  200. package/dist/esm/internal/Containment.js.map +1 -0
  201. package/dist/esm/internal/Pipeline.d.ts +18 -0
  202. package/dist/esm/internal/Pipeline.d.ts.map +1 -0
  203. package/dist/esm/internal/Pipeline.js +68 -0
  204. package/dist/esm/internal/Pipeline.js.map +1 -0
  205. package/dist/esm/internal/makeCapability.d.ts +16 -0
  206. package/dist/esm/internal/makeCapability.d.ts.map +1 -0
  207. package/dist/esm/internal/makeCapability.js +22 -0
  208. package/dist/esm/internal/makeCapability.js.map +1 -0
  209. package/dist/esm/test/FileSystemBatchContract.d.ts +15 -0
  210. package/dist/esm/test/FileSystemBatchContract.d.ts.map +1 -0
  211. package/dist/esm/test/FileSystemBatchContract.js +57 -0
  212. package/dist/esm/test/FileSystemBatchContract.js.map +1 -0
  213. package/dist/esm/test/HostContract.d.ts +225 -0
  214. package/dist/esm/test/HostContract.d.ts.map +1 -0
  215. package/dist/esm/test/HostContract.js +460 -0
  216. package/dist/esm/test/HostContract.js.map +1 -0
  217. package/dist/esm/test/TestGrantStore.d.ts +31 -0
  218. package/dist/esm/test/TestGrantStore.d.ts.map +1 -0
  219. package/dist/esm/test/TestGrantStore.js +57 -0
  220. package/dist/esm/test/TestGrantStore.js.map +1 -0
  221. package/docs/README.md +162 -0
  222. package/docs/api.md +1532 -0
  223. package/docs/concepts/decoration-in-place.md +117 -0
  224. package/docs/concepts/filesystem-batches.md +58 -0
  225. package/docs/concepts/filesystem-confinement.md +168 -0
  226. package/docs/concepts/grant-decisions.md +181 -0
  227. package/docs/concepts/process-containment.md +155 -0
  228. package/docs/contract.md +14 -0
  229. package/docs/guides/adapt-a-new-host-platform.md +148 -0
  230. package/docs/guides/answer-permission-requests.md +151 -0
  231. package/docs/guides/authorize-network-and-model-calls.md +118 -0
  232. package/docs/guides/contain-spawned-processes.md +118 -0
  233. package/docs/guides/guard-a-host-bundle.md +109 -0
  234. package/docs/guides/persist-grants-across-restarts.md +135 -0
  235. package/docs/guides/write-a-capability-policy.md +159 -0
  236. package/docs/installation.md +115 -0
  237. package/docs/quickstart.md +182 -0
  238. package/docs/testing.md +108 -0
  239. package/docs/troubleshooting.md +281 -0
  240. package/package.json +344 -3
  241. package/src/CapabilitySet.ts +6 -0
  242. package/src/ChildProcessEnvironment.ts +98 -0
  243. package/src/ChildProcessSpawner.ts +414 -0
  244. package/src/CommandLine.ts +250 -0
  245. package/src/ContainedSpawner.ts +284 -0
  246. package/src/FileSystem.ts +1655 -0
  247. package/src/FileSystemBatch.ts +90 -0
  248. package/src/GrantEvent.ts +174 -0
  249. package/src/GrantStore.ts +1345 -0
  250. package/src/HostError.ts +37 -0
  251. package/src/HostServices.ts +133 -0
  252. package/src/HttpClient.ts +406 -0
  253. package/src/Jj.ts +159 -0
  254. package/src/JournalGrantStore.ts +461 -0
  255. package/src/Path.ts +47 -0
  256. package/src/ProcessConfinement.ts +156 -0
  257. package/src/ProcessLedger.ts +423 -0
  258. package/src/Rooted.ts +128 -0
  259. package/src/Unreachable.ts +30 -0
  260. package/src/Workspace.ts +65 -0
  261. package/src/index.ts +181 -0
  262. package/src/internal/Containment.ts +37 -0
  263. package/src/internal/Pipeline.ts +85 -0
  264. package/src/internal/makeCapability.ts +26 -0
  265. package/src/test/FileSystemBatchContract.ts +59 -0
  266. package/src/test/HostContract.ts +785 -0
  267. package/src/test/TestGrantStore.ts +73 -0
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Permission-aware process spawning.
3
+ *
4
+ * Smithers has no shell service of its own — process execution is Effect's
5
+ * `effect/unstable/process`. The only thing the kernel adds is the
6
+ * `proc:spawn` capability check and the host confinement wrapper, applied to
7
+ * the one primitive every other helper is derived from.
8
+ *
9
+ * There is no kernel spawner interface and no kernel spawner tag. Effect owns
10
+ * both, and its tag fixes the error channel to `PlatformError`, so this module
11
+ * is only the middleware: a `Layer` over Effect's own tag. A denied command is
12
+ * projected into `PlatformError` by `Permission.toPlatformError`, which keeps
13
+ * the structured kernel failure on the error's `cause`. Because the channel
14
+ * stays `PlatformError`, the full six-method surface is derived from the one
15
+ * guarded `spawn` through Effect's own `ChildProcessSpawner.make` without a
16
+ * cast.
17
+ *
18
+ * Governing design:
19
+ * `docs/specs/Concepts/Permission Kernel.md` and
20
+ * `docs/specs/Concepts/Host Adapters.md`.
21
+ *
22
+ * @since 1.0.0-rc.0
23
+ */
24
+ import { toPlatformError } from "@smthrs/capability/Permission";
25
+ import { Effect, FileSystem, Layer, Option, Path } from "effect";
26
+ import { systemError } from "effect/PlatformError";
27
+ import * as ChildProcess from "effect/unstable/process/ChildProcess";
28
+ import { ChildProcessSpawner, make as makeSpawner } from "effect/unstable/process/ChildProcessSpawner";
29
+ import * as CommandLine from "./CommandLine.js";
30
+ import { GrantStore } from "./GrantStore.js";
31
+ import * as Containment from "./internal/Containment.js";
32
+ import { makeCapability } from "./internal/makeCapability.js";
33
+ import * as ProcessConfinement from "./ProcessConfinement.js";
34
+ import * as Rooted from "./Rooted.js";
35
+ import { Workspace } from "./Workspace.js";
36
+ const data = (value, name) => {
37
+ const descriptor = Object.getOwnPropertyDescriptor(value, name);
38
+ if (descriptor === undefined || !("value" in descriptor)) {
39
+ throw new TypeError(`command ${String(name)} must be a data property`);
40
+ }
41
+ return descriptor.value;
42
+ };
43
+ const optionalData = (value, name) => {
44
+ const descriptor = Object.getOwnPropertyDescriptor(value, name);
45
+ if (descriptor === undefined)
46
+ return undefined;
47
+ if (!("value" in descriptor))
48
+ throw new TypeError(`command ${String(name)} must be a data property`);
49
+ return descriptor.value;
50
+ };
51
+ const snapshotRecord = (value) => {
52
+ if (value === undefined)
53
+ return undefined;
54
+ const snapshot = Object.create(null);
55
+ for (const [name, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
56
+ if (!descriptor.enumerable)
57
+ continue;
58
+ if (!("value" in descriptor) || (descriptor.value !== undefined && typeof descriptor.value !== "string")) {
59
+ throw new TypeError("command environment must contain string data properties");
60
+ }
61
+ snapshot[name] = descriptor.value;
62
+ }
63
+ return Object.freeze(snapshot);
64
+ };
65
+ const snapshotStreamConfig = (value) => {
66
+ if (typeof value !== "object" || value === null)
67
+ return value;
68
+ const descriptors = Object.getOwnPropertyDescriptors(value);
69
+ if (descriptors.stream === undefined)
70
+ return value;
71
+ if (!("value" in descriptors.stream))
72
+ throw new TypeError("command stream must be a data property");
73
+ const snapshot = { stream: descriptors.stream.value };
74
+ for (const name of ["endOnDone", "encoding"]) {
75
+ const descriptor = descriptors[name];
76
+ if (descriptor !== undefined) {
77
+ if (!("value" in descriptor))
78
+ throw new TypeError(`command ${name} must be a data property`);
79
+ snapshot[name] = descriptor.value;
80
+ }
81
+ }
82
+ return Object.freeze(snapshot);
83
+ };
84
+ const snapshotAdditionalFds = (value) => {
85
+ if (value === undefined)
86
+ return undefined;
87
+ const snapshot = {};
88
+ for (const [name, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
89
+ if (!descriptor.enumerable)
90
+ continue;
91
+ if (!("value" in descriptor) || typeof descriptor.value !== "object" || descriptor.value === null) {
92
+ throw new TypeError("additional file descriptors must be data records");
93
+ }
94
+ const config = descriptor.value;
95
+ const type = data(config, "type");
96
+ if (type === "input") {
97
+ const stream = Object.getOwnPropertyDescriptor(config, "stream");
98
+ snapshot[name] = Object.freeze({
99
+ type,
100
+ ...(stream === undefined ? {} : "value" in stream ? { stream: stream.value } : (() => {
101
+ throw new TypeError("additional fd stream must be a data property");
102
+ })())
103
+ });
104
+ }
105
+ else if (type === "output") {
106
+ const sink = Object.getOwnPropertyDescriptor(config, "sink");
107
+ snapshot[name] = Object.freeze({
108
+ type,
109
+ ...(sink === undefined ? {} : "value" in sink ? { sink: sink.value } : (() => {
110
+ throw new TypeError("additional fd sink must be a data property");
111
+ })())
112
+ });
113
+ }
114
+ else {
115
+ throw new TypeError("additional file descriptor type is invalid");
116
+ }
117
+ }
118
+ return Object.freeze(snapshot);
119
+ };
120
+ const snapshotOptions = (options) => Object.freeze({
121
+ killSignal: options.killSignal,
122
+ forceKillAfter: options.forceKillAfter,
123
+ cwd: options.cwd,
124
+ env: snapshotRecord(options.env),
125
+ extendEnv: options.extendEnv,
126
+ shell: options.shell,
127
+ detached: options.detached,
128
+ windowsHide: options.windowsHide,
129
+ stdin: snapshotStreamConfig(options.stdin),
130
+ stdout: snapshotStreamConfig(options.stdout),
131
+ stderr: snapshotStreamConfig(options.stderr),
132
+ additionalFds: snapshotAdditionalFds(options.additionalFds)
133
+ });
134
+ const snapshotCommand = (command) => {
135
+ const tag = data(command, "_tag");
136
+ if (tag === "StandardCommand") {
137
+ const executable = data(command, "command");
138
+ const args = data(command, "args");
139
+ const options = data(command, "options");
140
+ if (typeof executable !== "string" || !Array.isArray(args) || !args.every((arg) => typeof arg === "string")) {
141
+ throw new TypeError("standard command executable and arguments are invalid");
142
+ }
143
+ if (typeof options !== "object" || options === null)
144
+ throw new TypeError("command options are invalid");
145
+ return ChildProcess.make(executable, Object.freeze([...args]), snapshotOptions(options));
146
+ }
147
+ if (tag === "PipedCommand") {
148
+ const left = data(command, "left");
149
+ const right = data(command, "right");
150
+ const options = data(command, "options");
151
+ if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) {
152
+ throw new TypeError("pipeline commands are invalid");
153
+ }
154
+ if (typeof options !== "object" || options === null)
155
+ throw new TypeError("pipeline options are invalid");
156
+ const from = optionalData(options, "from");
157
+ const to = optionalData(options, "to");
158
+ return ChildProcess.pipeTo(snapshotCommand(right), Object.freeze({
159
+ ...(from === undefined ? {} : { from: from }),
160
+ ...(to === undefined ? {} : { to: to })
161
+ }))(snapshotCommand(left));
162
+ }
163
+ throw new TypeError("unsupported command shape");
164
+ };
165
+ const maximumEnvironmentNames = 64;
166
+ /**
167
+ * The environment this process holds, which a child built with
168
+ * `ChildProcessEnvironment.make` inherits its bootstrap names from. Empty on a
169
+ * host with no process environment, so every declared bootstrap name counts
170
+ * as an override there.
171
+ */
172
+ const ambientEnvironment = () => globalThis.process?.env ?? {};
173
+ const environmentNames = (command) => {
174
+ const environment = command.options.env;
175
+ if (environment === undefined)
176
+ return undefined;
177
+ const names = Object.entries(environment)
178
+ .filter(([, value]) => value !== undefined)
179
+ .map(([name]) => name)
180
+ .sort();
181
+ if (names.length === 0)
182
+ return undefined;
183
+ return names.length <= maximumEnvironmentNames
184
+ ? names
185
+ : [...names.slice(0, maximumEnvironmentNames), `+${names.length - maximumEnvironmentNames} more`];
186
+ };
187
+ /**
188
+ * Effect's own process spawner tag, unchanged. Re-exported so the kernel
189
+ * namespace stays one-stop; it is the *same* tag, never a second one.
190
+ *
191
+ * @category services
192
+ * @since 1.0.0-rc.0
193
+ */
194
+ export { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
195
+ /**
196
+ * Derives the full six-method surface from one `spawn`, so `exitCode`,
197
+ * `string`, `lines`, and both `stream*` helpers can never bypass whatever
198
+ * `spawn` was given.
199
+ *
200
+ * @category constructors
201
+ * @since 1.0.0-rc.0
202
+ */
203
+ export { make } from "effect/unstable/process/ChildProcessSpawner";
204
+ /**
205
+ * Constructs an unavailable spawner stub.
206
+ *
207
+ * Every derived helper reports the missing host as a `NotFound`
208
+ * `PlatformError` naming the command line, so an unconfigured capability
209
+ * answers rather than vanishing.
210
+ *
211
+ * @category constructors
212
+ * @since 1.0.0-rc.0
213
+ */
214
+ export const makeNoop = (overrides = {}) => ({
215
+ ...makeSpawner((command) => Effect.fail(systemError({
216
+ _tag: "NotFound",
217
+ module: "ChildProcessSpawner",
218
+ method: "spawn",
219
+ description: `no process host for \`${CommandLine.render(command)}\``
220
+ }))),
221
+ ...overrides
222
+ });
223
+ /**
224
+ * Provides an unavailable spawner stub.
225
+ *
226
+ * @category layers
227
+ * @since 1.0.0-rc.0
228
+ */
229
+ export const layerNoop = (overrides = {}) => Layer.succeed(ChildProcessSpawner)(makeNoop(overrides));
230
+ /**
231
+ * Decorates the process spawner in place with a `proc:spawn` capability check.
232
+ *
233
+ * The check is suspended into the spawn itself, so building a `Command` or a
234
+ * stream neither requests permission nor starts a process; only running one
235
+ * does. Every pipeline stage is checked as its own capability, left to right,
236
+ * and no stage starts until all pass. A stage's resource is
237
+ * `CommandLine.resource`: the command line `CommandLine.render` produces, with
238
+ * a `shell: true` line that holds shell control syntax marked as
239
+ * `sh -c '<line>'`, any environment variable the child would not inherit from
240
+ * this process named in an `env <NAME>… -- ` prefix, and a working directory
241
+ * outside `Workspace.root` named in a `cwd <path> -- ` prefix. Custom shell
242
+ * paths are included explicitly in the resource; browser and remote adapters
243
+ * reject them rather than silently substituting a different shell.
244
+ *
245
+ * Every approved stage requires `ProcessConfinement` in the host context.
246
+ * A missing service refuses spawning; its `layerNoop` is the explicit choice
247
+ * for an unconfined host.
248
+ *
249
+ * Pipeline `from`/`to` routing remains outside the grant. The working
250
+ * directory and overridden environment names also reach attended surfaces as
251
+ * display metadata; environment values never leave the command.
252
+ *
253
+ * The layer provides the tag it also requires: compose it over a host spawner
254
+ * layer with `Layer.provide` and a `ChildProcess.Command` run as a plain
255
+ * `Effect` is checked too.
256
+ *
257
+ * A command with no `cwd`, or a relative one, runs in `Workspace.root`, never
258
+ * the process's own directory; the check sees that resolved directory. When
259
+ * the host filesystem is in the layer's context, the working directory and
260
+ * `Workspace.root` are compared by real path, so a symlink inside the
261
+ * workspace that leads out of it is named, as its target, in the `cwd` prefix.
262
+ *
263
+ * @category layers
264
+ * @since 1.0.0-rc.0
265
+ */
266
+ export const layer = Layer.effect(ChildProcessSpawner, Effect.gen(function* () {
267
+ const spawner = yield* ChildProcessSpawner;
268
+ const grants = yield* GrantStore;
269
+ const confinement = yield* Effect.serviceOption(ProcessConfinement.ProcessConfinement);
270
+ const path = yield* Path.Path;
271
+ const rooted = Rooted.path(path, path.resolve((yield* Workspace).root));
272
+ const root = rooted.resolve(".");
273
+ // The raw host filesystem this layer is composed over, when there is one.
274
+ // A symlink inside the workspace can lead out of it, so containment is
275
+ // judged on real paths; a directory that cannot be resolved (it does not
276
+ // exist, or the host has no filesystem) is judged lexically.
277
+ const fileSystem = yield* Effect.serviceOption(FileSystem.FileSystem);
278
+ const real = (directory) => Option.match(fileSystem, {
279
+ onNone: () => Effect.succeed(directory),
280
+ onSome: (fs) => fs.realPath(directory).pipe(Effect.orElseSucceed(() => directory))
281
+ });
282
+ const within = (base) => (directory) => {
283
+ const relative = path.relative(base, directory);
284
+ return relative.split(path.sep)[0] !== ".." && !path.isAbsolute(relative);
285
+ };
286
+ const checkStage = (stage) => Effect.gen(function* () {
287
+ // Rooted.command gives every stage an absolute cwd.
288
+ const cwd = stage.options.cwd;
289
+ const realRoot = yield* real(root);
290
+ const resolvedCwd = yield* real(cwd);
291
+ const contains = resolvedCwd === cwd
292
+ ? (directory) => within(root)(directory) || within(realRoot)(directory)
293
+ : within(realRoot);
294
+ return yield* checkResolved(stage, CommandLine.resource(stage, {
295
+ ambient: ambientEnvironment(),
296
+ contains,
297
+ resolvedCwd
298
+ }));
299
+ });
300
+ const checkResolved = (stage, rendered) => {
301
+ const environment = environmentNames(stage);
302
+ return makeCapability("proc:spawn", rendered).pipe(Effect.flatMap((capability) => grants.check(capability, {
303
+ cwd: stage.options.cwd,
304
+ ...(environment === undefined ? {} : { env: environment })
305
+ })), Effect.mapError((error) => toPlatformError({
306
+ module: "ChildProcessSpawner",
307
+ method: "spawn",
308
+ pathOrDescriptor: rendered,
309
+ error
310
+ })));
311
+ };
312
+ const check = (command) => Effect.forEach(CommandLine.stages(command), checkStage, { discard: true });
313
+ const confine = (command) => Option.match(confinement, {
314
+ onNone: () => Effect.fail(systemError({
315
+ _tag: "NotFound",
316
+ module: "ProcessConfinement",
317
+ method: "confine",
318
+ description: "process confinement is required; use ProcessConfinement.layerNoop to explicitly run unconfined"
319
+ })),
320
+ onSome: (service) => Effect.gen(function* () {
321
+ const profile = yield* ProcessConfinement.profile(grants, root, path).pipe(Effect.mapError((error) => toPlatformError({ module: "ChildProcessSpawner", method: "spawn", error })));
322
+ const wrap = (command) => command._tag === "StandardCommand"
323
+ ? service.confine(command, profile)
324
+ : Effect.gen(function* () {
325
+ const left = yield* wrap(command.left);
326
+ const right = yield* wrap(command.right);
327
+ return ChildProcess.pipeTo(right, command.options)(left);
328
+ });
329
+ return yield* wrap(command);
330
+ })
331
+ });
332
+ return Containment.inherit(spawner, makeSpawner(Effect.fn("ChildProcessSpawner.spawn")((command) => Effect.try({
333
+ try: () => snapshotCommand(command),
334
+ catch: () => systemError({
335
+ _tag: "InvalidData",
336
+ module: "ChildProcessSpawner",
337
+ method: "spawn",
338
+ description: "command must be an immutable supported process description"
339
+ })
340
+ }).pipe(Effect.map((snapshot) => Rooted.command(snapshot, rooted)), Effect.flatMap((rooted) => check(rooted).pipe(Effect.andThen(confine(rooted)), Effect.flatMap(spawner.spawn)))))));
341
+ }));
342
+ //# sourceMappingURL=ChildProcessSpawner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChildProcessSpawner.js","sourceRoot":"","sources":["../../src/ChildProcessSpawner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Permission-aware process spawning.\n *\n * Smithers has no shell service of its own — process execution is Effect's\n * `effect/unstable/process`. The only thing the kernel adds is the\n * `proc:spawn` capability check and the host confinement wrapper, applied to\n * the one primitive every other helper is derived from.\n *\n * There is no kernel spawner interface and no kernel spawner tag. Effect owns\n * both, and its tag fixes the error channel to `PlatformError`, so this module\n * is only the middleware: a `Layer` over Effect's own tag. A denied command is\n * projected into `PlatformError` by `Permission.toPlatformError`, which keeps\n * the structured kernel failure on the error's `cause`. Because the channel\n * stays `PlatformError`, the full six-method surface is derived from the one\n * guarded `spawn` through Effect's own `ChildProcessSpawner.make` without a\n * cast.\n *\n * Governing design:\n * `docs/specs/Concepts/Permission Kernel.md` and\n * `docs/specs/Concepts/Host Adapters.md`.\n *\n * @since 1.0.0-rc.0\n */\n\nimport { toPlatformError } from \"@smthrs/capability/Permission\"\nimport { Effect, FileSystem, Layer, Option, Path, type Scope } from \"effect\"\nimport { type PlatformError, systemError } from \"effect/PlatformError\"\nimport * as ChildProcess from \"effect/unstable/process/ChildProcess\"\nimport { ChildProcessSpawner, make as makeSpawner } from \"effect/unstable/process/ChildProcessSpawner\"\nimport * as CommandLine from \"./CommandLine.ts\"\nimport { GrantStore } from \"./GrantStore.ts\"\nimport * as Containment from \"./internal/Containment.ts\"\nimport { makeCapability } from \"./internal/makeCapability.ts\"\nimport * as ProcessConfinement from \"./ProcessConfinement.ts\"\nimport * as Rooted from \"./Rooted.ts\"\nimport { Workspace } from \"./Workspace.ts\"\n\nconst data = (value: object, name: PropertyKey): unknown => {\n const descriptor = Object.getOwnPropertyDescriptor(value, name)\n if (descriptor === undefined || !(\"value\" in descriptor)) {\n throw new TypeError(`command ${String(name)} must be a data property`)\n }\n return descriptor.value\n}\n\nconst optionalData = (value: object, name: PropertyKey): unknown => {\n const descriptor = Object.getOwnPropertyDescriptor(value, name)\n if (descriptor === undefined) return undefined\n if (!(\"value\" in descriptor)) throw new TypeError(`command ${String(name)} must be a data property`)\n return descriptor.value\n}\n\nconst snapshotRecord = (\n value: Readonly<Record<string, string | undefined>> | undefined\n): Record<string, string | undefined> | undefined => {\n if (value === undefined) return undefined\n const snapshot: Record<string, string | undefined> = Object.create(null)\n for (const [name, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {\n if (!descriptor.enumerable) continue\n if (!(\"value\" in descriptor) || (descriptor.value !== undefined && typeof descriptor.value !== \"string\")) {\n throw new TypeError(\"command environment must contain string data properties\")\n }\n snapshot[name] = descriptor.value\n }\n return Object.freeze(snapshot)\n}\n\nconst snapshotStreamConfig = <A>(value: A): A => {\n if (typeof value !== \"object\" || value === null) return value\n const descriptors = Object.getOwnPropertyDescriptors(value)\n if (descriptors.stream === undefined) return value\n if (!(\"value\" in descriptors.stream)) throw new TypeError(\"command stream must be a data property\")\n const snapshot: Record<string, unknown> = { stream: descriptors.stream.value }\n for (const name of [\"endOnDone\", \"encoding\"] as const) {\n const descriptor = descriptors[name]\n if (descriptor !== undefined) {\n if (!(\"value\" in descriptor)) throw new TypeError(`command ${name} must be a data property`)\n snapshot[name] = descriptor.value\n }\n }\n return Object.freeze(snapshot) as A\n}\n\nconst snapshotAdditionalFds = (\n value: ChildProcess.CommandOptions[\"additionalFds\"]\n): ChildProcess.CommandOptions[\"additionalFds\"] => {\n if (value === undefined) return undefined\n const snapshot: Record<`fd${number}`, ChildProcess.AdditionalFdConfig> = {}\n for (const [name, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {\n if (!descriptor.enumerable) continue\n if (!(\"value\" in descriptor) || typeof descriptor.value !== \"object\" || descriptor.value === null) {\n throw new TypeError(\"additional file descriptors must be data records\")\n }\n const config = descriptor.value as ChildProcess.AdditionalFdConfig\n const type = data(config, \"type\")\n if (type === \"input\") {\n const stream = Object.getOwnPropertyDescriptor(config, \"stream\")\n snapshot[name as `fd${number}`] = Object.freeze({\n type,\n ...(stream === undefined ? {} : \"value\" in stream ? { stream: stream.value } : (() => {\n throw new TypeError(\"additional fd stream must be a data property\")\n })())\n })\n } else if (type === \"output\") {\n const sink = Object.getOwnPropertyDescriptor(config, \"sink\")\n snapshot[name as `fd${number}`] = Object.freeze({\n type,\n ...(sink === undefined ? {} : \"value\" in sink ? { sink: sink.value } : (() => {\n throw new TypeError(\"additional fd sink must be a data property\")\n })())\n })\n } else {\n throw new TypeError(\"additional file descriptor type is invalid\")\n }\n }\n return Object.freeze(snapshot)\n}\n\nconst snapshotOptions = (options: ChildProcess.CommandOptions): ChildProcess.CommandOptions =>\n Object.freeze({\n killSignal: options.killSignal,\n forceKillAfter: options.forceKillAfter,\n cwd: options.cwd,\n env: snapshotRecord(options.env),\n extendEnv: options.extendEnv,\n shell: options.shell,\n detached: options.detached,\n windowsHide: options.windowsHide,\n stdin: snapshotStreamConfig(options.stdin),\n stdout: snapshotStreamConfig(options.stdout),\n stderr: snapshotStreamConfig(options.stderr),\n additionalFds: snapshotAdditionalFds(options.additionalFds)\n })\n\nconst snapshotCommand = (command: ChildProcess.Command): ChildProcess.Command => {\n const tag = data(command, \"_tag\")\n if (tag === \"StandardCommand\") {\n const executable = data(command, \"command\")\n const args = data(command, \"args\")\n const options = data(command, \"options\")\n if (typeof executable !== \"string\" || !Array.isArray(args) || !args.every((arg) => typeof arg === \"string\")) {\n throw new TypeError(\"standard command executable and arguments are invalid\")\n }\n if (typeof options !== \"object\" || options === null) throw new TypeError(\"command options are invalid\")\n return ChildProcess.make(\n executable,\n Object.freeze([...args]),\n snapshotOptions(options)\n )\n }\n if (tag === \"PipedCommand\") {\n const left = data(command, \"left\")\n const right = data(command, \"right\")\n const options = data(command, \"options\")\n if (typeof left !== \"object\" || left === null || typeof right !== \"object\" || right === null) {\n throw new TypeError(\"pipeline commands are invalid\")\n }\n if (typeof options !== \"object\" || options === null) throw new TypeError(\"pipeline options are invalid\")\n const from = optionalData(options, \"from\")\n const to = optionalData(options, \"to\")\n return ChildProcess.pipeTo(\n snapshotCommand(right as ChildProcess.Command),\n Object.freeze({\n ...(from === undefined ? {} : { from: from as ChildProcess.PipeFromOption }),\n ...(to === undefined ? {} : { to: to as ChildProcess.PipeToOption })\n })\n )(snapshotCommand(left as ChildProcess.Command))\n }\n throw new TypeError(\"unsupported command shape\")\n}\n\nconst maximumEnvironmentNames = 64\n\n/**\n * The environment this process holds, which a child built with\n * `ChildProcessEnvironment.make` inherits its bootstrap names from. Empty on a\n * host with no process environment, so every declared bootstrap name counts\n * as an override there.\n */\nconst ambientEnvironment = (): Readonly<Record<string, string | undefined>> =>\n (globalThis as { readonly process?: { readonly env?: Record<string, string | undefined> } }).process?.env ?? {}\n\nconst environmentNames = (command: ChildProcess.StandardCommand): ReadonlyArray<string> | undefined => {\n const environment = command.options.env\n if (environment === undefined) return undefined\n const names = Object.entries(environment)\n .filter(([, value]) => value !== undefined)\n .map(([name]) => name)\n .sort()\n if (names.length === 0) return undefined\n return names.length <= maximumEnvironmentNames\n ? names\n : [...names.slice(0, maximumEnvironmentNames), `+${names.length - maximumEnvironmentNames} more`]\n}\n\n/**\n * Effect's own process spawner tag, unchanged. Re-exported so the kernel\n * namespace stays one-stop; it is the *same* tag, never a second one.\n *\n * @category services\n * @since 1.0.0-rc.0\n */\nexport { ChildProcessSpawner } from \"effect/unstable/process/ChildProcessSpawner\"\n\n/**\n * Derives the full six-method surface from one `spawn`, so `exitCode`,\n * `string`, `lines`, and both `stream*` helpers can never bypass whatever\n * `spawn` was given.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n */\nexport { make } from \"effect/unstable/process/ChildProcessSpawner\"\n\n/**\n * Constructs an unavailable spawner stub.\n *\n * Every derived helper reports the missing host as a `NotFound`\n * `PlatformError` naming the command line, so an unconfigured capability\n * answers rather than vanishing.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n */\nexport const makeNoop = (\n overrides: Partial<ChildProcessSpawner[\"Service\"]> = {}\n): ChildProcessSpawner[\"Service\"] => ({\n ...makeSpawner((command) =>\n Effect.fail(\n systemError({\n _tag: \"NotFound\",\n module: \"ChildProcessSpawner\",\n method: \"spawn\",\n description: `no process host for \\`${CommandLine.render(command)}\\``\n })\n )\n ),\n ...overrides\n})\n\n/**\n * Provides an unavailable spawner stub.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerNoop = (\n overrides: Partial<ChildProcessSpawner[\"Service\"]> = {}\n): Layer.Layer<ChildProcessSpawner> => Layer.succeed(ChildProcessSpawner)(makeNoop(overrides))\n\n/**\n * Decorates the process spawner in place with a `proc:spawn` capability check.\n *\n * The check is suspended into the spawn itself, so building a `Command` or a\n * stream neither requests permission nor starts a process; only running one\n * does. Every pipeline stage is checked as its own capability, left to right,\n * and no stage starts until all pass. A stage's resource is\n * `CommandLine.resource`: the command line `CommandLine.render` produces, with\n * a `shell: true` line that holds shell control syntax marked as\n * `sh -c '<line>'`, any environment variable the child would not inherit from\n * this process named in an `env <NAME>… -- ` prefix, and a working directory\n * outside `Workspace.root` named in a `cwd <path> -- ` prefix. Custom shell\n * paths are included explicitly in the resource; browser and remote adapters\n * reject them rather than silently substituting a different shell.\n *\n * Every approved stage requires `ProcessConfinement` in the host context.\n * A missing service refuses spawning; its `layerNoop` is the explicit choice\n * for an unconfined host.\n *\n * Pipeline `from`/`to` routing remains outside the grant. The working\n * directory and overridden environment names also reach attended surfaces as\n * display metadata; environment values never leave the command.\n *\n * The layer provides the tag it also requires: compose it over a host spawner\n * layer with `Layer.provide` and a `ChildProcess.Command` run as a plain\n * `Effect` is checked too.\n *\n * A command with no `cwd`, or a relative one, runs in `Workspace.root`, never\n * the process's own directory; the check sees that resolved directory. When\n * the host filesystem is in the layer's context, the working directory and\n * `Workspace.root` are compared by real path, so a symlink inside the\n * workspace that leads out of it is named, as its target, in the `cwd` prefix.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layer: Layer.Layer<\n ChildProcessSpawner,\n never,\n ChildProcessSpawner | GrantStore | Path.Path | Workspace\n> = Layer.effect(\n ChildProcessSpawner,\n Effect.gen(function*() {\n const spawner = yield* ChildProcessSpawner\n const grants = yield* GrantStore\n const confinement = yield* Effect.serviceOption(ProcessConfinement.ProcessConfinement)\n const path = yield* Path.Path\n const rooted = Rooted.path(path, path.resolve((yield* Workspace).root))\n const root = rooted.resolve(\".\")\n // The raw host filesystem this layer is composed over, when there is one.\n // A symlink inside the workspace can lead out of it, so containment is\n // judged on real paths; a directory that cannot be resolved (it does not\n // exist, or the host has no filesystem) is judged lexically.\n const fileSystem = yield* Effect.serviceOption(FileSystem.FileSystem)\n const real = (directory: string) =>\n Option.match(fileSystem, {\n onNone: () => Effect.succeed(directory),\n onSome: (fs) => fs.realPath(directory).pipe(Effect.orElseSucceed(() => directory))\n })\n const within = (base: string) => (directory: string) => {\n const relative = path.relative(base, directory)\n return relative.split(path.sep)[0] !== \"..\" && !path.isAbsolute(relative)\n }\n const checkStage = (stage: ChildProcess.StandardCommand) =>\n Effect.gen(function*() {\n // Rooted.command gives every stage an absolute cwd.\n const cwd = stage.options.cwd!\n const realRoot = yield* real(root)\n const resolvedCwd = yield* real(cwd)\n const contains = resolvedCwd === cwd\n ? (directory: string) => within(root)(directory) || within(realRoot)(directory)\n : within(realRoot)\n return yield* checkResolved(\n stage,\n CommandLine.resource(stage, {\n ambient: ambientEnvironment(),\n contains,\n resolvedCwd\n })\n )\n })\n const checkResolved = (stage: ChildProcess.StandardCommand, rendered: string) => {\n const environment = environmentNames(stage)\n return makeCapability(\"proc:spawn\", rendered).pipe(\n Effect.flatMap((capability) =>\n grants.check(capability, {\n cwd: stage.options.cwd,\n ...(environment === undefined ? {} : { env: environment })\n })\n ),\n Effect.mapError((error) =>\n toPlatformError({\n module: \"ChildProcessSpawner\",\n method: \"spawn\",\n pathOrDescriptor: rendered,\n error\n })\n )\n )\n }\n const check = (command: ChildProcess.Command) =>\n Effect.forEach(CommandLine.stages(command), checkStage, { discard: true })\n const confine = (\n command: ChildProcess.Command\n ): Effect.Effect<\n ChildProcess.Command,\n PlatformError,\n Scope.Scope\n > =>\n Option.match(confinement, {\n onNone: () =>\n Effect.fail(systemError({\n _tag: \"NotFound\",\n module: \"ProcessConfinement\",\n method: \"confine\",\n description:\n \"process confinement is required; use ProcessConfinement.layerNoop to explicitly run unconfined\"\n })),\n onSome: (service) =>\n Effect.gen(function*() {\n const profile = yield* ProcessConfinement.profile(grants, root, path).pipe(\n Effect.mapError((error) => toPlatformError({ module: \"ChildProcessSpawner\", method: \"spawn\", error }))\n )\n const wrap = (\n command: ChildProcess.Command\n ): Effect.Effect<\n ChildProcess.Command,\n PlatformError,\n Scope.Scope\n > =>\n command._tag === \"StandardCommand\"\n ? service.confine(command, profile)\n : Effect.gen(function*() {\n const left = yield* wrap(command.left)\n const right = yield* wrap(command.right)\n return ChildProcess.pipeTo(right, command.options)(left)\n })\n return yield* wrap(command)\n })\n })\n return Containment.inherit(\n spawner,\n makeSpawner(\n Effect.fn(\"ChildProcessSpawner.spawn\")((command: ChildProcess.Command) =>\n Effect.try({\n try: () => snapshotCommand(command),\n catch: () =>\n systemError({\n _tag: \"InvalidData\",\n module: \"ChildProcessSpawner\",\n method: \"spawn\",\n description: \"command must be an immutable supported process description\"\n })\n }).pipe(\n Effect.map((snapshot) => Rooted.command(snapshot, rooted)),\n Effect.flatMap((rooted) =>\n check(rooted).pipe(Effect.andThen(confine(rooted)), Effect.flatMap(spawner.spawn))\n )\n )\n )\n )\n )\n })\n)\n"]}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @since 1.0.0-rc.0
3
+ *
4
+ * Rendering an `effect/unstable/process` `Command` back to a shell command
5
+ * line.
6
+ *
7
+ * Two callers need the same string and must agree on it exactly:
8
+ *
9
+ * 1. `@smthrs/kernel/ChildProcessSpawner` uses it as the `proc:spawn`
10
+ * capability resource, so a grant reads the way an operator wrote it; and
11
+ * 2. `@smthrs/platform-browser/BrowserChildProcessSpawner` uses it as the line
12
+ * it hands to the in-browser bash interpreter, which has no `argv` to spawn
13
+ * with.
14
+ *
15
+ * A single renderer keeps a granted capability and the command a browser
16
+ * actually runs from drifting apart.
17
+ *
18
+ * The module is pure string handling — no host access — so it stays on the
19
+ * browser-safe side of the package.
20
+ */
21
+ import type * as ChildProcess from "effect/unstable/process/ChildProcess";
22
+ /**
23
+ * Quotes one token for a POSIX shell, leaving obviously safe tokens alone.
24
+ *
25
+ * @category rendering
26
+ * @since 1.0.0-rc.0
27
+ */
28
+ export declare const quote: (token: string) => string;
29
+ /**
30
+ * Renders a `Command` as a single shell command line.
31
+ *
32
+ * A standard command with `shell: true` renders its tokens verbatim, matching
33
+ * the line Node and the browser interpreter hand to the default shell. A
34
+ * custom shell renders as an explicit `<shell> -c <line>` invocation so the
35
+ * selected executable is part of the permission resource. Without `shell`,
36
+ * every token is POSIX-quoted to preserve literal argv semantics. These
37
+ * distinctions are security-sensitive: the rendered value is also the
38
+ * `proc:spawn` capability resource, so it must describe what will execute.
39
+ * The rendering is POSIX-only by contract: on Windows, Node invokes the shell
40
+ * with `/d /s /c` rather than `-c`, so the rendered line describes the POSIX
41
+ * invocation, not a cmd.exe one.
42
+ *
43
+ * The `proc:spawn` capability resource is {@link resource}, which starts from
44
+ * this line and adds what the line alone cannot show.
45
+ *
46
+ * A `PipedCommand` renders with `|` between its sides. That is a faithful
47
+ * rendering of what the pipeline does, and it is the only form an interpreter
48
+ * that takes a command line rather than an `argv` can be given. `from`/`to`
49
+ * pipe options are *not* expressible this way; rendering ignores them, so a
50
+ * pipeline that redirects `stderr` renders like one that pipes `stdout`.
51
+ * Capability checks therefore see the commands, never the plumbing.
52
+ *
53
+ * @category rendering
54
+ * @since 1.0.0-rc.0
55
+ */
56
+ export declare const render: (command: ChildProcess.Command) => string;
57
+ /**
58
+ * What {@link resource} compares a stage against, supplied by the spawner that
59
+ * checks it.
60
+ *
61
+ * @category models
62
+ * @since 1.0.0-rc.1
63
+ */
64
+ export interface ResourceContext {
65
+ /**
66
+ * The environment the spawning process itself holds. A bootstrap name
67
+ * (`PATH`, `HOME`, ...) whose declared value equals the ambient value is the
68
+ * inherited default and stays out of the resource; one set to anything else
69
+ * is named. Omitted, every declared bootstrap name is named.
70
+ */
71
+ readonly ambient?: Readonly<Record<string, string | undefined>> | undefined;
72
+ /**
73
+ * Whether a stage's working directory is inside the directory the flow
74
+ * already governs. A `cwd` it rejects is named in the resource. Omitted,
75
+ * every `cwd` a stage sets is named.
76
+ */
77
+ readonly contains?: ((directory: string) => boolean) | undefined;
78
+ /**
79
+ * The stage `cwd` with every symlink resolved, when the host could resolve
80
+ * it. It replaces the lexical `cwd` both for `contains` and in the
81
+ * `cwd <path> -- ` prefix, so a symlink inside the governed directory that
82
+ * leads out of it is judged, and shown, by where the command really runs.
83
+ */
84
+ readonly resolvedCwd?: string | undefined;
85
+ }
86
+ /**
87
+ * The stages of a command in execution order: the command itself, or every
88
+ * `StandardCommand` of a pipeline from left to right. The spawner checks each
89
+ * stage as its own `proc:spawn` capability.
90
+ *
91
+ * @category rendering
92
+ * @since 1.0.0-rc.1
93
+ */
94
+ export declare const stages: (command: ChildProcess.Command) => ReadonlyArray<ChildProcess.StandardCommand>;
95
+ /**
96
+ * The `proc:spawn` capability resource for one stage: {@link render}, with
97
+ * three additions that keep a prefix grant from authorizing code the stage
98
+ * never named.
99
+ *
100
+ * - A `shell: true` stage whose line holds shell control syntax (`;`, `&`,
101
+ * `|`, `` ` ``, `$`, `<`, `>`, `(`, `)`, or a line break) is marked as the
102
+ * explicit `sh -c '<line>'` it is. A grant such as `git status *` then
103
+ * cannot match `git status; curl x | sh`, while a simple shell line such as
104
+ * `git status --short` keeps its verbatim resource. An fd duplication such
105
+ * as `2>&1` and a discard to `/dev/null` do not count as control syntax.
106
+ * - A stage that sets an environment variable the child would not otherwise
107
+ * inherit is prefixed with `env <NAME>… -- `, sorted names only. That is
108
+ * every name outside the bootstrap set `ChildProcessEnvironment` inherits
109
+ * (`PATH`, `HOME`, `USER`, `LANG`, `TERM`, `TMPDIR`, `SHELL`, `LC_*`), and a
110
+ * bootstrap name whose value differs from `context.ambient`. A grant for
111
+ * `git status` covers neither `env GIT_SSH_COMMAND -- git status` nor
112
+ * `env PATH -- git status`. Values never enter the resource because they
113
+ * often carry credentials.
114
+ * - A stage whose `cwd` `context.contains` rejects is prefixed with
115
+ * `cwd <path> -- `, so a grant does not follow the command into a
116
+ * directory whose configuration the flow does not govern. When
117
+ * `context.resolvedCwd` is given, that real path is judged and named
118
+ * instead of the lexical `cwd`.
119
+ *
120
+ * A pipeline has no single resource: the spawner checks every one of its
121
+ * {@link stages}, so `git status | sh` needs a grant for `sh` too.
122
+ *
123
+ * The `env` and `cwd` prefixes share their spelling with a literal command:
124
+ * `make("env", ["PATH", "--", "git", "status"])` renders as
125
+ * `env PATH -- git status`, the resource of `git status` with `PATH`
126
+ * overridden. The alias grants nothing extra. The literal command runs a
127
+ * program named `PATH` (or `cwd`) from the ambient `PATH`, not `git`. A grant
128
+ * with `*` inside the prefix, such as `env * -- git status`, also matches
129
+ * `env -C /x -- git status`, but it already admits any variable name,
130
+ * including `GIT_SSH_COMMAND` and `LD_PRELOAD`, which run arbitrary code.
131
+ * Grant a prefixed resource exactly as the request shows it.
132
+ *
133
+ * @category rendering
134
+ * @since 1.0.0-rc.1
135
+ */
136
+ export declare const resource: (command: ChildProcess.StandardCommand, context?: ResourceContext) => string;
137
+ /**
138
+ * The executable a command runs, without its arguments.
139
+ *
140
+ * A durable record of a spawned process names its program with this rather
141
+ * than with {@link render}: arguments carry credentials (`curl -u user:pass`,
142
+ * `mysql -phunter2`), and a journal that keeps them keeps them permanently.
143
+ * Nothing that reads such a record needs more than the program name; a reaper
144
+ * matches processes by pid and process group.
145
+ *
146
+ * Without `shell`, `command` is the executable itself, spaces and all. With a
147
+ * shell, the line is what runs, so the program is its first token: both
148
+ * `make("mysql -phunter2", [], { shell: true })` and
149
+ * `make("mysql", ["-phunter2"])` yield `mysql`. A pipeline names one
150
+ * executable per stage, joined the way {@link render} joins the stages.
151
+ *
152
+ * @category rendering
153
+ * @since 1.0.0-rc.0
154
+ */
155
+ export declare const executable: (command: ChildProcess.Command) => string;
156
+ /**
157
+ * The working directory a command runs in, taking the leftmost stage of a
158
+ * pipeline, which is the stage `setCwd` and the spawners agree to treat as the
159
+ * pipeline's own directory.
160
+ *
161
+ * @category rendering
162
+ * @since 1.0.0-rc.0
163
+ */
164
+ export declare const cwd: (command: ChildProcess.Command) => string | undefined;
165
+ /**
166
+ * The environment overrides a command runs with, taking the leftmost stage of
167
+ * a pipeline for the same reason {@link cwd} does.
168
+ *
169
+ * @category rendering
170
+ * @since 1.0.0-rc.0
171
+ */
172
+ export declare const env: (command: ChildProcess.Command) => Record<string, string | undefined> | undefined;
173
+ //# sourceMappingURL=CommandLine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommandLine.d.ts","sourceRoot":"","sources":["../../src/CommandLine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,KAAK,YAAY,MAAM,sCAAsC,CAAA;AAMzE;;;;;GAKG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,KAAG,MAC4C,CAAA;AAElF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,MAAM,GAAI,SAAS,YAAY,CAAC,OAAO,KAAG,MAOG,CAAA;AAiB1D;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,CAAA;IAC3E;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,SAAS,CAAA;IAChE;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1C;AAgBD;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,GAAI,SAAS,YAAY,CAAC,OAAO,KAAG,aAAa,CAAC,YAAY,CAAC,eAAe,CACK,CAAA;AAEtG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,QAAQ,GAAI,SAAS,YAAY,CAAC,eAAe,EAAE,UAAS,eAAoB,KAAG,MAW/F,CAAA;AASD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,UAAU,GAAI,SAAS,YAAY,CAAC,OAAO,KAAG,MAKO,CAAA;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,GAAG,GAAI,SAAS,YAAY,CAAC,OAAO,KAAG,MAAM,GAAG,SACiB,CAAA;AAE9E;;;;;;GAMG;AACH,eAAO,MAAM,GAAG,GACd,SAAS,YAAY,CAAC,OAAO,KAC5B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,SACsC,CAAA"}