@nervur-org/dock 0.5.0 → 0.6.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 (216) hide show
  1. package/README.md +7 -4
  2. package/api/route.ts +4 -72
  3. package/beings/avatar.ts +11 -7
  4. package/beings/carry.ts +129 -116
  5. package/beings/clock.ts +11 -7
  6. package/beings/desk.ts +7 -6
  7. package/beings/doorbell.ts +36 -7
  8. package/beings/index.ts +17 -4
  9. package/beings/join.ts +7 -4
  10. package/beings/link.ts +1 -1
  11. package/beings/porter.ts +5 -1
  12. package/beings/presence.ts +15 -5
  13. package/beings/setup.ts +27 -21
  14. package/beings/user.ts +26 -17
  15. package/beings/way.ts +1 -1
  16. package/{dock → box}/agents.ts +37 -44
  17. package/{dock → box}/apns.ts +4 -4
  18. package/box/box.ts +31 -0
  19. package/box/contract.ts +41 -0
  20. package/box/dialers.ts +88 -0
  21. package/box/dock.ts +148 -0
  22. package/box/envoy.ts +96 -0
  23. package/box/faculty.ts +111 -0
  24. package/{dock → box}/fcm.ts +1 -1
  25. package/box/grants.ts +90 -0
  26. package/box/http.ts +93 -0
  27. package/{dock → box}/http2.ts +7 -7
  28. package/box/index.ts +201 -0
  29. package/box/join.ts +78 -0
  30. package/box/local.ts +37 -0
  31. package/{dock → box}/location.ts +15 -3
  32. package/{dock → box}/notifications.ts +17 -5
  33. package/box/push.ts +85 -0
  34. package/box/ring.ts +85 -0
  35. package/{dock → box}/service.ts +2 -2
  36. package/box/timer.ts +150 -0
  37. package/box/tree.ts +22 -0
  38. package/{dock → box}/webpush.ts +2 -2
  39. package/cli/client.ts +1 -1
  40. package/cli/daemon.ts +101 -103
  41. package/cli/estate.ts +2 -3
  42. package/cli/nervur.ts +44 -25
  43. package/cli/stand.ts +1 -1
  44. package/dist/api/route.d.ts +1 -2
  45. package/dist/api/route.js +4 -76
  46. package/dist/beings/avatar.d.ts +4 -3
  47. package/dist/beings/avatar.js +9 -6
  48. package/dist/beings/carry.d.ts +10 -12
  49. package/dist/beings/carry.js +119 -127
  50. package/dist/beings/clock.js +10 -7
  51. package/dist/beings/desk.js +8 -7
  52. package/dist/beings/doorbell.d.ts +3 -1
  53. package/dist/beings/doorbell.js +37 -4
  54. package/dist/beings/index.d.ts +24 -4
  55. package/dist/beings/index.js +15 -4
  56. package/dist/beings/join.d.ts +1 -0
  57. package/dist/beings/join.js +6 -4
  58. package/dist/beings/link.d.ts +0 -1
  59. package/dist/beings/link.js +1 -1
  60. package/dist/beings/porter.js +5 -1
  61. package/dist/beings/presence.d.ts +1 -0
  62. package/dist/beings/presence.js +12 -3
  63. package/dist/beings/setup.d.ts +5 -2
  64. package/dist/beings/setup.js +16 -15
  65. package/dist/beings/user.d.ts +4 -3
  66. package/dist/beings/user.js +18 -11
  67. package/dist/{dock → box}/agents.d.ts +7 -4
  68. package/dist/{dock → box}/agents.js +30 -37
  69. package/dist/{dock → box}/apns.d.ts +1 -1
  70. package/dist/{dock → box}/apns.js +3 -3
  71. package/dist/box/box.d.ts +9 -0
  72. package/dist/box/box.js +18 -0
  73. package/dist/box/contract.d.ts +6 -0
  74. package/dist/box/contract.js +35 -0
  75. package/dist/{dock → box}/dialers.d.ts +7 -4
  76. package/dist/box/dialers.js +76 -0
  77. package/dist/box/dock.d.ts +182 -0
  78. package/dist/box/dock.js +128 -0
  79. package/dist/box/envoy.d.ts +44 -0
  80. package/dist/box/envoy.js +75 -0
  81. package/dist/box/faculty.d.ts +68 -0
  82. package/dist/box/faculty.js +97 -0
  83. package/dist/{dock → box}/fcm.d.ts +1 -1
  84. package/dist/{dock → box}/grants.d.ts +4 -3
  85. package/dist/box/grants.js +78 -0
  86. package/dist/box/http.d.ts +26 -0
  87. package/dist/box/http.js +89 -0
  88. package/dist/{dock → box}/http2.d.ts +1 -1
  89. package/dist/{dock → box}/http2.js +6 -6
  90. package/dist/box/index.d.ts +46 -0
  91. package/dist/box/index.js +179 -0
  92. package/dist/box/join.d.ts +19 -0
  93. package/dist/box/join.js +67 -0
  94. package/dist/{dock → box}/local.d.ts +3 -2
  95. package/dist/box/local.js +27 -0
  96. package/dist/{dock → box}/location.d.ts +3 -0
  97. package/dist/box/location.js +21 -0
  98. package/dist/{dock → box}/notifications.d.ts +3 -0
  99. package/dist/{dock → box}/notifications.js +14 -4
  100. package/dist/{dock → box}/push.d.ts +12 -18
  101. package/dist/box/push.js +83 -0
  102. package/dist/box/ring.d.ts +18 -0
  103. package/dist/box/ring.js +55 -0
  104. package/dist/{dock → box}/service.d.ts +1 -1
  105. package/dist/{dock → box}/service.js +1 -1
  106. package/dist/{dock → box}/timer.d.ts +2 -0
  107. package/dist/box/timer.js +127 -0
  108. package/dist/box/tree.d.ts +5 -0
  109. package/dist/box/tree.js +14 -0
  110. package/dist/{dock → box}/webpush.d.ts +1 -1
  111. package/dist/cli/client.d.ts +1 -0
  112. package/dist/cli/daemon.d.ts +2 -8
  113. package/dist/cli/daemon.js +102 -107
  114. package/dist/cli/estate.js +2 -3
  115. package/dist/cli/nervur.js +44 -24
  116. package/dist/cli/stand.d.ts +0 -14
  117. package/dist/cli/stand.js +1 -1
  118. package/dist/harbor/browser.d.ts +8 -5
  119. package/dist/harbor/browser.js +20 -21
  120. package/dist/harbor/capacitor.d.ts +3 -3
  121. package/dist/harbor/capacitor.js +9 -8
  122. package/dist/harbor/disk.d.ts +9 -9
  123. package/dist/harbor/disk.js +56 -51
  124. package/dist/harbor/edge/alarm.d.ts +4 -2
  125. package/dist/harbor/edge/alarm.js +9 -19
  126. package/dist/harbor/edge/edge.d.ts +7 -6
  127. package/dist/harbor/edge/edge.js +58 -53
  128. package/dist/harbor/edge/exercise.js +4 -4
  129. package/dist/harbor/edge/given.d.ts +0 -1
  130. package/dist/harbor/edge/given.js +0 -29
  131. package/dist/harbor/edge/storage.d.ts +0 -2
  132. package/dist/harbor/edge/storage.js +2 -2
  133. package/dist/harbor/http.js +4 -2
  134. package/dist/harbor/sealed.d.ts +0 -5
  135. package/dist/harbor/sealed.js +0 -39
  136. package/dist/harbor/tauri.d.ts +2 -2
  137. package/dist/harbor/tauri.js +13 -10
  138. package/dist/human/app.d.ts +1 -1
  139. package/dist/human/app.js +10 -12
  140. package/dist/human/door.d.ts +0 -1
  141. package/dist/human/door.js +1 -1
  142. package/dist/human/html.js +1 -1
  143. package/dist/human/move.js +1 -1
  144. package/dist/human/tab.bundle.txt +3629 -2824
  145. package/dist/human/wake.d.ts +5 -2
  146. package/dist/human/wake.js +23 -14
  147. package/dist/human/web.d.ts +0 -1
  148. package/dist/human/web.js +3 -3
  149. package/dist/human/worker.bundle.txt +2945 -2118
  150. package/dist/human/worlds.js +3 -3
  151. package/dist/mcp/agent.d.ts +0 -1
  152. package/dist/mcp/agent.js +1 -1
  153. package/dist/mcp/allow.js +3 -2
  154. package/dist/mcp/http.d.ts +0 -1
  155. package/dist/mcp/http.js +1 -1
  156. package/dist/mcp/oauth.d.ts +3 -4
  157. package/dist/mcp/oauth.js +4 -4
  158. package/dist/mcp/route.d.ts +1 -1
  159. package/dist/mcp/runner.d.ts +0 -1
  160. package/dist/mcp/runner.js +1 -1
  161. package/dist/mcp/server.d.ts +0 -1
  162. package/dist/mcp/server.js +1 -1
  163. package/harbor/browser.ts +22 -24
  164. package/harbor/capacitor.ts +9 -8
  165. package/harbor/disk.ts +53 -48
  166. package/harbor/edge/alarm.ts +15 -12
  167. package/harbor/edge/edge.ts +61 -55
  168. package/harbor/edge/exercise.ts +4 -4
  169. package/harbor/edge/given.ts +3 -33
  170. package/harbor/edge/storage.ts +2 -2
  171. package/harbor/http.ts +5 -3
  172. package/harbor/sealed.ts +0 -35
  173. package/harbor/tauri.ts +13 -10
  174. package/human/app.ts +12 -13
  175. package/human/door.ts +1 -1
  176. package/human/html.ts +1 -1
  177. package/human/local.ts +1 -1
  178. package/human/move.ts +1 -1
  179. package/human/tab.bundle.txt +3629 -2824
  180. package/human/wake.ts +36 -16
  181. package/human/web.ts +4 -4
  182. package/human/worker.bundle.txt +2945 -2118
  183. package/human/worlds.ts +3 -3
  184. package/mcp/agent.ts +1 -1
  185. package/mcp/allow.ts +2 -1
  186. package/mcp/http.ts +1 -1
  187. package/mcp/oauth.ts +4 -4
  188. package/mcp/route.ts +1 -1
  189. package/mcp/runner.ts +1 -1
  190. package/mcp/server.ts +1 -1
  191. package/package.json +9 -8
  192. package/beings/envoy.ts +0 -218
  193. package/dist/beings/envoy.d.ts +0 -54
  194. package/dist/beings/envoy.js +0 -211
  195. package/dist/dock/console.d.ts +0 -6
  196. package/dist/dock/console.js +0 -20
  197. package/dist/dock/dialers.js +0 -75
  198. package/dist/dock/faculty.d.ts +0 -24
  199. package/dist/dock/faculty.js +0 -142
  200. package/dist/dock/grants.js +0 -99
  201. package/dist/dock/index.d.ts +0 -41
  202. package/dist/dock/index.js +0 -113
  203. package/dist/dock/local.js +0 -44
  204. package/dist/dock/location.js +0 -11
  205. package/dist/dock/push.js +0 -132
  206. package/dist/dock/timer.js +0 -149
  207. package/dock/console.ts +0 -23
  208. package/dock/dialers.ts +0 -85
  209. package/dock/faculty.ts +0 -148
  210. package/dock/grants.ts +0 -111
  211. package/dock/index.ts +0 -133
  212. package/dock/local.ts +0 -48
  213. package/dock/push.ts +0 -161
  214. package/dock/timer.ts +0 -174
  215. /package/dist/{dock → box}/fcm.js +0 -0
  216. /package/dist/{dock → box}/webpush.js +0 -0
@@ -1,11 +1,18 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // A carrier: a being who shows the asks of the standings she holds as asks
3
- // of her own, and forwards. Whoever she carries for sees, in one describe,
2
+ // Carrying: a being shows the asks of the standings she holds as asks of
3
+ // her own, and forwards. Whoever she carries for sees, in one describe,
4
4
  // what she can be asked and what every being she holds can be asked, each
5
5
  // under that standing's name; asking one is an ask on that standing, in her
6
6
  // name, so the far being sees her and never who is behind her. A user being
7
- // carries acme for the human's devices; a house being could carry its
8
- // rooms; nothing here knows what she is.
7
+ // carries her org for the human's devices; a presence being carries her
8
+ // device's faculties to her world; the Dock carries the box's faculties to
9
+ // a screen. Nothing here knows what she is.
10
+ //
11
+ // It is composed and never extended: carrying is something a being does,
12
+ // not what she is, so a being on any rung of the kit's ladder carries by
13
+ // answering through `carry` and saying her rules as statics of her class.
14
+ // The four rules have a default each, and a class says only the ones she
15
+ // means to change.
9
16
  //
10
17
  // A carried ask is `<id>-<name>`, and the mapping back is kept in her cells
11
18
  // and never parsed from the name, so an id with a dash in it is no trouble.
@@ -18,8 +25,21 @@
18
25
  // `look` once per digest, and keeps both in her cells. So a describe of
19
26
  // hers may cost one ask per stale standing, which is the price of a page
20
27
  // that is never more than one ask behind.
21
- import { Being, isSilence, isWord, wordOf } from 'nervur';
28
+ import { isSilence, isWord, wordOf } from 'nervur';
22
29
  import { sanitise } from './look.js';
30
+ export const CARRIES = {
31
+ carries: () => false,
32
+ hidden: (id) => id.startsWith('to:'),
33
+ skips: () => false,
34
+ shows: () => true,
35
+ };
36
+ // Her rules, read off her class; one she left unsaid is the default.
37
+ function rulesOf(self) {
38
+ const C = self.constructor;
39
+ return { carries: C.carries ?? CARRIES.carries, hidden: C.hidden ?? CARRIES.hidden, skips: C.skips ?? CARRIES.skips, shows: C.shows ?? CARRIES.shows };
40
+ }
41
+ const carriedOf = (self) => (self.cells.carried ??= {});
42
+ const looksOf = (self) => (self.cells.looks ??= {});
23
43
  // The names a far being carries, read off her notes: every ask her
24
44
  // `standings` groups name, and each group's page. Empty when she carries
25
45
  // nothing, which a bare avatar does not.
@@ -39,136 +59,108 @@ function carriedBy(bp) {
39
59
  }
40
60
  return out;
41
61
  }
42
- export class Carrier extends Being {
43
- // Who sees what she carries. Nobody, until a subclass says who.
44
- static carries(_occupant, _asker) {
45
- return false;
46
- }
47
- // Standings that are plumbing: their own asks are never carried, and
48
- // what they carry is. A way back to a device is one, by the dock's own
49
- // convention, so a bare avatar's inbox never shows on her and a presence
50
- // being's faculties do, under `to:<id>-<name>`, which is how home carries
51
- // her phone. A subclass may name more.
52
- static hidden(id) {
53
- return id.startsWith('to:');
54
- }
55
- // Standings she never reads for carrying at all: a presence being's
56
- // standing at her own user being is one, since carrying it would carry
57
- // the user being's carrying of her, and round. None, until a subclass
58
- // says which.
59
- static skips(_id) {
60
- return false;
61
- }
62
- // Which of the standings she carries this asker may see. Every one,
63
- // until a subclass keeps some for fewer: a user being shows her own door
64
- // to a device that may mint and to nobody else.
65
- static shows(_id, _occupant, _asker) {
66
- return true;
67
- }
68
- get carried() {
69
- return (this.cells.carried ??= {});
70
- }
71
- get looks() {
72
- return (this.cells.looks ??= {});
73
- }
74
- // Her describe with her standings carried into it, for an asker who may
75
- // see them. Every other asker gets her describe alone.
76
- async blueprint(asker) {
77
- const C = this.constructor;
78
- const bp = this.describe(asker);
79
- if (!C.carries(this.occupant(asker), asker))
80
- return bp;
81
- const own = new Set(bp.asks.map((a) => a.name));
82
- const carried = {};
83
- const groups = {};
84
- const occ = this.occupant(asker);
85
- // By id, and never in the order she happened to take them. Her cells keep
86
- // standings in the order they were taken, which is the order a lend
87
- // answered, a device came back or a relation was dropped and taken again:
88
- // history, and no fact about her. Carried into her blueprint in that
89
- // order, her shape would come out differently on two runs that made her
90
- // the same way, and her digest with it. The digest is how everyone
91
- // holding a standing on her learns she changed, so a shape that follows
92
- // history says she changed when nothing about her did, and a watcher
93
- // refreshes or walks away over the order two lends came back in.
94
- for (const [id, rec] of [...Object.entries(this.cells.standings)].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
95
- if (C.skips(id))
96
- continue;
97
- const st = this.standings[id];
98
- if (!st)
62
+ // Her describe with her standings carried into it, for an asker who may
63
+ // see them. Every other asker gets her describe alone.
64
+ export async function blueprint(self, asker) {
65
+ const rules = rulesOf(self);
66
+ const bp = self.describe(asker);
67
+ const occ = self.occupant(asker);
68
+ if (!rules.carries(occ, asker))
69
+ return bp;
70
+ const own = new Set(bp.asks.map((a) => a.name));
71
+ const carried = {};
72
+ const groups = {};
73
+ const looks = looksOf(self);
74
+ // By id, and never in the order she happened to take them. Her cells keep
75
+ // standings in the order they were taken, which is the order a lend
76
+ // answered, a device came back or a relation was dropped and taken again:
77
+ // history, and no fact about her. Carried into her blueprint in that
78
+ // order, her shape would come out differently on two runs that made her
79
+ // the same way, and her digest with it. The digest is how everyone
80
+ // holding a standing on her learns she changed, so a shape that follows
81
+ // history says she changed when nothing about her did, and a watcher
82
+ // refreshes or walks away over the order two lends came back in.
83
+ for (const [id, rec] of [...Object.entries(self.cells.standings)].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
84
+ if (rules.skips(id))
85
+ continue;
86
+ const st = self.standings[id];
87
+ if (!st)
88
+ continue;
89
+ if (!rec.blueprint || rec.seen !== rec.digest)
90
+ await st.ask(); // her ward writes the record
91
+ if (!rec.blueprint)
92
+ continue;
93
+ // plumbing carries what it carries and nothing of its own: the names
94
+ // the far being's notes group under her standings, or none
95
+ const through = rules.hidden(id) ? carriedBy(rec.blueprint) : null;
96
+ if (through && through.size === 0)
97
+ continue;
98
+ // The mapping is written for every standing she carries, whoever is
99
+ // asking: it is one map in her cells, and the last describe must not
100
+ // decide what the next asker may call. Who sees a standing is decided
101
+ // here and again at answer, by `shows`.
102
+ const shown = rules.shows(id, occ, asker);
103
+ // her look is kept here once per digest and never carried as an ask;
104
+ // her page follows every call, so it is carried as one and named in
105
+ // the notes, where every side finds it and none takes it for a form
106
+ const names = [];
107
+ const group = { asks: names };
108
+ for (const a of rec.blueprint.asks) {
109
+ if (a.name === 'look')
99
110
  continue;
100
- if (!rec.blueprint || rec.seen !== rec.digest)
101
- await st.ask(); // her ward writes the record
102
- if (!rec.blueprint)
111
+ if (through && !through.has(a.name))
103
112
  continue;
104
- // plumbing carries what it carries and nothing of its own: the names
105
- // the far being's notes group under her standings, or none
106
- const through = C.hidden(id) ? carriedBy(rec.blueprint) : null;
107
- if (through && through.size === 0)
113
+ const name = `${id}-${a.name}`;
114
+ if (own.has(name))
108
115
  continue;
109
- // The mapping is written for every standing she carries, whoever is
110
- // asking: it is one map in her cells, and the last describe must not
111
- // decide what the next asker may call. Who sees a standing is decided
112
- // here and again at answer, by `shows`.
113
- const shown = C.shows(id, occ, asker);
114
- // her look is kept here once per digest and never carried as an ask;
115
- // her page follows every call, so it is carried as one and named in
116
- // the notes, where every side finds it and none takes it for a form
117
- const names = [];
118
- const group = { asks: names };
119
- for (const a of rec.blueprint.asks) {
120
- if (a.name === 'look')
121
- continue;
122
- if (through && !through.has(a.name))
123
- continue;
124
- const name = `${id}-${a.name}`;
125
- if (own.has(name))
126
- continue;
127
- carried[name] = { id, ask: a.name };
128
- if (!shown)
129
- continue;
130
- bp.asks.push({ ...a, name });
131
- if (a.name === 'page')
132
- group.page = name;
133
- else
134
- names.push(name);
135
- }
116
+ carried[name] = { id, ask: a.name };
136
117
  if (!shown)
137
118
  continue;
138
- if (rec.blueprint.asks.some((a) => a.name === 'look')) {
139
- const kept = this.looks[id];
140
- if (!kept || kept.digest !== rec.digest) {
141
- const l = await st.ask('look');
142
- this.looks[id] = { digest: rec.digest, look: isSilence(l) || isWord(l) ? {} : sanitise(l) };
143
- }
144
- group.look = this.looks[id].look;
119
+ bp.asks.push({ ...a, name });
120
+ if (a.name === 'page')
121
+ group.page = name;
122
+ else
123
+ names.push(name);
124
+ }
125
+ if (!shown)
126
+ continue;
127
+ if (rec.blueprint.asks.some((a) => a.name === 'look')) {
128
+ const kept = looks[id];
129
+ if (!kept || kept.digest !== rec.digest) {
130
+ const l = await st.ask('look');
131
+ looks[id] = { digest: rec.digest, look: isSilence(l) || isWord(l) ? {} : sanitise(l) };
145
132
  }
146
- groups[id] = group;
133
+ group.look = looks[id].look;
147
134
  }
148
- for (const id of Object.keys(this.looks))
149
- if (!(id in groups))
150
- delete this.looks[id];
151
- this.cells.carried = carried;
152
- const notes = bp.notes !== null && typeof bp.notes === 'object' && !Array.isArray(bp.notes) ? bp.notes : {};
153
- return { asks: bp.asks, notes: { ...notes, standings: groups } };
135
+ groups[id] = group;
154
136
  }
155
- async answer(asker, method, args = {}) {
156
- if (method === undefined)
157
- return this.blueprint(asker);
158
- const C = this.constructor;
159
- const to = Object.hasOwn(this.carried, method) ? this.carried[method] : undefined;
160
- const occ = this.occupant(asker);
161
- if (to && C.carries(occ, asker) && C.shows(to.id, occ, asker)) {
162
- const st = this.standings[to.id];
163
- if (!st)
164
- return { error: 'unknown ask' };
165
- const out = await st.ask(to.ask, args);
166
- if (isSilence(out))
167
- return out;
168
- if (isWord(out))
169
- return { error: wordOf(out) };
137
+ for (const id of Object.keys(looks))
138
+ if (!(id in groups))
139
+ delete looks[id];
140
+ self.cells.carried = carried;
141
+ const notes = bp.notes !== null && typeof bp.notes === 'object' && !Array.isArray(bp.notes) ? bp.notes : {};
142
+ return { asks: bp.asks, notes: { ...notes, standings: groups } };
143
+ }
144
+ // Her answer, carrying: the empty ask is her carried blueprint, a carried
145
+ // name is an ask on that standing in her name, and everything else is her
146
+ // own, which the class hands in as `own`.
147
+ export async function carry(self, asker, method, args, own) {
148
+ if (method === undefined)
149
+ return blueprint(self, asker);
150
+ const rules = rulesOf(self);
151
+ const carried = carriedOf(self);
152
+ const to = Object.hasOwn(carried, method) ? carried[method] : undefined;
153
+ const occ = self.occupant(asker);
154
+ if (to && rules.carries(occ, asker) && rules.shows(to.id, occ, asker)) {
155
+ const st = self.standings[to.id];
156
+ if (!st)
157
+ return { error: 'unknown ask' };
158
+ const out = await st.ask(to.ask, args);
159
+ if (isSilence(out))
170
160
  return out;
171
- }
172
- return super.answer(asker, method, args);
161
+ if (isWord(out))
162
+ return { error: wordOf(out) };
163
+ return out;
173
164
  }
165
+ return own(asker, method, args);
174
166
  }
@@ -30,7 +30,10 @@
30
30
  // from a file and nothing is set on the class, so a schedule outlives the
31
31
  // process the way every cell does, and is gone when it is dropped.
32
32
  import { Being, isSilence, isWord, wordOf, OWNER } from 'nervur';
33
- import { TIMER, join } from '../dock/index.js';
33
+ import { TIMER as TIMING } from '../box/timer.js';
34
+ import { join } from '../box/join.js';
35
+ // Her standing at the box's timer, under an id of her own.
36
+ const TIMER = 'timer';
34
37
  // The root and nobody else. `OWNER` is one of the ward's own words, refused at
35
38
  // every mint, so no occupant can ever wear it: an ask gated this way is the
36
39
  // device's own and cannot be reached through a door.
@@ -286,7 +289,7 @@ export class Clock extends Being {
286
289
  joined;
287
290
  constructor(stance) {
288
291
  super(stance);
289
- this.joined = join(this, TIMER, { method: 'tick' });
292
+ this.joined = join(this, TIMING, TIMER, { method: 'tick' });
290
293
  }
291
294
  static asks = {
292
295
  tick: { description: 'fire everything due at this moment, and answer the tally and when she is next due', input: NOW, for: timer },
@@ -351,9 +354,9 @@ export class Clock extends Being {
351
354
  async told() {
352
355
  if (!Object.hasOwn(this.cells.standings, TIMER))
353
356
  return;
354
- const timer = this.standings[TIMER];
355
- const said = await timer?.ask('now', {});
356
- const now = said !== null && typeof said === 'object' ? said.now : undefined;
357
+ const hers = this.standings[TIMER];
358
+ const heard = await hers?.ask('now', {});
359
+ const now = heard !== null && typeof heard === 'object' ? heard.now : undefined;
357
360
  if (typeof now !== 'number' || !Number.isFinite(now))
358
361
  return;
359
362
  // A schedule due at this very moment is a round now, not a round at the
@@ -361,7 +364,7 @@ export class Clock extends Being {
361
364
  // and the schedule kept inside it names it.
362
365
  const at = this.schedules().some((s) => this.#ready(s, now)) ? now : this.#soonest(floor(now));
363
366
  if (at !== null)
364
- await timer?.ask('due', { at });
367
+ await hers?.ask('due', { at });
365
368
  }
366
369
  // One list, her own cell and nothing else.
367
370
  schedules() {
@@ -509,7 +512,7 @@ export const CLOCK = 'clock';
509
512
  // because it is the same act: the owner creates and places, and the work is
510
513
  // hers from then on.
511
514
  export async function setupClock(hosted, wakes = []) {
512
- const out = (await hosted.ask('boot', { key: CLOCK, class: 'Clock' }));
515
+ const out = (await hosted.ask('boot', { key: CLOCK, class: 'beings/Clock' }));
513
516
  if (out.error)
514
517
  throw new Error(`clock: ${out.error}`);
515
518
  for (const being of wakes)
@@ -8,7 +8,7 @@
8
8
  // way in turns a proof of one kind into who it is for: which user, which
9
9
  // client identity. The ways in are beings, and the desk reaches each of
10
10
  // them as she reaches anything, at a standing under `way:<kind>`: the
11
- // world's own, placed by the root, and the box's, lent by the dock ward and
11
+ // world's own, placed by the root, and the box's, lent by the box ward and
12
12
  // taken at her birth, so a box with no local side offers no such kind.
13
13
  //
14
14
  // A kind whose proof the world has to hand out first answers `send` beside
@@ -17,8 +17,8 @@
17
17
  // sent to before, or what a link says. She keeps the ways in named in one
18
18
  // place, so a side that renders her renders every way into this world.
19
19
  import { Being, isSilence, isWord, wordOf } from 'nervur';
20
- import { join } from '../dock/faculty.js';
21
- import { LOCAL } from '../dock/local.js';
20
+ import { join } from '../box/join.js';
21
+ import { LOCAL } from '../box/local.js';
22
22
  import { WAY, kindOf, verified } from './way.js';
23
23
  export class Desk extends Being {
24
24
  static asks = {
@@ -31,7 +31,7 @@ export class Desk extends Being {
31
31
  // nothing under that id and the kind is not offered.
32
32
  constructor(stance) {
33
33
  super(stance);
34
- void join(this, LOCAL, undefined, WAY(LOCAL));
34
+ void join(this, LOCAL, WAY('local'));
35
35
  }
36
36
  // The ids of the ways in she holds, in the order they were placed.
37
37
  get ways() {
@@ -116,9 +116,10 @@ export class Desk extends Being {
116
116
  // key. A way in that does not say `make` reaches an existing household
117
117
  // and nothing else, which is what the local kind on a device does.
118
118
  if (!this.standings[id] && (who.make === true || typeof who.make === 'string')) {
119
- if (!/^[\w.-]{1,80}$/.test(who.user) || who.user === 'desk')
120
- return { error: 'a user is a word, and not desk' };
121
- if ((await this.boot(typeof who.make === 'string' ? who.make : 'User', who.user, id)) === null)
119
+ // a key the ward already holds, her own included, is refused by the boot
120
+ if (!/^[\w.-]{1,80}$/.test(who.user))
121
+ return { error: 'a user is a word' };
122
+ if ((await this.boot(typeof who.make === 'string' ? who.make : 'beings/User', who.user, id)) === null)
122
123
  return { error: 'no such user' };
123
124
  await this.standings[id].ask('hello', { name: who.user });
124
125
  }
@@ -4,6 +4,7 @@ declare const isDevice: (occ: OccupantRecord | undefined) => boolean;
4
4
  declare const isWorld: (occ: OccupantRecord | undefined) => boolean;
5
5
  declare const isMaker: (occ: OccupantRecord | undefined) => occ is OccupantRecord;
6
6
  export declare class Doorbell extends Being {
7
+ static readonly at = "beings/Doorbell";
7
8
  static cells: JsonObject;
8
9
  static asks: {
9
10
  claim: {
@@ -69,7 +70,8 @@ export declare class Doorbell extends Being {
69
70
  };
70
71
  describe(asker: Parameters<Being['describe']>[0]): import("nervur").Blueprint;
71
72
  claim(_args: JsonObject, asker: Asker): Promise<JsonObject>;
72
- keep(args: JsonObject): JsonObject;
73
+ keep(args: JsonObject): Promise<JsonObject>;
74
+ private at;
73
75
  admit(_args: JsonObject): Promise<JsonObject>;
74
76
  drop(args: JsonObject): JsonObject;
75
77
  allowed(_args: JsonObject): JsonObject;
@@ -28,11 +28,17 @@ const DEVICE = 'device';
28
28
  const MINE = 'push';
29
29
  // What a platform will carry. It is the faculty's number, because the
30
30
  // faculty is the one that hands a ring to a platform.
31
- import { SEAL } from '../dock/push.js';
31
+ import { SEAL, isToken } from '../box/ring.js';
32
+ import { PUSH } from '../box/push.js';
33
+ import { join } from '../box/join.js';
32
34
  const isDevice = (occ) => occ?.id === DEVICE;
33
35
  const isWorld = (occ) => occ !== undefined && occ.id !== DEVICE;
34
36
  const isMaker = (occ) => occ !== undefined;
35
37
  export class Doorbell extends Being {
38
+ // Where this class lives in a tree, which is her key in a ward. She says
39
+ // it herself, so the porter who boots her spells no path of her own and a
40
+ // class moved is one line moved with it.
41
+ static at = 'beings/Doorbell';
36
42
  static cells = { token: null, admitted: 0, claimed: false };
37
43
  static asks = {
38
44
  claim: { description: 'the one way in for this doorbell, handed out once', input: { type: 'object' }, for: isMaker },
@@ -70,13 +76,40 @@ export class Doorbell extends Being {
70
76
  }
71
77
  // Written once and rewritten whenever the platform hands the phone a new
72
78
  // one. No world is told anything, because no world was ever given it.
73
- keep(args) {
79
+ //
80
+ // A kind is one of the rings the box behind this bell holds, and a token
81
+ // of any other kind is refused here, at the door, with an error object.
82
+ // The faculty is asked which kinds it rings, because the faculty is the
83
+ // one that hands a ring to a platform and nothing else on this bell knows.
84
+ // A device told at the moment it hands a token can hand another or ask its
85
+ // human for a bell that can reach it; a device told nothing goes on
86
+ // believing it is reachable until a world rings and is refused, and by then
87
+ // the only one who could act is the only one not there.
88
+ async keep(args) {
74
89
  const token = args.token;
75
- if (token === null || typeof token !== 'object' || Array.isArray(token) || typeof token.kind !== 'string')
90
+ if (!isToken(token))
76
91
  return { error: 'a token names its kind' };
92
+ const at = await this.at();
93
+ if (!at)
94
+ return { error: 'this box rings nothing' };
95
+ const said = await at.ask('kinds', {});
96
+ if (said === undefined || isSilence(said))
97
+ return { error: 'this box rings nothing' };
98
+ if (isWord(said))
99
+ return { error: wordOf(said) };
100
+ const kinds = said.kinds;
101
+ if (!Array.isArray(kinds))
102
+ return { error: 'this box rings nothing' };
103
+ if (!kinds.includes(token.kind))
104
+ return { error: `this box cannot ring a ${token.kind}` };
77
105
  this.cells.token = token;
78
106
  return { kept: token.kind };
79
107
  }
108
+ // Her standing at the box's push faculty, taken the first time it is
109
+ // wanted and held from then on.
110
+ async at() {
111
+ return this.standings[MINE] ?? (await join(this, PUSH, MINE).then(() => this.standings[MINE]));
112
+ }
80
113
  // The device allows a world by asking for a way in and handing it over.
81
114
  // So a world can only ring a phone that chose it.
82
115
  async admit(_args) {
@@ -113,7 +146,7 @@ export class Doorbell extends Being {
113
146
  const token = this.cells.token;
114
147
  if (token === null)
115
148
  return { error: 'this device has handed no token' };
116
- const at = this.standings[MINE] ?? (await this.lend(MINE, MINE).then(() => this.standings[MINE]));
149
+ const at = await this.at();
117
150
  if (!at)
118
151
  return { error: 'this box rings nothing' };
119
152
  const out = await at.ask('ring', { token, sealed });
@@ -2,13 +2,33 @@ export { User, DESK, HOME, BELL } from './user.ts';
2
2
  export { Desk } from './desk.ts';
3
3
  export { WAY, kindOf, verified, VERIFY, SEND, type Proof, type Verified } from './way.ts';
4
4
  export { Avatar, USER, PUSHER } from './avatar.ts';
5
- export { Carrier } from './carry.ts';
5
+ export { carry, blueprint, CARRIES, type Carrying } from './carry.ts';
6
6
  export { Presence } from './presence.ts';
7
7
  export { Courier, MOVER, isMoving, type Moving } from './courier.ts';
8
8
  export { Porter } from './porter.ts';
9
9
  export { Doorbell } from './doorbell.ts';
10
- export { Envoy, owner, occupant, type Delivery, type Wake, type Heard } from './envoy.ts';
11
- export { DOCK, CONSOLE, LOCAL, LOCATION, NOTIFICATIONS, PUSH, TIMER, FACULTIES, FACULTY_CLASSES, Console, Faculty, Local, Location, Notifications, Push, Timer, isToken, join, settled, stand, standOn, lending, facultyOf, type Ring, type Token, type Where, type Watched, type Woke, type Rooted } from '../dock/index.ts';
12
- export { webPush, mintVapid, publicOf, apnsPush, pkcs8, HOSTS, fcmPush, isAccount, DOORS, handedTo, ringing, routed, type Vapid, type Apns, type Account, type Doors } from '../dock/index.ts';
10
+ export { BOX, BOX_SEED, Dock, LOCAL, LOCATION, NOTIFICATIONS, PUSH, TIMER, Faculty, Local, Location, Notifications, Push, Timer, contract, covers, nameOf, titleOf, keyed, join, settled, stand, standHarbor, classes, lent, type Contract, type Tree, type Where, type Watched, type Rooted } from '../box/index.ts';
11
+ export { isToken, type Ring, type Token } from '../box/index.ts';
12
+ export { webPush, mintVapid, publicOf, apnsPush, pkcs8, HOSTS, fcmPush, isAccount, DOORS, handedTo, ringing, routed, type Vapid, type Apns, type Account, type Doors } from '../box/index.ts';
13
13
  export { Clock, CLOCK, setupClock, placeClock, readSchedule, parseCron, nextFire, civil, fromCivil, type Schedule, type Fire, type Cron, type Civil } from './clock.ts';
14
14
  export { sanitise, hint, hintFor, groups, type Look, type Hint } from './look.ts';
15
+ import { User } from './user.ts';
16
+ import { Desk } from './desk.ts';
17
+ import { Avatar } from './avatar.ts';
18
+ import { Presence } from './presence.ts';
19
+ import { Clock } from './clock.ts';
20
+ import { Courier } from './courier.ts';
21
+ import { Porter } from './porter.ts';
22
+ import { Doorbell } from './doorbell.ts';
23
+ export declare const BEINGS: {
24
+ beings: {
25
+ User: typeof User;
26
+ Desk: typeof Desk;
27
+ Avatar: typeof Avatar;
28
+ Presence: typeof Presence;
29
+ Clock: typeof Clock;
30
+ Courier: typeof Courier;
31
+ Porter: typeof Porter;
32
+ Doorbell: typeof Doorbell;
33
+ };
34
+ };
@@ -4,13 +4,24 @@ export { User, DESK, HOME, BELL } from './user.js';
4
4
  export { Desk } from './desk.js';
5
5
  export { WAY, kindOf, verified, VERIFY, SEND } from './way.js';
6
6
  export { Avatar, USER, PUSHER } from './avatar.js';
7
- export { Carrier } from './carry.js';
7
+ export { carry, blueprint, CARRIES } from './carry.js';
8
8
  export { Presence } from './presence.js';
9
9
  export { Courier, MOVER, isMoving } from './courier.js';
10
10
  export { Porter } from './porter.js';
11
11
  export { Doorbell } from './doorbell.js';
12
- export { Envoy, owner, occupant } from './envoy.js';
13
- export { DOCK, CONSOLE, LOCAL, LOCATION, NOTIFICATIONS, PUSH, TIMER, FACULTIES, FACULTY_CLASSES, Console, Faculty, Local, Location, Notifications, Push, Timer, isToken, join, settled, stand, standOn, lending, facultyOf } from '../dock/index.js';
14
- export { webPush, mintVapid, publicOf, apnsPush, pkcs8, HOSTS, fcmPush, isAccount, DOORS, handedTo, ringing, routed } from '../dock/index.js';
12
+ export { BOX, BOX_SEED, Dock, LOCAL, LOCATION, NOTIFICATIONS, PUSH, TIMER, Faculty, Local, Location, Notifications, Push, Timer, contract, covers, nameOf, titleOf, keyed, join, settled, stand, standHarbor, classes, lent } from '../box/index.js';
13
+ export { isToken } from '../box/index.js';
14
+ export { webPush, mintVapid, publicOf, apnsPush, pkcs8, HOSTS, fcmPush, isAccount, DOORS, handedTo, ringing, routed } from '../box/index.js';
15
15
  export { Clock, CLOCK, setupClock, placeClock, readSchedule, parseCron, nextFire, civil, fromCivil } from './clock.js';
16
16
  export { sanitise, hint, hintFor, groups } from './look.js';
17
+ import { User } from './user.js';
18
+ import { Desk } from './desk.js';
19
+ import { Avatar } from './avatar.js';
20
+ import { Presence } from './presence.js';
21
+ import { Clock } from './clock.js';
22
+ import { Courier } from './courier.js';
23
+ import { Porter } from './porter.js';
24
+ import { Doorbell } from './doorbell.js';
25
+ // The beings this folder brings to every harbor, as a tree: a ward names
26
+ // one by where it lives, `beings/User`, as it names a faculty.
27
+ export const BEINGS = { beings: { User, Desk, Avatar, Presence, Clock, Courier, Porter, Doorbell } };
@@ -1,6 +1,7 @@
1
1
  import type { Hosted } from 'nervur/harbor';
2
2
  import type { Invitation } from 'nervur';
3
3
  import type { Avatar } from './avatar.ts';
4
+ export declare const isIdentity: (hosted: Hosted, identity: unknown) => identity is string;
4
5
  export declare function avatarOf(hosted: Hosted, identity: string): Promise<{
5
6
  avatar?: Avatar;
6
7
  error?: string;
@@ -1,12 +1,14 @@
1
- // Her avatar in a ward, found or booted under the identity's key. An
2
- // identity is a word, and never a being of the ward.
1
+ // An identity is a word, and never the key of a being the ward holds: the
2
+ // ward is asked, so no route names who its beings are.
3
+ export const isIdentity = (hosted, identity) => typeof identity === 'string' && /^[\w.-]+$/.test(identity) && !hosted.keys().includes(identity);
4
+ // Her avatar in a ward, found or booted under the identity's key.
3
5
  export async function avatarOf(hosted, identity) {
4
- if (!/^[\w.-]+$/.test(identity) || identity === hosted.record.user || identity === 'desk')
6
+ if (!isIdentity(hosted, identity))
5
7
  return { error: 'an identity is a word, and not a being of the ward' };
6
8
  const key = `avatar:${identity}`;
7
9
  let avatar = hosted.being(key);
8
10
  if (!avatar) {
9
- const out = (await hosted.ask('boot', { key, class: 'Avatar' }));
11
+ const out = (await hosted.ask('boot', { key, class: 'beings/Avatar' }));
10
12
  if (out.error)
11
13
  return { error: out.error };
12
14
  avatar = hosted.being(key);
@@ -1,6 +1,5 @@
1
1
  import type { Invitation } from 'nervur';
2
2
  export declare const KEY = "nervur";
3
- export declare const PROOF = "proof";
4
3
  export declare function format(inv: Invitation): string;
5
4
  export declare function parse(hash: string): Invitation | null;
6
5
  export declare function parseProof(hash: string): {
@@ -5,7 +5,7 @@ export const KEY = 'nervur';
5
5
  // a query string is in every log and every referer, and one read of it is
6
6
  // one way in taken. `<kind>.<token>`, the kind the desk knows it by and the
7
7
  // token that door minted.
8
- export const PROOF = 'proof';
8
+ const PROOF = 'proof';
9
9
  const HEX = (n) => `[0-9a-f]{${n}}`;
10
10
  const FULL = new RegExp(`^(${HEX(128)})\\.(${HEX(64)})\\.(${HEX(64)})$`);
11
11
  const PUBLIC = new RegExp(`^${HEX(128)}$`);
@@ -14,6 +14,7 @@
14
14
  // harbor's, never hers and never the door's, and the count below is only so
15
15
  // that a loop in this session stops somewhere a human can see.
16
16
  import { Being, isSilence, isWord, wordOf } from 'nervur';
17
+ import { Doorbell } from './doorbell.js';
17
18
  const ROOM = 64;
18
19
  export class Porter extends Being {
19
20
  static cells = { minted: 0 };
@@ -24,8 +25,11 @@ export class Porter extends Being {
24
25
  const minted = typeof this.cells.minted === 'number' ? this.cells.minted : 0;
25
26
  if (minted >= ROOM)
26
27
  return { error: 'this bell holds no more doorbells' };
28
+ // The class says where it lives and the porter reads it off her. A path
29
+ // spelled here would be the doorbell named by whoever boots her, which
30
+ // is a class described by someone who is not it.
27
31
  const key = `doorbell:${minted + 1}`;
28
- const held = await this.boot('Doorbell', key, key);
32
+ const held = await this.boot(Doorbell.at, key, key);
29
33
  if (held === null)
30
34
  return { error: 'this bell could not make a doorbell' };
31
35
  this.cells.minted = minted + 1;
@@ -3,6 +3,7 @@ import { Avatar } from './avatar.ts';
3
3
  export declare class Presence extends Avatar {
4
4
  static carries(occupant: OccupantRecord | undefined, _asker: Asker): boolean;
5
5
  static skips(id: string): boolean;
6
+ static needs: import("nervur").Blueprint[];
6
7
  readonly lent: Promise<Record<string, string | null>>;
7
8
  constructor(stance: Stance);
8
9
  }