nervur 0.9.0 → 0.11.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/admin.mjs +2 -2
- package/being.mjs +11 -5
- package/blueprint.mjs +33 -20
- package/client.mjs +1 -1
- package/ground.mjs +19 -9
- package/index.mjs +2 -2
- package/package.json +2 -2
- package/program.mjs +33 -0
package/admin.mjs
CHANGED
|
@@ -13,8 +13,8 @@ const ADMIN = `({
|
|
|
13
13
|
opened: Boolean @rights(is: [SELF, MEMBER, STRANGER])
|
|
14
14
|
}
|
|
15
15
|
type Mutation {
|
|
16
|
-
invite(address: String!, nextPkHash: String, rights: String): Boolean @rights(is: [SELF])
|
|
17
|
-
drop(address: String!): Boolean @rights(is: [SELF])
|
|
16
|
+
invite(address: String!, nextPkHash: String, rights: String): Boolean @rights(is: [SELF, MEMBER])
|
|
17
|
+
drop(address: String!): Boolean @rights(is: [SELF, MEMBER])
|
|
18
18
|
extend(program: String): Boolean @rights(is: [SELF])
|
|
19
19
|
create(program: String!, refs: [RefInput!], data: String, config: String): String
|
|
20
20
|
@rights(is: [SELF, MEMBER])
|
package/being.mjs
CHANGED
|
@@ -143,7 +143,10 @@ const rotates = ({ mine }, from, nextPkHash) => {
|
|
|
143
143
|
.list('refs/')
|
|
144
144
|
.map((key) => ({ key, referred: mine.read(key) }))
|
|
145
145
|
.find(({ referred }) => referred?.nextPkHash === claim);
|
|
146
|
-
if (
|
|
146
|
+
if (
|
|
147
|
+
held === undefined ||
|
|
148
|
+
(held.key !== `refs/${from}` && mine.read(`refs/${from}`) !== undefined)
|
|
149
|
+
) {
|
|
147
150
|
return undefined;
|
|
148
151
|
}
|
|
149
152
|
mine.write(`refs/${from}`, { nextPkHash, rights: held.referred.rights });
|
|
@@ -261,7 +264,7 @@ const Being = ({ voice, address, deliver, modules, registry, vouched, watch }) =
|
|
|
261
264
|
});
|
|
262
265
|
return answer({ referred: from, rights: pending.rights });
|
|
263
266
|
}
|
|
264
|
-
if (op?.ask === undefined || program === undefined) {
|
|
267
|
+
if (op?.ask === undefined || program === undefined || !program.visible(rights)) {
|
|
265
268
|
return undefined;
|
|
266
269
|
}
|
|
267
270
|
const asked = await program.ask({
|
|
@@ -336,10 +339,13 @@ const Registry = ({
|
|
|
336
339
|
if (voice === undefined) {
|
|
337
340
|
return undefined;
|
|
338
341
|
}
|
|
342
|
+
const declared = (Program(memory.read(at, 'program')?.value)?.modules ?? []).map(
|
|
343
|
+
(one) => one.module,
|
|
344
|
+
);
|
|
339
345
|
const door = Being({
|
|
340
346
|
address: at,
|
|
341
347
|
deliver,
|
|
342
|
-
modules: modulesFor(voice),
|
|
348
|
+
modules: modulesFor(voice, declared),
|
|
343
349
|
vouched,
|
|
344
350
|
voice,
|
|
345
351
|
watch,
|
|
@@ -355,7 +361,7 @@ const Registry = ({
|
|
|
355
361
|
if (program !== undefined && written === undefined) {
|
|
356
362
|
return undefined;
|
|
357
363
|
}
|
|
358
|
-
const missing = (written?.modules ?? []).filter((one) => modules
|
|
364
|
+
const missing = (written?.modules ?? []).filter((one) => !modules.has(one.module));
|
|
359
365
|
if (missing.length > 0) {
|
|
360
366
|
return undefined;
|
|
361
367
|
}
|
|
@@ -403,7 +409,7 @@ const Registry = ({
|
|
|
403
409
|
if (written === undefined) {
|
|
404
410
|
return undefined;
|
|
405
411
|
}
|
|
406
|
-
if (written.modules.some((one) => modules
|
|
412
|
+
if (written.modules.some((one) => !modules.has(one.module))) {
|
|
407
413
|
return undefined;
|
|
408
414
|
}
|
|
409
415
|
if (orphans(address, written).length > 0) {
|
package/blueprint.mjs
CHANGED
|
@@ -167,12 +167,12 @@ const merged = (base, added) =>
|
|
|
167
167
|
]),
|
|
168
168
|
);
|
|
169
169
|
|
|
170
|
+
// Bare names, not Type.field pairs: X's "if create answers, it created"
|
|
171
|
+
// makes Query.create and Mutation.create one reserved word.
|
|
170
172
|
const doorsIn = (sdl) =>
|
|
171
173
|
parse(sdl)
|
|
172
|
-
.definitions.filter(
|
|
173
|
-
|
|
174
|
-
)
|
|
175
|
-
.flatMap((one) => (one.fields ?? []).map((field) => `${one.name.value}.${field.name.value}`));
|
|
174
|
+
.definitions.filter((one) => doorHolder(one) && ['Mutation', 'Query'].includes(one.name.value))
|
|
175
|
+
.flatMap((one) => (one.fields ?? []).map((field) => field.name.value));
|
|
176
176
|
|
|
177
177
|
const composed = (baseSource, addedSource) => {
|
|
178
178
|
try {
|
|
@@ -319,23 +319,36 @@ const Blueprint = (source) => {
|
|
|
319
319
|
rights,
|
|
320
320
|
schema,
|
|
321
321
|
// The directive definition stays: pointer.mjs runs buildSchema on this output.
|
|
322
|
-
sdlFor: (held) =>
|
|
323
|
-
|
|
322
|
+
sdlFor: (held) => {
|
|
323
|
+
const kept = document.definitions
|
|
324
|
+
.map((one) =>
|
|
325
|
+
doorHolder(one)
|
|
326
|
+
? {
|
|
327
|
+
...one,
|
|
328
|
+
fields: (one.fields ?? []).filter((field) => {
|
|
329
|
+
const allowed = rightsOf(field);
|
|
330
|
+
return allowed === undefined || allowed.includes(held);
|
|
331
|
+
}),
|
|
332
|
+
}
|
|
333
|
+
: one,
|
|
334
|
+
)
|
|
335
|
+
.filter((one) => !doorHolder(one) || one.fields.length > 0);
|
|
336
|
+
const defined = new Set(
|
|
337
|
+
kept
|
|
338
|
+
.filter((one) => one.kind === Kind.OBJECT_TYPE_DEFINITION)
|
|
339
|
+
.map((one) => one.name.value),
|
|
340
|
+
);
|
|
341
|
+
// A filtered view where only a part's extension survives must still
|
|
342
|
+
// stand alone as a schema, or the right it serves opens nothing.
|
|
343
|
+
return print({
|
|
324
344
|
...document,
|
|
325
|
-
definitions:
|
|
326
|
-
.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return allowed === undefined || allowed.includes(held);
|
|
333
|
-
}),
|
|
334
|
-
}
|
|
335
|
-
: one,
|
|
336
|
-
)
|
|
337
|
-
.filter((one) => !doorHolder(one) || one.fields.length > 0),
|
|
338
|
-
}),
|
|
345
|
+
definitions: kept.map((one) =>
|
|
346
|
+
one.kind === Kind.OBJECT_TYPE_EXTENSION && !defined.has(one.name.value)
|
|
347
|
+
? { ...one, kind: Kind.OBJECT_TYPE_DEFINITION }
|
|
348
|
+
: one,
|
|
349
|
+
),
|
|
350
|
+
});
|
|
351
|
+
},
|
|
339
352
|
trespass: [],
|
|
340
353
|
});
|
|
341
354
|
} catch {
|
package/client.mjs
CHANGED
package/ground.mjs
CHANGED
|
@@ -93,7 +93,8 @@ const Ground = (modules = {}) => {
|
|
|
93
93
|
|
|
94
94
|
const extension = modules.blueprints?.ground();
|
|
95
95
|
const program = extension === undefined ? ADMIN : composed(ADMIN, extension);
|
|
96
|
-
|
|
96
|
+
const blueprint = program === undefined ? undefined : Blueprint(program);
|
|
97
|
+
if (blueprint?.digest === undefined) {
|
|
97
98
|
return undefined;
|
|
98
99
|
}
|
|
99
100
|
const installed = cells.peek('ground/admin');
|
|
@@ -141,18 +142,24 @@ const Ground = (modules = {}) => {
|
|
|
141
142
|
typeof one?.scope === 'function' ? one.scope(address) : one,
|
|
142
143
|
]),
|
|
143
144
|
);
|
|
144
|
-
const
|
|
145
|
+
const declaredOnly = (obj, declared) =>
|
|
146
|
+
Object.fromEntries(Object.entries(obj).filter(([name]) => declared.includes(name)));
|
|
147
|
+
const modulesFor = (voice, declared = []) =>
|
|
145
148
|
Object.freeze(
|
|
146
149
|
introduced(
|
|
147
150
|
{
|
|
148
|
-
...extra,
|
|
149
|
-
...boundFor(voice),
|
|
151
|
+
...declaredOnly(extra, declared),
|
|
152
|
+
...declaredOnly(boundFor(voice), declared),
|
|
150
153
|
memory,
|
|
151
154
|
},
|
|
152
155
|
Tools.address(voice.pk),
|
|
153
156
|
),
|
|
154
157
|
);
|
|
155
|
-
const
|
|
158
|
+
const offeredNames = new Set([...Object.keys(extra), ...bound.map(([name]) => name), 'memory']);
|
|
159
|
+
const adminDeclared = () =>
|
|
160
|
+
(Program(memory.read(address, 'program')?.value)?.modules ?? blueprint.modules).map(
|
|
161
|
+
(one) => one.module,
|
|
162
|
+
);
|
|
156
163
|
|
|
157
164
|
const adminMine = Compartment({
|
|
158
165
|
memory,
|
|
@@ -185,8 +192,11 @@ const Ground = (modules = {}) => {
|
|
|
185
192
|
|
|
186
193
|
const attest = async (name) => {
|
|
187
194
|
const one = extra[name];
|
|
188
|
-
if (one
|
|
189
|
-
return
|
|
195
|
+
if (one === undefined) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
if (one.at === undefined) {
|
|
199
|
+
return null;
|
|
190
200
|
}
|
|
191
201
|
const seen = await deliver(Tools.envelope(adminVoice, one.at, { describe: {} }, stamp()));
|
|
192
202
|
return seen?.body?.sdl === undefined
|
|
@@ -199,7 +209,7 @@ const Ground = (modules = {}) => {
|
|
|
199
209
|
adminMine,
|
|
200
210
|
address,
|
|
201
211
|
deliver: (envelope) => deliver(envelope),
|
|
202
|
-
modules:
|
|
212
|
+
modules: offeredNames,
|
|
203
213
|
modulesFor,
|
|
204
214
|
announce,
|
|
205
215
|
forget,
|
|
@@ -211,7 +221,7 @@ const Ground = (modules = {}) => {
|
|
|
211
221
|
Being({
|
|
212
222
|
address,
|
|
213
223
|
deliver: (envelope) => deliver(envelope),
|
|
214
|
-
modules:
|
|
224
|
+
modules: modulesFor(adminVoice, adminDeclared()),
|
|
215
225
|
registry,
|
|
216
226
|
vouched,
|
|
217
227
|
voice: adminVoice,
|
package/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { ADMIN } from './admin.mjs';
|
|
2
2
|
export { Blueprint, CLASSES, CORE_ONLY, OPEN, STRUCTURAL, coreClass, reads } from './blueprint.mjs';
|
|
3
|
-
export { Client } from './client.mjs';
|
|
3
|
+
export { Client, speaking } from './client.mjs';
|
|
4
4
|
export { Contract, Contracts } from './contract.mjs';
|
|
5
|
-
export { GROUND, Ground } from './ground.mjs';
|
|
5
|
+
export { FLOOR, GROUND, Ground } from './ground.mjs';
|
|
6
6
|
export { Cells, Clock, Dns, Entropy, Keychain, Memory, Paths, Wire } from './modules.mjs';
|
|
7
7
|
export { Mcp, Pointer } from './pointer.mjs';
|
|
8
8
|
export { Program } from './program.mjs';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nervur",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A distributed system of sovereign beings
|
|
3
|
+
"version": "0.11.0",
|
|
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",
|
|
7
7
|
"homepage": "https://nervur.org",
|
package/program.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
getIntrospectionQuery,
|
|
5
5
|
graphql,
|
|
6
6
|
isObjectType,
|
|
7
|
+
parse,
|
|
8
|
+
validate,
|
|
7
9
|
} from 'graphql';
|
|
8
10
|
import { Blueprint } from './blueprint.mjs';
|
|
9
11
|
|
|
@@ -41,8 +43,39 @@ const Program = (source) => {
|
|
|
41
43
|
}
|
|
42
44
|
return curtained.get(held);
|
|
43
45
|
};
|
|
46
|
+
// graphql's validator demands a Query root even to judge a mutation, so an
|
|
47
|
+
// all-mutation filtered world validates against a copy wearing an empty one
|
|
48
|
+
// — never printed, never executed against.
|
|
49
|
+
const askable = new Map();
|
|
50
|
+
const askableBy = (held) => {
|
|
51
|
+
if (!askable.has(held)) {
|
|
52
|
+
const seen = seenBy(held);
|
|
53
|
+
try {
|
|
54
|
+
askable.set(
|
|
55
|
+
held,
|
|
56
|
+
seen === undefined || seen.getQueryType() != null
|
|
57
|
+
? seen
|
|
58
|
+
: buildSchema(`${blueprint.sdlFor(held)}\ntype Query { _stands: Boolean }`),
|
|
59
|
+
);
|
|
60
|
+
} catch {
|
|
61
|
+
askable.set(held, undefined);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return askable.get(held);
|
|
65
|
+
};
|
|
44
66
|
return Object.freeze({
|
|
45
67
|
ask: async ({ source: asked, variables, context }) => {
|
|
68
|
+
const seen = askableBy(context.rights);
|
|
69
|
+
if (seen === undefined) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
if (validate(seen, parse(asked)).length > 0) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
46
79
|
const answered = await graphql({
|
|
47
80
|
contextValue: context,
|
|
48
81
|
schema,
|