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 +13 -1
- package/admin.mjs +11 -1
- package/being.mjs +99 -24
- package/blueprint.mjs +130 -27
- package/ground.mjs +53 -5
- package/index.mjs +12 -2
- package/modules.mjs +2 -1
- package/package.json +1 -1
- package/tools.mjs +3 -1
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
|
|
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: {
|
|
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
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
|
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) =>
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
183
|
-
|
|
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
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
-
return
|
|
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
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
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 };
|