@lapxo/head 0.1.6 → 0.1.7
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 +1 -1
- package/TARGET.bound +205 -112
- package/dist/src/access/contract.d.ts +0 -2
- package/dist/src/access/obtain.js +3 -3
- package/dist/src/ahead/budget.d.ts +4 -0
- package/dist/src/ahead/budget.js +8 -0
- package/dist/src/claims/claim.d.ts +1 -0
- package/dist/src/claims/claim.js +15 -15
- package/dist/src/claims/view.d.ts +3 -0
- package/dist/src/claims/view.js +11 -0
- package/dist/src/compile/pin.d.ts +7 -0
- package/dist/src/compile/pin.js +6 -7
- package/dist/src/core/answer.d.ts +1 -0
- package/dist/src/core/answer.js +1 -0
- package/dist/src/core/device.d.ts +2 -2
- package/dist/src/core/device.js +5 -5
- package/dist/src/core/lock.d.ts +1 -0
- package/dist/src/core/lock.js +2 -1
- package/dist/src/core/read.js +1 -2
- package/dist/src/core/worlds.js +2 -3
- package/dist/src/doors/guard.d.ts +18 -0
- package/dist/src/doors/guard.js +63 -0
- package/dist/src/doors/head.d.ts +60 -0
- package/dist/src/doors/head.js +136 -0
- package/dist/src/doors/http.d.ts +3 -0
- package/dist/src/doors/http.js +34 -0
- package/dist/src/doors/lines.d.ts +3 -0
- package/dist/src/doors/lines.js +21 -0
- package/dist/src/flows/device.js +4 -6
- package/dist/src/flows/plan.js +7 -10
- package/dist/src/flows/read.js +4 -1
- package/dist/src/index.js +17 -116
- package/dist/src/links/inspect.js +7 -9
- package/dist/src/places/found.js +1 -1
- package/dist/src/ports/bound.d.ts +9 -10
- package/dist/src/ports/bound.js +25 -20
- package/dist/src/ports/host.d.ts +35 -0
- package/dist/src/ports/host.js +108 -0
- package/dist/src/ports/keychain.d.ts +14 -2
- package/dist/src/ports/keychain.js +19 -7
- package/dist/src/ports/scratch.d.ts +4 -0
- package/dist/src/ports/scratch.js +10 -0
- package/dist/src/signer.js +8 -7
- package/dist/src/tools/card.js +3 -6
- package/dist/src/verbs/ask.js +2 -1
- package/dist/src/walk/context.js +3 -6
- package/package.json +3 -3
- package/dist/src/ports/signers.d.ts +0 -6
- package/dist/src/ports/signers.js +0 -14
- package/dist/src/ports/spare.d.ts +0 -1
- package/dist/src/ports/spare.js +0 -33
- package/dist/src/ports/warm.d.ts +0 -19
- package/dist/src/ports/warm.js +0 -45
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { valueOf } from "../core/lock.js";
|
|
3
|
+
import { sayIn } from "../core/surface.js";
|
|
4
|
+
import { shapeOf } from "../ports/surface.js";
|
|
5
|
+
import { plainOf } from "./head.js";
|
|
6
|
+
/** The http door, on this device's loopback alone: one POST a verb, its body the args, its answer the cells every door gives; a Host not its own, or any Origin, is refused by name. It ends when its input does. */
|
|
7
|
+
export const open = (lock, root, ext, name) => new Promise((done) => {
|
|
8
|
+
const [loopback, asked, say] = [valueOf(lock, 'doors/loopback'), process.argv[4] ?? '0', sayIn(lock)];
|
|
9
|
+
if (!/^\d+$/.test(asked)) {
|
|
10
|
+
process.stdout.write(`${JSON.stringify(shapeOf([say('door-bind', { asked, loopback })]))}\n`);
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
done();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const door = plainOf(lock, root, ext, name);
|
|
16
|
+
const server = createServer((request, out) => {
|
|
17
|
+
const [own, answer] = [`${loopback}:${server.address().port}`, (status, lines) => out.writeHead(status, { 'content-type': 'application/json' }).end(JSON.stringify(shapeOf(lines)))];
|
|
18
|
+
if (request.headers.origin !== undefined || request.headers.host !== own)
|
|
19
|
+
return void answer(403, [say(request.headers.origin !== undefined ? 'door-origin' : 'door-host', { origin: request.headers.origin ?? '', host: request.headers.host ?? '', own })]);
|
|
20
|
+
let body = '';
|
|
21
|
+
request.on('data', (chunk) => { body += chunk; }).on('end', async () => {
|
|
22
|
+
const args = ((t) => { try {
|
|
23
|
+
return JSON.parse(t || '{}');
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
} })(body);
|
|
28
|
+
const lines = request.method === 'POST' && args !== null && typeof args === 'object' && !Array.isArray(args) ? await door.asked((request.url ?? '').slice(1), args) : undefined;
|
|
29
|
+
answer(lines === undefined ? 400 : 200, lines ?? [say('door-unasked', { verbs: door.verbs.join(', ') })]);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
server.listen(Number(asked), loopback, () => process.stdout.write(`${JSON.stringify({ port: server.address().port })}\n`));
|
|
33
|
+
process.stdin.on('end', () => server.close(() => void door.settle().then(door.head.idle).then(() => done()))).resume();
|
|
34
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { sayIn } from "../core/surface.js";
|
|
3
|
+
import { shapeOf } from "../ports/surface.js";
|
|
4
|
+
import { plainOf } from "./head.js";
|
|
5
|
+
/** The lines door: one JSON request a line in, its answer one JSON line out, the cells as every door gives them. */
|
|
6
|
+
export const open = (lock, root, ext, name) => new Promise((done) => {
|
|
7
|
+
const [door, say] = [plainOf(lock, root, ext, name), sayIn(lock)];
|
|
8
|
+
const answered = (id, answer) => process.stdout.write(`${JSON.stringify({ id, ...shapeOf(answer ?? [say('door-unread', { verbs: door.verbs.join(', ') })]) })}\n`);
|
|
9
|
+
let pending = Promise.resolve();
|
|
10
|
+
createInterface({ input: process.stdin }).on('line', (text) => {
|
|
11
|
+
pending = pending.then(async () => {
|
|
12
|
+
const asked = ((t) => { try {
|
|
13
|
+
return JSON.parse(t);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return {};
|
|
17
|
+
} })(text);
|
|
18
|
+
answered(asked.id, await door.asked(asked.verb ?? '', asked.args));
|
|
19
|
+
});
|
|
20
|
+
}).on('close', () => void pending.then(door.settle).then(door.head.idle).then(() => done()));
|
|
21
|
+
});
|
package/dist/src/flows/device.js
CHANGED
|
@@ -7,19 +7,17 @@ import { adoptsIn } from "../adopt/adopt.js";
|
|
|
7
7
|
/** A key of this device by its role: read from the keychain, or made there once; only its public half is ever handed on. */
|
|
8
8
|
export const deviceKey = (context, role) => context.keys.made?.(deviceOf(context.lock, role).account);
|
|
9
9
|
/** The keychain account of a place's root, when this keychain holds it — who configures the place is here; else what head says of that. */
|
|
10
|
-
export const rootIn = (context, place) => ((account) => (context.keys.
|
|
10
|
+
export const rootIn = (context, place) => ((account) => (context.keys.held?.(account) === undefined ? { said: [sayIn(context.lock)('no-root', { place })] } : { account }))(fill(valueOf(context.lock, 'founding/root-account'), { place: placeNameOf(place) }));
|
|
11
11
|
export const widening = (context, place, lines, lot) => {
|
|
12
12
|
const parent = adoptsIn(context.lock, lines);
|
|
13
13
|
if (parent !== undefined)
|
|
14
14
|
return { said: [sayIn(context.lock)('parent-lock', { place, parent })] };
|
|
15
15
|
const [root, witness, field] = [rootIn(context, place), idIn(lines, context.device('person')), valueOf(context.lock, 'widening/witness')];
|
|
16
|
-
const wire = lines.filter((l) => l.scope === 'wire/fields');
|
|
16
|
+
const wire = lines.filter((l) => l.scope === 'wire/fields'), admits = wire.some((l) => members(l.value ?? '').includes(field));
|
|
17
17
|
const witnessed = (signed) => signed.map((line) => ((got) => (got.kind === 'fact' ? canonical({ ...got.value.fields, [field]: witness }) : line))(parse(line)));
|
|
18
18
|
if ('account' in root && witness !== undefined) {
|
|
19
|
-
if (
|
|
20
|
-
return { sign: (signed) => context.signRoot(place, witnessed(signed), root.account), land: (signed) => context.landRoot(place, witnessed(signed), root.account) };
|
|
21
|
-
if (lot.every((line) => / scope=wire\/fields /.test(line)))
|
|
22
|
-
return { sign: (signed) => context.signRoot(place, signed, root.account), land: (signed) => context.landRoot(place, signed, root.account) };
|
|
19
|
+
if (admits || lot.every((line) => / scope=wire\/fields /.test(line)))
|
|
20
|
+
return { sign: (signed) => context.signRoot(place, admits ? witnessed(signed) : signed, root.account), land: (signed) => context.landRoot(place, admits ? witnessed(signed) : signed, root.account) };
|
|
23
21
|
const [named, bare] = [[...new Set([...wire.flatMap((l) => members(l.value ?? '')), field])].join('|'), (l) => Object.fromEntries(Object.entries(l).filter(([k]) => !['sig', 'epoch'].includes(k)))];
|
|
24
22
|
return { said: [sayIn(context.lock)('unwitnessed', { place }), ...wire.map((l) => canonical({ ...bare(l), by: 'target', value: 'withdraw' })),
|
|
25
23
|
canonical({ at: `policy:head/wire/${new Date(context.now()).toISOString()}`, by: 'target', form: 'alphabet', measure: 'id', role: 'reads', scope: 'wire/fields', value: named })] };
|
package/dist/src/flows/plan.js
CHANGED
|
@@ -2,6 +2,7 @@ import { baseOf, inPlace, standingOr } from "../core/answer.js";
|
|
|
2
2
|
import { sayIn } from "../core/surface.js";
|
|
3
3
|
import { widening } from "./device.js";
|
|
4
4
|
import { parse } from '@lapxo/topos/wire';
|
|
5
|
+
const moved = async (context, place, base) => ((now) => ('said' in now ? now.said : baseOf(now.lines) !== base ? [sayIn(context.lock)('base-moved', { base })] : undefined))(await standingOr(context, place));
|
|
5
6
|
/**
|
|
6
7
|
* A proposal the person signs: drafted lines shown to the person with the place and the base they are made against; on
|
|
7
8
|
* their yes to exactly those, signed by their key; what bound prints folding it as if landed comes back as it printed it. Nothing lands; the lot is kept by its digest.
|
|
@@ -26,11 +27,9 @@ export const planLot = (asked, context) => inPlace(asked, context, async (place)
|
|
|
26
27
|
const signed = await by.sign(lot);
|
|
27
28
|
if ('refused' in signed)
|
|
28
29
|
return [signed.refused];
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
-
return
|
|
32
|
-
if (baseOf(now.lines) !== base)
|
|
33
|
-
return [say('base-moved', { base })];
|
|
30
|
+
const stale = await moved(context, place, base);
|
|
31
|
+
if (stale)
|
|
32
|
+
return stale;
|
|
34
33
|
const kept = context.cache.keep(Buffer.from(JSON.stringify({ place, base, signed: signed.lines })));
|
|
35
34
|
return [say('planned', { lot: kept, base }), ...(await context.told(place, signed.lines))];
|
|
36
35
|
});
|
|
@@ -41,11 +40,9 @@ export const landLot = async (asked, context) => {
|
|
|
41
40
|
if (bytes === undefined)
|
|
42
41
|
return [say('no-lot', { lot: asked['lot'] ?? '' })];
|
|
43
42
|
const kept = JSON.parse(Buffer.from(bytes).toString('utf8'));
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
46
|
-
return
|
|
47
|
-
if (baseOf(now.lines) !== kept.base)
|
|
48
|
-
return [say('base-moved', { base: kept.base })];
|
|
43
|
+
const stale = await moved(context, kept.place, kept.base);
|
|
44
|
+
if (stale)
|
|
45
|
+
return stale;
|
|
49
46
|
const landed = await context.land(kept.place, kept.signed);
|
|
50
47
|
return 'refused' in landed ? [landed.refused] : [say('landed', { count: String(landed.lines.length) }), ...landed.lines];
|
|
51
48
|
};
|
package/dist/src/flows/read.js
CHANGED
|
@@ -9,7 +9,7 @@ import { principalOf } from "../access/obtain.js";
|
|
|
9
9
|
import { jsonOf } from "../access/contract.js";
|
|
10
10
|
import { refusedAccess } from "../sources/refused.js";
|
|
11
11
|
import { hand, keptOf } from "../kept/kept.js";
|
|
12
|
-
import { markOf, overOf } from "../ahead/budget.js";
|
|
12
|
+
import { calledIn, markOf, overOf } from "../ahead/budget.js";
|
|
13
13
|
/** 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. */
|
|
14
14
|
const closeWith = async (intent, source, digest, { word, lines, lands }, context) => {
|
|
15
15
|
const say = sayIn(context.lock);
|
|
@@ -72,6 +72,9 @@ export const readSource = (asked, context) => called(asked, context, planRead, a
|
|
|
72
72
|
const again = was?.kind === 'again' ? say(`kept-${was.why}`, { source, operation, at: was.at }) : [];
|
|
73
73
|
if (overOf(lines, [family, at, word('ahead/mark')], source, context.now(), most('read/rate'), Number(word('ahead/share').split('..')[1]), asked))
|
|
74
74
|
return asked['ahead'] === 'true' ? [] : say('rate', { source });
|
|
75
|
+
const calls = lines.find((l) => l.scope === `${word('calls/family')}/${source}`), made = calls && calledIn(lines, [family, at, word('ahead/mark')], source, calls.measure ?? '', context.now());
|
|
76
|
+
if (calls && (made === undefined || made >= most(calls.scope)))
|
|
77
|
+
return asked['ahead'] === 'true' ? [] : say(made === undefined ? 'calls-unwindowed' : 'calls-owed', { source, calls: String(most(calls.scope)), window: calls.measure });
|
|
75
78
|
const attached = await attach();
|
|
76
79
|
if ('said' in attached)
|
|
77
80
|
return attached.said;
|
package/dist/src/index.js
CHANGED
|
@@ -7,144 +7,45 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
7
7
|
}
|
|
8
8
|
return path;
|
|
9
9
|
};
|
|
10
|
-
import { basename, join } from 'node:path';
|
|
11
10
|
import { fileURLToPath } from 'node:url';
|
|
12
11
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
13
12
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
13
|
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { birth, closing, cooled, founded, land, landAct, limits, pinnedAt, policyAt, run, settledView, sign, standingAt, told, warm, words } from "./ports/bound.js";
|
|
21
|
-
import { warmth } from "./ports/warm.js";
|
|
22
|
-
import { held, keep } from "./ports/cache.js";
|
|
23
|
-
import { registryFor } from "./ports/signers.js";
|
|
24
|
-
import { dropAt, folderOf, hasAt, layAt, listAt, lockAt, takeAt } from "./ports/place.js";
|
|
25
|
-
import * as documents from "./ports/documents.js";
|
|
26
|
-
import { keychain } from "./ports/keychain.js";
|
|
27
|
-
import { algorithmOf, deviceOf, idIn } from "./core/device.js";
|
|
28
|
-
import { publicOf } from "./ports/keys.js";
|
|
29
|
-
import { personOf } from "./ports/access/person.js";
|
|
30
|
-
import { readAhead } from "./ahead/read.js";
|
|
14
|
+
import { aboutOf, members, valueOf } from "./core/lock.js";
|
|
15
|
+
import { fill } from "./core/surface.js";
|
|
16
|
+
import { shapeOf, shapeSchema } from "./ports/surface.js";
|
|
17
|
+
import { folderOf, lockAt } from "./ports/place.js";
|
|
18
|
+
import { askedOf, headOf } from "./doors/head.js";
|
|
31
19
|
const [ext, root] = ((self) => [self.slice(self.lastIndexOf('.')), fileURLToPath(new URL(self.endsWith('.ts') ? '..' : '../..', self))])(import.meta.url);
|
|
32
20
|
const lock = lockAt(root);
|
|
33
|
-
|
|
21
|
+
const [door, doors] = [process.argv[3], members(valueOf(lock, 'cli/run/doors'))];
|
|
22
|
+
if (process.argv[2] !== valueOf(lock, 'cli/run') || (door !== undefined && !doors.includes(door))) {
|
|
34
23
|
process.stdout.write(`${aboutOf(lock, 'cli/help')}\n`);
|
|
35
24
|
process.exit(0);
|
|
36
25
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
Object.assign(birth, { at: valueOf(lock, 'founding/person-at'), file: valueOf(lock, 'founding/birth-file') });
|
|
42
|
-
Object.assign(warmth, { spares: Number(valueOf(lock, 'bound/spares').split('..')[1]), boot: valueOf(lock, 'bound/spare-env').split('|') });
|
|
43
|
-
/** One call at a time, each after the one before it and never on a timer, so one asked while another lands sees its receipt; bound started ahead for it, and nothing of it kept once it is answered. */
|
|
44
|
-
let [last, ahead] = [Promise.resolve(), new AbortController()];
|
|
45
|
-
const turn = (act) => ((next) => { last = next.catch(() => undefined); return next; })(last.then(() => { void warm(runs); return act().finally(cooled); }));
|
|
46
|
-
for (const end of ['SIGTERM', 'SIGINT'])
|
|
47
|
-
process.once(end, () => void last.finally(() => process.exit(0)));
|
|
26
|
+
if (door !== undefined && door !== doors[0]) {
|
|
27
|
+
await (await import(__rewriteRelativeImportExtension(new URL(`./doors/${door}${ext}`, import.meta.url).href))).open(lock, root, ext, door);
|
|
28
|
+
process.exit();
|
|
29
|
+
}
|
|
48
30
|
const offered = (n) => (n ? { listChanged: false } : undefined);
|
|
31
|
+
const head = headOf(lock, root, ext, {
|
|
32
|
+
can: () => ((e) => ({ url: Boolean(e?.url), form: e !== undefined && (e.form !== undefined || Object.keys(e).length === 0) }))(server.getClientCapabilities()?.elicitation), elicit: (params) => server.elicitInput(params, { timeout: Number(valueOf(lock, 'access/wait').split('..')[1]) }),
|
|
33
|
+
roots: async () => (server.getClientCapabilities()?.roots ? (await server.listRoots()).roots.flatMap((root) => folderOf(root.uri) ?? []) : []), client: () => ((c) => (c ? `${c.name}@${c.version}` : undefined))(server.getClientVersion()), name: doors[0],
|
|
34
|
+
}), surface = head.surface;
|
|
49
35
|
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) }, instructions: aboutOf(lock, 'prose/en/instructions') });
|
|
50
|
-
/** 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. */
|
|
51
|
-
let waited = 0;
|
|
52
|
-
const timed = (ask) => async (...args) => { const t = Date.now(); try {
|
|
53
|
-
return await ask(...args);
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
waited += Date.now() - t;
|
|
57
|
-
} };
|
|
58
|
-
const asked = personOf(lock, { can: () => ((e) => ({ url: Boolean(e?.url), form: e !== undefined && (e.form !== undefined || Object.keys(e).length === 0) }))(server.getClientCapabilities()?.elicitation), elicit: (params) => server.elicitInput(params, { timeout: Number(valueOf(lock, 'access/wait').split('..')[1]) }) });
|
|
59
|
-
const person = { consent: timed(asked.consent), enter: timed(asked.enter), choose: timed(asked.choose), visit: timed(asked.visit) };
|
|
60
|
-
/** head is its home: the folder of its index of places, and the keychain service its keys live under, one per home, so two heads on one keychain never share a key. A place's root is told to bound by the key id its lock names, its secret kept there under the place's account. */
|
|
61
|
-
const home = process.env[valueOf(lock, 'home/env')] || join(process.env[valueOf(lock, `platform/${process.platform}/home`)] ?? '', valueOf(lock, 'home/folder'));
|
|
62
|
-
const service = `${deviceOf(lock).service}/${basename(home).replace(/[^\w.-]+/g, '-')}`;
|
|
63
|
-
const store = valueOf(lock, `platform/${process.platform}/store`);
|
|
64
|
-
const keys = keychain(service, store);
|
|
65
|
-
const devices = new Map();
|
|
66
|
-
const device = (role) => devices.get(role) ?? ((secret) => (secret === undefined ? undefined : ((key) => { devices.set(role, key); return key; })(publicOf(secret))))(keys.read(deviceOf(lock, role).account));
|
|
67
|
-
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]);
|
|
68
|
-
/** 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. */
|
|
69
|
-
const contextOf = (signal) => {
|
|
70
|
-
/** One call's folds of a place, kept until something lands in it or is laid there; the places its root acted in, whose receipts close once, when the call ends; and those where anything but a read's receipts landed, as bound printed it. */
|
|
71
|
-
const [folds, touched, moved] = [new Map(), new Set(), new Set()], forget = (place) => { for (const k of [...folds.keys()])
|
|
72
|
-
if (k.endsWith(`\0${place}`))
|
|
73
|
-
folds.delete(k); };
|
|
74
|
-
const read = (at) => at?.startsWith(`${valueOf(lock, 'receipt/at')}:`) === true;
|
|
75
|
-
const once = (key, place, fold) => (folds.get(`${key}\0${place}`) ?? ((p) => (folds.set(`${key}\0${place}`, p), p))(fold()));
|
|
76
|
-
const acted = (place, root, done) => done.then((got) => { if ('lines' in got && !got.lines.every((l) => ((f) => f.kind === 'fact' && read(f.value.fields['at']))(parse(l))))
|
|
77
|
-
moved.add(place); return got; }).finally(() => { forget(place); if (root)
|
|
78
|
-
touched.add(place); });
|
|
79
|
-
const signRoot = (place, lot, account) => sign(runs, place, lot, ...asRoot(account), signal);
|
|
80
|
-
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);
|
|
81
|
-
return {
|
|
82
|
-
lock,
|
|
83
|
-
bound: (cwd, args) => run(runs, cwd, args, {}, signal),
|
|
84
|
-
standing: (place) => once('standing', place, () => standingAt(runs, place, (lines) => ['head', 'person'].flatMap((role) => ((id) => (id === undefined ? [] : [id]))(idIn(lines, device(role)))), (line) => read(line.at), signal)),
|
|
85
|
-
policy: (place) => once('policy', place, () => policyAt(runs, place, signal)),
|
|
86
|
-
world: (place, view) => once(`world/${view}`, place, () => settledView(runs, place, view, signal)),
|
|
87
|
-
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)}`) : ''),
|
|
88
|
-
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)) },
|
|
89
|
-
home: () => home,
|
|
90
|
-
personal: () => process.env[valueOf(lock, `platform/${process.platform}/home`)] ?? '',
|
|
91
|
-
found: (place, account) => acted(place, true, founded(runs, place, ...asRoot(account), signal)),
|
|
92
|
-
signRoot,
|
|
93
|
-
landRoot: (place, lot, account) => acted(place, true, landAct(runs, place, lot, ...asRoot(account), signal)),
|
|
94
|
-
moved: (place) => moved.has(place),
|
|
95
|
-
settle: async () => { const red = []; for (const place of touched) {
|
|
96
|
-
const why = await closing(runs, place, signal);
|
|
97
|
-
if (why !== undefined)
|
|
98
|
-
red.push(sayIn(lock)('receipts-open', { place, why }));
|
|
99
|
-
} touched.clear(); return red; },
|
|
100
|
-
pinned: (place, file) => pinnedAt(runs, place, file, signal),
|
|
101
|
-
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)),
|
|
102
|
-
person,
|
|
103
|
-
cache: { keep, held },
|
|
104
|
-
signAs,
|
|
105
|
-
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)),
|
|
106
|
-
land: (place, signed) => acted(place, true, land(runs, place, signed, signal)),
|
|
107
|
-
told: (place, signed) => told(runs, place, signed, words.standing, signal),
|
|
108
|
-
elicit: (message) => person.consent(message, aboutOf(lock, 'said/elicit/yes')),
|
|
109
|
-
now: () => Date.now(),
|
|
110
|
-
machine: () => Date.now() - waited,
|
|
111
|
-
begun: Date.now() - waited,
|
|
112
|
-
keys,
|
|
113
|
-
device,
|
|
114
|
-
roots: async () => (server.getClientCapabilities()?.roots ? (await server.listRoots()).roots.flatMap((root) => folderOf(root.uri) ?? []) : []), client: () => ((c) => (c ? `${c.name}@${c.version}` : undefined))(server.getClientVersion()),
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
const askedOf = (raw) => Object.fromEntries(Object.entries(raw ?? {}).map(([k, v]) => [k, v === undefined ? undefined : typeof v === 'object' ? JSON.stringify(v) : String(v)]));
|
|
118
36
|
const schemaOf = (fields) => ({ type: 'object',
|
|
119
37
|
properties: Object.fromEntries(fields.map((f) => [f.name, { type: f.type, description: f.about }])), required: fields.filter((f) => f.required).map((f) => f.name) });
|
|
120
38
|
if (surface.tools.length)
|
|
121
39
|
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: surface.tools.map((tool) => ({ name: tool.name, description: tool.about,
|
|
122
40
|
inputSchema: schemaOf([...tool.fields]), outputSchema: shapeSchema(lock), annotations: Object.fromEntries(tool.hints.map((hint) => [`${hint}Hint`, true])) })) }));
|
|
123
41
|
if (surface.tools.length)
|
|
124
|
-
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => (
|
|
125
|
-
const tool = surface.tools.find((one) => one.name === request.params.name);
|
|
126
|
-
if (tool === undefined)
|
|
127
|
-
return { isError: true, content: [] };
|
|
128
|
-
const refused = refusedOn(lock, process.platform);
|
|
129
|
-
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()];
|
|
130
|
-
void ((early) => turn(() => readAhead(shapeOf(answer).cells, contextOf(), early.signal)))(ahead = new AbortController());
|
|
131
|
-
return { content: [{ type: 'text', text: answer.join('\n') }], structuredContent: shapeOf(answer) };
|
|
132
|
-
})));
|
|
42
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => ((answer) => (answer === undefined ? { isError: true, content: [] } : { content: [{ type: 'text', text: answer.join('\n') }], structuredContent: shapeOf(answer) }))(await head.call(request.params.name, request.params.arguments, extra.signal)));
|
|
133
43
|
if (surface.resources.length)
|
|
134
44
|
server.setRequestHandler(ListResourcesRequestSchema, () => ({ resources: [] }));
|
|
135
45
|
if (surface.resources.length)
|
|
136
46
|
server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({ resourceTemplates: surface.resources.map((r) => ({ name: r.name, uriTemplate: r.template, description: r.about, mimeType: r.mime })) }));
|
|
137
47
|
if (surface.resources.length)
|
|
138
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request, extra) =>
|
|
139
|
-
const found = surface.resources.flatMap((r) => ((vars) => (vars === undefined ? [] : [{ r, vars }]))(matchTemplate(r.template, request.params.uri)))[0];
|
|
140
|
-
if (found === undefined)
|
|
141
|
-
return { contents: [] };
|
|
142
|
-
const refused = refusedOn(lock, process.platform);
|
|
143
|
-
if (refused !== undefined)
|
|
144
|
-
return { contents: [{ uri: request.params.uri, mimeType: found.r.mime, text: refused }] };
|
|
145
|
-
const lines = await (await answerOf('resources', found.r.name))(askedOf(found.vars), contextOf(extra.signal));
|
|
146
|
-
return { contents: [{ uri: request.params.uri, mimeType: found.r.mime, text: lines.join('\n') }] };
|
|
147
|
-
}));
|
|
48
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request, extra) => ((got) => ({ contents: got === undefined ? [] : [{ uri: request.params.uri, mimeType: got.mime, text: got.lines.join('\n') }] }))(await head.resource(request.params.uri, extra.signal)));
|
|
148
49
|
if (surface.prompts.length)
|
|
149
50
|
server.setRequestHandler(ListPromptsRequestSchema, () => ({ prompts: surface.prompts.map((p) => ({ name: p.name, description: p.about, arguments: p.fields.map((f) => ({ name: f.name, description: f.about, required: f.required })) })) }));
|
|
150
51
|
if (surface.prompts.length)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { decodeReference } from '@lapxo/bound/reference';
|
|
2
2
|
import { canonical } from '@lapxo/topos/wire';
|
|
3
|
-
import {
|
|
3
|
+
import { inStanding } from "../core/answer.js";
|
|
4
4
|
import { members, valueOf } from "../core/lock.js";
|
|
5
5
|
import { sayIn } from "../core/surface.js";
|
|
6
6
|
import { linesOf } from "../core/worlds.js";
|
|
@@ -12,12 +12,10 @@ import { rootIn } from "../flows/device.js";
|
|
|
12
12
|
* one, to sign; bytes that do not arrive are owed by the link until they do; bytes that are not what it names are refused.
|
|
13
13
|
* Asked why, it shows the transport it reads by: the profile's lines, laid at the place's birth by head, never the person's.
|
|
14
14
|
*/
|
|
15
|
-
export const linked = (asked, context) =>
|
|
16
|
-
const [say, word, link] = [sayIn(context.lock), (k) => valueOf(context.lock, k), asked['document'] ?? ''],
|
|
17
|
-
if ('said' in held)
|
|
18
|
-
return held.said;
|
|
15
|
+
export const linked = (asked, context) => inStanding(asked, context, async (place, standing) => {
|
|
16
|
+
const [say, word, link] = [sayIn(context.lock), (k) => valueOf(context.lock, k), asked['document'] ?? ''], admits = members(valueOf(context.lock, 'link/transport'));
|
|
19
17
|
if (asked['why'] === 'true')
|
|
20
|
-
return [...
|
|
18
|
+
return [...standing.filter((l) => admits.includes(l.scope ?? '') || l.scope?.startsWith('transport/')).map((l) => canonical(l)), say('why-transport', { place })];
|
|
21
19
|
const lines = context.lock.filter((l) => admits.some((s) => l.scope === `birth/${s}`)).map(({ about: _about, scope, ...l }) => canonical({ ...l, scope: scope.slice('birth/'.length), at: 'policy:person/transport', by: 'target' }));
|
|
22
20
|
let reference;
|
|
23
21
|
try {
|
|
@@ -26,10 +24,10 @@ export const linked = (asked, context) => inPlace(asked, context, async (place)
|
|
|
26
24
|
catch (refused) {
|
|
27
25
|
return [say('link-refused', { link, why: refused instanceof Error ? refused.message : String(refused) })];
|
|
28
26
|
}
|
|
29
|
-
if (![admits[0], `transport/${new URL(reference.source).protocol.slice(0, -1)}`].every((s) =>
|
|
27
|
+
if (![admits[0], `transport/${new URL(reference.source).protocol.slice(0, -1)}`].every((s) => standing.some((l) => l.scope === s)))
|
|
30
28
|
return [say('transport-missing', { place, lines: lines.join('\n') })];
|
|
31
29
|
const [printed, scope] = [await context.bound(place, [word('bound/read'), link]), `${word('place/needs')}/bytes/${reference.blob.slice(reference.algorithm.length + 1, reference.algorithm.length + 17)}`];
|
|
32
|
-
const [owed, root, why] = [
|
|
30
|
+
const [owed, root, why] = [standing.find((l) => l.scope === scope && l.role === 'demands' && l.value === link), rootIn(context, place), printed?.err.filter((l) => !l.startsWith('{')).at(-1) ?? ''];
|
|
33
31
|
if ('said' in root)
|
|
34
32
|
return root.said;
|
|
35
33
|
if (printed?.code !== 0 && !why.includes(word('link/unarrived')))
|
|
@@ -41,4 +39,4 @@ export const linked = (asked, context) => inPlace(asked, context, async (place)
|
|
|
41
39
|
const named = linesOf(new TextEncoder().encode(printed.out.filter((l) => l.startsWith('TOPOS ')).map((l) => l.slice('TOPOS '.length)).join('\n')));
|
|
42
40
|
const sources = named.filter((l) => /^sources\/[^/]+$/.test(l.scope ?? '')).map((l) => l.scope.split('/')[1]);
|
|
43
41
|
return [say('inspected', { source: reference.source, sources: sources.join(', ') || '—', digest: reference.digest }), say('sharer-unverified', {}), ...await settled(owed ? [canonical({ ...due, value: 'withdraw' })] : [])];
|
|
44
|
-
});
|
|
42
|
+
}, context.policy);
|
package/dist/src/places/found.js
CHANGED
|
@@ -18,7 +18,7 @@ const founded = async (context, dir, name) => {
|
|
|
18
18
|
const [head, person] = [deviceKey(context, 'head'), deviceKey(context, 'person')];
|
|
19
19
|
if (head === undefined || person === undefined)
|
|
20
20
|
return { refused: 'no-keychain' };
|
|
21
|
-
if (context.keys.
|
|
21
|
+
if (context.keys.held?.(account) !== undefined)
|
|
22
22
|
return { refused: 'place-taken' };
|
|
23
23
|
const root = context.keys.made?.(account);
|
|
24
24
|
if (root === undefined)
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Said } from '../core/lock.ts';
|
|
2
2
|
import type { Outcome } from '../core/answer.ts';
|
|
3
|
-
import {
|
|
3
|
+
import type { portOf } from './keychain.ts';
|
|
4
4
|
import type { Printed } from './process.ts';
|
|
5
5
|
export type { Printed };
|
|
6
6
|
export declare const limits: {
|
|
7
7
|
wait: number;
|
|
8
8
|
most: number;
|
|
9
9
|
};
|
|
10
|
-
/** bound
|
|
10
|
+
/** bound asked 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: a place or one of its views read through the place's fold host, else a process started now, as for a document, a lot told or a sign: a host is always handed its place first, and bound folds a document only when it is named first; what it read from the temporary folder is removed once it ends, however it ends. */
|
|
11
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 };
|
|
15
12
|
export type Folded = {
|
|
16
13
|
readonly lines: readonly Said[];
|
|
17
14
|
} | {
|
|
@@ -20,7 +17,9 @@ export type Folded = {
|
|
|
20
17
|
/** Read normative standing plus authenticated device evidence; ambiguous device history refuses. */
|
|
21
18
|
export declare const policyAt: (name: string, place: string, signal?: AbortSignal) => Promise<Folded>;
|
|
22
19
|
export declare const standingAt: (name: string, place: string, devicesOf: (lines: readonly Said[]) => readonly string[], isHistory: (line: Said) => boolean, signal?: AbortSignal) => Promise<Folded>;
|
|
23
|
-
|
|
20
|
+
/** A sign or a land: in a host of its own for this one call, signed by head's signer through the port of the key it names, the lot handed in as the host's input. */
|
|
21
|
+
type Port = ReturnType<typeof portOf>;
|
|
22
|
+
export declare const sign: (_name: string, place: string, lot: readonly string[], key: string, signer: string, port: Port, signal?: AbortSignal) => Promise<Outcome>;
|
|
24
23
|
/** 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. */
|
|
25
24
|
/** A place's receipts rendered beside its lock, so bound alone checks the place: nothing when they closed, else why they did not. */
|
|
26
25
|
export declare const closing: (name: string, place: string, signal?: AbortSignal) => Promise<string | undefined>;
|
|
@@ -29,8 +28,8 @@ export declare const birth: {
|
|
|
29
28
|
at: string;
|
|
30
29
|
file: string;
|
|
31
30
|
}, birthed: (place: string, lot: readonly string[]) => void;
|
|
32
|
-
export declare const landAct: (
|
|
33
|
-
export declare const land: (
|
|
31
|
+
export declare const landAct: (_name: string, place: string, lot: readonly string[], key: string, signer: string, port: Port, signal?: AbortSignal) => Promise<Outcome>;
|
|
32
|
+
export declare const land: (_name: string, place: string, signed: readonly string[], signal?: AbortSignal) => Promise<Outcome>;
|
|
34
33
|
export declare const told: (name: string, place: string, signed: readonly string[], view: string, signal?: AbortSignal) => Promise<readonly string[]>;
|
|
35
34
|
export declare const pinnedAt: (name: string, place: string, file: string, signal?: AbortSignal) => Promise<{
|
|
36
35
|
readonly pin: string;
|
|
@@ -39,7 +38,7 @@ export declare const pinnedAt: (name: string, place: string, file: string, signa
|
|
|
39
38
|
} | {
|
|
40
39
|
readonly refused: string;
|
|
41
40
|
} | undefined>;
|
|
42
|
-
export declare const founded: (
|
|
41
|
+
export declare const founded: (_name: string, place: string, key: string, signer: string, port: Port, signal?: AbortSignal) => Promise<Outcome>;
|
|
43
42
|
export declare const words: {
|
|
44
43
|
read: string;
|
|
45
44
|
view: string;
|
package/dist/src/ports/bound.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
3
|
import { dirname, join } from 'node:path';
|
|
5
4
|
import { canonical, LOCK, parse } from '@lapxo/topos/wire';
|
|
5
|
+
import { factsOf } from "../core/lock.js";
|
|
6
6
|
import { spawned } from "./process.js";
|
|
7
|
-
import {
|
|
7
|
+
import { hosted, hosting, signedIn } from "./host.js";
|
|
8
|
+
import { cleared, scratchOf } from "./scratch.js";
|
|
8
9
|
import { folded } from "../ahead/fold.js";
|
|
9
10
|
import { readingOf } from "./reading.js";
|
|
10
11
|
const binOf = (name) => {
|
|
@@ -18,9 +19,16 @@ const binOf = (name) => {
|
|
|
18
19
|
}
|
|
19
20
|
};
|
|
20
21
|
export const limits = { wait: 0, most: 0 };
|
|
21
|
-
/** bound
|
|
22
|
-
export const run = async (name, cwd, args, env = {}, signal) =>
|
|
23
|
-
|
|
22
|
+
/** bound asked 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: a place or one of its views read through the place's fold host, else a process started now, as for a document, a lot told or a sign: a host is always handed its place first, and bound folds a document only when it is named first; what it read from the temporary folder is removed once it ends, however it ends. */
|
|
23
|
+
export const run = async (name, cwd, args, env = {}, signal) => {
|
|
24
|
+
try {
|
|
25
|
+
return await ((bin, at, bounds) => (bin === undefined ? undefined
|
|
26
|
+
: (args[0] === words.read && (args.length === 1 || (args.length === 3 && args[1] === words.view)) && hosting.installed && !Object.keys(env).length ? hosted(cwd, args, signal) : spawned(process.execPath, [bin, ...args], at, bounds)).then(apart)))(binOf(name), { cwd, env: { ...process.env, ...env } }, { wait: limits.wait, most: limits.most || Number.MAX_SAFE_INTEGER, ...(signal ? { signal } : {}) });
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
cleared([...args, ...Object.values(env)]);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
24
32
|
/** The record bound prints after a refusal's diagnostic, read apart from it: what bound printed is kept whole, the record beside it, and why bound refused is the diagnostic's last line, never the record. */
|
|
25
33
|
const recordOf = (line) => { try {
|
|
26
34
|
return ((got) => (got?.profile === words.refusal ? got : undefined))(JSON.parse(line));
|
|
@@ -31,18 +39,14 @@ catch {
|
|
|
31
39
|
const isRecord = (line) => line.startsWith('{') && recordOf(line) !== undefined;
|
|
32
40
|
const apart = (printed) => ((record) => (record === undefined ? printed : { ...printed, refusal: recordOf(record) }))([...printed.err].reverse().find(isRecord));
|
|
33
41
|
const why = (printed) => printed?.err.filter((line) => !isRecord(line)).at(-1);
|
|
34
|
-
|
|
35
|
-
export const warm = (name) => ((bin) => (bin === undefined ? Promise.resolve() : kept(bin)))(binOf(name));
|
|
36
|
-
export { cooled };
|
|
37
|
-
const facts = (out) => out.flatMap((line) => ((got) => (got.kind === 'fact' ? [got.value.fields] : []))(parse(line)));
|
|
38
|
-
const readOf = (printed) => (printed === undefined || printed.code !== 0 ? { refused: why(printed) ?? '' } : { lines: facts(printed.out) });
|
|
42
|
+
const readOf = (printed) => (printed === undefined || printed.code !== 0 ? { refused: why(printed) ?? '' } : { lines: factsOf(printed.out) });
|
|
39
43
|
/** Native identities and expected-identity results come from the same public CLI contract. Without it, read fresh. */
|
|
40
44
|
const nativeView = (name, place, view, signal) => {
|
|
41
45
|
const read = async (expected) => {
|
|
42
46
|
if (expected === undefined)
|
|
43
47
|
return readOf(await run(name, place, [words.read, words.view, view], {}, signal));
|
|
44
48
|
const result = readingOf(await run(name, place, [words.read, words.view, `${words.identity}@${view}@${expected}`], {}, signal), view, expected);
|
|
45
|
-
return 'refused' in result ? result : { lines:
|
|
49
|
+
return 'refused' in result ? result : { lines: factsOf(result.text.split('\n')) };
|
|
46
50
|
};
|
|
47
51
|
return !words.identity ? read() : folded(`${name}/${view}`, place, async () => { const result = readingOf(await run(name, place, [words.read, words.view, `${words.identity}@${view}`], {}, signal), view); return 'refused' in result ? undefined : result.identity; }, read);
|
|
48
52
|
};
|
|
@@ -61,25 +65,26 @@ export const standingAt = async (name, place, devicesOf, isHistory, signal) => {
|
|
|
61
65
|
return unfolded === undefined ? { lines: [...folded.lines, ...own] }
|
|
62
66
|
: { refused: `REFUSE·standing ${unfolded.by} attests ${key(unfolded)} more than once, withdrawn, or beside the place's own line: folding a device key's lines is bound's, and bound publishes no view of it` };
|
|
63
67
|
};
|
|
64
|
-
const lotFile = (lines) => (
|
|
65
|
-
|
|
68
|
+
const lotFile = (lines) => scratchOf('head-lot-', 'lot.bound', `${lines.join('\n')}\n`);
|
|
69
|
+
const through = (place, verb, lot, key, signer, port, signal) => signedIn(place, [verb, ...(key ? [words.key, key, words.signer, signer] : [])], lot, new Map(key && port ? [[key, { name: signer, sign: port }]] : []), key, signal).then(apart);
|
|
70
|
+
export const sign = async (_name, place, lot, key, signer, port, signal) => ((got) => (got !== undefined && got.code === 0 ? { lines: got.out.filter((line) => parse(line).kind === 'fact') } : { refused: why(got) ?? '' }))(await through(place, words.sign, lot, key, signer, port, signal));
|
|
66
71
|
/** 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. */
|
|
67
72
|
/** A place's receipts rendered beside its lock, so bound alone checks the place: nothing when they closed, else why they did not. */
|
|
68
73
|
export const closing = async (name, place, signal) => ((got) => (got !== undefined && got.code === 0 ? undefined : why(got) ?? 'nothing printed'))(await run(name, place, [words.read, words.view, words.receipts], {}, signal));
|
|
69
74
|
/** A place's own lines in the person's words, beside its lock, the text they read and share: each line head lands at the person's at, drafted with no signature, gone with its withdraw. Bound reads the file as it reads the place, so it is written with the land, never while a fold runs. Set from head's lock. */
|
|
70
75
|
export const birth = { at: '', file: '' }, birthed = (place, lot) => {
|
|
71
|
-
const [file, mine] = [join(place, birth.file),
|
|
76
|
+
const [file, mine] = [join(place, birth.file), factsOf(lot).filter((l) => birth.file && l['at']?.startsWith(birth.at))], bare = (l) => JSON.stringify(Object.entries(l).filter(([k]) => !['about', 'value', 'sig', 'epoch', 'by'].includes(k)).sort());
|
|
72
77
|
if (mine.length)
|
|
73
|
-
writeFileSync(file, [...(existsSync(file) ?
|
|
78
|
+
writeFileSync(file, [...(existsSync(file) ? factsOf(readFileSync(file, 'utf8').split('\n')) : []).filter((k) => !mine.some((m) => bare(m) === bare(k))), ...mine.filter((m) => m['value'] !== 'withdraw')].map(({ sig: _sig, epoch: _epoch, ...l }) => `${canonical({ ...l, by: 'target' })}\n`).join(''));
|
|
74
79
|
};
|
|
75
|
-
export const landAct = async (
|
|
80
|
+
export const landAct = async (_name, place, lot, key, signer, port, signal) => ((got) => {
|
|
76
81
|
if (got === undefined || got.code !== 0)
|
|
77
82
|
return { refused: why(got) ?? '' };
|
|
78
83
|
const at = got.out.findIndex((line) => line.startsWith(words.act));
|
|
79
84
|
return at < 0 ? { refused: `REFUSE·act bound printed no act result, and the place declares ${words.profile}: what was admitted is bound's to say` }
|
|
80
85
|
: (birthed(place, lot), { lines: got.out.slice(at + 1).filter((line) => parse(line).kind === 'fact').slice(0, -1) });
|
|
81
|
-
})(await
|
|
82
|
-
export const land = async (
|
|
86
|
+
})(await through(place, words.land, lot, key, signer, port, signal));
|
|
87
|
+
export const land = async (_name, place, signed, signal) => ((got) => (got !== undefined && got.code === 0 ? (birthed(place, signed), { lines: signed }) : { refused: why(got) ?? '' }))(await through(place, words.land, signed, undefined, undefined, undefined, signal));
|
|
83
88
|
export const told = async (name, place, signed, view, signal) => ((got) => [...(got?.out ?? []), ...(got?.err ?? [])])(await run(name, place, [words.read, words.view, view, lotFile(signed)], {}, signal));
|
|
84
89
|
export const pinnedAt = async (name, place, file, signal) => {
|
|
85
90
|
const printed = await run(name, place, [words.read, file], {}, signal);
|
|
@@ -90,6 +95,6 @@ export const pinnedAt = async (name, place, file, signal) => {
|
|
|
90
95
|
const bytes = printed.out.filter((line) => line.startsWith(words.topos)).map((line) => `${line.slice(words.topos.length)}\n`).join('');
|
|
91
96
|
return pin === undefined || !bytes ? undefined : { pin, bytes, lines: read.lines };
|
|
92
97
|
};
|
|
93
|
-
export const founded = async (
|
|
98
|
+
export const founded = async (_name, place, key, signer, port, signal) => ((got) => (got !== undefined && got.code === 0 ? (birthed(place, readFileSync(join(place, LOCK), 'utf8').split('\n')), { lines: got.out }) : { refused: why(got) ?? '' }))(await through(place, words.land, undefined, key, signer, port, signal));
|
|
94
99
|
export const words = { read: '', view: '', sign: '', land: '', key: '', signer: '', pin: '', topos: '', standing: '', evidence: '', act: '', profile: '', receipts: '', refusal: '', identity: '' };
|
|
95
100
|
export const settledView = async (name, place, view, signal) => readOf(await run(name, place, [words.read, words.view, view], {}, signal));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { NativeHostOptions } from '@lapxo/bound/host';
|
|
2
|
+
import type { Printed } from './process.ts';
|
|
3
|
+
import { type Said } from '../core/lock.ts';
|
|
4
|
+
/** What a place's fold host is held to, from head's lock: the installed instrument it must be, how long it stays idle and lives, and its limits. */
|
|
5
|
+
export declare const hosting: {
|
|
6
|
+
installed: string;
|
|
7
|
+
idle: number;
|
|
8
|
+
life: number;
|
|
9
|
+
wait: number;
|
|
10
|
+
bytes: number;
|
|
11
|
+
heap: number;
|
|
12
|
+
queued: number;
|
|
13
|
+
http: NativeHostOptions["http"];
|
|
14
|
+
};
|
|
15
|
+
/** Receiver permissions and work budgets are declared by head's own lock, never inferred from a redirect. */
|
|
16
|
+
export declare const configureHost: (lock: readonly Said[], bounds: {
|
|
17
|
+
readonly wait: number;
|
|
18
|
+
readonly most: number;
|
|
19
|
+
}) => void;
|
|
20
|
+
/** Every host closed, its scratch removed with it: whenever a caller asks, and when head ends, its scratch then removed at once, whatever its host is still doing. */
|
|
21
|
+
export declare const cooled: () => void;
|
|
22
|
+
/** The process a place's fold host runs as, while one is held for it. */
|
|
23
|
+
export declare const hostOf: (place: string) => number | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* A read of a place's view through the place's own fold host, which only folds and never holds a signer: started on the first
|
|
26
|
+
* read, kept while it is asked within its idle and its life, the installed bound checked file by file before every call.
|
|
27
|
+
* A refusal, an error or a changed instrument closes it, and the next read starts another.
|
|
28
|
+
*/
|
|
29
|
+
export declare const hosted: (place: string, args: readonly string[], signal?: AbortSignal, from?: string) => Promise<Printed>;
|
|
30
|
+
/**
|
|
31
|
+
* A sign or a land in a host of its own, started for this one call with the signers the lock names and closed once it
|
|
32
|
+
* answers: the signer never outlives the call it signed for. The place's fold host is closed first, a place having one
|
|
33
|
+
* host at a time; the lot goes in as the host's input, never a file of head's.
|
|
34
|
+
*/
|
|
35
|
+
export declare const signedIn: (place: string, args: readonly string[], lot: readonly string[] | undefined, signers: NonNullable<NativeHostOptions["signers"]>, keyId?: string, signal?: AbortSignal, from?: string) => Promise<Printed>;
|