nervur 0.22.1 → 0.22.2-4

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 (396) hide show
  1. package/AUTHORING.md +814 -0
  2. package/KIT-SPEC.md +286 -0
  3. package/README.md +128 -431
  4. package/dist/app/app-ground.d.ts +18 -0
  5. package/dist/app/app-ground.js +25 -0
  6. package/dist/app/index.d.ts +2 -0
  7. package/dist/app/index.js +5 -0
  8. package/dist/app/native.d.ts +46 -0
  9. package/dist/app/native.js +101 -0
  10. package/dist/being/being.d.ts +229 -0
  11. package/dist/being/being.js +20 -0
  12. package/dist/being/covers.d.ts +9 -0
  13. package/dist/being/covers.js +21 -0
  14. package/dist/being/index.d.ts +3 -0
  15. package/dist/being/index.js +5 -0
  16. package/dist/being/need.d.ts +49 -0
  17. package/dist/being/need.js +33 -0
  18. package/dist/being/schema.d.ts +100 -0
  19. package/dist/being/schema.js +132 -0
  20. package/dist/being/table.d.ts +42 -0
  21. package/dist/being/table.js +288 -0
  22. package/dist/bench/bench-ground.d.ts +55 -0
  23. package/dist/bench/bench-ground.js +101 -0
  24. package/dist/bench/bench.d.ts +87 -0
  25. package/dist/bench/bench.js +260 -0
  26. package/dist/bench/fake-carry.d.ts +32 -0
  27. package/dist/bench/fake-carry.js +56 -0
  28. package/dist/bench/fake-clock.d.ts +17 -0
  29. package/dist/bench/fake-clock.js +34 -0
  30. package/dist/bench/fake-custody.d.ts +9 -0
  31. package/dist/bench/fake-custody.js +10 -0
  32. package/dist/bench/fake-faculty.d.ts +25 -0
  33. package/dist/bench/fake-faculty.js +48 -0
  34. package/dist/bench/fake-keys.d.ts +5 -0
  35. package/dist/bench/fake-keys.js +9 -0
  36. package/dist/bench/fake-memory.d.ts +14 -0
  37. package/dist/bench/fake-memory.js +46 -0
  38. package/dist/bench/fake-network.d.ts +57 -0
  39. package/dist/bench/fake-network.js +169 -0
  40. package/dist/bench/index.d.ts +11 -0
  41. package/dist/bench/index.js +13 -0
  42. package/dist/bench/seeded.d.ts +11 -0
  43. package/dist/bench/seeded.js +37 -0
  44. package/dist/bench/settle.d.ts +2 -0
  45. package/dist/bench/settle.js +49 -0
  46. package/dist/bench/stewards.d.ts +17 -0
  47. package/dist/bench/stewards.js +20 -0
  48. package/dist/bodies/class-list.d.ts +14 -0
  49. package/dist/bodies/class-list.js +33 -0
  50. package/dist/bodies/joined-carry.d.ts +23 -0
  51. package/dist/bodies/joined-carry.js +42 -0
  52. package/dist/bodies/noble-crypto.d.ts +22 -0
  53. package/dist/bodies/noble-crypto.js +88 -0
  54. package/dist/bodies/seed-keys.d.ts +10 -0
  55. package/dist/bodies/seed-keys.js +42 -0
  56. package/dist/bodies/strict-tools.d.ts +10 -0
  57. package/dist/bodies/strict-tools.js +306 -0
  58. package/dist/bodies/web-carry.d.ts +65 -0
  59. package/dist/bodies/web-carry.js +224 -0
  60. package/dist/bodies/web-clock.d.ts +12 -0
  61. package/dist/bodies/web-clock.js +33 -0
  62. package/dist/browser/browser-ground.d.ts +69 -0
  63. package/dist/browser/browser-ground.js +229 -0
  64. package/dist/browser/index.d.ts +4 -0
  65. package/dist/browser/index.js +7 -0
  66. package/dist/browser/indexeddb-memory.d.ts +17 -0
  67. package/dist/browser/indexeddb-memory.js +97 -0
  68. package/dist/browser/locked-custody.d.ts +27 -0
  69. package/dist/browser/locked-custody.js +93 -0
  70. package/dist/browser/origin-classes.d.ts +7 -0
  71. package/dist/browser/origin-classes.js +31 -0
  72. package/dist/foundation.d.ts +150 -0
  73. package/dist/foundation.js +4 -0
  74. package/dist/ground/ground.d.ts +177 -0
  75. package/dist/ground/ground.js +363 -0
  76. package/dist/house/crossing.d.ts +37 -0
  77. package/dist/house/crossing.js +131 -0
  78. package/dist/house/house.d.ts +80 -0
  79. package/dist/house/house.js +1753 -0
  80. package/dist/house/rows-shape.d.ts +103 -0
  81. package/dist/house/rows-shape.js +12 -0
  82. package/dist/house/rows.d.ts +27 -0
  83. package/dist/house/rows.js +168 -0
  84. package/dist/index.d.ts +29 -1
  85. package/dist/index.js +25 -14
  86. package/dist/node/bridge.d.ts +17 -0
  87. package/dist/node/bridge.js +188 -0
  88. package/dist/node/cli.js +170 -0
  89. package/dist/node/custody.d.ts +20 -0
  90. package/dist/node/custody.js +34 -0
  91. package/dist/node/file-keys.d.ts +7 -0
  92. package/dist/node/file-keys.js +30 -0
  93. package/dist/node/file-memory.d.ts +13 -0
  94. package/dist/node/file-memory.js +91 -0
  95. package/dist/node/folder-classes.d.ts +5 -0
  96. package/dist/node/folder-classes.js +40 -0
  97. package/dist/node/gone.d.ts +2 -0
  98. package/dist/node/gone.js +7 -0
  99. package/dist/node/hand.d.ts +29 -0
  100. package/dist/node/hand.js +85 -0
  101. package/dist/node/held-keys.d.ts +14 -0
  102. package/dist/node/held-keys.js +36 -0
  103. package/dist/node/http.d.ts +11 -0
  104. package/dist/node/http.js +90 -0
  105. package/dist/node/index.d.ts +13 -0
  106. package/dist/node/index.js +15 -0
  107. package/dist/node/keychain-keys.d.ts +10 -0
  108. package/dist/node/keychain-keys.js +51 -0
  109. package/dist/node/ledger-memory.d.ts +17 -0
  110. package/dist/node/ledger-memory.js +151 -0
  111. package/dist/node/lock.d.ts +10 -0
  112. package/dist/node/lock.js +78 -0
  113. package/dist/node/node-ground.d.ts +33 -0
  114. package/dist/node/node-ground.js +147 -0
  115. package/dist/node/notify.d.ts +1 -0
  116. package/dist/node/notify.js +18 -0
  117. package/dist/node/tcp-carry.d.ts +39 -0
  118. package/dist/node/tcp-carry.js +216 -0
  119. package/dist/node/websocket.d.ts +6 -0
  120. package/dist/node/websocket.js +118 -0
  121. package/dist/quo/frame.d.ts +20 -0
  122. package/dist/quo/frame.js +48 -0
  123. package/dist/quo/room.d.ts +154 -0
  124. package/dist/quo/room.js +362 -0
  125. package/dist/serve/index.d.ts +27 -0
  126. package/dist/serve/index.js +55 -0
  127. package/package.json +40 -56
  128. package/dist/browser.d.ts +0 -1
  129. package/dist/browser.js +0 -8
  130. package/dist/cli.js +0 -16
  131. package/dist/core/being/being.d.ts +0 -17
  132. package/dist/core/being/being.js +0 -72
  133. package/dist/core/being/digest.d.ts +0 -3
  134. package/dist/core/being/digest.js +0 -17
  135. package/dist/core/being/faculty.d.ts +0 -10
  136. package/dist/core/being/faculty.js +0 -77
  137. package/dist/core/being/index.d.ts +0 -6
  138. package/dist/core/being/index.js +0 -8
  139. package/dist/core/being/kind.d.ts +0 -6
  140. package/dist/core/being/kind.js +0 -34
  141. package/dist/core/being/types.d.ts +0 -61
  142. package/dist/core/being/types.js +0 -4
  143. package/dist/core/being/words.d.ts +0 -13
  144. package/dist/core/being/words.js +0 -14
  145. package/dist/core/browser/index.d.ts +0 -37
  146. package/dist/core/browser/index.js +0 -196
  147. package/dist/core/browser/worker.d.ts +0 -11
  148. package/dist/core/browser/worker.js +0 -37
  149. package/dist/core/cli/command.d.ts +0 -13
  150. package/dist/core/cli/command.js +0 -330
  151. package/dist/core/cli/edge.d.ts +0 -7
  152. package/dist/core/cli/edge.js +0 -79
  153. package/dist/core/cli/harbor.d.ts +0 -25
  154. package/dist/core/cli/harbor.js +0 -115
  155. package/dist/core/contract/index.d.ts +0 -42
  156. package/dist/core/contract/index.js +0 -34
  157. package/dist/core/contract/link.d.ts +0 -4
  158. package/dist/core/contract/link.js +0 -45
  159. package/dist/core/crypto/aes.d.ts +0 -3
  160. package/dist/core/crypto/aes.js +0 -24
  161. package/dist/core/crypto/bytes.d.ts +0 -6
  162. package/dist/core/crypto/bytes.js +0 -33
  163. package/dist/core/crypto/ed25519.d.ts +0 -3
  164. package/dist/core/crypto/ed25519.js +0 -93
  165. package/dist/core/crypto/hash.d.ts +0 -2
  166. package/dist/core/crypto/hash.js +0 -11
  167. package/dist/core/crypto/index.d.ts +0 -7
  168. package/dist/core/crypto/index.js +0 -10
  169. package/dist/core/crypto/json.d.ts +0 -8
  170. package/dist/core/crypto/json.js +0 -250
  171. package/dist/core/crypto/mlkem.d.ts +0 -12
  172. package/dist/core/crypto/mlkem.js +0 -36
  173. package/dist/core/crypto/subtle.d.ts +0 -3
  174. package/dist/core/crypto/subtle.js +0 -11
  175. package/dist/core/crypto/x25519.d.ts +0 -2
  176. package/dist/core/crypto/x25519.js +0 -24
  177. package/dist/core/edge/index.d.ts +0 -164
  178. package/dist/core/edge/index.js +0 -739
  179. package/dist/core/edge/kit.d.ts +0 -2
  180. package/dist/core/edge/kit.js +0 -2
  181. package/dist/core/folder/index.d.ts +0 -18
  182. package/dist/core/folder/index.js +0 -130
  183. package/dist/core/git/http.d.ts +0 -6
  184. package/dist/core/git/http.js +0 -102
  185. package/dist/core/git/index.d.ts +0 -5
  186. package/dist/core/git/index.js +0 -10
  187. package/dist/core/git/object.d.ts +0 -40
  188. package/dist/core/git/object.js +0 -103
  189. package/dist/core/git/pack.d.ts +0 -9
  190. package/dist/core/git/pack.js +0 -171
  191. package/dist/core/git/store.d.ts +0 -23
  192. package/dist/core/git/store.js +0 -120
  193. package/dist/core/git/zlib.d.ts +0 -6
  194. package/dist/core/git/zlib.js +0 -210
  195. package/dist/core/harbor/carrying.d.ts +0 -50
  196. package/dist/core/harbor/carrying.js +0 -66
  197. package/dist/core/harbor/catalogue.d.ts +0 -33
  198. package/dist/core/harbor/catalogue.js +0 -365
  199. package/dist/core/harbor/dna.d.ts +0 -13
  200. package/dist/core/harbor/dna.js +0 -120
  201. package/dist/core/harbor/dock.d.ts +0 -62
  202. package/dist/core/harbor/dock.js +0 -381
  203. package/dist/core/harbor/harbor.d.ts +0 -16
  204. package/dist/core/harbor/harbor.js +0 -448
  205. package/dist/core/harbor/index.d.ts +0 -11
  206. package/dist/core/harbor/index.js +0 -14
  207. package/dist/core/harbor/memory.d.ts +0 -10
  208. package/dist/core/harbor/memory.js +0 -25
  209. package/dist/core/harbor/package.d.ts +0 -20
  210. package/dist/core/harbor/package.js +0 -172
  211. package/dist/core/harbor/probe.d.ts +0 -22
  212. package/dist/core/harbor/probe.js +0 -15
  213. package/dist/core/harbor/registry.d.ts +0 -15
  214. package/dist/core/harbor/registry.js +0 -110
  215. package/dist/core/harbor/root-line.d.ts +0 -7
  216. package/dist/core/harbor/root-line.js +0 -9
  217. package/dist/core/harbor/terrain.d.ts +0 -25
  218. package/dist/core/harbor/terrain.js +0 -28
  219. package/dist/core/http/index.d.ts +0 -2
  220. package/dist/core/http/index.js +0 -154
  221. package/dist/core/http/websocket.d.ts +0 -29
  222. package/dist/core/http/websocket.js +0 -133
  223. package/dist/core/line/answer.d.ts +0 -8
  224. package/dist/core/line/answer.js +0 -60
  225. package/dist/core/line/frame.d.ts +0 -29
  226. package/dist/core/line/frame.js +0 -77
  227. package/dist/core/line/ground.d.ts +0 -3
  228. package/dist/core/line/ground.js +0 -7
  229. package/dist/core/line/index.d.ts +0 -4
  230. package/dist/core/line/index.js +0 -8
  231. package/dist/core/line/web.d.ts +0 -12
  232. package/dist/core/line/web.js +0 -117
  233. package/dist/core/node/index.d.ts +0 -23
  234. package/dist/core/node/index.js +0 -124
  235. package/dist/core/pointer/bodies.d.ts +0 -31
  236. package/dist/core/pointer/bodies.js +0 -118
  237. package/dist/core/pointer/index.d.ts +0 -2
  238. package/dist/core/pointer/index.js +0 -4
  239. package/dist/core/pointer/world.d.ts +0 -45
  240. package/dist/core/pointer/world.js +0 -106
  241. package/dist/core/proof/contracts.d.ts +0 -19
  242. package/dist/core/proof/contracts.js +0 -116
  243. package/dist/core/proof/expect.d.ts +0 -6
  244. package/dist/core/proof/expect.js +0 -18
  245. package/dist/core/proof/index.d.ts +0 -3
  246. package/dist/core/proof/index.js +0 -14
  247. package/dist/core/proof/law.d.ts +0 -25
  248. package/dist/core/proof/law.js +0 -715
  249. package/dist/core/quo/address.d.ts +0 -19
  250. package/dist/core/quo/address.js +0 -84
  251. package/dist/core/quo/door.d.ts +0 -34
  252. package/dist/core/quo/door.js +0 -174
  253. package/dist/core/quo/index.d.ts +0 -9
  254. package/dist/core/quo/index.js +0 -12
  255. package/dist/core/quo/invitation.d.ts +0 -8
  256. package/dist/core/quo/invitation.js +0 -19
  257. package/dist/core/quo/keys.d.ts +0 -40
  258. package/dist/core/quo/keys.js +0 -79
  259. package/dist/core/quo/payload.d.ts +0 -15
  260. package/dist/core/quo/payload.js +0 -55
  261. package/dist/core/quo/relations.d.ts +0 -40
  262. package/dist/core/quo/relations.js +0 -33
  263. package/dist/core/quo/reply.d.ts +0 -13
  264. package/dist/core/quo/reply.js +0 -35
  265. package/dist/core/quo/seal.d.ts +0 -42
  266. package/dist/core/quo/seal.js +0 -78
  267. package/dist/core/quo/standing.d.ts +0 -39
  268. package/dist/core/quo/standing.js +0 -90
  269. package/dist/core/tcp/index.d.ts +0 -27
  270. package/dist/core/tcp/index.js +0 -209
  271. package/dist/core/ward/allowance.d.ts +0 -14
  272. package/dist/core/ward/allowance.js +0 -32
  273. package/dist/core/ward/cells.d.ts +0 -7
  274. package/dist/core/ward/cells.js +0 -74
  275. package/dist/core/ward/house.d.ts +0 -70
  276. package/dist/core/ward/house.js +0 -499
  277. package/dist/core/ward/index.d.ts +0 -5
  278. package/dist/core/ward/index.js +0 -7
  279. package/dist/core/ward/partition.d.ts +0 -51
  280. package/dist/core/ward/partition.js +0 -66
  281. package/dist/core/ward/stance.d.ts +0 -31
  282. package/dist/core/ward/stance.js +0 -193
  283. package/dist/core/ward/ward.d.ts +0 -44
  284. package/dist/core/ward/ward.js +0 -140
  285. package/dist/core.d.ts +0 -10
  286. package/dist/core.js +0 -19
  287. package/dist/defaults/dock/index.d.ts +0 -4
  288. package/dist/defaults/dock/index.js +0 -11
  289. package/dist/defaults/index.d.ts +0 -5
  290. package/dist/defaults/index.js +0 -10
  291. package/dist/defaults/memory/index.d.ts +0 -12
  292. package/dist/defaults/memory/index.js +0 -39
  293. package/dist/defaults/tcp/index.d.ts +0 -16
  294. package/dist/defaults/tcp/index.js +0 -82
  295. package/dist/defaults/ward/index.d.ts +0 -4
  296. package/dist/defaults/ward/index.js +0 -11
  297. package/dist/defaults/web/index.d.ts +0 -15
  298. package/dist/defaults/web/index.js +0 -90
  299. package/dist/edge.d.ts +0 -1
  300. package/dist/edge.js +0 -9
  301. package/dist/kit.d.ts +0 -4
  302. package/dist/kit.js +0 -12
  303. package/dist/node.d.ts +0 -1
  304. package/dist/node.js +0 -8
  305. package/src/browser.ts +0 -10
  306. package/src/cli.ts +0 -15
  307. package/src/core/being/being.ts +0 -84
  308. package/src/core/being/digest.ts +0 -18
  309. package/src/core/being/faculty.ts +0 -76
  310. package/src/core/being/index.ts +0 -8
  311. package/src/core/being/kind.ts +0 -37
  312. package/src/core/being/types.ts +0 -74
  313. package/src/core/being/words.ts +0 -31
  314. package/src/core/browser/index.ts +0 -219
  315. package/src/core/browser/worker.ts +0 -63
  316. package/src/core/cli/command.ts +0 -311
  317. package/src/core/cli/edge.ts +0 -86
  318. package/src/core/cli/harbor.ts +0 -120
  319. package/src/core/contract/index.ts +0 -82
  320. package/src/core/contract/link.ts +0 -58
  321. package/src/core/crypto/aes.ts +0 -26
  322. package/src/core/crypto/bytes.ts +0 -37
  323. package/src/core/crypto/ed25519.ts +0 -92
  324. package/src/core/crypto/hash.ts +0 -14
  325. package/src/core/crypto/index.ts +0 -10
  326. package/src/core/crypto/json.ts +0 -241
  327. package/src/core/crypto/mlkem.ts +0 -38
  328. package/src/core/crypto/subtle.ts +0 -13
  329. package/src/core/crypto/x25519.ts +0 -23
  330. package/src/core/edge/index.ts +0 -865
  331. package/src/core/folder/index.ts +0 -137
  332. package/src/core/git/http.ts +0 -103
  333. package/src/core/git/index.ts +0 -10
  334. package/src/core/git/object.ts +0 -116
  335. package/src/core/git/pack.ts +0 -147
  336. package/src/core/git/store.ts +0 -122
  337. package/src/core/git/zlib.ts +0 -197
  338. package/src/core/harbor/carrying.ts +0 -121
  339. package/src/core/harbor/catalogue.ts +0 -386
  340. package/src/core/harbor/dna.ts +0 -116
  341. package/src/core/harbor/dock.ts +0 -420
  342. package/src/core/harbor/harbor.ts +0 -441
  343. package/src/core/harbor/index.ts +0 -14
  344. package/src/core/harbor/memory.ts +0 -31
  345. package/src/core/harbor/package.ts +0 -195
  346. package/src/core/harbor/probe.ts +0 -39
  347. package/src/core/harbor/registry.ts +0 -107
  348. package/src/core/harbor/root-line.ts +0 -19
  349. package/src/core/harbor/terrain.ts +0 -54
  350. package/src/core/http/index.ts +0 -147
  351. package/src/core/http/websocket.ts +0 -124
  352. package/src/core/line/answer.ts +0 -60
  353. package/src/core/line/frame.ts +0 -83
  354. package/src/core/line/ground.ts +0 -18
  355. package/src/core/line/index.ts +0 -8
  356. package/src/core/line/web.ts +0 -123
  357. package/src/core/node/index.ts +0 -139
  358. package/src/core/pointer/bodies.ts +0 -122
  359. package/src/core/pointer/index.ts +0 -4
  360. package/src/core/pointer/world.ts +0 -134
  361. package/src/core/proof/contracts.ts +0 -134
  362. package/src/core/proof/expect.ts +0 -25
  363. package/src/core/proof/index.ts +0 -14
  364. package/src/core/proof/law.ts +0 -742
  365. package/src/core/quo/address.ts +0 -87
  366. package/src/core/quo/door.ts +0 -180
  367. package/src/core/quo/index.ts +0 -12
  368. package/src/core/quo/invitation.ts +0 -19
  369. package/src/core/quo/keys.ts +0 -91
  370. package/src/core/quo/payload.ts +0 -61
  371. package/src/core/quo/relations.ts +0 -62
  372. package/src/core/quo/reply.ts +0 -38
  373. package/src/core/quo/seal.ts +0 -101
  374. package/src/core/quo/standing.ts +0 -111
  375. package/src/core/stand/main.ts +0 -20
  376. package/src/core/stand/stand.ts +0 -259
  377. package/src/core/tcp/index.ts +0 -227
  378. package/src/core/ward/allowance.ts +0 -47
  379. package/src/core/ward/cells.ts +0 -77
  380. package/src/core/ward/house.ts +0 -550
  381. package/src/core/ward/index.ts +0 -7
  382. package/src/core/ward/partition.ts +0 -120
  383. package/src/core/ward/stance.ts +0 -215
  384. package/src/core/ward/ward.ts +0 -166
  385. package/src/core.ts +0 -55
  386. package/src/defaults/dock/index.ts +0 -12
  387. package/src/defaults/index.ts +0 -10
  388. package/src/defaults/memory/index.ts +0 -50
  389. package/src/defaults/tcp/index.ts +0 -89
  390. package/src/defaults/ward/index.ts +0 -12
  391. package/src/defaults/web/index.ts +0 -97
  392. package/src/edge.ts +0 -11
  393. package/src/index.ts +0 -18
  394. package/src/kit.ts +0 -15
  395. package/src/node.ts +0 -10
  396. /package/dist/{cli.d.ts → node/cli.d.ts} +0 -0
package/AUTHORING.md ADDED
@@ -0,0 +1,814 @@
1
+ # Writing for nervur
2
+
3
+ This guide teaches the two things you write with `nervur`. A **being**
4
+ holds logic and state. A **faculty** reaches the world outside. A
5
+ **ground** runs them on a machine, and the library ships it. The
6
+ examples build one small shop, and every file here is a file the
7
+ package's own tests run.
8
+
9
+ The shop has three classes and one faculty.
10
+
11
+ - `Order` is one order, from its first item to its shipping.
12
+ - `Shop` is the steward, the being that runs the house.
13
+ - `Lobby` is the public being, which strangers may ask.
14
+ - `Payments` is a faculty that charges money, which the ground's
15
+ `recipe.ts` makes.
16
+
17
+ ## Words
18
+
19
+ | Word | What it is |
20
+ | --- | --- |
21
+ | being | an instance of a class, born for one ask and dropped after it |
22
+ | cells | her state, JSON values, kept when an ask lands |
23
+ | ask | a method others may call on her, with its entry |
24
+ | asker | who calls her now: an id and notes |
25
+ | occupant | someone who may ask her, by id |
26
+ | standing | someone she may ask, by id |
27
+ | blueprint | the shape of what can be called: a name and its methods |
28
+ | need | the blueprint she calls, at its minimum, under a member of her own |
29
+ | faculty | anything outside the house that answers a blueprint |
30
+ | offer | a faculty the ground hands the house: its blueprint and its object |
31
+ | role | a named test over the asker and her cells |
32
+ | state | a name read from her cells that decides which asks exist |
33
+ | house | what holds beings, keeps their cells, and seals every ask |
34
+ | ground | the process houses run in, holding their seeds, faculties and hands |
35
+
36
+ ## A being
37
+
38
+ A being is pure logic over her cells. She never knows where she runs,
39
+ who carries her asks, or how her cells are kept. The house does all of
40
+ it, and she trusts it.
41
+
42
+ ```ts
43
+ // classes/order.ts
44
+ import { Being, s, need, type Args } from 'nervur/being';
45
+
46
+ export const Payments = need('payments', {
47
+ charge: {
48
+ args: s.object({ order: s.string(), amount: s.number(), notify: s.handle() }),
49
+ result: s.object({ pending: s.boolean() }),
50
+ },
51
+ });
52
+
53
+ export const Courier = need('courier', {
54
+ pickup: { args: s.object({ items: s.array(s.string()) }) },
55
+ });
56
+
57
+ export class Order extends Being.of({
58
+ kind: 'com.acme.order',
59
+ description: 'One order, from its first item to its shipping.',
60
+ needs: { pay: Payments },
61
+ cells: {
62
+ items: [] as string[],
63
+ total: 0,
64
+ state: 'open',
65
+ paidAt: 0,
66
+ courier: '',
67
+ },
68
+ roles: { owner: (asker) => asker.steward.owner === true },
69
+ state: (me) => me.cells.state,
70
+ asks: {
71
+ hire: {
72
+ for: 'steward',
73
+ hints: { idempotent: true },
74
+ args: s.object({ courier: s.handle() }),
75
+ result: s.string(),
76
+ },
77
+ add: {
78
+ in: 'open',
79
+ for: 'owner',
80
+ to: 'open',
81
+ args: s.object({ sku: s.string(), price: s.number() }),
82
+ result: s.object({ total: s.number() }),
83
+ },
84
+ checkout: { in: 'open', for: 'owner', to: 'paying' },
85
+ charged: { in: 'paying', args: s.reply(Payments.charge), to: ['paying', 'open'] },
86
+ settled: { in: 'paying', for: 'handle', to: 'paid' },
87
+ ship: { in: 'paid', for: 'owner', to: 'shipped' },
88
+ },
89
+ }) {
90
+ // The courier's invitation arrives as her standing. The same one twice is the same standing.
91
+ hire({ courier }: Args<Order, 'hire'>) {
92
+ this.cells.courier = courier;
93
+ return courier;
94
+ }
95
+
96
+ add({ sku, price }: Args<Order, 'add'>) {
97
+ this.cells.items = [...this.cells.items, sku];
98
+ this.cells.total += price;
99
+ return { total: this.cells.total };
100
+ }
101
+
102
+ checkout() {
103
+ if (this.cells.items.length === 0) this.fail('Add an item first.');
104
+ const notify = this.handle('settled', { once: true });
105
+ this.pay.charge({ order: this.id, amount: this.cells.total, notify }, { reply: 'charged' });
106
+ this.cells.state = 'paying';
107
+ }
108
+
109
+ charged({ error }: Args<Order, 'charged'>) {
110
+ if (error) this.cells.state = 'open';
111
+ }
112
+
113
+ settled() {
114
+ this.cells.state = 'paid';
115
+ this.cells.paidAt = this.house.now();
116
+ }
117
+
118
+ ship() {
119
+ if (this.cells.courier === '') this.fail('Hire a courier first.');
120
+ this.held(this.cells.courier, Courier).pickup({ items: this.cells.items });
121
+ this.cells.state = 'shipped';
122
+ }
123
+ }
124
+ ```
125
+
126
+ The order wrote no retry, no catch, no key and no address. `charge` is
127
+ an effect, so it leaves only once `checkout` has landed. Its answer comes
128
+ back to `charged`, and a refused charge opens the order again. The
129
+ provider calls `settled` through the handle once the money arrives.
130
+ `hire` takes a courier's invitation as her standing, and `ship` asks the
131
+ courier through it. `shipped` is a terminal state, and nothing leaves it.
132
+
133
+ ### The declaration
134
+
135
+ A class extends `Being.of({ … })`. That one object declares the class,
136
+ and TypeScript reads from it the types of her cells and her needs.
137
+
138
+ | Field | What it is |
139
+ | --- | --- |
140
+ | `kind` | the code's name: a reversed domain you own, then a name |
141
+ | `description` | one line for readers and agents |
142
+ | `cells` | the state and its defaults, written where a key is missing |
143
+ | `needs` | blueprints she calls, each under a member name she chooses |
144
+ | `roles` | named tests over the asker and her cells |
145
+ | `state` | a function of the being that names her current state |
146
+ | `asks` | one entry per method she answers |
147
+ | `view` | markup as text over her asks, which a screen renders |
148
+
149
+ A view is data, at most 64 KiB, carried in her describe to every asker.
150
+ It holds no script, and a renderer loads nothing it names, so a view
151
+ neither acts nor tracks. The library defines no markup: a renderer
152
+ speaks its own.
153
+
154
+ Every field is optional but `kind` and `asks`. A class with no `state`
155
+ has one state, named `ready`. The kind is how the house finds her code
156
+ again, so a bundler that renames classes changes nothing.
157
+
158
+ A method in TypeScript names its args with `Args<Class, 'method'>`,
159
+ since a subclass's method takes no type from its base. A method in
160
+ JavaScript writes nothing.
161
+
162
+ ### An ask's entry
163
+
164
+ | Field | What it says |
165
+ | --- | --- |
166
+ | `in` | the states where the ask exists; omitted, every state |
167
+ | `for` | the roles that may call it; omitted, every occupant but handles |
168
+ | `to` | the states it may land in; omitted, the state it began in |
169
+ | `args` | the schema of what it takes; omitted, the empty object alone |
170
+ | `result` | the schema of what it answers; omitted, nothing |
171
+ | `hints` | `readOnly`, `idempotent`, `destructive` |
172
+ | `examples` | cells, a role, args, fakes, and what it gives |
173
+ | `description` | one line for readers and agents |
174
+ | `wait` | milliseconds she may run; omitted, thirty seconds |
175
+
176
+ `in`, `for` and `to` each take one name or a list of names. A method
177
+ with no entry is never reached. An entry with no method is refused when
178
+ the house first loads the class.
179
+
180
+ Five roles are the house's. `handle` is whoever holds a handle to this
181
+ ask. `stranger` is the asker of a public being. `steward` is her
182
+ steward. `being` is another being her steward introduced to her. `root`
183
+ is the owner, through the house's hand. Every other role is yours, a
184
+ function of `(asker, me)`.
185
+
186
+ The house checks the table when it first loads the class. It refuses a
187
+ state no ask reaches, an ask no role reaches, and a role no ask names.
188
+ After a method runs, a state outside the entry's `to` fails the ask, and
189
+ nothing lands.
190
+
191
+ ### What she reaches
192
+
193
+ | Member | What it is |
194
+ | --- | --- |
195
+ | `this.id` | her own id |
196
+ | `this.position` | `steward`, `public` or `normal` |
197
+ | `this.asker` | `{ id, notes, steward }` of who asks now; `signer` for a stranger |
198
+ | `this.cells` | her values |
199
+ | `this.house.now()` | the time, in milliseconds since the epoch |
200
+ | `this.house.random({ length })` | random bytes |
201
+ | `this.house.alarm({ at, ask, args, key })` | one of her asks, at that time |
202
+ | `this.house.cancelAlarm({ key })` | an alarm removed |
203
+ | `this.<need>.<method>({…}, { reply? })` | an awaited call, or an effect |
204
+ | `this.held(id, Need).<ask>({…}, { reply?, after? })` | the same, on a standing, and a watch where `after` is given |
205
+ | `this.standings` | `list()`, `note(id, notes)` and `drop(id)` |
206
+ | `this.handle(ask, { bind?, notes?, once?, expires? })` | a handle to one of her asks |
207
+ | `this.invite(id, { notes?, expires? })` | a new occupant, as a handle |
208
+ | `this.occupants` | `list()`, `note(id, notes)` and `dismiss(id)` |
209
+ | `this.steward` | her steward, where she is not one |
210
+ | `this.powers` | the house's powers, where she is the steward |
211
+ | `this.fail(message)` | an error the asker can act on |
212
+
213
+ An alarm lives in the house's rows and survives every restart. A second
214
+ alarm under one key replaces the first. When its time comes, the house
215
+ asks her named ask as her steward would.
216
+
217
+ ### Awaited calls and effects
218
+
219
+ The callee decides how it is called, with one flag. A method or ask
220
+ marked `idempotent` is safe to repeat, so the caller awaits it during
221
+ her ask. Everything else is an effect. `readOnly` is stricter: it writes
222
+ nothing, it implies `idempotent`, and the house holds it.
223
+
224
+ An awaited call answers during her ask. `await this.fx.rate({…})`
225
+ returns the answer, and a failure throws an error she may catch. It
226
+ waits thirty seconds unless its entry says otherwise, and never more
227
+ than five minutes.
228
+
229
+ An awaited call never comes back to a being in its own chain. While she
230
+ awaits, she holds her queue. A call back to her, other than a
231
+ `readOnly` one, waits behind the ask that waits for it, until the wait
232
+ runs out and the call fails as `answered nothing`. Call back with an
233
+ effect instead.
234
+
235
+ An effect leaves after her ask lands. She calls it and it returns
236
+ nothing. The house writes it in the same write as her cells, then sends
237
+ it with one call id until it is answered. So an effect acts at most
238
+ once. Its answer comes back to the ask `reply` names, as `{ result }` or
239
+ `{ error: { message } }`.
240
+
241
+ An effect gives up at its deadline, seven days for a standing and the
242
+ offer's window for a faculty. The reply then hears an error. Effects to
243
+ one receiver leave one at a time, in the order she called them.
244
+
245
+ Asks to one being run one at a time, in the order they arrive. A
246
+ `readOnly` ask runs beside that queue, on the cells last landed.
247
+
248
+ ### Watching an answer
249
+
250
+ A watch is a `readOnly` ask asked with `after`, the answer you already
251
+ hold. The house answers at once where the answer differs. Where it is
252
+ the same, the house holds the ask and runs it again each time an ask of
253
+ that being lands. It answers the first answer that differs, or the same
254
+ one when the wait runs out. So a chat, an order's status or a dashboard
255
+ is a loop of watches, and nothing polls.
256
+
257
+ Through the hand, pass `after` beside the method, as the answer you
258
+ hold: `{ result: [...] }`. From a being, pass the result she holds:
259
+ `this.held(room, Messages).messages({}, { after: seen })`, inside a
260
+ `readOnly` ask of her own, so she stays free while she waits. Only a
261
+ `readOnly` ask is watched, and a watch on anything else is refused where
262
+ she makes it.
263
+
264
+ A watch moves only on what its asker could read, since it runs as that
265
+ asker. One asker holds one watch on one ask with the same args, and a
266
+ second answers the first at once. A watch across a door holds its
267
+ relation until it answers, since Quo moves a relation one ask at a time.
268
+ So watch a far being through a handle to the watched ask alone, a
269
+ relation of its own, and ask everything else on your other standing.
270
+
271
+ ### Relations
272
+
273
+ An **occupant** is someone who may ask her. She mints one with
274
+ `this.invite(id, { notes })`, which answers a handle, and lets one go with
275
+ `dismiss`.
276
+
277
+ A **standing** is someone she may ask. She receives one where an ask's
278
+ args carry an invitation under `s.handle`, or where her steward
279
+ introduces one. She asks through it with `this.held(id, Need)`, which
280
+ checks the standing's describe covers the need.
281
+
282
+ Every relation carries two sets of notes. `notes` are hers alone.
283
+ `steward` are her steward's, written when the steward made the relation,
284
+ and she reads them and never writes them. The order's `owner` role reads
285
+ the steward's notes, so only the steward decides who owns an order.
286
+
287
+ A **handle** is the only way a relation leaves her. `this.handle(ask)`
288
+ admits its holder to that one ask. `once` dismisses it after its first
289
+ ask lands, and `bind` fixes args the holder cannot change. The house
290
+ turns a handle into an invitation for a far house, or a token for a
291
+ faculty. A handle never enters her cells.
292
+
293
+ `expires`, on `this.handle`, `this.invite` and the steward's `invite`,
294
+ gives each invitation minted from the handle that many milliseconds to
295
+ be taken. Its first knock lands within that time, or it hears silence.
296
+ One taken in time never expires, and one without `expires` waits for
297
+ ever. Give it to every invitation a being mints on each answer, so the
298
+ unspent ones go.
299
+
300
+ ### The steward
301
+
302
+ Every house has one steward, with the id `steward`. The holder of the
303
+ house's hand asks her as the occupant `root`, which no sealed box can
304
+ forge. She alone holds `this.powers`.
305
+
306
+ ```ts
307
+ // classes/shop.ts
308
+ import { Being, s, type Args } from 'nervur/being';
309
+
310
+ export class Shop extends Being.of({
311
+ kind: 'com.acme.shop',
312
+ description: 'The shop’s steward: opens orders, and enrols whoever signs up.',
313
+ cells: { opened: 0 },
314
+ roles: { pilot: (asker) => asker.id === 'root' },
315
+ asks: {
316
+ open: {
317
+ for: 'pilot',
318
+ description: 'Opens an order, and hands back an invitation for its owner.',
319
+ args: s.object({ id: s.string() }),
320
+ result: s.object({ owner: s.invitation() }),
321
+ },
322
+ orders: { for: 'pilot', hints: { readOnly: true }, result: s.array(s.string()) },
323
+ hire: {
324
+ for: 'pilot',
325
+ description: 'Hands a courier’s invitation to an order, which takes it.',
326
+ hints: { idempotent: true },
327
+ args: s.object({ order: s.string(), courier: s.invitation() }),
328
+ result: s.string(),
329
+ },
330
+ enroll: {
331
+ for: 'being',
332
+ hints: { idempotent: true },
333
+ args: s.object({ signer: s.bytes() }),
334
+ result: s.object({ invitation: s.invitation() }),
335
+ },
336
+ },
337
+ }) {
338
+ open({ id }: Args<Shop, 'open'>) {
339
+ this.powers!.bear({ kind: 'com.acme.order', id });
340
+ this.cells.opened += 1;
341
+ return { owner: this.powers!.invite({ id, occupant: 'owner', notes: { owner: true } }) };
342
+ }
343
+
344
+ async orders() {
345
+ return (await this.powers!.list()).filter((being) => being.kind === 'com.acme.order').map((being) => being.id);
346
+ }
347
+
348
+ // She carries the invitation unopened, and the order she names takes it.
349
+ async hire({ order, courier }: Args<Shop, 'hire'>) {
350
+ return (await this.powers!.ask({ id: order, method: 'hire', args: { courier } })) as string;
351
+ }
352
+
353
+ // One order a signer: asked twice, the same id is borne once.
354
+ enroll({ signer }: Args<Shop, 'enroll'>) {
355
+ const id = `order-${Array.from(signer.subarray(0, 8), (byte) => byte.toString(16).padStart(2, '0')).join('')}`;
356
+ this.powers!.bear({ kind: 'com.acme.order', id });
357
+ const occupant = `owner-${Array.from(this.house.random({ length: 4 }), (byte) => byte.toString(16).padStart(2, '0')).join('')}`;
358
+ return { invitation: this.powers!.invite({ id, occupant, notes: { owner: true } }) };
359
+ }
360
+ }
361
+ ```
362
+
363
+ | Power | What it does |
364
+ | --- | --- |
365
+ | `bear({ kind, id, args })` | places a new being; the same id twice answers the first |
366
+ | `remove({ id })` | removes a being and everything of hers |
367
+ | `list()` | every being, her kind, whether she is absent, and her dead letters |
368
+ | `ask({ id, method, args }, { reply? })` | asks any being as her occupant `steward` |
369
+ | `introduce({ from, to, notes })` | gives `from` a standing on `to`, and `to` an occupant |
370
+ | `invite({ id, occupant, notes, expires? })` | gives a being a new occupant, and the steward its handle |
371
+
372
+ `bear`, `remove`, `introduce` and `invite` land in the steward's own
373
+ write. If her ask fails, none of them happened. A being borne runs her
374
+ `born` ask first, where her class declares one.
375
+
376
+ Every other being is placed as `normal`. She holds the standing
377
+ `steward` and the occupant `steward`, and can drop neither.
378
+
379
+ An invitation from outside lands where the owner says. The courier's
380
+ invitation reaches the owner by any road, a mail or a link. The owner
381
+ hands it to the steward's `hire` with the order it is for. `hire`
382
+ declares it `s.invitation`, so the steward carries it unopened and never
383
+ holds it. The order's `hire` declares it `s.handle`, so the order takes
384
+ it, and the standing is hers.
385
+
386
+ Taking an invitation is safe to repeat. The same invitation taken again
387
+ gives the same standing, so the order's `hire` is `idempotent`, and the
388
+ steward awaits it. The owner hears the standing, or why it was refused.
389
+ A steward that bears a being for an invitation bears her first, then
390
+ hands it to her the same way.
391
+
392
+ ### A public being
393
+
394
+ A house may name one public being, with the id `public`. She answers
395
+ strangers: every asker with no relation is the occupant `stranger`, and
396
+ `this.asker.signer` is the key the ask was signed with.
397
+
398
+ ```ts
399
+ // classes/lobby.ts
400
+ import { Being, need, s } from 'nervur/being';
401
+
402
+ /** What the lobby asks of her steward. */
403
+ const Signup = need('signup', {
404
+ enroll: {
405
+ hints: { idempotent: true },
406
+ args: s.object({ signer: s.bytes() }),
407
+ result: s.object({ invitation: s.invitation() }),
408
+ },
409
+ });
410
+
411
+ export class Lobby extends Being.of({
412
+ kind: 'com.acme.lobby',
413
+ description: 'The shop’s front door: a stranger signs up and receives an order of their own.',
414
+ asks: {
415
+ signup: { for: 'stranger', result: s.object({ invitation: s.invitation() }) },
416
+ },
417
+ }) {
418
+ signup() {
419
+ return this.held('steward', Signup).enroll({ signer: this.asker.signer! });
420
+ }
421
+ }
422
+ ```
423
+
424
+ A public being's asks are idempotent by default, since a stranger's box
425
+ may arrive twice. The house answers a replayed stranger's box from a
426
+ cache for ten minutes, and nothing runs twice. An ask marked
427
+ `hints: { idempotent: false }` is refused to strangers.
428
+
429
+ Signup awaits the steward. The lobby asks `enroll`, which is
430
+ `idempotent`, so the lobby awaits its answer and returns the invitation
431
+ unopened. A stranger who signs up twice holds one order.
432
+
433
+ Signup is this shop's choice, not the house's. A house is its owner's,
434
+ and a public being does only what its owner wrote. One may answer who
435
+ the house is and nothing more. A house with no public being answers
436
+ strangers silence.
437
+
438
+ ### Schemas
439
+
440
+ One builder gives the schema and the TypeScript type: `s.object`,
441
+ `s.string`, `s.number`, `s.integer`, `s.boolean`, `s.array`, `s.enum`,
442
+ `s.const`, `s.bytes`, `s.optional`, `s.handle`, `s.invitation` and
443
+ `s.reply`. They write JSON Schema 2020-12, in a subset the house checks
444
+ on every ask.
445
+
446
+ - `s.bytes` is a `Uint8Array` in the process and lowercase hex across a
447
+ door.
448
+ - `s.handle` carries a relation. A handle leaves as an invitation, and
449
+ an invitation arrives as a new standing id.
450
+ - `s.invitation` carries an invitation unopened, as the lobby does.
451
+ Passed on under `s.handle`, it is taken by the being that receives
452
+ it. Taking one is safe to repeat: the same invitation gives the same
453
+ standing.
454
+ - `s.reply(method)` is the args of an effect's reply: `{ result }` or
455
+ `{ error: { message } }`.
456
+
457
+ ### Needs
458
+
459
+ A need is a blueprint at its minimum: what she will call, never what a
460
+ faculty offers. `need(name, methods)` writes one, and she binds it to a
461
+ member of her own in `needs`. An offer covers a need when four things
462
+ hold.
463
+
464
+ 1. The blueprint's name is the same.
465
+ 2. Every method the need names is offered. More are allowed.
466
+ 3. The offer requires no property the need does not require.
467
+ 4. Each method is `idempotent` in both, or in neither.
468
+
469
+ Every need is covered, or she is absent. An absent being answers silence
470
+ and keeps her cells, and answers again once an offer covers her needs.
471
+
472
+ ### Testing on the bench
473
+
474
+ The bench opens two houses in one process, on fake memory, keys, clock
475
+ and carry. So every ask crosses a door as it would in production.
476
+
477
+ ```ts
478
+ // order.test.ts
479
+ import assert from 'node:assert/strict';
480
+ import { test } from 'node:test';
481
+ import { Bench } from 'nervur/bench';
482
+ import { Order } from './classes/order.ts';
483
+ import { Payments, paymentsOffer } from './payments.ts';
484
+
485
+ test('Order keeps her table: every state and role shows what it owes', async () => {
486
+ await Bench.check(Order);
487
+ });
488
+
489
+ test('An order is paid once the provider calls the handle it was given', async () => {
490
+ const payments = new Payments();
491
+ const bench = await Bench.open({ classes: [Order], offers: [paymentsOffer(payments)] });
492
+ const order = await bench.place(Order, { id: 'first' });
493
+
494
+ assert.deepEqual(await order.ask('checkout'), { error: { message: 'Add an item first.' } });
495
+ assert.deepEqual(await order.ask('add', { sku: 'tea', price: 4 }), { result: { total: 4 } });
496
+ assert.deepEqual(await order.ask('checkout'), { result: null });
497
+ await bench.settle();
498
+ assert.equal((await order.cells())!.state, 'paying', 'the charge left once checkout landed, and answered pending');
499
+
500
+ assert.deepEqual(await payments.settle('first'), { result: null });
501
+ await bench.settle();
502
+ assert.equal((await order.cells())!.state, 'paid');
503
+ assert.deepEqual(await order.ask('add', { sku: 'jam', price: 1 }), { error: { message: 'not in this state' } });
504
+ });
505
+ ```
506
+
507
+ `place(Class, { id, cells })` bears a being and starts her from those
508
+ cells. A placed being is asked with `ask(method, args, { role })`,
509
+ described with `describe({ role })`, and read with `cells()`. `settle()`
510
+ lets every effect and reply run, and `advance(ms)` moves the fake clock.
511
+
512
+ The bench plays a role with an occupant named for it, whose own notes and
513
+ steward notes both hold the role as `true`.
514
+
515
+ A steward and a public being are placed where the house places them.
516
+ `Bench.open({ steward: Shop, public: Lobby, classes: [Order] })` opens the
517
+ author's house with them there, and `place(Shop)` and `place(Lobby)` find
518
+ them. A role `root` holds is played through the hand, on any being. On
519
+ the public being, a role a stranger holds is played by a box with no
520
+ relation, signed with a key drawn from the bench's seed.
521
+ `Bench.check(Shop, { position: 'steward' })` and `Bench.check(Lobby, {
522
+ position: 'public', steward: Shop })` check them.
523
+
524
+ An entry's `examples` are tests the bench runs. Each is one ask on a
525
+ fresh bench: `cells` start her, `role` asks, `args` are the ask's,
526
+ `fakes` answer her needs, and `gives` is the answer owed. `Bench.check`
527
+ runs every example twice from one seed and flags a class that answers
528
+ differently. It then describes every state to every role, and names every
529
+ finding that failed.
530
+
531
+ ## A faculty
532
+
533
+ A faculty is anything a being may call that is not a being: a payment
534
+ provider, a mail sender, a model, a sensor. The ground hands it to the
535
+ house as an offer, and the house matches it to every need it covers.
536
+
537
+ ```ts
538
+ // payments.ts
539
+ import type { FacultyContext, Offer } from 'nervur';
540
+ import { need, s } from 'nervur/being';
541
+
542
+ /** What the faculty offers. An order's need is covered by it. */
543
+ export const PaymentsBlueprint = need('payments', {
544
+ charge: {
545
+ description: 'Charges an order, and calls notify once the money arrives.',
546
+ args: s.object({ order: s.string(), amount: s.number(), notify: s.handle() }),
547
+ result: s.object({ pending: s.boolean() }),
548
+ },
549
+ });
550
+
551
+ type Answer = { result: { pending: boolean } } | { error: { message: string } };
552
+
553
+ /**
554
+ * A payment provider in the ground's process. It answers a call id it has
555
+ * seen with the answer it gave, so an effect sent twice charges once. A
556
+ * provider that changes the world keeps these where a restart keeps them.
557
+ */
558
+ export class Payments {
559
+ readonly #answered = new Map<string, Answer>();
560
+ readonly #waiting = new Map<string, { notify: string; context: FacultyContext }>();
561
+
562
+ async charge({ order, amount, notify }: { order: string; amount: number; notify: string }, context: FacultyContext): Promise<Answer> {
563
+ const seen = this.#answered.get(context.id);
564
+ if (seen !== undefined) return seen;
565
+ const answer: Answer = amount > 0 ? { result: { pending: true } } : { error: { message: 'Nothing to charge.' } };
566
+ if (amount > 0) this.#waiting.set(order, { notify, context });
567
+ this.#answered.set(context.id, answer);
568
+ return answer;
569
+ }
570
+
571
+ /** The money for an order arrived: the provider calls the handle it was given. */
572
+ settle(order: string) {
573
+ const waiting = this.#waiting.get(order);
574
+ if (waiting === undefined) throw new Error(`no charge waits for ${order}`);
575
+ this.#waiting.delete(order);
576
+ return waiting.context.call({ token: waiting.notify, id: `settle:${order}` });
577
+ }
578
+ }
579
+
580
+ /** The offer a ground hands: the blueprint, the object, and a week's memory of call ids. */
581
+ export const paymentsOffer = (payments: Payments): Offer => ({ blueprint: PaymentsBlueprint, object: payments, window: 7 * 86_400_000 });
582
+ ```
583
+
584
+ An offer is `{ blueprint, object, kinds?, window?, handler?, stop? }`.
585
+
586
+ - **The object has the blueprint's methods.** Each takes one args object
587
+ and a context, and answers `{ result }` or `{ error: { message } }`. A
588
+ throw is a failure to answer, and the house tries again.
589
+ - **The context holds the call id.** An effect arrives again with the same
590
+ call id when an answer was lost. A faculty that changes the world
591
+ answers a call id it has seen with the answer it gave.
592
+ - **A handle arrives as a token.** The faculty calls it with
593
+ `context.call({ token, args, id })`. Its own `id` makes that call run
594
+ once, however often it is sent.
595
+ - **`kinds` is the ground's grant.** It lists the classes that may hold
596
+ the offer. Without it, every class whose need it covers holds it.
597
+ - **`window` is how long the faculty remembers a call id.** It is seven
598
+ days where omitted, and the house gives up on an effect at it.
599
+ - **`handler` answers HTTP on the ground's one listener.** It takes a
600
+ `Request` and answers a `Response`, or `null` where the request is not
601
+ its own. A site, an API or an MCP server is a faculty with a handler.
602
+ - **`stop` is called when the ground stops**, faculties in the reverse of
603
+ the order they were made.
604
+
605
+ The object arrives living. The ground makes and starts the faculty, and
606
+ the house never starts, stops or restarts it. Every being whose need it
607
+ covers holds the same object.
608
+
609
+ A faculty is the ground's, never a house's. The ground's recipe makes
610
+ each one by name, from the settings the ground hands it. `env` is the
611
+ ground's environment, so a secret stays on its machine and out of the
612
+ code. `dir(name)` answers a folder of the faculty's own.
613
+
614
+ ```ts
615
+ // recipe.ts
616
+ import { Payments, paymentsOffer } from './payments.ts';
617
+
618
+ export const faculties = () => ({
619
+ payments: paymentsOffer(new Payments()),
620
+ });
621
+ ```
622
+
623
+ Policy over a faculty is written as a being. Limits, approvals and
624
+ quotas are not the faculty's. One being holds the raw faculty through
625
+ `kinds`, and every other being reaches her through a standing.
626
+
627
+ ## A ground
628
+
629
+ A ground is the process houses run in, and you write none. `nervur up`
630
+ runs one on a folder: the recipe of its faculties, and a folder of code
631
+ for each house. It keeps a record of its houses, and opens each on the
632
+ bodies the record names.
633
+
634
+ ### The shop's folder
635
+
636
+ ```text
637
+ nervur-ground/
638
+ recipe.ts
639
+ payments.ts
640
+ classes/index.ts, order.ts, shop.ts, lobby.ts
641
+ state/ made by the ground, its owner's alone
642
+ ```
643
+
644
+ A house's folder names what the house holds.
645
+
646
+ ```ts
647
+ // classes/index.ts
648
+ export { Shop as steward } from './shop.ts';
649
+ export { Lobby as public } from './lobby.ts';
650
+ import { Order } from './order.ts';
651
+
652
+ export const beings = [Order];
653
+ ```
654
+
655
+ ### Running it
656
+
657
+ ```bash
658
+ npx nervur up .
659
+ ```
660
+
661
+ The ground boots in one order, and a stop is that order reversed, on an
662
+ interrupt and on `SIGTERM`.
663
+
664
+ 1. **Lock.** One ground to its state.
665
+ 2. **Its own.** Its seeds and its record open.
666
+ 3. **Faculties.** The recipe makes each, in its order.
667
+ 4. **Houses.** Each house of the record opens on its bodies.
668
+ 5. **Hook.** The listeners, then the hand on its socket.
669
+ 6. **Ready.** It tells systemd it is up, where systemd waits.
670
+
671
+ It is set by its environment.
672
+
673
+ | Setting | What it sets |
674
+ | --- | --- |
675
+ | `NERVUR_TCP_PORT` | its TCP port, 7300 where unset |
676
+ | `NERVUR_HTTP_PORT` | its HTTP port, for Quo over the web and every handler; no HTTP where unset |
677
+ | `NERVUR_ADDRESSES` | the public addresses it writes into invitations, by commas |
678
+ | `NERVUR_BIND` | the address it listens on, `0.0.0.0` where unset |
679
+ | `NERVUR_STATE` | its state, `state/` in its folder where unset |
680
+ | `NERVUR_KEYCHAIN` | a keychain service holding its seeds on macOS, in place of files |
681
+ | `NERVUR_WAIT` | its bound on every ask, in milliseconds |
682
+
683
+ The state holds each house's seed in a file its owner alone reads, and
684
+ each house's ledger. A ledger appends every write and never rewrites
685
+ one, and one behind its witness is refused, so a restored backup cannot
686
+ replay what a house already answered. A lost seed is a lost house.
687
+
688
+ A house is added once, and the ground opens it again at every start.
689
+
690
+ ```bash
691
+ npx nervur houses add name=shop memory='{"body":"ledger"}' classes='{"body":"folder","at":"classes"}' faculties='["payments"]'
692
+ ```
693
+
694
+ `memory` and `classes` name the bodies the house is handed. `ledger` and
695
+ `folder` are the ground's own, and a recipe may add more, a git registry
696
+ or another store among them. `faculties` names what the house receives,
697
+ and within it each offer's `kinds` names the classes that hold it.
698
+
699
+ ### The hand
700
+
701
+ The command is a face on the ground's hand. It holds no word of its own
702
+ beyond `up` and `service`, and reads every other from what the ground
703
+ describes.
704
+
705
+ ```bash
706
+ npx nervur help
707
+ ```
708
+
709
+ A faculty's method is called as its owner calls it, and a faculty named
710
+ alone shows its methods. Args are one JSON object, or words `key=value`.
711
+
712
+ ```bash
713
+ npx nervur houses list
714
+ ```
715
+
716
+ `ask` asks a being in a house as `root`: the steward, or the being
717
+ `--id` names. The owner lands a paper in a being with one ask, and her
718
+ `accept` names `root` in its `for`, or names no `for`.
719
+
720
+ ```bash
721
+ npx nervur ask shop open id=first
722
+ ```
723
+
724
+ ```bash
725
+ npx nervur ask shop --id alice accept invitation=7b22…
726
+ ```
727
+
728
+ Each prints one JSON value. It exits 0 on a result and 1 on an error
729
+ answered. It exits 2 where nothing was asked, so a script tells a
730
+ refusal from a ground that is down. The hand is `state/hand` in the
731
+ folder where the command runs, or the socket `--at` or `NERVUR_HAND`
732
+ names.
733
+
734
+ ### Running it as a service
735
+
736
+ `nervur service` writes the unit that runs a folder: a systemd user unit
737
+ on Linux, started again after any exit, and a launchd agent on macOS.
738
+
739
+ ```bash
740
+ npx nervur service . > ~/.config/systemd/user/nervur-ground.service
741
+ ```
742
+
743
+ ```bash
744
+ systemctl --user enable --now nervur-ground
745
+ ```
746
+
747
+ A user unit stops when its user logs out, unless lingering is enabled
748
+ with `loginctl enable-linger`. On macOS, the agent goes to
749
+ `~/Library/LaunchAgents/` and is started with `launchctl bootstrap`.
750
+
751
+ ### In a page
752
+
753
+ `BrowserGround.open({ recipe })` from `nervur/browser` runs the same
754
+ houses in a page. Every tab and the service worker of one origin share
755
+ one ground: the one holding the Web Lock runs it, and the others reach
756
+ its hand over a `BroadcastChannel`. When it closes, the next opens the
757
+ ground from the same storage. `hand` answers the same three requests
758
+ the command sends: `describe`, a faculty's method, and an ask of a being
759
+ in a named house.
760
+
761
+ Its bodies are the browser's. Seeds are sealed under an AES key that
762
+ IndexedDB holds unextractable, and memory is IndexedDB, named
763
+ `indexeddb` in an entry. Classes load from the page's own origin through
764
+ the `origin` body, `{ body: 'origin', at: '/house/index.js' }`, and a
765
+ path off the origin is refused. The recipe is the object you pass: its
766
+ faculties, made by a function, and any custom body.
767
+
768
+ Any script on the origin can use the ground's keys, so the ground is
769
+ whoever serves the origin's script. Give it an origin of its own, serve
770
+ nothing a stranger wrote there, and set a strict content security
771
+ policy. The page and the house's module must share one copy of
772
+ `nervur/being`, since a house knows a class by a mark that module gives.
773
+ A bundler's shared chunk or an import map gives the one copy.
774
+
775
+ A service worker opens the ground the same way, on a push, where no tab
776
+ runs it. It may not `import()`, so hand it the modules it imported
777
+ itself: `platform: { load: (href) => modules[new URL(href).pathname] }`.
778
+
779
+ ### In an app
780
+
781
+ `AppGround.open({ shell })` from `nervur/app` is a BrowserGround in the
782
+ app's web view, on two interfaces the shell fills in native code:
783
+
784
+ - `NativeSecrets`: `get(name)` and `set(name, value)`, text kept by the
785
+ iOS Keychain or the Android Keystore on this device alone.
786
+ - `NativeStore`: `get(key)`, `keys(prefix)`, and `swap(writes, expect)`,
787
+ which lands every write only where each key in `expect` still holds
788
+ what it names, and answers whether it landed.
789
+
790
+ The library holds the rest. Each house's seed goes into the secret
791
+ store, and each memory into the native store, which the system never
792
+ evicts as it may a web view's storage. A push token reaches the ground
793
+ through a faculty your recipe makes.
794
+
795
+ ## What the house guarantees
796
+
797
+ 1. **Every need is covered, or she is absent.** An absent being answers
798
+ silence and keeps her cells.
799
+ 2. **The asker's id is true.** Wherever the asker lives, the id she reads
800
+ is who asked.
801
+ 3. **Her notes are hers.** No one else writes them, and she never writes
802
+ her steward's.
803
+ 4. **She never holds an invitation's bytes.** Handles leave, standing ids
804
+ arrive, and `s.invitation` passes through unopened.
805
+ 5. **One ask at a time.** Asks to one being run in order, and `readOnly`
806
+ asks run beside them.
807
+ 6. **An ask lands whole or not at all.** Her cells, her relations, her
808
+ effects and her call ids land in one write.
809
+ 7. **An effect acts at most once, and its outcome is known.** Its answer,
810
+ or its giving up, reaches her through `reply`.
811
+ 8. **Her class is checked before her first ask.** A class that fails
812
+ leaves her absent, and the refusal says why.
813
+
814
+ A failed ask changed nothing she owns, so asking again is safe.