nervur 0.14.0 → 0.16.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/being.mjs DELETED
@@ -1,639 +0,0 @@
1
- import { ADMIN } from './admin.mjs';
2
- import { OPEN, STRUCTURAL, composed, coreClass } from './blueprint.mjs';
3
- import { Program } from './program.mjs';
4
- import { Bytes, MEASURE, SPAN, Stamp, Tools } from './tools.mjs';
5
- import { Voice } from './voice.mjs';
6
-
7
- const Compartment = ({ memory, voice, address, root, classOf, cipher }) => {
8
- const held = (key) => memory.read(address, key);
9
- const sealed = (key) => {
10
- const kind = classOf(key);
11
- return kind === undefined || OPEN.includes(kind) ? undefined : Tools.derive(root, kind);
12
- };
13
- const put = (key, value) => {
14
- const claim = { address, key, value, version: (held(key)?.version ?? 0) + 1 };
15
- return memory.write({ ...claim, sig: voice.sign(Tools.canon(claim)) });
16
- };
17
-
18
- return Object.freeze({
19
- classOf,
20
- list: (prefix) =>
21
- memory.list(address, prefix).filter((key) => memory.read(address, key)?.value !== null),
22
- read: (key) => {
23
- const cell = held(key);
24
- if (cell === undefined || cell.value === null || classOf(key) === undefined) {
25
- return undefined;
26
- }
27
- const secret = sealed(key);
28
- if (secret === undefined) {
29
- return cell.value;
30
- }
31
- return cipher === undefined ? undefined : cipher.unseal(secret, cell.value);
32
- },
33
- write: (key, plain) => {
34
- if (classOf(key) === undefined) {
35
- return undefined;
36
- }
37
- if (plain === undefined) {
38
- return put(key, null);
39
- }
40
- const secret = sealed(key);
41
- if (secret === undefined) {
42
- return put(key, plain);
43
- }
44
- if (cipher === undefined) {
45
- return undefined;
46
- }
47
- const shut = cipher.seal(secret, plain);
48
- return shut === undefined ? undefined : put(key, shut);
49
- },
50
- });
51
- };
52
-
53
- const spanOf = (given) =>
54
- typeof given === 'number' && Number.isFinite(given) && given > 0 && given < SPAN ? given : SPAN;
55
-
56
- const within = (span, work) => {
57
- let timer;
58
- const lapsed = new Promise((settle) => {
59
- timer = setTimeout(() => settle(undefined), span);
60
- timer.unref?.();
61
- });
62
- return Promise.race([work, lapsed]).finally(() => clearTimeout(timer));
63
- };
64
-
65
- const Fence = () => {
66
- let standing = true;
67
- return Object.freeze({
68
- close: () => {
69
- standing = false;
70
- return undefined;
71
- },
72
- open: () => standing,
73
- });
74
- };
75
-
76
- const fenced = (mine, fence) =>
77
- Object.freeze({
78
- classOf: mine.classOf,
79
- list: mine.list,
80
- read: mine.read,
81
- write: (key, plain) => (fence.open() ? mine.write(key, plain) : undefined),
82
- });
83
-
84
- const custodial = (registry, fence) =>
85
- registry === undefined
86
- ? undefined
87
- : Object.freeze({
88
- ...registry,
89
- create: (one) => (fence.open() ? registry.create(one) : undefined),
90
- destroy: (at) => (fence.open() ? registry.destroy(at) : undefined),
91
- extend: (source) => (fence.open() ? registry.extend(source) : undefined),
92
- upgrade: (one) => (fence.open() ? registry.upgrade(one) : undefined),
93
- });
94
-
95
- const offered = (modules, program, mine, seat) =>
96
- Object.freeze({
97
- ...Object.fromEntries(
98
- (program?.modules ?? [])
99
- .map((one) => one.module)
100
- .filter((name) => name !== 'memory' && modules[name] !== undefined)
101
- .map((name) => [name, modules[name]]),
102
- ),
103
- memory: scoped(mine, program, seat),
104
- });
105
-
106
- const grantable = (program) =>
107
- (program?.rights ?? [...STRUCTURAL, 'MEMBER']).filter((one) => !STRUCTURAL.includes(one));
108
-
109
- const HORIZON = 7_776_000_000;
110
-
111
- const whole = (one) => Number.isSafeInteger(one) && one >= 0;
112
-
113
- const invitation = (plain, rights, program, seat) => {
114
- const count = plain.count ?? 1;
115
- const expiresAt = plain.expiresAt ?? 0;
116
- if (!whole(count) || count < 1 || !whole(expiresAt)) {
117
- return undefined;
118
- }
119
- const now = seat.now();
120
- if (seat.administration) {
121
- return count === 1 && expiresAt > 0 && expiresAt < now + HORIZON
122
- ? { ...plain, count, expiresAt, rights }
123
- : undefined;
124
- }
125
- const ceiling = program?.invites?.(rights);
126
- if (ceiling !== undefined) {
127
- if (ceiling.count > 0 && count > ceiling.count) {
128
- return undefined;
129
- }
130
- if (ceiling.within > 0 && (expiresAt === 0 || expiresAt > now + ceiling.within)) {
131
- return undefined;
132
- }
133
- }
134
- return { ...plain, count, expiresAt, rights };
135
- };
136
-
137
- const referable = (mine, key, plain, program, seat) => {
138
- const rights = plain.rights ?? 'MEMBER';
139
- if (!grantable(program).includes(rights)) {
140
- return undefined;
141
- }
142
- if (key.startsWith('refs/pending/')) {
143
- return invitation(plain, rights, program, seat);
144
- }
145
- const held = mine.read(key);
146
- if (
147
- held?.nextPkHash != null &&
148
- plain.nextPkHash !== undefined &&
149
- plain.nextPkHash !== null &&
150
- plain.nextPkHash !== held.nextPkHash
151
- ) {
152
- return undefined;
153
- }
154
- return {
155
- ...plain,
156
- nextPkHash: plain.nextPkHash ?? held?.nextPkHash ?? null,
157
- rights,
158
- seen: Math.max(plain.seen ?? 0, held?.seen ?? 0),
159
- };
160
- };
161
-
162
- const scoped = (mine, program, seat) =>
163
- Object.freeze({
164
- classOf: mine.classOf,
165
- list: mine.list,
166
- read: mine.read,
167
- write: (key, plain) => {
168
- if (key.startsWith('refs/') && plain !== undefined) {
169
- const guarded = referable(mine, key, plain, program, seat);
170
- return guarded === undefined ? undefined : mine.write(key, guarded);
171
- }
172
- return key.startsWith('refs/') ||
173
- key.startsWith('handles/') ||
174
- program?.declares(key) !== undefined
175
- ? mine.write(key, plain)
176
- : undefined;
177
- },
178
- });
179
-
180
- const rootFor = ({ memory, voice, address, cipher }) => {
181
- const cell = memory.read(address, `keyring/${Tools.address(voice.pk)}`);
182
- const opened = cell === undefined ? undefined : voice.open(cell.value.wrapped, cipher);
183
- return opened === undefined ? undefined : Bytes.unbase64(opened);
184
- };
185
-
186
- const Port = ({ voice, mine, deliver, rights, stamp, lanes, fence }) => {
187
- const send = async (to, op) => {
188
- if (fence !== undefined && !fence.open()) {
189
- return undefined;
190
- }
191
- const turn = (lanes.get(to) ?? Promise.resolve()).then(async () => {
192
- const handle = mine.read(`handles/${to}`);
193
- const envelope = await Tools.envelope(
194
- handle === undefined ? voice : Voice.revive(handle.face),
195
- to,
196
- op,
197
- stamp(),
198
- );
199
- const claim = Tools.digest(
200
- Tools.canon({ from: envelope.from, op: envelope.op, seq: envelope.seq, to: envelope.to }),
201
- );
202
- const reply = await deliver(envelope);
203
- return reply === undefined ? undefined : { ...reply, claim };
204
- });
205
- const settled = turn.then(
206
- () => undefined,
207
- () => undefined,
208
- );
209
- lanes.set(to, settled);
210
- settled.then(() => {
211
- if (lanes.get(to) === settled) {
212
- lanes.delete(to);
213
- }
214
- });
215
- return turn;
216
- };
217
- return Object.freeze({
218
- face: (to) => {
219
- const handle = mine.read(`handles/${to}`);
220
- return Tools.address(handle === undefined ? voice.pk : Voice.revive(handle.face).pk);
221
- },
222
- mint: (to) => {
223
- const minted = Voice();
224
- mine.write(`handles/${to}`, { face: minted.pack() });
225
- return { address: Tools.address(minted.pk), nextPkHash: minted.nextPkHash };
226
- },
227
- release: async (to) => {
228
- const gone = await send(to, { release: {} });
229
- mine.write(`handles/${to}`, undefined);
230
- return gone;
231
- },
232
- rights,
233
- send,
234
- });
235
- };
236
-
237
- const rotates = ({ mine }, from, nextPkHash) => {
238
- const claim = Tools.digest(Tools.pkOf(from));
239
- const held = mine
240
- .list('refs/')
241
- .map((key) => ({ key, referred: mine.read(key) }))
242
- .find(({ referred }) => referred?.nextPkHash === claim);
243
- if (
244
- held === undefined ||
245
- (held.key !== `refs/${from}` && mine.read(`refs/${from}`) !== undefined)
246
- ) {
247
- return undefined;
248
- }
249
- mine.write(`refs/${from}`, { nextPkHash, rights: held.referred.rights });
250
- mine.write(held.key, undefined);
251
- return from;
252
- };
253
-
254
- const SUCCESSION = 'chain/succession/';
255
-
256
- const successionOf = (memory, address) =>
257
- memory
258
- .list(address, SUCCESSION)
259
- .map((key) => memory.read(address, key)?.value)
260
- .filter((one) => one !== undefined)
261
- .sort((one, other) => one.v - other.v);
262
-
263
- const Being = ({
264
- voice,
265
- address,
266
- cipher,
267
- deliver,
268
- modules,
269
- registry,
270
- administration,
271
- span,
272
- vouched,
273
- watch,
274
- }) => {
275
- const held = spanOf(span);
276
- const memory = modules.memory;
277
- const root = rootFor({ memory, voice, address, cipher });
278
- const source = memory.read(address, 'program')?.value;
279
- const program = source === undefined ? undefined : Program(source);
280
- const mine = Compartment({
281
- memory,
282
- voice,
283
- address,
284
- root,
285
- classOf: (key) => program?.classOf(key) ?? coreClass(key),
286
- cipher,
287
- });
288
- const succession = successionOf(memory, address);
289
- const stamp = Stamp();
290
- const seat = Object.freeze({ administration: administration === true, now: stamp });
291
- const lanes = new Map();
292
- const answer = (spoken, claim) => {
293
- const body = Tools.canon(spoken);
294
- if (body.length > MEASURE) {
295
- return undefined;
296
- }
297
- const signed = { body: spoken, sig: voice.sign(Tools.canon({ body: spoken, claim })) };
298
- return succession.length === 0 ? signed : { ...signed, succession };
299
- };
300
- const note = (envelope, outcome) => {
301
- try {
302
- watch?.(
303
- Object.freeze({
304
- from: envelope?.from,
305
- kind: envelope?.op === undefined ? undefined : Object.keys(envelope.op)[0],
306
- outcome,
307
- to: envelope?.to,
308
- }),
309
- );
310
- } catch {
311
- return undefined;
312
- }
313
- };
314
-
315
- return Object.freeze(async (envelope, token) => {
316
- let gated;
317
- const spoken = await (async () => {
318
- try {
319
- const { from, op, seq, sig, to } = envelope;
320
- const claim = Tools.canon({ from, op, seq, to });
321
- const claimDigest = Tools.digest(claim);
322
- const callerPk = Tools.pkOf(from);
323
- const proven =
324
- claim.length <= MEASURE &&
325
- callerPk !== null &&
326
- ((vouched !== undefined && token === vouched) || Tools.verifies(callerPk, claim, sig));
327
- if (to !== address || !proven) {
328
- gated = 'unproven';
329
- return undefined;
330
- }
331
- const self = from === Tools.address(voice.pk);
332
- const row = self ? undefined : mine.read(`refs/${from}`);
333
- if (row !== undefined && !(vouched !== undefined && token === vouched)) {
334
- if (!(seq > (row.seen ?? 0))) {
335
- gated = 'replayed';
336
- return undefined;
337
- }
338
- mine.write(`refs/${from}`, { ...row, seen: seq });
339
- }
340
- const rights = self ? 'SELF' : (row?.rights ?? 'STRANGER');
341
- if (op?.describe !== undefined) {
342
- if (program !== undefined && !program.visible(rights)) {
343
- return undefined;
344
- }
345
- return answer(
346
- {
347
- address,
348
- box: Bytes.base64(voice.boxPk),
349
- digest: program?.digest,
350
- rights,
351
- sdl: program?.sdlFor(rights),
352
- },
353
- claimDigest,
354
- );
355
- }
356
- if (op?.introspect !== undefined) {
357
- const seen = await program?.introspect(rights);
358
- return seen === undefined ? undefined : answer({ introspection: seen }, claimDigest);
359
- }
360
- if (op?.rotate !== undefined) {
361
- const rotated = rotates({ mine }, from, op.rotate.nextPkHash);
362
- return rotated === undefined ? undefined : answer({ rotated }, claimDigest);
363
- }
364
- if (op?.release !== undefined) {
365
- if (row === undefined) {
366
- return undefined;
367
- }
368
- mine.write(`refs/${from}`, undefined);
369
- return answer({ released: from }, claimDigest);
370
- }
371
- if (op?.acceptInvite !== undefined) {
372
- const offer = Tools.digest(Bytes.unbase64(op.acceptInvite.proof));
373
- const pending = mine.read(`refs/pending/${offer}`);
374
- const now = stamp();
375
- if (
376
- pending === undefined ||
377
- !(pending.count > 0) ||
378
- (pending.expiresAt > 0 && now > pending.expiresAt)
379
- ) {
380
- return undefined;
381
- }
382
- mine.write(`refs/pending/${offer}`, { ...pending, count: pending.count - 1 });
383
- mine.write(`refs/${from}`, {
384
- nextPkHash: op.acceptInvite.nextPkHash ?? null,
385
- rights: pending.rights,
386
- seen: seq ?? 0,
387
- });
388
- return answer({ referred: from, rights: pending.rights }, claimDigest);
389
- }
390
- if (op?.ask === undefined || program === undefined || !program.visible(rights)) {
391
- return undefined;
392
- }
393
- const fence = Fence();
394
- const turn = fenced(mine, fence);
395
- const asked = await within(
396
- held,
397
- program.ask({
398
- context: {
399
- from: program.caller ? from : undefined,
400
- modules: offered(modules, program, turn, seat),
401
- port: Port({ voice, mine: turn, deliver, fence, lanes, rights, stamp }),
402
- registry: custodial(registry, fence),
403
- rights,
404
- },
405
- source: op.ask.source,
406
- variables: op.ask.variables,
407
- }),
408
- ).finally(fence.close);
409
- return asked === undefined ? undefined : answer(asked, claimDigest);
410
- } catch {
411
- return undefined;
412
- }
413
- })();
414
- note(envelope, gated ?? (spoken === undefined ? 'mute' : 'answered'));
415
- return spoken;
416
- });
417
- };
418
-
419
- // VII: the chain takes no plain write above version 1, so its rows stand.
420
- const spend = ({ memory, voice, address }) => {
421
- for (const key of memory.list(address, '')) {
422
- const cell = key.startsWith('chain/') ? undefined : memory.read(address, key);
423
- if (cell === undefined || cell.value === null) {
424
- continue;
425
- }
426
- const claim = { address, key, value: null, version: cell.version + 1 };
427
- memory.write({ ...claim, sig: voice.sign(Tools.canon(claim)) });
428
- }
429
- return address;
430
- };
431
-
432
- const make = ({ memory, voice, address, program, cipher }) => {
433
- const root = Tools.secret();
434
- const put = (key, value) => {
435
- const claim = { address, key, value, version: (memory.read(address, key)?.version ?? 0) + 1 };
436
- return memory.write({ ...claim, sig: voice.sign(Tools.canon(claim)) });
437
- };
438
- put('chain/pk', Bytes.hex(voice.pk));
439
- put('chain/nextPkHash', voice.nextPkHash);
440
- put('program', program ?? '({})');
441
- put(`keyring/${Tools.address(voice.pk)}`, {
442
- boxPk: Bytes.base64(voice.boxPk),
443
- wrapped: Voice.sealTo(voice.boxPk, Bytes.base64(root), cipher),
444
- });
445
- const written = program === undefined ? undefined : Program(program);
446
- return Compartment({
447
- memory,
448
- voice,
449
- address,
450
- root,
451
- classOf: (key) => written?.classOf(key) ?? coreClass(key),
452
- cipher,
453
- });
454
- };
455
-
456
- const Registry = ({
457
- memory,
458
- adminMine,
459
- address,
460
- cipher,
461
- deliver,
462
- modules,
463
- modulesFor,
464
- announce,
465
- forget,
466
- span,
467
- vouched,
468
- attest,
469
- watch,
470
- extended,
471
- }) => {
472
- const live = new Map();
473
- const stranded = (at, written) => {
474
- const source = memory.read(at, 'program')?.value;
475
- const standing = source === undefined ? undefined : Program(source);
476
- return memory
477
- .list(at, '')
478
- .filter((key) => memory.read(at, key)?.value !== null)
479
- .filter((key) => {
480
- const under = written.classOf(key);
481
- const was = standing?.classOf(key) ?? coreClass(key);
482
- return under === undefined || (was !== undefined && was !== under);
483
- });
484
- };
485
- const activate = (at) => {
486
- if (live.has(at)) {
487
- return live.get(at);
488
- }
489
- const held = adminMine.read(`identity/custody/${at}`);
490
- const voice = held === undefined ? undefined : Voice.revive(held);
491
- if (voice === undefined) {
492
- return undefined;
493
- }
494
- const declared = (Program(memory.read(at, 'program')?.value)?.modules ?? []).map(
495
- (one) => one.module,
496
- );
497
- const door = Being({
498
- address: at,
499
- cipher,
500
- deliver,
501
- modules: modulesFor(voice, declared),
502
- span,
503
- vouched,
504
- voice,
505
- watch,
506
- });
507
- live.set(at, door);
508
- return door;
509
- };
510
- return Object.freeze({
511
- activate,
512
- attest,
513
- create: ({ program, refs = [], data = {}, bindings, config } = {}) => {
514
- const written = program === undefined ? undefined : Program(program);
515
- if (program !== undefined && written === undefined) {
516
- return undefined;
517
- }
518
- const missing = (written?.modules ?? []).filter((one) => !modules.has(one.module));
519
- if (missing.length > 0) {
520
- return undefined;
521
- }
522
- const brought = written?.bindings(bindings);
523
- const settings = written?.config(config);
524
- if (written !== undefined && (brought === undefined || settings === undefined)) {
525
- return undefined;
526
- }
527
- const allowed = grantable(written);
528
- if (refs.some((ref) => !allowed.includes(ref.rights ?? 'MEMBER'))) {
529
- return undefined;
530
- }
531
- if (refs.some((ref) => typeof ref.address !== 'string' || Tools.pkOf(ref.address) === null)) {
532
- return undefined;
533
- }
534
- const voice = Voice();
535
- const address = Tools.address(voice.pk);
536
- const mine = make({ memory, voice, address, program, cipher });
537
- for (const [key, value] of Object.entries(data)) {
538
- if (mine.write(key, value) === undefined) {
539
- forget(address);
540
- return undefined;
541
- }
542
- }
543
- if (written !== undefined) {
544
- mine.write('config', settings);
545
- for (const [module, bound] of Object.entries(brought)) {
546
- mine.write(`bindings/${module}`, bound);
547
- }
548
- }
549
- for (const ref of refs) {
550
- mine.write(`refs/${ref.address}`, {
551
- nextPkHash: ref.nextPkHash ?? null,
552
- rights: ref.rights ?? 'MEMBER',
553
- });
554
- }
555
- adminMine.write(`identity/custody/${address}`, voice.pack());
556
- announce(address);
557
- return address;
558
- },
559
- deactivate: (at) => live.delete(at),
560
- extend: (source) => {
561
- const program = source === undefined || source === null ? ADMIN : composed(ADMIN, source);
562
- const written = program === undefined ? undefined : Program(program);
563
- if (written === undefined) {
564
- return undefined;
565
- }
566
- if (written.modules.some((one) => !modules.has(one.module))) {
567
- return undefined;
568
- }
569
- if (stranded(address, written).length > 0) {
570
- return undefined;
571
- }
572
- if (adminMine.write('program', program) === undefined) {
573
- return undefined;
574
- }
575
- extended?.();
576
- return address;
577
- },
578
- upgrade: ({ address: target, program }) => {
579
- if (target === address) {
580
- return undefined;
581
- }
582
- const written = program === undefined ? undefined : Program(program);
583
- if (written === undefined) {
584
- return undefined;
585
- }
586
- if (stranded(target, written).length > 0) {
587
- return undefined;
588
- }
589
- const held = adminMine.read(`identity/custody/${target}`);
590
- const voice = held === undefined ? undefined : Voice.revive(held);
591
- if (voice === undefined) {
592
- return undefined;
593
- }
594
- const claim = {
595
- address: target,
596
- key: 'program',
597
- value: program,
598
- version: (memory.read(target, 'program')?.version ?? 0) + 1,
599
- };
600
- if (memory.write({ ...claim, sig: voice.sign(Tools.canon(claim)) }) === undefined) {
601
- return undefined;
602
- }
603
- live.delete(target);
604
- return target;
605
- },
606
- destroy: (at) => {
607
- const held = adminMine.read(`identity/custody/${at}`);
608
- const voice = held === undefined ? undefined : Voice.revive(held);
609
- if (voice === undefined) {
610
- return undefined;
611
- }
612
- adminMine.write(`identity/custody/${at}`, undefined);
613
- live.delete(at);
614
- forget(at, voice);
615
- return at;
616
- },
617
- residents: () => {
618
- const aliasFor = new Map();
619
- for (const key of adminMine.list('aliases/')) {
620
- const named = adminMine.read(key);
621
- if (typeof named === 'string') {
622
- aliasFor.set(named, key.slice('aliases/'.length));
623
- }
624
- }
625
- return adminMine.list('identity/custody/').map((key) => {
626
- const at = key.slice('identity/custody/'.length);
627
- const digest = Program(memory.read(at, 'program')?.value)?.digest ?? null;
628
- return {
629
- address: at,
630
- alias: aliasFor.get(at) ?? null,
631
- active: live.has(at),
632
- digest,
633
- };
634
- });
635
- },
636
- });
637
- };
638
-
639
- export { Being, Compartment, Registry, SUCCESSION, make, rootFor, spend, successionOf };