@lapxo/head 0.1.0 → 0.1.3
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/TARGET.bound +392 -49
- package/dist/src/access/contract.d.ts +2 -0
- package/dist/src/access/obtain.js +2 -2
- package/dist/src/adopt/adopt.js +5 -2
- package/dist/src/compile/pin.js +5 -5
- package/dist/src/core/act.d.ts +1 -0
- package/dist/src/core/act.js +1 -0
- package/dist/src/core/answer.d.ts +16 -4
- package/dist/src/core/answer.js +7 -4
- package/dist/src/core/lock.d.ts +2 -0
- package/dist/src/core/lock.js +2 -0
- package/dist/src/core/package.d.ts +54 -0
- package/dist/src/core/package.js +65 -0
- package/dist/src/core/read.d.ts +3 -0
- package/dist/src/core/read.js +8 -4
- package/dist/src/core/surface.d.ts +1 -40
- package/dist/src/core/surface.js +4 -29
- package/dist/src/core/trust.d.ts +7 -0
- package/dist/src/core/trust.js +19 -0
- package/dist/src/flows/device.js +1 -6
- package/dist/src/flows/read.js +18 -8
- package/dist/src/flows/world.js +3 -3
- package/dist/src/index.js +37 -18
- package/dist/src/onboard/first.d.ts +7 -0
- package/dist/src/onboard/first.js +21 -0
- package/dist/src/places/admit.js +1 -1
- package/dist/src/places/found.d.ts +1 -0
- package/dist/src/places/found.js +8 -7
- package/dist/src/ports/access/authorizer.d.ts +10 -0
- package/dist/src/ports/access/authorizer.js +34 -0
- package/dist/src/ports/access/flow.js +13 -3
- package/dist/src/ports/access/person.d.ts +3 -2
- package/dist/src/ports/access/person.js +12 -5
- package/dist/src/ports/adapters/request.d.ts +3 -0
- package/dist/src/ports/adapters/request.js +5 -3
- package/dist/src/ports/bound.d.ts +12 -2
- package/dist/src/ports/bound.js +30 -13
- package/dist/src/ports/documents.d.ts +34 -0
- package/dist/src/ports/documents.js +64 -0
- package/dist/src/ports/keychain.d.ts +2 -1
- package/dist/src/ports/keychain.js +3 -2
- package/dist/src/ports/place.d.ts +1 -0
- package/dist/src/ports/place.js +7 -1
- package/dist/src/ports/process.d.ts +9 -0
- package/dist/src/ports/process.js +5 -3
- package/dist/src/ports/spare.d.ts +1 -0
- package/dist/src/ports/spare.js +33 -0
- package/dist/src/ports/surface.d.ts +126 -0
- package/dist/src/ports/surface.js +48 -0
- package/dist/src/ports/warm.d.ts +19 -0
- package/dist/src/ports/warm.js +46 -0
- package/dist/src/signer.js +1 -1
- package/dist/src/sources/access.d.ts +10 -1
- package/dist/src/sources/access.js +15 -10
- package/dist/src/sources/connect.d.ts +5 -2
- package/dist/src/sources/connect.js +98 -36
- package/dist/src/tools/card.js +1 -1
- package/dist/src/tools/look.d.ts +2 -2
- package/dist/src/tools/look.js +4 -3
- package/dist/src/tools/places.d.ts +7 -0
- package/dist/src/tools/places.js +32 -0
- package/dist/src/tools/share.d.ts +1 -0
- package/dist/src/tools/share.js +1 -0
- package/package.json +6 -5
- /package/dist/src/{core → ports}/keys.d.ts +0 -0
- /package/dist/src/{core → ports}/keys.js +0 -0
package/dist/src/flows/read.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { under } from "../core/lock.js";
|
|
2
|
+
import { observedAt, planRead } from "../core/read.js";
|
|
2
3
|
import { basisOf, draftOf, receiptsOf, scopeOf, spentOf } from "../core/receipt.js";
|
|
3
4
|
import { redIn, sayIn } from "../core/surface.js";
|
|
4
|
-
import { grantedOf, isOpen } from "../core/act.js";
|
|
5
|
+
import { grantedOf, isOpen, untilOf } from "../core/act.js";
|
|
5
6
|
import { canonical } from '@lapxo/topos/wire';
|
|
6
7
|
import { called } from "./call.js";
|
|
7
8
|
import { principalOf } from "../access/obtain.js";
|
|
9
|
+
import { jsonOf } from "../access/contract.js";
|
|
8
10
|
/** An intent an earlier act left open, closed by the read that looked at what it would have changed: a line naming that read-s digest, signed by head. */
|
|
9
11
|
const closeWith = async (intent, source, digest, { word, lines, lands }, context) => {
|
|
10
12
|
const say = sayIn(context.lock);
|
|
@@ -28,7 +30,12 @@ export const readSource = (asked, context) => ((started) => called(asked, contex
|
|
|
28
30
|
const scopes = (principal) => [scopeOf(family, source, operation, prepared.key, principal), `${scopeOf(word('receipt/kept'), source, operation, prepared.key, principal)}/${basisOf(lines, word, source)}`];
|
|
29
31
|
const held = (digest) => ((bytes) => (bytes === undefined || digest === undefined ? undefined : { digest, bytes }))(digest === undefined ? undefined : context.cache.held(digest));
|
|
30
32
|
const keeping = (kept, answer) => (answer.length > 1 ? [draftOf(word('receipt/kept'), at, kept, context.cache.keep(Buffer.from(answer.join('\n'))), when)] : []);
|
|
31
|
-
const allowed = (key, measure) => lines.find((l) => l.scope === `${word(key)}/${source}` && l.measure === measure)?.value;
|
|
33
|
+
const allowed = (key, measure) => (lines.find((l) => l.scope === `${word(key)}/${source}` && l.measure === measure) ?? lines.find((l) => l.scope === word(key) && l.measure === measure))?.value;
|
|
34
|
+
if (asked['why'] === 'true') {
|
|
35
|
+
const asOf = principalOf(context, plan, false), [newest] = asOf === undefined ? [] : receiptsOf(lines, at, scopes(asOf)[0]), pin = lines.find((l) => l.scope === `uses/${source}`);
|
|
36
|
+
const signed = newest && lines.find((l) => l.scope === newest.scope && l.value === newest.digest && l.at === `${at}:${newest.at}`), worlds = lines.filter((l) => l.scope?.startsWith('uses/') && (!l.needs || under(context.lock, 'place/worlds', l.needs))).map((l) => `${l.scope.slice('uses/'.length)} ${l.value}`);
|
|
37
|
+
return newest === undefined ? say('why-none', { source, operation }) : say('why', { source, operation, origin: plan.origin, at: newest.at, by: signed?.by ?? '', epoch: signed?.epoch ?? '', digest: newest.digest, pin: pin?.value ?? '', pinned: pin?.epoch ?? '', worlds: worlds.join(', ') });
|
|
38
|
+
}
|
|
32
39
|
if (asked['history'] === 'true') {
|
|
33
40
|
const asOf = principalOf(context, plan, false);
|
|
34
41
|
if (allowed('history/family', 'status') !== 'present')
|
|
@@ -47,15 +54,15 @@ export const readSource = (asked, context) => ((started) => called(asked, contex
|
|
|
47
54
|
return stopped;
|
|
48
55
|
if (!grantedOf(lines, word('grant/family'), source, context.now())) {
|
|
49
56
|
const sources = [...new Set(lines.filter((l) => l.scope?.startsWith(`${word('vocab/region')}/`) && l.measure === word('vocab/origins')).map((l) => l.scope.split('/')[1]))].sort();
|
|
50
|
-
const until =
|
|
57
|
+
const until = untilOf(word('access/period'), context.now());
|
|
51
58
|
const line = (measure, value) => canonical({ at: 'policy:person/grants', by: 'target', form: 'alphabet', measure, role: 'writes', scope: word('grant/family'), value });
|
|
52
59
|
const granted = await widen(say('grant', { sources: sources.join(', '), place, until })[0], [line('sources', sources.join('|')), line('until', until)]);
|
|
53
60
|
if ('said' in granted)
|
|
54
61
|
return granted.said;
|
|
55
62
|
}
|
|
56
63
|
const reuse = Number(/^\d+\.\.(\d+)$/.exec(allowed('reuse/family', 'seconds') ?? '')?.[1] ?? Number.NaN);
|
|
57
|
-
const [newest] = principal === undefined ? [] : receiptsOf(lines, at, scopes(principal)[0]);
|
|
58
|
-
if (principal !== undefined && newest !== undefined && context.now() - Date.parse(newest.at) <= reuse * 1000) {
|
|
64
|
+
const [newest] = principal === undefined ? [] : receiptsOf(lines, at, scopes(principal)[0]), stood = ((b) => b !== undefined && Number(Buffer.from(b.bytes.subarray(0, b.bytes.indexOf(10))).toString('utf8')) < 300)(held(newest?.digest));
|
|
65
|
+
if (principal !== undefined && newest !== undefined && stood && context.now() - Date.parse(newest.at) <= reuse * 1000) {
|
|
59
66
|
const [scope, kept] = scopes(principal);
|
|
60
67
|
const answered = held(receiptsOf(lines, at, kept).find((k) => k.at >= newest.at)?.digest);
|
|
61
68
|
if (answered !== undefined)
|
|
@@ -80,6 +87,9 @@ export const readSource = (asked, context) => ((started) => called(asked, contex
|
|
|
80
87
|
return say(got.kind === 'over' ? 'too-large' : 'unreachable-source', { source });
|
|
81
88
|
const digest = context.cache.keep(Buffer.concat([Buffer.from(`${got.status}\n`), got.bytes]));
|
|
82
89
|
const answer = await typed(String(got.status), got.bytes);
|
|
83
|
-
const
|
|
84
|
-
|
|
90
|
+
const observed = plan.access.probe && got.status < 300 ? [canonical({ at: `${at}:${when}`, by: 'target', form: 'alphabet', measure: word('vocab/access'), role: 'writes', scope: observedAt(context.lock, source, operation), value: word('vocab/anonymous') })] : [];
|
|
91
|
+
const found = plan.access.probe && got.status === 401 && got.authenticate ? await (await context.access(word('access/adapter')))?.discover?.(plan.origin, got.authenticate, async (url) => jsonOf((await context.files.read.documentOf(context, place, url))?.bytes ?? new Uint8Array())) : undefined;
|
|
92
|
+
const by = `${word('access/family')}/${source}/${word('access/authorizer')}`, authorizer = Object.entries(found ?? {}).flatMap(([k, f]) => Object.entries(f).map(([measure, all]) => canonical({ at: `${at}:${when}`, by: 'target', form: 'alphabet', measure, role: 'writes', scope: k ? `${by}/${k}` : by, value: all.join('|') })));
|
|
93
|
+
const landed = await lands(draftOf(family, at, scope, digest, when), ...(attached.principal === undefined ? [] : keeping(kept, answer)), ...observed, ...authorizer);
|
|
94
|
+
return [...answer, ...('lines' in landed ? [...landed.lines, ...(await closing(digest))] : say('not-landed', { why: landed.refused })), ...(found?.['']?.['issuer'] && 'lines' in landed ? say('authorizer-observed', { source, issuer: found['']['issuer'][0] }) : [])];
|
|
85
95
|
}))(context.machine());
|
package/dist/src/flows/world.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { valueOf } from "../core/lock.js";
|
|
2
|
+
import { under, valueOf } from "../core/lock.js";
|
|
3
3
|
import { sayIn } from "../core/surface.js";
|
|
4
4
|
import { pinnedOf } from "../core/worlds.js";
|
|
5
5
|
import { adoptsIn } from "../adopt/adopt.js";
|
|
@@ -9,14 +9,14 @@ import { adoptsIn } from "../adopt/adopt.js";
|
|
|
9
9
|
* is no standing. A place that pins a lock as its parent stands on nothing head may read: composing the two is bound's, never head's. A place that pins none and names a view has bound compile it, asked once; a fold bound refused is no standing either.
|
|
10
10
|
*/
|
|
11
11
|
export const standingFor = async (context, place) => {
|
|
12
|
-
const [held, view
|
|
12
|
+
const [held, view] = [await context.standing(place), valueOf(context.lock, 'place/view')];
|
|
13
13
|
if ('refused' in held)
|
|
14
14
|
return { unsettled: held.refused };
|
|
15
15
|
const own = held.lines;
|
|
16
16
|
const parent = adoptsIn(context.lock, own);
|
|
17
17
|
if (parent !== undefined)
|
|
18
18
|
return { unsettled: sayIn(context.lock)('parent-lock', { place, parent }) };
|
|
19
|
-
const pins = own.filter((l) => l.scope?.startsWith('uses/') && l.needs
|
|
19
|
+
const pins = own.filter((l) => l.scope?.startsWith('uses/') && under(context.lock, 'place/compiled', l.needs)).map((l) => [l, pinnedOf(context.lock, context.files.take(join(place, l.needs)))]);
|
|
20
20
|
const moved = pins.find(([l, p]) => p?.pin !== l.value);
|
|
21
21
|
if (moved)
|
|
22
22
|
return { unsettled: sayIn(context.lock)('compiled-moved', { file: moved[0].needs }) };
|
package/dist/src/index.js
CHANGED
|
@@ -14,22 +14,28 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
14
14
|
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
15
15
|
import { answerOf } from "./core/answer.js";
|
|
16
16
|
import { valueOf } from "./core/lock.js";
|
|
17
|
-
import { fill,
|
|
18
|
-
import {
|
|
17
|
+
import { fill, sayIn } from "./core/surface.js";
|
|
18
|
+
import { matchTemplate, refusedOn, shapeOf, shapeSchema, surfaceOf } from "./ports/surface.js";
|
|
19
|
+
import { closing, cooled, founded, land, landAct, limits, pinnedAt, policyAt, run, settledView, sign, standingAt, told, warm, words } from "./ports/bound.js";
|
|
20
|
+
import { warmth } from "./ports/warm.js";
|
|
19
21
|
import { held, keep } from "./ports/cache.js";
|
|
20
22
|
import { registryFor } from "./ports/signers.js";
|
|
21
|
-
import { dropAt, folderOf, hasAt, layAt, lockAt, takeAt } from "./ports/place.js";
|
|
23
|
+
import { dropAt, folderOf, hasAt, layAt, listAt, lockAt, takeAt } from "./ports/place.js";
|
|
24
|
+
import * as documents from "./ports/documents.js";
|
|
22
25
|
import { keychain } from "./ports/keychain.js";
|
|
23
26
|
import { algorithmOf, deviceOf, idIn } from "./core/device.js";
|
|
24
|
-
import { publicOf } from "./
|
|
27
|
+
import { publicOf } from "./ports/keys.js";
|
|
25
28
|
import { personOf } from "./ports/access/person.js";
|
|
26
29
|
import { inTurn } from "./flows/call.js";
|
|
27
30
|
const [ext, root] = ((self) => [self.slice(self.lastIndexOf('.')), fileURLToPath(new URL(self.endsWith('.ts') ? '..' : '../..', self))])(import.meta.url);
|
|
28
31
|
const lock = lockAt(root);
|
|
29
32
|
const surface = surfaceOf(lock);
|
|
30
33
|
const runs = valueOf(lock, 'runs');
|
|
31
|
-
Object.assign(words, Object.fromEntries(['read', 'view', 'sign', 'land', 'key', 'signer', 'pin', 'topos', 'standing', 'evidence', 'act', 'profile'].map((w) => [w, valueOf(lock, `bound/${w}`)])));
|
|
34
|
+
Object.assign(words, Object.fromEntries(['read', 'view', 'sign', 'land', 'key', 'signer', 'pin', 'topos', 'standing', 'evidence', 'act', 'profile', 'receipts', 'refusal'].map((w) => [w, valueOf(lock, `bound/${w}`)])));
|
|
32
35
|
Object.assign(limits, { wait: Number(valueOf(lock, 'bound/wait').split('..')[1]), most: Number(valueOf(lock, 'bound/printed').split('..')[1]) });
|
|
36
|
+
Object.assign(warmth, { spares: Number(valueOf(lock, 'bound/spares').split('..')[1]), boot: valueOf(lock, 'bound/spare-env').split('|') });
|
|
37
|
+
/** One call at a time, with bound started ahead for it and nothing of it kept once it is answered. */
|
|
38
|
+
const turn = (act) => inTurn('', () => { void warm(runs); return act().finally(cooled); });
|
|
33
39
|
const offered = (n) => (n ? { listChanged: false } : undefined);
|
|
34
40
|
const server = new Server({ name: surface.name, version: surface.version }, { capabilities: { tools: offered(surface.tools.length), resources: offered(surface.resources.length), prompts: offered(surface.prompts.length) } });
|
|
35
41
|
/** The person, asked as the client lets head ask; every wait on them is counted, so a time ceiling measures head's work and never the person's. */
|
|
@@ -52,26 +58,40 @@ const device = (role) => devices.get(role) ?? ((secret) => (secret === undefined
|
|
|
52
58
|
const asRoot = (account) => ((device, key) => [key, device.signer, { BOUND_SIGNERS: registryFor(device.signer, service, account, key, valueOf(lock, 'founding/algorithm'), store) }])(deviceOf(lock), valueOf(lock, 'founding/root').split('/')[1]);
|
|
53
59
|
/** What a call is answered with, one call at a time as when bound blocked head: every bound process it starts is stopped when the client cancels it, and a call cancelled while it waits its turn is never begun. */
|
|
54
60
|
const contextOf = (signal) => {
|
|
61
|
+
/** One call's folds of a place, kept until something lands in it or is laid there; and the places its root acted in, whose receipts close once, when the call ends. */
|
|
62
|
+
const [folds, touched] = [new Map(), new Set()], forget = (place) => { for (const k of [...folds.keys()])
|
|
63
|
+
if (k.endsWith(`\0${place}`))
|
|
64
|
+
folds.delete(k); };
|
|
65
|
+
const once = (key, place, fold) => (folds.get(`${key}\0${place}`) ?? ((p) => (folds.set(`${key}\0${place}`, p), p))(fold()));
|
|
66
|
+
const acted = (place, root, done) => done.finally(() => { forget(place); if (root)
|
|
67
|
+
touched.add(place); });
|
|
55
68
|
const signRoot = (place, lot, account) => sign(runs, place, lot, ...asRoot(account), signal);
|
|
56
69
|
const signAs = (place, lot, role, lines) => ((one, id) => sign(runs, place, lot, id, one.signer, { BOUND_SIGNERS: registryFor(one.signer, service, one.account, id, algorithmOf(lines), store) }, signal))(deviceOf(lock, role), idIn(lines, device(role)) ?? deviceOf(lock, role).account);
|
|
57
70
|
return {
|
|
58
71
|
lock,
|
|
59
72
|
bound: (cwd, args) => run(runs, cwd, args, {}, signal),
|
|
60
|
-
standing: (place) => standingAt(runs, place, (lines) => ['head', 'person'].flatMap((role) => ((id) => (id === undefined ? [] : [id]))(idIn(lines, device(role)))), (line) => line.at?.startsWith(`${valueOf(lock, 'receipt/at')}:`) === true, signal),
|
|
61
|
-
|
|
73
|
+
standing: (place) => once('standing', place, () => standingAt(runs, place, (lines) => ['head', 'person'].flatMap((role) => ((id) => (id === undefined ? [] : [id]))(idIn(lines, device(role)))), (line) => line.at?.startsWith(`${valueOf(lock, 'receipt/at')}:`) === true, signal)),
|
|
74
|
+
policy: (place) => once('policy', place, () => policyAt(runs, place, signal)),
|
|
75
|
+
world: (place, view) => once(`world/${view}`, place, () => settledView(runs, place, view, signal)),
|
|
62
76
|
adapter: async (origin) => ((name) => (!/^[a-z-]+$/.test(name) ? undefined : import(__rewriteRelativeImportExtension(new URL(`./ports/adapters/${name}${ext}`, import.meta.url).href))))(URL.canParse(origin) ? valueOf(lock, `adapter/${new URL(origin).protocol.slice(0, -1)}`) : ''),
|
|
63
|
-
files: { lay: layAt, drop: dropAt, has: hasAt, take: takeAt, own: (rel) => takeAt(join(root, rel)) },
|
|
77
|
+
files: { lay: (place, rel, bytes) => { forget(place); layAt(place, rel, bytes); }, drop: (place, rel) => { forget(place); dropAt(place, rel); }, has: hasAt, list: listAt, read: documents, take: takeAt, own: (rel) => takeAt(join(root, rel)) },
|
|
64
78
|
home: () => home,
|
|
65
|
-
|
|
79
|
+
personal: () => process.env[valueOf(lock, `platform/${process.platform}/home`)] ?? '',
|
|
80
|
+
found: (place, account) => acted(place, true, founded(runs, place, ...asRoot(account), signal)),
|
|
66
81
|
signRoot,
|
|
67
|
-
landRoot: (place, lot, account) => landAct(runs, place, lot, ...asRoot(account), signal),
|
|
82
|
+
landRoot: (place, lot, account) => acted(place, true, landAct(runs, place, lot, ...asRoot(account), signal)),
|
|
83
|
+
settle: async () => { const red = []; for (const place of touched) {
|
|
84
|
+
const why = await closing(runs, place, signal);
|
|
85
|
+
if (why !== undefined)
|
|
86
|
+
red.push(sayIn(lock)('receipts-open', { place, why }));
|
|
87
|
+
} touched.clear(); return red; },
|
|
68
88
|
pinned: (place, file) => pinnedAt(runs, place, file, signal),
|
|
69
89
|
access: async (kind) => (kind !== valueOf(lock, 'access/adapter') || !/^[a-z0-9-]+$/.test(kind) ? undefined : import(__rewriteRelativeImportExtension(new URL(`./ports/access/${kind}${ext}`, import.meta.url).href)).then((m) => m.adapter)),
|
|
70
90
|
person,
|
|
71
91
|
cache: { keep, held },
|
|
72
92
|
signAs,
|
|
73
|
-
landAs: (place, lot, role, lines) => ((one, id) => landAct(runs, place, lot, id, one.signer, { BOUND_SIGNERS: registryFor(one.signer, service, one.account, id, algorithmOf(lines), store) }, signal))(deviceOf(lock, role), idIn(lines, device(role)) ?? deviceOf(lock, role).account),
|
|
74
|
-
land: (place, signed) => land(runs, place, signed, signal),
|
|
93
|
+
landAs: (place, lot, role, lines) => acted(place, false, ((one, id) => landAct(runs, place, lot, id, one.signer, { BOUND_SIGNERS: registryFor(one.signer, service, one.account, id, algorithmOf(lines), store) }, signal))(deviceOf(lock, role), idIn(lines, device(role)) ?? deviceOf(lock, role).account)),
|
|
94
|
+
land: (place, signed) => acted(place, true, land(runs, place, signed, signal)),
|
|
75
95
|
told: (place, signed) => told(runs, place, signed, words.standing, signal),
|
|
76
96
|
elicit: (message) => person.consent(message, valueOf(lock, 'said/elicit/yes')),
|
|
77
97
|
now: () => Date.now(),
|
|
@@ -86,23 +106,22 @@ const schemaOf = (fields) => ({ type: 'object',
|
|
|
86
106
|
properties: Object.fromEntries(fields.map((f) => [f.name, { type: f.type, description: f.about }])), required: fields.filter((f) => f.required).map((f) => f.name) });
|
|
87
107
|
if (surface.tools.length)
|
|
88
108
|
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: surface.tools.map((tool) => ({ name: tool.name, description: tool.about,
|
|
89
|
-
inputSchema: schemaOf([...tool.fields]), annotations: Object.fromEntries(tool.hints.map((hint) => [`${hint}Hint`, true])) })) }));
|
|
109
|
+
inputSchema: schemaOf([...tool.fields]), outputSchema: shapeSchema(lock), annotations: Object.fromEntries(tool.hints.map((hint) => [`${hint}Hint`, true])) })) }));
|
|
90
110
|
if (surface.tools.length)
|
|
91
|
-
server.setRequestHandler(CallToolRequestSchema, async (request, extra) =>
|
|
111
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => turn(async () => {
|
|
92
112
|
const tool = surface.tools.find((one) => one.name === request.params.name);
|
|
93
113
|
if (tool === undefined)
|
|
94
114
|
return { isError: true, content: [] };
|
|
95
115
|
const refused = refusedOn(lock, process.platform);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return { content: [{ type: 'text', text: extra.signal.aborted ? '' : (await (await answerOf('tools', tool.name))(askedOf(request.params.arguments), contextOf(extra.signal))).join('\n') }] };
|
|
116
|
+
const context = contextOf(extra.signal), answer = refused !== undefined ? [refused] : extra.signal.aborted ? [] : [...await (await answerOf('tools', tool.name))(askedOf(request.params.arguments), context), ...await context.settle()];
|
|
117
|
+
return { content: [{ type: 'text', text: answer.join('\n') }], structuredContent: shapeOf(answer) };
|
|
99
118
|
}));
|
|
100
119
|
if (surface.resources.length)
|
|
101
120
|
server.setRequestHandler(ListResourcesRequestSchema, () => ({ resources: [] }));
|
|
102
121
|
if (surface.resources.length)
|
|
103
122
|
server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({ resourceTemplates: surface.resources.map((r) => ({ name: r.name, uriTemplate: r.template, description: r.about, mimeType: r.mime })) }));
|
|
104
123
|
if (surface.resources.length)
|
|
105
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request, extra) =>
|
|
124
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request, extra) => turn(async () => {
|
|
106
125
|
const found = surface.resources.flatMap((r) => ((vars) => (vars === undefined ? [] : [{ r, vars }]))(matchTemplate(r.template, request.params.uri)))[0];
|
|
107
126
|
if (found === undefined)
|
|
108
127
|
return { contents: [] };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Asked, type Context } from '../core/answer.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The first source a person connects with no place named and none to read from: their place is founded in the folder
|
|
4
|
+
* the lock names under their own home, and the source connected to it, on one yes for both. A folder that already holds
|
|
5
|
+
* a place is used as it stands, never founded again, and its connect asks its own yes.
|
|
6
|
+
*/
|
|
7
|
+
export declare const firstRun: (asked: Asked, context: Context, then: (place: string, covered: boolean) => Promise<readonly string[]>) => Promise<readonly string[]>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { basename, join } from 'node:path';
|
|
2
|
+
import { LOCK } from '@lapxo/topos/wire';
|
|
3
|
+
import { unless } from "../core/answer.js";
|
|
4
|
+
import { valueOf } from "../core/lock.js";
|
|
5
|
+
import { sayIn } from "../core/surface.js";
|
|
6
|
+
import { bornAt } from "../places/found.js";
|
|
7
|
+
/**
|
|
8
|
+
* The first source a person connects with no place named and none to read from: their place is founded in the folder
|
|
9
|
+
* the lock names under their own home, and the source connected to it, on one yes for both. A folder that already holds
|
|
10
|
+
* a place is used as it stands, never founded again, and its connect asks its own yes.
|
|
11
|
+
*/
|
|
12
|
+
export const firstRun = async (asked, context, then) => {
|
|
13
|
+
const say = sayIn(context.lock), dir = join(context.personal(), valueOf(context.lock, 'first-run/folder')), name = basename(dir);
|
|
14
|
+
if (context.files.has(dir, LOCK))
|
|
15
|
+
return then(dir, false);
|
|
16
|
+
const refused = await unless(context, say('elicit-first-run', { dir, source: asked['source'] ?? '', document: asked['document'] ?? '' }));
|
|
17
|
+
if (refused)
|
|
18
|
+
return refused;
|
|
19
|
+
const born = await bornAt(context, dir, name);
|
|
20
|
+
return born[0] === say('born', { name, dir }) ? [...born, ...(await then(dir, true))] : born;
|
|
21
|
+
};
|
package/dist/src/places/admit.js
CHANGED
|
@@ -15,7 +15,7 @@ export const admitted = (asked, context) => inPlace(asked, context, async (place
|
|
|
15
15
|
const key = (asked['key'] ?? '').split('\n').map((line) => parse(line.trim())).flatMap((got) => (got.kind === 'fact' && got.value.fields['measure'] === 'public-key' ? [got.value.fields['value'] ?? ''] : []))[0];
|
|
16
16
|
if (!name || !key)
|
|
17
17
|
return [say('no-key')];
|
|
18
|
-
const [root, held] = [rootIn(context, place), await standingOr(context, place)];
|
|
18
|
+
const [root, held] = [rootIn(context, place), await standingOr(context, place, context.policy)];
|
|
19
19
|
if ('said' in root)
|
|
20
20
|
return root.said;
|
|
21
21
|
if ('said' in held)
|
|
@@ -4,3 +4,4 @@ import type { Asked, Context } from '../core/answer.ts';
|
|
|
4
4
|
* and named in head's own index of places — itself a place, founded the first time — by a line its root signs.
|
|
5
5
|
*/
|
|
6
6
|
export declare const bornPlace: (asked: Asked, context: Context) => Promise<readonly string[]>;
|
|
7
|
+
export declare const bornAt: (context: Context, dir: string, name: string) => Promise<readonly string[]>;
|
package/dist/src/places/found.js
CHANGED
|
@@ -2,7 +2,6 @@ import { isAbsolute, basename } from 'node:path';
|
|
|
2
2
|
import { canonical, LOCK } from '@lapxo/topos/wire';
|
|
3
3
|
import { unless } from "../core/answer.js";
|
|
4
4
|
import { admissionOf } from "../core/device.js";
|
|
5
|
-
import { newKey } from "../core/keys.js";
|
|
6
5
|
import { valueOf } from "../core/lock.js";
|
|
7
6
|
import { deviceKey } from "../flows/device.js";
|
|
8
7
|
import { walkable } from "../walk/context.js";
|
|
@@ -25,9 +24,10 @@ const founded = async (context, dir, name) => {
|
|
|
25
24
|
return { refused: 'no-keychain' };
|
|
26
25
|
if (context.keys.read(account) !== undefined)
|
|
27
26
|
return { refused: 'place-taken' };
|
|
28
|
-
const root =
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const root = context.keys.made?.(account);
|
|
28
|
+
if (root === undefined)
|
|
29
|
+
return { refused: 'no-keychain' };
|
|
30
|
+
context.files.lay(dir, LOCK, new TextEncoder().encode(`${birthOf(context.lock, root, head, person, name).join('\n')}\n`));
|
|
31
31
|
const born = await context.found(dir, account);
|
|
32
32
|
return ((got) => ('refused' in got ? { refused: 'not-landed', why: got.refused } : got))('refused' in born ? born : await walkable(context, dir, account));
|
|
33
33
|
};
|
|
@@ -44,9 +44,10 @@ export const bornPlace = async (asked, context) => {
|
|
|
44
44
|
if (context.files.has(dir, LOCK))
|
|
45
45
|
return [say('place-exists', { dir })];
|
|
46
46
|
const refused = await unless(context, say('elicit-place', { name, dir }));
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
return refused ?? bornAt(context, dir, name);
|
|
48
|
+
};
|
|
49
|
+
export const bornAt = async (context, dir, name) => {
|
|
50
|
+
const say = sayIn(context.lock), born = await founded(context, dir, name);
|
|
50
51
|
if ('refused' in born)
|
|
51
52
|
return [say(born.refused, { name, dir, why: born.why ?? '' })];
|
|
52
53
|
const home = context.home();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Fields } from '../../access/contract.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The authorizer a resource names when it refuses a request: its protected resource metadata (RFC 9728) at the address
|
|
4
|
+
* its challenge gives, which must name the very origin that refused; the authorization server's metadata (RFC 8414),
|
|
5
|
+
* which must name the issuer the resource named and take S256; and from them the oauth2 authorization-code flow a
|
|
6
|
+
* document would have declared — the same steps, PKCE through head's page, a client registered (RFC 7591) only where
|
|
7
|
+
* the server offers registration and the person's own where it does not, the token and its refresh, renewed at the
|
|
8
|
+
* token endpoint. Every one of these reads goes with nothing attached; what does not hold is no authorizer.
|
|
9
|
+
*/
|
|
10
|
+
export declare const discovered: (resource: string, challenge: string, get: (url: string) => Promise<unknown>) => Promise<Readonly<Record<string, Fields>> | undefined>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const text = (json, key) => ((v) => (typeof v === 'string' && v ? v : undefined))(json?.[key]);
|
|
2
|
+
const texts = (json, key) => ((v) => (Array.isArray(v) ? v.filter((x) => typeof x === 'string') : []))(json?.[key]);
|
|
3
|
+
const fields = (pairs) => Object.fromEntries(Object.entries(pairs).map(([k, v]) => [k, [v].flat()]));
|
|
4
|
+
/**
|
|
5
|
+
* The authorizer a resource names when it refuses a request: its protected resource metadata (RFC 9728) at the address
|
|
6
|
+
* its challenge gives, which must name the very origin that refused; the authorization server's metadata (RFC 8414),
|
|
7
|
+
* which must name the issuer the resource named and take S256; and from them the oauth2 authorization-code flow a
|
|
8
|
+
* document would have declared — the same steps, PKCE through head's page, a client registered (RFC 7591) only where
|
|
9
|
+
* the server offers registration and the person's own where it does not, the token and its refresh, renewed at the
|
|
10
|
+
* token endpoint. Every one of these reads goes with nothing attached; what does not hold is no authorizer.
|
|
11
|
+
*/
|
|
12
|
+
export const discovered = async (resource, challenge, get) => {
|
|
13
|
+
const at = /resource_metadata="([^"]+)"/.exec(challenge)?.[1];
|
|
14
|
+
if (at === undefined || !URL.canParse(at, resource))
|
|
15
|
+
return undefined;
|
|
16
|
+
const named = await get(new URL(at, resource).href), [issuer] = texts(named, 'authorization_servers'), own = text(named, 'resource');
|
|
17
|
+
if (own === undefined || !URL.canParse(own) || new URL(own).origin !== new URL(resource).origin || issuer === undefined || !URL.canParse(issuer))
|
|
18
|
+
return undefined;
|
|
19
|
+
const server = await get(`${new URL(issuer).origin}/.well-known/oauth-authorization-server${new URL(issuer).pathname.replace(/\/$/, '')}`);
|
|
20
|
+
const [authorize, token, register, methods] = [text(server, 'authorization_endpoint'), text(server, 'token_endpoint'), text(server, 'registration_endpoint'), texts(server, 'code_challenge_methods_supported')];
|
|
21
|
+
if (text(server, 'issuer') !== issuer || authorize === undefined || token === undefined || (methods.length > 0 && !methods.includes('S256')))
|
|
22
|
+
return undefined;
|
|
23
|
+
const [client, scopes] = [register ? '$steps.register.outputs.client' : '$client', texts(named, 'scopes_supported').join(' ')];
|
|
24
|
+
const sent = (step, pairs) => Object.fromEntries(Object.entries(pairs).filter(([, v]) => v).map(([k, v]) => [`${step}/payload/${k}`, fields({ value: v })]));
|
|
25
|
+
return {
|
|
26
|
+
'': fields({ type: 'oauth2/authorizationCode', issuer, 'attach-in': 'header', 'attach-name': 'Authorization', 'attach-template': 'Bearer {token}', token: '$steps.exchange.outputs.token', expires: '$steps.exchange.outputs.expires', refresh: '$steps.exchange.outputs.refresh', renew: token }),
|
|
27
|
+
...(register ? { 'steps/0': fields({ id: 'register', url: register, encoding: 'json' }), 'steps/0/payload/redirect_uris': fields({ items: '$callback' }), 'steps/0/payload/grant_types': fields({ items: ['authorization_code', 'refresh_token'] }),
|
|
28
|
+
'steps/0/payload/response_types': fields({ items: 'code' }), ...sent('steps/0', { client_name: 'head', token_endpoint_auth_method: "'none'" }), 'steps/0/outputs/client': fields({ value: '$response.body#/client_id' }) } : {}),
|
|
29
|
+
'steps/1': fields({ id: 'authorize', page: authorize }), 'steps/1/outputs/code': fields({ value: '$response.body#/code' }),
|
|
30
|
+
...sent('steps/1', { client_id: client, redirect_uri: '$callback', response_type: 'code', scope: scopes, state: '$state', code_challenge: 's256($verifier)', code_challenge_method: 'S256', resource: new URL(resource).origin }),
|
|
31
|
+
'steps/2': fields({ id: 'exchange', url: token, encoding: 'form' }), ...sent('steps/2', { grant_type: 'authorization_code', code: '$steps.authorize.outputs.code', redirect_uri: '$callback', client_id: client, code_verifier: '$verifier', resource: new URL(resource).origin }),
|
|
32
|
+
'steps/2/outputs/token': fields({ value: '$response.body#/access_token' }), 'steps/2/outputs/expires': fields({ value: '$response.body#/expires_in' }), 'steps/2/outputs/refresh': fields({ value: '$response.body#/refresh_token' }),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { jsonOf } from "../../access/contract.js";
|
|
3
|
+
import { discovered } from "./authorizer.js";
|
|
2
4
|
const one = (f, measure) => f?.[measure]?.[0];
|
|
3
5
|
const pointed = (json, pointer) => pointer.split('/').slice(1).map((s) => s.replaceAll('~1', '/').replaceAll('~0', '~'))
|
|
4
6
|
.reduce((at, step) => at.flatMap((v) => (v === null || typeof v !== 'object' ? [] : step === '*' ? Object.values(v) : [v[step]])), [json])
|
|
@@ -18,9 +20,16 @@ const originOf = (url) => (URL.canParse(url) ? new URL(url).origin : '');
|
|
|
18
20
|
* the token is kept, and it rides a request as the flow's template says.
|
|
19
21
|
*/
|
|
20
22
|
export const adapter = {
|
|
23
|
+
discover: discovered,
|
|
21
24
|
attach: (secret, params) => ((into, name, value) => (into === 'header' ? { headers: { [name]: value }, query: {} } : into === 'query' ? { headers: {}, query: { [name]: value } } : { headers: {}, query: {} }))(one(params, 'attach-in'), one(params, 'attach-name') ?? '', (one(params, 'attach-template') ?? '{token}').split('{token}').join(secret)),
|
|
22
25
|
obtain: async (want) => {
|
|
23
|
-
const [d,
|
|
26
|
+
const [d, renewed] = [want.declared, ((r) => (r && typeof r === 'object' ? r : undefined))(jsonOf(want.renew ?? ''))];
|
|
27
|
+
if (renewed?.refresh && one(d[''], 'renew')) {
|
|
28
|
+
const got = await want.post(one(d[''], 'renew'), { grant_type: 'refresh_token', refresh_token: renewed.refresh, ...(renewed.client ? { client_id: renewed.client } : {}) }, 'form'), [secret, lasts, refresh] = ['/access_token', '/expires_in', '/refresh_token'].map((at) => pointed(got?.json, at)[0]);
|
|
29
|
+
if (got !== undefined && got.status < 300 && secret)
|
|
30
|
+
return { secret, ...(Number.isFinite(Number(lasts)) && lasts ? { expires: want.now + Number(lasts) * 1000 } : {}), renew: JSON.stringify({ ...renewed, refresh: refresh ?? renewed.refresh }) };
|
|
31
|
+
}
|
|
32
|
+
const [gen, inputs, outputs] = [{ state: randomBytes(24).toString('base64url'), verifier: randomBytes(32).toString('base64url') }, new Map(), new Map()];
|
|
24
33
|
let [callback, secrets, refused] = ['', 0, undefined];
|
|
25
34
|
const stop = (why) => { refused ??= why; return undefined; };
|
|
26
35
|
const ask = async (name) => {
|
|
@@ -110,11 +119,12 @@ export const adapter = {
|
|
|
110
119
|
for (const k of page >= 0 ? steps.slice(page + 1) : steps)
|
|
111
120
|
if (refused || !(await run(k)))
|
|
112
121
|
break;
|
|
113
|
-
const [token, expires] =
|
|
122
|
+
const [token, expires, refresh] = await Promise.all(['token', 'expires', 'refresh'].map(async (k) => (await Promise.all((d['']?.[k] ?? []).map((e) => resolve(e)))).find((v) => !empty(v))));
|
|
114
123
|
if (refused)
|
|
115
124
|
return refused;
|
|
116
125
|
if (typeof token !== 'string' || !token)
|
|
117
126
|
return { refused: 'login-refused' };
|
|
118
|
-
return { secret: token, ...(typeof expires === 'string' && Number.isFinite(Number(expires)) ? { expires: want.now + Number(expires) * 1000 } : {}), ...(steps.length ? {} : { manual: true })
|
|
127
|
+
return { secret: token, ...(typeof expires === 'string' && Number.isFinite(Number(expires)) ? { expires: want.now + Number(expires) * 1000 } : {}), ...(steps.length ? {} : { manual: true }),
|
|
128
|
+
...(typeof refresh === 'string' && refresh ? { renew: JSON.stringify({ refresh, client: outputs.get('register')?.get('client') ?? one(d[''], 'client') }) } : {}) };
|
|
119
129
|
},
|
|
120
130
|
};
|
|
@@ -14,8 +14,9 @@ export interface Client {
|
|
|
14
14
|
/**
|
|
15
15
|
* The person at this device, asked through the richest channel the client declares, in the order head's lock lines name:
|
|
16
16
|
* the client's own form; head's one-time page, sent by the client as an address; the same page opened by this device's
|
|
17
|
-
* browser. A secret goes only through the page — never a form the model could see — and a yes
|
|
18
|
-
*
|
|
17
|
+
* browser. A secret goes only through the page — never a form the model could see — and a yes is the person's answer on
|
|
18
|
+
* any of them, never the model's: where none reaches the person, nothing was asked and the lines are theirs to sign.
|
|
19
|
+
* A visit to a provider comes back to such a page.
|
|
19
20
|
*/
|
|
20
21
|
export declare const personOf: (lock: readonly Said[], client: Client) => {
|
|
21
22
|
consent: (message: string, word: string) => Promise<"yes" | "no" | "none">;
|
|
@@ -6,8 +6,9 @@ import { open } from "./page.js";
|
|
|
6
6
|
/**
|
|
7
7
|
* The person at this device, asked through the richest channel the client declares, in the order head's lock lines name:
|
|
8
8
|
* the client's own form; head's one-time page, sent by the client as an address; the same page opened by this device's
|
|
9
|
-
* browser. A secret goes only through the page — never a form the model could see — and a yes
|
|
10
|
-
*
|
|
9
|
+
* browser. A secret goes only through the page — never a form the model could see — and a yes is the person's answer on
|
|
10
|
+
* any of them, never the model's: where none reaches the person, nothing was asked and the lines are theirs to sign.
|
|
11
|
+
* A visit to a provider comes back to such a page.
|
|
11
12
|
*/
|
|
12
13
|
export const personOf = (lock, client) => {
|
|
13
14
|
const wait = Number(valueOf(lock, 'access/wait').split('..')[1]);
|
|
@@ -18,9 +19,15 @@ export const personOf = (lock, client) => {
|
|
|
18
19
|
const paged = async (title, fields, how) => offered(fill(aboutOf(lock, fields.some((f) => f.secret) ? 'said/enter' : 'said/answer'), { title }), await open(title, fields, wait), how);
|
|
19
20
|
const form = async (message, properties) => ((got) => (got.action === 'accept' ? Object.fromEntries(Object.entries(got.content ?? {}).map(([k, v]) => [k, String(v)])) : undefined))(await client.elicit({ message, requestedSchema: { type: 'object', properties, required: Object.keys(properties) } }));
|
|
20
21
|
return {
|
|
21
|
-
consent: async (message, word) =>
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
consent: async (message, word) => {
|
|
23
|
+
const how = channel('ask/consent-channels'), said = (got) => (got?.['yes'] === 'true' ? 'yes' : 'no');
|
|
24
|
+
if (how === undefined)
|
|
25
|
+
return 'none';
|
|
26
|
+
if (how === 'form')
|
|
27
|
+
return said(await form(message, { yes: { type: 'boolean', title: word } }));
|
|
28
|
+
const page = await open(message, [{ name: 'yes', label: word, secret: false, options: [{ value: 'true', label: word }, { value: 'false', label: 'No' }] }], wait);
|
|
29
|
+
return (await offer(fill(aboutOf(lock, 'said/answer'), { title: message }), page.url, how)) ? said(await page.held) : (page.close(), 'none');
|
|
30
|
+
},
|
|
24
31
|
enter: async (title, fields) => paged(title, fields, channel('ask/secret-channels')),
|
|
25
32
|
choose: async (message, choices) => ((how) => (how === 'form'
|
|
26
33
|
? form(message, Object.fromEntries(choices.map((c) => [c.name, { type: 'string', title: c.label ?? c.name, ...(c.options ? { oneOf: c.options.map((o) => ({ const: o.value, title: o.label })) } : {}) }])))
|
|
@@ -6,6 +6,8 @@ export type Fetched = {
|
|
|
6
6
|
readonly status: number;
|
|
7
7
|
readonly bytes: Uint8Array;
|
|
8
8
|
readonly type: string;
|
|
9
|
+
readonly location?: string;
|
|
10
|
+
readonly authenticate?: string;
|
|
9
11
|
} | {
|
|
10
12
|
readonly kind: 'over';
|
|
11
13
|
} | {
|
|
@@ -21,6 +23,7 @@ export type Prepared = {
|
|
|
21
23
|
readonly refused: string;
|
|
22
24
|
};
|
|
23
25
|
export declare const published: (url: string, digest: string, most: number, wait: number) => Promise<Uint8Array | undefined>;
|
|
26
|
+
export declare const fetched: (url: string, attachment: Attachment | undefined, accept: string | undefined, most: number, wait: number) => Promise<Fetched>;
|
|
24
27
|
export declare const get: (url: string, wait: number) => Promise<{
|
|
25
28
|
readonly bytes: Uint8Array;
|
|
26
29
|
readonly type: string;
|
|
@@ -17,16 +17,18 @@ const requestCapped = async (method, url, headers, body, most, wait, redirect =
|
|
|
17
17
|
break;
|
|
18
18
|
chunks.push(chunk);
|
|
19
19
|
}
|
|
20
|
-
return size > most ? { kind: 'over' } : { kind: 'got', status: response.status, bytes: Buffer.concat(chunks), type: response.headers.get('content-type') ?? '' };
|
|
20
|
+
return size > most ? { kind: 'over' } : { kind: 'got', status: response.status, bytes: Buffer.concat(chunks), type: response.headers.get('content-type') ?? '', ...((at) => (at ? { location: at } : {}))(response.headers.get('location')), ...((asks) => (asks ? { authenticate: asks } : {}))(response.headers.get('www-authenticate')) };
|
|
21
21
|
}
|
|
22
22
|
catch {
|
|
23
23
|
return { kind: 'failed' };
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
const loopback = new Set(['127.0.0.1', 'localhost', '[::1]']);
|
|
27
|
-
export const published = async (url, digest, most, wait) => (URL.canParse(url) &&
|
|
27
|
+
export const published = async (url, digest, most, wait) => (URL.canParse(url) && ((at) => at.protocol === 'https:' || loopback.has(at.hostname))(new URL(url))
|
|
28
28
|
? ((got) => (got.kind === 'got' && got.status < 300 && `sha256:${createHash('sha256').update(got.bytes).digest('hex')}` === digest ? got.bytes : undefined))(await requestCapped('GET', url, {}, undefined, most, wait, 'follow')) : undefined);
|
|
29
|
-
export const
|
|
29
|
+
export const fetched = async (url, attachment, accept, most, wait) => (!URL.canParse(url) || !((at) => at.protocol === 'https:' || loopback.has(at.hostname))(new URL(url)) ? { kind: 'failed' }
|
|
30
|
+
: requestCapped('GET', ((extra) => (extra.size ? `${url}${url.includes('?') ? '&' : '?'}${extra}` : url))(new URLSearchParams(Object.entries(attachment?.query ?? {}))), { ...(attachment?.headers ?? {}), ...(accept ? { accept } : {}) }, undefined, most, wait));
|
|
31
|
+
export const get = async (url, wait) => (URL.canParse(url) && ((at) => at.protocol === 'https:' || loopback.has(at.hostname))(new URL(url)) ? ((got) => (got.kind === 'got' && got.status < 300 ? got : undefined))(await requestCapped('GET', url, {}, undefined, 16_777_216, wait)) : undefined);
|
|
30
32
|
/** A body posted to an address a declaration names — as a form, or as JSON where the declaration's protocol asks it — as one request under the same rules: encrypted unless it is this device, capped, never redirected. */
|
|
31
33
|
export const post = async (url, body, most, wait, as = 'form') => {
|
|
32
34
|
if (!URL.canParse(url) || !((at) => at.protocol === 'https:' || loopback.has(at.hostname))(new URL(url)))
|
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
import type { Said } from '../core/lock.ts';
|
|
2
2
|
import type { Outcome } from '../core/answer.ts';
|
|
3
|
+
import { cooled } from './warm.ts';
|
|
3
4
|
import type { Printed } from './process.ts';
|
|
4
5
|
export type { Printed };
|
|
5
6
|
export declare const limits: {
|
|
6
7
|
wait: number;
|
|
7
8
|
most: number;
|
|
8
9
|
};
|
|
9
|
-
/** bound run as a process in a folder, beside head and never blocking it, within the wait and the bytes head's lock gives it and stopped when the asker cancels. */
|
|
10
|
+
/** bound run as a process in a folder, beside head and never blocking it, within the wait and the bytes head's lock gives it and stopped when the asker cancels: one started ahead when head holds one, else one started now. */
|
|
10
11
|
export declare const run: (name: string, cwd: string, args: readonly string[], env?: Readonly<Record<string, string>>, signal?: AbortSignal) => Promise<Printed | undefined>;
|
|
12
|
+
/** bound started ahead of the first ask of a call, as many times as head's lock keeps it started; resolved once each is ready. */
|
|
13
|
+
export declare const warm: (name: string) => Promise<unknown>;
|
|
14
|
+
export { cooled };
|
|
11
15
|
export type Folded = {
|
|
12
16
|
readonly lines: readonly Said[];
|
|
13
17
|
} | {
|
|
14
18
|
readonly refused: string;
|
|
15
19
|
};
|
|
16
20
|
/**
|
|
17
|
-
* The lines a place stands on, from bound's public views alone: its folded normative standing (the lines view)
|
|
21
|
+
* The lines a place stands on, from bound's public views alone: its folded normative standing (the lines view) — its
|
|
22
|
+
* policy, all that a reader of its pins, keys and views needs, read alone where nothing else is — and from
|
|
18
23
|
* its authenticated evidence what this device's own keys signed. Receipts are history, each kept as it was made; any other
|
|
19
24
|
* line of a device key, the person's own attestation, stands only while it is the one line of its scope and measure,
|
|
20
25
|
* unwithdrawn and beside no line of the place's own: anything else needs a fold bound does not publish, and is refused by
|
|
21
26
|
* name. A place whose land prints no act result is refused before anything lands in it. Any refusal of bound is no standing.
|
|
22
27
|
*/
|
|
28
|
+
export declare const policyAt: (name: string, place: string, signal?: AbortSignal) => Promise<Folded>;
|
|
23
29
|
export declare const standingAt: (name: string, place: string, devicesOf: (lines: readonly Said[]) => readonly string[], isHistory: (line: Said) => boolean, signal?: AbortSignal) => Promise<Folded>;
|
|
24
30
|
export declare const sign: (name: string, place: string, lot: readonly string[], key: string, signer: string, env: Readonly<Record<string, string>>, signal?: AbortSignal) => Promise<Outcome>;
|
|
25
31
|
/** A lot signed and landed in one process, its result read from what bound printed of the committed act: the records it admitted, never the receipt beside them; a place that declares no act result is refused by name. */
|
|
32
|
+
/** A place's receipts rendered beside its lock, so bound alone checks the place: nothing when they closed, else why they did not. */
|
|
33
|
+
export declare const closing: (name: string, place: string, signal?: AbortSignal) => Promise<string | undefined>;
|
|
26
34
|
export declare const landAct: (name: string, place: string, lot: readonly string[], key: string, signer: string, env: Readonly<Record<string, string>>, signal?: AbortSignal) => Promise<Outcome>;
|
|
27
35
|
export declare const land: (name: string, place: string, signed: readonly string[], signal?: AbortSignal) => Promise<Outcome>;
|
|
28
36
|
export declare const told: (name: string, place: string, signed: readonly string[], view: string, signal?: AbortSignal) => Promise<readonly string[]>;
|
|
@@ -47,5 +55,7 @@ export declare const words: {
|
|
|
47
55
|
evidence: string;
|
|
48
56
|
act: string;
|
|
49
57
|
profile: string;
|
|
58
|
+
receipts: string;
|
|
59
|
+
refusal: string;
|
|
50
60
|
};
|
|
51
61
|
export declare const settledView: (name: string, place: string, view: string, signal?: AbortSignal) => Promise<Folded>;
|