nervur 0.13.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/being.mjs +55 -10
- package/blueprint.mjs +52 -2
- package/ground.mjs +1 -0
- package/package.json +1 -1
- package/program.mjs +1 -0
package/being.mjs
CHANGED
|
@@ -92,7 +92,7 @@ const custodial = (registry, fence) =>
|
|
|
92
92
|
upgrade: (one) => (fence.open() ? registry.upgrade(one) : undefined),
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
const offered = (modules, program, mine) =>
|
|
95
|
+
const offered = (modules, program, mine, seat) =>
|
|
96
96
|
Object.freeze({
|
|
97
97
|
...Object.fromEntries(
|
|
98
98
|
(program?.modules ?? [])
|
|
@@ -100,19 +100,47 @@ const offered = (modules, program, mine) =>
|
|
|
100
100
|
.filter((name) => name !== 'memory' && modules[name] !== undefined)
|
|
101
101
|
.map((name) => [name, modules[name]]),
|
|
102
102
|
),
|
|
103
|
-
memory: scoped(mine, program),
|
|
103
|
+
memory: scoped(mine, program, seat),
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
const grantable = (program) =>
|
|
107
107
|
(program?.rights ?? [...STRUCTURAL, 'MEMBER']).filter((one) => !STRUCTURAL.includes(one));
|
|
108
108
|
|
|
109
|
-
const
|
|
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) => {
|
|
110
138
|
const rights = plain.rights ?? 'MEMBER';
|
|
111
139
|
if (!grantable(program).includes(rights)) {
|
|
112
140
|
return undefined;
|
|
113
141
|
}
|
|
114
142
|
if (key.startsWith('refs/pending/')) {
|
|
115
|
-
return
|
|
143
|
+
return invitation(plain, rights, program, seat);
|
|
116
144
|
}
|
|
117
145
|
const held = mine.read(key);
|
|
118
146
|
if (
|
|
@@ -131,14 +159,14 @@ const referable = (mine, key, plain, program) => {
|
|
|
131
159
|
};
|
|
132
160
|
};
|
|
133
161
|
|
|
134
|
-
const scoped = (mine, program) =>
|
|
162
|
+
const scoped = (mine, program, seat) =>
|
|
135
163
|
Object.freeze({
|
|
136
164
|
classOf: mine.classOf,
|
|
137
165
|
list: mine.list,
|
|
138
166
|
read: mine.read,
|
|
139
167
|
write: (key, plain) => {
|
|
140
168
|
if (key.startsWith('refs/') && plain !== undefined) {
|
|
141
|
-
const guarded = referable(mine, key, plain, program);
|
|
169
|
+
const guarded = referable(mine, key, plain, program, seat);
|
|
142
170
|
return guarded === undefined ? undefined : mine.write(key, guarded);
|
|
143
171
|
}
|
|
144
172
|
return key.startsWith('refs/') ||
|
|
@@ -232,7 +260,18 @@ const successionOf = (memory, address) =>
|
|
|
232
260
|
.filter((one) => one !== undefined)
|
|
233
261
|
.sort((one, other) => one.v - other.v);
|
|
234
262
|
|
|
235
|
-
const Being = ({
|
|
263
|
+
const Being = ({
|
|
264
|
+
voice,
|
|
265
|
+
address,
|
|
266
|
+
cipher,
|
|
267
|
+
deliver,
|
|
268
|
+
modules,
|
|
269
|
+
registry,
|
|
270
|
+
administration,
|
|
271
|
+
span,
|
|
272
|
+
vouched,
|
|
273
|
+
watch,
|
|
274
|
+
}) => {
|
|
236
275
|
const held = spanOf(span);
|
|
237
276
|
const memory = modules.memory;
|
|
238
277
|
const root = rootFor({ memory, voice, address, cipher });
|
|
@@ -248,6 +287,7 @@ const Being = ({ voice, address, cipher, deliver, modules, registry, span, vouch
|
|
|
248
287
|
});
|
|
249
288
|
const succession = successionOf(memory, address);
|
|
250
289
|
const stamp = Stamp();
|
|
290
|
+
const seat = Object.freeze({ administration: administration === true, now: stamp });
|
|
251
291
|
const lanes = new Map();
|
|
252
292
|
const answer = (spoken, claim) => {
|
|
253
293
|
const body = Tools.canon(spoken);
|
|
@@ -331,10 +371,15 @@ const Being = ({ voice, address, cipher, deliver, modules, registry, span, vouch
|
|
|
331
371
|
if (op?.acceptInvite !== undefined) {
|
|
332
372
|
const offer = Tools.digest(Bytes.unbase64(op.acceptInvite.proof));
|
|
333
373
|
const pending = mine.read(`refs/pending/${offer}`);
|
|
334
|
-
|
|
374
|
+
const now = stamp();
|
|
375
|
+
if (
|
|
376
|
+
pending === undefined ||
|
|
377
|
+
!(pending.count > 0) ||
|
|
378
|
+
(pending.expiresAt > 0 && now > pending.expiresAt)
|
|
379
|
+
) {
|
|
335
380
|
return undefined;
|
|
336
381
|
}
|
|
337
|
-
mine.write(`refs/pending/${offer}`, {
|
|
382
|
+
mine.write(`refs/pending/${offer}`, { ...pending, count: pending.count - 1 });
|
|
338
383
|
mine.write(`refs/${from}`, {
|
|
339
384
|
nextPkHash: op.acceptInvite.nextPkHash ?? null,
|
|
340
385
|
rights: pending.rights,
|
|
@@ -352,7 +397,7 @@ const Being = ({ voice, address, cipher, deliver, modules, registry, span, vouch
|
|
|
352
397
|
program.ask({
|
|
353
398
|
context: {
|
|
354
399
|
from: program.caller ? from : undefined,
|
|
355
|
-
modules: offered(modules, program, turn),
|
|
400
|
+
modules: offered(modules, program, turn, seat),
|
|
356
401
|
port: Port({ voice, mine: turn, deliver, fence, lanes, rights, stamp }),
|
|
357
402
|
registry: custodial(registry, fence),
|
|
358
403
|
rights,
|
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 {
|
|
@@ -241,6 +269,17 @@ const faults = (source) => {
|
|
|
241
269
|
if (bare.length > 0) {
|
|
242
270
|
found.push(`the door ${bare.join(', ')} declares no rights, and every door declares them (VI)`);
|
|
243
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
|
+
}
|
|
244
283
|
for (const [held, doors] of outside) {
|
|
245
284
|
found.push(
|
|
246
285
|
`the Rights enum does not name ${held}, which ${doors.length} door${doors.length === 1 ? '' : 's'} stand behind: ${doors.join(', ')} (VI)`,
|
|
@@ -342,7 +381,9 @@ const Blueprint = (source) => {
|
|
|
342
381
|
try {
|
|
343
382
|
const written = evaluated(source);
|
|
344
383
|
const declares = /enum\s+Rights\b/.test(written.interface) ? '' : DEFAULT_RIGHTS;
|
|
345
|
-
const sdl = [RIGHTS_DIRECTIVE, declares, written.interface]
|
|
384
|
+
const sdl = [RIGHTS_DIRECTIVE, INVITES_DIRECTIVE, declares, written.interface]
|
|
385
|
+
.filter(Boolean)
|
|
386
|
+
.join('\n');
|
|
346
387
|
const document = parse(sdl);
|
|
347
388
|
const schema = buildSchema(sdl);
|
|
348
389
|
const declaredNames = (schema.getType('Rights')?.getValues() ?? []).map((one) => one.name);
|
|
@@ -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)
|
package/ground.mjs
CHANGED
|
@@ -284,6 +284,7 @@ const Ground = (modules = {}, { span } = {}) => {
|
|
|
284
284
|
const buildAdmin = () =>
|
|
285
285
|
Being({
|
|
286
286
|
address,
|
|
287
|
+
administration: true,
|
|
287
288
|
cipher: modules.cipher,
|
|
288
289
|
deliver: (envelope) => deliver(envelope),
|
|
289
290
|
modules: modulesFor(adminVoice, adminDeclared()),
|
package/package.json
CHANGED
package/program.mjs
CHANGED