nervur 0.12.0 → 0.14.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.
package/blueprint.mjs CHANGED
@@ -85,6 +85,8 @@ const doorHolder = (one) =>
85
85
 
86
86
  const RIGHTS_DIRECTIVE = 'directive @rights(is: [Rights!]!) on FIELD_DEFINITION';
87
87
 
88
+ const INVITES_DIRECTIVE = 'directive @invites(count: Int!, within: Int!) on ENUM_VALUE';
89
+
88
90
  const DEFAULT_RIGHTS = `enum Rights { SELF MEMBER STRANGER }`;
89
91
 
90
92
  const codeOnly = (source) => {
@@ -150,6 +152,30 @@ const rightsOf = (field) => {
150
152
  : listed.value.values.map((one) => one.value ?? one.name?.value);
151
153
  };
152
154
 
155
+ const whole = (one) => Number.isSafeInteger(one) && one >= 0;
156
+
157
+ const ceilingOf = (value) => {
158
+ const marked = value.directives?.find((one) => one.name.value === 'invites');
159
+ if (marked === undefined) {
160
+ return undefined;
161
+ }
162
+ const argOf = (name) => {
163
+ const given = marked.arguments?.find((one) => one.name.value === name)?.value?.value;
164
+ return given === undefined ? undefined : Number(given);
165
+ };
166
+ return { count: argOf('count'), within: argOf('within') };
167
+ };
168
+
169
+ const ceilingsIn = (document) =>
170
+ new Map(
171
+ document.definitions
172
+ .filter((one) => one.kind === Kind.ENUM_TYPE_DEFINITION && one.name.value === 'Rights')
173
+ .flatMap((one) => one.values ?? [])
174
+ .map((value) => [value.name.value, ceilingOf(value)])
175
+ .filter(([, ceiling]) => ceiling !== undefined)
176
+ .map(([name, ceiling]) => [name, Object.freeze(ceiling)]),
177
+ );
178
+
153
179
  const resolverSources = (resolvers) =>
154
180
  Object.keys(resolvers)
155
181
  .sort()
@@ -202,7 +228,9 @@ const faults = (source) => {
202
228
  return ['it settles no blueprint object (VIII)'];
203
229
  }
204
230
  const declares = /enum\s+Rights\b/.test(written.interface) ? '' : DEFAULT_RIGHTS;
205
- const sdl = [RIGHTS_DIRECTIVE, declares, written.interface].filter(Boolean).join('\n');
231
+ const sdl = [RIGHTS_DIRECTIVE, INVITES_DIRECTIVE, declares, written.interface]
232
+ .filter(Boolean)
233
+ .join('\n');
206
234
  let document;
207
235
  let schema;
208
236
  try {
@@ -211,13 +239,14 @@ const faults = (source) => {
211
239
  } catch (error) {
212
240
  return [`its schema does not compile: ${error.message} (VIII)`];
213
241
  }
214
- const rights = [
215
- ...new Set([
216
- ...STRUCTURAL,
217
- ...(schema.getType('Rights')?.getValues() ?? []).map((one) => one.name),
218
- ]),
219
- ];
242
+ const declaredNames = (schema.getType('Rights')?.getValues() ?? []).map((one) => one.name);
243
+ const rights = [...new Set([...STRUCTURAL, ...declaredNames])];
220
244
  const found = [];
245
+ if (!declares && STRUCTURAL.some((one) => !declaredNames.includes(one))) {
246
+ found.push(
247
+ `the Rights enum does not name ${STRUCTURAL.filter((one) => !declaredNames.includes(one)).join(', ')}, and every Rights enum names both SELF and STRANGER (VI)`,
248
+ );
249
+ }
221
250
  for (const [key, cell] of Object.entries(written.memory ?? {})) {
222
251
  if (coreClass(key) !== undefined) {
223
252
  found.push(`the cell ${key} is the core's own, and no program declares it (VIII)`);
@@ -240,6 +269,17 @@ const faults = (source) => {
240
269
  if (bare.length > 0) {
241
270
  found.push(`the door ${bare.join(', ')} declares no rights, and every door declares them (VI)`);
242
271
  }
272
+ for (const [right, ceiling] of ceilingsIn(document)) {
273
+ if (STRUCTURAL.includes(right)) {
274
+ found.push(
275
+ `${right} carries an @invites ceiling, and a structural right is never invited into (VI)`,
276
+ );
277
+ } else if (!whole(ceiling.count) || !whole(ceiling.within)) {
278
+ found.push(
279
+ `${right}'s @invites ceiling is not two whole numbers of zero or more (VI): count ${ceiling.count}, within ${ceiling.within}`,
280
+ );
281
+ }
282
+ }
243
283
  for (const [held, doors] of outside) {
244
284
  found.push(
245
285
  `the Rights enum does not name ${held}, which ${doors.length} door${doors.length === 1 ? '' : 's'} stand behind: ${doors.join(', ')} (VI)`,
@@ -341,15 +381,16 @@ const Blueprint = (source) => {
341
381
  try {
342
382
  const written = evaluated(source);
343
383
  const declares = /enum\s+Rights\b/.test(written.interface) ? '' : DEFAULT_RIGHTS;
344
- const sdl = [RIGHTS_DIRECTIVE, declares, written.interface].filter(Boolean).join('\n');
384
+ const sdl = [RIGHTS_DIRECTIVE, INVITES_DIRECTIVE, declares, written.interface]
385
+ .filter(Boolean)
386
+ .join('\n');
345
387
  const document = parse(sdl);
346
388
  const schema = buildSchema(sdl);
347
- const rights = [
348
- ...new Set([
349
- ...STRUCTURAL,
350
- ...(schema.getType('Rights')?.getValues() ?? []).map((one) => one.name),
351
- ]),
352
- ];
389
+ const declaredNames = (schema.getType('Rights')?.getValues() ?? []).map((one) => one.name);
390
+ if (!declares && STRUCTURAL.some((one) => !declaredNames.includes(one))) {
391
+ return undefined;
392
+ }
393
+ const rights = [...new Set([...STRUCTURAL, ...declaredNames])];
353
394
 
354
395
  const admits = (typeName, fieldName, held) => {
355
396
  const field = document.definitions
@@ -376,9 +417,18 @@ const Blueprint = (source) => {
376
417
  if (undeclared.length > 0) {
377
418
  return undefined;
378
419
  }
420
+ const ceilings = ceilingsIn(document);
421
+ const misinvited = [...ceilings].filter(
422
+ ([right, ceiling]) =>
423
+ STRUCTURAL.includes(right) || !whole(ceiling.count) || !whole(ceiling.within),
424
+ );
425
+ if (misinvited.length > 0) {
426
+ return undefined;
427
+ }
379
428
 
380
429
  return Object.freeze({
381
430
  admits,
431
+ invites: (held) => ceilings.get(held),
382
432
  bindings: (given) =>
383
433
  modules
384
434
  .filter((one) => one.binding !== undefined)
@@ -395,7 +445,7 @@ const Blueprint = (source) => {
395
445
  needs.config === undefined ? Object.freeze({}) : inputCheck(schema, needs.config, given),
396
446
  digest: Tools.digest(
397
447
  Tools.canon({
398
- interface: print(document),
448
+ interface: sdl,
399
449
  memory,
400
450
  name: written.name,
401
451
  needs,
package/client.mjs CHANGED
@@ -1,7 +1,5 @@
1
1
  import { Stamp, Tools } from './tools.mjs';
2
2
 
3
- const KEY = /^[0-9a-f]{64}$/;
4
-
5
3
  const speaking = (born, at, succession) => {
6
4
  if (succession === undefined) {
7
5
  return born;
@@ -11,13 +9,14 @@ const speaking = (born, at, succession) => {
11
9
  }
12
10
  let pk = born;
13
11
  for (const step of succession) {
14
- if (!KEY.test(step?.next ?? '')) {
12
+ const next = typeof step?.next === 'string' ? Tools.pkOf(step.next) : null;
13
+ if (next === null) {
15
14
  return undefined;
16
15
  }
17
16
  if (!Tools.verifies(pk, Tools.canon({ at, next: step.next, v: step.v }), step.sig)) {
18
17
  return undefined;
19
18
  }
20
- pk = Buffer.from(step.next, 'hex');
19
+ pk = next;
21
20
  }
22
21
  return pk;
23
22
  };
@@ -32,11 +31,15 @@ const Client = ({ voice, deliver, resolve = (one) => one }) => {
32
31
  return undefined;
33
32
  }
34
33
  const turn = (lanes.get(at) ?? Promise.resolve()).then(async () => {
35
- const reply = await deliver(Tools.envelope(voice, at, op, stamp()));
34
+ const envelope = await Tools.envelope(voice, at, op, stamp());
35
+ const claim = Tools.digest(
36
+ Tools.canon({ from: envelope.from, op: envelope.op, seq: envelope.seq, to: envelope.to }),
37
+ );
38
+ const reply = await deliver(envelope);
36
39
  const answering = speaking(born, at, reply?.succession);
37
40
  return reply?.body === undefined ||
38
41
  answering === undefined ||
39
- !Tools.verifies(answering, Tools.canon(reply.body), reply.sig)
42
+ !Tools.verifies(answering, Tools.canon({ body: reply.body, claim }), reply.sig)
40
43
  ? undefined
41
44
  : reply;
42
45
  });
package/contract.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { buildSchema, isEnumType, isInputObjectType, parse, print } from 'graphql';
1
+ import { buildSchema, isEnumType, isInputObjectType, specifiedScalarTypes } from 'graphql';
2
2
  import { Tools } from './tools.mjs';
3
3
 
4
4
  const argsAgree = (wanted, given) => {
@@ -16,9 +16,8 @@ const argsAgree = (wanted, given) => {
16
16
  const Contract = (name, sdl) => {
17
17
  try {
18
18
  const schema = buildSchema(sdl);
19
- const printed = print(parse(sdl));
20
19
  return Object.freeze({
21
- digest: Tools.digest(Tools.canon({ interface: printed, name })),
20
+ digest: Tools.digest(Tools.canon({ interface: sdl, name })),
22
21
  name,
23
22
  satisfies: (offered) => {
24
23
  const theirs = (() => {
@@ -36,6 +35,9 @@ const Contract = (name, sdl) => {
36
35
  continue;
37
36
  }
38
37
  const given = theirs.getTypeMap()[typeName];
38
+ if (given === undefined && !specifiedScalarTypes.some((one) => one.name === typeName)) {
39
+ return false;
40
+ }
39
41
  if (isEnumType(wanted)) {
40
42
  if (
41
43
  !isEnumType(given) ||
@@ -73,7 +75,7 @@ const Contract = (name, sdl) => {
73
75
  }
74
76
  return true;
75
77
  },
76
- sdl: printed,
78
+ sdl,
77
79
  });
78
80
  } catch {
79
81
  return undefined;
package/ground.mjs CHANGED
@@ -1,17 +1,36 @@
1
1
  import { ADMIN } from './admin.mjs';
2
- import { Being, Compartment, Registry, SUCCESSION, make, rootFor, spend } from './being.mjs';
2
+ import {
3
+ Being,
4
+ Compartment,
5
+ Registry,
6
+ SUCCESSION,
7
+ make,
8
+ rootFor,
9
+ spend,
10
+ successionOf,
11
+ } from './being.mjs';
3
12
  import { Blueprint, composed, coreClass } from './blueprint.mjs';
4
13
  import { Client } from './client.mjs';
5
14
  import { Contracts } from './contract.mjs';
6
- import { Memory, Paths, Wire } from './modules.mjs';
15
+ import { Memory } from './modules.mjs';
7
16
  import { Mcp, Pointer } from './pointer.mjs';
8
17
  import { Program } from './program.mjs';
9
- import { Stamp, Tools } from './tools.mjs';
18
+ import { Bytes, Stamp, Tools } from './tools.mjs';
10
19
  import { Voice } from './voice.mjs';
11
20
 
12
21
  const GROUND = 'Ground';
13
22
 
14
- const FLOOR = ['blueprints', 'cells', 'contracts', 'keychain', 'paths', 'watch', 'wire'];
23
+ const FLOOR = [
24
+ 'blueprints',
25
+ 'cells',
26
+ 'cipher',
27
+ 'contracts',
28
+ 'keychain',
29
+ 'moved',
30
+ 'paths',
31
+ 'watch',
32
+ 'wire',
33
+ ];
15
34
 
16
35
  const UNPROVEN = 'ground/unproven/';
17
36
 
@@ -19,7 +38,11 @@ const HEIR = 'ground/heir';
19
38
 
20
39
  const commits = (cells, bundle) => {
21
40
  const boxPk = bundle?.heirBoxPk;
22
- if (typeof boxPk !== 'string' || Buffer.from(boxPk, 'base64').length !== 32) {
41
+ try {
42
+ if (typeof boxPk !== 'string' || Bytes.unbase64(boxPk).length !== 32) {
43
+ return undefined;
44
+ }
45
+ } catch {
23
46
  return undefined;
24
47
  }
25
48
  cells.put(HEIR, { boxPk, nextPkHash: bundle.nextPkHash });
@@ -43,8 +66,8 @@ const unprovenAt = (cells) => (at) => {
43
66
 
44
67
  const unprovenRows = (cells) => cells.keys(UNPROVEN).map((row) => cells.peek(row));
45
68
 
46
- const install = ({ cells, keychain, program, bundle }) => {
47
- if (bundle?.next !== undefined) {
69
+ const install = ({ cells, keychain, program, bundle, cipher }) => {
70
+ if (cells.peek('ground/admin') !== undefined || bundle?.next !== undefined) {
48
71
  return undefined;
49
72
  }
50
73
  const voice = Voice.revive(bundle ?? {});
@@ -57,13 +80,13 @@ const install = ({ cells, keychain, program, bundle }) => {
57
80
  if (sealed === undefined) {
58
81
  return undefined;
59
82
  }
60
- make({ memory, voice, address, program });
83
+ make({ memory, voice, address, program, cipher });
61
84
  cells.put('ground/admin', { address, sealed });
62
85
  commits(cells, bundle);
63
86
  return address;
64
87
  };
65
88
 
66
- const succeeds = ({ adminMine, adminVoice, address, bundle, cells, keychain, memory }) => {
89
+ const succeeds = ({ adminMine, adminVoice, address, bundle, cells, cipher, keychain, memory }) => {
67
90
  if (bundle?.next !== undefined) {
68
91
  return undefined;
69
92
  }
@@ -72,21 +95,22 @@ const succeeds = ({ adminMine, adminVoice, address, bundle, cells, keychain, mem
72
95
  if (heir === undefined || committed?.value !== Tools.digest(heir.pk)) {
73
96
  return undefined;
74
97
  }
75
- const root = rootFor({ memory, voice: adminVoice, address });
98
+ const root = rootFor({ memory, voice: adminVoice, address, cipher });
76
99
  const sealed = root === undefined ? undefined : keychain.seal(heir.pack());
77
100
  if (sealed === undefined) {
78
101
  return undefined;
79
102
  }
80
103
  const rewrapped = adminMine.write(`keyring/${Tools.address(heir.pk)}`, {
81
- boxPk: Buffer.from(heir.boxPk).toString('base64'),
82
- wrapped: Voice.sealTo(heir.boxPk, root.toString('base64')),
104
+ boxPk: Bytes.base64(heir.boxPk),
105
+ wrapped: Voice.sealTo(heir.boxPk, Bytes.base64(root), cipher),
83
106
  });
84
107
  if (rewrapped === undefined) {
85
108
  return undefined;
86
109
  }
87
110
  const version = committed.version + 1;
88
- const handover = { at: address, next: Buffer.from(heir.pk).toString('hex'), v: version };
89
- const certified = adminMine.write(`${SUCCESSION}${version}`, {
111
+ const certV = successionOf(memory, address).length + 1;
112
+ const handover = { at: address, next: Tools.address(heir.pk), v: certV };
113
+ const certified = adminMine.write(`${SUCCESSION}${certV}`, {
90
114
  ...handover,
91
115
  sig: adminVoice.sign(Tools.canon(handover)),
92
116
  });
@@ -96,7 +120,7 @@ const succeeds = ({ adminMine, adminVoice, address, bundle, cells, keychain, mem
96
120
  const claim = {
97
121
  address,
98
122
  nextPkHash: heir.nextPkHash,
99
- pk: Buffer.from(heir.pk).toString('hex'),
123
+ pk: Bytes.hex(heir.pk),
100
124
  version,
101
125
  };
102
126
  if (memory.replace({ ...claim, sig: heir.sign(Tools.canon(claim)) }) === undefined) {
@@ -112,7 +136,14 @@ const succeeds = ({ adminMine, adminVoice, address, bundle, cells, keychain, mem
112
136
  const Ground = (modules = {}, { span } = {}) => {
113
137
  const cells = modules.cells;
114
138
  const keychain = modules.keychain;
115
- if (cells === undefined || keychain?.seal === undefined || keychain?.unseal === undefined) {
139
+ const cipher = modules.cipher;
140
+ if (
141
+ cells === undefined ||
142
+ keychain?.seal === undefined ||
143
+ keychain?.unseal === undefined ||
144
+ cipher?.seal === undefined ||
145
+ cipher?.unseal === undefined
146
+ ) {
116
147
  return undefined;
117
148
  }
118
149
 
@@ -134,7 +165,7 @@ const Ground = (modules = {}, { span } = {}) => {
134
165
  const installed = cells.peek('ground/admin');
135
166
  if (installed === undefined) {
136
167
  return Object.freeze({
137
- init: (bundle) => install({ cells, keychain, program, bundle }),
168
+ init: (bundle) => install({ cells, keychain, program, bundle, cipher: modules.cipher }),
138
169
  });
139
170
  }
140
171
  const bundle = keychain.unseal(installed.sealed);
@@ -155,17 +186,22 @@ const Ground = (modules = {}, { span } = {}) => {
155
186
  };
156
187
 
157
188
  const address = installed.address;
158
- const memory = Memory(cells, unprovenAt(cells));
159
- const paths = modules.paths ?? Paths(new Map());
160
- const wire = modules.wire ?? Wire();
189
+ const memory = Memory(cells, unprovenAt(cells), modules.moved);
190
+ const paths = modules.paths ?? Object.freeze({ find: () => undefined, publish: () => undefined });
191
+ const wire =
192
+ modules.wire ??
193
+ Object.freeze({
194
+ carry: async (endpoint, envelope) =>
195
+ typeof endpoint === 'function' ? endpoint(envelope) : undefined,
196
+ });
161
197
 
162
198
  const stamp = Stamp();
163
199
  const boundFor = (voice) =>
164
200
  Object.fromEntries(
165
201
  bound.map(([name, one]) => [
166
202
  name,
167
- Pointer(written.get(one.contract).sdl, (source, variables) =>
168
- deliver(Tools.envelope(voice, one.at, { ask: { source, variables } }, stamp())),
203
+ Pointer(written.get(one.contract).sdl, async (source, variables) =>
204
+ deliver(await Tools.envelope(voice, one.at, { ask: { source, variables } }, stamp())),
169
205
  ),
170
206
  ]),
171
207
  );
@@ -199,9 +235,10 @@ const Ground = (modules = {}, { span } = {}) => {
199
235
  memory,
200
236
  voice: adminVoice,
201
237
  address,
202
- root: rootFor({ memory, voice: adminVoice, address }),
238
+ root: rootFor({ memory, voice: adminVoice, address, cipher: modules.cipher }),
203
239
  classOf: (key) =>
204
240
  Program(memory.read(address, 'program')?.value)?.classOf(key) ?? coreClass(key),
241
+ cipher: modules.cipher,
205
242
  });
206
243
 
207
244
  const receive = async (envelope, token) => {
@@ -223,6 +260,7 @@ const Ground = (modules = {}, { span } = {}) => {
223
260
  paths.publish(at, undefined);
224
261
  if (voice === undefined) {
225
262
  cells.drop(`${at}/`);
263
+ modules.moved?.(at);
226
264
  return undefined;
227
265
  }
228
266
  return spend({ memory, voice, address: at });
@@ -236,16 +274,31 @@ const Ground = (modules = {}, { span } = {}) => {
236
274
  if (one.at === undefined) {
237
275
  return null;
238
276
  }
239
- const seen = await deliver(Tools.envelope(adminVoice, one.at, { describe: {} }, stamp()));
277
+ const seen = await deliver(await Tools.envelope(adminVoice, one.at, { describe: {} }, stamp()));
240
278
  return seen?.body?.sdl === undefined
241
279
  ? false
242
280
  : written.get(one.contract).satisfies(seen.body.sdl);
243
281
  };
244
282
 
283
+ let adminDoor;
284
+ const buildAdmin = () =>
285
+ Being({
286
+ address,
287
+ administration: true,
288
+ cipher: modules.cipher,
289
+ deliver: (envelope) => deliver(envelope),
290
+ modules: modulesFor(adminVoice, adminDeclared()),
291
+ registry,
292
+ span,
293
+ vouched,
294
+ voice: adminVoice,
295
+ watch,
296
+ });
245
297
  const registry = Registry({
246
298
  memory,
247
299
  adminMine,
248
300
  address,
301
+ cipher: modules.cipher,
249
302
  deliver: (envelope) => deliver(envelope),
250
303
  modules: offeredNames,
251
304
  modulesFor,
@@ -255,18 +308,16 @@ const Ground = (modules = {}, { span } = {}) => {
255
308
  vouched,
256
309
  attest,
257
310
  watch,
311
+ extended: () => {
312
+ adminDoor = undefined;
313
+ },
258
314
  });
259
- const admin = (envelope, token) =>
260
- Being({
261
- address,
262
- deliver: (envelope) => deliver(envelope),
263
- modules: modulesFor(adminVoice, adminDeclared()),
264
- registry,
265
- span,
266
- vouched,
267
- voice: adminVoice,
268
- watch,
269
- })(envelope, token);
315
+ const admin = (envelope, token) => {
316
+ if (adminDoor === undefined) {
317
+ adminDoor = buildAdmin();
318
+ }
319
+ return adminDoor(envelope, token);
320
+ };
270
321
 
271
322
  announce(address);
272
323
  for (const key of adminMine.list('identity/custody/')) {
@@ -284,7 +335,16 @@ const Ground = (modules = {}, { span } = {}) => {
284
335
  address,
285
336
  receive,
286
337
  rotate: (bundle) =>
287
- succeeds({ adminMine, adminVoice, address, bundle, cells, keychain, memory }),
338
+ succeeds({
339
+ adminMine,
340
+ adminVoice,
341
+ address,
342
+ bundle,
343
+ cells,
344
+ cipher: modules.cipher,
345
+ keychain,
346
+ memory,
347
+ }),
288
348
  serve: (voice) => {
289
349
  const client = Client({ voice, deliver, resolve });
290
350
  const sdlOf = async (at) => (await client.describe(at))?.body?.sdl;
package/index.mjs CHANGED
@@ -13,8 +13,8 @@ export {
13
13
  export { Client, speaking } from './client.mjs';
14
14
  export { Contract, Contracts } from './contract.mjs';
15
15
  export { FLOOR, GROUND, Ground } from './ground.mjs';
16
- export { Cells, Clock, Dns, Entropy, Keychain, Memory, Paths, Wire } from './modules.mjs';
16
+ export { Memory } from './modules.mjs';
17
17
  export { Mcp, Pointer } from './pointer.mjs';
18
18
  export { Program } from './program.mjs';
19
- export { MEASURE, SPAN, Stamp, Tools } from './tools.mjs';
19
+ export { Bytes, MEASURE, SPAN, Stamp, Tools } from './tools.mjs';
20
20
  export { Voice } from './voice.mjs';
package/modules.mjs CHANGED
@@ -1,38 +1,13 @@
1
- import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
- import { Tools } from './tools.mjs';
1
+ import { Bytes, Tools } from './tools.mjs';
3
2
 
4
- const Cells = ({ file } = {}) => {
5
- const cells = new Map(
6
- file && existsSync(file) ? Object.entries(JSON.parse(readFileSync(file, 'utf8'))) : [],
7
- );
8
- const flush = () => {
9
- if (file) {
10
- writeFileSync(`${file}.next`, JSON.stringify(Object.fromEntries(cells)));
11
- renameSync(`${file}.next`, file);
12
- }
13
- };
14
- return Object.freeze({
15
- drop: (prefix) => {
16
- for (const cell of [...cells.keys()].filter((one) => one.startsWith(prefix))) {
17
- cells.delete(cell);
18
- }
19
- flush();
20
- },
21
- dump: () => JSON.stringify(Object.fromEntries(cells)),
22
- keys: (prefix) => [...cells.keys()].filter((cell) => cell.startsWith(prefix)),
23
- peek: (cell) => cells.get(cell),
24
- put: (cell, held) => {
25
- cells.set(cell, held);
26
- flush();
27
- },
28
- });
29
- };
30
-
31
- const Memory = (cells, unproven) => {
3
+ // The ground's own machinery over its cells. Every implementation a host
4
+ // supplies — cells, keychain, cipher, paths, wire — belongs to that host's
5
+ // floor and never to the core (X, Everything is a module).
6
+ const Memory = (cells, unproven, moved) => {
32
7
  const held = (address, key) => cells.peek(`${address}/${key}`);
33
8
  const writerAt = (address) => {
34
9
  const chain = held(address, 'chain/pk');
35
- return chain === undefined ? Tools.pkOf(address) : Buffer.from(chain.value, 'hex');
10
+ return chain === undefined ? Tools.pkOf(address) : Bytes.unhex(chain.value);
36
11
  };
37
12
  return Object.freeze({
38
13
  list: (address, prefix) =>
@@ -42,16 +17,17 @@ const Memory = (cells, unproven) => {
42
17
 
43
18
  replace: ({ address, pk, nextPkHash, sig }) => {
44
19
  const committed = held(address, 'chain/nextPkHash');
45
- if (committed === undefined || Tools.digest(Buffer.from(pk, 'hex')) !== committed.value) {
20
+ if (committed === undefined || Tools.digest(Bytes.unhex(pk)) !== committed.value) {
46
21
  return undefined;
47
22
  }
48
23
  const version = committed.version + 1;
49
24
  const claim = { address, nextPkHash, pk, version };
50
- if (!Tools.verifies(Buffer.from(pk, 'hex'), Tools.canon(claim), sig)) {
25
+ if (!Tools.verifies(Bytes.unhex(pk), Tools.canon(claim), sig)) {
51
26
  return undefined;
52
27
  }
53
28
  cells.put(`${address}/chain/pk`, { value: pk, version });
54
29
  cells.put(`${address}/chain/nextPkHash`, { value: nextPkHash, version });
30
+ moved?.(address);
55
31
  return version;
56
32
  },
57
33
 
@@ -68,45 +44,12 @@ const Memory = (cells, unproven) => {
68
44
  return undefined;
69
45
  }
70
46
  cells.put(`${address}/${key}`, { value, version });
47
+ if (!key.startsWith('refs/')) {
48
+ moved?.(address);
49
+ }
71
50
  return version;
72
51
  },
73
52
  });
74
53
  };
75
54
 
76
- const Clock = (ticks) => {
77
- let at = 0;
78
- return Object.freeze({ now: () => ticks[Math.min(at++, ticks.length - 1)] });
79
- };
80
-
81
- const Entropy = (grains) => {
82
- let at = 0;
83
- return Object.freeze({ grain: () => grains[Math.min(at++, grains.length - 1)] });
84
- };
85
-
86
- const Keychain = (secret) =>
87
- Object.freeze({
88
- seal: (value) => {
89
- try {
90
- return Tools.seal(secret, value);
91
- } catch {
92
- return undefined;
93
- }
94
- },
95
- unseal: (sealed) => Tools.unseal(secret, sealed),
96
- });
97
-
98
- const Dns = () => new Map();
99
-
100
- const Paths = (dns) =>
101
- Object.freeze({
102
- find: (address) => dns.get(address),
103
- publish: (address, endpoint) =>
104
- endpoint === undefined ? dns.delete(address) : dns.set(address, endpoint),
105
- });
106
-
107
- const Wire = () =>
108
- Object.freeze({
109
- carry: async (endpoint, envelope) => (endpoint === undefined ? undefined : endpoint(envelope)),
110
- });
111
-
112
- export { Cells, Clock, Dns, Entropy, Keychain, Memory, Paths, Wire };
55
+ export { Memory };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nervur",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "A distributed system of sovereign beings — one door, one question: by whose authority.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Razvan Gherghina",
@@ -11,12 +11,15 @@
11
11
  },
12
12
  "files": [
13
13
  "*.mjs",
14
- "NOTICE"
14
+ "GETTING-STARTED.md",
15
+ "NOTICE",
16
+ "SECURITY.md"
15
17
  ],
16
18
  "engines": {
17
19
  "node": ">=22"
18
20
  },
19
21
  "dependencies": {
22
+ "@noble/curves": "2.3.0",
20
23
  "graphql": "^17.0.2"
21
24
  }
22
25
  }
package/program.mjs CHANGED
@@ -90,6 +90,7 @@ const Program = (source) => {
90
90
  config: blueprint.config,
91
91
  declares: blueprint.declares,
92
92
  digest: blueprint.digest,
93
+ invites: blueprint.invites,
93
94
  introspect: async (held) => {
94
95
  const filtered = seenBy(held);
95
96
  if (filtered === undefined) {