nervur 0.13.0 → 0.15.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/LICENSE +2 -2
- package/README.md +151 -456
- package/package.json +12 -13
- package/src/being.js +125 -0
- package/src/contract.js +161 -0
- package/src/ground.js +273 -0
- package/src/index.js +5 -0
- package/src/program.js +74 -0
- package/src/projection.js +102 -0
- package/GETTING-STARTED.md +0 -111
- package/NOTICE +0 -2
- package/SECURITY.md +0 -11
- package/admin.mjs +0 -83
- package/being.mjs +0 -594
- package/blueprint.mjs +0 -462
- package/client.mjs +0 -70
- package/contract.mjs +0 -98
- package/ground.mjs +0 -381
- package/index.mjs +0 -20
- package/modules.mjs +0 -55
- package/pointer.mjs +0 -79
- package/program.mjs +0 -118
- package/tools.mjs +0 -162
- package/voice.mjs +0 -107
package/package.json
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nervur",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.15.0",
|
|
4
|
+
"description": "Nervur grown on the Quo kit: the graph, the organs and the floor, with @quo-systems/js as the whole of the protocol",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Razvan Gherghina",
|
|
7
|
-
"homepage": "https://nervur.org",
|
|
8
7
|
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
9
|
"exports": {
|
|
10
|
-
".": "./index.
|
|
10
|
+
".": "./src/index.js"
|
|
11
11
|
},
|
|
12
|
-
"files": [
|
|
13
|
-
"*.mjs",
|
|
14
|
-
"GETTING-STARTED.md",
|
|
15
|
-
"NOTICE",
|
|
16
|
-
"SECURITY.md"
|
|
17
|
-
],
|
|
18
12
|
"engines": {
|
|
19
13
|
"node": ">=22"
|
|
20
14
|
},
|
|
21
15
|
"dependencies": {
|
|
22
|
-
"@
|
|
23
|
-
"graphql": "
|
|
24
|
-
}
|
|
16
|
+
"@quo-systems/js": "0.0.3",
|
|
17
|
+
"graphql": "17.0.2"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"src",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"README.md"
|
|
23
|
+
]
|
|
25
24
|
}
|
package/src/being.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Refusal, parse, recordsOf, encode, decodeAll } from '@quo-systems/js';
|
|
2
|
+
import { contract as read } from './contract.js';
|
|
3
|
+
import { program } from './program.js';
|
|
4
|
+
import { project } from './projection.js';
|
|
5
|
+
|
|
6
|
+
const refuse = (code, detail) => {
|
|
7
|
+
throw new Refusal(code, detail);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// A being is a contract and its resolvers, and nothing else. The contract is a
|
|
11
|
+
// GraphQL schema — authoring, and authoring only — projected to a Quo blueprint
|
|
12
|
+
// before anything holds it, so the warden gets the notation it has always got
|
|
13
|
+
// and a stranger implementing Quo elsewhere reads a blueprint like any other.
|
|
14
|
+
//
|
|
15
|
+
// A being is compiled by `program`, which compiles organs too and cannot tell
|
|
16
|
+
// which it is holding. What this file adds is the wire skin the kit leaves
|
|
17
|
+
// open: bytes in and bytes out, and the object the warden dispatches from.
|
|
18
|
+
//
|
|
19
|
+
// Two things are Nervur's to keep, and the law is what asks for them. "What a
|
|
20
|
+
// blueprint does not declare does not exist," and "granting a being is granting
|
|
21
|
+
// every field it declares" — together they make the declared set the scope of a
|
|
22
|
+
// grant. A held object with a method the blueprint never named would answer
|
|
23
|
+
// callers anyway, which is a grant that grants more than its digest says.
|
|
24
|
+
//
|
|
25
|
+
// A being is raised rather than built: the organs it declared with
|
|
26
|
+
// `schema @needs(organs: [...])` are supplied by the ground at `hold`, and the
|
|
27
|
+
// resolvers are closed over them. Reach is a fact about a being, not about a
|
|
28
|
+
// call, so it arrives once — and that is what makes the refusal structural. An
|
|
29
|
+
// organ the being did not declare is not absent from a check; it is absent from
|
|
30
|
+
// the object, and there is nothing to police.
|
|
31
|
+
//
|
|
32
|
+
// `contacts` arrives the same way, as the resolver factory's second argument.
|
|
33
|
+
// What is closed over is a **reader**, not a value: reach is fixed at raise and
|
|
34
|
+
// contacts are not, so `list()` goes to the warden's record at the moment it is
|
|
35
|
+
// called and there is nothing here to go stale.
|
|
36
|
+
export function being(sdl, resolvers) {
|
|
37
|
+
const held = read(sdl);
|
|
38
|
+
const text = project(held);
|
|
39
|
+
const blueprint = parse(text);
|
|
40
|
+
const records = recordsOf(blueprint);
|
|
41
|
+
|
|
42
|
+
const raise = (supplied = {}, contacts = null) =>
|
|
43
|
+
object(held, blueprint, records, resolvers, supplied, contacts);
|
|
44
|
+
// A being that cannot stand should say so at once rather than at a hold. But
|
|
45
|
+
// only where saying so costs nothing: a resolver factory may allocate, or
|
|
46
|
+
// register itself somewhere, and running it speculatively would do that twice
|
|
47
|
+
// for a being with no organs and once for a being with them. So a plain set
|
|
48
|
+
// of resolvers is checked here, and a factory is run exactly once, at hold.
|
|
49
|
+
if (held.needs.length === 0 && typeof resolvers !== 'function') raise();
|
|
50
|
+
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
contract: text,
|
|
53
|
+
organs: Object.freeze([...held.needs]),
|
|
54
|
+
raise,
|
|
55
|
+
schema: sdl,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function object(held, blueprint, records, resolvers, supplied, contacts) {
|
|
60
|
+
// The ground offers; the being takes what its schema declared. Two
|
|
61
|
+
// narrowings, and an organ offered but not needed is simply not taken — a
|
|
62
|
+
// ground handing the same tray to every being it holds is the ordinary case.
|
|
63
|
+
const reach = {};
|
|
64
|
+
for (const alias of held.needs) {
|
|
65
|
+
if (!supplied[alias]) refuse('ORGAN_MISSING', alias);
|
|
66
|
+
reach[alias] = supplied[alias];
|
|
67
|
+
}
|
|
68
|
+
const organs = Object.freeze(reach);
|
|
69
|
+
|
|
70
|
+
const built = typeof resolvers === 'function' ? resolvers(organs, contacts) : resolvers;
|
|
71
|
+
|
|
72
|
+
// Both halves of the mismatch are refused, and refused here rather than at a
|
|
73
|
+
// call: a being that stands and then falls over on one field is a being whose
|
|
74
|
+
// digest lied to everyone who read it. `hasOwn`, not a bare lookup — a
|
|
75
|
+
// contract declaring `toString` would otherwise be satisfied by
|
|
76
|
+
// `Object.prototype`'s, and a resolver is a thing an author wrote, not a
|
|
77
|
+
// thing a prototype happens to supply.
|
|
78
|
+
for (const field of Object.keys(held.fields)) {
|
|
79
|
+
if (!Object.hasOwn(built, field) || typeof built[field] !== 'function') {
|
|
80
|
+
refuse('NO_RESOLVER', field);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// A resolver the contract does not declare is unreachable by design. Silence
|
|
84
|
+
// would hide the likelier cause — a name that does not match — so it is an
|
|
85
|
+
// error instead.
|
|
86
|
+
for (const name of Object.keys(built)) {
|
|
87
|
+
if (!Object.hasOwn(held.fields, name)) refuse('UNDECLARED_RESOLVER', name);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const one = program(held, built);
|
|
91
|
+
|
|
92
|
+
// No prototype, and this is the gate rather than a tidiness. The kit
|
|
93
|
+
// dispatches with `object[name]` and `typeof === 'function'`, and an
|
|
94
|
+
// inherited method passes both — a plain object here answers a sealed ask for
|
|
95
|
+
// `constructor`, because `Object` is a function and hands back the very bytes
|
|
96
|
+
// it was called with, which is exactly what the warden accepts as an answer.
|
|
97
|
+
// Freezing does not help: freezing does not remove a prototype.
|
|
98
|
+
const table = Object.create(null);
|
|
99
|
+
for (const field of blueprint.fields) {
|
|
100
|
+
const types = field.args.map((argument) => argument.type);
|
|
101
|
+
const names = field.args.map((argument) => argument.name);
|
|
102
|
+
|
|
103
|
+
// The kit's signature: bytes in, bytes or nothing out. A resolver sees
|
|
104
|
+
// neither. It sees its arguments by the names its contract gave them, and
|
|
105
|
+
// its leash — never a caller, never an address, because a being learns
|
|
106
|
+
// neither and Nervur does not soften it.
|
|
107
|
+
table[field.name] = async (bytes, leash) => {
|
|
108
|
+
const values = decodeAll(types, bytes, records);
|
|
109
|
+
const answered = await one.answer(
|
|
110
|
+
field.name,
|
|
111
|
+
Object.fromEntries(names.map((name, at) => [name, values[at]])),
|
|
112
|
+
leash,
|
|
113
|
+
);
|
|
114
|
+
// A field declaring no answer answers nothing, and that is an answer: the
|
|
115
|
+
// door sends an envelope with no data in it.
|
|
116
|
+
if (!field.answer) return undefined;
|
|
117
|
+
// An answer that does not fit its declared type never leaves. `encode`
|
|
118
|
+
// refuses it, the warden's global catch turns that into silence, and
|
|
119
|
+
// silence is the only refusal a door can express.
|
|
120
|
+
return encode(field.answer, answered, records);
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return Object.freeze(table);
|
|
125
|
+
}
|
package/src/contract.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { parse as parseGraphQL } from 'graphql';
|
|
2
|
+
import { Refusal } from '@quo-systems/js';
|
|
3
|
+
|
|
4
|
+
// What a thing answers, read once from its own text. A being's contract and an
|
|
5
|
+
// organ's contract are the same file under the same name, because they are one
|
|
6
|
+
// kind of code — so there is one reader here and no second one anywhere.
|
|
7
|
+
//
|
|
8
|
+
// The contract is a GraphQL schema. That is authoring and nothing else: it
|
|
9
|
+
// never reaches the wire, is never an answer, and is never a describe. Every
|
|
10
|
+
// implementer of Quo speaks the notation and has never heard of Nervur, so a
|
|
11
|
+
// contract is projected to a Quo blueprint at the ground's edge and that
|
|
12
|
+
// projection is the only boundary.
|
|
13
|
+
//
|
|
14
|
+
// A contract carries no identity of its own. The being's name is the digest of
|
|
15
|
+
// its projected blueprint, which the warden takes, and a second identity taken
|
|
16
|
+
// over the GraphQL bytes would be a second truth about the same thing.
|
|
17
|
+
|
|
18
|
+
const refuse = (code, detail) => {
|
|
19
|
+
throw new Refusal(code, detail);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const OPERATIONS = Object.freeze(['query', 'mutation', 'subscription']);
|
|
23
|
+
|
|
24
|
+
const named = (node) => node.name.value;
|
|
25
|
+
|
|
26
|
+
// A field's type, flattened to the name it ends at, whether it is a list, and
|
|
27
|
+
// whether the caller must give it. GraphQL is nullable by default and says so
|
|
28
|
+
// by omission; the projection is where that becomes `?`, not here.
|
|
29
|
+
const inner = (node) => (node.kind === 'NamedType' ? named(node) : inner(node.type));
|
|
30
|
+
|
|
31
|
+
const typed = (node) =>
|
|
32
|
+
node.kind === 'NonNullType'
|
|
33
|
+
? { ...typed(node.type), required: true }
|
|
34
|
+
: node.kind === 'ListType'
|
|
35
|
+
? { list: true, of: inner(node.type), required: false, element: typed(node.type) }
|
|
36
|
+
: { of: named(node), required: false };
|
|
37
|
+
|
|
38
|
+
// What a contract says it needs to work, written in the schema itself:
|
|
39
|
+
//
|
|
40
|
+
// schema @needs(organs: ["book", "far"]) { query: Clerk }
|
|
41
|
+
//
|
|
42
|
+
// It is a need and never a binding. A contract is written to stand on any
|
|
43
|
+
// ground, so it names what it must reach and says nothing about whose
|
|
44
|
+
// implementation answers.
|
|
45
|
+
//
|
|
46
|
+
// Undeclared is unreachable, and never by a check: a contract that never asked
|
|
47
|
+
// for an organ does not have it and cannot learn that it exists, because the
|
|
48
|
+
// object it is handed has no such key.
|
|
49
|
+
const needed = (definition) => {
|
|
50
|
+
for (const directive of definition?.directives ?? []) {
|
|
51
|
+
if (directive.name.value !== 'needs') continue;
|
|
52
|
+
const organs = (directive.arguments ?? []).find((one) => one.name.value === 'organs');
|
|
53
|
+
if (organs?.value?.kind !== 'ListValue') refuse('BAD_NEEDS', 'organs must be a list of names');
|
|
54
|
+
return organs.value.values.map((one) => {
|
|
55
|
+
if (one.kind !== 'StringValue') refuse('BAD_NEEDS', 'an organ is named by a string');
|
|
56
|
+
return one.value;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return [];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Which type is the root of each operation. GraphQL's own `schema` block names
|
|
63
|
+
// them, and that is where the being's name comes from — `schema { query: Clerk }`
|
|
64
|
+
// makes a class block called `Clerk`. With no `schema` block the conventional
|
|
65
|
+
// type names stand in, which is the plain form every engineer already writes.
|
|
66
|
+
const rooted = (definition) => {
|
|
67
|
+
const roots = { query: 'Query', mutation: 'Mutation', subscription: 'Subscription' };
|
|
68
|
+
for (const operation of definition?.operationTypes ?? []) {
|
|
69
|
+
roots[operation.operation] = named(operation.type);
|
|
70
|
+
}
|
|
71
|
+
return roots;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const fieldsOf = (definition) =>
|
|
75
|
+
Object.fromEntries(
|
|
76
|
+
(definition.fields ?? []).map((field) => [
|
|
77
|
+
named(field),
|
|
78
|
+
Object.freeze({
|
|
79
|
+
args: Object.freeze(
|
|
80
|
+
Object.fromEntries(
|
|
81
|
+
(field.arguments ?? []).map((one) => [named(one), Object.freeze(typed(one.type))]),
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
answers: Object.freeze(typed(field.type)),
|
|
85
|
+
}),
|
|
86
|
+
]),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
export function contract(sdl) {
|
|
90
|
+
if (typeof sdl !== 'string') refuse('NOT_A_SCHEMA', typeof sdl);
|
|
91
|
+
let document;
|
|
92
|
+
try {
|
|
93
|
+
document = parseGraphQL(sdl);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
refuse('UNREADABLE_SCHEMA', error.message);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const schema = document.definitions.find(
|
|
99
|
+
(one) => one.kind === 'SchemaDefinition' || one.kind === 'SchemaExtension',
|
|
100
|
+
);
|
|
101
|
+
const roots = rooted(schema);
|
|
102
|
+
const rootNames = new Set(Object.values(roots));
|
|
103
|
+
|
|
104
|
+
const objects = new Map();
|
|
105
|
+
const inputs = new Map();
|
|
106
|
+
for (const definition of document.definitions) {
|
|
107
|
+
if (definition.kind === 'ObjectTypeDefinition') {
|
|
108
|
+
if (objects.has(named(definition))) refuse('DUPLICATE_TYPE', named(definition));
|
|
109
|
+
objects.set(named(definition), definition);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (definition.kind === 'InputObjectTypeDefinition') {
|
|
113
|
+
if (inputs.has(named(definition))) refuse('DUPLICATE_TYPE', named(definition));
|
|
114
|
+
inputs.set(named(definition), definition);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (definition.kind === 'SchemaDefinition' || definition.kind === 'SchemaExtension') continue;
|
|
118
|
+
// Everything else — interfaces, unions, enums, fragments, an operation —
|
|
119
|
+
// is a thing the notation has no word for, and a contract carrying one is
|
|
120
|
+
// refused where it is read rather than guessed at where it is projected.
|
|
121
|
+
refuse('NO_WORD', definition.kind);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Every root field, flat, with the operation it stands under. This is what a
|
|
125
|
+
// caller may ask, and there is no register of fields anywhere else.
|
|
126
|
+
// Resolvers are flat under the contract, not nested under a root: one
|
|
127
|
+
// contract is one class block in the notation, so a field name is unique
|
|
128
|
+
// across the whole contract and a second layer would name nothing.
|
|
129
|
+
const fields = {};
|
|
130
|
+
for (const operation of OPERATIONS) {
|
|
131
|
+
const definition = objects.get(roots[operation]);
|
|
132
|
+
if (!definition) continue;
|
|
133
|
+
for (const [name, one] of Object.entries(fieldsOf(definition))) {
|
|
134
|
+
if (Object.hasOwn(fields, name)) refuse('DUPLICATE_FIELD', name);
|
|
135
|
+
fields[name] = Object.freeze({ ...one, operation });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (Object.keys(fields).length === 0) refuse('NO_FIELDS', 'a contract that answers nothing');
|
|
139
|
+
|
|
140
|
+
// The name a being wears: the query root's, or the mutation root's where a
|
|
141
|
+
// contract only writes. One contract is one class block, so one name.
|
|
142
|
+
const name = objects.has(roots.query)
|
|
143
|
+
? roots.query
|
|
144
|
+
: objects.has(roots.mutation)
|
|
145
|
+
? roots.mutation
|
|
146
|
+
: roots.subscription;
|
|
147
|
+
|
|
148
|
+
const records = {};
|
|
149
|
+
for (const [typeName, definition] of [...objects, ...inputs]) {
|
|
150
|
+
if (rootNames.has(typeName)) continue;
|
|
151
|
+
records[typeName] = Object.freeze(fieldsOf(definition));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return Object.freeze({
|
|
155
|
+
fields: Object.freeze(fields),
|
|
156
|
+
name,
|
|
157
|
+
needs: Object.freeze([...new Set(needed(schema))]),
|
|
158
|
+
records: Object.freeze(records),
|
|
159
|
+
sdl,
|
|
160
|
+
});
|
|
161
|
+
}
|
package/src/ground.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { Warden, commitment, readAnswer, reach, signingPair } from '@quo-systems/js';
|
|
2
|
+
import { serve } from '@quo-systems/js/door';
|
|
3
|
+
|
|
4
|
+
const same = (a, b) => a.length === b.length && a.every((byte, at) => byte === b[at]);
|
|
5
|
+
|
|
6
|
+
// The kit keys both of its records by a pk's hex, and does not export the
|
|
7
|
+
// spelling. Zero dependencies and no host API: this is the whole of it.
|
|
8
|
+
const hex = (bytes) => [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
9
|
+
|
|
10
|
+
// One process, many beings, exactly one door. The kit's `Warden` is the door
|
|
11
|
+
// and everything behind it, so a ground is a closure over one warden and
|
|
12
|
+
// nothing else — Nervur adds no protocol of its own, ever.
|
|
13
|
+
//
|
|
14
|
+
// The instance is closed over rather than returned. Nervur is written
|
|
15
|
+
// Crockford: factory functions, no `this` at a call site, frozen surfaces. A
|
|
16
|
+
// warden handed to a caller is a warden whose methods must be called bound,
|
|
17
|
+
// which puts the kit's class idiom in Nervur's hands. It stops here.
|
|
18
|
+
//
|
|
19
|
+
// Every seed and every draw of randomness arrives as an argument, the way the
|
|
20
|
+
// kit takes them. A ground that reached for entropy would be a ground the
|
|
21
|
+
// bench cannot replay.
|
|
22
|
+
export async function ground({ nameSeed, padlockSeed, heirSeed, hints = [], limit = 0n }) {
|
|
23
|
+
const warden = await Warden.open({ nameSeed, padlockSeed, heirSeed, hints, limit });
|
|
24
|
+
|
|
25
|
+
// The half of an invitation the kit's record has nowhere to keep: the heir
|
|
26
|
+
// this ground rotates to the first time it spends the relation. An
|
|
27
|
+
// invitation hands over the heir keys and nothing else, so the holder's
|
|
28
|
+
// first ask must arrive as a rotation carrying a commitment to a successor
|
|
29
|
+
// the granting house has never seen. Meeting that once is a trap; meeting it
|
|
30
|
+
// at every call site is the trap kept. So `remember` derives the successor
|
|
31
|
+
// and `ask` spends it, and a caller never learns the difference.
|
|
32
|
+
const rotations = new Map();
|
|
33
|
+
|
|
34
|
+
// A relation is named by the being that spends it and the house it stands
|
|
35
|
+
// at, never by a handle: a handle passed around is a relation one being can
|
|
36
|
+
// spend on another's behalf, and the whole point of the outbound record is
|
|
37
|
+
// that it cannot.
|
|
38
|
+
const relationFor = (from, at) => {
|
|
39
|
+
for (const row of warden.relationsOf(from)) if (same(row.warden, at)) return row;
|
|
40
|
+
return null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Contacts: who reaches this being, and the two acts that change it. Quo
|
|
44
|
+
// keeps the record and refuses to have an opinion about it — it has no word
|
|
45
|
+
// for member, owner or guest and must not, because a protocol a stranger
|
|
46
|
+
// implements in another language cannot carry meanings. The opinion is
|
|
47
|
+
// Nervur's, and this is where it lives. Nothing is copied: `list` walks the
|
|
48
|
+
// warden's own inbound record at the moment it is asked, `admit` is the
|
|
49
|
+
// warden's `grant`, `remove` is the warden's `amend`.
|
|
50
|
+
//
|
|
51
|
+
// The warden holds the record the other way round — voice to beings — so the
|
|
52
|
+
// inverse is derived here rather than kept, because a second index is a
|
|
53
|
+
// second truth and the first one moves without telling it.
|
|
54
|
+
//
|
|
55
|
+
// The scoping is the design, and it is structural rather than a check that
|
|
56
|
+
// refuses: `beingPk` is shut inside this closure at mint, so a being's
|
|
57
|
+
// context has no address it could name to reach another's row. The same
|
|
58
|
+
// shape `errand` uses for a relation.
|
|
59
|
+
const contacts = (beingPk) => {
|
|
60
|
+
const at = hex(beingPk);
|
|
61
|
+
const reaches = (row) => row.beings.has(at);
|
|
62
|
+
return Object.freeze({
|
|
63
|
+
// The voices that reach this being, read live.
|
|
64
|
+
list: () => [...warden.inbound.values()].filter(reaches).map((row) => row.voice),
|
|
65
|
+
|
|
66
|
+
// Mint a voice against this being and hand back the invitation. A being
|
|
67
|
+
// still cannot mint standing — it has no warden — and this does not give
|
|
68
|
+
// it one: what it has is a reach the ground minted for it, at one being.
|
|
69
|
+
admit: (keys) => warden.grant(beingPk, keys),
|
|
70
|
+
|
|
71
|
+
// Take this being out of that voice's row, and nothing else. A voice that
|
|
72
|
+
// does not reach this being is not amended at all — the warden would
|
|
73
|
+
// answer true to a removal that removed nothing, and a being told its act
|
|
74
|
+
// succeeded when it touched no record has been told a lie.
|
|
75
|
+
remove: (voicePk) => {
|
|
76
|
+
const row = warden.standing(voicePk);
|
|
77
|
+
if (!row || !reaches(row)) return false;
|
|
78
|
+
return warden.amend(voicePk, { remove: [beingPk] });
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// The one act of spending a relation, written once. `ask` below is a caller
|
|
84
|
+
// of it and so is every organ minted by `errand`, because they are the same
|
|
85
|
+
// act: seal down a relation, move it, read what came back. A second copy of
|
|
86
|
+
// this would be a second place the first-ask rotation could be forgotten.
|
|
87
|
+
// `leash`, where there is one, is the call this ask is made in the course of.
|
|
88
|
+
// It is handed to the kit rather than read here: what may go onward is the
|
|
89
|
+
// difference between when the message arrived and this moment, and only the
|
|
90
|
+
// kit can take the second reading at the moment of sealing. A being holding a
|
|
91
|
+
// leash therefore cannot widen an allowance even by mistake, which is the
|
|
92
|
+
// whole reason the remainder never passes through the being as a number.
|
|
93
|
+
const spend = async ({ from, at, being, method, allowance, leash = null, random }) => {
|
|
94
|
+
const row = relationFor(from, at);
|
|
95
|
+
if (!row) throw new Error('no relation');
|
|
96
|
+
const rotation = rotations.get(row) ?? null;
|
|
97
|
+
// Spent whether or not an answer comes back: the far door rotates the
|
|
98
|
+
// standing at step three, long before it decides whether to speak.
|
|
99
|
+
rotations.delete(row);
|
|
100
|
+
const envelope = await warden.ask(row, {
|
|
101
|
+
seq: row.seq + 1n,
|
|
102
|
+
commitment: rotation,
|
|
103
|
+
allowance,
|
|
104
|
+
leash,
|
|
105
|
+
being,
|
|
106
|
+
method,
|
|
107
|
+
random,
|
|
108
|
+
});
|
|
109
|
+
// An ask that could not be judged is not made, and the kit says so by
|
|
110
|
+
// handing back nothing to send.
|
|
111
|
+
if (!envelope) return null;
|
|
112
|
+
const answer = await reach(row.hints, envelope);
|
|
113
|
+
if (answer === null) return null;
|
|
114
|
+
return readAnswer({
|
|
115
|
+
envelope: answer,
|
|
116
|
+
padlockSecret: warden.padlock.secret,
|
|
117
|
+
wardenPk: row.warden,
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return Object.freeze({
|
|
122
|
+
// What a stranger holds: the door's name, its commitment, its padlock and
|
|
123
|
+
// where to reach it. No voice, so it opens nothing.
|
|
124
|
+
card: () => warden.card(),
|
|
125
|
+
|
|
126
|
+
// The door's own being — the one every voice reaches, holders included.
|
|
127
|
+
publicBeing: () => warden.publicBeing(),
|
|
128
|
+
|
|
129
|
+
// Hold a being: what is held is a contract and its resolvers together,
|
|
130
|
+
// never an object and a blueprint that happen to have been handed in at
|
|
131
|
+
// the same moment. Those two can disagree, and the whole meaning of a
|
|
132
|
+
// digest rests on their not disagreeing.
|
|
133
|
+
//
|
|
134
|
+
// The organs are supplied here, which is what "scoped by the ground"
|
|
135
|
+
// means: the ground decides what is on the tray, and the being takes only
|
|
136
|
+
// what it declared. A being cannot reach for one that was never offered,
|
|
137
|
+
// and cannot be given one it never asked for.
|
|
138
|
+
// The contacts context is minted here, per hold, over this being's own
|
|
139
|
+
// name — derived from the seed the way the kit derives it, because the
|
|
140
|
+
// being must be shut inside its own context and the hold that names it has
|
|
141
|
+
// not happened yet.
|
|
142
|
+
hold: async (one, { seed, heirSeed, cells, organs }) => {
|
|
143
|
+
const pk = (await signingPair(seed)).pk;
|
|
144
|
+
return warden.hold(one.raise(organs, contacts(pk)), {
|
|
145
|
+
seed,
|
|
146
|
+
heirSeed,
|
|
147
|
+
cells,
|
|
148
|
+
blueprint: one.contract,
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// Mint a voice and write the standing that lets it reach one being.
|
|
153
|
+
grant: (being, keys) => warden.grant(being, keys),
|
|
154
|
+
|
|
155
|
+
// The other direction: record an invitation this ground was handed, and say
|
|
156
|
+
// which of its beings spends it. A relation nobody here owns belongs to the
|
|
157
|
+
// ground itself and travels nowhere, which is a different thing and not
|
|
158
|
+
// this; a being is named, always.
|
|
159
|
+
//
|
|
160
|
+
// What comes back reads the relation and cannot move it. `seq` is the count
|
|
161
|
+
// kept against that far door, which is the one fact a caller has any
|
|
162
|
+
// business watching.
|
|
163
|
+
remember: async (invitation, { being, heirSeed }) => {
|
|
164
|
+
const row = warden.remember(invitation, { being });
|
|
165
|
+
const next = await signingPair(heirSeed);
|
|
166
|
+
rotations.set(row, await commitment(invitation.warden, next.pk));
|
|
167
|
+
return Object.freeze({
|
|
168
|
+
warden: invitation.warden,
|
|
169
|
+
being,
|
|
170
|
+
hints: () => [...row.hints],
|
|
171
|
+
seq: () => row.seq,
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// Spend a relation: seal the ask down it, post it at the hints the
|
|
176
|
+
// invitation carried, and read the answer back. All four acts are the
|
|
177
|
+
// kit's — `ask`, `carry` under it, `reach`, `readAnswer` — and what is
|
|
178
|
+
// Nervur's is only which relation, which number, and the padlock secret
|
|
179
|
+
// staying inside this closure.
|
|
180
|
+
//
|
|
181
|
+
// The number is this ground's to pick, not a caller's: a relation that
|
|
182
|
+
// repeats one is a relation the far door refuses, and a caller counting by
|
|
183
|
+
// hand is a caller that will eventually get it wrong.
|
|
184
|
+
//
|
|
185
|
+
// `random` is the **drawn bytes** for this one seal, as at `judge` and
|
|
186
|
+
// unlike `listen`, because here the ground has one message and not a
|
|
187
|
+
// stream of them.
|
|
188
|
+
//
|
|
189
|
+
// Silence and unreachable are told apart, and they must be: silence is
|
|
190
|
+
// `null`, the far house having judged and said nothing, while a road that
|
|
191
|
+
// does not answer throws out of the carriage. Only one of the two means the
|
|
192
|
+
// far house said no. A being spending a relation it does not hold is
|
|
193
|
+
// neither — it is a fault at home, and it throws before a byte moves.
|
|
194
|
+
ask: ({ from, at, being = null, method = null, allowance, random }) =>
|
|
195
|
+
spend({ from, at, being, method, allowance, random }),
|
|
196
|
+
|
|
197
|
+
// The organ that crosses. Everything above is the ground talking to another
|
|
198
|
+
// house; this is how a being does it, and a being may do it no other way.
|
|
199
|
+
//
|
|
200
|
+
// What is minted is a reach and nothing more. The being does not hold the
|
|
201
|
+
// warden, does not hold the relation, and cannot name a house: `from` and
|
|
202
|
+
// `at` are shut inside this closure at mint, so an organ is one road to one
|
|
203
|
+
// house on behalf of one being. That is what keeps it unspendable twice —
|
|
204
|
+
// there is nothing here to copy and point elsewhere.
|
|
205
|
+
//
|
|
206
|
+
// Which being at the far house is still the caller's to name, because the
|
|
207
|
+
// standing the invitation carries is what decides that, and it is judged
|
|
208
|
+
// over there. An organ that filtered it here would be this house guessing
|
|
209
|
+
// at the far house's answer.
|
|
210
|
+
//
|
|
211
|
+
// The leash is handed in rather than chosen, and it is the one the door
|
|
212
|
+
// gave the resolver. The being passes it on whole and never a number read
|
|
213
|
+
// off it: what may go onward is this door's dwell subtracted at the moment
|
|
214
|
+
// of sealing, which is later than any moment the being could have read. So
|
|
215
|
+
// the remainder reaches the far house without the being having chosen it,
|
|
216
|
+
// and a being cannot hand its organ a wider allowance than it received. The
|
|
217
|
+
// ground adds no arithmetic of its own — a leash that has run out is
|
|
218
|
+
// refused by the kit at the moment of carrying, and shows up here as
|
|
219
|
+
// silence.
|
|
220
|
+
//
|
|
221
|
+
// `random` is a **function** the organ draws from per message, as at
|
|
222
|
+
// `listen` and unlike `judge`: an organ has a stream of messages ahead of
|
|
223
|
+
// it and not one, and a seal that reused its bytes would be a seal.
|
|
224
|
+
errand: ({ from, at, random }) =>
|
|
225
|
+
Object.freeze({
|
|
226
|
+
ask: (leash, { being = null, method = null } = {}) =>
|
|
227
|
+
spend({ from, at, being, method, leash, random: random() }),
|
|
228
|
+
}),
|
|
229
|
+
|
|
230
|
+
// The whole of the door: bytes in, bytes or silence out.
|
|
231
|
+
//
|
|
232
|
+
// `random` here is the **drawn bytes** for this one judgment. At `listen`
|
|
233
|
+
// below it is a **function** the host draws from, once per message. The
|
|
234
|
+
// two differ because the drawing belongs to whoever has messages to count,
|
|
235
|
+
// and that is the host — so a ground handed a function here would meet
|
|
236
|
+
// every ask with silence, the warden's global catch turning the fault into
|
|
237
|
+
// the same refusal as everything else.
|
|
238
|
+
judge: (envelope, terrain) => warden.judge(envelope, terrain),
|
|
239
|
+
|
|
240
|
+
// Listen. This is the one place the warden is handed anywhere, and it goes
|
|
241
|
+
// to the kit's own host adapter rather than out of this closure — the door
|
|
242
|
+
// is the half of the carriage that cannot be portable, so a ground that
|
|
243
|
+
// only calls out never reaches this and never imports a host's API.
|
|
244
|
+
//
|
|
245
|
+
// The hint that comes back is the whole address: Quo posts to it exactly
|
|
246
|
+
// as given. `close` drops the listening socket and the keep-alive
|
|
247
|
+
// connections with it, because a door that waits for the last idle caller
|
|
248
|
+
// to time out is a door that never shuts.
|
|
249
|
+
// A door holds callers to the limit its warden publishes, because a
|
|
250
|
+
// published limit nobody enforces is worse than none — a caller can
|
|
251
|
+
// compute it before sending and would be told the truth about a door that
|
|
252
|
+
// then reads anything. A warden that publishes zero has stated no limit,
|
|
253
|
+
// so nothing is enforced; the kit's door spells that `null`.
|
|
254
|
+
//
|
|
255
|
+
// A ground that declared no hints publishes the one it just got: an
|
|
256
|
+
// ephemeral port is not knowable before the socket is open, so a ground
|
|
257
|
+
// could otherwise only ever grant invitations with no road in them. A
|
|
258
|
+
// ground that declared its own hints keeps them — it knows where it is
|
|
259
|
+
// reachable from and a loopback address appended to that would be a road
|
|
260
|
+
// no stranger can take.
|
|
261
|
+
listen: async ({ clock, random, host, port, limit } = {}) => {
|
|
262
|
+
const door = await serve(warden, {
|
|
263
|
+
clock,
|
|
264
|
+
random,
|
|
265
|
+
host,
|
|
266
|
+
port,
|
|
267
|
+
limit: limit ?? (warden.limit > 0n ? warden.limit : null),
|
|
268
|
+
});
|
|
269
|
+
if (warden.hints.length === 0) warden.hints = [door.hint];
|
|
270
|
+
return door;
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|