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/GETTING-STARTED.md +111 -0
- package/README.md +44 -18
- package/SECURITY.md +11 -0
- package/being.mjs +127 -42
- package/blueprint.mjs +65 -15
- package/client.mjs +9 -6
- package/contract.mjs +6 -4
- package/ground.mjs +96 -36
- package/index.mjs +2 -2
- package/modules.mjs +13 -70
- package/package.json +5 -2
- package/program.mjs +1 -0
- package/tools.mjs +67 -48
- package/voice.mjs +49 -63
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]
|
|
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
|
|
215
|
-
|
|
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]
|
|
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
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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,
|
|
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:
|
|
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
|
|
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 {
|
|
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
|
|
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 = [
|
|
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
|
-
|
|
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:
|
|
82
|
-
wrapped: Voice.sealTo(heir.boxPk,
|
|
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
|
|
89
|
-
const
|
|
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:
|
|
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
|
-
|
|
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 ??
|
|
160
|
-
const 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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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({
|
|
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 {
|
|
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 {
|
|
2
|
-
import { Tools } from './tools.mjs';
|
|
1
|
+
import { Bytes, Tools } from './tools.mjs';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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) :
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
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