@nervur-org/dock 0.4.1 → 0.6.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 +17 -15
- package/api/route.ts +4 -72
- package/beings/avatar.ts +11 -7
- package/beings/carry.ts +129 -116
- package/beings/clock.ts +11 -7
- package/beings/desk.ts +7 -6
- package/beings/doorbell.ts +36 -7
- package/beings/index.ts +17 -4
- package/beings/join.ts +7 -4
- package/beings/link.ts +1 -1
- package/beings/porter.ts +5 -1
- package/beings/presence.ts +15 -5
- package/beings/setup.ts +27 -21
- package/beings/user.ts +26 -17
- package/beings/way.ts +1 -1
- package/{dock → box}/agents.ts +37 -44
- package/{dock → box}/apns.ts +4 -4
- package/box/box.ts +31 -0
- package/box/contract.ts +41 -0
- package/box/dialers.ts +88 -0
- package/box/dock.ts +148 -0
- package/box/envoy.ts +96 -0
- package/box/faculty.ts +111 -0
- package/{dock → box}/fcm.ts +1 -1
- package/box/grants.ts +90 -0
- package/box/http.ts +93 -0
- package/{dock → box}/http2.ts +7 -7
- package/box/index.ts +201 -0
- package/box/join.ts +78 -0
- package/box/local.ts +37 -0
- package/{dock → box}/location.ts +15 -3
- package/{dock → box}/notifications.ts +17 -5
- package/box/push.ts +85 -0
- package/box/ring.ts +85 -0
- package/{dock → box}/service.ts +2 -2
- package/box/timer.ts +150 -0
- package/box/tree.ts +22 -0
- package/{dock → box}/webpush.ts +2 -2
- package/cli/client.ts +1 -1
- package/cli/daemon.ts +101 -103
- package/cli/estate.ts +2 -3
- package/cli/nervur.ts +44 -25
- package/cli/stand.ts +1 -1
- package/dist/api/route.d.ts +1 -2
- package/dist/api/route.js +4 -76
- package/dist/beings/avatar.d.ts +4 -3
- package/dist/beings/avatar.js +9 -6
- package/dist/beings/carry.d.ts +10 -12
- package/dist/beings/carry.js +119 -127
- package/dist/beings/clock.js +10 -7
- package/dist/beings/desk.js +8 -7
- package/dist/beings/doorbell.d.ts +3 -1
- package/dist/beings/doorbell.js +37 -4
- package/dist/beings/index.d.ts +24 -4
- package/dist/beings/index.js +15 -4
- package/dist/beings/join.d.ts +1 -0
- package/dist/beings/join.js +6 -4
- package/dist/beings/link.d.ts +0 -1
- package/dist/beings/link.js +1 -1
- package/dist/beings/porter.js +5 -1
- package/dist/beings/presence.d.ts +1 -0
- package/dist/beings/presence.js +12 -3
- package/dist/beings/setup.d.ts +5 -2
- package/dist/beings/setup.js +16 -15
- package/dist/beings/user.d.ts +4 -3
- package/dist/beings/user.js +18 -11
- package/dist/{dock → box}/agents.d.ts +7 -4
- package/dist/{dock → box}/agents.js +30 -37
- package/dist/{dock → box}/apns.d.ts +1 -1
- package/dist/{dock → box}/apns.js +3 -3
- package/dist/box/box.d.ts +9 -0
- package/dist/box/box.js +18 -0
- package/dist/box/contract.d.ts +6 -0
- package/dist/box/contract.js +35 -0
- package/dist/{dock → box}/dialers.d.ts +7 -4
- package/dist/box/dialers.js +76 -0
- package/dist/box/dock.d.ts +182 -0
- package/dist/box/dock.js +128 -0
- package/dist/box/envoy.d.ts +44 -0
- package/dist/box/envoy.js +75 -0
- package/dist/box/faculty.d.ts +68 -0
- package/dist/box/faculty.js +97 -0
- package/dist/{dock → box}/fcm.d.ts +1 -1
- package/dist/{dock → box}/grants.d.ts +4 -3
- package/dist/box/grants.js +78 -0
- package/dist/box/http.d.ts +26 -0
- package/dist/box/http.js +89 -0
- package/dist/{dock → box}/http2.d.ts +1 -1
- package/dist/{dock → box}/http2.js +6 -6
- package/dist/box/index.d.ts +46 -0
- package/dist/box/index.js +179 -0
- package/dist/box/join.d.ts +19 -0
- package/dist/box/join.js +67 -0
- package/dist/{dock → box}/local.d.ts +3 -2
- package/dist/box/local.js +27 -0
- package/dist/{dock → box}/location.d.ts +3 -0
- package/dist/box/location.js +21 -0
- package/dist/{dock → box}/notifications.d.ts +3 -0
- package/dist/{dock → box}/notifications.js +14 -4
- package/dist/{dock → box}/push.d.ts +12 -18
- package/dist/box/push.js +83 -0
- package/dist/box/ring.d.ts +18 -0
- package/dist/box/ring.js +55 -0
- package/dist/{dock → box}/service.d.ts +1 -1
- package/dist/{dock → box}/service.js +1 -1
- package/dist/{dock → box}/timer.d.ts +2 -0
- package/dist/box/timer.js +127 -0
- package/dist/box/tree.d.ts +5 -0
- package/dist/box/tree.js +14 -0
- package/dist/{dock → box}/webpush.d.ts +1 -1
- package/dist/cli/client.d.ts +1 -0
- package/dist/cli/daemon.d.ts +2 -8
- package/dist/cli/daemon.js +102 -107
- package/dist/cli/estate.js +2 -3
- package/dist/cli/nervur.js +44 -24
- package/dist/cli/stand.d.ts +0 -14
- package/dist/cli/stand.js +1 -1
- package/dist/harbor/browser.d.ts +8 -5
- package/dist/harbor/browser.js +20 -21
- package/dist/harbor/capacitor.d.ts +3 -3
- package/dist/harbor/capacitor.js +9 -8
- package/dist/harbor/disk.d.ts +9 -9
- package/dist/harbor/disk.js +56 -51
- package/dist/harbor/edge/alarm.d.ts +4 -2
- package/dist/harbor/edge/alarm.js +9 -19
- package/dist/harbor/edge/edge.d.ts +7 -6
- package/dist/harbor/edge/edge.js +58 -53
- package/dist/harbor/edge/exercise.js +4 -4
- package/dist/harbor/edge/given.d.ts +0 -1
- package/dist/harbor/edge/given.js +0 -29
- package/dist/harbor/edge/storage.d.ts +0 -2
- package/dist/harbor/edge/storage.js +2 -2
- package/dist/harbor/http.js +4 -2
- package/dist/harbor/sealed.d.ts +0 -5
- package/dist/harbor/sealed.js +0 -39
- package/dist/harbor/tauri.d.ts +2 -2
- package/dist/harbor/tauri.js +13 -10
- package/dist/human/app.d.ts +1 -1
- package/dist/human/app.js +10 -12
- package/dist/human/door.d.ts +0 -1
- package/dist/human/door.js +1 -1
- package/dist/human/html.js +1 -1
- package/dist/human/move.js +1 -1
- package/dist/human/tab.bundle.txt +3629 -2824
- package/dist/human/wake.d.ts +5 -2
- package/dist/human/wake.js +23 -14
- package/dist/human/web.d.ts +0 -1
- package/dist/human/web.js +3 -3
- package/dist/human/worker.bundle.txt +2945 -2118
- package/dist/human/worlds.js +3 -3
- package/dist/mcp/agent.d.ts +0 -1
- package/dist/mcp/agent.js +1 -1
- package/dist/mcp/allow.js +3 -2
- package/dist/mcp/http.d.ts +0 -1
- package/dist/mcp/http.js +1 -1
- package/dist/mcp/oauth.d.ts +3 -4
- package/dist/mcp/oauth.js +4 -4
- package/dist/mcp/route.d.ts +1 -1
- package/dist/mcp/runner.d.ts +0 -1
- package/dist/mcp/runner.js +1 -1
- package/dist/mcp/server.d.ts +0 -1
- package/dist/mcp/server.js +1 -1
- package/harbor/browser.ts +22 -24
- package/harbor/capacitor.ts +9 -8
- package/harbor/disk.ts +53 -48
- package/harbor/edge/alarm.ts +15 -12
- package/harbor/edge/edge.ts +61 -55
- package/harbor/edge/exercise.ts +4 -4
- package/harbor/edge/given.ts +3 -33
- package/harbor/edge/storage.ts +2 -2
- package/harbor/http.ts +5 -3
- package/harbor/sealed.ts +0 -35
- package/harbor/tauri.ts +13 -10
- package/human/app.ts +12 -13
- package/human/door.ts +1 -1
- package/human/html.ts +1 -1
- package/human/local.ts +1 -1
- package/human/move.ts +1 -1
- package/human/tab.bundle.txt +3629 -2824
- package/human/wake.ts +36 -16
- package/human/web.ts +4 -4
- package/human/worker.bundle.txt +2945 -2118
- package/human/worlds.ts +3 -3
- package/mcp/agent.ts +1 -1
- package/mcp/allow.ts +2 -1
- package/mcp/http.ts +1 -1
- package/mcp/oauth.ts +4 -4
- package/mcp/route.ts +1 -1
- package/mcp/runner.ts +1 -1
- package/mcp/server.ts +1 -1
- package/package.json +10 -18
- package/GETTING_STARTED.md +0 -138
- package/GLOSSARY.md +0 -118
- package/WORLDS.md +0 -569
- package/beings/envoy.ts +0 -218
- package/dist/beings/envoy.d.ts +0 -54
- package/dist/beings/envoy.js +0 -211
- package/dist/dock/console.d.ts +0 -6
- package/dist/dock/console.js +0 -20
- package/dist/dock/dialers.js +0 -75
- package/dist/dock/faculty.d.ts +0 -24
- package/dist/dock/faculty.js +0 -142
- package/dist/dock/grants.js +0 -99
- package/dist/dock/index.d.ts +0 -41
- package/dist/dock/index.js +0 -113
- package/dist/dock/local.js +0 -44
- package/dist/dock/location.js +0 -11
- package/dist/dock/push.js +0 -132
- package/dist/dock/timer.js +0 -149
- package/dock/console.ts +0 -23
- package/dock/dialers.ts +0 -85
- package/dock/faculty.ts +0 -148
- package/dock/grants.ts +0 -111
- package/dock/index.ts +0 -133
- package/dock/local.ts +0 -48
- package/dock/push.ts +0 -161
- package/dock/timer.ts +0 -174
- package/quo-api.md +0 -196
- package/quo-app-bells.md +0 -280
- package/quo-dock.md +0 -1945
- package/quo-harbor.md +0 -874
- package/quo-human.md +0 -1042
- package/quo-mcp.md +0 -343
- /package/dist/{dock → box}/fcm.js +0 -0
- /package/dist/{dock → box}/webpush.js +0 -0
package/dock/local.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// The box's way in: `local`, one faculty of the dock ward. The daemon saw a
|
|
3
|
-
// process reach a socket only the device's user can open, and that is the
|
|
4
|
-
// whole of the vouch; file permissions are the device's rules, which is what
|
|
5
|
-
// `quo/SPEC.md` allows a harbor to hand the root by.
|
|
6
|
-
//
|
|
7
|
-
// She holds the nonces this box minted, in this process and never in cells:
|
|
8
|
-
// a vouch that outlived a restart would be a vouch for a process that is
|
|
9
|
-
// gone. Each is honoured once, and a `local` proof arriving any other way
|
|
10
|
-
// names no nonce and is refused.
|
|
11
|
-
//
|
|
12
|
-
// The device's own code mints on her directly, as a device fires its own
|
|
13
|
-
// timer: `mint` is not an ask, it crosses no door, and nothing at a door
|
|
14
|
-
// can reach it. The desk asks `verify` at her standing, as she asks any way
|
|
15
|
-
// in.
|
|
16
|
-
import { Faculty } from './faculty.ts';
|
|
17
|
-
import type { JsonObject } from 'nervur';
|
|
18
|
-
import { VERIFY, type Verified } from '../beings/way.ts';
|
|
19
|
-
|
|
20
|
-
// The name the dock ward lends her under. A box with no local side lends no
|
|
21
|
-
// such name, and the desk there holds no such standing.
|
|
22
|
-
export const LOCAL = 'local';
|
|
23
|
-
|
|
24
|
-
export class Local extends Faculty {
|
|
25
|
-
static override asks = { verify: VERIFY };
|
|
26
|
-
|
|
27
|
-
readonly #nonces = new Map<string, Verified>();
|
|
28
|
-
|
|
29
|
-
// A nonce for one client this box saw itself, spent by the first proof
|
|
30
|
-
// that names it. The caller drops it when the admit is over: a nonce that
|
|
31
|
-
// was never spent is not a way in that stays open.
|
|
32
|
-
mint(nonce: string, who: Verified): void {
|
|
33
|
-
this.#nonces.set(nonce, who);
|
|
34
|
-
}
|
|
35
|
-
drop(nonce: string): void {
|
|
36
|
-
this.#nonces.delete(nonce);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
verify(args: JsonObject): Verified | null {
|
|
40
|
-
const proof = args.proof;
|
|
41
|
-
if (!proof || typeof proof !== 'object' || Array.isArray(proof)) return null;
|
|
42
|
-
const nonce = (proof as JsonObject).nonce;
|
|
43
|
-
const who = typeof nonce === 'string' ? this.#nonces.get(nonce) : undefined;
|
|
44
|
-
if (!who) return null;
|
|
45
|
-
this.#nonces.delete(nonce as string);
|
|
46
|
-
return who;
|
|
47
|
-
}
|
|
48
|
-
}
|
package/dock/push.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// Waking a device that is not reachable, the one way its platform allows: a
|
|
3
|
-
// push that carries the device's token and the banner sealed to that
|
|
4
|
-
// device's own key, which nothing between the world and the phone can
|
|
5
|
-
// read. A token names its
|
|
6
|
-
// kind, and a kind is one ring: `web` is Web Push under a keypair a world
|
|
7
|
-
// mints itself, `apns` and `fcm` are the two stores' own, handed on to the
|
|
8
|
-
// publisher's bell service by a world that holds no platform key. A box that
|
|
9
|
-
// was given no ring for a kind says so, which is what a world that rings no
|
|
10
|
-
// iPhones says to one.
|
|
11
|
-
//
|
|
12
|
-
// The rings are live objects and not values, so they are not cells: a
|
|
13
|
-
// terrain subclasses and builds its own in `rings`, out of what its
|
|
14
|
-
// environment and this being's cells give it.
|
|
15
|
-
import { OWNER } from 'nervur';
|
|
16
|
-
import { arithmetic } from 'nervur/ward';
|
|
17
|
-
import type { Asker, JsonObject, OccupantRecord } from 'nervur';
|
|
18
|
-
import { Faculty } from './faculty.ts';
|
|
19
|
-
import { mintVapid } from './webpush.ts';
|
|
20
|
-
|
|
21
|
-
// What a device hands a world so it can be woken, kept in that device's
|
|
22
|
-
// occupant notes and re-sent whenever the device comes to the foreground,
|
|
23
|
-
// since a push service may hand a device a new one at any time. A token of
|
|
24
|
-
// a store's kind names its bell, the address of the service holding the
|
|
25
|
-
// keys of the app that minted it, and that app, so a world rings a phone
|
|
26
|
-
// running an app it never shipped and holds no key; Web Push is the
|
|
27
|
-
// world's own key and names no service.
|
|
28
|
-
export type Token = { kind: string; bell?: string; app?: string } & JsonObject;
|
|
29
|
-
export const isToken = (v: unknown): v is Token => v !== null && typeof v === 'object' && !Array.isArray(v) && typeof (v as { kind?: unknown }).kind === 'string';
|
|
30
|
-
|
|
31
|
-
// The bell a token names, when it names one a ring can reach.
|
|
32
|
-
export function bellOf(token: Token): string | undefined {
|
|
33
|
-
if (typeof token.bell !== 'string') return undefined;
|
|
34
|
-
try {
|
|
35
|
-
const u = new URL(token.bell);
|
|
36
|
-
return u.protocol === 'https:' || u.protocol === 'http:' ? token.bell : undefined;
|
|
37
|
-
} catch {
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// One platform's way of ringing. It is handed the token whole, because only
|
|
43
|
-
// the platform knows what is in one, and it says nothing back: a ring either
|
|
44
|
-
// left or threw.
|
|
45
|
-
//
|
|
46
|
-
// `sealed` is the banner, sealed to the device's banner key by the world
|
|
47
|
-
// that rang. Nothing between the world and the device can read it: not this
|
|
48
|
-
// box, not the bell service, not the platform that carries it. A ring with
|
|
49
|
-
// neither a seal nor a line wakes the device and says nothing, which is
|
|
50
|
-
// what a world with nothing to show sends.
|
|
51
|
-
//
|
|
52
|
-
// `line` is the same banner, unsealed, and only a ground whose own
|
|
53
|
-
// encryption the world performs may take one: Web Push, where RFC 8291
|
|
54
|
-
// encrypts the body to keys only that browser holds and the world holds the
|
|
55
|
-
// keypair the subscription was made under. A store's ground is handed the
|
|
56
|
-
// seal and never this, because what carries it is a service the world does
|
|
57
|
-
// not own.
|
|
58
|
-
export type Line = { title: string; body: string };
|
|
59
|
-
export type Ring = { ring(token: Token, sealed?: string, line?: Line): Promise<void> };
|
|
60
|
-
|
|
61
|
-
// What a platform will carry, so a seal too big is refused where a human
|
|
62
|
-
// can still read why rather than two hops later by a service nobody reads.
|
|
63
|
-
export const SEAL = 4096;
|
|
64
|
-
|
|
65
|
-
// The line a device shows when it was woken while nobody was looking,
|
|
66
|
-
// composed from one object. A pushed object is whatever the far being sent,
|
|
67
|
-
// so the two names a notification has are taken when they are there and the
|
|
68
|
-
// object is shown as it is when they are not.
|
|
69
|
-
export function banner(o: JsonObject): Line {
|
|
70
|
-
const text = (v: unknown) => (typeof v === 'string' && v.trim() ? v.trim() : '');
|
|
71
|
-
const title = text(o.title) || text(o.from) || 'Nervur';
|
|
72
|
-
const body =
|
|
73
|
-
text(o.body) ||
|
|
74
|
-
text(o.said) ||
|
|
75
|
-
Object.entries(o)
|
|
76
|
-
.filter(([k]) => k !== 'title' && k !== 'from')
|
|
77
|
-
.map(([k, v]) => `${k}: ${typeof v === 'string' ? v : JSON.stringify(v)}`)
|
|
78
|
-
.join(', ');
|
|
79
|
-
return { title, body };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// A banner sealed to a device's banner key, as hex, or null when it will
|
|
83
|
-
// not seal or will not fit. The world seals and the process that draws the
|
|
84
|
-
// banner opens it; no doorbell, no bell service and no platform between
|
|
85
|
-
// them holds either half, which is why a ring may carry a line at all.
|
|
86
|
-
export async function sealBanner(line: JsonObject, key: string): Promise<string | null> {
|
|
87
|
-
if (!/^[0-9a-f]{64}$/.test(key)) return null;
|
|
88
|
-
try {
|
|
89
|
-
const boxed = await arithmetic.box(new TextEncoder().encode(JSON.stringify(line)), arithmetic.unhex(key), globalThis.crypto.getRandomValues(new Uint8Array(32)));
|
|
90
|
-
const wire = arithmetic.hex(boxed.bytes);
|
|
91
|
-
return wire.length > SEAL ? null : wire;
|
|
92
|
-
} catch {
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// The root and nobody else: where a box hands a phone's ring is the box's own.
|
|
98
|
-
const root = (_occ: OccupantRecord | undefined, asker: Asker) => asker.id === OWNER;
|
|
99
|
-
|
|
100
|
-
export class Push extends Faculty {
|
|
101
|
-
// What she keeps, beside every faculty's: the world's doorbell key, a P-256
|
|
102
|
-
// JWK she mints herself the first time a terrain asks for one, so a Web
|
|
103
|
-
// Push is signed under a key that lives with the wards and in no file; and
|
|
104
|
-
// the bell service a phone's ring is handed to when its token names none,
|
|
105
|
-
// a row the root keeps, since whose service it is belongs to an estate.
|
|
106
|
-
static override cells: JsonObject = { watched: {}, minted: 0, key: null, service: null };
|
|
107
|
-
static override asks = {
|
|
108
|
-
ring: { description: 'wake a device: its token, the banner sealed to it, or the line itself where the ground encrypts it', input: { type: 'object', properties: { token: { type: 'object' }, sealed: { type: 'string' }, line: { type: 'object', properties: { title: { type: 'string' }, body: { type: 'string' } }, required: ['title', 'body'] } }, required: ['token'] } },
|
|
109
|
-
kinds: { description: 'which kinds this box can ring', input: { type: 'object' } },
|
|
110
|
-
keep: { description: 'the bell service a phone is rung through when its token names none; null forgets it', input: { type: 'object', properties: { service: { type: ['string', 'null'] } }, required: ['service'] }, for: root },
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
// The doorbell key, minted once and kept. A terrain asks for it when it
|
|
114
|
-
// serves a web route; a box that never does holds none and rings no tabs.
|
|
115
|
-
async mint(): Promise<JsonWebKey> {
|
|
116
|
-
if (this.cells.key === null) this.cells.key = (await mintVapid()).key as unknown as JsonObject;
|
|
117
|
-
return this.cells.key as unknown as JsonWebKey;
|
|
118
|
-
}
|
|
119
|
-
get key(): JsonWebKey | null {
|
|
120
|
-
return this.cells.key as unknown as JsonWebKey | null;
|
|
121
|
-
}
|
|
122
|
-
get service(): string | undefined {
|
|
123
|
-
return typeof this.cells.service === 'string' ? this.cells.service : undefined;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
keep(args: JsonObject): JsonObject {
|
|
127
|
-
const service = args.service;
|
|
128
|
-
if (service !== null && (typeof service !== 'string' || !/^https?:\/\/\S+$/.test(service))) return { error: 'a bell service is a URL over http or https, or null' };
|
|
129
|
-
this.cells.service = service === null ? null : service.replace(/\/$/, '');
|
|
130
|
-
return { service: this.cells.service };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// What this box can ring. A terrain overrides it and builds its rings from
|
|
134
|
-
// its environment and this being's cells; the base rings nothing.
|
|
135
|
-
protected get rings(): Readonly<Record<string, Ring>> {
|
|
136
|
-
return {};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
kinds(): JsonObject {
|
|
140
|
-
return { kinds: Object.keys(this.rings) };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async ring(args: JsonObject): Promise<JsonObject> {
|
|
144
|
-
const token = args.token;
|
|
145
|
-
if (!isToken(token)) return { error: 'a token names its kind' };
|
|
146
|
-
const sealed = args.sealed;
|
|
147
|
-
if (sealed !== undefined && typeof sealed !== 'string') return { error: 'a seal is text' };
|
|
148
|
-
if (typeof sealed === 'string' && sealed.length > SEAL) return { error: `a seal is at most ${SEAL} bytes` };
|
|
149
|
-
const said = args.line;
|
|
150
|
-
if (said !== undefined && (said === null || typeof said !== 'object' || Array.isArray(said) || typeof said.title !== 'string' || typeof said.body !== 'string')) return { error: 'a line is a title and a body' };
|
|
151
|
-
const line = said as Line | undefined;
|
|
152
|
-
const r = Object.hasOwn(this.rings, token.kind) ? this.rings[token.kind] : undefined;
|
|
153
|
-
if (!r) return { error: `this box cannot ring a ${token.kind}` };
|
|
154
|
-
try {
|
|
155
|
-
await r.ring(token, sealed, line);
|
|
156
|
-
return { rang: token.kind };
|
|
157
|
-
} catch (e) {
|
|
158
|
-
return { error: e instanceof Error ? e.message : 'the ring threw' };
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
package/dock/timer.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// The box's clock, and the one being on a device that reads one. A being who
|
|
3
|
-
// could read a clock could not be moved, since the moment she read would be
|
|
4
|
-
// her box's and no other, so the moment reaches every other being the one way
|
|
5
|
-
// anything reaches a being: as an ask at her door, from this faculty, as the
|
|
6
|
-
// occupant she named when she asked to be woken.
|
|
7
|
-
//
|
|
8
|
-
// She arms for the next due moment and for nothing else. Every watcher
|
|
9
|
-
// answers a round with when she is next due, this arms for the earliest of
|
|
10
|
-
// them, and a box whose watchers all say null arms nothing and sleeps until
|
|
11
|
-
// somebody keeps something. So a box with no schedules never ticks, and a
|
|
12
|
-
// home on the edge with a sweep at three in the morning wakes once a night.
|
|
13
|
-
//
|
|
14
|
-
// A round is armed for one moment at a time and never overlaps the one
|
|
15
|
-
// before, and the moment is floored to the minute with half a second after
|
|
16
|
-
// it, since a watcher floors the moment she is handed and a round that fired
|
|
17
|
-
// a hair early would floor to the minute before.
|
|
18
|
-
//
|
|
19
|
-
// One method knows a terrain, `arm`, and a subclass replaces it: a
|
|
20
|
-
// `setTimeout` here, the object's storage alarm on a worker. Nothing else in
|
|
21
|
-
// her reads a platform.
|
|
22
|
-
import type { JsonObject } from 'nervur';
|
|
23
|
-
import { Faculty, type Woke } from './faculty.ts';
|
|
24
|
-
|
|
25
|
-
const MINUTE = 60_000;
|
|
26
|
-
// A timeout further out than this is not trusted, so the arming is capped and
|
|
27
|
-
// made again when it goes off. It is the base class's own bound and no rule
|
|
28
|
-
// of the faculty: a terrain whose alarm holds a month arms for the month.
|
|
29
|
-
const DAY = 86_400_000;
|
|
30
|
-
|
|
31
|
-
export class Timer extends Faculty {
|
|
32
|
-
static override asks = {
|
|
33
|
-
watch: { description: 'wake me when I say I am next due', input: { type: 'object', properties: { invitation: { type: 'object' }, method: { type: 'string' }, args: { type: 'object' } }, required: ['invitation', 'method'] } },
|
|
34
|
-
due: { description: 'bring my next round forward to this moment', input: { type: 'object', properties: { at: { type: 'number' } }, required: ['at'] } },
|
|
35
|
-
now: { description: 'this box’s moment', input: { type: 'object' } },
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
#timeout: ReturnType<typeof setTimeout> | undefined;
|
|
39
|
-
#stopped = false;
|
|
40
|
-
#armed: number | null = null;
|
|
41
|
-
#round: Promise<Woke[]> | null = null;
|
|
42
|
-
|
|
43
|
-
// The moment she is armed for, or null while she is asleep. For the
|
|
44
|
-
// device's own code and for a suite; it crosses no door.
|
|
45
|
-
get armed(): number | null {
|
|
46
|
-
return this.#armed;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// A watcher says she is due sooner than the box knew. It only ever brings
|
|
50
|
-
// the round forward: what fires is decided at the tick, in the watcher's
|
|
51
|
-
// own cells, so an arming that moved later on somebody's word would be a
|
|
52
|
-
// schedule somebody else held silenced. The door is the whole gate here:
|
|
53
|
-
// a standing at a faculty comes from `lend` alone, and this adds no second
|
|
54
|
-
// one.
|
|
55
|
-
due(args: JsonObject): JsonObject {
|
|
56
|
-
const at = args.at;
|
|
57
|
-
if (typeof at !== 'number' || !Number.isFinite(at)) return { error: 'a due carries the moment: { at }' };
|
|
58
|
-
const when = moment(at);
|
|
59
|
-
if (this.#armed === null || when < this.#armed) this.#arm(when);
|
|
60
|
-
return { armed: this.#armed };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Whoever was just filed is ticked at once, and what she answers is what
|
|
64
|
-
// the box arms for. One move covers a box booting, a home moving in and a
|
|
65
|
-
// watcher born long after the box was up: she is never waiting for a round
|
|
66
|
-
// nobody scheduled.
|
|
67
|
-
protected override async welcomed(id: string): Promise<void> {
|
|
68
|
-
await this.round(Date.now(), id);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// The arming went off. A moment still well ahead is the day's cap this
|
|
72
|
-
// class put on its own timeout, so it is armed again rather than fired: a
|
|
73
|
-
// terrain calls this and never has to know how far out the arming was.
|
|
74
|
-
async rang(): Promise<Woke[]> {
|
|
75
|
-
if (this.#stopped) return [];
|
|
76
|
-
if (this.#armed !== null && this.#armed - Date.now() > 1_000) {
|
|
77
|
-
this.arm(this.#armed);
|
|
78
|
-
return [];
|
|
79
|
-
}
|
|
80
|
-
return this.fire(Date.now());
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// The moment, read here and handed on. A device's own code may call it at a
|
|
84
|
-
// moment it names, which is how a suite proves a wake without waiting for
|
|
85
|
-
// one and how an operator makes a round happen now.
|
|
86
|
-
fire(now: number): Promise<Woke[]> {
|
|
87
|
-
return this.round(now);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// One round, and never two: a second while one is in flight is handed the
|
|
91
|
-
// one already running. What every watcher answered decides the next
|
|
92
|
-
// arming, and a `due` that landed while the round ran is not lost.
|
|
93
|
-
protected round(now: number, only?: string): Promise<Woke[]> {
|
|
94
|
-
if (this.#round) return this.#round;
|
|
95
|
-
const mine = this.#run(now, only);
|
|
96
|
-
this.#round = mine;
|
|
97
|
-
return mine.finally(() => {
|
|
98
|
-
if (this.#round === mine) this.#round = null;
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async #run(now: number, only?: string): Promise<Woke[]> {
|
|
103
|
-
if (only === undefined) this.#arm(null); // the arming is spent, whether it went off or the device called this by hand
|
|
104
|
-
const woke = await this.woke((w) => ({ ...w.args, now }), only);
|
|
105
|
-
if (woke === null) {
|
|
106
|
-
this.stop(); // her ward took her out; there is nobody left to wake
|
|
107
|
-
return [];
|
|
108
|
-
}
|
|
109
|
-
// A moment already gone is no arming. Every watcher works out when she is
|
|
110
|
-
// next due from the moment she was handed, so a round fired by hand at a
|
|
111
|
-
// moment that is not this box's answers one that is not this box's
|
|
112
|
-
// either, and a box that armed for it would run a round for a minute long
|
|
113
|
-
// past. A round at the box's own moment always names one still ahead.
|
|
114
|
-
const named = soonest(woke);
|
|
115
|
-
const said = named !== null && named > Date.now() ? named : null;
|
|
116
|
-
const held = this.#armed;
|
|
117
|
-
const at = said === null ? held : held === null ? moment(said) : Math.min(held, moment(said));
|
|
118
|
-
if (at !== null && at !== this.#armed) this.#arm(at);
|
|
119
|
-
else if (at === null && this.#armed !== null) this.#arm(null);
|
|
120
|
-
return woke;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
#arm(when: number | null): void {
|
|
124
|
-
if (this.#stopped) return;
|
|
125
|
-
this.#armed = when;
|
|
126
|
-
this.arm(when);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// The one thing a terrain says about itself: be woken at this moment, or at
|
|
130
|
-
// no moment at all. A subclass replaces this and nothing else of her.
|
|
131
|
-
//
|
|
132
|
-
// Here it is a timeout, capped at a day and armed again when the cap goes
|
|
133
|
-
// off. The box stays up for its work and never for this, so the handle is
|
|
134
|
-
// unreferenced.
|
|
135
|
-
protected arm(when: number | null): void {
|
|
136
|
-
clearTimeout(this.#timeout);
|
|
137
|
-
this.#timeout = undefined;
|
|
138
|
-
if (when === null) return;
|
|
139
|
-
this.#timeout = setTimeout(() => void this.rang(), Math.min(Math.max(when - Date.now(), 0), DAY));
|
|
140
|
-
this.#timeout.unref?.();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// A being her ward took out reaches nobody, so nothing is left to wake. The
|
|
144
|
-
// device calls this when it puts the box down.
|
|
145
|
-
stop(): void {
|
|
146
|
-
this.#stopped = true;
|
|
147
|
-
this.#armed = null;
|
|
148
|
-
this.arm(null);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
now(): JsonObject {
|
|
152
|
-
return { now: Date.now() };
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Half a second past the minute a moment falls in, and never before the
|
|
157
|
-
// moment itself: a watcher floors what she is handed, so a round that fired a
|
|
158
|
-
// hair early would floor to the minute before and fire nothing.
|
|
159
|
-
const moment = (at: number): number => Math.max(at, Math.floor(at / MINUTE) * MINUTE + 500);
|
|
160
|
-
|
|
161
|
-
// What one round learned. Every watcher says when she is next due, and the
|
|
162
|
-
// box arms for the earliest of them. A watcher who named no moment has
|
|
163
|
-
// nothing to be woken for, and so has one who answered something that is no
|
|
164
|
-
// tally at all.
|
|
165
|
-
function soonest(woke: Woke[]): number | null {
|
|
166
|
-
let at: number | null = null;
|
|
167
|
-
for (const w of woke) {
|
|
168
|
-
if (w.out === null || typeof w.out !== 'object') continue;
|
|
169
|
-
const next = (w.out as { next?: unknown }).next;
|
|
170
|
-
if (typeof next !== 'number' || !Number.isFinite(next)) continue;
|
|
171
|
-
if (at === null || next < at) at = next;
|
|
172
|
-
}
|
|
173
|
-
return at;
|
|
174
|
-
}
|
package/quo-api.md
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
# The api route
|
|
2
|
-
|
|
3
|
-
This is the fourth rendering of one describe. The screen speaks a being's
|
|
4
|
-
describe as forms, the model side speaks it as tools, the CLI speaks it as
|
|
5
|
-
lines, and this route speaks it as plain HTTP and JSON. It is one route of
|
|
6
|
-
the daemon described in `papers/quo-dock.md`, which owns the routes, the
|
|
7
|
-
grants, the avatar and the shared invariants; this document assumes all of
|
|
8
|
-
that and adds only what the route does. `papers/WORLDS.md` decides what
|
|
9
|
-
every route is held to: a route says the grammar a caller speaks and never
|
|
10
|
-
who the caller is, and the same credential is honoured on every route.
|
|
11
|
-
|
|
12
|
-
## What it is for
|
|
13
|
-
|
|
14
|
-
A route hears every rung. A request carrying nothing is the stranger every
|
|
15
|
-
route knows: an ask at the named ward's public being as `{}`, which is what
|
|
16
|
-
every stranger is at every door, and what a browser fetch, a page
|
|
17
|
-
rendering server side, a crawler and a tab on another origin are. A
|
|
18
|
-
request carrying a bearer the box granted is an occupant: the identity's
|
|
19
|
-
avatar in the world the grant names, asked through her one standing at the
|
|
20
|
-
world's being. What a script sees with a bearer is what a model sees with
|
|
21
|
-
the same bearer as tools and a tab sees as forms, because all three are
|
|
22
|
-
one avatar's describe for one identity, and the gate is one decision for
|
|
23
|
-
describe and for dispatch. The route judges nothing and names nobody: the
|
|
24
|
-
public being answers a stranger by her own gate, the world's being answers
|
|
25
|
-
an occupant by hers, and the route carries the answer.
|
|
26
|
-
|
|
27
|
-
A grant is the box's and may die at any moment: a box wiped, an occupant
|
|
28
|
-
removed at the world's being, thirty days without a refresh. Nothing of
|
|
29
|
-
the world dies with it, since the avatar and her standing are the ward's;
|
|
30
|
-
only the box's memory of which token names her is gone, and the holder
|
|
31
|
-
trades again on a fresh invitation and lands on the same avatar. So a
|
|
32
|
-
bearer is held by something with a human near it: a model client whose
|
|
33
|
-
human clicks the allow page again, a script whose root mints again, a
|
|
34
|
-
SaaS whose operator signs up again as at any vendor. A program of the
|
|
35
|
-
org's that must run without a human is not a caller of this route. It is
|
|
36
|
-
a harbor of the org with a ward and a standing, or an agent the daemon
|
|
37
|
-
boots on the box, and either speaks Quo on keys that do not expire.
|
|
38
|
-
|
|
39
|
-
A world is a ward, so the route names one. Every ward `init` made holds a
|
|
40
|
-
desk, who is public for the first hello and answers a stranger nothing
|
|
41
|
-
else, so a world's public reads live in a ward of their own, and the route is what
|
|
42
|
-
makes a second ward reachable by name from outside: the corpus of a court
|
|
43
|
-
watcher, a catalogue, a board. An occupant's world is the one her
|
|
44
|
-
invitation named, and a bearer on any other world is refused.
|
|
45
|
-
|
|
46
|
-
## The mapping
|
|
47
|
-
|
|
48
|
-
`packages/dock/api/route.ts` is the route, `Request` in and `Response` out
|
|
49
|
-
and naming no terrain: the daemon mounts it under `/api` and the proxy maps
|
|
50
|
-
it from the api. hostname, and the worker at the edge mounts the same file
|
|
51
|
-
under its harbor's prefix. It asks its harbor for the wards it holds and for
|
|
52
|
-
which of them a caller who names none is at, and for nothing else.
|
|
53
|
-
|
|
54
|
-
| request | Quo |
|
|
55
|
-
| ------------------------------------ | -------------------------------------------------------------------- |
|
|
56
|
-
| `POST /api` `{identity, invitation}` | the trade: her avatar knocks with it, and a grant is issued |
|
|
57
|
-
| `POST /api` `{refresh}` | the next grant; the old refresh token dies |
|
|
58
|
-
| `GET /api` | the describe of the default ward, as `GET /api/<that ward>` |
|
|
59
|
-
| `GET /api/<ward>` | the describe: the public being for nobody, the avatar under a bearer |
|
|
60
|
-
| `GET /api/<ward>/<ask>?k=v` | that ask, the query as args, every value a string |
|
|
61
|
-
| `POST /api/<ward>/<ask>` JSON body | that ask, the object as args |
|
|
62
|
-
| `POST /api/<ward>/<being>/deliver` | a vendor's delivery, the raw body and the headers to that envoy |
|
|
63
|
-
| `GET /api/<ward>/<being>/deliver` | the same with an empty body: a redirect lands here, query in headers |
|
|
64
|
-
| `OPTIONS` | the preflight, any origin, `authorization` allowed |
|
|
65
|
-
|
|
66
|
-
What a caller leaves unsaid follows `papers/WORLDS.md`, and none of it is
|
|
67
|
-
a default the route picks. A credential names an identity in a world, so an
|
|
68
|
-
occupant's ward is her grant's and a bearer on any other ward is refused.
|
|
69
|
-
A stranger carries nothing that names a world, so a stranger who names no
|
|
70
|
-
ward is at the ward the terrain says it is at, the one the box hosts or the
|
|
71
|
-
one it marks, and `GET /api` is what a tab at the web
|
|
72
|
-
route's root sees and a model's first tool list. A being is never named,
|
|
73
|
-
because a caller
|
|
74
|
-
does not choose who answers: naming a ward is naming a door, and behind it
|
|
75
|
-
the ward decides, the public being as nobody for a stranger and the
|
|
76
|
-
identity's avatar for an occupant. The one path that names a being is the
|
|
77
|
-
delivery, and there it is not a choice either: the envoy claimed that path
|
|
78
|
-
at her birth, and a delivery with no envoy named has nowhere to land and
|
|
79
|
-
is 404, never the public being.
|
|
80
|
-
|
|
81
|
-
The trade is the allow page of the model side without the page. Nobody at
|
|
82
|
-
a page vouches for a program, so a trade is a knock that spends: the
|
|
83
|
-
invitation is the one the root minted for that identity on the world's
|
|
84
|
-
being with `nervur invite`, sent as a line, and the avatar knocks with it
|
|
85
|
-
inside the trade. An identity already in is refused with `already in` and
|
|
86
|
-
refreshes instead, or is removed at the world's being and invited again,
|
|
87
|
-
and never re-enters on a spent invitation. What comes back is the box's
|
|
88
|
-
grant, `dock/grants.ts`: an access token for an hour, a refresh token for
|
|
89
|
-
thirty days, the same grant the OAuth dance issues, honoured on the model
|
|
90
|
-
route too.
|
|
91
|
-
|
|
92
|
-
What she says crosses as the model side crosses it:
|
|
93
|
-
|
|
94
|
-
| she answered | the client gets |
|
|
95
|
-
| ------------------------------ | ------------------------------------------------------ |
|
|
96
|
-
| an object | 200 with that JSON |
|
|
97
|
-
| `{ error }` | 400 with that JSON |
|
|
98
|
-
| silence, a throw, a wait spent | 503 `{ error: 'silence' }`: the work may have happened |
|
|
99
|
-
| an ask not hers for this asker | 400 `{ error: 'unknown ask' }`, her own answer |
|
|
100
|
-
|
|
101
|
-
For a stranger, a ward with nobody at the door is 503 beside them, and
|
|
102
|
-
not a 404 of its own: on the wire a stranger who asks a ward that has no
|
|
103
|
-
public being hears one silence, and a device speaking for strangers says
|
|
104
|
-
what the door would. A stranger learns which world exists here and nothing
|
|
105
|
-
else about it. For an occupant the ward's words cross by name, `unreached`
|
|
106
|
-
as 503 `{ error: 'unreached' }`, and `removed` is 401 `{ error: 'removed'
|
|
107
|
-
}` with the grant forgotten at the box, so a script the world's being
|
|
108
|
-
removed starts over with a fresh invitation, exactly as a model client
|
|
109
|
-
does.
|
|
110
|
-
|
|
111
|
-
And what the route says on its own: 404 for a ward not here and for a path
|
|
112
|
-
that is not one of the shapes, 400 for a body that is not a JSON object,
|
|
113
|
-
400 `{ error: 'already in' }` for a trade on an identity that holds a live
|
|
114
|
-
standing, 401 `{ error: 'unauthorized' }` for a bearer nobody issued or one
|
|
115
|
-
that died, 403 `{ error: 'not your world' }` for a bearer on a world the
|
|
116
|
-
grant does not name, 413 for a body past a megabyte, 405 for any other
|
|
117
|
-
method. Every answer is JSON and open to any origin: the route carries
|
|
118
|
-
nothing a stranger can use that she did not choose to say, and a bearer is
|
|
119
|
-
the caller's to keep.
|
|
120
|
-
|
|
121
|
-
The route holds none of the judgement. For a stranger it asks the ward the
|
|
122
|
-
owner's `ask` with no being named, which is the public being asked as
|
|
123
|
-
nobody, and the ward bounds the wait, names the three choices and saves
|
|
124
|
-
before the pointer returns: a public being may write while answering, a
|
|
125
|
-
tally, a board, and what she wrote is hers to keep. For an occupant it
|
|
126
|
-
asks her avatar, `tools` for the describe and `call` for an ask, the two
|
|
127
|
-
things every side asks of her, and saves the ward after.
|
|
128
|
-
|
|
129
|
-
## The delivery
|
|
130
|
-
|
|
131
|
-
A vendor's webhook is not a stranger's ask and does not land as `{}`, and
|
|
132
|
-
it is not an occupant's ask either: it is the owner asking the envoy
|
|
133
|
-
`deliver`, as `papers/quo-dock.md` says of envoys, and the route carries
|
|
134
|
-
the bytes and nothing of the judgement. `POST /api/<ward>/<being>/deliver`
|
|
135
|
-
is its own row and not an ask: the body is read as a string and never
|
|
136
|
-
parsed here, since the vendor signed the bytes and a reparsed body fails
|
|
137
|
-
her check, and the rule that a body is a JSON object does not apply. The
|
|
138
|
-
route asks the ward's owner pointer for that being's `deliver` with `{
|
|
139
|
-
body, headers }`, and answers what she answers: `{ ok }` as 200, her error
|
|
140
|
-
object as 400, silence as 503. No bearer is read on this path, because the
|
|
141
|
-
rung is not the caller's to prove: the signature is hers to check, and
|
|
142
|
-
what fails it is refused by her and never by the route.
|
|
143
|
-
|
|
144
|
-
A `GET` on the same path is the same ask with an empty body, and is where
|
|
145
|
-
an OAuth redirect lands when an envoy begins an account, the code in the
|
|
146
|
-
query. The query crosses inside the headers object under the name
|
|
147
|
-
`:query`, which no header on the wire can carry, so her one ask in stays
|
|
148
|
-
one and an author writes no second door for redirects. What she answers
|
|
149
|
-
crosses as JSON like any delivery.
|
|
150
|
-
|
|
151
|
-
**And a browser gets a page.** This is a road for programs with exactly one
|
|
152
|
-
human arrival, and it is somebody who has just paid for something: a vendor
|
|
153
|
-
returns her by opening this URL in her tab, so without a page she ends a
|
|
154
|
-
payment reading what an envoy says to a machine. A `GET` whose `accept`
|
|
155
|
-
names HTML is answered with the human side's landing page, saying which of
|
|
156
|
-
the two happened and offering the world's own page, since a tab a vendor
|
|
157
|
-
opened has no history to go back through. What the envoy answered is never
|
|
158
|
-
on it. Everything else is unchanged: a vendor asking for JSON is answered
|
|
159
|
-
in JSON, a `GET` that says nothing about what it wants is answered in JSON,
|
|
160
|
-
and the page carries no cross-origin header, since a page is for the
|
|
161
|
-
browser that was sent here and never for a script somewhere else.
|
|
162
|
-
|
|
163
|
-
On its own the route says 404 for a ward not here, for a being not hers
|
|
164
|
-
and for a third segment that is not `deliver`, 413 past a megabyte and 405
|
|
165
|
-
for any method but `POST` and `GET`. A vendor that retries on anything but a quick
|
|
166
|
-
200 lands once, because she keeps the delivery id and answers `{ ok }` for
|
|
167
|
-
one she has seen before she wakes anyone.
|
|
168
|
-
|
|
169
|
-
## What is not here
|
|
170
|
-
|
|
171
|
-
A ward's delivery reaching more than one envoy, and a vendor that cannot
|
|
172
|
-
sign what it sends. Both stand when a first user does.
|
|
173
|
-
|
|
174
|
-
## The proof
|
|
175
|
-
|
|
176
|
-
`packages/dock/test/api.test.ts`, over a real daemon on loopback. A
|
|
177
|
-
stranger: a world with a public board described and asked by anyone, args
|
|
178
|
-
by query and by body, what she wrote kept across a reboot, no ward named
|
|
179
|
-
being the device's own, and every refusal. An occupant: an invitation
|
|
180
|
-
traded for a grant, the describe under the bearer being the avatar's own
|
|
181
|
-
with no ward named and with hers, an ask through her, another world
|
|
182
|
-
refused, refresh rotating, removal at the world's being ending the grant,
|
|
183
|
-
and the identity let in again on a fresh invitation. One identity, two
|
|
184
|
-
grammars: a bearer the OAuth dance issued honoured here and its refresh
|
|
185
|
-
rotating at the trade, a bearer the trade issued opening an MCP session,
|
|
186
|
-
and the tool list being this route's describe with her look and page
|
|
187
|
-
left out. The delivery: signed bytes landing on an envoy's `deliver` as
|
|
188
|
-
the owner, a good signature kept and woken once, a retry landing once, a
|
|
189
|
-
bad one her refusal as 400, no bearer read, what she kept outliving the
|
|
190
|
-
daemon, and the route's own 404, 405 and 413.
|
|
191
|
-
|
|
192
|
-
The same route on a second terrain is
|
|
193
|
-
`packages/dock/test/terrain/edge.test.ts`, behind `npm run deep:dock`: the
|
|
194
|
-
trade at a worker's `/api`, the ask through her avatar, and the answer
|
|
195
|
-
compared against a Node daemon doing the same thing, because a rendering of
|
|
196
|
-
one describe must not differ by terrain.
|