nervur 0.19.2 → 0.20.1

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 (235) hide show
  1. package/README.md +156 -104
  2. package/dist/being/being.d.ts +8 -15
  3. package/dist/being/being.js +20 -67
  4. package/dist/being/digest.d.ts +1 -1
  5. package/dist/being/digest.js +7 -27
  6. package/dist/being/faculty.d.ts +10 -0
  7. package/dist/being/faculty.js +74 -0
  8. package/dist/being/index.d.ts +5 -5
  9. package/dist/being/index.js +6 -5
  10. package/dist/being/kind.d.ts +4 -0
  11. package/dist/being/kind.js +28 -0
  12. package/dist/being/types.d.ts +35 -63
  13. package/dist/being/types.js +3 -57
  14. package/dist/being/words.d.ts +13 -0
  15. package/dist/being/words.js +14 -0
  16. package/dist/cli/command.d.ts +13 -0
  17. package/dist/cli/command.js +194 -0
  18. package/dist/cli/harbor.d.ts +21 -0
  19. package/dist/cli/harbor.js +141 -0
  20. package/dist/cli/main.d.ts +2 -0
  21. package/dist/cli/main.js +14 -0
  22. package/dist/contract/index.d.ts +39 -0
  23. package/dist/contract/index.js +33 -0
  24. package/dist/crypto/aes.d.ts +3 -0
  25. package/dist/crypto/aes.js +24 -0
  26. package/dist/crypto/bytes.d.ts +6 -0
  27. package/dist/crypto/bytes.js +33 -0
  28. package/dist/crypto/ed25519.d.ts +3 -0
  29. package/dist/crypto/ed25519.js +85 -0
  30. package/dist/crypto/hash.d.ts +2 -0
  31. package/dist/crypto/hash.js +11 -0
  32. package/dist/crypto/index.d.ts +7 -0
  33. package/dist/crypto/index.js +10 -0
  34. package/dist/crypto/json.d.ts +8 -0
  35. package/dist/crypto/json.js +250 -0
  36. package/dist/crypto/mlkem.d.ts +12 -0
  37. package/dist/crypto/mlkem.js +36 -0
  38. package/dist/crypto/subtle.d.ts +10 -0
  39. package/dist/crypto/subtle.js +29 -0
  40. package/dist/crypto/x25519.d.ts +2 -0
  41. package/dist/crypto/x25519.js +21 -0
  42. package/dist/folder/index.d.ts +14 -0
  43. package/dist/folder/index.js +87 -0
  44. package/dist/harbor/catalogue.d.ts +25 -0
  45. package/dist/harbor/catalogue.js +93 -0
  46. package/dist/harbor/dock.d.ts +29 -0
  47. package/dist/harbor/dock.js +122 -0
  48. package/dist/harbor/harbor.d.ts +31 -0
  49. package/dist/harbor/harbor.js +146 -0
  50. package/dist/harbor/index.d.ts +7 -10
  51. package/dist/harbor/index.js +9 -18
  52. package/dist/harbor/package.d.ts +10 -0
  53. package/dist/harbor/package.js +115 -0
  54. package/dist/harbor/registry.d.ts +9 -0
  55. package/dist/harbor/registry.js +78 -0
  56. package/dist/harbor/root-line.d.ts +9 -0
  57. package/dist/harbor/root-line.js +36 -0
  58. package/dist/harbor/terrain.d.ts +9 -0
  59. package/dist/harbor/terrain.js +2 -0
  60. package/dist/index.d.ts +7 -0
  61. package/dist/index.js +14 -0
  62. package/dist/pointer/bodies.d.ts +26 -0
  63. package/dist/pointer/bodies.js +64 -0
  64. package/dist/pointer/index.d.ts +2 -0
  65. package/dist/pointer/index.js +4 -0
  66. package/dist/pointer/world.d.ts +35 -0
  67. package/dist/pointer/world.js +85 -0
  68. package/dist/quo/door.d.ts +34 -0
  69. package/dist/quo/door.js +172 -0
  70. package/dist/quo/index.d.ts +8 -0
  71. package/dist/quo/index.js +11 -0
  72. package/dist/quo/invitation.d.ts +7 -0
  73. package/dist/quo/invitation.js +15 -0
  74. package/dist/quo/keys.d.ts +40 -0
  75. package/dist/quo/keys.js +79 -0
  76. package/dist/quo/payload.d.ts +15 -0
  77. package/dist/quo/payload.js +55 -0
  78. package/dist/quo/relations.d.ts +40 -0
  79. package/dist/quo/relations.js +33 -0
  80. package/dist/quo/reply.d.ts +13 -0
  81. package/dist/quo/reply.js +35 -0
  82. package/dist/quo/seal.d.ts +42 -0
  83. package/dist/quo/seal.js +78 -0
  84. package/dist/quo/standing.d.ts +39 -0
  85. package/dist/quo/standing.js +90 -0
  86. package/dist/tcp/frame.d.ts +29 -0
  87. package/dist/tcp/frame.js +74 -0
  88. package/dist/tcp/index.d.ts +18 -0
  89. package/dist/tcp/index.js +164 -0
  90. package/dist/ward/allowance.d.ts +13 -9
  91. package/dist/ward/allowance.js +28 -67
  92. package/dist/ward/cells.d.ts +6 -6
  93. package/dist/ward/cells.js +67 -179
  94. package/dist/ward/index.d.ts +6 -10
  95. package/dist/ward/index.js +7 -13
  96. package/dist/ward/partition.d.ts +44 -60
  97. package/dist/ward/partition.js +56 -285
  98. package/dist/ward/pilot.d.ts +8 -0
  99. package/dist/ward/pilot.js +50 -0
  100. package/dist/ward/stance.d.ts +27 -20
  101. package/dist/ward/stance.js +174 -406
  102. package/dist/ward/ward-being.d.ts +35 -0
  103. package/dist/ward/ward-being.js +82 -0
  104. package/dist/ward/ward.d.ts +45 -11
  105. package/dist/ward/ward.js +239 -357
  106. package/package.json +24 -31
  107. package/src/being/being.ts +35 -78
  108. package/src/being/digest.ts +7 -35
  109. package/src/being/faculty.ts +73 -0
  110. package/src/being/index.ts +6 -6
  111. package/src/being/kind.ts +30 -0
  112. package/src/being/types.ts +50 -146
  113. package/src/being/words.ts +31 -0
  114. package/src/cli/command.ts +188 -0
  115. package/src/cli/harbor.ts +137 -0
  116. package/src/cli/main.ts +13 -0
  117. package/src/contract/index.ts +73 -0
  118. package/src/crypto/aes.ts +26 -0
  119. package/src/crypto/bytes.ts +37 -0
  120. package/src/crypto/ed25519.ts +84 -0
  121. package/src/crypto/hash.ts +14 -0
  122. package/src/crypto/index.ts +10 -0
  123. package/src/crypto/json.ts +241 -0
  124. package/src/crypto/mlkem.ts +38 -0
  125. package/src/crypto/subtle.ts +33 -0
  126. package/src/crypto/x25519.ts +21 -0
  127. package/src/folder/index.ts +92 -0
  128. package/src/harbor/catalogue.ts +110 -0
  129. package/src/harbor/dock.ts +137 -0
  130. package/src/harbor/harbor.ts +180 -0
  131. package/src/harbor/index.ts +9 -19
  132. package/src/harbor/package.ts +131 -0
  133. package/src/harbor/registry.ts +73 -0
  134. package/src/harbor/root-line.ts +36 -0
  135. package/src/harbor/terrain.ts +15 -0
  136. package/src/index.ts +20 -0
  137. package/src/pointer/bodies.ts +65 -0
  138. package/src/pointer/index.ts +4 -0
  139. package/src/pointer/world.ts +94 -0
  140. package/src/quo/door.ts +178 -0
  141. package/src/quo/index.ts +11 -0
  142. package/src/quo/invitation.ts +15 -0
  143. package/src/quo/keys.ts +91 -0
  144. package/src/quo/payload.ts +61 -0
  145. package/src/quo/relations.ts +62 -0
  146. package/src/quo/reply.ts +38 -0
  147. package/src/quo/seal.ts +101 -0
  148. package/src/quo/standing.ts +111 -0
  149. package/src/stand/main.ts +20 -0
  150. package/src/stand/stand.ts +220 -0
  151. package/src/tcp/frame.ts +78 -0
  152. package/src/tcp/index.ts +173 -0
  153. package/src/ward/allowance.ts +37 -75
  154. package/src/ward/cells.ts +63 -176
  155. package/src/ward/index.ts +7 -17
  156. package/src/ward/partition.ts +86 -326
  157. package/src/ward/pilot.ts +50 -0
  158. package/src/ward/stance.ts +186 -420
  159. package/src/ward/ward-being.ts +103 -0
  160. package/src/ward/ward.ts +256 -359
  161. package/dist/being/lent.d.ts +0 -32
  162. package/dist/being/lent.js +0 -72
  163. package/dist/being/silence.d.ts +0 -12
  164. package/dist/being/silence.js +0 -41
  165. package/dist/conformance/assert.d.ts +0 -11
  166. package/dist/conformance/assert.js +0 -106
  167. package/dist/conformance/beings.d.ts +0 -199
  168. package/dist/conformance/beings.js +0 -188
  169. package/dist/conformance/estate.d.ts +0 -5
  170. package/dist/conformance/estate.js +0 -388
  171. package/dist/conformance/index.d.ts +0 -80
  172. package/dist/conformance/index.js +0 -819
  173. package/dist/conformance/reach.d.ts +0 -10
  174. package/dist/conformance/reach.js +0 -72
  175. package/dist/conformance/store.d.ts +0 -5
  176. package/dist/conformance/store.js +0 -113
  177. package/dist/harbor/box.d.ts +0 -121
  178. package/dist/harbor/box.js +0 -121
  179. package/dist/harbor/core.d.ts +0 -55
  180. package/dist/harbor/core.js +0 -662
  181. package/dist/harbor/dial.d.ts +0 -9
  182. package/dist/harbor/dial.js +0 -81
  183. package/dist/harbor/memory.d.ts +0 -29
  184. package/dist/harbor/memory.js +0 -104
  185. package/dist/harbor/reach.d.ts +0 -36
  186. package/dist/harbor/reach.js +0 -199
  187. package/dist/harbor/store.d.ts +0 -35
  188. package/dist/harbor/store.js +0 -62
  189. package/dist/vector/cases.d.ts +0 -42
  190. package/dist/vector/cases.js +0 -223
  191. package/dist/vector/index.d.ts +0 -6
  192. package/dist/vector/index.js +0 -8
  193. package/dist/vector/stand.d.ts +0 -9
  194. package/dist/vector/stand.js +0 -77
  195. package/dist/vector/world.d.ts +0 -144
  196. package/dist/vector/world.js +0 -209
  197. package/dist/ward/arithmetic.d.ts +0 -31
  198. package/dist/ward/arithmetic.js +0 -249
  199. package/dist/ward/door.d.ts +0 -18
  200. package/dist/ward/door.js +0 -186
  201. package/dist/ward/ground.d.ts +0 -29
  202. package/dist/ward/ground.js +0 -56
  203. package/dist/ward/heirs.d.ts +0 -13
  204. package/dist/ward/heirs.js +0 -117
  205. package/dist/ward/json.d.ts +0 -2
  206. package/dist/ward/json.js +0 -163
  207. package/dist/ward/owner.d.ts +0 -13
  208. package/dist/ward/owner.js +0 -220
  209. package/dist/ward/seal.d.ts +0 -52
  210. package/dist/ward/seal.js +0 -150
  211. package/src/being/lent.ts +0 -72
  212. package/src/being/silence.ts +0 -46
  213. package/src/conformance/assert.ts +0 -100
  214. package/src/conformance/beings.ts +0 -188
  215. package/src/conformance/estate.ts +0 -412
  216. package/src/conformance/index.ts +0 -965
  217. package/src/conformance/reach.ts +0 -83
  218. package/src/conformance/store.ts +0 -125
  219. package/src/harbor/box.ts +0 -131
  220. package/src/harbor/core.ts +0 -699
  221. package/src/harbor/dial.ts +0 -112
  222. package/src/harbor/memory.ts +0 -123
  223. package/src/harbor/reach.ts +0 -221
  224. package/src/harbor/store.ts +0 -91
  225. package/src/vector/cases.ts +0 -257
  226. package/src/vector/index.ts +0 -11
  227. package/src/vector/stand.ts +0 -76
  228. package/src/vector/world.ts +0 -232
  229. package/src/ward/arithmetic.ts +0 -251
  230. package/src/ward/door.ts +0 -186
  231. package/src/ward/ground.ts +0 -142
  232. package/src/ward/heirs.ts +0 -116
  233. package/src/ward/json.ts +0 -144
  234. package/src/ward/owner.ts +0 -214
  235. package/src/ward/seal.ts +0 -178
package/README.md CHANGED
@@ -1,119 +1,171 @@
1
- # Nervur
1
+ # nervur
2
2
 
3
- Nervur is the first open source kit of Quo, the library itself: harbor, ward
4
- and being, with no dependencies.
3
+ Nervur's kit of [Quo](https://quo.systems). Quo is a protocol: an object
4
+ asks another object and gets an answer, without knowing where it is. This
5
+ package is one implementation of it, and the two names never stand for
6
+ the same thing. Where they disagree, Quo's spec wins.
5
7
 
6
- Quo is a protocol that lets an object ask another object and get an answer,
7
- without knowing whether that other object is in the same process, on the same
8
- device, or on another planet. Quo is the protocol and this is one
9
- implementation of it; the two names never stand for the same thing.
8
+ You write classes, beings and faculties, and nothing else. A harbor
9
+ unpacks a whole world from them.
10
10
 
11
- It is three words, two of which are beings, and two edges.
12
- Nothing else is Quo.
11
+ ## Install
13
12
 
14
- - **Harbor.** The program a device runs to boot wards. Owns the wire and the
15
- operating system. Not a being.
16
- - **Ward.** One process of its harbor. A being plus ward functions. Keeps
17
- beings and judges its door.
18
- - **Being.** One ordinary object, one voice.
19
-
20
- The truth is the spec, at <https://quo.systems/spec>, with the vectors it
21
- is proved by at <https://quo.systems/vectors>. It is the protocol alone:
22
- what any ward in any language must do for its bytes to be Quo. It is
23
- self-contained and assumes nothing from any other document, this README
24
- included. Read it first, at the source, which is Quo's and not this
25
- package's to carry.
26
-
27
- What this one kit chose, and another kit may refuse, is documented at
28
- <https://nervur.org/docs>. It assumes the spec, and where the two disagree
29
- the spec wins.
30
-
31
- ## This package
32
-
33
- A TypeScript implementation, written against Node's own type stripping, with
34
- no dependencies. The package ships JavaScript with declarations beside the
35
- source it was emitted from, because Node strips types nowhere under
36
- `node_modules`.
37
-
38
- ```
39
- src/being/ the Being side: what a being author imports, if anything
40
- src/ward/ the ward: the Ground contract, door, seal, arithmetic, heirs, stance, allowance
41
- src/harbor/ MemoryHarbor, the box's being, and the harbor core with its store, reach and dialer
42
- src/conformance/ behaviours any ward must show, written against the truth
43
- src/vector/ vector mode, and the world the door's cases are taken in
13
+ ```sh
14
+ npm install nervur
44
15
  ```
45
16
 
46
- The suites live beside the source in the tree this is developed in and are
47
- not in the package: what proves the kit is not what a consumer installs.
48
- What does ship of them is `src/conformance/`, which is the part written for
49
- somebody else's ward rather than for this one.
50
-
51
- ## Requirements
17
+ Node 22.18 or later. The main entry imports no platform, so it runs in a
18
+ browser, Deno, Bun and workerd as well. `nervur/folder` keeps a harbor on
19
+ a disk and needs Node's file system.
52
20
 
53
- Node 22.18 or later, and nothing else. The package has no dependencies, and
54
- it ships JavaScript with declarations, so nothing is compiled on the way in.
21
+ ## A world in one process
55
22
 
56
- ## Where it is proven
57
-
58
- The same conformance suite this package exports runs against several worlds:
59
- one ward, one harbor with a ward per being, two harbors, and the harbor core
60
- over a memory store. A world declares what it can express and a chapter it
61
- cannot reach is skipped by name rather than failed, which is how a kit in
62
- another language reports the same suite honestly.
63
-
64
- It is run again out of a bundle in a real Chromium, in workerd, in Deno and
65
- in Bun, because a ward's truth must hold wherever a ward runs.
23
+ ```js
24
+ import { Being, Faculty, World } from 'nervur';
25
+
26
+ // A contract, and a faculty that fulfils it. Every class names its kind.
27
+ class Clock extends Faculty {
28
+ static kind = 'org.example.clock';
29
+ }
30
+ class SystemTime extends Clock {
31
+ static kind = 'org.example.system-time';
32
+ static asks = { now: {} };
33
+ now() {
34
+ return { now: Date.now() };
35
+ }
36
+ }
37
+
38
+ // A being, who lends a clock by its contract.
39
+ class Greeter extends Being {
40
+ static kind = 'org.example.greeter';
41
+ static asks = { hello: {} };
42
+ async hello(args, asker) {
43
+ if (!this.stance.standings.get('clock')) await this.stance.lend(Clock, 'clock');
44
+ const time = await this.stance.standings.get('clock')?.ask('now');
45
+ return { hello: asker.id ?? 'stranger', time };
46
+ }
47
+ }
48
+
49
+ // The module these classes are: its name, its version, its classes.
50
+ const greetings = { module: 'org.example.greetings', version: '1.0.0', classes: [SystemTime, Greeter] };
51
+
52
+ // A harbor whose terrain can load that module, and whose catalogue runs it.
53
+ const world = new World();
54
+ const harbor = await world.harbor('home', [greetings]);
55
+ await harbor.root('ask', { being: 'catalogue', method: 'add', args: { from: 'org.example.greetings' } });
56
+ await harbor.root('open', { key: 'clock', class: 'org.example.system-time' });
57
+ await harbor.root('host', { ward: 'alice' });
58
+ await harbor.ward('alice').root('boot', { key: 'greeter', class: 'org.example.greeter' });
59
+ ```
66
60
 
67
- ## Entry points
61
+ A second harbor in the same world takes an invitation on the greeter and
62
+ asks it through the world, sealed as Quo's bytes. `world.restart('home')`
63
+ opens the harbor again from what its memory kept.
64
+
65
+ ## Kinds
66
+
67
+ A row keeps the kind of the class a being is born of, and a being lends
68
+ by a contract's kind. A kind is declared on the class, because a bundler
69
+ renames classes. It is a domain you own, reversed, then a name, as Apple
70
+ and Matrix name things: `com.acme.shop`. Every segment is lowercase
71
+ letters, digits and `-`, and there are three at least. Each class
72
+ declares its own: a subclass that stands, and every contract between a
73
+ faculty and `Faculty`. `org.nervur.` is the kit's, and `org.example.` is
74
+ for examples like the one above. A module's classes stand under its own
75
+ domain: `com.acme.shop` stands `com.acme.*` kinds. A harbor refuses a
76
+ module in which one kind names two classes or two contracts, or names a
77
+ class and a contract.
78
+
79
+ ## Modules
80
+
81
+ Your classes come in modules: `{ module, version, classes }`, a module
82
+ named as a kind is. A harbor's catalogue is a faculty of its own, and her
83
+ cells name the modules the harbor runs. `add { from }` runs the module
84
+ the terrain's loader finds there, and `remove { module }` stops one no
85
+ being stands on. A restart loads every module she names and refuses to
86
+ stand when one is missing. A module found at a new version wakes, and
87
+ `modules` shows the version kept and the version running.
88
+
89
+ ## The onion
90
+
91
+ A harbor is one package. Only the seed and the terrain's bodies, its
92
+ loader among them, stand outside it.
93
+
94
+ 1. The terrain gives the harbor's seed, which opens its memory. The
95
+ terrain keeps that memory as sealed bytes and reads none of it.
96
+ 2. The box ward unpacks under the seed, its ward-being the dock.
97
+ 3. The catalogue wakes, and loads every module she names.
98
+ 4. The other faculties are born from their rows.
99
+ 5. The routes the dock keeps are handed to the carrier.
100
+ 6. Every ward the dock hosts unpacks, under the seed the dock keeps for it.
101
+ 7. In every ward, the ward-being first, then each being from her row.
102
+
103
+ Cells decide what stands. The modules only resolve a kind a row keeps.
104
+ Opening a harbor on empty memory is its genesis: the dock stands the
105
+ catalogue, and the root's asks write the rest: `add` on the catalogue,
106
+ `open`, `host`, `route`, and `boot` on a ward. `host { ward, seed? }`
107
+ stands a ward under a seed you name, or a drawn one.
108
+
109
+ ## What it holds
110
+
111
+ - **Being, Faculty.** What you extend. A faculty's contract is its class
112
+ and every class between it and `Faculty`.
113
+ - **Entropy, Clock, Custody, Memory, Carrier, Loader.** The contracts a
114
+ terrain fulfils. Each has one suite, and every body that fulfils it
115
+ passes it.
116
+ - **Harbor, Terrain, Dock, Catalogue, Registry, WardBeing.** The onion.
117
+ - **World, PointerTerrain** and the pointer bodies, `HeldLoader` among
118
+ them. A whole world with this package alone.
119
+ - **FolderMemory, FolderCustody**, from `nervur/folder`.
120
+ - **TcpCarrier, TcpListener**, from `nervur/tcp`: Quo over TCP. Route a
121
+ ward pk to `host:port` on the carrier, hand the listener your harbor,
122
+ and harbors in two processes, or a harbor and another kit, speak.
123
+
124
+ Other carriers, storage beyond a folder, key custody, and every screen
125
+ are `@nervur-org/dock`'s, as classes fulfilling these contracts.
126
+
127
+ ## The command
128
+
129
+ `nervur` stands a harbor on this machine: its seed and sealed memory in a
130
+ folder, Quo over TCP as its carrier. A module is a file that exports
131
+ `module`, `version` and `classes`.
132
+
133
+ ```sh
134
+ nervur init --dir ./harbor
135
+ nervur module add ./greetings.js --dir ./harbor
136
+ nervur serve --dir ./harbor --port 7000
137
+ nervur host alice --dir ./harbor
138
+ nervur boot --ward alice greeter org.example.greeter --dir ./harbor
139
+ nervur route <ward pk> host:port --dir ./harbor
140
+ nervur ask --ward alice greeter hello --dir ./harbor
141
+ ```
68
142
 
69
- ```js
70
- import { Being, silence, digest } from 'nervur';
71
- import { Ward } from 'nervur/ward';
72
- import { MemoryHarbor, Harbor, request, dial } from 'nervur/harbor';
73
- import { conform } from 'nervur/conformance';
74
- import { Stand } from 'nervur/vector';
143
+ `init` makes the harbor and prints its pk and an owner invitation. `serve`
144
+ listens for Quo on TCP and for the root's asks on a local socket, open to
145
+ you alone. Every other command is one root ask, sent to the served
146
+ harbor, or answered from the folder when none is served. Each prints one
147
+ JSON line. `--dir` defaults to `$NERVUR_DIR`, then `~/.nervur`. The
148
+ catalogue keeps a module by its file's absolute path, and routes live in
149
+ the harbor like everything else, so a restart finds both. `nervur
150
+ modules` shows what runs, and `nervur module remove <module>` stops one.
151
+
152
+ A harbor elsewhere is piloted with the owner invitation its `init`
153
+ printed, from a harbor of your own:
154
+
155
+ ```sh
156
+ nervur pilot far '<owner invitation>' host:port --dir ./mine
157
+ nervur host shop --via far --dir ./mine
75
158
  ```
76
159
 
77
- ## Another language
78
-
79
- The hand to a kit in another language is Quo's own and not this package's.
80
- The spec is the protocol and assumes nothing: a kit is written against it
81
- and against nothing else. The vectors beside it are the byte-level hand,
82
- everything a stranger can observe: `arithmetic.json`, the primitives the
83
- seal rests on, SHA-256, Ed25519, X25519, HKDF and AES-256-GCM;
84
- `framing.json`, Quo's own, the ward pk, the digest, the signed ask body,
85
- the sealed shapes, the invitation and the knock; `wire.json`, the frames on
86
- a socket and the one request a door takes; and `door.json`, the door's
87
- thirteen cases, each one an arrival a ward will not answer, with the bytes
88
- that arrive, the bytes that leave and the partition's digest on both sides
89
- of the judgement. All of it is at <https://quo.systems>, downloadable and
90
- versionless, and a kit reproduces the bytes or it is not this protocol.
91
-
92
- `door.json` is also replayed from outside, by a verifier holding no key,
93
- against a kit standing in vector mode, which the spec describes under that
94
- name. This kit stands in it with `Stand` from `nervur/vector`, which is
95
- vector mode and names no runtime: put it behind any listener that hands it
96
- a request body and returns what it answers.
97
-
98
- Nothing in this package is the protocol. `src/` is this kit's
99
- interpretation, and `src/conformance/` is a checklist a kit ports rather
100
- than a harness it runs: it imports the base class, the silence spelling and
101
- this kit's store and reach, so the beings it is shown with run only in a
102
- TypeScript ward.
103
-
104
- ## Versions
105
-
106
- No compatibility promise before 1.0.0: the words may still move, and there
107
- is no migration to write because there is nothing yet to migrate from. This
108
- package carries the version of its own work and is bound to no other's, so a
109
- number equal to `@nervur-org/dock`'s or `@nervur-org/ui`'s is a coincidence.
110
-
111
- What ships is the emitted `dist/`, the source it came from, this file, the
112
- licence and the notice. No tests and no configs. The spec and the vectors
113
- are not in the tarball: they are Quo's and not this package's to carry, and
114
- they stand at <https://quo.systems>, which is where this file points.
160
+ `pilot` boots an `org.nervur.pilot` being in your harbor that holds the
161
+ invitation as an ordinary Quo relation, kept in your harbor's memory. With
162
+ `--via far`, any command is asked of the far harbor as its owner, sealed
163
+ over TCP, except `own` and `disown`, which stay the far root's.
164
+ `nervur unboot far` lets it go.
165
+
166
+ `rootLine(harbor, request)` is the same request in code, for any other
167
+ front: `{ ward?, method?, args? }` in, one JSON answer out.
115
168
 
116
169
  ## License
117
170
 
118
- Apache-2.0. Copyright 2026 Razvan Gherghina. See [LICENSE](LICENSE) and
119
- [NOTICE](NOTICE); every source file carries an SPDX line.
171
+ Apache-2.0.
@@ -1,24 +1,17 @@
1
- import type { Asker, Blueprint, Cells, Invitation, JsonObject, Wanted, OccupantRecord, Occupants, Reply, Schema, Stance, Standings, Answer } from './types.ts';
1
+ import type { Asker, Blueprint, JsonObject, Reply, Schema, Stance } from './types.ts';
2
2
  export type AskSpec = {
3
- description?: string;
4
- input?: Schema;
5
- output?: Schema;
6
- for?: (occupant: OccupantRecord | undefined, asker: Asker) => boolean;
3
+ readonly description?: string;
4
+ readonly input?: Schema;
5
+ readonly for?: (asker: Asker, notes: JsonObject | undefined) => boolean;
7
6
  };
8
7
  export declare class Being {
8
+ static readonly kind: string;
9
9
  static cells: JsonObject;
10
10
  static asks: Record<string, AskSpec>;
11
11
  readonly stance: Stance;
12
12
  constructor(stance: Stance);
13
- get cells(): Cells;
14
- get standings(): Standings;
15
- get occupants(): Occupants;
16
- lend(name: string, id: string): Promise<string | null>;
17
- invite(id: string, notes?: JsonObject): Promise<Invitation | null>;
18
- knock(invitation: Invitation, method?: string, args?: JsonObject, wanted?: Wanted): Promise<Answer>;
19
- take(id: string, invitation: Invitation): Promise<string | null>;
20
- boot(className: string, key: string, id?: string): Promise<string | null>;
21
- occupant(asker: Asker): OccupantRecord | undefined;
13
+ get cells(): JsonObject;
14
+ notes(asker: Asker): JsonObject | undefined;
22
15
  describe(asker: Asker): Blueprint;
23
- answer(asker: Asker, method?: string, args?: JsonObject): Promise<Reply>;
16
+ answer(asker: Asker, method: string | undefined, args: JsonObject): Promise<Reply>;
24
17
  }
@@ -1,10 +1,6 @@
1
- // Names a subclass may not use for an ask, because they are the base's own.
2
- const RESERVED = new Set(['answer', 'describe', 'stance', 'cells', 'standings', 'occupants', 'occupant', 'invite', 'knock', 'take', 'boot', 'lend', 'constructor']);
3
- // Whether she has a method of that name, written on her own prototype chain
4
- // below Object's. A name Object lends every object, `hasOwnProperty` or
5
- // `toString`, is not a method she wrote; and a field she assigns in her own
6
- // constructor is not there yet when the base checks, so an ask is a method
7
- // on the prototype and nothing else.
1
+ // Names a subclass may not declare as an ask, because they are the base's.
2
+ const RESERVED = new Set(['answer', 'describe', 'stance', 'cells', 'notes', 'constructor']);
3
+ // Whether she wrote a method of that name, below Object's prototype.
8
4
  const wrote = (self, name) => {
9
5
  for (let p = Object.getPrototypeOf(self); p !== null && p !== Object.prototype; p = Object.getPrototypeOf(p)) {
10
6
  if (Object.hasOwn(p, name))
@@ -12,18 +8,15 @@ const wrote = (self, name) => {
12
8
  }
13
9
  return false;
14
10
  };
15
- // Both statics below are read off the class the object was made from, so a
16
- // subclass declaring either replaces its parent's rather than adding to it.
17
- // That is the rule: her blueprint is exactly what the class in front of you
18
- // declares, in the order she chose, and merging down a chain would hand her
19
- // asks she may mean to drop and an order she did not write. A subclass that
20
- // means to extend says so, `static override asks = { ...Parent.asks, mine: {} }`.
11
+ // A subclass's statics replace its parent's and never merge: her blueprint
12
+ // is what the class in front of you declares, in its order. Her kind is
13
+ // never inherited: see `kind.ts`.
21
14
  export class Being {
22
- // Her cells' defaults. Merged in at birth, only where a key is missing, so
23
- // a restart keeps what she wrote.
15
+ // The name her row keeps for this class. Every class that stands declares
16
+ // its own.
17
+ static kind = 'org.nervur.being';
18
+ // Her cells' defaults, written at birth only where a key is missing.
24
19
  static cells = {};
25
- // What she can be asked. Declaration order is blueprint order, except a
26
- // name that reads as an array index, which the language lists first.
27
20
  static asks = {};
28
21
  stance;
29
22
  constructor(stance) {
@@ -33,10 +26,8 @@ export class Being {
33
26
  if (RESERVED.has(name))
34
27
  throw new Error(`ask '${name}' is a reserved name`);
35
28
  if (!wrote(this, name))
36
- throw new Error(`ask '${name}' has no method on the prototype`);
29
+ throw new Error(`ask '${name}' has no method`);
37
30
  }
38
- // Own keys only: a default named after a member of Object's prototype is
39
- // still hers, and still missing until she writes it.
40
31
  for (const [k, v] of Object.entries(C.cells))
41
32
  if (!Object.hasOwn(stance.cells, k))
42
33
  stance.cells[k] = structuredClone(v);
@@ -44,66 +35,28 @@ export class Being {
44
35
  get cells() {
45
36
  return this.stance.cells;
46
37
  }
47
- get standings() {
48
- return this.stance.standings;
38
+ // The notes she invited this asker under.
39
+ notes(asker) {
40
+ return asker.id === undefined ? undefined : this.stance.occupants.notes(asker.id);
49
41
  }
50
- get occupants() {
51
- return this.stance.occupants;
52
- }
53
- // A standing at one of the things this device can do, under an id of hers.
54
- // The ward knocks and takes it for her; the invitation never reaches her.
55
- lend(name, id) {
56
- return this.stance.lend(name, id);
57
- }
58
- invite(id, notes) {
59
- return this.stance.occupants.invite(id, notes);
60
- }
61
- knock(invitation, method, args, wanted) {
62
- return this.stance.standings.knock(invitation, method, args, wanted);
63
- }
64
- take(id, invitation) {
65
- return this.stance.standings.take(id, invitation);
66
- }
67
- // A new being of her ward, by class name, under a key she chooses. With an
68
- // id, she holds a standing to the being she made, who knows her by her key.
69
- boot(className, key, id) {
70
- return this.stance.boot(className, key, id);
71
- }
72
- // The occupant record for whoever is at the door. Undefined at a public being.
73
- occupant(asker) {
74
- return asker.id !== undefined && Object.hasOwn(this.cells.occupants, asker.id) ? this.cells.occupants[asker.id] : undefined;
75
- }
76
- // Her blueprint for this asker. Override to shape it by hand.
77
42
  describe(asker) {
78
43
  const C = this.constructor;
79
- const rec = this.occupant(asker);
44
+ const notes = this.notes(asker);
80
45
  const asks = [];
81
46
  for (const [name, spec] of Object.entries(C.asks)) {
82
- if (spec.for && !spec.for(rec, asker))
47
+ if (spec.for && !spec.for(asker, notes))
83
48
  continue;
84
- const ask = { name, input: spec.input ?? { type: 'object' } };
85
- if (spec.description !== undefined)
86
- ask.description = spec.description;
87
- if (spec.output !== undefined)
88
- ask.output = spec.output;
89
- asks.push(ask);
49
+ asks.push({ name, ...(spec.description === undefined ? {} : { description: spec.description }), input: spec.input ?? { type: 'object' } });
90
50
  }
91
51
  return { asks, notes: {} };
92
52
  }
93
- // The one function. Override to wrap it; call super to keep the dispatch.
94
- async answer(asker, method, args = {}) {
53
+ async answer(asker, method, args) {
95
54
  if (method === undefined)
96
55
  return this.describe(asker);
97
56
  const C = this.constructor;
98
- // Declared, by her, on purpose. `asks` is an ordinary object, so a bare
99
- // lookup would also find every name on Object's prototype: `valueOf`
100
- // would answer with her stance, `toString` with a string, and neither is
101
- // an ask she wrote. Only her own keys are asks, which is what describe
102
- // shows. What she shows is what she can be asked.
103
57
  const spec = Object.hasOwn(C.asks, method) ? C.asks[method] : undefined;
104
- if (!spec || (spec.for && !spec.for(this.occupant(asker), asker)))
58
+ if (!spec || (spec.for && !spec.for(asker, this.notes(asker))))
105
59
  return { error: 'unknown ask' };
106
- const fn = this[method];
107
- return fn.call(this, args ?? {}, asker);
60
+ return this[method].call(this, args, asker);
108
61
  }
109
62
  }
@@ -1,3 +1,3 @@
1
- import type { Json } from './types.ts';
1
+ import { type Json } from '../crypto/index.ts';
2
2
  export declare const canonical: (v: Json) => string;
3
3
  export declare const digest: (blueprint: Json) => Promise<string>;
@@ -1,37 +1,17 @@
1
- // A value I-JSON has no room for: a key left empty, a function, a symbol.
2
- // None of them cross an edge, so none of them may reach a digest. A key
3
- // carrying one is dropped and a slot carrying one is null, which is what
4
- // crossing does to them, so the digest names what arrived and not what she
5
- // happened to be holding.
6
- const absent = (v) => v === undefined || typeof v === 'function' || typeof v === 'symbol';
7
- // JCS for I-JSON values: sorted keys, no whitespace, JSON escaping. Numbers
8
- // are serialized as ES does, which is what RFC 8785 specifies. A hole in a
9
- // list is null, as JSON writes it.
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // A being's `seen`: SHA-256, lowercase hex, over the canonical form of her
3
+ // blueprint for one asker. Keys sorted by UTF-16 code units, numbers as
4
+ // ECMAScript writes them, no whitespace.
5
+ import { hex, sha256, utf8 } from '../crypto/index.js';
10
6
  export const canonical = (v) => {
11
7
  if (Array.isArray(v))
12
- return `[${Array.from(v, (slot) => (absent(slot) ? 'null' : canonical(slot))).join(',')}]`;
8
+ return `[${v.map(canonical).join(',')}]`;
13
9
  if (v !== null && typeof v === 'object') {
14
10
  return `{${Object.keys(v)
15
- .filter((k) => !absent(v[k]))
16
11
  .sort()
17
12
  .map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`)
18
13
  .join(',')}}`;
19
14
  }
20
15
  return JSON.stringify(v);
21
16
  };
22
- // Hex and the guard are spelled here and again in `src/ward/arithmetic.ts`,
23
- // which is the price of the boundary: nothing under `src/being` imports
24
- // anything above it, because this is the whole world a being's own code sees
25
- // and a being reaching the ward is the thing the shape is against.
26
- const hex = (bytes) => Array.from(new Uint8Array(bytes), (b) => b.toString(16).padStart(2, '0')).join('');
27
- // `crypto.subtle` is read at the call and never captured at load. A browser
28
- // on a plain http:// origin has `crypto` without `subtle`, and a terrain may
29
- // install one after this module is first imported; either way the failure is
30
- // one sentence and not a TypeError from inside a digest nobody can read.
31
- const subtle = () => {
32
- const s = globalThis.crypto?.subtle;
33
- if (!s)
34
- throw new Error('this terrain has no crypto.subtle: a being needs a secure context to be described');
35
- return s;
36
- };
37
- export const digest = async (blueprint) => hex(await subtle().digest('SHA-256', new TextEncoder().encode(canonical(blueprint))));
17
+ export const digest = async (blueprint) => hex(await sha256(utf8(canonical(blueprint))));
@@ -0,0 +1,10 @@
1
+ import { Being } from './being.ts';
2
+ import { type Asker, type Blueprint, type JsonObject, type Reply } from './types.ts';
3
+ export declare class Faculty extends Being {
4
+ #private;
5
+ static readonly kind: string;
6
+ static contracts(C: abstract new (...args: never[]) => unknown): string[];
7
+ static fulfils(C: unknown): C is typeof Faculty;
8
+ describe(asker: Asker): Blueprint;
9
+ answer(asker: Asker, method: string | undefined, args: JsonObject): Promise<Reply>;
10
+ }
@@ -0,0 +1,74 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // A faculty: a being of the box ward that fulfils a contract. A contract is
3
+ // a class between `Faculty` and the class that stands, usually abstract:
4
+ //
5
+ // abstract class Timer extends Faculty { the contract
6
+ // static override kind = 'com.acme.timer' }
7
+ // class IntervalTimer extends Timer { one class that fulfils it
8
+ // static override kind = 'com.acme.interval-timer' }
9
+ //
10
+ // A being lends by the contract's kind and never learns the class. Every
11
+ // class between the faculty and `Faculty` is a contract and declares its
12
+ // own kind, and a faculty fulfils each of them and her own. Two asks are the
13
+ // faculty's own and answered to the ward-being that opened her alone:
14
+ // `offer`, an invitation on herself for a ward the dock vouches for, and
15
+ // `retract`, an offer that was not taken.
16
+ import { Being } from './being.js';
17
+ import { kindOf } from './kind.js';
18
+ import { WARD } from './types.js';
19
+ export class Faculty extends Being {
20
+ static kind = 'org.nervur.faculty';
21
+ // The contracts a class fulfils: itself and every class between it and
22
+ // `Faculty`, by kind. A class among them with no kind of its own throws.
23
+ static contracts(C) {
24
+ const kinds = [];
25
+ for (let c = C; typeof c === 'function' && c !== Faculty; c = Object.getPrototypeOf(c))
26
+ kinds.push(kindOf(c));
27
+ return kinds;
28
+ }
29
+ static fulfils(C) {
30
+ return typeof C === 'function' && C.prototype instanceof Faculty;
31
+ }
32
+ #open() {
33
+ if (this.cells.offers === undefined)
34
+ this.cells.offers = {};
35
+ return this.cells.offers;
36
+ }
37
+ async #offer() {
38
+ const n = (this.cells.offered ?? 0) + 1;
39
+ this.cells.offered = n;
40
+ const id = `lent:${n}`;
41
+ const invitation = await this.stance.occupants.invite(id);
42
+ if (invitation === null)
43
+ return { error: 'not invited' };
44
+ this.#open()[invitation.heir] = id;
45
+ return { invitation };
46
+ }
47
+ #retract(args) {
48
+ const open = this.#open();
49
+ const id = typeof args.heir === 'string' && Object.hasOwn(open, args.heir) ? open[args.heir] : undefined;
50
+ if (id === undefined)
51
+ return { retracted: null };
52
+ this.stance.occupants.remove(id);
53
+ delete open[args.heir];
54
+ return { retracted: id };
55
+ }
56
+ describe(asker) {
57
+ const blueprint = super.describe(asker);
58
+ if (asker.id !== WARD)
59
+ return blueprint;
60
+ return { ...blueprint, asks: [...blueprint.asks, { name: 'offer', input: { type: 'object' } }, { name: 'retract', input: { type: 'object', required: ['heir'] } }] };
61
+ }
62
+ // An offer is closed by the first word from the occupant it was made for.
63
+ answer(asker, method, args) {
64
+ if (asker.id === WARD && method === 'offer')
65
+ return this.#offer();
66
+ if (asker.id === WARD && method === 'retract')
67
+ return Promise.resolve(this.#retract(args));
68
+ const open = this.#open();
69
+ for (const [heir, id] of Object.entries(open))
70
+ if (id === asker.id)
71
+ delete open[heir];
72
+ return super.answer(asker, method, args);
73
+ }
74
+ }
@@ -1,6 +1,6 @@
1
1
  export { Being, type AskSpec } from './being.ts';
2
- export { Lent, BOX, LENT_ASKS } from './lent.ts';
3
- export { silence, isSilence, answered, unreached, isUnreached, word, isWord, wordOf, told, DOOR_WORDS, isDoorWord } from './silence.ts';
4
- export { digest, canonical } from './digest.ts';
5
- export { OWNER, PUBLIC, RESERVED_IDS, isBlueprint, invitationArgs, isInvitation } from './types.ts';
6
- export type * from './types.ts';
2
+ export { canonical, digest } from './digest.ts';
3
+ export { Faculty } from './faculty.ts';
4
+ export { isKind, KIT, kindOf, ownKind } from './kind.ts';
5
+ export { OWNER, WARD, type Answer, type Ask, type Asker, type BeingClass, type BeingLike, type Blueprint, type Invitation, type Json, type JsonObject, type Occupants, type Reply, type Schema, type Stance, type Standing, type Standings, type Wanted } from './types.ts';
6
+ export { isSilence, isWord, silence, told, word, type Silence, type Word, type WordName } from './words.ts';
@@ -1,7 +1,8 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // nervur — the Being side. What a being author imports, if anything.
2
+ // What a being author imports.
3
3
  export { Being } from './being.js';
4
- export { Lent, BOX, LENT_ASKS } from './lent.js';
5
- export { silence, isSilence, answered, unreached, isUnreached, word, isWord, wordOf, told, DOOR_WORDS, isDoorWord } from './silence.js';
6
- export { digest, canonical } from './digest.js';
7
- export { OWNER, PUBLIC, RESERVED_IDS, isBlueprint, invitationArgs, isInvitation } from './types.js';
4
+ export { canonical, digest } from './digest.js';
5
+ export { Faculty } from './faculty.js';
6
+ export { isKind, KIT, kindOf, ownKind } from './kind.js';
7
+ export { OWNER, WARD } from './types.js';
8
+ export { isSilence, isWord, silence, told, word } from './words.js';
@@ -0,0 +1,4 @@
1
+ export declare const KIT = "org.nervur.";
2
+ export declare const isKind: (text: unknown) => text is string;
3
+ export declare const ownKind: (C: unknown) => string | undefined;
4
+ export declare const kindOf: (C: unknown) => string;
@@ -0,0 +1,28 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // A kind: the name a row keeps for the class a being is born of, and the
3
+ // name a contract is lent by. It is declared on the class, never taken from
4
+ // the name the language gives it, because a bundler renames classes.
5
+ //
6
+ // static kind = 'com.acme.shop'
7
+ //
8
+ // A kind is a reversed domain its author owns, then a name: at least three
9
+ // segments joined by `.`, each lowercase letters, digits and `-`, starting
10
+ // with a letter or digit. `org.nervur.` is the kit's own. A class declares
11
+ // its own kind: one it inherits names its parent, so it is none.
12
+ export const KIT = 'org.nervur.';
13
+ const KIND = /^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*){2,}$/;
14
+ export const isKind = (text) => typeof text === 'string' && KIND.test(text);
15
+ // The kind a class declares on itself, or undefined.
16
+ export const ownKind = (C) => {
17
+ if (typeof C !== 'function' || !Object.hasOwn(C, 'kind'))
18
+ return undefined;
19
+ const kind = C.kind;
20
+ return isKind(kind) ? kind : undefined;
21
+ };
22
+ // The kind a class declares on itself, or a throw naming the class.
23
+ export const kindOf = (C) => {
24
+ const kind = ownKind(C);
25
+ if (kind === undefined)
26
+ throw new Error(`class ${typeof C === 'function' ? C.name : String(C)} declares no kind of its own`);
27
+ return kind;
28
+ };