@prestack/thread 0.1.3 → 0.1.5

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 (270) hide show
  1. package/CHANGELOG.md +563 -0
  2. package/dist/core/actor.d.ts +1 -1
  3. package/dist/core/actor.js +5 -4
  4. package/dist/core/actor.ts +5 -4
  5. package/dist/core/attach.d.ts +0 -7
  6. package/dist/core/attach.js +38 -67
  7. package/dist/core/attach.ts +34 -80
  8. package/dist/core/chain.js +13 -17
  9. package/dist/core/chain.ts +11 -15
  10. package/dist/core/condition.d.ts +4 -4
  11. package/dist/core/condition.js +2 -2
  12. package/dist/core/descend.d.ts +4 -2
  13. package/dist/core/descend.js +91 -23
  14. package/dist/core/descend.ts +90 -21
  15. package/dist/core/errors/catalogue.d.ts +52 -0
  16. package/dist/{operations/errors/errors.js → core/errors/catalogue.js} +1 -1
  17. package/dist/{operations/errors/errors.ts → core/errors/catalogue.ts} +1 -1
  18. package/dist/{operations/errors/catalog.d.ts → core/errors/error.d.ts} +2 -3
  19. package/dist/{operations/errors/catalog.js → core/errors/error.js} +1 -16
  20. package/dist/{operations/errors/catalog.ts → core/errors/error.ts} +3 -19
  21. package/dist/core/errors/namespaces.d.ts +3 -0
  22. package/dist/core/errors/namespaces.js +15 -0
  23. package/dist/core/errors/namespaces.ts +19 -0
  24. package/dist/core/finding.js +2 -2
  25. package/dist/core/finding.ts +1 -2
  26. package/dist/core/init.d.ts +1 -1
  27. package/dist/core/init.js +51 -5
  28. package/dist/core/init.ts +58 -5
  29. package/dist/core/invoked.d.ts +1 -1
  30. package/dist/core/invoked.js +3 -1
  31. package/dist/core/invoked.ts +5 -2
  32. package/dist/core/round.js +36 -22
  33. package/dist/core/round.ts +26 -9
  34. package/dist/core/rows.d.ts +4 -0
  35. package/dist/core/rows.js +31 -2
  36. package/dist/core/rows.ts +33 -1
  37. package/dist/core/scope.d.ts +1 -0
  38. package/dist/core/scope.js +1 -0
  39. package/dist/core/scope.ts +2 -0
  40. package/dist/core/thread.d.ts +3 -3
  41. package/dist/core/tree.d.ts +4 -1
  42. package/dist/core/tree.js +26 -6
  43. package/dist/core/tree.ts +29 -6
  44. package/dist/core/types.d.ts +3 -3
  45. package/dist/core/types.js +3 -1
  46. package/dist/core/types.ts +2 -1
  47. package/dist/derivation/attach.js +3 -2
  48. package/dist/derivation/attach.ts +2 -1
  49. package/dist/derivation/generators/build.js +6 -6
  50. package/dist/derivation/generators/build.ts +1 -1
  51. package/dist/derivation/generators/check.js +1 -1
  52. package/dist/derivation/generators/determinism.js +1 -1
  53. package/dist/derivation/generators/release.js +60 -47
  54. package/dist/derivation/generators/release.ts +66 -43
  55. package/dist/derivation/release.d.ts +7 -3
  56. package/dist/derivation/release.js +14 -1
  57. package/dist/derivation/release.ts +22 -2
  58. package/dist/integrations/attach.d.ts +4 -0
  59. package/dist/integrations/attach.js +37 -56
  60. package/dist/integrations/attach.ts +54 -60
  61. package/dist/integrations/cli.d.ts +37 -0
  62. package/dist/integrations/cli.js +440 -0
  63. package/dist/integrations/cli.ts +543 -0
  64. package/dist/{core/files.d.ts → integrations/disk.d.ts} +16 -24
  65. package/dist/integrations/disk.js +435 -0
  66. package/dist/integrations/disk.ts +465 -0
  67. package/dist/integrations/generators/read.js +6 -6
  68. package/dist/integrations/generators/read.ts +4 -4
  69. package/dist/integrations/generators/vault.js +10 -11
  70. package/dist/integrations/generators/vault.ts +4 -5
  71. package/dist/{operations/http.d.ts → integrations/https.d.ts} +10 -6
  72. package/dist/integrations/https.js +170 -0
  73. package/dist/integrations/https.ts +216 -0
  74. package/dist/integrations/pglite.d.ts +26 -0
  75. package/dist/integrations/pglite.js +143 -0
  76. package/dist/integrations/pglite.ts +206 -0
  77. package/dist/integrations/secret.d.ts +8 -2
  78. package/dist/integrations/secret.js +3 -5
  79. package/dist/integrations/secret.ts +10 -6
  80. package/dist/integrations/target.d.ts +155 -14
  81. package/dist/integrations/target.js +171 -50
  82. package/dist/integrations/target.ts +377 -61
  83. package/dist/integrations/vault.d.ts +1 -0
  84. package/dist/integrations/vault.js +20 -5
  85. package/dist/integrations/vault.ts +17 -5
  86. package/dist/integrity/attach.js +8 -77
  87. package/dist/integrity/attach.ts +7 -76
  88. package/dist/integrity/check-threads.js +5 -5
  89. package/dist/integrity/check-threads.ts +2 -2
  90. package/dist/integrity/constraint.d.ts +0 -1
  91. package/dist/integrity/constraint.js +267 -366
  92. package/dist/integrity/constraint.ts +267 -375
  93. package/dist/integrity/generators/compiler.js +14 -17
  94. package/dist/integrity/generators/compiler.ts +10 -15
  95. package/dist/integrity/generators/gate.js +15 -15
  96. package/dist/integrity/generators/gate.ts +10 -10
  97. package/dist/integrity/generators/issues.js +7 -7
  98. package/dist/integrity/generators/publish.js +39 -57
  99. package/dist/integrity/generators/publish.ts +44 -55
  100. package/dist/integrity/generators/trial.d.ts +3 -3
  101. package/dist/integrity/generators/trial.js +133 -165
  102. package/dist/integrity/generators/trial.ts +145 -169
  103. package/dist/integrity/lint.js +9 -13
  104. package/dist/integrity/lint.ts +7 -10
  105. package/dist/integrity/lsp/bin.js +5 -4
  106. package/dist/integrity/lsp/bin.ts +3 -1
  107. package/dist/integrity/lsp/diagnostics.js +3 -3
  108. package/dist/integrity/lsp/diagnostics.ts +1 -1
  109. package/dist/integrity/lsp/lint.js +19 -30
  110. package/dist/integrity/lsp/lint.ts +16 -32
  111. package/dist/integrity/lsp/server.js +3 -3
  112. package/dist/integrity/slopnator.d.ts +3 -13
  113. package/dist/integrity/slopnator.js +59 -38
  114. package/dist/integrity/slopnator.ts +54 -38
  115. package/dist/integrity/slopnators/sl_broken_lineage.js +2 -2
  116. package/dist/integrity/slopnators/sl_declared_elsewhere.js +1 -1
  117. package/dist/integrity/slopnators/sl_derived_drift.js +4 -4
  118. package/dist/integrity/slopnators/sl_derived_drift.ts +1 -1
  119. package/dist/integrity/slopnators/sl_door_in_a_node.js +220 -0
  120. package/dist/integrity/slopnators/sl_door_in_a_node.ts +250 -0
  121. package/dist/integrity/slopnators/sl_inert_island.js +10 -8
  122. package/dist/integrity/slopnators/sl_inert_island.ts +8 -6
  123. package/dist/integrity/slopnators/sl_missing_row.js +6 -51
  124. package/dist/integrity/slopnators/sl_missing_row.ts +4 -50
  125. package/dist/integrity/slopnators/sl_no_barrel.js +1 -1
  126. package/dist/integrity/slopnators/sl_no_child_process.js +36 -18
  127. package/dist/integrity/slopnators/sl_no_child_process.ts +44 -18
  128. package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +80 -149
  129. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +95 -147
  130. package/dist/integrity/slopnators/sl_no_import_cycle.js +15 -4
  131. package/dist/integrity/slopnators/sl_no_import_cycle.ts +20 -4
  132. package/dist/integrity/slopnators/sl_no_such_command.js +2 -2
  133. package/dist/integrity/slopnators/sl_no_such_command.ts +1 -1
  134. package/dist/integrity/slopnators/sl_outside_an_operation.js +20 -0
  135. package/dist/integrity/slopnators/sl_outside_an_operation.ts +25 -0
  136. package/dist/integrity/slopnators/sl_screen_holds_only_declared.d.ts +2 -0
  137. package/dist/integrity/slopnators/sl_screen_holds_only_declared.js +61 -0
  138. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +70 -0
  139. package/dist/integrity/slopnators/sl_secret_literal.js +1 -33
  140. package/dist/integrity/slopnators/sl_secret_literal.ts +1 -33
  141. package/dist/integrity/slopnators/sl_stale_path.js +5 -22
  142. package/dist/integrity/slopnators/sl_stale_path.ts +4 -20
  143. package/dist/integrity/slopnators/sl_twice_meant.js +1 -1
  144. package/dist/integrity/slopnators/sl_twice_parented.js +29 -5
  145. package/dist/integrity/slopnators/sl_twice_parented.ts +28 -4
  146. package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +4 -1
  147. package/dist/integrity/slopnators/sl_undeclared_crossing.js +426 -62
  148. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +454 -70
  149. package/dist/integrity/slopnators/sl_undeclared_destination.js +59 -25
  150. package/dist/integrity/slopnators/sl_undeclared_destination.ts +69 -21
  151. package/dist/integrity/slopnators/sl_undeclared_list.d.ts +2 -0
  152. package/dist/integrity/slopnators/sl_undeclared_list.js +363 -0
  153. package/dist/integrity/slopnators/sl_undeclared_list.ts +358 -0
  154. package/dist/integrity/slopnators/sl_undeclared_node.js +1 -11
  155. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -10
  156. package/dist/integrity/slopnators/sl_undeclared_operation.js +111 -29
  157. package/dist/integrity/slopnators/sl_undeclared_operation.ts +134 -26
  158. package/dist/integrity/slopnators/sl_undeclared_variant.js +2 -2
  159. package/dist/integrity/slopnators/sl_undeclared_webhook.d.ts +2 -0
  160. package/dist/integrity/slopnators/sl_undeclared_webhook.js +90 -0
  161. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +108 -0
  162. package/dist/integrity/slopnators/sl_underived_route.d.ts +2 -0
  163. package/dist/integrity/slopnators/sl_underived_route.js +61 -0
  164. package/dist/integrity/slopnators/sl_underived_route.ts +72 -0
  165. package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -3
  166. package/dist/integrity/slopnators/sl_ungoverned_island.ts +1 -1
  167. package/dist/integrity/slopnators/sl_unlisted_program.d.ts +2 -0
  168. package/dist/integrity/slopnators/sl_unlisted_program.js +39 -0
  169. package/dist/integrity/slopnators/sl_unlisted_program.ts +40 -0
  170. package/dist/integrity/slopnators/sl_unnamed_island.js +1 -1
  171. package/dist/integrity/slopnators/sl_unpaired_member.js +3 -3
  172. package/dist/integrity/slopnators/sl_unpaired_member.ts +1 -1
  173. package/dist/integrity/slopnators/sl_unpaired_slopnator.js +4 -4
  174. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +1 -1
  175. package/dist/integrity/slopnators/sl_unprefixed_member.js +5 -31
  176. package/dist/integrity/slopnators/sl_unprefixed_member.ts +2 -28
  177. package/dist/integrity/slopnators/sl_unthreaded.js +11 -7
  178. package/dist/integrity/slopnators/sl_unthreaded.ts +6 -7
  179. package/dist/integrity/slopnators/sl_untranslated_key.js +2 -25
  180. package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -25
  181. package/dist/integrity/slopnators/sl_unvaulted_secret.d.ts +2 -0
  182. package/dist/integrity/slopnators/sl_unvaulted_secret.js +107 -0
  183. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +132 -0
  184. package/dist/integrity/slopnators/sl_unversioned.js +6 -4
  185. package/dist/integrity/slopnators/sl_unversioned.ts +5 -3
  186. package/dist/integrity/sweep.d.ts +1 -0
  187. package/dist/integrity/sweep.js +105 -42
  188. package/dist/integrity/sweep.ts +115 -42
  189. package/dist/knowledge/attach.js +4 -3
  190. package/dist/knowledge/attach.ts +3 -2
  191. package/dist/knowledge/generators/docsgen.js +8 -6
  192. package/dist/knowledge/generators/docsgen.ts +4 -2
  193. package/dist/knowledge/generators/glossary.js +4 -4
  194. package/dist/knowledge/generators/glossary.ts +1 -1
  195. package/dist/operations/access.d.ts +23 -0
  196. package/dist/operations/access.js +28 -1
  197. package/dist/operations/access.ts +43 -0
  198. package/dist/operations/attach.js +33 -113
  199. package/dist/operations/attach.ts +51 -117
  200. package/dist/operations/capabilities.d.ts +14 -5
  201. package/dist/operations/capabilities.js +13 -4
  202. package/dist/operations/capabilities.ts +27 -8
  203. package/dist/operations/dispatch.d.ts +8 -2
  204. package/dist/operations/dispatch.js +128 -185
  205. package/dist/operations/dispatch.ts +138 -191
  206. package/dist/operations/error-results.d.ts +1 -1
  207. package/dist/operations/error-results.js +2 -2
  208. package/dist/operations/error-results.ts +2 -2
  209. package/dist/operations/generators/capability.d.ts +11 -1
  210. package/dist/operations/generators/capability.js +481 -224
  211. package/dist/operations/generators/capability.ts +562 -233
  212. package/dist/operations/generators/compile.js +66 -39
  213. package/dist/operations/generators/compile.ts +78 -39
  214. package/dist/operations/generators/permits.js +11 -12
  215. package/dist/operations/generators/permits.ts +3 -4
  216. package/dist/operations/generators/seed.d.ts +1 -0
  217. package/dist/operations/generators/seed.js +19 -17
  218. package/dist/operations/generators/seed.ts +22 -14
  219. package/dist/operations/generators/trial.d.ts +6 -8
  220. package/dist/operations/generators/trial.js +200 -70
  221. package/dist/operations/generators/trial.ts +230 -93
  222. package/dist/operations/nodes.d.ts +15 -16
  223. package/dist/operations/nodes.ts +16 -17
  224. package/dist/operations/operations.d.ts +40 -4
  225. package/dist/operations/operations.js +1 -0
  226. package/dist/operations/operations.ts +61 -4
  227. package/dist/operations/result.d.ts +2 -2
  228. package/dist/operations/result.ts +2 -2
  229. package/package.json +1 -4
  230. package/dist/core/answers.d.ts +0 -171
  231. package/dist/core/answers.js +0 -789
  232. package/dist/core/answers.ts +0 -999
  233. package/dist/core/client.d.ts +0 -63
  234. package/dist/core/client.js +0 -249
  235. package/dist/core/client.ts +0 -289
  236. package/dist/core/files.js +0 -171
  237. package/dist/core/files.ts +0 -222
  238. package/dist/integrations/module.d.ts +0 -6
  239. package/dist/integrations/module.js +0 -153
  240. package/dist/integrations/module.ts +0 -191
  241. package/dist/integrity/slopnators/sl_unkept_rows.js +0 -104
  242. package/dist/integrity/slopnators/sl_unkept_rows.ts +0 -111
  243. package/dist/integrity/slopnators/sl_unseamed_reach.js +0 -73
  244. package/dist/integrity/slopnators/sl_unseamed_reach.ts +0 -79
  245. package/dist/operations/cost.d.ts +0 -14
  246. package/dist/operations/cost.js +0 -37
  247. package/dist/operations/cost.ts +0 -58
  248. package/dist/operations/errors/errors.d.ts +0 -52
  249. package/dist/operations/http.js +0 -124
  250. package/dist/operations/http.ts +0 -169
  251. package/dist/operations/memory.d.ts +0 -104
  252. package/dist/operations/memory.js +0 -365
  253. package/dist/operations/memory.ts +0 -470
  254. package/dist/operations/metering.d.ts +0 -55
  255. package/dist/operations/metering.js +0 -247
  256. package/dist/operations/metering.ts +0 -328
  257. package/dist/operations/queue-policy.d.ts +0 -3
  258. package/dist/operations/queue-policy.js +0 -8
  259. package/dist/operations/queue-policy.ts +0 -10
  260. package/dist/operations/seams.d.ts +0 -239
  261. package/dist/operations/seams.js +0 -45
  262. package/dist/operations/seams.ts +0 -364
  263. package/dist/operations/store.d.ts +0 -14
  264. package/dist/operations/store.js +0 -1
  265. package/dist/operations/store.ts +0 -29
  266. /package/dist/{operations → core}/errors/classes.d.ts +0 -0
  267. /package/dist/{operations → core}/errors/classes.js +0 -0
  268. /package/dist/{operations → core}/errors/classes.ts +0 -0
  269. /package/dist/integrity/slopnators/{sl_unkept_rows.d.ts → sl_door_in_a_node.d.ts} +0 -0
  270. /package/dist/integrity/slopnators/{sl_unseamed_reach.d.ts → sl_outside_an_operation.d.ts} +0 -0
@@ -1,4 +1,8 @@
1
+ import { join } from 'node:path'
2
+ import { execPath } from 'node:process'
3
+
1
4
  import type { Member } from '../core/thread.ts'
5
+ import type { Actor } from '../core/actor.ts'
2
6
 
3
7
  import { SECRECY, secrecyOf, type Secret } from './secret.ts'
4
8
 
@@ -7,21 +11,28 @@ export const REACH = {
7
11
  pglite: 'pl_',
8
12
  browser: 'zu_',
9
13
  https: 'ht_',
10
- module: 'mod_',
11
14
  cli: 'cli_',
15
+ disk: 'dsk_',
16
+ sdk: 'sdk_',
12
17
  } as const satisfies Readonly<Record<string, string>>
13
18
 
14
19
  export type Reach = keyof typeof REACH
15
20
 
21
+ export const ADDRESS = {
22
+ row: 'reached at the address written on its row, which anybody may read',
23
+ secret: 'reached at a secret, because its address is a connection string and a connection string carries a password',
24
+ nothing: 'addressed by nothing',
25
+ } as const satisfies Readonly<Record<string, string>>
26
+
27
+ export type Addressed = keyof typeof ADDRESS
28
+
16
29
  export interface Holds {
17
30
 
18
- readonly takes: 'entities' | 'calls' | 'names'
31
+ readonly takes: 'entities' | 'calls' | null
19
32
 
20
33
  readonly from: 'server' | 'browser' | 'either'
21
34
 
22
- readonly addressed: boolean
23
-
24
- readonly arrives: 'npm' | 'machine' | null
35
+ readonly addressed: Addressed
25
36
 
26
37
  readonly migrations: boolean
27
38
 
@@ -33,46 +44,279 @@ export interface Holds {
33
44
  }
34
45
 
35
46
  export const HOLDS: Readonly<Record<Reach, Holds>> = {
36
- postgres: { takes: 'entities', from: 'server', addressed: true, arrives: null, migrations: true, references: true, sql: true, driver: 'postgres' },
37
- pglite: { takes: 'entities', from: 'either', addressed: true, arrives: null, migrations: true, references: true, sql: true, driver: '@electric-sql/pglite' },
38
- browser: { takes: 'entities', from: 'browser', addressed: false, arrives: null, migrations: false, references: false, sql: false, driver: 'zustand' },
39
- https: { takes: 'calls', from: 'either', addressed: true, arrives: null, migrations: false, references: false, sql: false, driver: null },
40
-
41
- module: { takes: 'names', from: 'either', addressed: false, arrives: 'npm', migrations: false, references: false, sql: false, driver: null },
42
- cli: { takes: 'names', from: 'server', addressed: false, arrives: 'machine', migrations: false, references: false, sql: false, driver: null },
47
+ postgres: { takes: 'entities', from: 'server', addressed: 'secret', migrations: true, references: true, sql: true, driver: 'postgres' },
48
+ pglite: { takes: 'entities', from: 'either', addressed: 'nothing', migrations: true, references: true, sql: true, driver: '@electric-sql/pglite' },
49
+ browser: { takes: 'entities', from: 'browser', addressed: 'nothing', migrations: false, references: false, sql: false, driver: 'zustand' },
50
+ https: { takes: 'calls', from: 'either', addressed: 'row', migrations: false, references: false, sql: false, driver: null },
51
+ cli: { takes: 'calls', from: 'server', addressed: 'nothing', migrations: false, references: false, sql: false, driver: null },
52
+ disk: { takes: null, from: 'server', addressed: 'nothing', migrations: false, references: false, sql: false, driver: null },
53
+ sdk: { takes: 'calls', from: 'server', addressed: 'nothing', migrations: false, references: false, sql: false, driver: null },
43
54
  }
44
55
 
45
56
  export const SAID: Readonly<Record<Reach, string>> = {
46
57
  postgres:
47
58
  'A postgres this app connects to over the network. It keeps entities and holds everything a declaration can state about their shape — tables, references and invariants — so what is kept here is held to that shape by the database itself rather than by whatever remembered to check. Who may touch a row is not asked of it: that is answered by the app, against the caller who actually arrived. It is reached from a server only: a connection to it from a page is a database somebody else can open. This is where a thing goes when losing it would matter.',
48
59
  pglite:
49
- 'A postgres running inside the process that opened it. It keeps entities and holds tables, references and invariants, exactly as the networked one does — what differs is who can reach it, not what it can be told to hold. What belongs here is what the caller is already entitled to see: a local copy, a working set, an offline cache.',
60
+ 'A postgres running inside the process that opened it. It keeps entities and holds tables, references and invariants, exactly as the networked one does — what differs is who can reach it, not what it can be told to hold. It is addressed by nothing: its files sit under .thread beside the tree, in a folder named after the target, so nobody types a path and a fresh clone starts it empty. What belongs here is what the caller is already entitled to see: a local copy, a working set, an offline cache.',
50
61
  browser:
51
62
  'A store in the browser, held in memory the viewer owns. It keeps entities and it is addressed by nothing, because it is already where the code runs. It holds no migration, because a page load builds it from nothing every time. What belongs here is what the interface needs to be responsive and can afford to lose.',
52
63
  https:
53
- 'Something this app calls over https — a vendor, a service, another app. It accepts calls rather than entities: each is declared with the verb and the path it takes, so a node names the call and never writes a url. Its address and its credential are both secrets, and whether it may be reached from a browser is decided by which kind of secret it needs rather than by the reach itself.',
54
- module:
55
- 'Something this app brings in from outside the repository and calls — an npm package, an SDK, an installer. Declaring one installs it and reads the methods it publishes, then asks which of them this app may call. What is picked is written down, and a method that is not on that list is refused before the package is imported: what can ever run is fixed when the target is declared, not by whoever calls it. A credential is named only if it needs one, and it comes from the vault at the moment of the call and is never written anywhere. A package you only import is a dependency rather than one of these, and the manifest already declares it.',
64
+ 'Something this app calls over https — a vendor, a service, another app. It accepts calls rather than entities: each is declared with the verb and the path it takes, so a node names the call and never writes a url. Where it is goes on its row as address, and a header every call carries goes on its row as headers, because both are facts anybody may read; a credential it sends is a secret, and requiring one is what keeps the target out of a browser.',
56
65
  cli:
57
- 'A program this app runs as a command — git is the one that made this necessary. It is not installed, because it is not a dependency: it is either on the machine or it is not, and declaring one checks that it is there and says what to do when it is not. What it accepts is its subcommands, read off the program itself at the moment it is declared and written down: the whole surface the tool really has, because it was asked rather than typed, and a subcommand that is not on that list never runs. Declaring one is what brings `thread run <name>` into being, so the CLI grows a command by the app stating an integration rather than by anybody writing a verb — and an app that has not declared it has no such command at all.',
66
+ 'A program this app runs as a command. git is the one that made this necessary. It is not installed, because it is not a dependency: it is on the machine or it is not, and a run finds out. What it accepts is the words it is asked with, declared beside it as calls with thread create target call: a subcommand where the program has them, and the option that selects what it does where it has none, each spelled the way the program spells it. A word that is not declared never runs. The program is the one the target is named after, so declaring one runs nothing and reads nothing off the machine. An operation that requires it is handed one function per declared word, and that is the only way this app ever runs it.',
67
+ disk:
68
+ 'The disk this app runs on. It answers a fixed set of orders — is this there, read it, write it, list what is beside it, how big it is, remove it, and what it really is — and those orders are the same for every disk, because they are what a filesystem does rather than what any one of them chose to offer. None of them runs anything: a disk holds files and a program that installs packages is not a file order, which is why installing what a tree depends on happens where somebody typed the command and is not something a disk can be asked for. It is reached from a server only: a page has no filesystem. A trial stands one of these up out of its own scenario, so a rule can be measured against files that exist for the length of that run and nothing is written where you work.',
69
+ sdk:
70
+ 'A package this app calls inside its own process — a vendor\'s SDK, fetched from npm, a git link or a tarball. It accepts calls rather than entities: the file this target declares exports `calls`, each call names the function there it runs as its path, and that file is the only place the package is imported, so an operation names the call and never the package. The integration it is part of names what to fetch in needs, and declaring the integration fetches it. It runs where the app runs, so it is addressed by nothing and reached from a server only.',
71
+ }
72
+
73
+ const on = (host: string, names: readonly string[]): readonly string[] => names.map((one) => host + '.' + one)
74
+
75
+ const driverOfReach = (one: Reach): readonly string[] => {
76
+ const driver = HOLDS[one].driver
77
+ return driver === null ? [] : ['import ' + driver]
78
+ }
79
+
80
+ export const OPENS: Readonly<Record<Reach, readonly string[]>> = {
81
+ postgres: driverOfReach('postgres'),
82
+ pglite: driverOfReach('pglite'),
83
+ browser: driverOfReach('browser'),
84
+ https: [
85
+ 'fetch',
86
+ 'XMLHttpRequest',
87
+ 'WebSocket',
88
+ 'EventSource',
89
+ ...on('window', ['fetch']),
90
+ ...on('navigator', ['sendBeacon']),
91
+ 'import node:http',
92
+ 'import node:https',
93
+ 'import node:http2',
94
+ 'import node:net',
95
+ 'import node:tls',
96
+ 'import node:dgram',
97
+ 'import node:dns',
98
+ ],
99
+ cli: ['import node:child_process', 'import node:worker_threads', 'import node:cluster'],
100
+ disk: ['import node:fs', 'import node:fs/promises'],
101
+ sdk: [],
102
+ }
103
+
104
+ export type Kind = 'file' | 'folder' | 'other' | 'none'
105
+
106
+ export interface Listed {
107
+ readonly name: string
108
+
109
+ readonly kind: Exclude<Kind, 'none'>
110
+ }
111
+
112
+ export interface Measured {
113
+ readonly size: number
114
+
115
+ readonly modified: number
116
+ }
117
+
118
+ export interface Answering {
119
+ readonly kind: (at: string) => Kind
120
+ readonly read: (at: string) => string
121
+ readonly write: (at: string, said: string, mode?: number) => void
122
+ readonly list: (at: string) => readonly Listed[]
123
+ readonly stat: (at: string) => Measured
124
+ readonly remove: (at: string) => void
125
+ readonly real: (at: string) => string
126
+ }
127
+
128
+ export interface Attached extends Answering {
129
+
130
+ readonly fetch: (at: string) => string
131
+
132
+ readonly run: (at: string, words: readonly string[]) => Ran
133
+ }
134
+
135
+ export const ORDERS = {
136
+ kind: 'kind',
137
+ read: 'read',
138
+ write: 'written',
139
+ list: 'list',
140
+ stat: 'stat',
141
+ remove: 'removed',
142
+ real: 'real',
143
+ fetch: 'fetched',
144
+ run: 'ran',
145
+ } as const
146
+
147
+ export interface Order {
148
+ readonly order: keyof typeof ORDERS
149
+ readonly at: string
150
+ readonly said?: string
151
+ readonly mode?: number
152
+ readonly words?: readonly string[]
153
+ }
154
+
155
+ export interface Answer {
156
+ readonly answered: string
157
+ readonly found?: unknown
158
+ readonly said?: string
159
+ readonly at?: string
160
+ readonly because?: string
161
+ }
162
+
163
+ export interface Recorded {
164
+ readonly order: Order
165
+ readonly answered: Answer
166
+ }
167
+
168
+ export interface How {
169
+ readonly at?: string
170
+ readonly hears?: string
171
+ readonly of?: string
172
+ readonly answered?: readonly Answer[]
173
+ readonly recording?: readonly Recorded[]
174
+ readonly answering?: number
175
+ }
176
+
177
+ export interface Ran {
178
+
179
+ readonly there: boolean
180
+
181
+ readonly ok: boolean
182
+
183
+ readonly code: number
184
+
185
+ readonly out: string
186
+
187
+ readonly err: string
188
+
189
+ readonly killed: string | null
190
+
191
+ readonly why: string
192
+
193
+ readonly of?: string
194
+
195
+ readonly asks?: readonly Order[]
196
+
197
+ readonly lost?: string
198
+ }
199
+
200
+ export type Running<C extends string = string> = Readonly<
201
+ Record<
202
+ C,
203
+ (
204
+ args: readonly string[],
205
+ shows?: boolean | 'errors' | 'tee',
206
+ within?: number,
207
+ how?: How,
208
+ ) => Promise<Ran>
209
+ >
210
+ >
211
+
212
+ export type Calling<C extends string = string> = { readonly [K in C]: (given?: unknown) => Promise<unknown> }
213
+
214
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
215
+
216
+ export interface HttpRequest {
217
+ readonly target: string
218
+
219
+ readonly address: string
220
+ readonly method: HttpMethod
221
+
222
+ readonly path: string
223
+
224
+ readonly headers?: Readonly<Record<string, string>>
225
+
226
+ readonly body?: Uint8Array
227
+ readonly contentType?: string
228
+
229
+ readonly idempotencyKey?: string
230
+
231
+ readonly told?: (said: unknown) => void
232
+ }
233
+
234
+ export interface HttpResponse {
235
+ readonly status: number
236
+ readonly headers: Readonly<Record<string, string>>
237
+ readonly body: Uint8Array
238
+ }
239
+
240
+ export interface Http {
241
+ call(request: HttpRequest, within?: number): Promise<HttpResponse>
242
+ }
243
+
244
+ export type Row = Record<string, unknown>
245
+
246
+ export type PropertyValue = string | number | boolean | null | readonly (string | number | boolean)[]
247
+
248
+ export function isRow(v: unknown): v is Row {
249
+ return typeof v === 'object' && v !== null && typeof (v as { id?: unknown }).id === 'string'
250
+ }
251
+
252
+ export type ReadField = `fld_${string}` | `rel_${string}` | (string & {})
253
+
254
+ export type FieldList = readonly [ReadField, ...ReadField[]]
255
+
256
+ export interface Store {
257
+ insert(actor: Actor, entity: string, row: Row): Promise<Row>
258
+
259
+ get(actor: Actor, entity: string, key: string | number, fields: FieldList): Promise<Row | null>
260
+
261
+ list(
262
+ actor: Actor,
263
+ entity: string,
264
+ opts?: { limit?: number; where?: Readonly<Record<string, string | number | boolean | null>> },
265
+ ): Promise<Row[]>
266
+
267
+ update(
268
+ actor: Actor,
269
+ entity: string,
270
+ key: string | number,
271
+ patch: Record<string, unknown>,
272
+ expectedVersion?: number,
273
+ ): Promise<Row>
274
+
275
+ remove(actor: Actor, entity: string, key: string | number): Promise<void>
276
+ }
277
+
278
+ export interface Transactional {
279
+ transaction<T>(actor: Actor, fn: () => Promise<T>): Promise<T>
280
+ }
281
+
282
+ export function isTransactional(store: Store): store is Store & Transactional {
283
+ return typeof (store as Partial<Transactional>).transaction === 'function'
284
+ }
285
+
286
+ export interface Integration extends Member {
287
+
288
+ readonly needs?: Readonly<Record<string, string>>
58
289
  }
59
290
 
60
291
  export interface Target extends Member {
61
292
 
293
+ readonly of: string
294
+
62
295
  readonly takes: readonly string[]
63
296
 
64
297
  readonly requires: readonly string[]
65
298
 
66
- readonly uses?: string
299
+ readonly address?: string
67
300
 
68
- readonly offers?: readonly string[]
301
+ readonly headers?: Readonly<Record<string, string>>
302
+ }
303
+
304
+ export interface Webhook extends Member {
305
+
306
+ readonly of: string
307
+
308
+ readonly path: string
309
+
310
+ readonly arrives?: string
311
+
312
+ readonly invokes: string
69
313
  }
70
314
 
71
315
  export interface Call extends Member {
72
316
 
73
317
  readonly of: string
74
318
 
75
- readonly method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
319
+ readonly method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
76
320
 
77
321
  readonly path: string
78
322
  }
@@ -87,27 +331,35 @@ export function reachOf(id: string): Reach | undefined {
87
331
  return (Object.keys(REACH) as Reach[]).find((one) => id.startsWith(REACH[one]))
88
332
  }
89
333
 
334
+ export const standsHere = (one: Target, targets: readonly Target[]): boolean =>
335
+ !one.requires.some((each) => targets.some((two) => two.id === each))
336
+
90
337
  export function holdsOf(id: string): Holds | undefined {
91
338
  const what = reachOf(id)
92
339
  return what === undefined ? undefined : HOLDS[what]
93
340
  }
94
341
 
342
+ export function requirable(targets: readonly Target[], calls: readonly Call[]): readonly Target[] {
343
+ const declared = new Set(calls.map((one) => one.id))
344
+ return targets.filter((one) => holdsOf(one.id)?.takes !== 'calls' || one.takes.some((each) => declared.has(each)))
345
+ }
346
+
95
347
  export function driverOf(id: string): string | null {
96
348
  return holdsOf(id)?.driver ?? null
97
349
  }
98
350
 
99
351
  export function addressOf(one: Target, secrets: readonly Secret[]): Secret | undefined {
100
352
 
101
- if (holdsOf(one.id)?.addressed !== true) return undefined
353
+ if (holdsOf(one.id)?.addressed !== 'secret') return undefined
102
354
  const held = new Map(secrets.map((each) => [each.id, each]))
103
- return one.requires.map((each) => held.get(each)).find((each) => each !== undefined && each.sends === undefined)
355
+ return one.requires.map((each) => held.get(each)).find((each) => each !== undefined && each.sends === undefined && each.signs === undefined)
104
356
  }
105
357
 
106
358
  export function credentialsOf(one: Target, secrets: readonly Secret[]): readonly Secret[] {
107
359
  const held = new Map(secrets.map((each) => [each.id, each]))
108
360
  return one.requires
109
361
  .map((each) => held.get(each))
110
- .filter((each): each is Secret => each !== undefined && each.sends !== undefined)
362
+ .filter((each): each is Secret => each !== undefined && (each.sends !== undefined || each.signs !== undefined))
111
363
  }
112
364
 
113
365
  export function servesBrowser(one: Target, secrets: readonly Secret[]): true | string {
@@ -144,28 +396,50 @@ export const unreachable = (targets: readonly Target[]): readonly string[] =>
144
396
  'nothing can tell how to reach it — and how a thing is reached decides what may be declared for it',
145
397
  )
146
398
 
147
- const SHAPED: Readonly<Record<'module' | 'cli', RegExp>> = {
399
+ export const LOCKFILES: Readonly<Record<string, string>> = {
400
+ 'pnpm-lock.yaml': 'pnpm',
401
+ 'yarn.lock': 'yarn',
402
+ 'bun.lockb': 'bun',
403
+ 'package-lock.json': 'npm',
404
+ }
405
+
406
+ export const PACKAGER = 'packager'
407
+
408
+ const NAMED: readonly string[] = ['git', 'thread']
409
+
410
+ const RUNTIME = 'node'
148
411
 
149
- module: /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/,
412
+ export const managerOf = (at: string, there: (path: string) => boolean): string =>
413
+ Object.entries(LOCKFILES).find(([lock]) => there(join(at, lock)))?.[1] ?? 'npm'
150
414
 
151
- cli: /^[a-z][a-z0-9-]*$/,
415
+ export function programOf(name: string, at: string, there: (path: string) => boolean): string | undefined {
416
+ if (NAMED.includes(name)) return name
417
+ if (name === RUNTIME) return execPath
418
+ if (name === PACKAGER) return managerOf(at, there)
419
+ const installed = join(at, 'node_modules', '.bin', name)
420
+ return there(installed) ? installed : undefined
421
+ }
422
+
423
+ export const unlisted = (name: string): string =>
424
+ '`' + REACH.cli + name + '` names ' + name + ', and nothing lists a program by that name: it is not under node_modules/.bin, so this tree did not install it; it is not git, node or thread; and it is not `' + PACKAGER + '`, the word for whichever package manager the lockfile picks. A program the tree did not install is whatever the machine happens to have, and a target on that is a shell by another name. Add the package that provides it and declare the target again, or ask for it through a declared call on `' + REACH.cli + PACKAGER + '`'
425
+
426
+ const SHAPED: Readonly<Record<'cli', RegExp>> = {
427
+ cli: /^-{0,2}[A-Za-z][A-Za-z0-9-]*$/,
152
428
  }
153
429
 
154
430
  const EVERY_VALUE = new Set(['constructor', '__proto__', 'prototype'])
155
431
 
156
432
  export function misnamed(target: string, name: string): string | undefined {
157
433
  const reach = reachOf(target)
158
- if (reach !== 'module' && reach !== 'cli') return undefined
434
+ if (reach !== 'cli') return undefined
159
435
  if (EVERY_VALUE.has(name)) {
160
436
  return (
161
- `\`${name}\` is a step every JavaScript value has rather than something \`${target}\` offers, and walking to ` +
437
+ `\`${name}\` is a step every JavaScript value has rather than something \`${target}\` answers to, and walking to ` +
162
438
  'it leaves what was declared'
163
439
  )
164
440
  }
165
441
  if (!SHAPED[reach].test(name)) {
166
- return reach === 'module'
167
- ? `\`${name}\` is not a method path — one name, or names joined by dots, e.g. \`customers.list\``
168
- : `\`${name}\` is not a subcommand — one word as the program spells it, e.g. \`commit\` or \`rev-parse\``
442
+ return name + ' is not one word as the program spells it. Give a subcommand, e.g. commit or rev-parse, or, where the program has none, the option that selects what it does, e.g. -p'
169
443
  }
170
444
  return undefined
171
445
  }
@@ -185,12 +459,8 @@ export function misaccepted(
185
459
  if (holds.takes === 'entities' && isEntity.has(named)) continue
186
460
  if (holds.takes === 'calls' && isCall.has(named)) continue
187
461
 
188
- if (holds.takes === 'names') {
189
- found.push(
190
- `\`${one.id}\` is reached as ${String(reachOf(one.id))} and its \`takes\` names "${named}" — what an ` +
191
- 'installed package or a program responds to goes in `offers`, not here. `takes` is a demand, and the ' +
192
- 'descent requires every word on one to be coined by a row in this tree; a method name never can be',
193
- )
462
+ if (holds.takes === null) {
463
+ found.push(`\`${one.id}\` takes "${named}", and a ${String(reachOf(one.id))} takes nothing: it answers the orders every ${String(reachOf(one.id))} answers, stated once on the target itself`)
194
464
  continue
195
465
  }
196
466
  found.push(
@@ -202,21 +472,52 @@ export function misaccepted(
202
472
  return found
203
473
  }
204
474
 
475
+ export const placedAt = (targets: readonly Target[], id: string): string => targets.find((one) => one.id === id)?.address ?? ''
476
+
477
+ export function placed(one: Target): readonly string[] {
478
+ const said = one.address
479
+ if (said === undefined) {
480
+ return [one.id + ' is reached over https and its row says nothing about where. Give it an address: the absolute url every call on it is written from, scheme and host and nothing after the path']
481
+ }
482
+ let held: URL
483
+ try {
484
+ held = new URL(said)
485
+ } catch {
486
+ return [one.id + ' has the address ' + JSON.stringify(said) + ', and that is not an absolute url. Write the whole of it, scheme and host']
487
+ }
488
+ if ([held.search, held.hash].some((each) => each !== '')) {
489
+ return [one.id + ' has the address ' + said + ', which carries a query or a fragment. Those belong to a call, so an address ends at its path']
490
+ }
491
+ const wrong = Object.entries(one.headers ?? {}).filter(([, value]) => typeof value !== 'string')
492
+ if (wrong.length > 0) {
493
+ return [one.id + ' carries the header ' + String(wrong[0]?.[0] ?? '') + ' with a value that is not text. A header every call carries is written as text, exactly as it goes on the wire']
494
+ }
495
+ return []
496
+ }
497
+
498
+ const stated = (one: Target, holds: Holds): readonly string[] => {
499
+ if (holds.addressed === 'row') return placed(one)
500
+ const carried = [...(one.address === undefined ? [] : ['an address']), ...(one.headers === undefined ? [] : ['headers'])]
501
+ if (carried.length === 0) return []
502
+ return [one.id + ' carries ' + carried.join(' and ') + ', and a ' + String(reachOf(one.id)) + ' is ' + ADDRESS[holds.addressed] + '. Take it off the row']
503
+ }
504
+
205
505
  export function unaddressed(targets: readonly Target[], secrets: readonly Secret[]): readonly string[] {
206
506
  const held = new Map(secrets.map((one) => [one.id, one]))
207
507
  const found: string[] = []
208
508
  for (const one of targets) {
209
509
  const holds = holdsOf(one.id)
210
510
  if (holds === undefined) continue
511
+ found.push(...stated(one, holds))
211
512
  const addresses = one.requires
212
513
  .map((each) => held.get(each))
213
- .filter((each): each is Secret => each !== undefined && each.sends === undefined)
214
- if (!holds.addressed) {
514
+ .filter((each): each is Secret => each !== undefined && each.sends === undefined && each.signs === undefined)
515
+ if (holds.addressed !== 'secret') {
215
516
 
216
- if (holds.arrives === null && addresses.length > 0) {
517
+ if (addresses.length > 0) {
217
518
  found.push(
218
- `\`${one.id}\` is reached over ${String(reachOf(one.id))}, which is addressed by nothing and needs nothing to ` +
219
- `reach it, and it requires \`${addresses[0]?.id ?? ''}\` — there is nowhere for that value to go`,
519
+ one.id + ' is reached over ' + String(reachOf(one.id)) + ', which is ' + ADDRESS[holds.addressed] + ', and it requires ' +
520
+ (addresses[0]?.id ?? '') + ', a secret with nowhere to be sent. A secret is a credential: give it the header it goes on with sends, or take it off this row',
220
521
  )
221
522
  }
222
523
  continue
@@ -239,14 +540,15 @@ export function unaddressed(targets: readonly Target[], secrets: readonly Secret
239
540
  }
240
541
 
241
542
  export function unheld(targets: readonly Target[], secrets: readonly Secret[]): readonly string[] {
242
- const known = new Set(secrets.map((one) => one.id))
543
+ const known = new Set([...secrets.map((one) => one.id), ...targets.map((one) => one.id)])
243
544
  const found: string[] = []
244
545
  for (const one of targets) {
245
546
  for (const named of one.requires) {
246
547
  if (known.has(named)) continue
247
548
  found.push(
248
- `\`${one.id}\` requires "${named}", and this app declares no secret by that name. A secret is declared before it ` +
249
- 'is required, so the one place saying who may hold a value is never whichever place happened to need it',
549
+ `\`${one.id}\` requires "${named}", and this app declares no secret and no target by that name. A secret is declared before it ` +
550
+ 'is required, and a target before it is reached through, so the one place saying who holds a value, or ' +
551
+ 'answers for a reach, is never whichever place happened to need it',
250
552
  )
251
553
  }
252
554
  }
@@ -265,11 +567,25 @@ export function unanswered(calls: readonly Call[], targets: readonly Target[]):
265
567
  const holds = holdsOf(of.id)
266
568
  if (holds !== undefined && holds.takes !== 'calls') {
267
569
  found.push(
268
- `\`${one.id}\` is a call on \`${of.id}\`, which is reached over ${String(reachOf(of.id))} and keeps entities. ` +
269
- 'A place you keep rows in is not a place you issue verbs at',
570
+ holds.takes === 'entities'
571
+ ? `\`${one.id}\` is a call on \`${of.id}\`, which is reached over ${String(reachOf(of.id))} and keeps entities. A place you keep rows in is not a place you issue verbs at`
572
+ : `\`${one.id}\` is a call on \`${of.id}\`, which is a ${String(reachOf(of.id))} and answers the orders every ${String(reachOf(of.id))} answers, so nothing is declared on it. A node is handed those orders by requiring it`,
270
573
  )
271
574
  continue
272
575
  }
576
+ if (reachOf(of.id) === 'https' && one.method === undefined) {
577
+ found.push(`\`${one.id}\` is a call over https and states no method. A call over https says its verb: GET, POST, PUT, PATCH or DELETE`)
578
+ continue
579
+ }
580
+ if (reachOf(of.id) !== 'https' && one.method !== undefined) {
581
+ found.push(`\`${one.id}\` states the method ${one.method}, and \`${of.id}\` is a ${String(reachOf(of.id))}. A subcommand or a method has no http verb, so take \`method\` off that row`)
582
+ continue
583
+ }
584
+ const shaped = misnamed(of.id, one.path)
585
+ if (shaped !== undefined) {
586
+ found.push(`\`${one.id}\` is a call on \`${of.id}\`, and its path ${shaped}`)
587
+ continue
588
+ }
273
589
  if (!of.takes.includes(one.id)) {
274
590
  found.push(
275
591
  `\`${one.id}\` is a call on \`${of.id}\` and \`${of.id}\` does not accept it. Add it to that target's \`takes\` — ` +
@@ -280,25 +596,24 @@ export function unanswered(calls: readonly Call[], targets: readonly Target[]):
280
596
  return found
281
597
  }
282
598
 
283
- export function misused(targets: readonly Target[]): readonly string[] {
599
+ export function unplaced(targets: readonly Target[], integrations: readonly Integration[]): readonly string[] {
600
+ const known = new Set(integrations.map((one) => one.id))
284
601
  const found: string[] = []
285
602
  for (const one of targets) {
286
- const holds = holdsOf(one.id)
287
- if (holds === undefined) continue
288
- if (holds.arrives !== null && (one.uses ?? '') === '') {
603
+ const named = one.of ?? ''
604
+ if (named === '') {
289
605
  found.push(
290
- `\`${one.id}\` is reached as ${String(reachOf(one.id))} and does not say what it uses — ` +
291
- `${holds.arrives === 'npm' ? 'the npm package to install' : 'the program to run'} goes in \`uses\`, because the ` +
292
- 'id is what this app calls it and not what it is',
606
+ `\`${one.id}\` names no integration, and every target is one way into something outside this app — a vendor, ` +
607
+ 'a service, a program on the machine. Say which one it reaches into: `thread create integration` lists ' +
608
+ 'the ones already declared and declares another',
293
609
  )
294
610
  continue
295
611
  }
296
- if (holds.arrives === null && one.uses !== undefined) {
297
- found.push(
298
- `\`${one.id}\` is reached over ${String(reachOf(one.id))}, where nothing is installed and nothing is spawned, ` +
299
- `and it states \`uses: '${one.uses}'\` — there is nothing for that name to be`,
300
- )
301
- }
612
+ if (known.has(named)) continue
613
+ found.push(
614
+ `\`${one.id}\` is part of "${named}", and this app declares no integration by that name. Declare it with ` +
615
+ '`thread create integration`, or point this row at one already declared',
616
+ )
302
617
  }
303
618
  return found
304
619
  }
@@ -308,14 +623,15 @@ export function refusals(
308
623
  secrets: readonly Secret[],
309
624
  calls: readonly Call[],
310
625
  entities: readonly string[],
626
+ integrations: readonly Integration[],
311
627
  ): readonly string[] {
312
628
  return [
313
629
  ...unreachable(targets),
630
+ ...unplaced(targets, integrations),
314
631
  ...unheld(targets, secrets),
315
632
  ...unaddressed(targets, secrets),
316
633
  ...unanswered(calls, targets),
317
634
  ...misaccepted(targets, entities, calls),
318
- ...misused(targets),
319
635
  ]
320
636
  }
321
637
 
@@ -11,4 +11,5 @@ export declare function keyed(root: string): Buffer;
11
11
  export declare function seal(root: string, named: string, env: Env, value: string): void;
12
12
  export declare const sealedIn: (root: string, env: Env) => readonly string[];
13
13
  export declare function opened(root: string, declared: readonly string[], env: Env): (refs: readonly string[]) => Promise<Readonly<Record<string, string>>>;
14
+ export declare function unseal(root: string, named: string, envs: readonly Env[]): readonly Env[];
14
15
  export declare const IGNORES: readonly string[];