@lapxo/head 0.1.4 → 0.1.6
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 +6 -4
- package/TARGET.bound +204 -29
- package/dist/src/ahead/budget.d.ts +6 -0
- package/dist/src/ahead/budget.js +11 -0
- package/dist/src/ahead/fold.d.ts +2 -0
- package/dist/src/ahead/fold.js +19 -0
- package/dist/src/ahead/read.d.ts +10 -0
- package/dist/src/ahead/read.js +18 -0
- package/dist/src/answers/answered.d.ts +4 -0
- package/dist/src/answers/answered.js +22 -0
- package/dist/src/claims/claim.d.ts +9 -0
- package/dist/src/claims/claim.js +40 -0
- package/dist/src/compile/pin.js +2 -6
- package/dist/src/core/answer.d.ts +1 -0
- package/dist/src/core/device.d.ts +1 -4
- package/dist/src/core/device.js +2 -5
- package/dist/src/core/package.js +4 -3
- package/dist/src/flows/act.d.ts +1 -5
- package/dist/src/flows/act.js +1 -5
- package/dist/src/flows/call.d.ts +2 -9
- package/dist/src/flows/call.js +13 -13
- package/dist/src/flows/read.d.ts +1 -7
- package/dist/src/flows/read.js +26 -31
- package/dist/src/index.js +16 -7
- package/dist/src/kept/kept.d.ts +30 -0
- package/dist/src/kept/kept.js +20 -0
- package/dist/src/links/inspect.d.ts +9 -0
- package/dist/src/links/inspect.js +44 -0
- package/dist/src/links/link.d.ts +7 -0
- package/dist/src/links/link.js +25 -0
- package/dist/src/marks/compare.d.ts +9 -0
- package/dist/src/marks/compare.js +48 -0
- package/dist/src/onboard/first.js +1 -1
- package/dist/src/places/admit.js +1 -1
- package/dist/src/places/found.js +5 -9
- package/dist/src/ports/adapters/request.d.ts +1 -0
- package/dist/src/ports/adapters/request.js +2 -2
- package/dist/src/ports/bound.d.ts +7 -8
- package/dist/src/ports/bound.js +27 -17
- package/dist/src/ports/documents.js +1 -1
- package/dist/src/ports/reading.d.ts +8 -0
- package/dist/src/ports/reading.js +14 -0
- package/dist/src/ports/spare.js +1 -1
- package/dist/src/ports/warm.js +1 -2
- package/dist/src/sources/connect.js +3 -1
- package/dist/src/tools/claim.d.ts +1 -0
- package/dist/src/tools/claim.js +1 -0
- package/dist/src/tools/look.js +5 -5
- package/dist/src/use/named.d.ts +12 -0
- package/dist/src/use/named.js +25 -0
- package/dist/src/verbs/ask.d.ts +1 -9
- package/dist/src/verbs/ask.js +4 -11
- package/dist/src/verbs/card.d.ts +2 -2
- package/dist/src/verbs/card.js +3 -3
- package/package.json +3 -3
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Asked } from '../core/answer.ts';
|
|
2
|
+
import type { Said } from '../core/lock.ts';
|
|
3
|
+
/** Whether a read would spend past what the place allows: a question, past the place's rate; a read ahead, past the share of what the questions leave of it that reads ahead may spend. A read ahead is told by the mark beside its receipt, and counted once. */
|
|
4
|
+
export declare const overOf: (lines: readonly Said[], [family, at, mark]: readonly [string, string, string], source: string, now: number, rate: number, share: number, asked: Asked) => boolean;
|
|
5
|
+
/** The mark a read made ahead lands beside its receipt, under the receipt's own at, so what reads ahead spent is counted apart from what questions spent. */
|
|
6
|
+
export declare const markOf: (asked: Asked, [at, mark]: readonly [string, string], scope: string, digest: string, when: string) => readonly string[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { canonical } from '@lapxo/topos/wire';
|
|
2
|
+
import { spentOf } from "../core/receipt.js";
|
|
3
|
+
const early = (asked) => asked['ahead'] === 'true';
|
|
4
|
+
/** Whether a read would spend past what the place allows: a question, past the place's rate; a read ahead, past the share of what the questions leave of it that reads ahead may spend. A read ahead is told by the mark beside its receipt, and counted once. */
|
|
5
|
+
export const overOf = (lines, [family, at, mark], source, now, rate, share, asked) => {
|
|
6
|
+
const ahead = lines.filter((l) => l.scope?.startsWith(`${family}/${source}/`) && l.scope.endsWith(`/${mark}`) && l.at?.startsWith(`${at}:`) && now - Date.parse(l.at.slice(at.length + 1)) < 3_600_000).length;
|
|
7
|
+
const calls = spentOf(lines, family, at, source, now) - ahead;
|
|
8
|
+
return calls >= rate || (early(asked) && ahead + 1 > ((rate - (calls - ahead)) * share) / 100);
|
|
9
|
+
};
|
|
10
|
+
/** The mark a read made ahead lands beside its receipt, under the receipt's own at, so what reads ahead spent is counted apart from what questions spent. */
|
|
11
|
+
export const markOf = (asked, [at, mark], scope, digest, when) => early(asked) ? [canonical({ at: `${at}:${when}`, by: 'target', form: 'alphabet', measure: 'digest', role: 'writes', scope: `${scope}/${mark}`, value: digest })] : [];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Cached projections are indexed only by identities returned by the instrument. */
|
|
2
|
+
const kept = new Map();
|
|
3
|
+
/** A refused identity cannot validate an old answer. The reader binds new text to the expected identity. */
|
|
4
|
+
export const folded = async (kind, place, identity, read) => {
|
|
5
|
+
const key = `${kind}\0${place}`, current = await identity(), held = kept.get(key);
|
|
6
|
+
if (current === undefined) {
|
|
7
|
+
kept.delete(key);
|
|
8
|
+
return read();
|
|
9
|
+
}
|
|
10
|
+
if (held?.identity === current)
|
|
11
|
+
return held.folded;
|
|
12
|
+
const reading = read(current);
|
|
13
|
+
kept.set(key, { identity: current, folded: reading });
|
|
14
|
+
const forget = () => { if (kept.get(key)?.folded === reading)
|
|
15
|
+
kept.delete(key); };
|
|
16
|
+
reading.then((result) => { if (result && typeof result === 'object' && 'refused' in result)
|
|
17
|
+
forget(); }, forget);
|
|
18
|
+
return reading;
|
|
19
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Context } from '../core/answer.ts';
|
|
2
|
+
import type { Cell } from '../ports/surface.ts';
|
|
3
|
+
/**
|
|
4
|
+
* What an answer leaves to do before the next call, done after it is answered; another call coming gives up what is not
|
|
5
|
+
* begun, never what is, so no land is cut halfway:
|
|
6
|
+
* the reads a known next asks for, read in a context that asks the person nothing — a read only, never an act, nothing it
|
|
7
|
+
* would need a yes or a secret for — and then every place the answer names folded again where it moved, so the next
|
|
8
|
+
* question finds it folded. A fold calls no source, so it spends nothing of a place's rate.
|
|
9
|
+
*/
|
|
10
|
+
export declare const readAhead: (cells: readonly Cell[], context: Context, signal: AbortSignal) => Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readSource } from "../flows/read.js";
|
|
2
|
+
const none = async () => 'none', nothing = async () => undefined;
|
|
3
|
+
/**
|
|
4
|
+
* What an answer leaves to do before the next call, done after it is answered; another call coming gives up what is not
|
|
5
|
+
* begun, never what is, so no land is cut halfway:
|
|
6
|
+
* the reads a known next asks for, read in a context that asks the person nothing — a read only, never an act, nothing it
|
|
7
|
+
* would need a yes or a secret for — and then every place the answer names folded again where it moved, so the next
|
|
8
|
+
* question finds it folded. A fold calls no source, so it spends nothing of a place's rate.
|
|
9
|
+
*/
|
|
10
|
+
export const readAhead = async (cells, context, signal) => {
|
|
11
|
+
const quiet = { ...context, elicit: none, person: { consent: none, enter: nothing, choose: nothing, visit: nothing } };
|
|
12
|
+
for (const { next } of cells)
|
|
13
|
+
if (!signal.aborted && 'tool' in next && next.tool === 'ask' && next.args['source'] && next.args['operation'] && !next.args['why'] && !next.args['history'])
|
|
14
|
+
await readSource({ ...next.args, ahead: 'true' }, quiet);
|
|
15
|
+
await quiet.settle();
|
|
16
|
+
for (const place of new Set(cells.flatMap((c) => [c.fields['place'] ?? '', 'tool' in c.next ? c.next.args['place'] ?? '' : '']).filter((p) => p && !signal.aborted)))
|
|
17
|
+
await Promise.all([context.standing(place), context.policy(place)]).catch(() => undefined);
|
|
18
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Asked, Context } from '../core/answer.ts';
|
|
2
|
+
import type { Frame } from '../flows/call.ts';
|
|
3
|
+
/** Distinguish an empty response from missing transport bytes; only the latter leaves a named need. */
|
|
4
|
+
export declare const answering: (context: Context, place: string, asked: Asked, frame: Frame) => Frame;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { canonical } from '@lapxo/topos/wire';
|
|
2
|
+
import { valueOf } from "../core/lock.js";
|
|
3
|
+
import { rootIn } from "../flows/device.js";
|
|
4
|
+
/** A body that answers nothing: no byte of it, or a list with no member. */
|
|
5
|
+
const nothing = (bytes) => ((text) => text === '' || /^\[\s*\]$/.test(text))(new TextDecoder().decode(bytes).trim());
|
|
6
|
+
/** Distinguish an empty response from missing transport bytes; only the latter leaves a named need. */
|
|
7
|
+
export const answering = (context, place, asked, frame) => {
|
|
8
|
+
const need = `${valueOf(context.lock, 'place/needs')}/bytes/${frame.source}/${frame.operation}`, held = frame.lines.find((l) => l.scope === need && l.role === 'demands' && l.value !== 'withdraw');
|
|
9
|
+
const owe = (line) => ((root) => ('said' in root ? undefined : context.landRoot(place, [line], root.account)))(rootIn(context, place));
|
|
10
|
+
const withdrawn = ({ about: _about, sig: _sig, epoch: _epoch, ...l }) => canonical({ ...l, by: 'target', value: 'withdraw' });
|
|
11
|
+
return { ...frame,
|
|
12
|
+
typed: (status, bytes) => (nothing(bytes) ? Promise.resolve(frame.say('answered-nothing', { source: frame.source, operation: frame.operation, status, at: new Date(context.now()).toISOString() })) : frame.typed(status, bytes)),
|
|
13
|
+
send: async (attachment) => {
|
|
14
|
+
const got = await frame.send(attachment);
|
|
15
|
+
if (asked['ahead'] !== 'true' && got.kind === 'failed' && held === undefined)
|
|
16
|
+
await owe(canonical({ at: `policy:head/owed/${new Date(context.now()).toISOString()}`, by: 'target', form: 'alphabet', measure: 'id', role: 'demands', scope: need, value: frame.plan.origin }));
|
|
17
|
+
if (got.kind === 'got' && held !== undefined)
|
|
18
|
+
await owe(withdrawn(held));
|
|
19
|
+
return got;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Asked, type Context } from '../core/answer.ts';
|
|
2
|
+
/**
|
|
3
|
+
* What the person states, kept as theirs: a line their own key attests, their words exactly as given, in the place named;
|
|
4
|
+
* asked once a conversation whether what they state there is kept, and each statement said back as it goes in, so they see
|
|
5
|
+
* it, with who carried it here as the client says it is. Taken back by the same words: what a person attests is evidence, and
|
|
6
|
+
* stands for good, so taking it back is attesting that, beside it. A place whose lines admit no carrier is handed the line that does.
|
|
7
|
+
* Only this action keeps one, never a cell, an answer or a read ahead, so nothing the model concluded becomes the person's.
|
|
8
|
+
*/
|
|
9
|
+
export declare const claimed: (asked: Asked, context: Context) => Promise<readonly string[]>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonical } from '@lapxo/topos/wire';
|
|
3
|
+
import { inPlace, standingOr, unless } from "../core/answer.js";
|
|
4
|
+
import { uncoveredIn } from "../core/device.js";
|
|
5
|
+
import { aboutOf, members, valueOf } from "../core/lock.js";
|
|
6
|
+
import { fill, sayIn } from "../core/surface.js";
|
|
7
|
+
/** Whether the person said, in this conversation, that what they state in a place is kept as theirs: asked once a place, a no kept as a yes is. */
|
|
8
|
+
const answered = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* What the person states, kept as theirs: a line their own key attests, their words exactly as given, in the place named;
|
|
11
|
+
* asked once a conversation whether what they state there is kept, and each statement said back as it goes in, so they see
|
|
12
|
+
* it, with who carried it here as the client says it is. Taken back by the same words: what a person attests is evidence, and
|
|
13
|
+
* stands for good, so taking it back is attesting that, beside it. A place whose lines admit no carrier is handed the line that does.
|
|
14
|
+
* Only this action keeps one, never a cell, an answer or a read ahead, so nothing the model concluded becomes the person's.
|
|
15
|
+
*/
|
|
16
|
+
export const claimed = (asked, context) => inPlace(asked, context, async (place) => {
|
|
17
|
+
const [say, word, said, held] = [sayIn(context.lock), (k) => valueOf(context.lock, k), (asked['said'] ?? '').trim(), await standingOr(context, place)];
|
|
18
|
+
if ('said' in held)
|
|
19
|
+
return held.said;
|
|
20
|
+
if (!said)
|
|
21
|
+
return [say('missing-param', { operation: 'claim', param: 'said' })];
|
|
22
|
+
const [family, withdraw, back] = [`${word('claim/family')}/${createHash('sha256').update(said).digest('hex').slice(0, 16)}`, asked['withdraw'] === 'true', word('claim/withdrawn')];
|
|
23
|
+
const standing = held.lines.filter((l) => l.scope?.startsWith(`${family}/`) && !l.scope.endsWith(`/${back}`) && !held.lines.some((w) => w.scope === `${l.scope}/${back}`)).map((l) => l.scope);
|
|
24
|
+
if (withdraw && !standing.length)
|
|
25
|
+
return [say('claim-none', { place, said })];
|
|
26
|
+
const wire = held.lines.find((l) => l.scope === 'wire/fields'), via = word('claim/via');
|
|
27
|
+
const bare = wire && Object.fromEntries(Object.entries(wire).filter(([k]) => !['sig', 'epoch', 'about'].includes(k)));
|
|
28
|
+
if (bare && !members(wire.value ?? '').includes(via))
|
|
29
|
+
return [say('claims-unadmitted', { place, lines: [canonical({ ...bare, by: 'target', value: 'withdraw' }), canonical({ ...bare, by: 'target', at: `policy:head/wire/${new Date(context.now()).toISOString()}`, value: `${wire.value}|${via}` })].join('\n') })];
|
|
30
|
+
const line = (scope) => canonical({ about: said, at: word('claim/at'), by: 'target', form: 'alphabet', measure: 'text', role: 'writes', scope, value: 'lock', [via]: context.client() ?? word('claim/via-unknown') });
|
|
31
|
+
const lot = withdraw ? standing.map((scope) => line(`${scope}/${back}`)) : [line(`${family}/${new Date(context.now()).toISOString().replace(/\D/g, '')}`)], outside = uncoveredIn(context.lock, held.lines, context.device('person'), place, lot);
|
|
32
|
+
if (outside)
|
|
33
|
+
return outside;
|
|
34
|
+
if (!withdraw && !answered.has(place))
|
|
35
|
+
answered.set(place, (await unless(context, fill(aboutOf(context.lock, 'said/elicit-claims'), { place }))) === undefined);
|
|
36
|
+
if (!withdraw && !answered.get(place))
|
|
37
|
+
return [say('claims-declined', { place, said })];
|
|
38
|
+
const landed = await context.landAs(place, lot, 'person', held.lines);
|
|
39
|
+
return 'refused' in landed ? [say('not-landed', { why: landed.refused })] : [say(withdraw ? 'unclaimed' : 'claimed', { place, said }), ...landed.lines];
|
|
40
|
+
});
|
package/dist/src/compile/pin.js
CHANGED
|
@@ -4,11 +4,7 @@ import { members, under, valueOf } from "../core/lock.js";
|
|
|
4
4
|
import { fieldsOf, vocabOf } from "../core/read.js";
|
|
5
5
|
import { sayIn } from "../core/surface.js";
|
|
6
6
|
import { digestIn, linesOf, pinnedOf } from "../core/worlds.js";
|
|
7
|
-
/**
|
|
8
|
-
* One compile, by the fold: the place view asked once, this source's lines kept with a region of its own — and the flow
|
|
9
|
-
* of each issuer the source declares, so a flow is pinned once, by whoever declares it — and the standing bound selects
|
|
10
|
-
* from them pinned and laid where its input names it.
|
|
11
|
-
*/
|
|
7
|
+
/** Pin the compiled source and adopt only the declared outputs. */
|
|
12
8
|
const compiledOf = async (context, place, source, file) => {
|
|
13
9
|
const say = sayIn(context.lock), v = vocabOf(context.lock);
|
|
14
10
|
const compiled = await context.world(place, valueOf(context.lock, 'place/view'));
|
|
@@ -53,7 +49,7 @@ export const pinIn = (context, place, lands, line) => async (source, read) => {
|
|
|
53
49
|
if ('said' in pinned || (old?.needs === file && old.value === pinned.pin))
|
|
54
50
|
return pinned;
|
|
55
51
|
const withdraw = old === undefined ? [] : [canonical({ ...Object.fromEntries(Object.entries(old).filter(([k]) => !['sig', 'epoch'].includes(k))), by: 'target', value: 'withdraw' })];
|
|
56
|
-
const landed = await lands([...withdraw, line(`uses/${source}`, 'digest', pinned.pin, { needs: file, at: `policy:
|
|
52
|
+
const landed = await lands([...withdraw, line(`uses/${source}`, 'digest', pinned.pin, { needs: file, at: `policy:person/connect/${new Date(context.now()).toISOString()}` })]);
|
|
57
53
|
return 'refused' in landed ? { said: [say('not-landed', { why: landed.refused })] } : pinned;
|
|
58
54
|
};
|
|
59
55
|
/**
|
|
@@ -15,6 +15,7 @@ export interface Context {
|
|
|
15
15
|
readonly lock: readonly Said[];
|
|
16
16
|
readonly bound: (cwd: string, args: readonly string[]) => Promise<Printed | undefined>;
|
|
17
17
|
readonly roots: () => Promise<readonly string[]>;
|
|
18
|
+
readonly client: () => string | undefined;
|
|
18
19
|
readonly keys: KeyStore;
|
|
19
20
|
readonly device: (role: string) => string | undefined;
|
|
20
21
|
readonly standing: (place: string) => Promise<{
|
|
@@ -5,10 +5,7 @@ export interface Device {
|
|
|
5
5
|
readonly signer: string;
|
|
6
6
|
}
|
|
7
7
|
export declare const deviceOf: (lock: readonly Said[], role?: string) => Device;
|
|
8
|
-
/**
|
|
9
|
-
* The drafted lines a place signs to admit a key: each device/<role>/<measure> line of head-s lock, said of keys/<id>, and
|
|
10
|
-
* its public half. The id is the role's own unless whoever admits names it; so is the coverage, unless they give it.
|
|
11
|
-
*/
|
|
8
|
+
/** Derive device authority from the admitted lock. */
|
|
12
9
|
export declare const admissionOf: (lock: readonly Said[], publicKey: string, role?: string, as?: {
|
|
13
10
|
readonly id?: string;
|
|
14
11
|
readonly coverage?: string;
|
package/dist/src/core/device.js
CHANGED
|
@@ -3,14 +3,11 @@ import { members, said, valueOf } from "./lock.js";
|
|
|
3
3
|
import { sayIn } from "./surface.js";
|
|
4
4
|
const HEAD = 'head';
|
|
5
5
|
export const deviceOf = (lock, role = HEAD) => ({ account: valueOf(lock, `device/${role}/account`), service: valueOf(lock, 'device/service'), signer: valueOf(lock, `device/${role}/signer`) });
|
|
6
|
-
/**
|
|
7
|
-
* The drafted lines a place signs to admit a key: each device/<role>/<measure> line of head-s lock, said of keys/<id>, and
|
|
8
|
-
* its public half. The id is the role's own unless whoever admits names it; so is the coverage, unless they give it.
|
|
9
|
-
*/
|
|
6
|
+
/** Derive device authority from the admitted lock. */
|
|
10
7
|
export const admissionOf = (lock, publicKey, role = HEAD, as = {}) => ((id, at) => [
|
|
11
8
|
...['class', 'coverage', 'resolution', 'signer'].flatMap((measure) => ((line) => (line === undefined ? [] : [canonical({ at, by: 'target', form: line.form ?? 'alphabet', measure, role: 'writes', scope: `keys/${id}`, value: measure === 'coverage' && as.coverage ? as.coverage : line.value ?? '' })]))(said(lock, `device/${role}/${measure}`))),
|
|
12
9
|
canonical({ at, by: 'target', form: 'alphabet', measure: 'public-key', role: 'writes', scope: `keys/${id}`, value: publicKey }),
|
|
13
|
-
])(as.id ?? valueOf(lock, `device/${role}/account`), as.at ?? 'policy:
|
|
10
|
+
])(as.id ?? valueOf(lock, `device/${role}/account`), as.at ?? 'policy:person/device');
|
|
14
11
|
export const algorithmOf = (lines) => ((era) => (lines.find((l) => l.scope === 'wire/signature-algorithms')?.value ?? '').split('|').find((one) => one.endsWith(`:${era}`)) ?? '')(valueOf(lines, 'wire/era'));
|
|
15
12
|
/** The id a place admits a public key under, as its own lines say: a key is who the place says it is, and a key it never admitted is no one there. */
|
|
16
13
|
export const idIn = (lines, publicKey) => publicKey === undefined ? undefined : lines.find((l) => l.scope?.startsWith('keys/') && l.measure === 'public-key' && l.value === publicKey)?.scope.slice('keys/'.length);
|
package/dist/src/core/package.js
CHANGED
|
@@ -5,6 +5,7 @@ import { members, under, valueOf } from "./lock.js";
|
|
|
5
5
|
import { fieldsOf, vocabOf } from "./read.js";
|
|
6
6
|
import { sayIn } from "./surface.js";
|
|
7
7
|
import { digestIn, linesOf } from "./worlds.js";
|
|
8
|
+
import { linkOf } from "../links/link.js";
|
|
8
9
|
/** A line no package carries: one that is not a world, or a source's document, overlay or pin, said once by digest — with, beside a digest that names bytes, the encrypted address they are read at. */
|
|
9
10
|
export const foreignIn = (lines) => lines.find((l) => !/^(sources\/[a-z0-9-]+(\/overlay)?|uses\/[a-z0-9-]+)$/.test(l.scope ?? '') || lines.some((o) => o !== l && o.scope === l.scope && o.measure === l.measure)
|
|
10
11
|
|| (l.measure !== 'digest' && !(l.measure === 'id' && URL.canParse(l.value ?? '') && lines.some((o) => o.scope === l.scope && o.measure === 'digest' && o.needs))));
|
|
@@ -27,13 +28,13 @@ export const shared = (asked, context) => inPlace(asked, context, async (place)
|
|
|
27
28
|
return [say('share-to', { place, to })];
|
|
28
29
|
if ('said' in held)
|
|
29
30
|
return held.said;
|
|
30
|
-
const
|
|
31
|
+
const urls = ((u) => (u && typeof u === 'object' ? u : {}))((() => { try {
|
|
31
32
|
return JSON.parse(asked['urls'] || '{}');
|
|
32
33
|
}
|
|
33
34
|
catch {
|
|
34
35
|
return {};
|
|
35
|
-
} })())), bytes = new TextEncoder().encode(`${pack.lines.join('\n')}\n`);
|
|
36
|
-
return !pack.names.length ? [say('share-none', { place })] : ([...pack.files, [LOCK, bytes]].forEach(([at, laid]) => context.files.lay(to, at, laid)), [say('shared', { place, to, sources: pack.names.join(', '), digest: digestIn(context.lock)(bytes) }), ...pack.lines]);
|
|
36
|
+
} })()), pack = packed(context, place, held.lines, urls), bytes = new TextEncoder().encode(`${pack.lines.join('\n')}\n`);
|
|
37
|
+
return !pack.names.length ? [say('share-none', { place })] : ([...pack.files, [LOCK, bytes]].forEach(([at, laid]) => context.files.lay(to, at, laid)), [say('shared', { place, to, sources: pack.names.join(', '), digest: digestIn(context.lock)(bytes) }), ...linkOf(context, to, pack.lines, urls), ...pack.lines]);
|
|
37
38
|
});
|
|
38
39
|
/** What a package names, checked whole before anything is asked: every line a world or a source's document, overlay or pin, by digest; each world the one this head pins; an order its pins rest in. A source whose bytes did not arrive, or did not hash to their line, is open, and so is every source resting on it: absence is not a refusal, and the rest arrives. */
|
|
39
40
|
export const unpacked = (context, document, bytes, read) => {
|
package/dist/src/flows/act.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
1
|
import type { Asked, Context } from '../core/answer.ts';
|
|
2
|
-
/**
|
|
3
|
-
* One offer that is not of the class head reads: within a cap the person signed it is called as far as the cap goes;
|
|
4
|
-
* without one, each call asks the person once, and their yes is signed for exactly that request. The intent lands before
|
|
5
|
-
* the call, the receipt after; a call that never answered stays open until a look of what it would have changed closes it.
|
|
6
|
-
*/
|
|
2
|
+
/** Run one action within its declared authority, with the person's approval when required. */
|
|
7
3
|
export declare const act: (asked: Asked, context: Context) => Promise<readonly string[]>;
|
package/dist/src/flows/act.js
CHANGED
|
@@ -4,11 +4,7 @@ import { draftOf } from "../core/receipt.js";
|
|
|
4
4
|
import { sayIn } from "../core/surface.js";
|
|
5
5
|
import { canonical } from '@lapxo/topos/wire';
|
|
6
6
|
import { called } from "./call.js";
|
|
7
|
-
/**
|
|
8
|
-
* One offer that is not of the class head reads: within a cap the person signed it is called as far as the cap goes;
|
|
9
|
-
* without one, each call asks the person once, and their yes is signed for exactly that request. The intent lands before
|
|
10
|
-
* the call, the receipt after; a call that never answered stays open until a look of what it would have changed closes it.
|
|
11
|
-
*/
|
|
7
|
+
/** Run one action within its declared authority, with the person's approval when required. */
|
|
12
8
|
export const act = (asked, context) => called(asked, context, planAct, async ({ word, say, lines, source, operation, prepared, typed, attach, send, lands, widen, uncovered }) => {
|
|
13
9
|
const [family, done] = [word('act/family'), word('act/done')];
|
|
14
10
|
const allowed = allowedOf(lines, word('allow/family'), source, operation);
|
package/dist/src/flows/call.d.ts
CHANGED
|
@@ -3,10 +3,7 @@ import type { Said } from '../core/lock.ts';
|
|
|
3
3
|
import type { Call, Plan } from '../core/read.ts';
|
|
4
4
|
import type { Attachment } from '../access/contract.ts';
|
|
5
5
|
import type { Fetched, Prepared } from '../ports/adapters/request.ts';
|
|
6
|
-
/**
|
|
7
|
-
* The origin of a source a person picks once for a place, when its document declares several: offered as a choice among
|
|
8
|
-
* them, none a default, and kept as the person's line; the lines a call stands on, with it.
|
|
9
|
-
*/
|
|
6
|
+
/** Ask for one declared origin when the source offers several. */
|
|
10
7
|
export declare const pickedOrigin: (context: Context, place: string, lines: readonly Said[], source: string, plan: Call | Plan) => Promise<{
|
|
11
8
|
readonly lines: readonly Said[];
|
|
12
9
|
} | {
|
|
@@ -40,9 +37,5 @@ export interface Frame {
|
|
|
40
37
|
readonly send: (attachment: Attachment | undefined) => Promise<Fetched>;
|
|
41
38
|
readonly uncovered: (role: string, scopes: readonly string[]) => readonly string[] | undefined;
|
|
42
39
|
}
|
|
43
|
-
/**
|
|
44
|
-
* The steps a read and an act share: the place, the lines it stands on once bound settled them, the offer's
|
|
45
|
-
* plan, the transport its origin names and the request it prepares; then what is particular, given a frame that says
|
|
46
|
-
* by the lock, attaches access when asked, types what came back and lands a line under head's key.
|
|
47
|
-
*/
|
|
40
|
+
/** Prepare one call, authorize its access and pass its frame to the read or action flow. */
|
|
48
41
|
export declare const called: (asked: Asked, context: Context, planOf: (lines: readonly Said[], lock: readonly Said[], source: string, operation: string, place: string) => Call | Plan, then: (frame: Frame) => Promise<readonly string[]>) => Promise<readonly string[]>;
|
package/dist/src/flows/call.js
CHANGED
|
@@ -5,13 +5,12 @@ import { sayIn } from "../core/surface.js";
|
|
|
5
5
|
import { valueOf } from "../core/lock.js";
|
|
6
6
|
import { uncoveredIn } from "../core/device.js";
|
|
7
7
|
import { widening } from "./device.js";
|
|
8
|
+
import { namedIn } from "../use/named.js";
|
|
9
|
+
import { answering } from "../answers/answered.js";
|
|
8
10
|
import { attachmentFor } from "../access/obtain.js";
|
|
9
11
|
import { typedAnswer } from "./answer.js";
|
|
10
12
|
import { standingFor } from "./world.js";
|
|
11
|
-
/**
|
|
12
|
-
* The origin of a source a person picks once for a place, when its document declares several: offered as a choice among
|
|
13
|
-
* them, none a default, and kept as the person's line; the lines a call stands on, with it.
|
|
14
|
-
*/
|
|
13
|
+
/** Ask for one declared origin when the source offers several. */
|
|
15
14
|
export const pickedOrigin = async (context, place, lines, source, plan) => {
|
|
16
15
|
if (plan.kind !== 'said' || plan.key !== 'pick-origin')
|
|
17
16
|
return { lines };
|
|
@@ -27,18 +26,18 @@ export const pickedOrigin = async (context, place, lines, source, plan) => {
|
|
|
27
26
|
const landed = await by.land([canonical(line(chosen))]);
|
|
28
27
|
return 'refused' in landed ? { said: [sayIn(context.lock)('not-landed', { why: landed.refused })] } : { lines: [...lines, line(chosen)] };
|
|
29
28
|
};
|
|
30
|
-
/**
|
|
31
|
-
* The steps a read and an act share: the place, the lines it stands on once bound settled them, the offer's
|
|
32
|
-
* plan, the transport its origin names and the request it prepares; then what is particular, given a frame that says
|
|
33
|
-
* by the lock, attaches access when asked, types what came back and lands a line under head's key.
|
|
34
|
-
*/
|
|
29
|
+
/** Prepare one call, authorize its access and pass its frame to the read or action flow. */
|
|
35
30
|
export const called = async (asked, context, planOf, then) => {
|
|
36
31
|
const say = (key, fields = {}) => [sayIn(context.lock)(key, fields)];
|
|
37
|
-
const
|
|
32
|
+
const source = asked['source'] ?? '';
|
|
38
33
|
return inPlace(asked, context, async (place) => {
|
|
39
34
|
const held = await standingFor(context, place);
|
|
40
35
|
if ('unsettled' in held)
|
|
41
36
|
return say('unsettled', { source, why: held.unsettled });
|
|
37
|
+
const named = await namedIn(context, held.lines, source, asked['operation'] ?? '', asked['ahead'] === 'true');
|
|
38
|
+
if ('said' in named)
|
|
39
|
+
return named.said;
|
|
40
|
+
const operation = named.operation;
|
|
42
41
|
const widen = async (message, lot) => {
|
|
43
42
|
const by = widening(context, place, held.lines, lot);
|
|
44
43
|
if ('said' in by)
|
|
@@ -64,10 +63,11 @@ export const called = async (asked, context, planOf, then) => {
|
|
|
64
63
|
if ('refused' in prepared)
|
|
65
64
|
return say(prepared.refused, { source, origin: plan.origin });
|
|
66
65
|
const most = (scope) => Number((lines.find((l) => l.scope === scope)?.value ?? '0..0').split('..')[1]);
|
|
67
|
-
const typed = async (status, bytes) => ((got) => ['lines' in got ? say('answered', { source, operation, status })[0] : say('untyped', { source, operation, status, why: got.untyped })[0], ...('lines' in got ? got.lines : [])])(await typedAnswer(context, place, lines, source, operation, prepared.key, bytes));
|
|
68
|
-
const lands = (...lot) => context.landAs(place, lot, 'head', lines);
|
|
66
|
+
const typed = async (status, bytes) => ((got, at) => ['lines' in got ? say('answered', { source, operation, status, at })[0] : say('untyped', { source, operation, status, at, why: got.untyped })[0], ...('lines' in got ? got.lines : [])])(await typedAnswer(context, place, lines, source, operation, prepared.key, bytes), new Date(context.now()).toISOString());
|
|
69
67
|
const attach = async () => ((got) => ('said' in got ? { said: say(got.said, { source, operation, kinds: plan.access.kind, input: got.input }) } : got))(await attachmentFor(context, place, lines, plan));
|
|
70
68
|
const uncovered = (role, scopes) => uncoveredIn(context.lock, lines, context.device(role), place, [], scopes);
|
|
71
|
-
|
|
69
|
+
let use = named.use !== undefined && uncovered('head', [named.scope]) === undefined ? [named.use] : [];
|
|
70
|
+
const lands = (...lot) => { const once = use; use = []; return context.landAs(place, [...once, ...lot], 'head', lines).then((out) => ('lines' in out ? { lines: out.lines.filter((l) => !l.includes(` scope=${valueOf(context.lock, 'use/family')}/`)) } : out)); };
|
|
71
|
+
return then(answering(context, place, asked, { widen, word: (scope) => valueOf(context.lock, scope), say, place, lines, source, operation, plan, prepared, most, typed, attach, send: (attachment) => prepared.send(attachment, most('read/response-bytes')), lands, uncovered }));
|
|
72
72
|
});
|
|
73
73
|
};
|
package/dist/src/flows/read.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
1
|
import type { Asked, Context } from '../core/answer.ts';
|
|
2
|
-
/**
|
|
3
|
-
* A read of one offer the place declares of the class head reads: the response comes back once, typed by the world,
|
|
4
|
-
* and both are kept — the body's digest and the typed answer's, receipts the device key signs. Asked again as the same
|
|
5
|
-
* principal, while the place stands on what typed it, the person's grant holds now and the newest read is younger than
|
|
6
|
-
* the reuse the place allows, it answers from what was kept: nothing called. Asked for history, it shows what was kept
|
|
7
|
-
* of that request as of when it was read — evidence of that read, never a current answer — where the place allows it.
|
|
8
|
-
*/
|
|
2
|
+
/** Read one permitted offer, reuse only under its policy and land the signed receipt. */
|
|
9
3
|
export declare const readSource: (asked: Asked, context: Context) => Promise<readonly string[]>;
|
package/dist/src/flows/read.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { under } from "../core/lock.js";
|
|
2
2
|
import { observedAt, planRead } from "../core/read.js";
|
|
3
|
-
import { basisOf, draftOf, receiptsOf, scopeOf
|
|
3
|
+
import { basisOf, draftOf, receiptsOf, scopeOf } from "../core/receipt.js";
|
|
4
4
|
import { redIn, sayIn } from "../core/surface.js";
|
|
5
5
|
import { grantedOf, isOpen, untilOf } from "../core/act.js";
|
|
6
6
|
import { canonical } from '@lapxo/topos/wire';
|
|
@@ -8,6 +8,8 @@ import { called } from "./call.js";
|
|
|
8
8
|
import { principalOf } from "../access/obtain.js";
|
|
9
9
|
import { jsonOf } from "../access/contract.js";
|
|
10
10
|
import { refusedAccess } from "../sources/refused.js";
|
|
11
|
+
import { hand, keptOf } from "../kept/kept.js";
|
|
12
|
+
import { markOf, overOf } from "../ahead/budget.js";
|
|
11
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. */
|
|
12
14
|
const closeWith = async (intent, source, digest, { word, lines, lands }, context) => {
|
|
13
15
|
const say = sayIn(context.lock);
|
|
@@ -18,19 +20,13 @@ const closeWith = async (intent, source, digest, { word, lines, lands }, context
|
|
|
18
20
|
const landed = await lands(draftOf('', word('receipt/at'), `${intent}/${word('act/closed')}`, digest, new Date(context.now()).toISOString()));
|
|
19
21
|
return 'refused' in landed ? [say('not-landed', { why: landed.refused })] : [...landed.lines, say('closed', { intent, digest })];
|
|
20
22
|
};
|
|
21
|
-
/**
|
|
22
|
-
* A read of one offer the place declares of the class head reads: the response comes back once, typed by the world,
|
|
23
|
-
* and both are kept — the body's digest and the typed answer's, receipts the device key signs. Asked again as the same
|
|
24
|
-
* principal, while the place stands on what typed it, the person's grant holds now and the newest read is younger than
|
|
25
|
-
* the reuse the place allows, it answers from what was kept: nothing called. Asked for history, it shows what was kept
|
|
26
|
-
* of that request as of when it was read — evidence of that read, never a current answer — where the place allows it.
|
|
27
|
-
*/
|
|
23
|
+
/** Read one permitted offer, reuse only under its policy and land the signed receipt. */
|
|
28
24
|
export const readSource = (asked, context) => called(asked, context, planRead, async ({ word, say, place, lines, source, operation, plan, prepared, most, typed, attach, send, lands, widen, uncovered }) => {
|
|
29
25
|
const [family, at, when] = [word('receipt/family'), word('receipt/at'), new Date(context.now()).toISOString()];
|
|
30
26
|
const closing = (digest) => closeWith(asked['closes'], source, digest, { word, lines, lands }, context);
|
|
31
27
|
const scopes = (principal) => [scopeOf(family, source, operation, prepared.key, principal), `${scopeOf(word('receipt/kept'), source, operation, prepared.key, principal)}/${basisOf(lines, word, source)}`];
|
|
32
28
|
const held = (digest) => ((bytes) => (bytes === undefined || digest === undefined ? undefined : { digest, bytes }))(digest === undefined ? undefined : context.cache.held(digest));
|
|
33
|
-
const keeping = (kept, answer) => (answer.length > 1 ? [draftOf(word('receipt/kept'), at, kept, context.cache.keep(Buffer.from(answer.join('\n'))), when)] : []);
|
|
29
|
+
const keeping = (kept, answer) => (answer.length > 1 ? [draftOf(word('receipt/kept'), at, kept, (asked['ahead'] === 'true' ? String : hand)(context.cache.keep(Buffer.from(answer.join('\n')))), when)] : []);
|
|
34
30
|
const allowed = (key, measure) => (lines.find((l) => l.scope === `${word(key)}/${source}` && l.measure === measure) ?? lines.find((l) => l.scope === word(key) && l.measure === measure))?.value;
|
|
35
31
|
if (asked['why'] === 'true') {
|
|
36
32
|
const asOf = principalOf(context, plan, false), [newest] = asOf === undefined ? [] : receiptsOf(lines, at, scopes(asOf)[0]), pin = lines.find((l) => l.scope === `uses/${source}`);
|
|
@@ -54,38 +50,37 @@ export const readSource = (asked, context) => called(asked, context, planRead, a
|
|
|
54
50
|
if (stopped)
|
|
55
51
|
return stopped;
|
|
56
52
|
if (!grantedOf(lines, word('grant/family'), source, context.now())) {
|
|
53
|
+
if (asked['ahead'] === 'true')
|
|
54
|
+
return [];
|
|
57
55
|
const sources = [...new Set(lines.filter((l) => l.scope?.startsWith(`${word('vocab/region')}/`) && l.measure === word('vocab/origins')).map((l) => l.scope.split('/')[1]))].sort();
|
|
58
56
|
const until = untilOf(word('access/period'), context.now());
|
|
59
|
-
const line = (measure, value) => canonical({ at:
|
|
60
|
-
const
|
|
57
|
+
const line = (measure, value) => canonical({ at: `policy:person/grants/${new Date(context.now()).toISOString()}`, by: 'target', form: 'alphabet', measure, role: 'writes', scope: word('grant/family'), value });
|
|
58
|
+
const before = [...new Set(lines.filter((l) => l.scope === word('grant/family')).map(({ about: _about, sig: _sig, epoch: _epoch, ...l }) => canonical({ ...l, by: 'target', value: 'withdraw' })))];
|
|
59
|
+
const granted = await widen(say('grant', { sources: sources.join(', '), place, until })[0], [...before, line('sources', sources.join('|')), line('until', until)]);
|
|
61
60
|
if ('said' in granted)
|
|
62
61
|
return granted.said;
|
|
63
62
|
}
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (body !== undefined) {
|
|
73
|
-
const cut = body.bytes.indexOf(10);
|
|
74
|
-
const answer = await typed(Buffer.from(body.bytes.subarray(0, cut)).toString('utf8'), body.bytes.subarray(cut + 1));
|
|
75
|
-
const keptLines = keeping(kept, answer);
|
|
76
|
-
const landed = keptLines.length ? await lands(...keptLines) : { lines: [] };
|
|
77
|
-
return [...answer, ...say('reread', { scope, digest: body.digest, at: newest.at }), ...('lines' in landed ? landed.lines : []), ...(await closing(body.digest))];
|
|
78
|
-
}
|
|
63
|
+
const was = principal === undefined ? undefined : keptOf(lines, at, scopes(principal), grantedOf(lines, word('grant/family'), source, context.now()), allowed('reuse/family', 'seconds'), context.now(), held);
|
|
64
|
+
if (was?.kind === 'kept')
|
|
65
|
+
return [...(was.bytes ? [...new TextDecoder().decode(was.bytes).split('\n'), ...say('reused', { scope: scopes(principal)[1], digest: hand(was.digest), at: was.at, receipt: was.receipt })] : say('kept', { source, operation, at: was.at, digest: was.digest, receipt: was.receipt })),
|
|
66
|
+
...(await closing(was.receipt)), ...redIn(context.lock)('answer/kept', `answer/${source}/${operation}`, context.begun, context.machine())];
|
|
67
|
+
if (was?.kind === 'reread') {
|
|
68
|
+
const answer = await typed(Buffer.from(was.bytes.subarray(0, was.bytes.indexOf(10))).toString('utf8'), was.bytes.subarray(was.bytes.indexOf(10) + 1)), keptLines = keeping(scopes(principal)[1], answer);
|
|
69
|
+
const landed = keptLines.length ? await lands(...keptLines) : { lines: [] };
|
|
70
|
+
return [...answer, ...say('reread', { scope: scopes(principal)[0], digest: was.digest, at: was.at, receipt: was.digest }), ...('lines' in landed ? landed.lines : []), ...(await closing(was.digest))];
|
|
79
71
|
}
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
const again = was?.kind === 'again' ? say(`kept-${was.why}`, { source, operation, at: was.at }) : [];
|
|
73
|
+
if (overOf(lines, [family, at, word('ahead/mark')], source, context.now(), most('read/rate'), Number(word('ahead/share').split('..')[1]), asked))
|
|
74
|
+
return asked['ahead'] === 'true' ? [] : say('rate', { source });
|
|
82
75
|
const attached = await attach();
|
|
83
76
|
if ('said' in attached)
|
|
84
77
|
return attached.said;
|
|
85
78
|
const [scope, kept] = scopes(attached.principal ?? '');
|
|
86
79
|
const got = await send(attached.attachment);
|
|
80
|
+
if (asked['ahead'] === 'true' && (got.kind !== 'got' || got.status >= 300))
|
|
81
|
+
return [];
|
|
87
82
|
if (got.kind !== 'got')
|
|
88
|
-
return say(got.kind === 'over' ? 'too-large' : 'unreachable-source', { source });
|
|
83
|
+
return say(got.kind === 'over' ? 'too-large' : 'unreachable-source', { source, why: got.kind === 'failed' ? got.why : '' });
|
|
89
84
|
if (got.status === 401 && attached.principal !== 'anonymous')
|
|
90
85
|
return refusedAccess(context, place, lines, plan, operation);
|
|
91
86
|
const digest = context.cache.keep(Buffer.concat([Buffer.from(`${got.status}\n`), got.bytes]));
|
|
@@ -93,6 +88,6 @@ export const readSource = (asked, context) => called(asked, context, planRead, a
|
|
|
93
88
|
const observed = plan.access.probe && got.status < 300 ? [canonical({ at: `${at}:${when}`, by: 'target', form: 'alphabet', measure: word('vocab/access'), role: 'writes', scope: observedAt(context.lock, source, operation), value: word('vocab/anonymous') })] : [];
|
|
94
89
|
const found = plan.access.probe && got.status === 401 && got.authenticate ? await (await context.access(word('access/adapter')))?.discover?.(plan.origin, got.authenticate, async (url) => jsonOf((await context.files.read.documentOf(context, place, url))?.bytes ?? new Uint8Array())) : undefined;
|
|
95
90
|
const by = `${word('access/family')}/${source}/${word('access/authorizer')}`, authorizer = Object.entries(found ?? {}).flatMap(([k, f]) => Object.entries(f).map(([measure, all]) => canonical({ at: `${at}:${when}`, by: 'target', form: 'alphabet', measure, role: 'writes', scope: k ? `${by}/${k}` : by, value: all.join('|') })));
|
|
96
|
-
const landed = await lands(draftOf(family, at, scope, digest, when), ...(attached.principal === undefined ? [] : keeping(kept, answer)), ...observed, ...authorizer);
|
|
97
|
-
return [...answer, ...('lines' in landed ? [...landed.lines, ...(await closing(digest))] : say('not-landed', { why: landed.refused })), ...(found?.['']?.['issuer'] && 'lines' in landed ? say('authorizer-observed', { source, issuer: found['']['issuer'][0] }) : [])];
|
|
91
|
+
const landed = await lands(draftOf(family, at, scope, digest, when), ...markOf(asked, [at, word('ahead/mark')], scope, digest, when), ...(attached.principal === undefined ? [] : keeping(kept, answer)), ...observed, ...authorizer);
|
|
92
|
+
return [...again, ...answer, ...('lines' in landed ? [...landed.lines, ...(await closing(digest))] : say('not-landed', { why: landed.refused })), ...(found?.['']?.['issuer'] && 'lines' in landed ? say('authorizer-observed', { source, issuer: found['']['issuer'][0] }) : [])];
|
|
98
93
|
});
|
package/dist/src/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import { parse } from '@lapxo/topos/wire';
|
|
|
17
17
|
import { aboutOf, valueOf } from "./core/lock.js";
|
|
18
18
|
import { fill, sayIn } from "./core/surface.js";
|
|
19
19
|
import { matchTemplate, refusedOn, shapeOf, shapeSchema, surfaceOf } from "./ports/surface.js";
|
|
20
|
-
import { closing, cooled, founded, land, landAct, limits, pinnedAt, policyAt, run, settledView, sign, standingAt, told, warm, words } from "./ports/bound.js";
|
|
20
|
+
import { birth, closing, cooled, founded, land, landAct, limits, pinnedAt, policyAt, run, settledView, sign, standingAt, told, warm, words } from "./ports/bound.js";
|
|
21
21
|
import { warmth } from "./ports/warm.js";
|
|
22
22
|
import { held, keep } from "./ports/cache.js";
|
|
23
23
|
import { registryFor } from "./ports/signers.js";
|
|
@@ -27,18 +27,26 @@ import { keychain } from "./ports/keychain.js";
|
|
|
27
27
|
import { algorithmOf, deviceOf, idIn } from "./core/device.js";
|
|
28
28
|
import { publicOf } from "./ports/keys.js";
|
|
29
29
|
import { personOf } from "./ports/access/person.js";
|
|
30
|
+
import { readAhead } from "./ahead/read.js";
|
|
30
31
|
const [ext, root] = ((self) => [self.slice(self.lastIndexOf('.')), fileURLToPath(new URL(self.endsWith('.ts') ? '..' : '../..', self))])(import.meta.url);
|
|
31
32
|
const lock = lockAt(root);
|
|
33
|
+
if (process.argv[2] !== valueOf(lock, 'cli/run')) {
|
|
34
|
+
process.stdout.write(`${aboutOf(lock, 'cli/help')}\n`);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
32
37
|
const surface = surfaceOf(lock);
|
|
33
38
|
const runs = valueOf(lock, 'runs');
|
|
34
|
-
Object.assign(words, Object.fromEntries(['read', 'view', 'sign', 'land', 'key', 'signer', 'pin', 'topos', 'standing', 'evidence', 'act', 'profile', 'receipts', 'refusal'].map((w) => [w, valueOf(lock, `bound/${w}`)])));
|
|
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}`)])));
|
|
35
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') });
|
|
36
42
|
Object.assign(warmth, { spares: Number(valueOf(lock, 'bound/spares').split('..')[1]), boot: valueOf(lock, 'bound/spare-env').split('|') });
|
|
37
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. */
|
|
38
|
-
let last = Promise.resolve();
|
|
44
|
+
let [last, ahead] = [Promise.resolve(), new AbortController()];
|
|
39
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)));
|
|
40
48
|
const offered = (n) => (n ? { listChanged: false } : undefined);
|
|
41
|
-
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) } });
|
|
49
|
+
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') });
|
|
42
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. */
|
|
43
51
|
let waited = 0;
|
|
44
52
|
const timed = (ask) => async (...args) => { const t = Date.now(); try {
|
|
@@ -103,7 +111,7 @@ const contextOf = (signal) => {
|
|
|
103
111
|
begun: Date.now() - waited,
|
|
104
112
|
keys,
|
|
105
113
|
device,
|
|
106
|
-
roots: async () => (server.getClientCapabilities()?.roots ? (await server.listRoots()).roots.flatMap((root) => folderOf(root.uri) ?? []) : []),
|
|
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()),
|
|
107
115
|
};
|
|
108
116
|
};
|
|
109
117
|
const askedOf = (raw) => Object.fromEntries(Object.entries(raw ?? {}).map(([k, v]) => [k, v === undefined ? undefined : typeof v === 'object' ? JSON.stringify(v) : String(v)]));
|
|
@@ -113,14 +121,15 @@ if (surface.tools.length)
|
|
|
113
121
|
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: surface.tools.map((tool) => ({ name: tool.name, description: tool.about,
|
|
114
122
|
inputSchema: schemaOf([...tool.fields]), outputSchema: shapeSchema(lock), annotations: Object.fromEntries(tool.hints.map((hint) => [`${hint}Hint`, true])) })) }));
|
|
115
123
|
if (surface.tools.length)
|
|
116
|
-
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => turn(async () => {
|
|
124
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => (ahead.abort(), turn(async () => {
|
|
117
125
|
const tool = surface.tools.find((one) => one.name === request.params.name);
|
|
118
126
|
if (tool === undefined)
|
|
119
127
|
return { isError: true, content: [] };
|
|
120
128
|
const refused = refusedOn(lock, process.platform);
|
|
121
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());
|
|
122
131
|
return { content: [{ type: 'text', text: answer.join('\n') }], structuredContent: shapeOf(answer) };
|
|
123
|
-
}));
|
|
132
|
+
})));
|
|
124
133
|
if (surface.resources.length)
|
|
125
134
|
server.setRequestHandler(ListResourcesRequestSchema, () => ({ resources: [] }));
|
|
126
135
|
if (surface.resources.length)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Said } from '../core/lock.ts';
|
|
2
|
+
export declare const hand: (digest: string) => string;
|
|
3
|
+
type Held = (digest: string | undefined) => {
|
|
4
|
+
readonly digest: string;
|
|
5
|
+
readonly bytes: Uint8Array;
|
|
6
|
+
} | undefined;
|
|
7
|
+
export type Was = {
|
|
8
|
+
readonly kind: 'kept';
|
|
9
|
+
readonly at: string;
|
|
10
|
+
readonly digest: string;
|
|
11
|
+
readonly receipt: string;
|
|
12
|
+
readonly bytes?: Uint8Array;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: 'reread';
|
|
15
|
+
readonly at: string;
|
|
16
|
+
readonly digest: string;
|
|
17
|
+
readonly bytes: Uint8Array;
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: 'again';
|
|
20
|
+
readonly at: string;
|
|
21
|
+
readonly why: 'withdrawn' | 'changed' | 'expired';
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* A question read before, as its receipts and the place stand now. Where the place declares a reuse and the newest read
|
|
25
|
+
* stood, it is kept — its bytes only where this conversation does not hold them yet — or typed again from its receipt if
|
|
26
|
+
* nothing typed was kept; but read again, and why, where the person's grant had to be given again, the source is pinned
|
|
27
|
+
* otherwise than when it was read, or the read is older than the reuse allows. Never read, or no reuse: nothing is said.
|
|
28
|
+
*/
|
|
29
|
+
export declare const keptOf: (lines: readonly Said[], at: string, [read, kept]: readonly [string, string], granted: boolean, reuse: string | undefined, now: number, held: Held) => Was | undefined;
|
|
30
|
+
export {};
|