@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
package/src/Rooted.ts ADDED
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Filesystem, path and process services bound to one root directory.
3
+ *
4
+ * Node resolves a relative path against `process.cwd()`. A host serves one
5
+ * directory whatever the process's own, so here every relative path, and every
6
+ * command spawned without a `cwd`, resolves against `root`. Absolute paths pass
7
+ * through unchanged, and so does a symlink's target, which is relative to the
8
+ * link rather than to any directory.
9
+ *
10
+ * @since 1.0.0
11
+ */
12
+
13
+ import { Context, Effect, FileSystem, Layer, Path } from "effect"
14
+ import * as ChildProcess from "effect/unstable/process/ChildProcess"
15
+ import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"
16
+ import * as Containment from "./internal/Containment.ts"
17
+
18
+ /**
19
+ * `base` with every relative path resolved against the absolute `root`.
20
+ *
21
+ * @category constructors
22
+ * @since 1.0.0
23
+ */
24
+ export const path = (base: Path.Path, root: string): Path.Path => {
25
+ const resolve = (...segments: ReadonlyArray<string>) => base.resolve(root, ...segments)
26
+ return {
27
+ ...base,
28
+ resolve,
29
+ relative: (from, to) => base.relative(resolve(from), resolve(to)),
30
+ toFileUrl: (value) => base.toFileUrl(resolve(value))
31
+ }
32
+ }
33
+
34
+ /**
35
+ * `base` with every relative path resolved by `rooted`, a {@link path}.
36
+ *
37
+ * @category constructors
38
+ * @since 1.0.0
39
+ */
40
+ export const fileSystem = (base: FileSystem.FileSystem, rooted: Path.Path): FileSystem.FileSystem => {
41
+ const at = (value: string) => rooted.resolve(value)
42
+ const temp = <O extends { readonly directory?: string | undefined }>(options: O | undefined) =>
43
+ options?.directory === undefined ? options : { ...options, directory: at(options.directory) }
44
+ return {
45
+ ...base,
46
+ access: (value, options) => base.access(at(value), options),
47
+ copy: (from, to, options) => base.copy(at(from), at(to), options),
48
+ copyFile: (from, to) => base.copyFile(at(from), at(to)),
49
+ chmod: (value, mode) => base.chmod(at(value), mode),
50
+ chown: (value, uid, gid) => base.chown(at(value), uid, gid),
51
+ glob: (pattern, options) => base.glob(pattern, { ...options, root: at(options?.root ?? ".") }),
52
+ exists: (value) => base.exists(at(value)),
53
+ link: (from, to) => base.link(at(from), at(to)),
54
+ makeDirectory: (value, options) => base.makeDirectory(at(value), options),
55
+ makeTempDirectory: (options) => base.makeTempDirectory(temp(options)),
56
+ makeTempDirectoryScoped: (options) => base.makeTempDirectoryScoped(temp(options)),
57
+ makeTempFile: (options) => base.makeTempFile(temp(options)),
58
+ makeTempFileScoped: (options) => base.makeTempFileScoped(temp(options)),
59
+ open: (value, options) => base.open(at(value), options),
60
+ readDirectory: (value, options) => base.readDirectory(at(value), options),
61
+ readFile: (value) => base.readFile(at(value)),
62
+ readFileString: (value, encoding) => base.readFileString(at(value), encoding),
63
+ readLink: (value) => base.readLink(at(value)),
64
+ realPath: (value) => base.realPath(at(value)),
65
+ remove: (value, options) => base.remove(at(value), options),
66
+ rename: (from, to) => base.rename(at(from), at(to)),
67
+ sink: (value, options) => base.sink(at(value), options),
68
+ stat: (value) => base.stat(at(value)),
69
+ stream: (value, options) => base.stream(at(value), options),
70
+ symlink: (target, value) => base.symlink(target, at(value)),
71
+ truncate: (value, length) => base.truncate(at(value), length),
72
+ utimes: (value, atime, mtime) => base.utimes(at(value), atime, mtime),
73
+ watch: (value, options) => base.watch(at(value), options),
74
+ writeFile: (value, data, options) => base.writeFile(at(value), data, options),
75
+ writeFileString: (value, data, options) => base.writeFileString(at(value), data, options)
76
+ }
77
+ }
78
+
79
+ /**
80
+ * `value` with every stage's `cwd` resolved by `rooted`, a {@link path}; its
81
+ * root for a stage without one.
82
+ *
83
+ * @category constructors
84
+ * @since 1.0.0
85
+ */
86
+ export const command = (value: ChildProcess.Command, rooted: Path.Path): ChildProcess.Command =>
87
+ ChildProcess.isStandardCommand(value)
88
+ ? ChildProcess.setCwd(value, rooted.resolve(value.options.cwd ?? "."))
89
+ : ChildProcess.pipeTo(command(value.left, rooted), command(value.right, rooted), value.options)
90
+
91
+ /**
92
+ * `base` spawning each command as {@link command} roots it. A contained `base`
93
+ * stays contained.
94
+ *
95
+ * @category constructors
96
+ * @since 1.0.0
97
+ */
98
+ export const spawner = (
99
+ base: ChildProcessSpawner.ChildProcessSpawner["Service"],
100
+ rooted: Path.Path
101
+ ): ChildProcessSpawner.ChildProcessSpawner["Service"] =>
102
+ Containment.inherit(base, ChildProcessSpawner.make((value) => base.spawn(command(value, rooted))))
103
+
104
+ /**
105
+ * Decorates the `FileSystem`, `Path` and `ChildProcessSpawner` it is provided
106
+ * in place, so everything downstream resolves against `root`.
107
+ *
108
+ * @category layers
109
+ * @since 1.0.0
110
+ */
111
+ export const layer = (
112
+ root: string
113
+ ): Layer.Layer<
114
+ FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner,
115
+ never,
116
+ FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner
117
+ > =>
118
+ Layer.effectContext(Effect.gen(function*() {
119
+ const base = yield* Path.Path
120
+ const rooted = path(base, base.resolve(root))
121
+ return Context.make(Path.Path, rooted).pipe(
122
+ Context.add(FileSystem.FileSystem, fileSystem(yield* FileSystem.FileSystem, rooted)),
123
+ Context.add(
124
+ ChildProcessSpawner.ChildProcessSpawner,
125
+ spawner(yield* ChildProcessSpawner.ChildProcessSpawner, rooted)
126
+ )
127
+ )
128
+ }))
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Typed host connectivity failures, including failed command diagnostics.
3
+ *
4
+ * @since 1.0.0
5
+ */
6
+
7
+ import * as Schema from "effect/Schema"
8
+
9
+ /**
10
+ * A temporarily unreachable resolver, connection, or HTTP service.
11
+ *
12
+ * @category errors
13
+ * @since 1.0.0
14
+ */
15
+ export class Unreachable extends Schema.TaggedError<Unreachable>()("@smthrs/kernel/Unreachable", {
16
+ message: Schema.String,
17
+ cause: Schema.optional(Schema.Unknown)
18
+ }) {}
19
+
20
+ const outage =
21
+ /Could not resolve host|Failed to connect to|Connection timed out|Operation timed out|Connection reset|Recv failure|Network is unreachable|Temporary failure in name resolution|SSL_ERROR_SYSCALL|The remote end hung up unexpectedly|\bEAI_AGAIN\b|\bENOTFOUND\b|\bHTTP(?:\/\d(?:\.\d)?)?\s*(?:(?:error|status|code)\s*[:=]?\s*)?[:=]?\s*(?:429|5\d{2})\b/i
22
+
23
+ /**
24
+ * Classifies stderr from a failed host command; other exits keep their domain error.
25
+ *
26
+ * @category constructors
27
+ * @since 1.0.0
28
+ */
29
+ export const classifyExit = (stderr: string): Unreachable | undefined =>
30
+ outage.test(stderr) ? new Unreachable({ message: stderr }) : undefined
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The workspace root used to make filesystem capability resources stable.
3
+ *
4
+ * It lives beside the closed host list rather than in a platform package: a
5
+ * workspace root is a policy decision the kernel needs before it can name a
6
+ * filesystem capability, not something a platform can answer.
7
+ *
8
+ * Governing design:
9
+ * `docs/specs/Concepts/Effect Taxonomy.md` and
10
+ * `docs/specs/Concepts/Permission Kernel.md`.
11
+ *
12
+ * @since 1.0.0-rc.0
13
+ */
14
+
15
+ import { Context, Layer } from "effect"
16
+
17
+ /**
18
+ * Workspace-root configuration.
19
+ *
20
+ * @category models
21
+ * @since 1.0.0-rc.0
22
+ */
23
+ export interface Service {
24
+ readonly root: string
25
+ }
26
+
27
+ /**
28
+ * The workspace-root service tag.
29
+ *
30
+ * @category services
31
+ * @since 1.0.0-rc.0
32
+ */
33
+ export class Workspace extends Context.Service<Workspace, Service>()("@smthrs/kernel/Workspace") {}
34
+
35
+ /**
36
+ * Constructs workspace-root configuration.
37
+ *
38
+ * @category constructors
39
+ * @since 1.0.0-rc.0
40
+ */
41
+ export const make = (root: string): Service => Workspace.of({ root })
42
+
43
+ /**
44
+ * Provides a workspace root.
45
+ *
46
+ * @category layers
47
+ * @since 1.0.0-rc.0
48
+ */
49
+ export const layer = (root: string): Layer.Layer<Workspace> => Layer.succeed(Workspace, make(root))
50
+
51
+ /**
52
+ * Constructs a relative workspace-root stub for tests.
53
+ *
54
+ * @category constructors
55
+ * @since 1.0.0-rc.0
56
+ */
57
+ export const makeNoop: Service = make(".")
58
+
59
+ /**
60
+ * Provides a relative workspace-root stub for tests.
61
+ *
62
+ * @category layers
63
+ * @since 1.0.0-rc.0
64
+ */
65
+ export const layerNoop: Layer.Layer<Workspace> = Layer.succeed(Workspace, makeNoop)
package/src/index.ts ADDED
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Capability enforcement at the Host boundary.
3
+ *
4
+ * Every slot of the closed Host surface is decorated in place: a middleware
5
+ * `Layer` over the very tag a platform adapter provides, checking a capability
6
+ * against a `GrantStore` before it delegates. There is no second, protected
7
+ * tag, so code that never heard of the kernel is guarded the moment the layer
8
+ * is composed over its host.
9
+ *
10
+ * @since 1.0.0-rc.0
11
+ */
12
+
13
+ /**
14
+ * Capability values, patterns, matching, and effect tiers.
15
+ *
16
+ * @category namespace exports
17
+ * @since 1.0.0-rc.0
18
+ */
19
+ export * as Capability from "@smthrs/capability/Capability"
20
+
21
+ /**
22
+ * Monotone ambient authority and intersection.
23
+ *
24
+ * @category namespace exports
25
+ * @since 1.0.0-rc.0
26
+ */
27
+ export * as CapabilitySet from "./CapabilitySet.ts"
28
+
29
+ /**
30
+ * The `proc:spawn` middleware over Effect's own `ChildProcessSpawner` tag.
31
+ *
32
+ * @category namespace exports
33
+ * @since 1.0.0-rc.0
34
+ */
35
+ export * as ChildProcessSpawner from "./ChildProcessSpawner.ts"
36
+
37
+ /**
38
+ * Least-authority child-process environment construction.
39
+ *
40
+ * @category namespace exports
41
+ * @since 1.0.0-rc.0
42
+ */
43
+ export * as ChildProcessEnvironment from "./ChildProcessEnvironment.ts"
44
+
45
+ /**
46
+ * Process containment middleware over Effect's own `ChildProcessSpawner` tag.
47
+ *
48
+ * @category namespace exports
49
+ * @since 1.0.0-rc.0
50
+ */
51
+ export * as ContainedSpawner from "./ContainedSpawner.ts"
52
+
53
+ /**
54
+ * Rendering `ChildProcess` commands back to command lines.
55
+ *
56
+ * @category namespace exports
57
+ * @since 1.0.0-rc.0
58
+ */
59
+ export * as CommandLine from "./CommandLine.ts"
60
+
61
+ /**
62
+ * The `fs:read`/`fs:write` middleware over Effect's own `FileSystem` tag.
63
+ *
64
+ * @category namespace exports
65
+ * @since 1.0.0-rc.0
66
+ */
67
+ export * as FileSystem from "./FileSystem.ts"
68
+
69
+ /**
70
+ * Durable grant decision schemas.
71
+ *
72
+ * @category namespace exports
73
+ * @since 1.0.0-rc.0
74
+ */
75
+ export * as GrantEvent from "./GrantEvent.ts"
76
+
77
+ /**
78
+ * Attended and unattended grant-store services.
79
+ *
80
+ * @category namespace exports
81
+ * @since 1.0.0-rc.0
82
+ */
83
+ export * as GrantStore from "./GrantStore.ts"
84
+
85
+ /**
86
+ * The union of failures the closed Host surface is allowed to produce.
87
+ *
88
+ * A type, not a namespace: process execution now fails with Effect's own
89
+ * `PlatformError`, so nothing is left here to construct.
90
+ *
91
+ * @category models
92
+ * @since 1.0.0-rc.0
93
+ */
94
+ export type { HostError } from "./HostError.ts"
95
+
96
+ /**
97
+ * The closed protected Host service layer.
98
+ *
99
+ * @category namespace exports
100
+ * @since 1.0.0-rc.0
101
+ */
102
+ export * as HostServices from "./HostServices.ts"
103
+
104
+ /**
105
+ * The `net:get`/`net:post`/`model:call` middleware over Effect's own
106
+ * `HttpClient` tag.
107
+ *
108
+ * @category namespace exports
109
+ * @since 1.0.0-rc.0
110
+ */
111
+ export * as HttpClient from "./HttpClient.ts"
112
+
113
+ /**
114
+ * The jj capability middleware over `@smthrs/jj`'s own `Jj` tag.
115
+ *
116
+ * @category namespace exports
117
+ * @since 1.0.0-rc.0
118
+ */
119
+ export * as Jj from "./Jj.ts"
120
+
121
+ /**
122
+ * Journal-backed grant persistence.
123
+ *
124
+ * @category namespace exports
125
+ * @since 1.0.0-rc.0
126
+ */
127
+ export * as JournalGrantStore from "./JournalGrantStore.ts"
128
+
129
+ /**
130
+ * The explicit pure path-service decision.
131
+ *
132
+ * @category namespace exports
133
+ * @since 1.0.0-rc.0
134
+ */
135
+ export * as Path from "./Path.ts"
136
+
137
+ /**
138
+ * Typed permission errors and policy rules.
139
+ *
140
+ * @category namespace exports
141
+ * @since 1.0.0-rc.0
142
+ */
143
+ export * as Permission from "@smthrs/capability/Permission"
144
+
145
+ /**
146
+ * The host's durable record of the processes it started.
147
+ *
148
+ * @category namespace exports
149
+ * @since 1.0.0-rc.0
150
+ */
151
+ export * as ProcessLedger from "./ProcessLedger.ts"
152
+
153
+ /**
154
+ * Filesystem, path and process services bound to one root directory.
155
+ *
156
+ * @category namespace exports
157
+ * @since 1.0.0
158
+ */
159
+ export * as Rooted from "./Rooted.ts"
160
+
161
+ /**
162
+ * The shared workspace-root service.
163
+ *
164
+ * @category namespace exports
165
+ * @since 1.0.0-rc.0
166
+ */
167
+ export * as Workspace from "./Workspace.ts"
168
+
169
+ /** Host process confinement.
170
+ * @category namespace exports
171
+ * @since 1.0.0-rc.1
172
+ */
173
+ export * as ProcessConfinement from "./ProcessConfinement.ts"
174
+
175
+ /**
176
+ * Typed connectivity failures and host command exit classification.
177
+ *
178
+ * @category namespace exports
179
+ * @since 1.0.0
180
+ */
181
+ export * as Unreachable from "./Unreachable.ts"
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Trusted composition metadata shared by contained and permission-aware spawners.
3
+ * @since 1.0.0
4
+ */
5
+
6
+ import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
7
+
8
+ // The registry key also works when a service crosses the ESM/CJS projections.
9
+ // This declares a trusted lifecycle contract; it cannot prove a supplied
10
+ // lifecycle is honest or turn process containment into a security sandbox.
11
+ const key = Symbol.for("@smthrs/kernel/ContainedSpawner/Lifecycle/v1")
12
+
13
+ /**
14
+ * Reads only an own data property, without invoking an accessor.
15
+ * @private
16
+ * @since 1.0.0
17
+ */
18
+ export const isContained = (spawner: ChildProcessSpawner["Service"]): boolean =>
19
+ Object.getOwnPropertyDescriptor(spawner, key)?.value === true
20
+
21
+ /**
22
+ * Marks a newly constructed service without copying through object spreads.
23
+ * @private
24
+ * @since 1.0.0
25
+ */
26
+ export const mark = (spawner: ChildProcessSpawner["Service"]): ChildProcessSpawner["Service"] =>
27
+ Object.defineProperty(spawner, key, { value: true, enumerable: false, writable: false, configurable: false })
28
+
29
+ /**
30
+ * Preserves the declaration on a wrapper that delegates its process lifetime.
31
+ * @private
32
+ * @since 1.0.0
33
+ */
34
+ export const inherit = (
35
+ source: ChildProcessSpawner["Service"],
36
+ wrapper: ChildProcessSpawner["Service"]
37
+ ): ChildProcessSpawner["Service"] => isContained(source) ? mark(wrapper) : wrapper
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Pipeline wiring around a per-process spawner, so every leg has its own lifecycle.
3
+ * @since 1.0.0
4
+ */
5
+
6
+ import * as Effect from "effect/Effect"
7
+ import * as Exit from "effect/Exit"
8
+ import type * as PlatformError from "effect/PlatformError"
9
+ import type * as Scope from "effect/Scope"
10
+ import * as Stream from "effect/Stream"
11
+ import * as ChildProcess from "effect/unstable/process/ChildProcess"
12
+ import { type ChildProcessHandle, makeHandle } from "effect/unstable/process/ChildProcessSpawner"
13
+
14
+ type Spawn = (
15
+ command: ChildProcess.StandardCommand
16
+ ) => Effect.Effect<ChildProcessHandle, PlatformError.PlatformError, Scope.Scope>
17
+
18
+ const source = (handle: ChildProcessHandle, from: ChildProcess.PipeFromOption | undefined) => {
19
+ if (from === "stderr") return handle.stderr
20
+ if (from === "all") return handle.all
21
+ const fd = from === undefined ? undefined : ChildProcess.parseFdName(from)
22
+ return fd === undefined ? handle.stdout : handle.getOutputFd(fd)
23
+ }
24
+
25
+ /** An edge feeds the first process of its destination, including a nested pipeline. */
26
+ const withInput = (
27
+ command: ChildProcess.Command,
28
+ stream: Stream.Stream<Uint8Array, PlatformError.PlatformError>,
29
+ to: ChildProcess.PipeToOption | undefined
30
+ ): ChildProcess.Command => {
31
+ if (command._tag === "PipedCommand") {
32
+ return ChildProcess.pipeTo(withInput(command.left, stream, to), command.right, command.options)
33
+ }
34
+ const fd = to === undefined ? undefined : ChildProcess.parseFdName(to)
35
+ if (fd !== undefined) {
36
+ return ChildProcess.make(command.command, command.args, {
37
+ ...command.options,
38
+ additionalFds: {
39
+ ...command.options.additionalFds,
40
+ [ChildProcess.fdName(fd)]: { type: "input", stream }
41
+ }
42
+ })
43
+ }
44
+ const stdin = command.options.stdin
45
+ return ChildProcess.make(command.command, command.args, {
46
+ ...command.options,
47
+ stdin: {
48
+ ...(typeof stdin === "object" && !Stream.isStream(stdin) ? stdin : {}),
49
+ stream
50
+ }
51
+ })
52
+ }
53
+
54
+ /**
55
+ * Preserve Effect's rightmost output/status and reverse-order kill/reref semantics.
56
+ * @private
57
+ * @since 1.0.0
58
+ */
59
+ export const spawn = (
60
+ command: ChildProcess.Command,
61
+ standard: Spawn
62
+ ): Effect.Effect<ChildProcessHandle, PlatformError.PlatformError, Scope.Scope> =>
63
+ Effect.gen(function*() {
64
+ if (command._tag === "StandardCommand") return yield* standard(command)
65
+ const left = yield* spawn(command.left, standard)
66
+ const right = yield* spawn(
67
+ withInput(command.right, source(left, command.options.from), command.options.to),
68
+ standard
69
+ )
70
+ return makeHandle({
71
+ ...right,
72
+ kill: (options) =>
73
+ Effect.gen(function*() {
74
+ const rightExit = yield* Effect.exit(right.kill(options))
75
+ const leftExit = yield* Effect.exit(left.kill(options))
76
+ if (Exit.isFailure(rightExit)) return yield* Effect.failCause(rightExit.cause)
77
+ return yield* leftExit
78
+ }),
79
+ unref: Effect.gen(function*() {
80
+ const leftReref = yield* left.unref
81
+ const rightReref = yield* right.unref
82
+ return Effect.andThen(rightReref, leftReref)
83
+ })
84
+ })
85
+ })
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Typed capability construction for host decorators.
3
+ *
4
+ * @since 1.0.0-rc.0
5
+ */
6
+
7
+ import * as Capability from "@smthrs/capability/Capability"
8
+ import { GrantStoreError } from "@smthrs/capability/Permission"
9
+ import { Effect } from "effect"
10
+
11
+ /**
12
+ * Constructs decorator requests without leaking schema failures as defects.
13
+ *
14
+ * @category constructors
15
+ * @since 1.0.0-rc.0
16
+ */
17
+ export const makeCapability = (action: Capability.Action, resource: string) =>
18
+ Effect.try({
19
+ try: () => Capability.make(action, resource),
20
+ catch: () =>
21
+ new GrantStoreError({
22
+ code: "invalid_resolution",
23
+ message:
24
+ `capability requires a valid action and a resource within maximumCapabilityResourceLength (${Capability.maxResourceLength} UTF-16 code units)`
25
+ })
26
+ })
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The optional atomic batch portion of the shared host contract.
3
+ *
4
+ * @since 1.0.0
5
+ */
6
+
7
+ import { expect } from "@effect/vitest"
8
+ import { Effect, type FileSystem, Option, Path, Result } from "effect"
9
+ import * as KernelFileSystem from "../FileSystem.ts"
10
+
11
+ /**
12
+ * Exercises every advertised batch implementation over the host contract's
13
+ * fresh `source.txt` fixture. Isolated volumes may omit this optimization.
14
+ *
15
+ * @since 1.0.0
16
+ * @category testing
17
+ */
18
+ export const check = (fs: FileSystem.FileSystem, root: string) =>
19
+ Effect.gen(function*() {
20
+ const atomic = (fs as Partial<KernelFileSystem.AtomicHostFileSystem>)[KernelFileSystem.AtomicFileSystemTypeId]
21
+ if (atomic?.batchLimits === undefined) {
22
+ expect(KernelFileSystem.batch(fs)).toBeUndefined()
23
+ return
24
+ }
25
+ const path = yield* Path.Path
26
+ const boundaryRoot = yield* fs.realPath(root)
27
+ const rootIdentity = atomic.identifyRoot === undefined
28
+ ? yield* Effect.map(fs.stat(root), (info) => `${info.dev}:${Option.getOrThrow(info.ino)}`)
29
+ : yield* atomic.identifyRoot(boundaryRoot)
30
+ const response = yield* atomic.execute({
31
+ operation: "batch",
32
+ boundaryRoot,
33
+ logicalRoot: root,
34
+ rootIdentity,
35
+ requests: [
36
+ { operation: "digest", path: path.join(root, "source.txt"), content: true },
37
+ { operation: "stat", path: path.join(root, "source.txt") },
38
+ { operation: "readDirectory", path: root },
39
+ { operation: "glob", path: path.join(root, "*.txt"), root },
40
+ { operation: "digest", path: path.join(root, "absent") }
41
+ ]
42
+ })
43
+ expect(response.rootIdentity).toBe(rootIdentity)
44
+ expect(response.entries.map((entry) => entry.index)).toEqual([2, 3, 4, 0, 1])
45
+ const entries = [...response.entries].sort((a, b) => a.index - b.index)
46
+ expect(Result.getOrThrow(entries[0]!.result)).toEqual({
47
+ operation: "digest",
48
+ digest: "0e1c618211c39c03726d50ee05e7e520ca6d8a2a3619b17983adc574e3ca34bf",
49
+ sizeBytes: 13,
50
+ bytes: new TextEncoder().encode("host-contract")
51
+ })
52
+ expect(Result.getOrThrow(entries[1]!.result)).toMatchObject({
53
+ operation: "stat",
54
+ info: { type: "File", size: 13n }
55
+ })
56
+ expect(Result.getOrThrow(entries[2]!.result)).toEqual({ operation: "readDirectory", paths: ["source.txt"] })
57
+ expect(Result.getOrThrow(entries[3]!.result)).toEqual({ operation: "glob", paths: [path.join(root, "source.txt")] })
58
+ expect(entries[4]!.result).toMatchObject({ _tag: "Failure", failure: { reason: { _tag: "NotFound" } } })
59
+ })