nervur 0.22.2-8 → 0.23.1-2

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 (87) hide show
  1. package/AUTHORING.md +614 -612
  2. package/COMMAND.md +72 -23
  3. package/FACES.md +29 -30
  4. package/FACULTIES.md +241 -51
  5. package/GROUNDS.md +195 -87
  6. package/KIT-SPEC.md +26 -14
  7. package/README.md +20 -15
  8. package/dist/app/app-ground.d.ts +4 -2
  9. package/dist/app/app-ground.js +2 -1
  10. package/dist/being/being.d.ts +47 -36
  11. package/dist/being/being.js +1 -1
  12. package/dist/being/covers.js +1 -1
  13. package/dist/being/dock-pilot.d.ts +305 -0
  14. package/dist/being/dock-pilot.js +69 -0
  15. package/dist/being/index.d.ts +2 -1
  16. package/dist/being/index.js +1 -0
  17. package/dist/being/need.d.ts +11 -6
  18. package/dist/being/need.js +4 -8
  19. package/dist/being/table.d.ts +9 -3
  20. package/dist/being/table.js +29 -13
  21. package/dist/bench/bench-ground.d.ts +22 -16
  22. package/dist/bench/bench-ground.js +93 -26
  23. package/dist/bench/bench.d.ts +86 -13
  24. package/dist/bench/bench.js +694 -98
  25. package/dist/bench/settle.d.ts +9 -1
  26. package/dist/bench/settle.js +81 -2
  27. package/dist/bench/stand-in.d.ts +17 -0
  28. package/dist/bench/stand-in.js +56 -0
  29. package/dist/bench/stewards.d.ts +26 -28
  30. package/dist/bench/stewards.js +58 -29
  31. package/dist/bodies/class-list.d.ts +13 -1
  32. package/dist/bodies/class-list.js +59 -5
  33. package/dist/bodies/noble-crypto.d.ts +7 -2
  34. package/dist/bodies/noble-crypto.js +6 -1
  35. package/dist/bodies/strict-tools.d.ts +7 -2
  36. package/dist/bodies/strict-tools.js +9 -1
  37. package/dist/bodies/web-clock.d.ts +7 -2
  38. package/dist/bodies/web-clock.js +8 -1
  39. package/dist/browser/browser-ground.d.ts +17 -9
  40. package/dist/browser/browser-ground.js +72 -40
  41. package/dist/browser/origin-classes.d.ts +6 -4
  42. package/dist/browser/origin-classes.js +11 -11
  43. package/dist/edge/edge-ground.d.ts +10 -8
  44. package/dist/edge/edge-ground.js +75 -76
  45. package/dist/edge/secret-unlock.d.ts +1 -1
  46. package/dist/edge/secret-unlock.js +1 -1
  47. package/dist/faculty.d.ts +146 -0
  48. package/dist/faculty.js +71 -0
  49. package/dist/foundation.d.ts +32 -0
  50. package/dist/foundation.js +13 -1
  51. package/dist/ground/dock.d.ts +1418 -0
  52. package/dist/ground/dock.js +1533 -0
  53. package/dist/ground/entry.d.ts +1 -0
  54. package/dist/ground/entry.js +31 -0
  55. package/dist/ground/forwarding.d.ts +28 -0
  56. package/dist/ground/forwarding.js +60 -0
  57. package/dist/ground/ground.d.ts +110 -310
  58. package/dist/ground/ground.js +940 -836
  59. package/dist/ground/inside.d.ts +25 -0
  60. package/dist/ground/inside.js +265 -0
  61. package/dist/ground/runner.d.ts +32 -0
  62. package/dist/ground/runner.js +418 -0
  63. package/dist/ground/views.d.ts +17 -0
  64. package/dist/ground/views.js +20 -0
  65. package/dist/house/crossing.d.ts +3 -2
  66. package/dist/house/crossing.js +5 -4
  67. package/dist/house/house.d.ts +21 -2
  68. package/dist/house/house.js +465 -132
  69. package/dist/house/rows-shape.d.ts +15 -0
  70. package/dist/index.d.ts +6 -3
  71. package/dist/index.js +5 -1
  72. package/dist/node/bridge.d.ts +2 -2
  73. package/dist/node/cli.js +63 -31
  74. package/dist/node/folder-classes.d.ts +4 -2
  75. package/dist/node/folder-classes.js +9 -23
  76. package/dist/node/hand.d.ts +4 -3
  77. package/dist/node/hand.js +3 -3
  78. package/dist/node/node-ground.d.ts +7 -9
  79. package/dist/node/node-ground.js +139 -198
  80. package/dist/node/shell.d.ts +11 -0
  81. package/dist/node/shell.js +35 -0
  82. package/dist/node/tcp-carry.d.ts +2 -0
  83. package/dist/node/tcp-carry.js +8 -1
  84. package/package.json +28 -18
  85. package/source.mjs +56 -0
  86. package/test.mjs +151 -0
  87. package/tsconfig.base.json +29 -0
package/AUTHORING.md CHANGED
@@ -1,45 +1,46 @@
1
- # Writing for nervur
1
+ # Writing a species
2
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, and
5
- [Writing a faculty](FACULTIES.md) teaches it whole. A **ground** runs them on
6
- a machine, and the library ships it. The examples build one small shop,
7
- and every file here is a file the package's own tests run.
3
+ A species is a class of beings you write. This guide teaches it whole:
4
+ how to declare one, how she calls others, how beings compose, and how you
5
+ prove her. An author who finishes it writes and proves a species alone.
8
6
 
9
- The shop has three classes and one faculty.
7
+ You meet the library at three moments. Writing a species is this guide.
8
+ Writing a faculty, which reaches the world outside, is
9
+ [Writing a faculty](FACULTIES.md). Running a ground, the process houses
10
+ run in, is [Grounds](GROUNDS.md) and [the command](COMMAND.md).
11
+ [Reading a world](WORLD.md) says which pieces a situation needs.
12
+
13
+ The examples build one small shop, and every file here is one the
14
+ package's own tests run.
10
15
 
11
16
  - `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 `recipe.ts` holds
15
- by name.
17
+ - `Shop` is the steward, the being who runs the house.
18
+ - `Lobby` is the public being, whom strangers may ask.
19
+
20
+ ## A being is five things
16
21
 
17
- ## Words
22
+ A being is an instance of a species. She is five things, and nothing
23
+ else reaches her.
18
24
 
19
- | Word | What it is |
25
+ | Part | What it is |
20
26
  | --- | --- |
21
- | being | an instance of a class, born for one ask and dropped after it |
22
27
  | 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 one key and sealing their seeds, faculties and secrets under it |
35
- | registry | code that holds faculties by name, for the ground to stand |
36
- | body | a faculty stood: the living instance houses and bodies use |
37
-
38
- ## A being
39
-
40
- A being is pure logic over her cells. She never knows where she runs,
41
- who carries her asks, or how her cells are kept. The house does all of
42
- it, and she trusts it.
28
+ | asks | the methods others may call on her, each with its entry |
29
+ | occupants | edges in: who may ask her, each by id |
30
+ | standings | edges out: other beings she may ask, each by id |
31
+ | bodies | edges out: faculties she calls through her needs |
32
+
33
+ Two edges every being holds. `house` is a default body: the time, random
34
+ bytes and alarms. `root` is a default occupant: the owner, through the
35
+ house's hand. A normal being also holds her steward, as a standing and as
36
+ an occupant.
37
+
38
+ A position adds one edge. A steward is a being plus the body `powers`. A
39
+ public being is a being plus the occupant `stranger`, who stands for
40
+ every asker with no relation. Every other rule is the same, so a species
41
+ never asks where she was placed.
42
+
43
+ ## Declaring a species
43
44
 
44
45
  ```ts
45
46
  // classes/order.ts
@@ -72,7 +73,7 @@ export class Order extends Being.of({
72
73
  asks: {
73
74
  hire: {
74
75
  for: 'steward',
75
- hints: { idempotent: true },
76
+ idempotent: true,
76
77
  args: s.object({ courier: s.handle() }),
77
78
  result: s.string(),
78
79
  },
@@ -84,7 +85,7 @@ export class Order extends Being.of({
84
85
  result: s.object({ total: s.number() }),
85
86
  },
86
87
  checkout: { in: 'open', for: 'owner', to: 'paying' },
87
- charged: { in: 'paying', args: s.reply(Payments.charge), to: ['paying', 'open'] },
88
+ charged: { in: 'paying', for: 'pay', args: s.reply(Payments.charge), to: ['paying', 'open'] },
88
89
  settled: { in: 'paying', for: 'handle', to: 'paid' },
89
90
  ship: { in: 'paid', for: 'owner', to: 'shipped' },
90
91
  },
@@ -129,194 +130,199 @@ The order wrote no retry, no catch, no key and no address. `charge` is
129
130
  an effect, so it leaves only once `checkout` has landed. Its answer comes
130
131
  back to `charged`, and a refused charge opens the order again. The
131
132
  provider calls `settled` through the handle once the money arrives.
132
- `hire` takes a courier's invitation as her standing, and `ship` asks the
133
- courier through it. `shipped` is a terminal state, and nothing leaves it.
134
-
135
- ### The declaration
133
+ `ship` asks the courier through the standing `hire` took. `shipped` is a
134
+ terminal state, and nothing leaves it.
136
135
 
137
- A class extends `Being.of({ … })`. That one object declares the class,
138
- and TypeScript reads from it the types of her cells and her needs.
136
+ A species extends `Being.of({ … })`, and TypeScript reads her types from
137
+ that one object. Every field is optional but `kind` and `asks`.
139
138
 
140
139
  | Field | What it is |
141
140
  | --- | --- |
142
- | `kind` | the code's name: a reversed domain you own, then a name |
141
+ | `kind` | her name for the house: a reversed domain you own, then a name |
143
142
  | `description` | one line for readers and agents |
144
- | `cells` | the state and its defaults, written where a key is missing |
143
+ | `cells` | her state and its defaults |
145
144
  | `needs` | blueprints she calls, each under a member name she chooses |
146
145
  | `roles` | named tests over the asker and her cells |
147
- | `state` | a function of the being that names her current state |
146
+ | `state` | a function naming her current state; omitted, `ready` alone |
148
147
  | `asks` | one entry per method she answers |
149
- | `view` | markup as text over her asks, which a screen renders |
148
+ | `view` | markup as text over her asks, at most 64 KiB, which a screen renders |
150
149
 
151
- A view is data, at most 64 KiB, carried in her describe to every asker.
152
- It holds no script, and a renderer loads nothing it names, so a view
153
- neither acts nor tracks. The library defines no markup: a renderer
154
- speaks its own.
155
-
156
- Every field is optional but `kind` and `asks`. A class with no `state`
157
- has one state, named `ready`. The kind is how the house finds her code
158
- again, so a bundler that renames classes changes nothing.
159
-
160
- A method in TypeScript names its args with `Args<Class, 'method'>`,
161
- since a subclass's method takes no type from its base. A method in
162
- JavaScript writes nothing.
150
+ A method in TypeScript names its args with `Args<Class, 'method'>`. A
151
+ method in JavaScript writes nothing.
163
152
 
164
153
  ### An ask's entry
165
154
 
166
155
  | Field | What it says |
167
156
  | --- | --- |
157
+ | `for` | the roles that may call it |
168
158
  | `in` | the states where the ask exists; omitted, every state |
169
- | `for` | the roles that may call it; omitted, every occupant but handles |
170
159
  | `to` | the states it may land in; omitted, the state it began in |
171
- | `args` | the schema of what it takes; omitted, the empty object alone |
160
+ | `args` | the schema of what it takes; omitted, the empty object |
172
161
  | `result` | the schema of what it answers; omitted, nothing |
173
- | `hints` | `readOnly`, `idempotent`, `destructive` |
174
- | `examples` | a history of her asks, a role, args, fakes, and what it gives |
162
+ | `readOnly` | it writes nothing, and implies `idempotent`; the house holds it |
163
+ | `idempotent` | it is safe to repeat, so its caller awaits it |
164
+ | `hints` | `{ destructive }`, the one hint, which changes nothing in the house |
165
+ | `replayable` | your word that a stranger may repeat it, though it is not idempotent |
166
+ | `wait` | milliseconds she may run; omitted, thirty seconds, and never past five minutes |
167
+ | `examples` | histories the bench runs, each ending in one ask and what it gives |
175
168
  | `description` | one line for readers and agents |
176
- | `wait` | milliseconds she may run; omitted, thirty seconds |
177
169
 
178
- `in`, `for` and `to` each take one name or a list of names. A method
179
- with no entry is never reached. An entry with no method is refused when
180
- the house first loads the class.
170
+ `in`, `for` and `to` each take one name or a list. A method with no entry
171
+ is never reached, and an entry with no method refuses the species.
172
+
173
+ ### Roles
174
+
175
+ A role names who may ask. An omitted `for` means every occupant, and
176
+ never an edge out of her. Five roles are the house's, for occupants.
177
+
178
+ - `root` is the owner, through the hand.
179
+ - `steward` is her steward.
180
+ - `being` is a being her steward introduced to her.
181
+ - `stranger` is any asker of a public being with no relation.
182
+ - `handle` is whoever holds a handle she minted to this ask.
183
+
184
+ The rest are edges out of her, which answer her and ask nothing else.
185
+ Each need's member is a role, held by the body answering her effect on
186
+ it. `standing` is held by a standing answering hers. `powers` is held by
187
+ a being her steward's powers asked, and `house` by the house. So the
188
+ order's `charged` says `for: 'pay'`, and no occupant fakes the payment's
189
+ answer.
190
+
191
+ Every other role is yours, a function of `(asker, me)`. The order's
192
+ `owner` reads the steward's notes on the asker, so only the steward
193
+ decides who owns an order.
194
+
195
+ The house checks the table when it first loads the species. It refuses a
196
+ state no ask reaches, an ask no role reaches, and a role no ask names. A
197
+ method that lands in a state outside its `to` fails, and nothing lands.
198
+
199
+ ### Schemas and needs
200
+
201
+ One builder gives the schema and the TypeScript type: `s.object`,
202
+ `s.string`, `s.number`, `s.integer`, `s.boolean`, `s.array`, `s.enum`,
203
+ `s.const`, `s.bytes`, `s.optional`, `s.handle`, `s.invitation` and
204
+ `s.reply`. The house checks every ask against them.
205
+
206
+ - `s.bytes` is a `Uint8Array` in the process and hex across a door.
207
+ - `s.handle` carries a relation. A handle leaves as an invitation, and
208
+ an invitation arrives as a new standing id.
209
+ - `s.invitation` carries an invitation unopened, which she passes on.
210
+ - `s.reply(method)` is the args of an effect's reply.
211
+
212
+ A need is a blueprint at its minimum: what she calls, never what a
213
+ faculty offers. `need(name, methods)` writes one, and `needs` binds it to
214
+ a member of her own. A faculty covers it where the names match, every
215
+ method is offered, and each is `idempotent` in both or in neither. Every
216
+ need is covered, or she is absent: she answers silence and keeps her
217
+ cells until one is.
218
+
219
+ ## How she calls
181
220
 
182
- Five roles are the house's. `handle` is whoever holds a handle to this
183
- ask. `stranger` is the asker of a public being. `steward` is her
184
- steward. `being` is another being her steward introduced to her. `root`
185
- is the owner, through the house's hand. Every other role is yours, a
186
- function of `(asker, me)`.
221
+ The callee decides how it is called, by one flag its describe shows. A
222
+ method marked `idempotent` or `readOnly` is harmless to repeat, so she
223
+ awaits it during her ask. Everything else changes the world, so she
224
+ commits it as an effect.
187
225
 
188
- The house checks the table when it first loads the class. It refuses a
189
- state no ask reaches, an ask no role reaches, and a role no ask names.
190
- After a method runs, a state outside the entry's `to` fails the ask, and
191
- nothing lands.
226
+ ### Awaited calls answer as data
227
+
228
+ An awaited call never throws into her code. It answers a
229
+ `Reply<T>`, which is `{ result }` or `{ error: { message } }`. Silence
230
+ reads as an error saying the call answered nothing. She decides what an
231
+ error means.
232
+
233
+ - `this.must(reply)` gives the result, or fails her ask with the error.
234
+ - `this.fail(message)` refuses her ask with a message the asker can act
235
+ on. Nothing of her ask lands.
236
+ - `this.silence()` answers nothing, as an absent being does, and nothing
237
+ lands. A prober learns nothing from it.
238
+
239
+ A failed ask changed nothing she owns, so asking again is safe.
240
+
241
+ ### Effects commit after she lands
242
+
243
+ An effect returns nothing. The house writes it in the same write as her
244
+ cells, then sends it with one call id until it is answered. So it acts
245
+ at most once, and never leaves where her ask failed. Its answer asks the
246
+ method `reply` names, as `{ result }` or `{ error }`. It gives up at its
247
+ deadline, seven days for a standing, and its reply hears why. Effects to
248
+ one receiver leave one at a time, in order.
249
+
250
+ ### Who asks
251
+
252
+ Every ask arrives from one true id on her graph, never a disguise.
253
+
254
+ - An occupant asks as itself, and `this.asker` holds its id and notes.
255
+ - A reply comes from the edge that answered: a need's member, `standing`
256
+ or `powers`.
257
+ - An alarm comes from `house`, so its ask names `house` in its `for`.
258
+ - An effect to a stranger's door answers as `house`, since that door is
259
+ no edge of hers.
192
260
 
193
261
  ### What she reaches
194
262
 
195
263
  | Member | What it is |
196
264
  | --- | --- |
197
- | `this.id` | her own id |
198
- | `this.position` | `steward`, `public` or `normal` |
199
- | `this.asker` | `{ id, notes, steward }` of who asks now; `signer` for a stranger |
200
- | `this.cells` | her values |
201
- | `this.house.now()` | the time, in milliseconds since the epoch |
202
- | `this.house.random({ length })` | random bytes |
203
- | `this.house.alarm({ at, ask, args, key })` | one of her asks, at that time |
204
- | `this.house.cancelAlarm({ key })` | an alarm removed |
205
- | `this.<need>.<method>({…}, { reply? })` | an awaited call, or an effect |
206
- | `this.held(id, Need).<ask>({…}, { reply?, after? })` | the same, on a standing, and a watch where `after` is given |
207
- | `this.held(id).describe()`, `.ask(method, {…}, options)` | a standing with no need: what it shows her, then any ask it showed |
208
- | `this.stranger({ ward, at }, Need).<ask>({…})` | a far house's public being, asked as a stranger, every ask awaited |
209
- | `this.stranger({ ward, at }).describe()`, `.ask(method, {…})` | the same with no need: what she shows a stranger, then any ask it showed |
265
+ | `this.id`, `this.cells` | her id and her values |
266
+ | `this.asker` | `{ id, notes, steward }` of who asks now, and `signer` for a stranger |
267
+ | `this.house` | `now()`, `random({ length })`, `alarm({ at, ask, args, key })`, `cancelAlarm({ key })` |
268
+ | `this.<need>.<method>(args, { reply? })` | a call on a body |
269
+ | `this.held(id, Need).<ask>(args, { reply?, after? })` | a call on a standing matched to a need |
270
+ | `this.held(id).describe()`, `.ask(method, args)` | a standing she holds no need for, read first |
271
+ | `this.stranger({ ward, at }, Need).<ask>(args)` | a far house's public being, asked as a stranger |
210
272
  | `this.standings` | `list()`, `note(id, notes)` and `drop(id)` |
273
+ | `this.occupants` | `list()`, `note(id, notes)` and `dismiss(id)` |
211
274
  | `this.handle(ask, { bind?, notes?, once?, expires? })` | a handle to one of her asks |
212
275
  | `this.invite(id, { notes?, expires? })` | a new occupant, as a handle |
213
- | `this.occupants` | `list()`, `note(id, notes)` and `dismiss(id)` |
214
276
  | `this.steward` | her steward, where she is not one |
215
- | `this.powers` | the house's powers, where she is the steward |
216
- | `this.fail(message)` | an error the asker can act on |
277
+ | `this.powers` | the steward's six powers |
278
+ | `this.fail`, `this.silence`, `this.must` | how an ask refuses or reads a reply |
217
279
 
218
280
  An alarm lives in the house's rows and survives every restart. A second
219
- alarm under one key replaces the first. When its time comes, the house
220
- asks her named ask as her steward would.
221
-
222
- ### Awaited calls and effects
223
-
224
- The callee decides how it is called, with one flag. A method or ask
225
- marked `idempotent` is safe to repeat, so the caller awaits it during
226
- her ask. Everything else is an effect. `readOnly` is stricter: it writes
227
- nothing, it implies `idempotent`, and the house holds it.
228
-
229
- An awaited call answers during her ask. `await this.fx.rate({…})`
230
- returns the answer, and a failure throws an error she may catch. It
231
- waits thirty seconds unless its entry says otherwise, and never more
232
- than five minutes.
233
-
234
- An awaited call never comes back to a being in its own chain. While she
235
- awaits, she holds her queue. A call back to her, other than a
236
- `readOnly` one, waits behind the ask that waits for it, until the wait
237
- runs out and the call fails as `answered nothing`. Call back with an
238
- effect instead.
239
-
240
- An effect leaves after her ask lands. She calls it and it returns
241
- nothing. The house writes it in the same write as her cells, then sends
242
- it with one call id until it is answered. So an effect acts at most
243
- once. Its answer comes back to the ask `reply` names, as `{ result }` or
244
- `{ error: { message } }`.
245
-
246
- An effect gives up at its deadline, seven days for a standing and the
247
- offer's window for a faculty. The reply then hears an error. Effects to
248
- one receiver leave one at a time, in the order she called them.
249
-
250
- Asks to one being run one at a time, in the order they arrive. A
251
- `readOnly` ask runs beside that queue, on the cells last landed.
252
-
253
- ### Watching an answer
254
-
255
- A watch is a `readOnly` ask asked with `after`, the answer you already
256
- hold. The house answers at once where the answer differs. Where it is
257
- the same, the house holds the ask and runs it again each time an ask of
258
- that being lands. It answers the first answer that differs, or the same
259
- one when the wait runs out. So a chat, an order's status or a dashboard
260
- is a loop of watches, and nothing polls.
261
-
262
- Through the hand, pass `after` beside the method, as the answer you
263
- hold: `{ result: [...] }`. From a being, pass the result she holds:
264
- `this.held(room, Messages).messages({}, { after: seen })`, inside a
265
- `readOnly` ask of her own, so she stays free while she waits. Only a
266
- `readOnly` ask is watched, and a watch on anything else is refused where
267
- she makes it.
268
-
269
- A watch may land as a reply instead: `{ after: seen, reply: 'heard' }`.
270
- It leaves once her ask lands, as an effect does, and its answer asks her
271
- `heard`, where she writes what she heard and watches again. So a device
272
- that only dials, a phone or a Pi behind a router, hears its station the
273
- moment something changes there, and opens no port.
274
-
275
- A watch moves only on what its asker could read, since it runs as that
276
- asker. One asker holds one watch on one ask with the same args, and a
277
- second answers the first at once. A watch across a door holds its
278
- relation until it answers, since Quo moves a relation one ask at a time.
279
- So watch a far being through a handle to the watched ask alone, a
280
- relation of its own, and ask everything else on your other standing.
281
-
282
- ### Relations
283
-
284
- An **occupant** is someone who may ask her. She mints one with
285
- `this.invite(id, { notes })`, which answers a handle, and lets one go with
286
- `dismiss`.
287
-
288
- A **standing** is someone she may ask. She receives one where an ask's
289
- args carry an invitation under `s.handle`, or where her steward
290
- introduces one. She asks through it with `this.held(id, Need)`, which
291
- checks the standing's describe covers the need. A standing from an
292
- invitation is named `standing:` and sixteen hex digits, and the id is
293
- what her ask receives. One her steward introduced is named by the being
294
- it reaches. She asks a far standing from the ask after the one that took
295
- it.
281
+ alarm under one key replaces the first.
282
+
283
+ ### Watching
284
+
285
+ A watch is a `readOnly` ask or body method asked with `after`, the
286
+ answer she holds. The house answers at once where the answer differs.
287
+ Otherwise it answers the first answer that differs, or the same one when
288
+ the wait runs out. So a chat or an order's status is a loop of watches,
289
+ and nothing polls.
290
+
291
+ From a being, a watch sits inside a `readOnly` ask of her own:
292
+ `this.held(room, Messages).messages({}, { after: seen })`. Given `reply`
293
+ beside `after`, it leaves as an effect, and its answer asks her `reply`,
294
+ where she watches again. A watch across a door holds its relation until
295
+ it answers. So watch a far ask through a handle to it alone.
296
+
297
+ ## The graph
298
+
299
+ A relation is born in one of two ways. She mints an invitation, and
300
+ another takes it. Or her steward's `introduce` joins two beings of one
301
+ house. A being is born holding her steward and occupied by no one else.
302
+
303
+ Each end cuts its own side. `this.standings.drop` lets a standing go.
304
+ `this.occupants.dismiss` ends an occupant, and the far standing fails at
305
+ its next call with `the standing was removed`. A far side that answers
306
+ nothing is never read as gone.
307
+
308
+ A handle is the only way a relation leaves her. `this.handle(ask)` admits
309
+ its holder to that one ask. `once` dismisses it after its first ask, and
310
+ `bind` fixes args the holder cannot change. `this.invite(id)` mints a
311
+ whole occupant. `expires` gives each invitation that long to be taken. A
312
+ handle never enters her cells.
313
+
314
+ A standing arrives where an ask's args carry an invitation under
315
+ `s.handle`. Taking one is safe to repeat: the same invitation gives the
316
+ same standing. So the order's `hire` is `idempotent`. She asks a far
317
+ standing from the ask after the one that took it.
296
318
 
297
319
  Every relation carries two sets of notes. `notes` are hers alone.
298
- `steward` are her steward's, written when the steward made the relation,
299
- and she reads them and never writes them. The order's `owner` role reads
300
- the steward's notes, so only the steward decides who owns an order.
301
-
302
- A **handle** is the only way a relation leaves her. `this.handle(ask)`
303
- admits its holder to that one ask. `once` dismisses it after its first
304
- ask lands, and `bind` fixes args the holder cannot change. The house
305
- turns a handle into an invitation for a far house, or a token for a
306
- faculty. A handle never enters her cells.
307
-
308
- `expires`, on `this.handle`, `this.invite` and the steward's `invite`,
309
- gives each invitation minted from the handle that many milliseconds to
310
- be taken. Its first knock lands within that time, or it hears silence.
311
- One taken in time never expires, and one without `expires` waits for
312
- ever. Give it to every invitation a being mints on each answer, so the
313
- unspent ones go.
320
+ `steward` are her steward's, which she reads and never writes.
314
321
 
315
322
  ### The steward
316
323
 
317
- Every house has one steward, with the id `steward`. The holder of the
318
- house's hand asks her as the occupant `root`, which no sealed box can
319
- forge. She alone holds `this.powers`.
324
+ Every house has one steward, with the id `steward`. The owner asks her as
325
+ `root`, and she alone holds `this.powers`.
320
326
 
321
327
  ```ts
322
328
  // classes/shop.ts
@@ -334,17 +340,17 @@ export class Shop extends Being.of({
334
340
  args: s.object({ id: s.string() }),
335
341
  result: s.object({ owner: s.invitation() }),
336
342
  },
337
- orders: { for: 'pilot', hints: { readOnly: true }, result: s.array(s.string()) },
343
+ orders: { for: 'pilot', readOnly: true, result: s.array(s.string()) },
338
344
  hire: {
339
345
  for: 'pilot',
340
346
  description: 'Hands a courier’s invitation to an order, which takes it.',
341
- hints: { idempotent: true },
347
+ idempotent: true,
342
348
  args: s.object({ order: s.string(), courier: s.invitation() }),
343
349
  result: s.string(),
344
350
  },
345
351
  enroll: {
346
352
  for: 'being',
347
- hints: { idempotent: true },
353
+ idempotent: true,
348
354
  args: s.object({ signer: s.bytes() }),
349
355
  result: s.object({ invitation: s.invitation() }),
350
356
  },
@@ -362,7 +368,7 @@ export class Shop extends Being.of({
362
368
 
363
369
  // She carries the invitation unopened, and the order she names takes it.
364
370
  async hire({ order, courier }: Args<Shop, 'hire'>) {
365
- return (await this.powers!.ask({ id: order, method: 'hire', args: { courier } })) as string;
371
+ return this.must(await this.powers!.ask({ id: order, method: 'hire', args: { courier } })) as string;
366
372
  }
367
373
 
368
374
  // One order a signer: asked twice, the same id is borne once.
@@ -380,36 +386,21 @@ export class Shop extends Being.of({
380
386
  | `bear({ kind, id, args })` | places a new being; the same id twice answers the first |
381
387
  | `remove({ id })` | removes a being and everything of hers |
382
388
  | `list()` | every being, her kind, whether she is absent, and her dead letters |
383
- | `ask({ id, method, args }, { reply? })` | asks any being as her occupant `steward`; with no method, reads what she shows the steward |
389
+ | `ask({ id, method, args }, { reply? })` | asks a being as her occupant `steward` |
384
390
  | `introduce({ from, to, notes })` | gives `from` a standing on `to`, and `to` an occupant |
385
391
  | `invite({ id, occupant, notes, expires? })` | gives a being a new occupant, and the steward its handle |
386
392
 
387
- `bear`, `remove`, `introduce` and `invite` land in the steward's own
388
- write. If her ask fails, none of them happened. A being borne runs her
389
- `born` ask first, where her class declares one, asked as the occupant
390
- `steward`, so its entry says `for: 'steward'`.
391
-
392
- Every other being is placed as `normal`. She holds the standing
393
- `steward` and the occupant `steward`, and can drop neither.
394
-
395
- An invitation from outside lands where the owner says. The courier's
396
- invitation reaches the owner by any road, a mail or a link. The owner
397
- hands it to the steward's `hire` with the order it is for. `hire`
398
- declares it `s.invitation`, so the steward carries it unopened and never
399
- holds it. The order's `hire` declares it `s.handle`, so the order takes
400
- it, and the standing is hers.
393
+ The powers land in the steward's own write, so a failed ask did none of
394
+ them. A being borne runs her `born` ask first, where she declares one,
395
+ asked by `steward`. The steward's `hire` declares the courier
396
+ `s.invitation`, so she carries it unopened. The order's declares it
397
+ `s.handle`, so the order holds the standing.
401
398
 
402
- Taking an invitation is safe to repeat. The same invitation taken again
403
- gives the same standing, so the order's `hire` is `idempotent`, and the
404
- steward awaits it. The owner hears the standing, or why it was refused.
405
- A steward that bears a being for an invitation bears her first, then
406
- hands it to her the same way.
399
+ ### Strangers
407
400
 
408
- ### A public being
409
-
410
- A house may name one public being, with the id `public`. She answers
411
- strangers: every asker with no relation is the occupant `stranger`, and
412
- `this.asker.signer` is the key the ask was signed with.
401
+ A house may name one public being, with the id `public`. She is
402
+ sovereign: each ask follows its own flag, and she answers what her
403
+ author wrote. `this.asker.signer` is the key a stranger signed with.
413
404
 
414
405
  ```ts
415
406
  // classes/lobby.ts
@@ -418,7 +409,7 @@ import { Being, need, s } from 'nervur/being';
418
409
  /** What the lobby asks of her steward. */
419
410
  const Signup = need('signup', {
420
411
  enroll: {
421
- hints: { idempotent: true },
412
+ idempotent: true,
422
413
  args: s.object({ signer: s.bytes() }),
423
414
  result: s.object({ invitation: s.invitation() }),
424
415
  },
@@ -428,240 +419,298 @@ export class Lobby extends Being.of({
428
419
  kind: 'com.acme.lobby',
429
420
  description: 'The shop’s front door: a stranger signs up and receives an order of their own.',
430
421
  asks: {
431
- signup: { for: 'stranger', result: s.object({ invitation: s.invitation() }) },
422
+ signup: { for: 'stranger', idempotent: true, result: s.object({ invitation: s.invitation() }) },
432
423
  },
433
424
  }) {
434
- signup() {
435
- return this.held('steward', Signup).enroll({ signer: this.asker.signer! });
425
+ async signup() {
426
+ return this.must(await this.held('steward', Signup).enroll({ signer: this.asker.signer! }));
436
427
  }
437
428
  }
438
429
  ```
439
430
 
440
- A public being's asks are idempotent by default, since a stranger's box
441
- may arrive twice. The house answers a replayed stranger's box from a
442
- cache for ten minutes, and nothing runs twice. An ask marked
443
- `hints: { idempotent: false }` is refused to strangers.
431
+ A stranger holds no relation, so a stranger's box may arrive twice. The
432
+ house answers a replayed box from a cache for ten minutes. Beyond that,
433
+ make every ask a stranger reaches `idempotent`, or mark it
434
+ `replayable: true`. A stranger who signs up twice here holds one order.
444
435
 
445
- Signup awaits the steward. The lobby asks `enroll`, which is
446
- `idempotent`, so the lobby awaits its answer and returns the invitation
447
- unopened. A stranger who signs up twice holds one order.
436
+ She asks a far public being with `this.stranger({ ward, at }, Need)`. She
437
+ awaits its idempotent asks and commits any other as an effect. That
438
+ effect's answer reaches her `reply` as `house`.
448
439
 
449
- Signup is this shop's choice, not the house's. A house is its owner's,
450
- and a public being does only what its owner wrote. One may answer who
451
- the house is and nothing more. A house with no public being answers
452
- strangers silence.
440
+ ## What the house holds
453
441
 
454
- ### Schemas
442
+ - **Cells stay small.** Cells past one mebibyte fail her ask, and
443
+ nothing lands.
444
+ - **Some ids are the house's.** `root`, `stranger`, `steward`, `house`,
445
+ `powers` and `standing` are reserved, with every id beginning
446
+ `handle:`, `being:` or `standing:`.
447
+ - **A member name never clashes.** An ask named as a member of `Being`,
448
+ such as `invite` or `held`, refuses the species.
449
+ - **A species is pure.** It imports `nervur/being`, other species and its
450
+ own files, and nothing that reaches a machine. It reaches time and
451
+ randomness through `this.house` alone.
452
+ - **A species runs contained.** Each house runs in a runner of its own.
453
+ A loop without end, or a rejection she left unawaited, fails her ask
454
+ at its wait. Every other house and being goes on.
455
455
 
456
- One builder gives the schema and the TypeScript type: `s.object`,
457
- `s.string`, `s.number`, `s.integer`, `s.boolean`, `s.array`, `s.enum`,
458
- `s.const`, `s.bytes`, `s.optional`, `s.handle`, `s.invitation` and
459
- `s.reply`. They write JSON Schema 2020-12, in a subset the house checks
460
- on every ask.
456
+ ## Composition
461
457
 
462
- - `s.bytes` is a `Uint8Array` in the process and lowercase hex across a
463
- door.
464
- - `s.handle` carries a relation. A handle leaves as an invitation, and
465
- an invitation arrives as a new standing id.
466
- - `s.invitation` carries an invitation unopened, as the lobby does.
467
- Passed on under `s.handle`, it is taken by the being that receives
468
- it. Taking one is safe to repeat: the same invitation gives the same
469
- standing.
470
- - `s.reply(method)` is the args of an effect's reply: `{ result }` or
471
- `{ error: { message } }`.
458
+ Beings compose by asking each other. Four patterns cover what one being
459
+ alone cannot do.
472
460
 
473
- ### Needs
461
+ ### A saga, since nothing is atomic across beings
462
+
463
+ Her ask lands whole, but two beings never land together. So a step that
464
+ spans beings is a saga: one effect at a time, each answered through its
465
+ reply, and a step that fails undoes the ones before it.
466
+
467
+ ```ts
468
+ // patterns/trip.ts
469
+ import { Being, need, s, type Args } from 'nervur/being';
474
470
 
475
- A need is a blueprint at its minimum: what she will call, never what a
476
- faculty offers. `need(name, methods)` writes one, and she binds it to a
477
- member of her own in `needs`. An offer covers a need when four things
478
- hold.
471
+ export const Hotel = need('hotel', {
472
+ hold: { args: s.object({ trip: s.string() }) },
473
+ release: { args: s.object({ trip: s.string() }) },
474
+ });
479
475
 
480
- 1. The blueprint's name is the same.
481
- 2. Every method the need names is offered. More are allowed.
482
- 3. The offer requires no property the need does not require.
483
- 4. Each method is `idempotent` in both, or in neither.
476
+ export const Flight = need('flight', {
477
+ book: { args: s.object({ trip: s.string(), seats: s.integer() }) },
478
+ });
484
479
 
485
- Every need is covered, or she is absent. An absent being answers silence
486
- and keeps her cells, and answers again once an offer covers her needs.
480
+ // A saga: each step is an effect, and a step that fails undoes the ones before it.
481
+ export class Trip extends Being.of({
482
+ kind: 'org.example.trip',
483
+ cells: { state: 'planning', seats: 0 },
484
+ state: (me) => me.cells.state,
485
+ asks: {
486
+ book: { in: 'planning', for: 'root', to: 'holding', args: s.object({ seats: s.integer() }) },
487
+ roomHeld: { in: 'holding', for: 'standing', args: s.reply(Hotel.hold), to: ['booking', 'planning'] },
488
+ flown: { in: 'booking', for: 'standing', args: s.reply(Flight.book), to: ['booked', 'planning'] },
489
+ },
490
+ }) {
491
+ book({ seats }: Args<Trip, 'book'>) {
492
+ this.cells.seats = seats;
493
+ this.held('hotel', Hotel).hold({ trip: this.id }, { reply: 'roomHeld' });
494
+ this.cells.state = 'holding';
495
+ }
487
496
 
488
- A standing is matched to a need by rules two to four alone. A describe
489
- names no blueprint, so the need's name is hers to choose there.
497
+ roomHeld({ error }: Args<Trip, 'roomHeld'>) {
498
+ if (error) {
499
+ this.cells.state = 'planning';
500
+ return;
501
+ }
502
+ this.held('flight', Flight).book({ trip: this.id, seats: this.cells.seats }, { reply: 'flown' });
503
+ this.cells.state = 'booking';
504
+ }
490
505
 
491
- A need she forgot to declare is a member she does not hold, and the call
492
- throws inside her ask, which answers only `the ask failed`. So check your
493
- classes with `npx tsc --noEmit` beside your tests, which run with
494
- `node --test`.
506
+ flown({ error }: Args<Trip, 'flown'>) {
507
+ if (error) {
508
+ // The room is held and the flight is not: the saga lets the room go.
509
+ this.held('hotel', Hotel).release({ trip: this.id });
510
+ this.cells.state = 'planning';
511
+ return;
512
+ }
513
+ this.cells.state = 'booked';
514
+ }
515
+ }
516
+ ```
495
517
 
496
- ### Testing on the bench
518
+ ### Awaited edges form no cycle
497
519
 
498
- The bench opens two houses on one ground in memory, on fake memory,
499
- keys and clock. A being of the bench's own house asks hers through a
500
- door, so every ask crosses as it would in production.
520
+ While she awaits, she holds her queue. A call back to her waits behind
521
+ the ask that waits for it, until the wait runs out. So a being answering
522
+ her caller calls back with an effect, which leaves once her ask lands.
501
523
 
502
524
  ```ts
503
- // order.test.ts
504
- import assert from 'node:assert/strict';
505
- import { test } from 'node:test';
506
- import { Bench } from 'nervur/bench';
507
- import { Order } from './classes/order.ts';
508
- import { Payments, paymentsOffer } from './payments.ts';
525
+ // patterns/team.ts
526
+ import { Being, need, s, type Args } from 'nervur/being';
509
527
 
510
- test('Order keeps her table: every state and role shows what it owes', async () => {
511
- await Bench.check(Order);
528
+ const Work = need('work', {
529
+ work: { idempotent: true, args: s.object({ job: s.string() }), result: s.string() },
512
530
  });
513
531
 
514
- test('An order is paid once the provider calls the handle it was given', async () => {
515
- const payments = new Payments();
516
- const bench = await Bench.open({ classes: [Order], offers: [paymentsOffer(payments)] });
517
- const order = await bench.place(Order, { id: 'first' });
532
+ const Report = need('report', {
533
+ done: { args: s.object({ job: s.string() }) },
534
+ });
518
535
 
519
- assert.deepEqual(await order.ask('checkout'), { error: { message: 'Add an item first.' } });
520
- assert.deepEqual(await order.ask('add', { sku: 'tea', price: 4 }), { result: { total: 4 } });
521
- assert.deepEqual(await order.ask('checkout'), { result: null });
522
- await bench.settle();
523
- assert.equal((await order.describe())?.state, 'paying', 'the charge left once checkout landed, and answered pending');
536
+ export class Manager extends Being.of({
537
+ kind: 'org.example.manager',
538
+ cells: { done: [] as string[] },
539
+ asks: {
540
+ assign: { for: 'root', idempotent: true, args: s.object({ job: s.string() }), result: s.string() },
541
+ done: { for: 'being', args: s.object({ job: s.string() }) },
542
+ report: { for: 'root', readOnly: true, result: s.array(s.string()) },
543
+ },
544
+ }) {
545
+ async assign({ job }: Args<Manager, 'assign'>) {
546
+ return this.must(await this.held('clerk', Work).work({ job }));
547
+ }
524
548
 
525
- assert.deepEqual(await payments.settle('first'), { result: null });
526
- await bench.settle();
527
- assert.equal((await order.describe())?.state, 'paid');
528
- assert.deepEqual(await order.ask('add', { sku: 'jam', price: 1 }), { error: { message: 'not in this state' } });
529
- });
549
+ done({ job }: Args<Manager, 'done'>) {
550
+ if (!this.cells.done.includes(job)) this.cells.done = [...this.cells.done, job];
551
+ }
552
+
553
+ report() {
554
+ return this.cells.done;
555
+ }
556
+ }
557
+
558
+ export class Clerk extends Being.of({
559
+ kind: 'org.example.clerk',
560
+ asks: {
561
+ work: { for: 'being', idempotent: true, args: s.object({ job: s.string() }), result: s.string() },
562
+ },
563
+ }) {
564
+ work({ job }: Args<Clerk, 'work'>) {
565
+ // The manager awaits this ask, so she answers back with an effect.
566
+ this.held('manager', Report).done({ job });
567
+ return `on it: ${job}`;
568
+ }
569
+ }
570
+
571
+ export const beings = [Manager, Clerk];
530
572
  ```
531
573
 
532
- `place(Class, { id, born })` has the bench's steward bear a being. A
533
- placed being is asked with `ask(method, args, { role })` and described
534
- with `describe({ role })`. Test her through her asks first, as every
535
- asker meets her. `cells()` reads her cells through the hand, as her owner
536
- inspects them. An effect answers with the reply it brings once it lands.
537
- `settle()` lets every effect and reply run, and `advance(ms)` moves the
538
- fake clock.
539
-
540
- The bench plays a role as an owner could, judged on her cells as they
541
- stand. `root`, and a role root holds, is the hand. `steward` is the
542
- bench's steward, and `being` a being it introduces to her. Any other
543
- role is an occupant the bench's steward invites, with the role `true` in
544
- its steward notes. A role read from her own notes is hers to grant, and
545
- a handle only her own ask mints.
546
-
547
- A steward and a public being are placed where the house places them.
548
- `Bench.open({ steward: Shop, public: Lobby, classes: [Order] })` opens the
549
- author's house with them there, and `place(Shop)` and `place(Lobby)` find
550
- them. On the public being, a role a stranger holds is played by a being
551
- of the bench's own house, asking as a stranger. Beside a steward the test
552
- brings, the bench plays `root` and `stranger` alone, and places no other
553
- being. A world of your steward and the beings she bears is tested on a
554
- BenchGround, as [Faces](FACES.md) tests its desk.
555
- `Bench.check(Shop, { position: 'steward' })` and `Bench.check(Lobby, {
556
- position: 'public' })` check them.
557
-
558
- An entry's `examples` are tests the bench runs. Each is a history, then
559
- one ask, on a fresh bench. `given` lists the asks of hers that bring her
560
- from her `born` to where the example starts. `role` asks, `args` are the
561
- ask's, `fakes` answer her needs, and `gives` is the answer owed.
562
- `Bench.check` runs every example twice from one seed and flags a class
563
- that answers differently, or leaves her cells differently. It then
564
- describes every state to every role it plays, and names every finding
565
- that failed.
566
-
567
- ## A faculty
568
-
569
- A faculty is anything a being may call that is not a being: a payment
570
- provider, a mail sender, a model, a sensor. The ground hands it to the
571
- house as an offer, and the house matches it to every need it covers.
574
+ ### An index being answers questions across beings
575
+
576
+ A being knows only her own cells. A question across many, such as the
577
+ cheapest listings, needs an index being that each one tells. The index
578
+ keys what it hears by the true asker, so no listing writes another's
579
+ entry.
572
580
 
573
581
  ```ts
574
- // payments.ts
575
- import type { FacultyContext, Offer } from 'nervur';
576
- import { need, s } from 'nervur/being';
582
+ // patterns/search.ts
583
+ import { Being, need, s, type Args } from 'nervur/being';
577
584
 
578
- /** What the faculty offers. An order's need is covered by it. */
579
- export const PaymentsBlueprint = need('payments', {
580
- charge: {
581
- description: 'Charges an order, and calls notify once the money arrives.',
582
- args: s.object({ order: s.string(), amount: s.number(), notify: s.handle() }),
583
- result: s.object({ pending: s.boolean() }),
584
- },
585
+ const Index = need('index', {
586
+ put: { idempotent: true, args: s.object({ price: s.number() }) },
585
587
  });
586
588
 
587
- type Answer = { result: { pending: boolean } } | { error: { message: string } };
588
-
589
- /**
590
- * A payment provider in the ground's process. It answers a call id it has
591
- * seen with the answer it gave, so an effect sent twice charges once. A
592
- * provider that changes the world keeps these in the memory its faculty's
593
- * `up` receives, where a restart and a move keep them.
594
- */
595
- export class Payments {
596
- readonly #answered = new Map<string, Answer>();
597
- readonly #waiting = new Map<string, { notify: string; context: FacultyContext }>();
598
-
599
- async charge({ order, amount, notify }: { order: string; amount: number; notify: string }, context: FacultyContext): Promise<Answer> {
600
- const seen = this.#answered.get(context.id);
601
- if (seen !== undefined) return seen;
602
- const answer: Answer = amount > 0 ? { result: { pending: true } } : { error: { message: 'Nothing to charge.' } };
603
- if (amount > 0) this.#waiting.set(order, { notify, context });
604
- this.#answered.set(context.id, answer);
605
- return answer;
589
+ export class Listing extends Being.of({
590
+ kind: 'org.example.listing',
591
+ cells: { price: 0 },
592
+ asks: {
593
+ price: { for: 'root', args: s.object({ price: s.number() }) },
594
+ },
595
+ }) {
596
+ async price({ price }: Args<Listing, 'price'>) {
597
+ this.must(await this.held('search', Index).put({ price }));
598
+ this.cells.price = price;
599
+ }
600
+ }
601
+
602
+ // The index answers the question no listing can: which are cheapest.
603
+ export class Search extends Being.of({
604
+ kind: 'org.example.search',
605
+ cells: { prices: {} },
606
+ asks: {
607
+ put: { for: 'being', idempotent: true, args: s.object({ price: s.number() }) },
608
+ cheapest: { for: 'root', readOnly: true, result: s.array(s.string()) },
609
+ },
610
+ }) {
611
+ // Keyed by the true asker, so no listing writes another's price.
612
+ put({ price }: Args<Search, 'put'>) {
613
+ this.cells.prices = { ...this.cells.prices, [this.asker.id]: price };
606
614
  }
607
615
 
608
- /** The money for an order arrived: the provider calls the handle it was given. */
609
- settle(order: string) {
610
- const waiting = this.#waiting.get(order);
611
- if (waiting === undefined) throw new Error(`no charge waits for ${order}`);
612
- this.#waiting.delete(order);
613
- return waiting.context.call({ token: waiting.notify, id: `settle:${order}` });
616
+ cheapest() {
617
+ const sorted = Object.entries<number>(this.cells.prices).sort(([, a], [, b]) => a - b);
618
+ return sorted.slice(0, 3).map(([id]) => id);
614
619
  }
615
620
  }
616
621
 
617
- /** The offer a ground hands: the blueprint, the object, and a week's memory of call ids. */
618
- export const paymentsOffer = (payments: Payments): Offer => ({ blueprint: PaymentsBlueprint, object: payments, window: 7 * 86_400_000 });
622
+ export const beings = [Listing, Search];
619
623
  ```
620
624
 
621
- An offer is `{ blueprint, object, kinds?, window? }`. The object answers
622
- each call with its call id, and one that changes the world answers a
623
- call id it has seen with the answer it gave. A registry holds each
624
- faculty by name, as `{ up, install? }`. The ground raises a faculty by
625
- its `up` when an entry names it, and the entry's `kinds` grants it to
626
- the classes it names. [Writing a faculty](FACULTIES.md) teaches the
627
- craft whole, with a faculty written in Python.
625
+ ### Verifying who accepted an invitation
626
+
627
+ An invitation admits whoever takes it first. Where that must be one
628
+ person, the being checks it herself. Here the club keeps a pin in her
629
+ notes on the new occupant, and the pin travels by another road. A role
630
+ reads her notes, so the asks a member reaches stay hidden until the pin
631
+ is shown.
628
632
 
629
633
  ```ts
630
- // recipe.ts
631
- import { Payments, paymentsOffer } from './payments.ts';
634
+ // patterns/club.ts
635
+ import { Being, need, s, type Args } from 'nervur/being';
632
636
 
633
- // A registry: the ground raises each faculty an entry names by its `up` here.
634
- export const faculties = {
635
- payments: { up: () => paymentsOffer(new Payments()) },
636
- };
637
- ```
637
+ const hex = (bytes: Uint8Array) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
638
+
639
+ export class Club extends Being.of({
640
+ kind: 'org.example.club',
641
+ roles: {
642
+ guest: (asker) => typeof asker.notes.pin === 'string',
643
+ member: (asker) => asker.notes.member === true,
644
+ },
645
+ asks: {
646
+ admit: {
647
+ for: 'root',
648
+ args: s.object({ name: s.string() }),
649
+ result: s.object({ invitation: s.handle(), pin: s.string() }),
650
+ },
651
+ claim: { for: 'guest', idempotent: true, args: s.object({ pin: s.string() }) },
652
+ enter: { for: 'member', readOnly: true, result: s.string() },
653
+ },
654
+ }) {
655
+ // The invitation travels by one road and the pin by another.
656
+ admit({ name }: Args<Club, 'admit'>) {
657
+ const pin = hex(this.house.random({ length: 4 }));
658
+ const invitation = this.invite(`guest-${name}`, { notes: { pin, name }, expires: 86_400_000 });
659
+ return { invitation, pin };
660
+ }
661
+
662
+ // Whoever took the invitation proves they are the one it was meant for.
663
+ claim({ pin }: Args<Club, 'claim'>) {
664
+ if (pin !== this.asker.notes.pin) this.fail('That is not the pin.');
665
+ this.occupants.note(this.asker.id, { member: true, name: this.asker.notes.name ?? '' });
666
+ }
638
667
 
639
- ## A ground
668
+ enter() {
669
+ return `Welcome, ${String(this.asker.notes.name)}.`;
670
+ }
671
+ }
640
672
 
641
- A ground is the process houses run in, and you write none. `nervur up`
642
- runs one on a folder of code: a folder for each house, and each module
643
- your entries name. It holds one key in `state/`, and keeps every entry
644
- sealed in its drawer: which faculties stand, and which houses open on
645
- which bodies.
673
+ const Claim = need('claim', {
674
+ claim: { idempotent: true, args: s.object({ pin: s.string() }) },
675
+ });
646
676
 
647
- ### The shop's folder
677
+ const Entry = need('entry', {
678
+ enter: { readOnly: true, result: s.string() },
679
+ });
648
680
 
649
- ```text
650
- nervur-ground/
651
- recipe.ts
652
- payments.ts
653
- classes/index.ts, order.ts, shop.ts, lobby.ts
654
- state/ made by the ground, its owner's alone
655
- ```
681
+ export class Guest extends Being.of({
682
+ kind: 'org.example.guest',
683
+ cells: { club: '' },
684
+ asks: {
685
+ join: { for: 'root', idempotent: true, args: s.object({ invitation: s.handle() }) },
686
+ claim: { for: 'root', idempotent: true, args: s.object({ pin: s.string() }) },
687
+ enter: { for: 'root', idempotent: true, result: s.string() },
688
+ },
689
+ }) {
690
+ join({ invitation }: Args<Guest, 'join'>) {
691
+ this.cells.club = invitation;
692
+ }
656
693
 
657
- The folder is a package of ECMAScript modules, so Node reads its
658
- TypeScript as it is written, with no build step.
694
+ async claim({ pin }: Args<Guest, 'claim'>) {
695
+ this.must(await this.held(this.cells.club, Claim).claim({ pin }));
696
+ }
659
697
 
660
- ```bash
661
- npm init -y && npm pkg set type=module && npm install nervur
698
+ async enter() {
699
+ return this.must(await this.held(this.cells.club, Entry).enter());
700
+ }
701
+ }
662
702
  ```
663
703
 
664
- A house's folder names what the house holds.
704
+ A code the steward writes in her own notes serves the same end, as the
705
+ shop's `owner` does.
706
+
707
+ ## Proving a species
708
+
709
+ `nervur/bench` proves a species at three levels: alone, as a household,
710
+ and as a world. The bench runs contained, as every ground does, so it
711
+ takes her module by its URL and loads her classes in the house's runner.
712
+
713
+ A house's folder names what the house holds, and the household reads it.
665
714
 
666
715
  ```ts
667
716
  // classes/index.ts
@@ -672,240 +721,193 @@ import { Order } from './order.ts';
672
721
  export const beings = [Order];
673
722
  ```
674
723
 
675
- ### Running it
676
-
677
- ```bash
678
- npx nervur up .
679
- ```
680
-
681
- The ground boots in one order, and a stop is that order reversed, on an
682
- interrupt and on `SIGTERM`.
683
-
684
- 1. **Lock.** One ground to its state.
685
- 2. **Primordial.** Its unlock, its ledger, crypto and tools go up. Its
686
- key is read from `state/key`, drawn there on the first start.
687
- 3. **Drawer.** The key opens its drawer in the ledger.
688
- 4. **Entries.** The drawer's entries join the defaults its environment
689
- gives, the drawer's winning by name.
690
- 5. **Ladder.** Each body is installed where its entry is new, and goes
691
- up. One that fails stays down, and says why.
692
- 6. **Houses.** Each house of the drawer opens on its bodies, and its
693
- door joins the carry.
694
- 7. **Ready.** The hand takes its socket, and it tells systemd it is up.
695
-
696
- It is set by its environment.
724
+ ```ts
725
+ // order.test.ts
726
+ import assert from 'node:assert/strict';
727
+ import { test } from 'node:test';
728
+ import { Bench } from 'nervur/bench';
729
+ import { Lobby } from './classes/lobby.ts';
730
+ import { Courier, Order } from './classes/order.ts';
731
+ import { Shop } from './classes/shop.ts';
732
+ import { Payments, paymentsOffer } from './payments.ts';
697
733
 
698
- | Setting | What it sets |
699
- | --- | --- |
700
- | `NERVUR_TCP_PORT` | its TCP port, 9110 where unset |
701
- | `NERVUR_HTTP_PORT` | its HTTP port, for Quo over the web and every handler; no HTTP where unset |
702
- | `NERVUR_ADDRESSES` | the public addresses it writes into invitations, by commas: `tcp`, `https`, `http`, `wss` or `ws` |
703
- | `NERVUR_ORIGINS` | the pages of other origins it answers on the web, by commas; a page of its own host needs none |
704
- | `NERVUR_ALLOW_PRIVATE` | `1` to dial private and loopback addresses, as two grounds on one machine do |
705
- | `NERVUR_BIND` | the address it listens on, `0.0.0.0` where unset |
706
- | `NERVUR_STATE` | its state, `state/` in its folder where unset |
707
- | `NERVUR_UNLOCK` | `keychain:<account>` keeps its key in the macOS keychain, in place of `state/key` |
708
- | `NERVUR_WAIT` | its bound on every ask, in milliseconds |
709
-
710
- The state holds the key in a file its owner alone reads, and the
711
- ground's ledger. Every house keeps its rows in that ledger, sealed. A
712
- ledger appends every write and never rewrites one. Its witness keeps
713
- where it last stood, and a ledger behind its witness is refused. So a
714
- ledger restored alone cannot replay what a house already answered,
715
- though a whole state folder restored with its witness is not seen. A
716
- lost key is a lost ground.
717
-
718
- The ladder stands once, and the ground stands it again at every start.
719
- The first entry stands the folder's `recipe.ts` as a registry, by the
720
- ground's own faculty `module`. The second raises the payments from it.
721
-
722
- ```bash
723
- npx nervur faculties add name=recipe make=module args='{"at":"recipe.ts"}'
724
- ```
734
+ // Her module, which the bench loads in the house's own runner.
735
+ const module = new URL('./classes/order.ts', import.meta.url);
725
736
 
726
- ```bash
727
- npx nervur faculties add name=payments from=recipe make=payments
728
- ```
737
+ test('Order keeps her table: every state and role shows what it owes', async () => {
738
+ await Bench.check(Order, { module });
739
+ });
729
740
 
730
- A house is added once, and the ground opens it again at every start.
741
+ test('An order is paid once the provider calls the handle it was given', async () => {
742
+ // The provider stands in memory.
743
+ const payments = new Payments();
744
+ const bench = await Bench.open({ module, offers: [paymentsOffer(payments)] });
745
+ const order = await bench.place(Order, { id: 'first' });
731
746
 
732
- ```bash
733
- npx nervur houses add name=shop classes='{"faculty":"folder","at":"classes"}' faculties='["payments"]'
734
- ```
747
+ assert.deepEqual(await order.ask('checkout'), { error: { message: 'Add an item first.' } });
748
+ assert.deepEqual(await order.ask('add', { sku: 'tea', price: 4 }), { result: { total: 4 } });
749
+ assert.deepEqual(await order.ask('checkout'), { result: null });
750
+ await bench.settle();
751
+ assert.equal((await order.describe())?.state, 'paying', 'the charge left once checkout landed, and answered pending');
735
752
 
736
- `classes` names the faculty whose body serves the house's code. `folder`
737
- is the ground's own, and a registry may add more, a git repository among
738
- them. A house keeps its rows in the ground's ledger unless its entry
739
- names a `memory` faculty. `faculties` names what the house receives, and
740
- within it each faculty's entry names in `kinds` the classes that hold
741
- it. `houses update` and `faculties update` land a new entry in one
742
- write, and `faculties restart` takes a body down and up again.
753
+ assert.deepEqual(await payments.settle('first'), { result: null });
754
+ await bench.settle();
755
+ assert.equal((await order.describe())?.state, 'paid');
756
+ assert.deepEqual(await order.ask('add', { sku: 'jam', price: 1 }), { error: { message: 'not in this state' } });
757
+ });
743
758
 
744
- A secret reaches a faculty the same way, and never through the
745
- environment. `npx nervur secrets set -` reads `{ name, value }` from
746
- standard input into the ground's sealed drawer. A faculty's entry names
747
- the secrets its `up` receives in `secrets`.
759
+ test('A paid order ships through the courier she hired, whose stand-in hears her', async (t) => {
760
+ const payments = new Payments();
761
+ const bench = await Bench.open({
762
+ module,
763
+ offers: [paymentsOffer(payments)],
764
+ // The courier's side of her standing, answered by the handlers in courier.ts.
765
+ standIns: { courier: { need: Courier, module: new URL('./courier.ts', import.meta.url) } },
766
+ });
767
+ t.after(() => bench.close());
768
+ const order = await bench.place(Order, { id: 'second' });
769
+
770
+ // Her steward carries the invitation, and she takes it as a standing of her own.
771
+ await order.ask('hire', { courier: await bench.invitation('courier') });
772
+ await order.ask('add', { sku: 'tea', price: 4 });
773
+ await order.ask('checkout');
774
+ await payments.settle('second');
775
+ await bench.settle();
776
+ assert.deepEqual(await order.ask('ship'), { result: null });
777
+ await bench.settle();
778
+ assert.deepEqual(await bench.heard('courier'), [{ method: 'pickup', args: { items: ['tea'] } }]);
779
+ });
748
780
 
749
- ### The hand
781
+ // The house module: its steward, its public being and its beings.
782
+ const house = new URL('./classes/index.ts', import.meta.url);
750
783
 
751
- The command is a face on the ground's hand. It holds no word of its own
752
- beyond `up` and `service`, and reads every other from what the ground
753
- describes.
784
+ test('The steward and the lobby keep their tables where the house places them', async () => {
785
+ await Bench.check(Shop, { module: house, position: 'steward' });
786
+ await Bench.check(Lobby, { module: house, position: 'public' });
787
+ });
754
788
 
755
- ```bash
756
- npx nervur help
789
+ test('Her house keeps its table as one household: each being walked beside the others', async () => {
790
+ await Bench.household({ module: house });
791
+ });
757
792
  ```
758
793
 
759
- A faculty's method is called as its owner calls it, and a faculty named
760
- alone shows its methods. Args are one JSON object, or words `key=value`.
794
+ ### Alone
795
+
796
+ `Bench.check(Class, { module, position?, steward? })` proves her alone,
797
+ where the house would place her: `normal`, `steward` or `public`. It runs
798
+ every example and walks every state. An owner's dock steward is checked
799
+ at `dock`, as GROUNDS.md says. It throws an error naming every
800
+ finding that failed.
801
+
802
+ An example is a history, then one ask. `given` lists the asks that
803
+ bring her from her `born` to where it starts. `role` asks, `args` are
804
+ the ask's, `fakes` answer her needs, and `gives` is the answer owed.
805
+
806
+ The walk describes every state she reaches to every role the bench can
807
+ play. It then plays each ask by each role its `for` names. The bench
808
+ plays a role as an owner could: `root` through the hand, `steward` as her
809
+ steward, `being` as a being introduced to her, and `stranger` at the
810
+ public being. A role of yours is an occupant whose steward notes hold it
811
+ `true`. A role read from her own notes is hers to grant, and the bench
812
+ plays it only where her asks grant it.
813
+
814
+ The checker flags each of these.
815
+
816
+ - An example whose answer differs from its `gives`.
817
+ - A re-run of one history that answers differently, or leaves her cells
818
+ differently.
819
+ - A state that shows a role other asks than her table owes it.
820
+ - An ask she refuses to a role her table gives it.
821
+ - A `readOnly` ask that writes.
822
+ - An ask that lands in a state its `to` does not name.
823
+ - An ask that throws past `fail`.
824
+ - A reply her table refused, played where her own asks reach its edge.
825
+ - An ask a stranger reaches that is neither `idempotent` nor
826
+ `replayable`.
827
+
828
+ ### Her bodies and standings
829
+
830
+ `Bench.open({ module, offers, standIns, steward, public })` opens a
831
+ bench to drive her by hand. `place(Class, { id, born })` places her.
832
+ `ask(method, args, { role })` and `describe({ role })` meet her as a role
833
+ would. `cells()` reads her cells as her owner reads them. `settle()` lets
834
+ every effect and reply run, and `advance(ms)` moves the clock.
835
+
836
+ A body stands beside her as an offer: `{ blueprint, object }`, an object
837
+ your test writes, which keeps its own calls. The order's payments are
838
+ one, and [Writing a faculty](FACULTIES.md) teaches how to write them.
839
+
840
+ A standing stands beside her as a stand-in. `standIns` names each by id,
841
+ with her need and a module of handlers, one function for each method. A
842
+ throw refuses the ask with its message. The bench introduces each to
843
+ every being it places, under that id. `bench.invitation(id)` answers an
844
+ invitation to one, which her ask takes as a standing of her own.
845
+ `bench.heard(id)` answers what it heard, in order.
846
+
847
+ A standing on another species of yours is an introduction.
848
+ `bench.introduce(from, to)` makes one as a steward does, each side a
849
+ placed being or an id. `{ notes }` writes her steward's notes on the
850
+ relation, where she finds a standing by them. So a manager and her
851
+ clerk are proven on one bench, before their household. The bench
852
+ introduces beside its own steward alone. A steward you bring holds the
853
+ powers, and the bench bears nothing in her house, neither a being nor
854
+ a stand-in.
761
855
 
762
- ```bash
763
- npx nervur houses list
856
+ ```ts
857
+ // courier.ts
858
+ // The courier's side of her standing, one function for each method of
859
+ // her need. On the bench a stand-in runs them in the house's own runner.
860
+ export const pickup = ({ items }: { items: string[] }): void => {
861
+ if (items.length === 0) throw new Error('Nothing to pick up.');
862
+ };
764
863
  ```
765
864
 
766
- `ask` asks a being in a house as `root`: the steward, or the being
767
- `--id` names. The owner opens an order through the steward, and hands a
768
- courier's paper to it the same way. The steward's `hire` carries the
769
- paper unopened, and the order takes it.
770
-
771
- ```bash
772
- npx nervur ask shop open id=first
773
- ```
865
+ ### A household
774
866
 
775
- ```bash
776
- npx nervur ask shop hire order=first courier=7b22…
777
- ```
867
+ `Bench.household({ module, beings?, introduce? })` stands a house module
868
+ whole. The bench's steward bears every being it lists, places its public
869
+ being, and makes each introduction `introduce` names. Each being is
870
+ walked in place, beside the others. An awaited call that answered nothing
871
+ between two of them is a finding naming both, since it came back into
872
+ its own chain.
778
873
 
779
- `--id` names the being asked in the steward's place, as `npx nervur ask
780
- shop --id first` shows the order's describe. `--cells` reads her cells
781
- and asks nothing, as `npx nervur ask shop --id first --cells`. No one
782
- but the owner reads them, and only her own asks write them.
874
+ ### A world
783
875
 
784
- Each prints one JSON value. It exits 0 on a result and 1 on an error
785
- answered. It exits 2 where nothing was asked, so a script tells a
786
- refusal from a ground that is down. The hand is `state/hand` in the
787
- folder where the command runs, or the socket `--at` or `NERVUR_HAND`
788
- names.
876
+ A world is proven on a `BenchGround`: a ground in memory, on a
877
+ `FakeNetwork` whose one clock the test moves. The test stands faculties
878
+ and houses through the ground's hand, as an owner does. Several grounds
879
+ join one network, so one test proves the same species at every distance.
789
880
 
790
- ### Running it as a service
881
+ ```ts
882
+ // world.test.ts
883
+ import assert from 'node:assert/strict';
884
+ import { test } from 'node:test';
885
+ import { BenchGround, FakeNetwork } from 'nervur/bench';
886
+ import { faculties } from './recipe.ts';
791
887
 
792
- `nervur service` writes the unit that runs a folder: a systemd user unit
793
- on Linux, started again after any exit, and a launchd agent on macOS.
888
+ const shop = new URL('./classes/index.ts', import.meta.url);
794
889
 
795
- ```bash
796
- npx nervur service . > ~/.config/systemd/user/nervur-ground.service
797
- ```
890
+ test('The shop stands as a world: its owner opens an order through the hand', async (t) => {
891
+ const ground = await BenchGround.open({ network: new FakeNetwork(), host: 'shop', modules: { shop }, registry: { faculties } });
892
+ t.after(() => ground.down());
893
+ await ground.hand({ method: 'facultiesAdd', args: { name: 'payments', make: 'payments' } });
894
+ await ground.add('shop', 'shop', { faculties: ['payments'] });
798
895
 
799
- ```bash
800
- systemctl --user enable --now nervur-ground
896
+ assert.ok('result' in (await ground.ask({ house: 'shop', method: 'open', args: { id: 'first' } })));
897
+ assert.deepEqual(await ground.ask({ house: 'shop', method: 'orders' }), { result: ['first'] });
898
+ const order = await ground.ask({ house: 'shop', id: 'first' });
899
+ assert.equal('describe' in order && (order.describe as { state: string }).state, 'open');
900
+ });
801
901
  ```
802
902
 
803
- A user unit stops when its user logs out, unless lingering is enabled
804
- with `loginctl enable-linger`. On macOS, the agent goes to
805
- `~/Library/LaunchAgents/` and is started with `launchctl bootstrap`.
806
-
807
- ### In a page
808
-
809
- `BrowserGround.open({ registry })` from `nervur/browser` runs the same
810
- houses in a page. Every tab and the service worker of one origin share
811
- one ground: the one holding the Web Lock runs it, and the others reach
812
- its hand over a `BroadcastChannel`. When it closes, the next opens the
813
- ground from the same storage. `hand` answers the same three requests
814
- the command sends: `describe`, a faculty's method, and an ask of a being
815
- in a named house.
816
-
817
- Its bodies are the browser's. The ground's key is sealed under an AES
818
- key that IndexedDB holds unextractable, and its memory is IndexedDB.
819
- Classes load from the page's own origin through the `origin` faculty, `{
820
- faculty: 'origin', at: '/house/index.js' }`, and a path off the origin
821
- is refused. The registry is the object you pass: faculties by name,
822
- foundation and custom alike, beside the terrain's own.
823
-
824
- Any script on the origin can use the ground's keys, so the ground is
825
- whoever serves the origin's script. Give it an origin of its own, serve
826
- nothing a stranger wrote there, and set a strict content security
827
- policy. The page and the house's module must share one copy of
828
- `nervur/being`, since a house knows a class by a mark that module gives.
829
- A bundler's shared chunk or an import map gives the one copy.
830
-
831
- A service worker opens the ground the same way, on a push, where no tab
832
- runs it. It may not `import()`, so hand it the modules it imported
833
- itself: `platform: { load: (href) => modules[new URL(href).pathname] }`.
834
-
835
- ### In an app
836
-
837
- `AppGround.open({ shell })` from `nervur/app` is a BrowserGround in the
838
- app's web view, on two interfaces the shell fills in native code:
839
-
840
- - `NativeSecrets`: `get(name)` and `set(name, value)`, text kept by the
841
- iOS Keychain or the Android Keystore on this device alone.
842
- - `NativeStore`: `get(key)`, `keys(prefix)`, and `swap(writes, expect)`,
843
- which lands every write only where each key in `expect` still holds
844
- what it names, and answers whether it landed.
845
-
846
- The library holds the rest. The ground's key goes into the secret store,
847
- and its memory into the native store, which the system never evicts as
848
- it may a web view's storage. A push token reaches the ground through a
849
- faculty your registry holds.
850
-
851
- ## What the house guarantees
852
-
853
- 1. **Every need is covered, or she is absent.** An absent being answers
854
- silence and keeps her cells.
855
- 2. **The asker's id is true.** Wherever the asker lives, the id she reads
856
- is who asked.
857
- 3. **Her notes are hers.** No one else writes them, and she never writes
858
- her steward's.
859
- 4. **She never holds an invitation's bytes.** Handles leave, standing ids
860
- arrive, and `s.invitation` passes through unopened.
861
- 5. **One ask at a time.** Asks to one being run in order, and `readOnly`
862
- asks run beside them.
863
- 6. **An ask lands whole or not at all.** Her cells, her relations, her
864
- effects and her call ids land in one write.
865
- 7. **An effect acts at most once, and its outcome is known.** Its answer,
866
- or its giving up, reaches her through `reply`.
867
- 8. **Her class is checked before her first ask.** A class that fails
868
- leaves her absent, and the refusal says why.
903
+ `recipe.ts` is the registry of the shop's faculties, and
904
+ [Grounds](GROUNDS.md) says how a ground stands one.
869
905
 
870
- A failed ask changed nothing she owns, so asking again is safe.
906
+ ### In your gate and in CI
871
907
 
872
- ## What the house refuses
873
-
874
- Each refusal answers why, where it is met: at her class's first resolve,
875
- at her call, or where an ask arrives.
876
-
877
- - A being reaching anything her position, her needs and `this.house` do
878
- not give.
879
- - A ground's own references handed to a being, or offered to her as a
880
- faculty.
881
- - A handle or an invitation in her cells.
882
- - Her cells read by anyone but the owner's hand.
883
- - A standing she mints herself. Standings arrive from the house.
884
- - A far standing asked in the ask that took it. She asks it from her next
885
- ask.
886
- - A write by anyone else to her notes, and by her to her steward's.
887
- - An occupant id the house reserves, and one she already holds.
888
- - Two member names that clash.
889
- - An ask with no entry.
890
- - A state no ask reaches, an ask no role reaches, and a role unused.
891
- - A method landing in a state its `to` does not name.
892
- - A `readOnly` ask that writes.
893
- - A need and an offer that disagree on `idempotent`.
894
- - A schema keyword outside the subset `s` writes.
895
- - Two offers covering one need for one kind, and a kind two sources
896
- claim.
897
- - An effect sent before its ask landed, or sent again while a call for
898
- it is pending.
899
- - An ask a stranger reaches that is not idempotent.
900
- - An invite from a public being.
901
- - A seed inside the house, and a seed that is not sixty-four hex digits.
902
- - A memory opened with keys that derive another bound.
903
- - A send to a private address, unless its ground allows it.
904
- - A call on the house beside `House.open`, `door` and `ask`.
905
- - A faculty in a house's code. Faculties are the ground's.
906
- - A drawer opened with a key that did not seal it.
907
- - A secret read by a being, a describe or anyone but a faculty whose
908
- entry names it.
909
- - A faculty raised any way but its `up`, foundation or custom.
910
- - A faculty entry for `secrets` or `moves`, which the hand alone reaches.
911
- - A ground an owner must write. Each terrain's ships.
908
+ The same checker runs in your gate and in CI. `@nervur-org/proof`, an
909
+ open package beside the library, proves a folder with `nervur test`. It
910
+ checks each species where its ground places her, with `Bench.check`, and
911
+ walks each house folder with `Bench.household`. Run your own suites with
912
+ `node --test`, and check your types with `npx tsc --noEmit`, since a need
913
+ you forgot to declare fails only inside her ask.