nervur 0.15.0 → 0.17.0

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 (116) hide show
  1. package/GETTING_STARTED.md +138 -0
  2. package/LICENSE +2 -1
  3. package/NOTICE +6 -0
  4. package/README.md +119 -151
  5. package/dist/being/being.d.ts +24 -0
  6. package/dist/being/being.js +109 -0
  7. package/dist/being/digest.d.ts +3 -0
  8. package/dist/being/digest.js +37 -0
  9. package/dist/being/index.d.ts +5 -0
  10. package/dist/being/index.js +6 -0
  11. package/dist/being/silence.d.ts +12 -0
  12. package/dist/being/silence.js +41 -0
  13. package/dist/being/types.d.ts +89 -0
  14. package/dist/being/types.js +58 -0
  15. package/dist/conformance/assert.d.ts +11 -0
  16. package/dist/conformance/assert.js +106 -0
  17. package/dist/conformance/beings.d.ts +198 -0
  18. package/dist/conformance/beings.js +183 -0
  19. package/dist/conformance/estate.d.ts +5 -0
  20. package/dist/conformance/estate.js +388 -0
  21. package/dist/conformance/index.d.ts +81 -0
  22. package/dist/conformance/index.js +816 -0
  23. package/dist/conformance/reach.d.ts +10 -0
  24. package/dist/conformance/reach.js +72 -0
  25. package/dist/conformance/store.d.ts +5 -0
  26. package/dist/conformance/store.js +123 -0
  27. package/dist/harbor/core.d.ts +53 -0
  28. package/dist/harbor/core.js +621 -0
  29. package/dist/harbor/dial.d.ts +9 -0
  30. package/dist/harbor/dial.js +81 -0
  31. package/dist/harbor/index.d.ts +8 -0
  32. package/dist/harbor/index.js +16 -0
  33. package/dist/harbor/memory.d.ts +25 -0
  34. package/dist/harbor/memory.js +72 -0
  35. package/dist/harbor/reach.d.ts +36 -0
  36. package/dist/harbor/reach.js +191 -0
  37. package/dist/harbor/store.d.ts +40 -0
  38. package/dist/harbor/store.js +70 -0
  39. package/dist/vector/cases.d.ts +41 -0
  40. package/dist/vector/cases.js +195 -0
  41. package/dist/vector/index.d.ts +6 -0
  42. package/dist/vector/index.js +8 -0
  43. package/dist/vector/stand.d.ts +9 -0
  44. package/dist/vector/stand.js +76 -0
  45. package/dist/vector/world.d.ts +143 -0
  46. package/dist/vector/world.js +198 -0
  47. package/dist/ward/allowance.d.ts +10 -0
  48. package/dist/ward/allowance.js +71 -0
  49. package/dist/ward/arithmetic.d.ts +31 -0
  50. package/dist/ward/arithmetic.js +245 -0
  51. package/dist/ward/cells.d.ts +7 -0
  52. package/dist/ward/cells.js +186 -0
  53. package/dist/ward/door.d.ts +18 -0
  54. package/dist/ward/door.js +186 -0
  55. package/dist/ward/ground.d.ts +23 -0
  56. package/dist/ward/ground.js +38 -0
  57. package/dist/ward/heirs.d.ts +13 -0
  58. package/dist/ward/heirs.js +115 -0
  59. package/dist/ward/index.d.ts +9 -0
  60. package/dist/ward/index.js +13 -0
  61. package/dist/ward/owner.d.ts +13 -0
  62. package/dist/ward/owner.js +220 -0
  63. package/dist/ward/partition.d.ts +65 -0
  64. package/dist/ward/partition.js +295 -0
  65. package/dist/ward/seal.d.ts +52 -0
  66. package/dist/ward/seal.js +145 -0
  67. package/dist/ward/stance.d.ts +25 -0
  68. package/dist/ward/stance.js +413 -0
  69. package/dist/ward/ward.d.ts +12 -0
  70. package/dist/ward/ward.js +361 -0
  71. package/package.json +51 -11
  72. package/protocol/SPEC.md +1843 -0
  73. package/protocol/vectors/arithmetic.json +117 -0
  74. package/protocol/vectors/door.json +345 -0
  75. package/protocol/vectors/framing.json +92 -0
  76. package/protocol/vectors/wire.json +48 -0
  77. package/quo-kit.md +652 -0
  78. package/src/being/being.ts +123 -0
  79. package/src/being/digest.ts +46 -0
  80. package/src/being/index.ts +7 -0
  81. package/src/being/silence.ts +46 -0
  82. package/src/being/types.ts +170 -0
  83. package/src/conformance/assert.ts +100 -0
  84. package/src/conformance/beings.ts +183 -0
  85. package/src/conformance/estate.ts +412 -0
  86. package/src/conformance/index.ts +962 -0
  87. package/src/conformance/reach.ts +83 -0
  88. package/src/conformance/store.ts +136 -0
  89. package/src/harbor/core.ts +660 -0
  90. package/src/harbor/dial.ts +112 -0
  91. package/src/harbor/index.ts +17 -0
  92. package/src/harbor/memory.ts +83 -0
  93. package/src/harbor/reach.ts +215 -0
  94. package/src/harbor/store.ts +101 -0
  95. package/src/vector/cases.ts +229 -0
  96. package/src/vector/index.ts +11 -0
  97. package/src/vector/stand.ts +75 -0
  98. package/src/vector/world.ts +220 -0
  99. package/src/ward/allowance.ts +85 -0
  100. package/src/ward/arithmetic.ts +247 -0
  101. package/src/ward/cells.ts +190 -0
  102. package/src/ward/door.ts +186 -0
  103. package/src/ward/ground.ts +160 -0
  104. package/src/ward/heirs.ts +114 -0
  105. package/src/ward/index.ts +17 -0
  106. package/src/ward/owner.ts +214 -0
  107. package/src/ward/partition.ts +353 -0
  108. package/src/ward/seal.ts +174 -0
  109. package/src/ward/stance.ts +433 -0
  110. package/src/ward/ward.ts +378 -0
  111. package/src/being.js +0 -125
  112. package/src/contract.js +0 -161
  113. package/src/ground.js +0 -273
  114. package/src/index.js +0 -5
  115. package/src/program.js +0 -74
  116. package/src/projection.js +0 -102
@@ -0,0 +1,138 @@
1
+ # Getting started
2
+
3
+ Quo lets an object ask another object and get an answer, without knowing
4
+ whether that other object is in the same process, on the same device, or
5
+ on another planet. Three words: a harbor boots wards, a ward keeps beings
6
+ and judges its door, and a being is one ordinary object with one voice.
7
+ This is the shortest road from nothing to each of the three, for a
8
+ stranger with a terminal. `SPEC.md` is the truth behind every sentence
9
+ here and assumes nothing; `quo-dock.md` is the dock, the part a box runs.
10
+
11
+ Quo is the protocol and Nervur is this kit of it. Every name you install
12
+ and every command you type is Nervur's; `quo` stays on the wire, as the
13
+ `quo.` route a ward's door answers at.
14
+
15
+ Two packages, and you start with the one that fits what you have:
16
+
17
+ - `nervur`, the library. A harbor, a ward and a being in one
18
+ process, no wire, no files. For a program that wants Quo inside it.
19
+ - `@nervur-org/dock`, the dock. A daemon and one command, `nervur`, that
20
+ stand a box up: a person's world with a page, a model's side, an api, a
21
+ door on the wire. For a box that receives people and models.
22
+
23
+ The command is in the dock and not in the library, so `npm install nervur`
24
+ gives you something to import and no command, and the command arrives with
25
+ `npm install -g @nervur-org/dock`. That is how a kit of this shape is
26
+ always named: one unscoped headline name for the library, the parts under
27
+ the scope, and the CLI in the package that owns it.
28
+
29
+ ## A being, in one process
30
+
31
+ ```bash
32
+ npm install nervur
33
+ ```
34
+
35
+ A being is a class with `asks`, the methods anyone may reach, each with
36
+ the JSON schema of its input. Everything else on the class is hers alone.
37
+
38
+ ```js
39
+ import { Being } from 'nervur';
40
+ import { Ward } from 'nervur/ward';
41
+ import { MemoryHarbor } from 'nervur/harbor';
42
+
43
+ class Shop extends Being {
44
+ static asks = { price: { input: { type: 'object', properties: { item: { type: 'string' } } } } };
45
+ price({ item }) {
46
+ return { item, eur: 12 };
47
+ }
48
+ }
49
+ class Customer extends Being {
50
+ static asks = {};
51
+ }
52
+
53
+ const harbor = new MemoryHarbor();
54
+ const ward = await harbor.boot('acme', Ward, { Shop, Customer });
55
+ await ward.ask('boot', { key: 'shop', class: 'Shop' });
56
+ await ward.ask('boot', { key: 'ana', class: 'Customer' });
57
+ ```
58
+
59
+ The harbor booted a ward named `acme` with two classes it may make beings
60
+ of, and the ward's owner, the process itself, booted one of each by key.
61
+ The ward's own asks are seven, `boot`, `public`, `invite`, `knock`,
62
+ `remove`, `unboot` and `ask`, and `ward.ask()` with no method is her
63
+ describe: those asks and her `notes`, the ward's `pk` and her beings.
64
+
65
+ Nobody reaches a being she has not invited. An invitation is minted on a
66
+ being for one id, the shop's for `ana`, and the customer knocks with it;
67
+ from then she holds a standing at the shop, under the name she took, and
68
+ the shop holds her as an occupant.
69
+
70
+ ```js
71
+ const invitation = await ward.ask('invite', { being: 'shop', id: 'ana' });
72
+ const ana = harbor.objects.get(harbor.partitions.get('acme').beings.ana);
73
+ await ana.knock(invitation);
74
+ await ana.take('shop', invitation);
75
+ console.log(await ana.standings.shop.ask('price', { item: 'bread' }));
76
+ // { item: 'bread', eur: 12 }
77
+ ```
78
+
79
+ That is the whole protocol: a standing on one side, an occupant on the
80
+ other, an ask that rides the relation and an answer that rides it back.
81
+ The two beings here share a process; the same lines hold when the shop is
82
+ on a box across the sea, because a standing is an address and a key, and
83
+ the harbor owns the wire. `harbor.objects` is the memory harbor's hand for
84
+ a test and a first program; a being on a real box is reached through her
85
+ ward, never held.
86
+
87
+ ## A box
88
+
89
+ ```bash
90
+ npm install @nervur-org/dock
91
+ npx nervur init --dir ~/.nervur --ward acme --user razvan --domain acme.com --default --show
92
+ npx nervur serve --dir ~/.nervur --http 8787
93
+ ```
94
+
95
+ `init` mints the ward `acme`, boots razvan's user being, her doorbell and
96
+ the desk in it, marks it the default ward and shows it at the web route,
97
+ and writes `routes.json`, the four routes of a box, `mcp.`, `web.`, `quo.`
98
+ and `api.` under the domain, for a proxy to map onto the one loopback
99
+ port. Without `--domain`, write it yourself; on a Mac the four are paths
100
+ at one loopback address:
101
+
102
+ ```json
103
+ { "mcp": "http://127.0.0.1:8787/mcp", "web": "http://127.0.0.1:8787/web", "quo": "http://127.0.0.1:8787/quo", "api": "http://127.0.0.1:8787/api" }
104
+ ```
105
+
106
+ `serve` is the daemon, the one process over that folder, and every other
107
+ command is its client. From a second terminal, a phone:
108
+
109
+ ```bash
110
+ npx nervur invite --dir ~/.nervur '{"being":"razvan","id":"phone"}'
111
+ ```
112
+
113
+ The answer carries `link`, the ward's page with the invitation in its
114
+ fragment. Opened on the phone, the tab boots a harbor of its own, joins as
115
+ that device, and is in: no account, nothing typed. What a model gets is
116
+ the same world as tools at `mcp.`, what a program gets is the same asks as
117
+ JSON at `api.`, and what another box gets is the door at `quo.`; a being
118
+ answers each the same, because each is an ask at her door.
119
+
120
+ The box's own doings are rows on the faculties of its dock ward, placed by
121
+ the root with `nervur ask --ward dock`: a socket held to another box, an
122
+ agent run for a ward, a schedule on the clock. A second box owns this one
123
+ across the wire with an invitation on the ward's own pk, knocked with
124
+ from there, and every owner command with `--via` from then on. Each of
125
+ those, the edge, and what an estate stands beyond the init, is the
126
+ "Getting started" chapter of `quo-dock.md`, proven cold on a Mac by
127
+ somebody with nothing else to read.
128
+
129
+ ## Your own beings on a box
130
+
131
+ A box holds the dock's classes and yours. `classes/index.ts` beside the
132
+ wards exports each of yours by name, and `nervur boot '{"key":"shop",
133
+ "class":"Shop"}'` boots one; `nervur init --class Shop` makes the ward's home
134
+ being one of yours, an organisation's ward with the org as its being. A
135
+ being's asks may name who may reach them, `for`, over the record of the
136
+ occupant asking, and her `cells` are what she keeps between boots: the
137
+ whole of what a being is, in `SPEC.md`, and the words above the spec, a
138
+ world, a home, a membership, in `WORLDS.md` and `GLOSSARY.md`.
package/LICENSE CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  Apache License
2
3
  Version 2.0, January 2004
3
4
  http://www.apache.org/licenses/
@@ -178,7 +179,7 @@
178
179
  APPENDIX: How to apply the Apache License to your work.
179
180
 
180
181
  To apply the Apache License to your work, attach the following
181
- boilerplate notice, with the fields enclosed by brackets "{}"
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
183
  replaced with your own identifying information. (Don't include
183
184
  the brackets!) The text should be enclosed in the appropriate
184
185
  comment syntax for the file format. We also recommend that a
package/NOTICE ADDED
@@ -0,0 +1,6 @@
1
+ Nervur
2
+ Copyright 2026 Razvan Gherghina
3
+
4
+ This product includes software developed by Razvan Gherghina.
5
+
6
+ Licensed under the Apache License, Version 2.0. See LICENSE for the terms.
package/README.md CHANGED
@@ -1,152 +1,120 @@
1
- # nervur
2
-
3
- Nervur grown on the working Quo kit. It imports `@quo-systems/js` and adds no
4
- protocol
5
- of its own — no wire, no envelope, no judgment, no arithmetic, ever. What
6
- Nervur is for begins where the constitution stops: the graph, the organs, the
7
- floor, and the projection from an authored contract into Quo blueprints.
8
-
9
- ## The rules of this build
10
-
11
- 1. **`@quo-systems/js` is the whole of the protocol.** It is crossed by its
12
- specifier,
13
- never by a path. A gap the kit shows is reported to the quo table, never
14
- patched here.
15
- 2. **nervur-dream is parked beside this, as reference.** Its mechanics may be
16
- lifted — the compiler, the contract reader, the `@needs` directive. Its
17
- judgement may not: what a thing enforces, refuses or means is authored fresh
18
- against the constitution as it stands today, because dream was built against
19
- a protocol that no longer exists.
20
- 3. **Crockford.** Factory functions, closures for privacy, no `this`, frozen
21
- surfaces. The kit's class idiom stops at its specifier: the ground factory
22
- is the one place a `Warden` instance lives, closed over and never returned.
23
- 4. **One played bench question at a time.** Doubt is settled on the bench, not
24
- argued. Nothing lands without an assert that would fail if it broke.
25
- 5. **A being never learns who is calling, but it knows who may reach it.** The
26
- first half is the kit's doing: the warden places a voice at step three and
27
- hands the field its arguments and its leash, never the caller. Which being
28
- was reached already says who called. The second half is Nervur's: the warden
29
- keeps the record of which voices reach which beings, and a being is handed
30
- its own row to read and to change. No rights and no roles — those are
31
- meanings, and the record holds none.
32
-
33
- ## What stands
34
-
35
- **`ground()`** — one closure over one warden, holding beings and judging what
36
- arrives. `bench/ground.test.js` walks it end to end: a real sealed ask, a real
37
- answer, and silence where a standing does not reach.
38
-
39
- **The outbound relation** — `remember` records an invitation this ground was
40
- handed and names which of its beings spends it; `ask` seals down that relation,
41
- posts it at the hints the invitation carried, and reads the answer back. Every
42
- one of those acts is the kit's. What is Nervur's is the ergonomics: a relation
43
- is found by whose it is, so one being cannot spend another's; the number is the
44
- ground's to pick, so no caller counts by hand; and the first ask carries the
45
- rotation an invitation's heir keys oblige, so nobody meets that trap twice.
46
- `bench/estate.test.js` stands two houses on two real loopback doors and crosses
47
- between them.
48
-
49
- Silence, unreachable and a relation that is not yours are three different
50
- things and stay three: silence is `null`, a road that does not answer throws
51
- out of the carriage, and spending what a being does not hold throws before a
52
- byte moves.
53
-
54
- **`contract(sdl)` and `program(contract, resolvers)`** — the compiler, and
55
- there is exactly one of it. A being and an organ are both a contract plus
56
- resolvers, and `program` cannot tell which it is holding; that is not a
57
- convenience, it is the architecture. A field is read with `hasOwn` and never
58
- through the prototype chain, exactly the declared arguments reach a resolver
59
- and no others, a resolver short of a required argument is not called at all,
60
- and a resolver that throws is answered as silence — nothing here promises that
61
- an ask is fulfilled, so a refusal a caller must tell apart is carried in the
62
- answer type instead. `bench/program.test.js` asserts every one of those, and
63
- asserts the first on a being and an organ side by side.
64
-
65
- **The contract is a GraphQL schema** — schema, resolvers and a context, the
66
- shape every engineer already knows. It is authoring and nothing else: never at
67
- the wire, never an answer, never a describe. The organs a being reaches outward
68
- with are declared in the schema itself, with `schema @needs(organs: [...])`,
69
- and the being's own name is the root type's — `schema { query: Clerk }`.
70
-
71
- **`project(contract)`** — the boundary, and there must be exactly one of it.
72
- Every implementer of Quo speaks the notation and has never heard of Nervur, so
73
- a contract becomes a Quo blueprint in the kit's own notation before anything
74
- holds it, and what a stranger verifies is that blueprint's digest. GraphQL is
75
- nullable by default and the notation is required by default, so `String!`
76
- becomes `text` and `String` becomes `text?`; the schema's other object and
77
- input types become record blocks, and their ordering is the notation's own law
78
- and therefore the kit's `print` to judge. Where GraphQL says something the
79
- notation has no word for — `Float`, `ID`, an enum, a union, an interface, a
80
- record field that takes arguments — the contract is refused at build rather
81
- than guessed at. One word runs the other way: the notation has a field that
82
- answers nothing and GraphQL insists every field has a type, so `Nothing` in
83
- answer position is that field's spelling. `bench/projection.test.js` holds it.
84
-
85
- **`being(sdl, resolvers)`** — a schema and ordinary functions under it. It is
86
- compiled by `program` like anything else; what it adds is the wire skin the kit
87
- leaves open, and the two promises the kit leaves to Nervur: a resolver meets
88
- its arguments as named values and never bytes, and a being answers **exactly**
89
- the fields its contract declares. That second one is the gate the digest's
90
- meaning rests on — the law says what a blueprint does not declare does not
91
- exist, and the kit dispatches on whatever methods the held object happens to
92
- have.
93
-
94
- **Organs** — the only way a being _acts_ outward. That is not a style choice:
95
- a being has no warden and no caller, so it cannot mint standing and a field
96
- answering another being's name hands over a label rather than reach. What it
97
- can still do is pass on standing it was already given — the constitution says
98
- data can carry an `invitation`, and an invitation carries the heir keys — so
99
- "a being cannot hand over reach" is false as a general claim and only the
100
- minting half is true. A being names the organ aliases it needs in its own
101
- schema, the ground supplies them at `hold`, and the resolvers are closed over
102
- exactly those. An organ the schema never asked for is not absent from a check —
103
- it is absent from the object.
104
-
105
- **The organ that crosses** — `errand` mints the reach a being uses to speak to
106
- another house. It is shut over which being spends the relation and which house
107
- it stands at, so a being holds one road on its own behalf and nothing it could
108
- point elsewhere; it never holds the warden and never holds the relation. Which
109
- being at the far house is still the caller's to name, because the standing
110
- decides that and the standing is judged over there. The **leash** is handed in
111
- rather than chosen — the resolver passes on the one the door gave it, whole and
112
- never a number read off it, because what may go onward is this door's dwell
113
- subtracted at the moment of sealing, which is later than any moment the being
114
- could have read. So the remainder reaches the far house without the being
115
- having chosen it, and a being cannot hand its organ more than it received.
116
- `bench/errand.test.js` serves one sealed ask at the near house by crossing to
117
- the far one and answering with what came back.
118
-
119
- **Contacts** — who reaches this being, and the two acts that change it:
120
- `list()`, `admit(keys)` and `remove(voicePk)`. Every one of them goes to the
121
- warden's own inbound record — `grant`, `amend`, `standing` — read live at the
122
- moment it is called and never snapshotted, so a being that admitted someone a
123
- moment ago sees them. Quo keeps that record and refuses to have an opinion
124
- about it, having no word for member, owner or guest; the opinion is Nervur's,
125
- and this is the whole of it. The inverse — voices by being, where the warden
126
- keeps beings by voice — is derived rather than indexed, because a second index
127
- is a second truth.
128
-
129
- The scoping is structural, the same shape `errand` uses: the context is minted
130
- per `hold` with the being shut inside the closure, so there is no address it
131
- could name to reach another being's row. It reaches a resolver as the resolver
132
- factory's second argument, closed over at raise beside the organs, rather than
133
- as a third argument to every resolver. `bench/contacts.test.js` proves the
134
- admission and the removal at the door — a real sealed ask that gets in, and
135
- then meets silence.
136
-
137
- Organs are declared inside the contract, with `schema @needs(organs: [...])`.
138
- That directive is authoring and never reaches the wire: the projection drops
139
- it, so the blueprint a stranger reads carries no trace of what a being reaches
140
- outward with, which is inside-the-ground business the wire never sees.
141
-
142
- ## What is not here yet
143
-
144
- `own` and the floor, memory, standings and the graph walked across them, the
145
- per-call caller, ground-as-a-folder, persistence, register-and-invoke,
146
- collections-as-beings, per-caller beings.
147
-
148
- ## The bench
149
-
150
- ```bash
151
- npm run gate-nervur
1
+ # Nervur
2
+
3
+ Nervur is the first open source kit of Quo, the library itself: harbor, ward
4
+ and being, with the spec and the vectors inside, and no dependencies.
5
+
6
+ Quo is a protocol that lets an object ask another object and get an answer,
7
+ without knowing whether that other object is in the same process, on the same
8
+ device, or on another planet. Quo is the protocol and this is one
9
+ implementation of it; the two names never stand for the same thing.
10
+
11
+ It is three words, two of which are beings, and two edges.
12
+ Nothing else is Quo.
13
+
14
+ - **Harbor.** The program a device runs to boot wards. Owns the wire and the
15
+ operating system. Not a being.
16
+ - **Ward.** One process of its harbor. A being plus ward functions. Keeps
17
+ beings and judges its door.
18
+ - **Being.** One ordinary object, one voice.
19
+
20
+ [`protocol/SPEC.md`](protocol/SPEC.md) is the truth. It is the protocol
21
+ alone: what any ward in any language must do for its bytes to be Quo. It is
22
+ self-contained and assumes nothing from any other document, this README
23
+ included. Read it first.
24
+ [`quo-kit.md`](quo-kit.md) is this kit: what one TypeScript
25
+ implementation chose and another kit may refuse. It assumes the spec, and
26
+ where the two disagree the spec wins.
27
+
28
+ ## This package
29
+
30
+ A TypeScript implementation, written against Node's own type stripping, with
31
+ no dependencies. The package ships JavaScript with declarations beside the
32
+ source it was emitted from, because Node strips types nowhere under
33
+ `node_modules`.
34
+
152
35
  ```
36
+ protocol/ the shelf a kit in any language reads: SPEC.md and the vectors, no code
37
+ protocol/vectors/ fixed inputs and outputs, so another language proves its bytes
38
+ src/being/ the Being side: what a being author imports, if anything
39
+ src/ward/ the ward: the Ground contract, door, seal, arithmetic, heirs, stance, allowance
40
+ src/harbor/ MemoryHarbor, and the harbor core with its store, reach and dialer
41
+ src/conformance/ behaviours any ward must show, written against the truth
42
+ ```
43
+
44
+ The suites live beside the source in the tree this is developed in and are
45
+ not in the package: what proves the kit is not what a consumer installs.
46
+ What does ship of them is `src/conformance/`, which is the part written for
47
+ somebody else's ward rather than for this one.
48
+
49
+ ## Requirements
50
+
51
+ Node 22.18 or later, and nothing else. The package has no dependencies, and
52
+ it ships JavaScript with declarations, so nothing is compiled on the way in.
53
+
54
+ ## Where it is proven
55
+
56
+ The same conformance suite this package exports runs against several worlds:
57
+ one ward, one harbor with a ward per being, two harbors, and the harbor core
58
+ over a memory store. A world declares what it can express and a chapter it
59
+ cannot reach is skipped by name rather than failed, which is how a kit in
60
+ another language reports the same suite honestly.
61
+
62
+ It is run again out of a bundle in a real Chromium, in workerd, in Deno and
63
+ in Bun, because a ward's truth must hold wherever a ward runs.
64
+
65
+ ## Entry points
66
+
67
+ ```js
68
+ import { Being, silence, digest } from 'nervur';
69
+ import { Ward } from 'nervur/ward';
70
+ import { MemoryHarbor, Harbor, request, dial } from 'nervur/harbor';
71
+ import { conform } from 'nervur/conformance';
72
+ import { Stand } from 'nervur/vector';
73
+ ```
74
+
75
+ ## Another language
76
+
77
+ The hand to a kit in another language is one folder, `protocol/`, and it
78
+ holds two things of two kinds. `SPEC.md` is the protocol, and it assumes
79
+ nothing: a kit is written against it and against nothing else here. The
80
+ vectors beside it are the byte-level hand, everything a stranger can
81
+ observe: `protocol/vectors/arithmetic.json`, the primitives the seal rests
82
+ on, SHA-256, Ed25519, X25519, HKDF and AES-256-GCM;
83
+ `protocol/vectors/framing.json`, Quo's own, the ward pk, the digest, the
84
+ signed ask body, the sealed shapes, the invitation and the knock;
85
+ `protocol/vectors/wire.json`, the frames on a socket and the one request a
86
+ door takes; and `protocol/vectors/door.json`, the door's thirteen cases,
87
+ each one an arrival a ward will not answer, with the bytes that arrive, the
88
+ bytes that leave and the partition's digest on both sides of the judgement.
89
+ They import by name,
90
+ `nervur/protocol/vectors/framing.json`, so a kit's own suite can
91
+ read them from the package. A kit reproduces them or it is not this
92
+ protocol. `door.json` is also replayed from outside, by a verifier holding
93
+ no key, against a kit standing in vector mode, which `SPEC.md` describes
94
+ under that name. This kit stands in it with `Stand` from `nervur/vector`,
95
+ which is vector mode and names no runtime: put it behind any listener that
96
+ hands it a request body and returns what it answers.
97
+
98
+ Nothing outside that folder is the protocol. `src/` is this kit's
99
+ interpretation, and `src/conformance/` is a checklist a kit ports rather
100
+ than a harness it runs: it imports the base class, the silence spelling and
101
+ this kit's store and reach, so the beings it is shown with run only in a
102
+ TypeScript ward. `quo-kit.md` "The three shelves" says which is which.
103
+
104
+ ## Versions
105
+
106
+ No compatibility promise before 1.0.0: the words may still move, and there
107
+ is no migration to write because there is nothing yet to migrate from. This
108
+ package carries the version of its own work and is bound to no other's, so a
109
+ number equal to `@nervur-org/dock`'s or `@nervur-org/ui`'s is a coincidence.
110
+
111
+ What ships is the emitted `dist/`, the source it came from, the protocol
112
+ shelf with the spec and the vectors inside it, this file, the licence and
113
+ the notice. No tests and no configs. The spec ships because it is the truth
114
+ the source and the vectors are read against, and a package a stranger reads
115
+ with no truth beside it is a pile of names.
116
+
117
+ ## License
118
+
119
+ Apache-2.0. Copyright 2026 Razvan Gherghina. See [LICENSE](LICENSE) and
120
+ [NOTICE](NOTICE); every source file carries an SPDX line.
@@ -0,0 +1,24 @@
1
+ import type { Asker, Blueprint, Cells, Invitation, JsonObject, Wanted, OccupantRecord, Occupants, Reply, Schema, Stance, Standings, Answer } from './types.ts';
2
+ export type AskSpec = {
3
+ description?: string;
4
+ input?: Schema;
5
+ output?: Schema;
6
+ for?: (occupant: OccupantRecord | undefined, asker: Asker) => boolean;
7
+ };
8
+ export declare class Being {
9
+ static cells: JsonObject;
10
+ static asks: Record<string, AskSpec>;
11
+ readonly stance: Stance;
12
+ constructor(stance: Stance);
13
+ get cells(): Cells;
14
+ get standings(): Standings;
15
+ get occupants(): Occupants;
16
+ lend(name: string, id: string): Promise<string | null>;
17
+ invite(id: string, notes?: JsonObject): Promise<Invitation | null>;
18
+ knock(invitation: Invitation, method?: string, args?: JsonObject, wanted?: Wanted): Promise<Answer>;
19
+ take(id: string, invitation: Invitation): Promise<string | null>;
20
+ boot(className: string, key: string, id?: string): Promise<string | null>;
21
+ occupant(asker: Asker): OccupantRecord | undefined;
22
+ describe(asker: Asker): Blueprint;
23
+ answer(asker: Asker, method?: string, args?: JsonObject): Promise<Reply>;
24
+ }
@@ -0,0 +1,109 @@
1
+ // Names a subclass may not use for an ask, because they are the base's own.
2
+ const RESERVED = new Set(['answer', 'describe', 'stance', 'cells', 'standings', 'occupants', 'occupant', 'invite', 'knock', 'take', 'boot', 'lend', 'constructor']);
3
+ // Whether she has a method of that name, written on her own prototype chain
4
+ // below Object's. A name Object lends every object, `hasOwnProperty` or
5
+ // `toString`, is not a method she wrote; and a field she assigns in her own
6
+ // constructor is not there yet when the base checks, so an ask is a method
7
+ // on the prototype and nothing else.
8
+ const wrote = (self, name) => {
9
+ for (let p = Object.getPrototypeOf(self); p !== null && p !== Object.prototype; p = Object.getPrototypeOf(p)) {
10
+ if (Object.hasOwn(p, name))
11
+ return typeof p[name] === 'function';
12
+ }
13
+ return false;
14
+ };
15
+ // Both statics below are read off the class the object was made from, so a
16
+ // subclass declaring either replaces its parent's rather than adding to it.
17
+ // That is the rule: her blueprint is exactly what the class in front of you
18
+ // declares, in the order she chose, and merging down a chain would hand her
19
+ // asks she may mean to drop and an order she did not write. A subclass that
20
+ // means to extend says so, `static override asks = { ...Parent.asks, mine: {} }`.
21
+ export class Being {
22
+ // Her cells' defaults. Merged in at birth, only where a key is missing, so
23
+ // a restart keeps what she wrote.
24
+ static cells = {};
25
+ // What she can be asked. Declaration order is blueprint order, except a
26
+ // name that reads as an array index, which the language lists first.
27
+ static asks = {};
28
+ stance;
29
+ constructor(stance) {
30
+ this.stance = stance;
31
+ const C = this.constructor;
32
+ for (const name of Object.keys(C.asks)) {
33
+ if (RESERVED.has(name))
34
+ throw new Error(`ask '${name}' is a reserved name`);
35
+ if (!wrote(this, name))
36
+ throw new Error(`ask '${name}' has no method on the prototype`);
37
+ }
38
+ // Own keys only: a default named after a member of Object's prototype is
39
+ // still hers, and still missing until she writes it.
40
+ for (const [k, v] of Object.entries(C.cells))
41
+ if (!Object.hasOwn(stance.cells, k))
42
+ stance.cells[k] = structuredClone(v);
43
+ }
44
+ get cells() {
45
+ return this.stance.cells;
46
+ }
47
+ get standings() {
48
+ return this.stance.standings;
49
+ }
50
+ get occupants() {
51
+ return this.stance.occupants;
52
+ }
53
+ // A standing at one of the things this device can do, under an id of hers.
54
+ // The ward knocks and takes it for her; the invitation never reaches her.
55
+ lend(name, id) {
56
+ return this.stance.lend(name, id);
57
+ }
58
+ invite(id, notes) {
59
+ return this.stance.occupants.invite(id, notes);
60
+ }
61
+ knock(invitation, method, args, wanted) {
62
+ return this.stance.standings.knock(invitation, method, args, wanted);
63
+ }
64
+ take(id, invitation) {
65
+ return this.stance.standings.take(id, invitation);
66
+ }
67
+ // A new being of her ward, by class name, under a key she chooses. With an
68
+ // id, she holds a standing to the being she made, who knows her by her key.
69
+ boot(className, key, id) {
70
+ return this.stance.boot(className, key, id);
71
+ }
72
+ // The occupant record for whoever is at the door. Undefined at a public being.
73
+ occupant(asker) {
74
+ return asker.id !== undefined && Object.hasOwn(this.cells.occupants, asker.id) ? this.cells.occupants[asker.id] : undefined;
75
+ }
76
+ // Her blueprint for this asker. Override to shape it by hand.
77
+ describe(asker) {
78
+ const C = this.constructor;
79
+ const rec = this.occupant(asker);
80
+ const asks = [];
81
+ for (const [name, spec] of Object.entries(C.asks)) {
82
+ if (spec.for && !spec.for(rec, asker))
83
+ continue;
84
+ const ask = { name, input: spec.input ?? { type: 'object' } };
85
+ if (spec.description !== undefined)
86
+ ask.description = spec.description;
87
+ if (spec.output !== undefined)
88
+ ask.output = spec.output;
89
+ asks.push(ask);
90
+ }
91
+ return { asks, notes: {} };
92
+ }
93
+ // The one function. Override to wrap it; call super to keep the dispatch.
94
+ async answer(asker, method, args = {}) {
95
+ if (method === undefined)
96
+ return this.describe(asker);
97
+ const C = this.constructor;
98
+ // Declared, by her, on purpose. `asks` is an ordinary object, so a bare
99
+ // lookup would also find every name on Object's prototype: `valueOf`
100
+ // would answer with her stance, `toString` with a string, and neither is
101
+ // an ask she wrote. Only her own keys are asks, which is what describe
102
+ // shows. What she shows is what she can be asked.
103
+ const spec = Object.hasOwn(C.asks, method) ? C.asks[method] : undefined;
104
+ if (!spec || (spec.for && !spec.for(this.occupant(asker), asker)))
105
+ return { error: 'unknown ask' };
106
+ const fn = this[method];
107
+ return fn.call(this, args ?? {}, asker);
108
+ }
109
+ }
@@ -0,0 +1,3 @@
1
+ import type { Json } from './types.ts';
2
+ export declare const canonical: (v: Json) => string;
3
+ export declare const digest: (blueprint: Json) => Promise<string>;
@@ -0,0 +1,37 @@
1
+ // A value I-JSON has no room for: a key left empty, a function, a symbol.
2
+ // None of them cross an edge, so none of them may reach a digest. A key
3
+ // carrying one is dropped and a slot carrying one is null, which is what
4
+ // crossing does to them, so the digest names what arrived and not what she
5
+ // happened to be holding.
6
+ const absent = (v) => v === undefined || typeof v === 'function' || typeof v === 'symbol';
7
+ // JCS for I-JSON values: sorted keys, no whitespace, JSON escaping. Numbers
8
+ // are serialized as ES does, which is what RFC 8785 specifies. A hole in a
9
+ // list is null, as JSON writes it.
10
+ export const canonical = (v) => {
11
+ if (Array.isArray(v))
12
+ return `[${Array.from(v, (slot) => (absent(slot) ? 'null' : canonical(slot))).join(',')}]`;
13
+ if (v !== null && typeof v === 'object') {
14
+ return `{${Object.keys(v)
15
+ .filter((k) => !absent(v[k]))
16
+ .sort()
17
+ .map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`)
18
+ .join(',')}}`;
19
+ }
20
+ return JSON.stringify(v);
21
+ };
22
+ // Hex and the guard are spelled here and again in `src/ward/arithmetic.ts`,
23
+ // which is the price of the boundary: nothing under `src/being` imports
24
+ // anything above it, because this is the whole world a being's own code sees
25
+ // and a being reaching the ward is the thing the shape is against.
26
+ const hex = (bytes) => Array.from(new Uint8Array(bytes), (b) => b.toString(16).padStart(2, '0')).join('');
27
+ // `crypto.subtle` is read at the call and never captured at load. A browser
28
+ // on a plain http:// origin has `crypto` without `subtle`, and a terrain may
29
+ // install one after this module is first imported; either way the failure is
30
+ // one sentence and not a TypeError from inside a digest nobody can read.
31
+ const subtle = () => {
32
+ const s = globalThis.crypto?.subtle;
33
+ if (!s)
34
+ throw new Error('this terrain has no crypto.subtle: a being needs a secure context to be described');
35
+ return s;
36
+ };
37
+ export const digest = async (blueprint) => hex(await subtle().digest('SHA-256', new TextEncoder().encode(canonical(blueprint))));
@@ -0,0 +1,5 @@
1
+ export { Being, type AskSpec } from './being.ts';
2
+ export { silence, isSilence, answered, unreached, isUnreached, word, isWord, wordOf, told, DOOR_WORDS, isDoorWord } from './silence.ts';
3
+ export { digest, canonical } from './digest.ts';
4
+ export { OWNER, PUBLIC, RESERVED_IDS, isBlueprint, invitationArgs, isInvitation } from './types.ts';
5
+ export type * from './types.ts';
@@ -0,0 +1,6 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // nervur — the Being side. What a being author imports, if anything.
3
+ export { Being } from './being.js';
4
+ export { silence, isSilence, answered, unreached, isUnreached, word, isWord, wordOf, told, DOOR_WORDS, isDoorWord } from './silence.js';
5
+ export { digest, canonical } from './digest.js';
6
+ export { OWNER, PUBLIC, RESERVED_IDS, isBlueprint, invitationArgs, isInvitation } from './types.js';
@@ -0,0 +1,12 @@
1
+ import { type DoorWord, type Unreached, type Word, type WordName } from './types.ts';
2
+ export declare const silence: unique symbol;
3
+ export declare const isSilence: (x: unknown) => x is typeof silence;
4
+ export declare const DOOR_WORDS: readonly DoorWord[];
5
+ export declare const isDoorWord: (s: unknown) => s is DoorWord;
6
+ export declare const word: <W extends WordName>(name: W) => Word<W>;
7
+ export declare const isWord: (x: unknown) => x is Word;
8
+ export declare const wordOf: (x: Word) => WordName;
9
+ export declare const answered: <T>(x: T) => x is Exclude<T, typeof silence | Word>;
10
+ export declare const told: (x: unknown) => unknown;
11
+ export declare const unreached: () => Unreached;
12
+ export declare const isUnreached: (x: unknown) => x is Unreached;