nervur 0.11.0 → 0.12.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/README.md CHANGED
@@ -134,6 +134,17 @@ after. Both are custody's own calls on the shelf, never doors — which is
134
134
  what running-is-custody looks like from the outside, stated rather than
135
135
  hidden.
136
136
 
137
+ `unproven()` is custody's third call, and it is a read: the addresses whose
138
+ writes have stopped proving, with when each was first and last seen and how
139
+ many writes have been refused. A write is proven against the key on the
140
+ being's `chain/pk` cell, so whoever holds the shelf can overwrite that cell
141
+ and freeze a being's memory while it answers on, correctly signed and
142
+ indistinguishable to every caller. The ground records that in its own host
143
+ rows under `ground/` — no being reads them, nothing goes over the wire — and
144
+ this is where a host reads them back. Show it to whoever operates the
145
+ machine; a ground that has stopped proving its own writes is a custody
146
+ incident, not a bug.
147
+
137
148
  **`serve` is the client, and it has four faces of one door.** `pointer(at)`
138
149
  is a plain object with one function per field, generated from the schema that
139
150
  caller is allowed to see. `gql(at)` is the raw document. `mcp(at)` is the
@@ -215,7 +226,8 @@ silence, and silence is something local delivery never answers with.
215
226
  activates and destroys, reachable only through the admin being.
216
227
  - [admin.mjs](admin.mjs) — the administrative program every ground installs
217
228
  for itself, aliased `Ground`: census, create, activate, destroy, upgrade,
218
- alias, attest, and behind `SELF` alone invite, drop and extend. Upgrade is
229
+ alias, attest, invite, drop, wear, and behind `SELF` alone extend; `wears`
230
+ answers any stranger with the livery being the ground wears. Upgrade is
219
231
  custody moving the one cell that was always the core's to move: the
220
232
  program is replaced in place — address, cells, refs and chain surviving —
221
233
  refused whole when a cell the being holds would go undeclared, and visible
package/admin.mjs CHANGED
@@ -1,7 +1,10 @@
1
1
  const ADMIN = `({
2
2
  name: 'nervur.ground',
3
3
  needs: { modules: [{ module: 'memory', contract: 'nervur.memory' }] },
4
- memory: { 'aliases/*': { type: 'String', class: 'data' } },
4
+ memory: {
5
+ 'aliases/*': { type: 'String', class: 'data' },
6
+ wears: { type: 'String', class: 'data' },
7
+ },
5
8
  interface: \`
6
9
  input RefInput { address: String!, nextPkHash: String, rights: String }
7
10
  type Resident { address: String!, alias: String, digest: String, active: Boolean! }
@@ -11,6 +14,7 @@ const ADMIN = `({
11
14
  aliasOf(name: String!): String @rights(is: [SELF, MEMBER])
12
15
  attest(module: String!): Boolean @rights(is: [SELF, MEMBER])
13
16
  opened: Boolean @rights(is: [SELF, MEMBER, STRANGER])
17
+ wears: String @rights(is: [SELF, MEMBER, STRANGER])
14
18
  }
15
19
  type Mutation {
16
20
  invite(address: String!, nextPkHash: String, rights: String): Boolean @rights(is: [SELF, MEMBER])
@@ -22,6 +26,7 @@ const ADMIN = `({
22
26
  destroy(address: String!): String @rights(is: [SELF, MEMBER])
23
27
  upgrade(address: String!, program: String!): Boolean @rights(is: [SELF, MEMBER])
24
28
  alias(name: String!, address: String!): Boolean @rights(is: [SELF, MEMBER])
29
+ wear(address: String): Boolean @rights(is: [SELF, MEMBER])
25
30
  }
26
31
  \`,
27
32
  resolvers: {
@@ -37,6 +42,7 @@ const ADMIN = `({
37
42
  aliasOf: (_, { name }, { modules }) => modules.memory.read('aliases/' + name),
38
43
  attest: (_, { module }, { registry }) => registry.attest(module),
39
44
  opened: (_, __, { modules }) => modules.memory.list('refs/').length > 0,
45
+ wears: (_, __, { modules }) => modules.memory.read('wears') ?? null,
40
46
  },
41
47
  Mutation: {
42
48
  invite: (_, { address, nextPkHash, rights }, { modules }) =>
@@ -66,6 +72,10 @@ const ADMIN = `({
66
72
  registry.upgrade({ address, program }) !== undefined,
67
73
  alias: (_, { name, address }, { modules }) =>
68
74
  modules.memory.write('aliases/' + name, address) !== undefined,
75
+ wear: (_, { address }, { modules }) => {
76
+ modules.memory.write('wears', address ?? undefined);
77
+ return modules.memory.read('wears') === (address ?? undefined);
78
+ },
69
79
  },
70
80
  },
71
81
  })`;
package/being.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ADMIN } from './admin.mjs';
2
2
  import { OPEN, STRUCTURAL, composed, coreClass } from './blueprint.mjs';
3
3
  import { Program } from './program.mjs';
4
- import { MEASURE, Stamp, Tools } from './tools.mjs';
4
+ import { MEASURE, SPAN, Stamp, Tools } from './tools.mjs';
5
5
  import { Voice } from './voice.mjs';
6
6
 
7
7
  const Compartment = ({ memory, voice, address, root, classOf }) => {
@@ -40,6 +40,48 @@ const Compartment = ({ memory, voice, address, root, classOf }) => {
40
40
  });
41
41
  };
42
42
 
43
+ const spanOf = (given) =>
44
+ typeof given === 'number' && Number.isFinite(given) && given > 0 && given < SPAN ? given : SPAN;
45
+
46
+ const within = (span, work) => {
47
+ let timer;
48
+ const lapsed = new Promise((settle) => {
49
+ timer = setTimeout(() => settle(undefined), span);
50
+ timer.unref?.();
51
+ });
52
+ return Promise.race([work, lapsed]).finally(() => clearTimeout(timer));
53
+ };
54
+
55
+ const Fence = () => {
56
+ let standing = true;
57
+ return Object.freeze({
58
+ close: () => {
59
+ standing = false;
60
+ return undefined;
61
+ },
62
+ open: () => standing,
63
+ });
64
+ };
65
+
66
+ const fenced = (mine, fence) =>
67
+ Object.freeze({
68
+ classOf: mine.classOf,
69
+ list: mine.list,
70
+ read: mine.read,
71
+ write: (key, plain) => (fence.open() ? mine.write(key, plain) : undefined),
72
+ });
73
+
74
+ const custodial = (registry, fence) =>
75
+ registry === undefined
76
+ ? undefined
77
+ : Object.freeze({
78
+ ...registry,
79
+ create: (one) => (fence.open() ? registry.create(one) : undefined),
80
+ destroy: (at) => (fence.open() ? registry.destroy(at) : undefined),
81
+ extend: (source) => (fence.open() ? registry.extend(source) : undefined),
82
+ upgrade: (one) => (fence.open() ? registry.upgrade(one) : undefined),
83
+ });
84
+
43
85
  const offered = (modules, program, mine) =>
44
86
  Object.freeze({
45
87
  ...Object.fromEntries(
@@ -95,10 +137,11 @@ const rootFor = ({ memory, voice, address }) => {
95
137
  return opened === undefined ? undefined : Buffer.from(opened, 'base64');
96
138
  };
97
139
 
98
- const Port = ({ voice, mine, deliver, rights }) => {
99
- const stamp = Stamp();
100
- const lanes = new Map();
140
+ const Port = ({ voice, mine, deliver, rights, stamp, lanes, fence }) => {
101
141
  const send = async (to, op) => {
142
+ if (fence !== undefined && !fence.open()) {
143
+ return undefined;
144
+ }
102
145
  const turn = (lanes.get(to) ?? Promise.resolve()).then(() => {
103
146
  const handle = mine.read(`handles/${to}`);
104
147
  return deliver(
@@ -163,7 +206,8 @@ const successionOf = (memory, address) =>
163
206
  .filter((one) => one !== undefined)
164
207
  .sort((one, other) => one.v - other.v);
165
208
 
166
- const Being = ({ voice, address, deliver, modules, registry, vouched, watch }) => {
209
+ const Being = ({ voice, address, deliver, modules, registry, span, vouched, watch }) => {
210
+ const held = spanOf(span);
167
211
  const memory = modules.memory;
168
212
  const root = rootFor({ memory, voice, address });
169
213
  const source = memory.read(address, 'program')?.value;
@@ -176,6 +220,8 @@ const Being = ({ voice, address, deliver, modules, registry, vouched, watch }) =
176
220
  classOf: (key) => program?.classOf(key) ?? coreClass(key),
177
221
  });
178
222
  const succession = successionOf(memory, address);
223
+ const stamp = Stamp();
224
+ const lanes = new Map();
179
225
  const answer = (spoken) => {
180
226
  const body = Tools.canon(spoken);
181
227
  if (body.length > MEASURE) {
@@ -267,17 +313,22 @@ const Being = ({ voice, address, deliver, modules, registry, vouched, watch }) =
267
313
  if (op?.ask === undefined || program === undefined || !program.visible(rights)) {
268
314
  return undefined;
269
315
  }
270
- const asked = await program.ask({
271
- context: {
272
- from: program.caller ? from : undefined,
273
- modules: offered(modules, program, mine),
274
- port: Port({ voice, mine, deliver, rights }),
275
- registry,
276
- rights,
277
- },
278
- source: op.ask.source,
279
- variables: op.ask.variables,
280
- });
316
+ const fence = Fence();
317
+ const turn = fenced(mine, fence);
318
+ const asked = await within(
319
+ held,
320
+ program.ask({
321
+ context: {
322
+ from: program.caller ? from : undefined,
323
+ modules: offered(modules, program, turn),
324
+ port: Port({ voice, mine: turn, deliver, fence, lanes, rights, stamp }),
325
+ registry: custodial(registry, fence),
326
+ rights,
327
+ },
328
+ source: op.ask.source,
329
+ variables: op.ask.variables,
330
+ }),
331
+ ).finally(fence.close);
281
332
  return asked === undefined ? undefined : answer(asked);
282
333
  } catch {
283
334
  return undefined;
@@ -288,6 +339,19 @@ const Being = ({ voice, address, deliver, modules, registry, vouched, watch }) =
288
339
  });
289
340
  };
290
341
 
342
+ // VII: the chain takes no plain write above version 1, so its rows stand.
343
+ const spend = ({ memory, voice, address }) => {
344
+ for (const key of memory.list(address, '')) {
345
+ const cell = key.startsWith('chain/') ? undefined : memory.read(address, key);
346
+ if (cell === undefined || cell.value === null) {
347
+ continue;
348
+ }
349
+ const claim = { address, key, value: null, version: cell.version + 1 };
350
+ memory.write({ ...claim, sig: voice.sign(Tools.canon(claim)) });
351
+ }
352
+ return address;
353
+ };
354
+
291
355
  const make = ({ memory, voice, address, program }) => {
292
356
  const root = Tools.secret();
293
357
  const put = (key, value) => {
@@ -320,16 +384,24 @@ const Registry = ({
320
384
  modulesFor,
321
385
  announce,
322
386
  forget,
387
+ span,
323
388
  vouched,
324
389
  attest,
325
390
  watch,
326
391
  }) => {
327
392
  const live = new Map();
328
- const orphans = (at, written) =>
329
- memory
393
+ const stranded = (at, written) => {
394
+ const source = memory.read(at, 'program')?.value;
395
+ const standing = source === undefined ? undefined : Program(source);
396
+ return memory
330
397
  .list(at, '')
331
398
  .filter((key) => memory.read(at, key)?.value !== null)
332
- .filter((key) => written.classOf(key) === undefined);
399
+ .filter((key) => {
400
+ const under = written.classOf(key);
401
+ const was = standing?.classOf(key) ?? coreClass(key);
402
+ return under === undefined || (was !== undefined && was !== under);
403
+ });
404
+ };
333
405
  const activate = (at) => {
334
406
  if (live.has(at)) {
335
407
  return live.get(at);
@@ -346,6 +418,7 @@ const Registry = ({
346
418
  address: at,
347
419
  deliver,
348
420
  modules: modulesFor(voice, declared),
421
+ span,
349
422
  vouched,
350
423
  voice,
351
424
  watch,
@@ -412,7 +485,7 @@ const Registry = ({
412
485
  if (written.modules.some((one) => !modules.has(one.module))) {
413
486
  return undefined;
414
487
  }
415
- if (orphans(address, written).length > 0) {
488
+ if (stranded(address, written).length > 0) {
416
489
  return undefined;
417
490
  }
418
491
  return adminMine.write('program', program) === undefined ? undefined : address;
@@ -425,7 +498,7 @@ const Registry = ({
425
498
  if (written === undefined) {
426
499
  return undefined;
427
500
  }
428
- if (orphans(target, written).length > 0) {
501
+ if (stranded(target, written).length > 0) {
429
502
  return undefined;
430
503
  }
431
504
  const held = adminMine.read(`identity/custody/${target}`);
@@ -446,12 +519,14 @@ const Registry = ({
446
519
  return target;
447
520
  },
448
521
  destroy: (at) => {
449
- if (adminMine.read(`identity/custody/${at}`) === undefined) {
522
+ const held = adminMine.read(`identity/custody/${at}`);
523
+ const voice = held === undefined ? undefined : Voice.revive(held);
524
+ if (voice === undefined) {
450
525
  return undefined;
451
526
  }
452
527
  adminMine.write(`identity/custody/${at}`, undefined);
453
528
  live.delete(at);
454
- forget(at);
529
+ forget(at, voice);
455
530
  return at;
456
531
  },
457
532
  residents: () => {
@@ -476,4 +551,4 @@ const Registry = ({
476
551
  });
477
552
  };
478
553
 
479
- export { Being, Compartment, Registry, SUCCESSION, make, rootFor };
554
+ export { Being, Compartment, Registry, SUCCESSION, make, rootFor, spend };
package/blueprint.mjs CHANGED
@@ -174,25 +174,120 @@ const doorsIn = (sdl) =>
174
174
  .definitions.filter((one) => doorHolder(one) && ['Mutation', 'Query'].includes(one.name.value))
175
175
  .flatMap((one) => (one.fields ?? []).map((field) => field.name.value));
176
176
 
177
- const composed = (baseSource, addedSource) => {
177
+ const rootsIn = (document, schema) => {
178
+ const roots = [
179
+ schema.getQueryType()?.name,
180
+ schema.getMutationType()?.name,
181
+ schema.getSubscriptionType()?.name,
182
+ ].filter((one) => one !== undefined);
183
+ return document.definitions
184
+ .filter((one) => doorHolder(one) && roots.includes(one.name.value))
185
+ .flatMap((one) => one.fields ?? []);
186
+ };
187
+
188
+ // Names why Blueprint would refuse; Blueprint itself rules, and never reads
189
+ // this. The bench binds the two: every cause named here is one it refuses.
190
+ const faults = (source) => {
191
+ const trespass = reads(source);
192
+ if (trespass.length > 0) {
193
+ return [`it reads ambient power no blueprint may hold: ${trespass.join(', ')} (VIII)`];
194
+ }
195
+ let written;
196
+ try {
197
+ written = evaluated(source);
198
+ } catch (error) {
199
+ return [`it is not an expression settling a blueprint object: ${error.message} (VIII)`];
200
+ }
201
+ if (written === null || typeof written !== 'object') {
202
+ return ['it settles no blueprint object (VIII)'];
203
+ }
204
+ const declares = /enum\s+Rights\b/.test(written.interface) ? '' : DEFAULT_RIGHTS;
205
+ const sdl = [RIGHTS_DIRECTIVE, declares, written.interface].filter(Boolean).join('\n');
206
+ let document;
207
+ let schema;
208
+ try {
209
+ document = parse(sdl);
210
+ schema = buildSchema(sdl);
211
+ } catch (error) {
212
+ return [`its schema does not compile: ${error.message} (VIII)`];
213
+ }
214
+ const rights = [
215
+ ...new Set([
216
+ ...STRUCTURAL,
217
+ ...(schema.getType('Rights')?.getValues() ?? []).map((one) => one.name),
218
+ ]),
219
+ ];
220
+ const found = [];
221
+ for (const [key, cell] of Object.entries(written.memory ?? {})) {
222
+ if (coreClass(key) !== undefined) {
223
+ found.push(`the cell ${key} is the core's own, and no program declares it (VIII)`);
224
+ } else if (!CLASSES.includes(cell?.class)) {
225
+ found.push(`the cell ${key} names no class the memory law holds: ${cell?.class} (VIII)`);
226
+ }
227
+ }
228
+ const bare = [];
229
+ const outside = new Map();
230
+ for (const field of rootsIn(document, schema)) {
231
+ const allowed = rightsOf(field);
232
+ if (allowed === undefined) {
233
+ bare.push(field.name.value);
234
+ continue;
235
+ }
236
+ for (const held of allowed.filter((one) => !rights.includes(one))) {
237
+ outside.set(held, [...(outside.get(held) ?? []), field.name.value]);
238
+ }
239
+ }
240
+ if (bare.length > 0) {
241
+ found.push(`the door ${bare.join(', ')} declares no rights, and every door declares them (VI)`);
242
+ }
243
+ for (const [held, doors] of outside) {
244
+ found.push(
245
+ `the Rights enum does not name ${held}, which ${doors.length} door${doors.length === 1 ? '' : 's'} stand behind: ${doors.join(', ')} (VI)`,
246
+ );
247
+ }
248
+ return found;
249
+ };
250
+
251
+ const composition = (baseSource, addedSource) => {
252
+ const refused = (reason) => ({ refused: reason });
178
253
  try {
179
254
  const base = evaluated(baseSource);
180
255
  const added = evaluated(addedSource);
256
+ if (added === null || typeof added !== 'object') {
257
+ return refused('the part settles no blueprint object (VIII)');
258
+ }
181
259
  const standard = doorsIn(base.interface);
182
- if (doorsIn(added.interface).some((one) => standard.includes(one))) {
183
- return undefined;
260
+ const collides = [...new Set(doorsIn(added.interface).filter((one) => standard.includes(one)))];
261
+ if (collides.length > 0) {
262
+ return refused(
263
+ `the part declares the door ${collides.join(', ')}, which the administration already names — a standard door name is reserved on both roots (X)`,
264
+ );
184
265
  }
185
- const cells = { ...(base.memory ?? {}), ...(added.memory ?? {}) };
186
- if (
187
- Object.keys(base.memory ?? {}).some((key) => (added.memory ?? {})[key] !== undefined) ||
188
- (added.needs?.config !== undefined && base.needs?.config !== undefined)
189
- ) {
190
- return undefined;
266
+ const kept = Object.keys(base.memory ?? {}).filter(
267
+ (key) => (added.memory ?? {})[key] !== undefined,
268
+ );
269
+ if (kept.length > 0) {
270
+ return refused(
271
+ `the part declares the cell ${kept.join(', ')}, which the administration already keeps (X)`,
272
+ );
273
+ }
274
+ if (added.needs?.config !== undefined && base.needs?.config !== undefined) {
275
+ return refused(
276
+ 'the part declares needs.config, and the program it composes with declares one too — a program holds one config (X)',
277
+ );
191
278
  }
192
279
  const slots = [...(base.needs?.modules ?? []), ...(added.needs?.modules ?? [])];
193
- if (new Set(slots.map((one) => one.module)).size !== slots.length) {
194
- return undefined;
280
+ const twice = [
281
+ ...new Set(
282
+ slots.map((one) => one.module).filter((name, at, all) => all.indexOf(name) !== at),
283
+ ),
284
+ ];
285
+ if (twice.length > 0) {
286
+ return refused(
287
+ `the part declares the module slot ${twice.join(', ')}, which the administration already holds (X)`,
288
+ );
195
289
  }
290
+ const cells = { ...(base.memory ?? {}), ...(added.memory ?? {}) };
196
291
  const needs = {
197
292
  ...(base.needs ?? {}),
198
293
  ...(added.needs ?? {}),
@@ -208,18 +303,22 @@ const composed = (baseSource, addedSource) => {
208
303
  .join(', ')} }`,
209
304
  )
210
305
  .join(', ');
211
- return `({
306
+ const source = `({
212
307
  name: ${JSON.stringify(added.name)},
213
308
  needs: ${JSON.stringify(needs)},
214
309
  memory: ${JSON.stringify(cells)},
215
310
  interface: ${JSON.stringify(`${base.interface}\n${added.interface}`)},
216
311
  resolvers: { ${written} },
217
312
  })`;
218
- } catch {
219
- return undefined;
313
+ const found = faults(source);
314
+ return found.length > 0 ? refused(found.join('; ')) : { source };
315
+ } catch (error) {
316
+ return refused(`the part does not compose: ${error.message} (VIII)`);
220
317
  }
221
318
  };
222
319
 
320
+ const composed = (baseSource, addedSource) => composition(baseSource, addedSource).source;
321
+
223
322
  const inputCheck = (schema, typeName, given) => {
224
323
  const type = schema.getType(typeName);
225
324
  if (type?.getFields === undefined) {
@@ -270,18 +369,10 @@ const Blueprint = (source) => {
270
369
  if (misdeclared.length > 0) {
271
370
  return undefined;
272
371
  }
273
- const roots = [
274
- schema.getQueryType()?.name,
275
- schema.getMutationType()?.name,
276
- schema.getSubscriptionType()?.name,
277
- ].filter((one) => one !== undefined);
278
- const undeclared = document.definitions
279
- .filter((one) => doorHolder(one) && roots.includes(one.name.value))
280
- .flatMap((one) => one.fields ?? [])
281
- .filter((one) => {
282
- const allowed = rightsOf(one);
283
- return allowed === undefined || allowed.some((held) => !rights.includes(held));
284
- });
372
+ const undeclared = rootsIn(document, schema).filter((one) => {
373
+ const allowed = rightsOf(one);
374
+ return allowed === undefined || allowed.some((held) => !rights.includes(held));
375
+ });
285
376
  if (undeclared.length > 0) {
286
377
  return undefined;
287
378
  }
@@ -356,4 +447,16 @@ const Blueprint = (source) => {
356
447
  }
357
448
  };
358
449
 
359
- export { Blueprint, CLASSES, CORE_ONLY, FORBIDDEN, OPEN, STRUCTURAL, composed, coreClass, reads };
450
+ export {
451
+ Blueprint,
452
+ CLASSES,
453
+ CORE_ONLY,
454
+ FORBIDDEN,
455
+ OPEN,
456
+ STRUCTURAL,
457
+ composed,
458
+ composition,
459
+ coreClass,
460
+ faults,
461
+ reads,
462
+ };
package/ground.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ADMIN } from './admin.mjs';
2
- import { Being, Compartment, Registry, SUCCESSION, make, rootFor } from './being.mjs';
2
+ import { Being, Compartment, Registry, SUCCESSION, make, rootFor, spend } from './being.mjs';
3
3
  import { Blueprint, composed, coreClass } from './blueprint.mjs';
4
4
  import { Client } from './client.mjs';
5
5
  import { Contracts } from './contract.mjs';
@@ -13,6 +13,36 @@ const GROUND = 'Ground';
13
13
 
14
14
  const FLOOR = ['blueprints', 'cells', 'contracts', 'keychain', 'paths', 'watch', 'wire'];
15
15
 
16
+ const UNPROVEN = 'ground/unproven/';
17
+
18
+ const HEIR = 'ground/heir';
19
+
20
+ const commits = (cells, bundle) => {
21
+ const boxPk = bundle?.heirBoxPk;
22
+ if (typeof boxPk !== 'string' || Buffer.from(boxPk, 'base64').length !== 32) {
23
+ return undefined;
24
+ }
25
+ cells.put(HEIR, { boxPk, nextPkHash: bundle.nextPkHash });
26
+ return undefined;
27
+ };
28
+
29
+ const unprovenAt = (cells) => (at) => {
30
+ try {
31
+ const stood = cells.peek(`${UNPROVEN}${at}`);
32
+ const seen = Date.now();
33
+ cells.put(`${UNPROVEN}${at}`, {
34
+ at,
35
+ last: seen,
36
+ seen: (stood?.seen ?? 0) + 1,
37
+ since: stood?.since ?? seen,
38
+ });
39
+ } catch {
40
+ return undefined;
41
+ }
42
+ };
43
+
44
+ const unprovenRows = (cells) => cells.keys(UNPROVEN).map((row) => cells.peek(row));
45
+
16
46
  const install = ({ cells, keychain, program, bundle }) => {
17
47
  if (bundle?.next !== undefined) {
18
48
  return undefined;
@@ -29,6 +59,7 @@ const install = ({ cells, keychain, program, bundle }) => {
29
59
  }
30
60
  make({ memory, voice, address, program });
31
61
  cells.put('ground/admin', { address, sealed });
62
+ commits(cells, bundle);
32
63
  return address;
33
64
  };
34
65
 
@@ -72,10 +103,13 @@ const succeeds = ({ adminMine, adminVoice, address, bundle, cells, keychain, mem
72
103
  return undefined;
73
104
  }
74
105
  cells.put('ground/admin', { address, sealed });
106
+ commits(cells, bundle);
75
107
  return Tools.address(heir.pk);
76
108
  };
77
109
 
78
- const Ground = (modules = {}) => {
110
+ // Never a key in the modules object: everything there that is not in FLOOR
111
+ // becomes a module handed to programs under that name.
112
+ const Ground = (modules = {}, { span } = {}) => {
79
113
  const cells = modules.cells;
80
114
  const keychain = modules.keychain;
81
115
  if (cells === undefined || keychain?.seal === undefined || keychain?.unseal === undefined) {
@@ -121,7 +155,7 @@ const Ground = (modules = {}) => {
121
155
  };
122
156
 
123
157
  const address = installed.address;
124
- const memory = Memory(cells);
158
+ const memory = Memory(cells, unprovenAt(cells));
125
159
  const paths = modules.paths ?? Paths(new Map());
126
160
  const wire = modules.wire ?? Wire();
127
161
 
@@ -185,9 +219,13 @@ const Ground = (modules = {}) => {
185
219
  return door(envelope, token);
186
220
  };
187
221
  const announce = (at) => paths.publish(at, receive);
188
- const forget = (at) => {
222
+ const forget = (at, voice) => {
189
223
  paths.publish(at, undefined);
190
- cells.drop(`${at}/`);
224
+ if (voice === undefined) {
225
+ cells.drop(`${at}/`);
226
+ return undefined;
227
+ }
228
+ return spend({ memory, voice, address: at });
191
229
  };
192
230
 
193
231
  const attest = async (name) => {
@@ -213,6 +251,7 @@ const Ground = (modules = {}) => {
213
251
  modulesFor,
214
252
  announce,
215
253
  forget,
254
+ span,
216
255
  vouched,
217
256
  attest,
218
257
  watch,
@@ -223,6 +262,7 @@ const Ground = (modules = {}) => {
223
262
  deliver: (envelope) => deliver(envelope),
224
263
  modules: modulesFor(adminVoice, adminDeclared()),
225
264
  registry,
265
+ span,
226
266
  vouched,
227
267
  voice: adminVoice,
228
268
  watch,
@@ -268,6 +308,14 @@ const Ground = (modules = {}) => {
268
308
  },
269
309
  });
270
310
  },
311
+ seated: (at, from) => {
312
+ if (at === from) {
313
+ return true;
314
+ }
315
+ const row = held(at) ? memory.read(at, `refs/${from}`)?.value : undefined;
316
+ return row !== undefined && row !== null;
317
+ },
318
+ unproven: () => unprovenRows(cells),
271
319
  });
272
320
  };
273
321
 
package/index.mjs CHANGED
@@ -1,10 +1,20 @@
1
1
  export { ADMIN } from './admin.mjs';
2
- export { Blueprint, CLASSES, CORE_ONLY, OPEN, STRUCTURAL, coreClass, reads } from './blueprint.mjs';
2
+ export {
3
+ Blueprint,
4
+ CLASSES,
5
+ CORE_ONLY,
6
+ OPEN,
7
+ STRUCTURAL,
8
+ composition,
9
+ coreClass,
10
+ faults,
11
+ reads,
12
+ } from './blueprint.mjs';
3
13
  export { Client, speaking } from './client.mjs';
4
14
  export { Contract, Contracts } from './contract.mjs';
5
15
  export { FLOOR, GROUND, Ground } from './ground.mjs';
6
16
  export { Cells, Clock, Dns, Entropy, Keychain, Memory, Paths, Wire } from './modules.mjs';
7
17
  export { Mcp, Pointer } from './pointer.mjs';
8
18
  export { Program } from './program.mjs';
9
- export { MEASURE, Stamp, Tools } from './tools.mjs';
19
+ export { MEASURE, SPAN, Stamp, Tools } from './tools.mjs';
10
20
  export { Voice } from './voice.mjs';
package/modules.mjs CHANGED
@@ -28,7 +28,7 @@ const Cells = ({ file } = {}) => {
28
28
  });
29
29
  };
30
30
 
31
- const Memory = (cells) => {
31
+ const Memory = (cells, unproven) => {
32
32
  const held = (address, key) => cells.peek(`${address}/${key}`);
33
33
  const writerAt = (address) => {
34
34
  const chain = held(address, 'chain/pk');
@@ -64,6 +64,7 @@ const Memory = (cells) => {
64
64
  return undefined;
65
65
  }
66
66
  if (!Tools.verifies(writer, Tools.canon({ address, key, value, version }), sig)) {
67
+ unproven?.(address);
67
68
  return undefined;
68
69
  }
69
70
  cells.put(`${address}/${key}`, { value, version });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nervur",
3
- "version": "0.11.0",
3
+ "version": "0.12.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",
package/tools.mjs CHANGED
@@ -15,6 +15,8 @@ const VERSION_G = 6 << 3;
15
15
 
16
16
  const MEASURE = 8_388_608;
17
17
 
18
+ const SPAN = 60_000;
19
+
18
20
  const ordered = (value) =>
19
21
  value === null || typeof value !== 'object'
20
22
  ? value
@@ -138,4 +140,4 @@ const Stamp = () => {
138
140
  };
139
141
  };
140
142
 
141
- export { MEASURE, Stamp, Tools };
143
+ export { MEASURE, SPAN, Stamp, Tools };