nervur 0.22.2-6 → 0.22.2-8

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 (69) hide show
  1. package/AUTHORING.md +81 -45
  2. package/COMMAND.md +27 -10
  3. package/FACES.md +29 -12
  4. package/FACULTIES.md +178 -94
  5. package/GROUNDS.md +144 -66
  6. package/KIT-SPEC.md +10 -7
  7. package/README.md +16 -14
  8. package/dist/app/app-ground.d.ts +1 -1
  9. package/dist/app/app-ground.js +5 -5
  10. package/dist/app/index.d.ts +1 -1
  11. package/dist/app/index.js +1 -1
  12. package/dist/app/native.d.ts +3 -3
  13. package/dist/app/native.js +3 -3
  14. package/dist/bench/bench-ground.d.ts +18 -14
  15. package/dist/bench/bench-ground.js +61 -46
  16. package/dist/bench/fake-unlock.d.ts +6 -0
  17. package/dist/bench/fake-unlock.js +10 -0
  18. package/dist/bodies/kept.d.ts +5 -16
  19. package/dist/bodies/kept.js +17 -36
  20. package/dist/browser/browser-ground.d.ts +8 -16
  21. package/dist/browser/browser-ground.js +47 -38
  22. package/dist/browser/index.d.ts +1 -1
  23. package/dist/browser/index.js +1 -1
  24. package/dist/browser/locked-unlock.d.ts +24 -0
  25. package/dist/browser/locked-unlock.js +85 -0
  26. package/dist/edge/edge-ground.d.ts +8 -17
  27. package/dist/edge/edge-ground.js +57 -48
  28. package/dist/edge/index.d.ts +2 -2
  29. package/dist/edge/index.js +1 -1
  30. package/dist/edge/secret-unlock.d.ts +7 -0
  31. package/dist/edge/secret-unlock.js +13 -0
  32. package/dist/ground/ground.d.ts +249 -111
  33. package/dist/ground/ground.js +864 -357
  34. package/dist/ground/views.d.ts +59 -0
  35. package/dist/ground/views.js +188 -0
  36. package/dist/house/house.d.ts +8 -0
  37. package/dist/house/house.js +12 -3
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.js +2 -1
  40. package/dist/node/bridge.d.ts +6 -3
  41. package/dist/node/bridge.js +68 -7
  42. package/dist/node/cli.js +32 -19
  43. package/dist/node/index.d.ts +2 -4
  44. package/dist/node/index.js +1 -3
  45. package/dist/node/node-ground.d.ts +11 -20
  46. package/dist/node/node-ground.js +200 -92
  47. package/dist/node/tcp-carry.d.ts +2 -2
  48. package/dist/node/tcp-carry.js +6 -4
  49. package/dist/node/unlock.d.ts +17 -0
  50. package/dist/node/unlock.js +86 -0
  51. package/dist/serve/index.d.ts +20 -7
  52. package/dist/serve/index.js +21 -9
  53. package/package.json +1 -1
  54. package/dist/bench/fake-custody.d.ts +0 -15
  55. package/dist/bench/fake-custody.js +0 -39
  56. package/dist/bench/fake-keys.d.ts +0 -4
  57. package/dist/bench/fake-keys.js +0 -9
  58. package/dist/browser/locked-custody.d.ts +0 -36
  59. package/dist/browser/locked-custody.js +0 -114
  60. package/dist/edge/secret-custody.d.ts +0 -6
  61. package/dist/edge/secret-custody.js +0 -40
  62. package/dist/node/custody.d.ts +0 -34
  63. package/dist/node/custody.js +0 -58
  64. package/dist/node/file-keys.d.ts +0 -11
  65. package/dist/node/file-keys.js +0 -44
  66. package/dist/node/held-keys.d.ts +0 -14
  67. package/dist/node/held-keys.js +0 -36
  68. package/dist/node/keychain-keys.d.ts +0 -12
  69. package/dist/node/keychain-keys.js +0 -55
package/AUTHORING.md CHANGED
@@ -11,8 +11,8 @@ The shop has three classes and one faculty.
11
11
  - `Order` is one order, from its first item to its shipping.
12
12
  - `Shop` is the steward, the being that runs the house.
13
13
  - `Lobby` is the public being, which strangers may ask.
14
- - `Payments` is a faculty that charges money, which the ground's
15
- `recipe.ts` makes.
14
+ - `Payments` is a faculty that charges money, which `recipe.ts` holds
15
+ by name.
16
16
 
17
17
  ## Words
18
18
 
@@ -31,7 +31,9 @@ The shop has three classes and one faculty.
31
31
  | role | a named test over the asker and her cells |
32
32
  | state | a name read from her cells that decides which asks exist |
33
33
  | house | what holds beings, keeps their cells, and seals every ask |
34
- | ground | the process houses run in, holding their seeds, faculties and hands |
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 |
35
37
 
36
38
  ## A being
37
39
 
@@ -587,7 +589,8 @@ type Answer = { result: { pending: boolean } } | { error: { message: string } };
587
589
  /**
588
590
  * A payment provider in the ground's process. It answers a call id it has
589
591
  * seen with the answer it gave, so an effect sent twice charges once. A
590
- * provider that changes the world keeps these where a restart keeps them.
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.
591
594
  */
592
595
  export class Payments {
593
596
  readonly #answered = new Map<string, Answer>();
@@ -615,28 +618,31 @@ export class Payments {
615
618
  export const paymentsOffer = (payments: Payments): Offer => ({ blueprint: PaymentsBlueprint, object: payments, window: 7 * 86_400_000 });
616
619
  ```
617
620
 
618
- An offer is `{ blueprint, object, kinds?, window?, handler?, stop? }`.
619
- The object answers each call with its call id, and one that changes the
620
- world answers a call id it has seen with the answer it gave. The ground's
621
- recipe makes each faculty by name, and `kinds` grants it to the classes
622
- it names. [Writing a faculty](FACULTIES.md) teaches the craft whole, with a
623
- faculty written in Python.
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.
624
628
 
625
629
  ```ts
626
630
  // recipe.ts
627
631
  import { Payments, paymentsOffer } from './payments.ts';
628
632
 
629
- export const faculties = () => ({
630
- payments: paymentsOffer(new Payments()),
631
- });
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
+ };
632
637
  ```
633
638
 
634
639
  ## A ground
635
640
 
636
641
  A ground is the process houses run in, and you write none. `nervur up`
637
- runs one on a folder: the recipe of its faculties, and a folder of code
638
- for each house. It keeps a record of its houses, and opens each on the
639
- bodies the record names.
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.
640
646
 
641
647
  ### The shop's folder
642
648
 
@@ -676,11 +682,16 @@ The ground boots in one order, and a stop is that order reversed, on an
676
682
  interrupt and on `SIGTERM`.
677
683
 
678
684
  1. **Lock.** One ground to its state.
679
- 2. **Its own.** Its seeds and its record open.
680
- 3. **Faculties.** The recipe makes each, in its order.
681
- 4. **Houses.** Each house of the record opens on its bodies.
682
- 5. **Hook.** The listeners, then the hand on its socket.
683
- 6. **Ready.** It tells systemd it is up, where systemd waits.
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.
684
695
 
685
696
  It is set by its environment.
686
697
 
@@ -693,26 +704,47 @@ It is set by its environment.
693
704
  | `NERVUR_ALLOW_PRIVATE` | `1` to dial private and loopback addresses, as two grounds on one machine do |
694
705
  | `NERVUR_BIND` | the address it listens on, `0.0.0.0` where unset |
695
706
  | `NERVUR_STATE` | its state, `state/` in its folder where unset |
696
- | `NERVUR_KEYCHAIN` | a keychain service holding its seeds on macOS, in place of files |
707
+ | `NERVUR_UNLOCK` | `keychain:<account>` keeps its key in the macOS keychain, in place of `state/key` |
697
708
  | `NERVUR_WAIT` | its bound on every ask, in milliseconds |
698
709
 
699
- The state holds each house's seed in a file its owner alone reads, and
700
- each house's ledger. A ledger appends every write and never rewrites
701
- one. Its witness keeps where it last stood, and a ledger behind its
702
- witness is refused. So a ledger restored alone cannot replay what a
703
- house already answered, though a whole state folder restored with its
704
- witnesses is not seen. A lost seed is a lost house.
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
+ ```
725
+
726
+ ```bash
727
+ npx nervur faculties add name=payments from=recipe make=payments
728
+ ```
705
729
 
706
730
  A house is added once, and the ground opens it again at every start.
707
731
 
708
732
  ```bash
709
- npx nervur houses add name=shop memory='{"body":"ledger"}' classes='{"body":"folder","at":"classes"}' faculties='["payments"]'
733
+ npx nervur houses add name=shop classes='{"faculty":"folder","at":"classes"}' faculties='["payments"]'
710
734
  ```
711
735
 
712
- `memory` and `classes` name the bodies the house is handed. `ledger` and
713
- `folder` are the ground's own, and a recipe may add more, a git registry
714
- or another store among them. `faculties` names what the house receives,
715
- and within it each offer's `kinds` names the classes that hold it.
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.
743
+
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`.
716
748
 
717
749
  ### The hand
718
750
 
@@ -774,7 +806,7 @@ with `loginctl enable-linger`. On macOS, the agent goes to
774
806
 
775
807
  ### In a page
776
808
 
777
- `BrowserGround.open({ recipe })` from `nervur/browser` runs the same
809
+ `BrowserGround.open({ registry })` from `nervur/browser` runs the same
778
810
  houses in a page. Every tab and the service worker of one origin share
779
811
  one ground: the one holding the Web Lock runs it, and the others reach
780
812
  its hand over a `BroadcastChannel`. When it closes, the next opens the
@@ -782,12 +814,12 @@ ground from the same storage. `hand` answers the same three requests
782
814
  the command sends: `describe`, a faculty's method, and an ask of a being
783
815
  in a named house.
784
816
 
785
- Its bodies are the browser's. Seeds are sealed under an AES key that
786
- IndexedDB holds unextractable, and memory is IndexedDB, named
787
- `indexeddb` in an entry. Classes load from the page's own origin through
788
- the `origin` body, `{ body: 'origin', at: '/house/index.js' }`, and a
789
- path off the origin is refused. The recipe is the object you pass: its
790
- faculties, made by a function, and any custom body.
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.
791
823
 
792
824
  Any script on the origin can use the ground's keys, so the ground is
793
825
  whoever serves the origin's script. Give it an origin of its own, serve
@@ -811,10 +843,10 @@ app's web view, on two interfaces the shell fills in native code:
811
843
  which lands every write only where each key in `expect` still holds
812
844
  what it names, and answers whether it landed.
813
845
 
814
- The library holds the rest. Each house's seed goes into the secret
815
- store, and each memory into the native store, which the system never
816
- evicts as it may a web view's storage. A push token reaches the ground
817
- through a faculty your recipe makes.
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.
818
850
 
819
851
  ## What the house guarantees
820
852
 
@@ -871,5 +903,9 @@ at her call, or where an ask arrives.
871
903
  - A send to a private address, unless its ground allows it.
872
904
  - A call on the house beside `House.open`, `door` and `ask`.
873
905
  - A faculty in a house's code. Faculties are the ground's.
874
- - A custom body for a ground's own custody or memory.
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.
875
911
  - A ground an owner must write. Each terrain's ships.
package/COMMAND.md CHANGED
@@ -7,17 +7,21 @@ have read [the package's start](README.md), which runs a first ground.
7
7
 
8
8
  ## Starting a ground
9
9
 
10
- `nervur up` runs a NodeGround on a folder until it is stopped. The folder
11
- holds the ground's recipe, a folder of code for each house, and `state/`,
12
- where the ground keeps its seeds, its record and its hand.
10
+ `nervur up` runs a NodeGround on a folder until it is stopped. The
11
+ folder holds your code: a folder for each house, and each module or
12
+ program your entries name. `state/` holds the ground's key, its ledger
13
+ and its hand. The key is drawn on the first start, into a file your user
14
+ alone reads.
13
15
 
14
16
  ```bash
15
17
  npx nervur up .
16
18
  ```
17
19
 
18
20
  The ground stops cleanly on an interrupt and on `SIGTERM`. It takes a
19
- lock on its folder, so a second `nervur up` on the same folder refuses to
20
- start.
21
+ lock on its state, so a second `nervur up` on the same folder refuses to
22
+ start. Keep `state/` as you keep an ssh key: whoever holds its key and
23
+ its ledger holds the ground, and without the key the ledger opens
24
+ nothing.
21
25
 
22
26
  `nervur service` prints what keeps the ground running across reboots: a
23
27
  systemd unit on Linux, a launchd job on macOS. Nothing is installed; you
@@ -40,7 +44,7 @@ A ground reads its settings from the environment.
40
44
  | `NERVUR_ADDRESSES` | the public addresses written into invitations, by commas |
41
45
  | `NERVUR_ORIGINS` | the page origins the web listener answers, by commas |
42
46
  | `NERVUR_ALLOW_PRIVATE` | `1` to let the ground dial a private or loopback address |
43
- | `NERVUR_KEYCHAIN` | on macOS, a keychain service that keeps the seeds in place of files |
47
+ | `NERVUR_UNLOCK` | on macOS, `keychain:<account>` keeps the key in the keychain in place of `state/key` |
44
48
  | `NERVUR_HAND` | where the hand's socket is, `state/hand` where unset; a relative path is read from where the command runs |
45
49
  | `NERVUR_WAIT` | the longest any ask may run, in milliseconds |
46
50
 
@@ -58,18 +62,24 @@ Every other word goes to the ground's hand, a socket in `state/` that
58
62
  your user alone may open. Run the command in the ground's folder, or
59
63
  name the socket with `--at <socket>` or `NERVUR_HAND`.
60
64
 
61
- `help` prints what the ground holds: each faculty with its methods, and
62
- each house.
65
+ `help` prints what the ground holds: each faculty with its methods, or
66
+ why it is down, and each house.
63
67
 
64
68
  ```bash
65
69
  npx nervur help
66
70
  ```
67
71
 
68
72
  A faculty is called by its name and a method. Named alone, it prints its
69
- methods. The ground's own `houses` faculty adds, removes and lists houses.
73
+ methods. Four faculties are the ground's own. `secrets` keeps a secret
74
+ in the ground's sealed drawer. `faculties` raises a faculty by the `up`
75
+ its entry names, and `houses` opens a house on its entry. Each takes
76
+ `update` to land a new entry in one write, and `faculties restart`
77
+ takes a body down and up again.
70
78
 
71
79
  ```bash
72
- npx nervur houses add name=main memory='{"body":"ledger"}' classes='{"body":"folder","at":"house"}'
80
+ npx nervur faculties add name=recipe make=module args='{"at":"recipe.ts"}'
81
+ npx nervur faculties add name=payments from=recipe make=payments secrets='["stripe-key"]'
82
+ npx nervur houses add name=main classes='{"faculty":"folder","at":"house"}' faculties='["payments"]'
73
83
  ```
74
84
 
75
85
  `ask` asks a being of a house, as the house's owner. `--id <being>` names
@@ -101,6 +111,13 @@ JSON where it reads as JSON, and as text where it does not, so
101
111
  npx nervur ask main hello '{"name":"Ada"}'
102
112
  ```
103
113
 
114
+ A lone `-` reads the one JSON object from standard input instead. So a
115
+ secret never stands on a command line or in a shell's history.
116
+
117
+ ```bash
118
+ npx nervur secrets set - < stripe-key.json
119
+ ```
120
+
104
121
  Each word prints one JSON value, the answer, and its exit code says what
105
122
  came back.
106
123
 
package/FACES.md CHANGED
@@ -106,11 +106,12 @@ tries again acts once.
106
106
 
107
107
  ```ts
108
108
  // api.ts
109
- import type { Faculty, FacultyContext, Handler } from 'nervur';
109
+ import type { Body, FacultyContext, Handler } from 'nervur';
110
110
  import { need, s, type Json } from 'nervur/being';
111
111
 
112
112
  type Answer = Awaited<ReturnType<FacultyContext['call']>>;
113
113
  type Entry = { method: string; description?: string; args?: Json; hints?: Json };
114
+ type Calls = Pick<FacultyContext, 'call' | 'describe'>;
114
115
 
115
116
  /** What the steward arms the face with. */
116
117
  export const FaceBlueprint = need('face', {
@@ -124,7 +125,7 @@ export const FaceBlueprint = need('face', {
124
125
  * asks to an agent as tools.
125
126
  */
126
127
  export class Api {
127
- #context: FacultyContext | undefined;
128
+ #context: Calls | undefined;
128
129
  #signup = '';
129
130
  #calls = 0;
130
131
 
@@ -134,7 +135,12 @@ export class Api {
134
135
  return Promise.resolve({ result: null });
135
136
  }
136
137
 
137
- #held(): FacultyContext {
138
+ // Told its house opened: every door's token answers from the first request, after any restart.
139
+ opened(context: Calls): void {
140
+ this.#context = context;
141
+ }
142
+
143
+ #held(): Calls {
138
144
  if (this.#context === undefined) throw new Error('the face is not armed');
139
145
  return this.#context;
140
146
  }
@@ -179,8 +185,8 @@ export class Api {
179
185
  };
180
186
  }
181
187
 
182
- /** The offer a ground hands: the blueprint, the object, and its handler on the ground's listener. */
183
- export const apiOffer = (api: Api): Faculty => ({ blueprint: FaceBlueprint, object: api, handler: api.handler });
188
+ /** The face as a body. */
189
+ export const apiOffer = (api: Api): Body => ({ blueprint: FaceBlueprint, object: api, handler: api.handler, opened: (context) => api.opened(context) });
184
190
  ```
185
191
 
186
192
  **The describe maps onto tools by renaming.** An ask is a tool, its
@@ -188,9 +194,11 @@ method the tool's name, its args schema the tool's input schema, and its
188
194
  hints the tool's annotations. A moved describe is a changed list of
189
195
  tools.
190
196
 
191
- ## The recipe
197
+ ## The registry
192
198
 
193
- The ground makes the face from its recipe, as it makes any faculty. The
199
+ The ground raises the face from a registry by its `up`, as it raises any
200
+ faculty. A NodeGround stands the module below with the faculty
201
+ `module`, and the face from it by an entry that names `from`. The
194
202
  house's entry names `face` among its faculties, and the steward's `arm`
195
203
  hands the face its signup.
196
204
 
@@ -198,15 +206,23 @@ hands the face its signup.
198
206
  // recipe.ts
199
207
  import { Api, apiOffer } from './api.ts';
200
208
 
201
- export const faculties = () => ({
202
- face: apiOffer(new Api()),
203
- });
209
+ // A registry: the ground raises the face by its `up` when an entry names it.
210
+ export const faculties = {
211
+ face: { up: () => apiOffer(new Api()) },
212
+ };
213
+ ```
214
+
215
+ ```bash
216
+ npx nervur faculties add name=recipe make=module args='{"at":"recipe.ts"}'
217
+ npx nervur faculties add name=face from=recipe make=face
204
218
  ```
205
219
 
206
220
  ## Testing it
207
221
 
208
222
  A BenchGround answers a request with its faculties' handlers in turn, as
209
- a ground's one listener does, so the face is tested with no socket.
223
+ a ground's one listener does, so the face is tested with no socket. The
224
+ test hands the bench the registry, and stands the face through the
225
+ ground's hand, as an owner does.
210
226
 
211
227
  ```ts
212
228
  // api.test.ts
@@ -217,8 +233,9 @@ import * as desk from './desk.ts';
217
233
  import { faculties } from './recipe.ts';
218
234
 
219
235
  test('A person signs up at the face, and reaches their member as an API and as tools', async (t) => {
220
- const ground = await BenchGround.open({ network: new FakeNetwork(), host: 'desk', modules: { desk }, recipe: faculties });
236
+ const ground = await BenchGround.open({ network: new FakeNetwork(), host: 'desk', modules: { desk }, registry: { faculties } });
221
237
  t.after(() => ground.down());
238
+ await ground.hand({ faculty: 'faculties', method: 'add', args: { name: 'face', make: 'face' } });
222
239
  await ground.add('desk', 'desk', { faculties: ['face'] });
223
240
  await ground.ask({ house: 'desk', method: 'arm' });
224
241
  const web = async (path: string, body?: unknown, token?: string) =>