@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.
Files changed (53) hide show
  1. package/README.md +1 -1
  2. package/TARGET.bound +205 -112
  3. package/dist/src/access/contract.d.ts +0 -2
  4. package/dist/src/access/obtain.js +3 -3
  5. package/dist/src/ahead/budget.d.ts +4 -0
  6. package/dist/src/ahead/budget.js +8 -0
  7. package/dist/src/claims/claim.d.ts +1 -0
  8. package/dist/src/claims/claim.js +15 -15
  9. package/dist/src/claims/view.d.ts +3 -0
  10. package/dist/src/claims/view.js +11 -0
  11. package/dist/src/compile/pin.d.ts +7 -0
  12. package/dist/src/compile/pin.js +6 -7
  13. package/dist/src/core/answer.d.ts +1 -0
  14. package/dist/src/core/answer.js +1 -0
  15. package/dist/src/core/device.d.ts +2 -2
  16. package/dist/src/core/device.js +5 -5
  17. package/dist/src/core/lock.d.ts +1 -0
  18. package/dist/src/core/lock.js +2 -1
  19. package/dist/src/core/read.js +1 -2
  20. package/dist/src/core/worlds.js +2 -3
  21. package/dist/src/doors/guard.d.ts +18 -0
  22. package/dist/src/doors/guard.js +63 -0
  23. package/dist/src/doors/head.d.ts +60 -0
  24. package/dist/src/doors/head.js +136 -0
  25. package/dist/src/doors/http.d.ts +3 -0
  26. package/dist/src/doors/http.js +34 -0
  27. package/dist/src/doors/lines.d.ts +3 -0
  28. package/dist/src/doors/lines.js +21 -0
  29. package/dist/src/flows/device.js +4 -6
  30. package/dist/src/flows/plan.js +7 -10
  31. package/dist/src/flows/read.js +4 -1
  32. package/dist/src/index.js +17 -116
  33. package/dist/src/links/inspect.js +7 -9
  34. package/dist/src/places/found.js +1 -1
  35. package/dist/src/ports/bound.d.ts +9 -10
  36. package/dist/src/ports/bound.js +25 -20
  37. package/dist/src/ports/host.d.ts +35 -0
  38. package/dist/src/ports/host.js +108 -0
  39. package/dist/src/ports/keychain.d.ts +14 -2
  40. package/dist/src/ports/keychain.js +19 -7
  41. package/dist/src/ports/scratch.d.ts +4 -0
  42. package/dist/src/ports/scratch.js +10 -0
  43. package/dist/src/signer.js +8 -7
  44. package/dist/src/tools/card.js +3 -6
  45. package/dist/src/verbs/ask.js +2 -1
  46. package/dist/src/walk/context.js +3 -6
  47. package/package.json +3 -3
  48. package/dist/src/ports/signers.d.ts +0 -6
  49. package/dist/src/ports/signers.js +0 -14
  50. package/dist/src/ports/spare.d.ts +0 -1
  51. package/dist/src/ports/spare.js +0 -33
  52. package/dist/src/ports/warm.d.ts +0 -19
  53. 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,3 @@
1
+ import type { Said } from '../core/lock.ts';
2
+ /** The lines door: one JSON request a line in, its answer one JSON line out, the cells as every door gives them. */
3
+ export declare const open: (lock: readonly Said[], root: string, ext: string, name: string) => Promise<void>;
@@ -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
+ });
@@ -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.read(account) === undefined ? { said: [sayIn(context.lock)('no-root', { place })] } : { account }))(fill(valueOf(context.lock, 'founding/root-account'), { place: placeNameOf(place) }));
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 (wire.some((l) => members(l.value ?? '').includes(field)))
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 })] };
@@ -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 now = await standingOr(context, place);
30
- if ('said' in now)
31
- return now.said;
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 now = await standingOr(context, kept.place);
45
- if ('said' in now)
46
- return now.said;
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
  };
@@ -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 { answerOf } from "./core/answer.js";
16
- import { parse } from '@lapxo/topos/wire';
17
- import { aboutOf, valueOf } from "./core/lock.js";
18
- import { fill, sayIn } from "./core/surface.js";
19
- import { matchTemplate, refusedOn, shapeOf, shapeSchema, surfaceOf } from "./ports/surface.js";
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
- if (process.argv[2] !== valueOf(lock, 'cli/run')) {
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
- const surface = surfaceOf(lock);
38
- const runs = valueOf(lock, 'runs');
39
- Object.assign(words, Object.fromEntries(['read', 'view', 'sign', 'land', 'key', 'signer', 'pin', 'topos', 'standing', 'evidence', 'act', 'profile', 'receipts', 'refusal', 'identity'].map((w) => [w, valueOf(lock, `bound/${w}`)])));
40
- Object.assign(limits, { wait: Number(valueOf(lock, 'bound/wait').split('..')[1]), most: Number(valueOf(lock, 'bound/printed').split('..')[1]) });
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) => (ahead.abort(), turn(async () => {
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) => turn(async () => {
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 { inPlace, standingOr } from "../core/answer.js";
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) => inPlace(asked, context, async (place) => {
16
- const [say, word, link] = [sayIn(context.lock), (k) => valueOf(context.lock, k), asked['document'] ?? ''], [held, admits] = [await standingOr(context, place, context.policy), members(valueOf(context.lock, 'link/transport'))];
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 [...held.lines.filter((l) => admits.includes(l.scope ?? '') || l.scope?.startsWith('transport/')).map((l) => canonical(l)), say('why-transport', { place })];
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) => held.lines.some((l) => l.scope === 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] = [held.lines.find((l) => l.scope === scope && l.role === 'demands' && l.value === link), rootIn(context, place), printed?.err.filter((l) => !l.startsWith('{')).at(-1) ?? ''];
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);
@@ -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.read(account) !== undefined)
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 { Said } from '../core/lock.ts';
1
+ import { type Said } from '../core/lock.ts';
2
2
  import type { Outcome } from '../core/answer.ts';
3
- import { cooled } from './warm.ts';
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 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
+ /** 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
- export declare const sign: (name: string, place: string, lot: readonly string[], key: string, signer: string, env: Readonly<Record<string, string>>, signal?: AbortSignal) => Promise<Outcome>;
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: (name: string, place: string, lot: readonly string[], key: string, signer: string, env: Readonly<Record<string, string>>, signal?: AbortSignal) => Promise<Outcome>;
33
- export declare const land: (name: string, place: string, signed: readonly string[], signal?: AbortSignal) => Promise<Outcome>;
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: (name: string, place: string, key: string, signer: string, env: Readonly<Record<string, string>>, signal?: AbortSignal) => Promise<Outcome>;
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;
@@ -1,10 +1,11 @@
1
- import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
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 { cooled, kept, warmed } from "./warm.js";
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 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. */
22
- export const run = async (name, cwd, args, env = {}, signal) => ((bin, at, bounds) => (bin === undefined ? undefined
23
- : (warmed(bin, args, at, bounds) ?? 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 } : {}) });
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
- /** 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. */
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: facts(result.text.split('\n')) };
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) => ((at) => { writeFileSync(at, `${lines.join('\n')}\n`); return at; })(join(mkdtempSync(join(tmpdir(), 'head-lot-')), 'lot.bound'));
65
- export const sign = async (name, place, lot, key, signer, env, signal) => ((got) => (got !== undefined && got.code === 0 ? { lines: got.out.filter((line) => parse(line).kind === 'fact') } : { refused: why(got) ?? '' }))(await run(name, place, [words.sign, lotFile(lot), words.key, key, words.signer, signer], env, signal));
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), facts(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());
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) ? facts(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(''));
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 (name, place, lot, key, signer, env, signal) => ((got) => {
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 run(name, place, [words.land, lotFile(lot), words.key, key, words.signer, signer], env, signal));
82
- export const land = async (name, place, signed, signal) => ((got) => (got !== undefined && got.code === 0 ? (birthed(place, signed), { lines: signed }) : { refused: why(got) ?? '' }))(await run(name, place, [words.land, lotFile(signed)], {}, signal));
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 (name, place, key, signer, env, signal) => ((got) => (got !== undefined && got.code === 0 ? (birthed(place, readFileSync(join(place, LOCK), 'utf8').split('\n')), { lines: got.out }) : { refused: why(got) ?? '' }))(await run(name, place, [words.land, words.key, key, words.signer, signer], env, signal));
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>;