@lapxo/head 0.1.3 → 0.1.5
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 +24 -0
- package/TARGET.bound +317 -53
- package/dist/src/access/obtain.d.ts +7 -1
- package/dist/src/access/obtain.js +4 -4
- 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/claims/claim.d.ts +9 -0
- package/dist/src/claims/claim.js +40 -0
- package/dist/src/core/answer.d.ts +3 -0
- package/dist/src/core/package.js +4 -3
- package/dist/src/core/read.js +1 -1
- package/dist/src/flows/call.d.ts +1 -3
- package/dist/src/flows/call.js +4 -7
- package/dist/src/flows/read.js +29 -25
- package/dist/src/index.js +23 -14
- package/dist/src/kept/kept.d.ts +30 -0
- package/dist/src/kept/kept.js +20 -0
- package/dist/src/links/inspect.d.ts +8 -0
- package/dist/src/links/inspect.js +41 -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.d.ts +5 -3
- package/dist/src/onboard/first.js +25 -8
- package/dist/src/onboard/owed.d.ts +10 -0
- package/dist/src/onboard/owed.js +28 -0
- package/dist/src/onboard/site.d.ts +12 -0
- package/dist/src/onboard/site.js +22 -0
- package/dist/src/ports/bound.d.ts +1 -0
- package/dist/src/ports/bound.js +15 -3
- 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/surface.d.ts +5 -1
- package/dist/src/ports/surface.js +7 -5
- package/dist/src/ports/warm.js +1 -2
- package/dist/src/sources/connect.d.ts +8 -0
- package/dist/src/sources/connect.js +18 -47
- package/dist/src/sources/refused.d.ts +7 -0
- package/dist/src/sources/refused.js +16 -0
- package/dist/src/tools/ask.d.ts +1 -0
- package/dist/src/tools/ask.js +1 -0
- package/dist/src/tools/card.d.ts +1 -1
- package/dist/src/tools/card.js +6 -5
- package/dist/src/tools/claim.d.ts +1 -0
- package/dist/src/tools/claim.js +1 -0
- package/dist/src/tools/do.d.ts +1 -0
- package/dist/src/tools/do.js +1 -0
- package/dist/src/tools/look.d.ts +5 -4
- package/dist/src/tools/look.js +14 -13
- package/dist/src/verbs/ask.d.ts +11 -0
- package/dist/src/verbs/ask.js +36 -0
- package/dist/src/verbs/card.d.ts +11 -0
- package/dist/src/verbs/card.js +16 -0
- package/dist/src/verbs/do.d.ts +8 -0
- package/dist/src/verbs/do.js +28 -0
- package/package.json +3 -3
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { decodeReference } from '@lapxo/bound/reference';
|
|
2
|
+
import { canonical } from '@lapxo/topos/wire';
|
|
3
|
+
import { inPlace, standingOr } from "../core/answer.js";
|
|
4
|
+
import { members, valueOf } from "../core/lock.js";
|
|
5
|
+
import { sayIn } from "../core/surface.js";
|
|
6
|
+
import { linesOf } from "../core/worlds.js";
|
|
7
|
+
import { rootIn } from "../flows/device.js";
|
|
8
|
+
/**
|
|
9
|
+
* A #bound: link connected: inspected by the instrument in the person's place, never authenticated — its bytes are the
|
|
10
|
+
* link's, and nothing shows who shared it — and what it names is shown; nothing is connected or granted, since a package
|
|
11
|
+
* is received by the digest its sharer sent apart. A place that admits no content transport is handed the lines that admit
|
|
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
|
+
*/
|
|
14
|
+
export const linked = (asked, context) => inPlace(asked, context, async (place) => {
|
|
15
|
+
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'))];
|
|
16
|
+
if ('said' in held)
|
|
17
|
+
return held.said;
|
|
18
|
+
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' }));
|
|
19
|
+
let reference;
|
|
20
|
+
try {
|
|
21
|
+
reference = decodeReference(link);
|
|
22
|
+
}
|
|
23
|
+
catch (refused) {
|
|
24
|
+
return [say('link-refused', { link, why: refused instanceof Error ? refused.message : String(refused) })];
|
|
25
|
+
}
|
|
26
|
+
if (![admits[0], `transport/${new URL(reference.source).protocol.slice(0, -1)}`].every((s) => held.lines.some((l) => l.scope === s)))
|
|
27
|
+
return [say('transport-missing', { place, lines: lines.join('\n') })];
|
|
28
|
+
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)}`];
|
|
29
|
+
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
|
+
if ('said' in root)
|
|
31
|
+
return root.said;
|
|
32
|
+
if (printed?.code !== 0 && !why.includes(word('link/unarrived')))
|
|
33
|
+
return [say('link-refused', { link, why })];
|
|
34
|
+
const settled = (lot) => (lot.length ? context.landRoot(place, lot, root.account).then((got) => ('refused' in got ? say('not-landed', { why: got.refused }) : got.lines)) : Promise.resolve([]));
|
|
35
|
+
const due = { at: 'policy:head/link', by: 'target', form: 'alphabet', measure: 'id', role: 'demands', scope, value: link };
|
|
36
|
+
if (printed?.code !== 0)
|
|
37
|
+
return [say('link-owed', { place, source: reference.source }), ...await settled(owed ? [] : [canonical(due)])];
|
|
38
|
+
const named = linesOf(new TextEncoder().encode(printed.out.filter((l) => l.startsWith('TOPOS ')).map((l) => l.slice('TOPOS '.length)).join('\n')));
|
|
39
|
+
const sources = named.filter((l) => /^sources\/[^/]+$/.test(l.scope ?? '')).map((l) => l.scope.split('/')[1]);
|
|
40
|
+
return [say('inspected', { source: reference.source, sources: sources.join(', ') || '—', digest: reference.digest }), say('sharer-unverified', {}), ...await settled(owed ? [canonical({ ...due, value: 'withdraw' })] : [])];
|
|
41
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Context } from '../core/answer.ts';
|
|
2
|
+
/**
|
|
3
|
+
* A package's link: its lines as one standing document, laid in its folder as it is to be published — the link names that
|
|
4
|
+
* document's bytes, never the package's files — and the #bound: link to it at the address the person publishes it at.
|
|
5
|
+
* With no address there is no link, said so: a link needs where its bytes are published, and the digest still goes apart.
|
|
6
|
+
*/
|
|
7
|
+
export declare const linkOf: (context: Context, to: string, lines: readonly string[], urls: Readonly<Record<string, string>>) => readonly string[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { encodeReference } from '@lapxo/bound/reference';
|
|
4
|
+
import { semanticStandingBytes } from '@lapxo/topos/standing';
|
|
5
|
+
import { members, valueOf } from "../core/lock.js";
|
|
6
|
+
import { sayIn } from "../core/surface.js";
|
|
7
|
+
const sha = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
8
|
+
/**
|
|
9
|
+
* A package's link: its lines as one standing document, laid in its folder as it is to be published — the link names that
|
|
10
|
+
* document's bytes, never the package's files — and the #bound: link to it at the address the person publishes it at.
|
|
11
|
+
* With no address there is no link, said so: a link needs where its bytes are published, and the digest still goes apart.
|
|
12
|
+
*/
|
|
13
|
+
export const linkOf = (context, to, lines, urls) => {
|
|
14
|
+
const say = sayIn(context.lock), [name, profile, [kind, algorithm]] = [valueOf(context.lock, 'link/document'), valueOf(context.lock, 'link/profile'), members(valueOf(context.lock, 'link/reference'))];
|
|
15
|
+
const standing = semanticStandingBytes(lines), bytes = new TextEncoder().encode(JSON.stringify({ profile, standing })), at = urls[name], document = join(to, name);
|
|
16
|
+
context.files.lay(to, name, bytes);
|
|
17
|
+
if (at === undefined || !URL.canParse(at))
|
|
18
|
+
return [say('shared-unlinked', { document })];
|
|
19
|
+
try {
|
|
20
|
+
return [say('shared-link', { document, at, link: encodeReference({ class: kind, algorithm, digest: sha(standing), blob: `${algorithm}:${sha(bytes)}`, source: new URL(at).href }) })];
|
|
21
|
+
}
|
|
22
|
+
catch (refused) {
|
|
23
|
+
return [say('link-refused', { link: at, why: refused instanceof Error ? refused.message : String(refused) })];
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Asked, Context } from '../core/answer.ts';
|
|
2
|
+
/**
|
|
3
|
+
* One question asked of several sources at once — the same operation, the same arguments — and their typed answers
|
|
4
|
+
* compared field by field. Where they agree, the field is marked by the sources that say it, each mark resting on the
|
|
5
|
+
* receipt of its read: ● for each its own, ○ for one that is the same source in another form or declared to share its
|
|
6
|
+
* lineage, with what the place declares of that lineage and nothing more. Where they differ, each is shown with its source
|
|
7
|
+
* and hour, and the loss between them is not measured. None is ever picked.
|
|
8
|
+
*/
|
|
9
|
+
export declare const compared: (asked: Asked, context: Context, place: string) => Promise<readonly string[]>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { standingOr } from "../core/answer.js";
|
|
3
|
+
import { members, valueOf } from "../core/lock.js";
|
|
4
|
+
import { sayIn } from "../core/surface.js";
|
|
5
|
+
import { linesOf } from "../core/worlds.js";
|
|
6
|
+
import { readSource } from "../flows/read.js";
|
|
7
|
+
import { shapeOf } from "../ports/surface.js";
|
|
8
|
+
/** What one source answered the question: its typed fields by name, the hour it said them, and the receipt the read rests on; or what it said instead. */
|
|
9
|
+
const readOf = async (asked, context, place, source) => {
|
|
10
|
+
const [word, raw] = [(k) => valueOf(context.lock, k), await readSource({ ...asked, sources: undefined, source, place }, context)], got = shapeOf(raw);
|
|
11
|
+
const cell = got.cells.filter((c) => ['head/answered', 'head/reused', 'head/kept', 'head/reread'].includes(c.code)).at(-1), family = `${word('receipt/family')}/${source}/`;
|
|
12
|
+
const receipt = got.lines.map((l) => linesOf(new TextEncoder().encode(l))[0]).find((l) => l?.scope?.startsWith(family) && l.measure === 'digest')?.value ?? cell?.fields['receipt'];
|
|
13
|
+
const typed = cell?.code === 'head/kept' ? new TextDecoder().decode(context.cache.held(cell.fields['digest'] ?? '') ?? new Uint8Array()).split('\n') : got.lines;
|
|
14
|
+
const fields = new Map(linesOf(new TextEncoder().encode(typed.join('\n'))).flatMap((l) => ((m) => (m ? [[m[1], l.value ?? '']] : []))(new RegExp(`^${word('vocab/answer')}/[0-9a-f]{16}/(.+)$`).exec(l.scope ?? ''))));
|
|
15
|
+
return cell === undefined || receipt === undefined || !fields.size ? { said: raw.filter((l) => l.startsWith('said/')) } : { source, at: cell.fields['at'] ?? '', receipt, fields };
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* One question asked of several sources at once — the same operation, the same arguments — and their typed answers
|
|
19
|
+
* compared field by field. Where they agree, the field is marked by the sources that say it, each mark resting on the
|
|
20
|
+
* receipt of its read: ● for each its own, ○ for one that is the same source in another form or declared to share its
|
|
21
|
+
* lineage, with what the place declares of that lineage and nothing more. Where they differ, each is shown with its source
|
|
22
|
+
* and hour, and the loss between them is not measured. None is ever picked.
|
|
23
|
+
*/
|
|
24
|
+
export const compared = async (asked, context, place) => {
|
|
25
|
+
const [say, word, held] = [sayIn(context.lock), (k) => valueOf(context.lock, k), await standingOr(context, place)], [own, mark] = [members(word('marks/own-signers')), members(word('marks/mark'))];
|
|
26
|
+
if ('said' in held)
|
|
27
|
+
return held.said;
|
|
28
|
+
const got = [];
|
|
29
|
+
for (const source of members(asked['sources'] ?? ''))
|
|
30
|
+
got.push(await readOf(asked, context, place, source));
|
|
31
|
+
const reads = got.filter((g) => 'fields' in g), line = (scope, measure) => held.lines.find((l) => l.scope === scope && (!measure || l.measure === measure));
|
|
32
|
+
const pinned = (s) => linesOf(context.files.take(join(place, line(`uses/${s}`)?.needs ?? '')) ?? new Uint8Array()).filter((l) => l.scope === `${word('vocab/region')}/${s}`);
|
|
33
|
+
const origin = (s) => ((region) => ({ origins: members(region.find((l) => l.measure === word('vocab/origins'))?.value ?? ''), pin: region.find((l) => l.measure === word('vocab/documents'))?.value, by: ((b) => (b && !own.includes(b) ? b : undefined))(region.find((l) => l.by)?.by) }))(pinned(s));
|
|
34
|
+
const same = (a, b) => ((x, y) => x.origins.some((o) => y.origins.includes(o)) || (x.pin !== undefined && x.pin === y.pin) || (x.by !== undefined && x.by === y.by))(origin(a), origin(b));
|
|
35
|
+
const shares = (s) => ((l) => (l === undefined ? undefined : members(l.value ?? '')))(line(`${word('marks/shares')}/${s}`));
|
|
36
|
+
const kin = (s, earlier) => earlier.find((e) => same(s, e.source) || shares(s)?.includes(e.source) || shares(e.source)?.includes(s));
|
|
37
|
+
return [...got.flatMap((g) => ('said' in g ? g.said : [])), ...[...new Set(reads.flatMap((r) => [...r.fields.keys()]))].flatMap((field) => {
|
|
38
|
+
const values = reads.filter((r) => r.fields.has(field));
|
|
39
|
+
if (new Set(values.map((r) => r.fields.get(field))).size > 1)
|
|
40
|
+
return [...values.map((r) => say('forked', { field, source: r.source, value: r.fields.get(field), at: r.at })), say('unmeasured', { what: `${word('marks/loss')} ${values.map((r) => r.source).join(', ')}`, needs: word('marks/loss-needs') })];
|
|
41
|
+
if (values.length < 2 || values.length < reads.length)
|
|
42
|
+
return values.map((r) => say('partial', { field, source: r.source, value: r.fields.get(field), at: r.at }));
|
|
43
|
+
const kinOf = values.map((r, i) => kin(r.source, values.slice(0, i))), first = kinOf.findIndex((k) => k !== undefined);
|
|
44
|
+
const base = { field, value: values[0].fields.get(field), sources: values.map((r) => r.source).join(', '), marks: kinOf.map((k) => (k ? mark[1] : mark[0])).join(''), receipts: values.map((r) => r.receipt).join('|') };
|
|
45
|
+
return [first >= 0 ? say('agreed-lineage', { ...base, source: values[first].source, other: kinOf[first].source })
|
|
46
|
+
: values.every((r) => line(`${word('marks/shares')}/${r.source}`)?.value === word('marks/undeclared')) ? say('agreed-undeclared', base) : say('agreed-unknown', base)];
|
|
47
|
+
})];
|
|
48
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type Asked, type Context } from '../core/answer.ts';
|
|
2
2
|
/**
|
|
3
|
-
* The first source a person connects with no place named and none to read from
|
|
4
|
-
* the lock names under their own home,
|
|
5
|
-
*
|
|
3
|
+
* The first source a person connects with no place named and none to read from, on one yes that says all it grants:
|
|
4
|
+
* their place founded in the folder the lock names under their own home, the source connected to it, and reads of it
|
|
5
|
+
* granted until the access period ends. A document that does not arrive is no refusal: the place is founded owing it
|
|
6
|
+
* by its address. Either way the question asked comes back as what to ask next. A folder that already holds a place is
|
|
7
|
+
* used as it stands, never founded again, and its connect asks its own yes.
|
|
6
8
|
*/
|
|
7
9
|
export declare const firstRun: (asked: Asked, context: Context, then: (place: string, covered: boolean) => Promise<readonly string[]>) => Promise<readonly string[]>;
|
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
import { basename, join } from 'node:path';
|
|
2
|
-
import { LOCK } from '@lapxo/topos/wire';
|
|
2
|
+
import { canonical, LOCK } from '@lapxo/topos/wire';
|
|
3
3
|
import { unless } from "../core/answer.js";
|
|
4
|
+
import { untilOf } from "../core/act.js";
|
|
4
5
|
import { valueOf } from "../core/lock.js";
|
|
5
6
|
import { sayIn } from "../core/surface.js";
|
|
7
|
+
import { rootIn } from "../flows/device.js";
|
|
6
8
|
import { bornAt } from "../places/found.js";
|
|
9
|
+
import { attachedFor } from "../sources/access.js";
|
|
7
10
|
/**
|
|
8
|
-
* The first source a person connects with no place named and none to read from
|
|
9
|
-
* the lock names under their own home,
|
|
10
|
-
*
|
|
11
|
+
* The first source a person connects with no place named and none to read from, on one yes that says all it grants:
|
|
12
|
+
* their place founded in the folder the lock names under their own home, the source connected to it, and reads of it
|
|
13
|
+
* granted until the access period ends. A document that does not arrive is no refusal: the place is founded owing it
|
|
14
|
+
* by its address. Either way the question asked comes back as what to ask next. A folder that already holds a place is
|
|
15
|
+
* used as it stands, never founded again, and its connect asks its own yes.
|
|
11
16
|
*/
|
|
12
17
|
export const firstRun = async (asked, context, then) => {
|
|
13
|
-
const say = sayIn(context.lock), dir = join(context.personal(),
|
|
18
|
+
const say = sayIn(context.lock), word = (k) => valueOf(context.lock, k), dir = join(context.personal(), word('first-run/folder')), name = basename(dir);
|
|
14
19
|
if (context.files.has(dir, LOCK))
|
|
15
20
|
return then(dir, false);
|
|
16
|
-
const
|
|
21
|
+
const [source, document, until] = [(asked['source'] ?? '').toLowerCase().replace(/[^a-z0-9-]+/g, '-'), asked['document'] ?? '', untilOf(word('access/period'), context.now())];
|
|
22
|
+
const refused = await unless(context, say('elicit-first-run', { dir, source, document, until }));
|
|
17
23
|
if (refused)
|
|
18
24
|
return refused;
|
|
19
|
-
const born = await bornAt(context, dir, name);
|
|
20
|
-
|
|
25
|
+
const born = await bornAt(context, dir, name), root = rootIn(context, dir);
|
|
26
|
+
if (born[0] !== say('born', { name, dir }) || 'said' in root)
|
|
27
|
+
return 'said' in root ? [...born, ...root.said] : born;
|
|
28
|
+
const connected = await then(dir, true), unread = connected.some((l) => l.startsWith('said/head/document-unread ')), held = unread ? undefined : await context.policy(dir);
|
|
29
|
+
if (held !== undefined && ('refused' in held || !held.lines.some((l) => l.scope === `uses/${source}`)))
|
|
30
|
+
return [...born, ...connected];
|
|
31
|
+
const line = (scope, measure, value, role = 'writes') => canonical({ at: 'policy:head/first-run', by: 'target', form: 'alphabet', measure, role, scope, value });
|
|
32
|
+
const owed = unread ? [line(`${word('place/needs')}/bytes/${source}`, 'id', document, 'demands')] : [];
|
|
33
|
+
const landed = await context.landRoot(dir, [line(word('grant/family'), 'sources', source), line(word('grant/family'), 'until', until), ...owed], root.account);
|
|
34
|
+
if ('refused' in landed)
|
|
35
|
+
return [...born, ...connected, say('not-landed', { why: landed.refused })];
|
|
36
|
+
const access = unread ? undefined : await attachedFor(context, dir, source);
|
|
37
|
+
return [...born, ...(unread ? [] : connected), ...landed.lines, ...(access !== undefined && 'said' in access ? access.said : []), say(unread ? 'bytes-owed' : 'ready', { source, place: dir, document, operation: asked['operation'], args: asked['args'] })];
|
|
21
38
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Asked, type Context } from '../core/answer.ts';
|
|
2
|
+
/**
|
|
3
|
+
* What a place founded on a first connect still owes: its source's document, by the address the person gave. Asked again,
|
|
4
|
+
* it is read there; once it arrives, the source is connected from those bytes and pinned by their digest, the demand is
|
|
5
|
+
* withdrawn and the question is answered. Until then it is not: what is owed is said, and nothing else is asked.
|
|
6
|
+
*/
|
|
7
|
+
export declare const paidIn: (asked: Asked, context: Context, place: string) => Promise<{
|
|
8
|
+
readonly said: readonly string[];
|
|
9
|
+
readonly answer: boolean;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { canonical } from '@lapxo/topos/wire';
|
|
2
|
+
import { standingOr } from "../core/answer.js";
|
|
3
|
+
import { valueOf } from "../core/lock.js";
|
|
4
|
+
import { sayIn } from "../core/surface.js";
|
|
5
|
+
import { rootIn } from "../flows/device.js";
|
|
6
|
+
import { laidIn } from "../sources/connect.js";
|
|
7
|
+
/**
|
|
8
|
+
* What a place founded on a first connect still owes: its source's document, by the address the person gave. Asked again,
|
|
9
|
+
* it is read there; once it arrives, the source is connected from those bytes and pinned by their digest, the demand is
|
|
10
|
+
* withdrawn and the question is answered. Until then it is not: what is owed is said, and nothing else is asked.
|
|
11
|
+
*/
|
|
12
|
+
export const paidIn = async (asked, context, place) => {
|
|
13
|
+
const say = sayIn(context.lock), word = (k) => valueOf(context.lock, k), source = asked['source'] ?? '';
|
|
14
|
+
const held = await standingOr(context, place), scope = `${word('place/needs')}/bytes/${source}`;
|
|
15
|
+
const owed = 'said' in held ? undefined : held.lines.find((l) => l.scope === scope && l.role === 'demands' && l.value !== 'withdraw');
|
|
16
|
+
if (owed === undefined)
|
|
17
|
+
return { said: [], answer: true };
|
|
18
|
+
const document = owed.value ?? '', read = await context.files.read.documentOf(context, place, document), root = rootIn(context, place);
|
|
19
|
+
if ('said' in root)
|
|
20
|
+
return { said: root.said, answer: false };
|
|
21
|
+
if (read === undefined)
|
|
22
|
+
return { said: [say('bytes-owed', { source, place, document })], answer: false };
|
|
23
|
+
const laid = await laidIn(context, place, root.account)(source, document, read, undefined, context.machine());
|
|
24
|
+
if (!laid.connected)
|
|
25
|
+
return { said: laid.said, answer: false };
|
|
26
|
+
const landed = await context.landRoot(place, [canonical({ ...Object.fromEntries(Object.entries(owed).filter(([k]) => !['sig', 'epoch'].includes(k))), by: 'target', value: 'withdraw' })], root.account);
|
|
27
|
+
return 'refused' in landed ? { said: [...laid.said, say('not-landed', { why: landed.refused })], answer: false } : { said: [...laid.said, ...landed.lines], answer: true };
|
|
28
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Asked, Context } from '../core/answer.ts';
|
|
2
|
+
/**
|
|
3
|
+
* What a person names as a source's document, made a document head can read, before anything is founded or connected:
|
|
4
|
+
* an address or a file is as given; a site alone is looked for where it says its documents are (its API catalog) and at
|
|
5
|
+
* the paths the lock names, the first that reads taken; a folder, at the same names inside it. A source the person names
|
|
6
|
+
* none for is named after the site or the folder. A site where nothing reads is said, with what was tried.
|
|
7
|
+
*/
|
|
8
|
+
export declare const resolved: (asked: Asked, context: Context) => Promise<{
|
|
9
|
+
readonly asked: Asked;
|
|
10
|
+
} | {
|
|
11
|
+
readonly said: readonly string[];
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { basename, dirname, join } from 'node:path';
|
|
2
|
+
import { EXTENSION } from '@lapxo/topos/wire';
|
|
3
|
+
import { members, valueOf } from "../core/lock.js";
|
|
4
|
+
import { sayIn } from "../core/surface.js";
|
|
5
|
+
/**
|
|
6
|
+
* What a person names as a source's document, made a document head can read, before anything is founded or connected:
|
|
7
|
+
* an address or a file is as given; a site alone is looked for where it says its documents are (its API catalog) and at
|
|
8
|
+
* the paths the lock names, the first that reads taken; a folder, at the same names inside it. A source the person names
|
|
9
|
+
* none for is named after the site or the folder. A site where nothing reads is said, with what was tried.
|
|
10
|
+
*/
|
|
11
|
+
export const resolved = async (asked, context) => {
|
|
12
|
+
const [at, word] = [asked['document'] ?? '', (k) => valueOf(context.lock, k)], named = (name) => asked['source'] || name.toLowerCase().replace(/[^a-z0-9-]+/g, '-');
|
|
13
|
+
if (/^([a-z]+:\/\/)?[^/\s]+\.[^/\s]+\/?$/.test(at) && URL.canParse(/^[a-z]+:\/\//.test(at) ? at : `${word('use/scheme')}//${at}`)) {
|
|
14
|
+
const site = new URL(/^[a-z]+:\/\//.test(at) ? at : `${word('use/scheme')}//${at}`), tried = await context.files.read.describedAt(context, site.origin);
|
|
15
|
+
for (const document of tried)
|
|
16
|
+
if (((read) => read !== undefined && `.${read.kind}` !== EXTENSION)(await context.files.read.documentOf(context, '', document)))
|
|
17
|
+
return { asked: { ...asked, document, source: named(site.hostname) } };
|
|
18
|
+
return { said: [sayIn(context.lock)('use-none', { origin: site.origin, tried: tried.join(', ') || '—' })] };
|
|
19
|
+
}
|
|
20
|
+
const inside = at.startsWith('/') && context.files.list(dirname(at), basename(at)).length ? members(word('use/documents')).map((p) => basename(p)).find((n) => context.files.has(at, n)) : undefined;
|
|
21
|
+
return { asked: inside === undefined ? asked : { ...asked, document: join(at, inside), source: named(basename(at)) } };
|
|
22
|
+
};
|
package/dist/src/ports/bound.js
CHANGED
|
@@ -5,6 +5,8 @@ import { dirname, join } from 'node:path';
|
|
|
5
5
|
import { parse } from '@lapxo/topos/wire';
|
|
6
6
|
import { spawned } from "./process.js";
|
|
7
7
|
import { cooled, kept, warmed } from "./warm.js";
|
|
8
|
+
import { folded } from "../ahead/fold.js";
|
|
9
|
+
import { readingOf } from "./reading.js";
|
|
8
10
|
const binOf = (name) => {
|
|
9
11
|
try {
|
|
10
12
|
const manifest = createRequire(import.meta.url).resolve(join(name, 'package.json'));
|
|
@@ -34,6 +36,16 @@ export const warm = (name) => ((bin) => (bin === undefined ? Promise.resolve() :
|
|
|
34
36
|
export { cooled };
|
|
35
37
|
const facts = (out) => out.flatMap((line) => ((got) => (got.kind === 'fact' ? [got.value.fields] : []))(parse(line)));
|
|
36
38
|
const readOf = (printed) => (printed === undefined || printed.code !== 0 ? { refused: why(printed) ?? '' } : { lines: facts(printed.out) });
|
|
39
|
+
/** Native identities and expected-identity results come from the same public CLI contract. Without it, read fresh. */
|
|
40
|
+
const nativeView = (name, place, view, signal) => {
|
|
41
|
+
const read = async (expected) => {
|
|
42
|
+
if (expected === undefined)
|
|
43
|
+
return readOf(await run(name, place, [words.read, words.view, view], {}, signal));
|
|
44
|
+
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')) };
|
|
46
|
+
};
|
|
47
|
+
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
|
+
};
|
|
37
49
|
/**
|
|
38
50
|
* The lines a place stands on, from bound's public views alone: its folded normative standing (the lines view) — its
|
|
39
51
|
* policy, all that a reader of its pins, keys and views needs, read alone where nothing else is — and from
|
|
@@ -43,12 +55,12 @@ const readOf = (printed) => (printed === undefined || printed.code !== 0 ? { ref
|
|
|
43
55
|
* name. A place whose land prints no act result is refused before anything lands in it. Any refusal of bound is no standing.
|
|
44
56
|
*/
|
|
45
57
|
export const policyAt = async (name, place, signal) => ((folded) => ('refused' in folded || folded.lines.some((line) => line.scope === words.profile) ? folded
|
|
46
|
-
: { refused: `REFUSE·act the place declares no ${words.profile}: land would print nothing of what it admits, so head lands nothing there; its root can land the local-act@1 profile and view/act` }))(
|
|
58
|
+
: { refused: `REFUSE·act the place declares no ${words.profile}: land would print nothing of what it admits, so head lands nothing there; its root can land the local-act@1 profile and view/act` }))(await nativeView(name, place, words.standing, signal));
|
|
47
59
|
export const standingAt = async (name, place, devicesOf, isHistory, signal) => {
|
|
48
60
|
const folded = await policyAt(name, place, signal);
|
|
49
61
|
if ('refused' in folded)
|
|
50
62
|
return folded;
|
|
51
|
-
const evidence =
|
|
63
|
+
const evidence = await nativeView(name, place, words.evidence, signal);
|
|
52
64
|
if ('refused' in evidence)
|
|
53
65
|
return evidence;
|
|
54
66
|
const ids = new Set(devicesOf(folded.lines)), own = evidence.lines.filter((line) => ids.has(line.by ?? ''));
|
|
@@ -81,5 +93,5 @@ export const pinnedAt = async (name, place, file, signal) => {
|
|
|
81
93
|
return pin === undefined || !bytes ? undefined : { pin, bytes, lines: read.lines };
|
|
82
94
|
};
|
|
83
95
|
export const founded = async (name, place, key, signer, env, signal) => ((got) => (got !== undefined && got.code === 0 ? { lines: got.out } : { refused: why(got) ?? '' }))(await run(name, place, [words.land, words.key, key, words.signer, signer], env, signal));
|
|
84
|
-
export const words = { read: '', view: '', sign: '', land: '', key: '', signer: '', pin: '', topos: '', standing: '', evidence: '', act: '', profile: '', receipts: '', refusal: '' };
|
|
96
|
+
export const words = { read: '', view: '', sign: '', land: '', key: '', signer: '', pin: '', topos: '', standing: '', evidence: '', act: '', profile: '', receipts: '', refusal: '', identity: '' };
|
|
85
97
|
export const settledView = async (name, place, view, signal) => readOf(await run(name, place, [words.read, words.view, view], {}, signal));
|
|
@@ -32,7 +32,7 @@ export const fetchedAs = async (context, at, via, got, named, accept) => {
|
|
|
32
32
|
return { said: [say('fetch-redirect', { document: at })] };
|
|
33
33
|
return ((bytes) => (bytes ? { bytes, kind: kindOf(at) } : { said: [say('document-unread', { document: at })] }))(await transport.published(new URL(answer.location, at).href, named, most, wait));
|
|
34
34
|
}
|
|
35
|
-
return answer.status < 300 ? { bytes: answer.bytes, kind: kindOf(at, answer.type) } : { said: [say('fetch-status', { document: at, via, status: String(answer.status) })] };
|
|
35
|
+
return answer.status < 300 ? { bytes: answer.bytes, kind: kindOf(at, answer.type) } : { said: [say('fetch-status', { document: at, via, status: String(answer.status), at: new Date(context.now()).toISOString() })] };
|
|
36
36
|
};
|
|
37
37
|
/**
|
|
38
38
|
* Every byte a package's lines name, by the path each names: read where a line of the same scope says — an encrypted
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Printed } from './process.ts';
|
|
2
|
+
/** Only the installed instrument names a view's identity and the result bound to it. */
|
|
3
|
+
export declare const readingOf: (printed: Printed | undefined, view: string, expected?: string) => {
|
|
4
|
+
readonly identity: string;
|
|
5
|
+
readonly text?: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly refused: string;
|
|
8
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Only the installed instrument names a view's identity and the result bound to it. */
|
|
2
|
+
export const readingOf = (printed, view, expected) => {
|
|
3
|
+
if (!printed || printed.code !== 0)
|
|
4
|
+
return { refused: printed?.refusal?.['code'] ?? 'reading-identity/unavailable' };
|
|
5
|
+
try {
|
|
6
|
+
const value = printed.out.length === 1 ? JSON.parse(printed.out[0]) : undefined;
|
|
7
|
+
if (value?.['kind'] !== 'fact' || value['view'] !== view || typeof value['identity'] !== 'string' || !/^sha256:[0-9a-f]{64}$/.test(value['identity']) || (expected !== undefined && (value['identity'] !== expected || typeof value['text'] !== 'string')))
|
|
8
|
+
return { refused: 'reading-identity/context' };
|
|
9
|
+
return { identity: value['identity'], ...(expected === undefined ? {} : { text: value['text'] }) };
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return { refused: 'reading-identity/format' };
|
|
13
|
+
}
|
|
14
|
+
};
|
package/dist/src/ports/spare.js
CHANGED
|
@@ -18,7 +18,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
18
18
|
const [program] = process.argv.slice(2);
|
|
19
19
|
const url = pathToFileURL(program), pipe = new Socket({ fd: 3, readable: true, writable: true });
|
|
20
20
|
const asked = new Promise((resolve) => { let text = ''; pipe.setEncoding('utf8').on('data', (chunk) => { text += chunk; }).on('error', () => undefined).on('close', () => resolve(text)); });
|
|
21
|
-
await Promise.all([...readFileSync(program, 'utf8').matchAll(
|
|
21
|
+
await Promise.all([...readFileSync(program, 'utf8').matchAll(/(?:^|[;\n])\s*import\s*(?:(?:[^'";]*?)\s*from\s*)?['"](\.{1,2}\/[^'"]+)['"]/g)].map(([, path]) => import(__rewriteRelativeImportExtension(new URL(path, url).href)).catch(() => undefined)));
|
|
22
22
|
pipe.write('ready\n');
|
|
23
23
|
const text = await asked;
|
|
24
24
|
if (!text)
|
|
@@ -28,10 +28,14 @@ export interface Surface {
|
|
|
28
28
|
readonly name: string;
|
|
29
29
|
readonly version: string;
|
|
30
30
|
readonly tools: readonly Tool[];
|
|
31
|
+
readonly catalogue: readonly Tool[];
|
|
31
32
|
readonly resources: readonly Resource[];
|
|
32
33
|
readonly prompts: readonly Prompt[];
|
|
33
34
|
}
|
|
34
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Everything a client is offered, read off the lock alone, each with the words a model sees: the tools surface/tools
|
|
37
|
+
* names, out of the catalogue the tool lines are (every one of them where it names none), its resources and prompts.
|
|
38
|
+
*/
|
|
35
39
|
export declare const surfaceOf: (lock: readonly Said[]) => Surface;
|
|
36
40
|
export declare const refusedOn: (lock: readonly Said[], platform: string) => string | undefined;
|
|
37
41
|
/**
|
|
@@ -4,13 +4,15 @@ const nameOf = (line) => line.scope.split('/')[1];
|
|
|
4
4
|
const fieldsOf = (lock, scope) => ((required) => members(valueOf(lock, scope)).map((name) => ({
|
|
5
5
|
name, about: aboutOf(lock, `${scope}/${name}`), type: valueOf(lock, `${scope}/${name}`), required: required.includes(name)
|
|
6
6
|
})))(members(valueOf(lock, `${scope}/required`)));
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Everything a client is offered, read off the lock alone, each with the words a model sees: the tools surface/tools
|
|
9
|
+
* names, out of the catalogue the tool lines are (every one of them where it names none), its resources and prompts.
|
|
10
|
+
*/
|
|
11
|
+
export const surfaceOf = (lock) => ((catalogue, offered) => ({
|
|
12
|
+
name: valueOf(lock, 'name'), version: valueOf(lock, 'version'), catalogue, tools: offered.length ? catalogue.filter((t) => offered.includes(t.name)) : catalogue,
|
|
11
13
|
resources: below(lock, 'resource').map((line) => ({ name: nameOf(line), about: line.about ?? '', template: line.value ?? '', mime: valueOf(lock, `${line.scope}/mime`) })),
|
|
12
14
|
prompts: below(lock, 'prompt').map((line) => ({ name: nameOf(line), about: line.about ?? '', text: aboutOf(lock, `${line.scope}/text`), line: aboutOf(lock, `${line.scope}/line`), fields: fieldsOf(lock, line.scope) })),
|
|
13
|
-
});
|
|
15
|
+
}))(below(lock, 'tool').map((line) => ({ name: nameOf(line), about: line.about ?? '', fields: fieldsOf(lock, line.scope), hints: members(valueOf(lock, `${line.scope}/hints`)) })), members(valueOf(lock, 'surface/tools')));
|
|
14
16
|
export const refusedOn = (lock, platform) => ((supported) => (supported.includes(platform) ? undefined : sayIn(lock)('platform', { platform, supported: supported.join('|') })))(members(valueOf(lock, 'platform/supported')));
|
|
15
17
|
/**
|
|
16
18
|
* The fields a URI gives a resource template, or none when it is not one of its URIs: {name} matches one path step,
|
package/dist/src/ports/warm.js
CHANGED
|
@@ -40,7 +40,6 @@ export const warmed = (program, args, at, bounds) => {
|
|
|
40
40
|
const spare = index < 0 ? undefined : live.splice(index, 1)[0];
|
|
41
41
|
if (spare === undefined)
|
|
42
42
|
return undefined;
|
|
43
|
-
void kept(program);
|
|
44
43
|
spare.child.stdio[3].end(`${JSON.stringify({ cwd: at.cwd, args, env: at.env })}\n`);
|
|
45
|
-
return watched(spare.child, bounds);
|
|
44
|
+
return watched(spare.child, bounds).finally(() => { void kept(program); });
|
|
46
45
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Asked, Context } from '../core/answer.ts';
|
|
2
|
+
import type { Read } from '../ports/documents.ts';
|
|
2
3
|
/**
|
|
3
4
|
* A source connected to a place, once the person said yes; a lock is no source, and adopting one as a parent is refused
|
|
4
5
|
* by name, since composing two places' policies is bound's. A document — and an overlay beside it — is laid whatever its
|
|
@@ -11,3 +12,10 @@ import type { Asked, Context } from '../core/answer.ts';
|
|
|
11
12
|
* compiled. Nothing the sharing place was granted comes with it: access is asked here, issuer by issuer.
|
|
12
13
|
*/
|
|
13
14
|
export declare const connected: (asked: Asked, context: Context, covered?: boolean) => Promise<readonly string[]>;
|
|
15
|
+
type Connection = {
|
|
16
|
+
readonly said: readonly string[];
|
|
17
|
+
readonly connected: boolean;
|
|
18
|
+
};
|
|
19
|
+
type Laid = (source: string, document: string, doc: Read, overlay: Read | undefined, started: number) => Promise<Connection>;
|
|
20
|
+
export declare const laidIn: (context: Context, place: string, account: string) => Laid;
|
|
21
|
+
export {};
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { canonical, EXTENSION, LOCK } from '@lapxo/topos/wire';
|
|
1
|
+
import { canonical, EXTENSION } from '@lapxo/topos/wire';
|
|
3
2
|
import { inPlace, placeOf, unless } from "../core/answer.js";
|
|
4
3
|
import { valueOf } from "../core/lock.js";
|
|
5
4
|
import { fieldsOf, vocabOf } from "../core/read.js";
|
|
6
|
-
import {
|
|
5
|
+
import { redIn, sayIn } from "../core/surface.js";
|
|
7
6
|
import { digestIn, linesOf } from "../core/worlds.js";
|
|
8
7
|
import { coveredIn, owedIn, unpacked } from "../core/package.js";
|
|
9
8
|
import { paidBy, pinIn } from "../compile/pin.js";
|
|
10
9
|
import { adopted } from "../adopt/adopt.js";
|
|
11
10
|
import { firstRun } from "../onboard/first.js";
|
|
11
|
+
import { resolved } from "../onboard/site.js";
|
|
12
|
+
import { linked } from "../links/inspect.js";
|
|
12
13
|
import { rootIn } from "../flows/device.js";
|
|
13
|
-
import { bornAt } from "../places/found.js";
|
|
14
|
-
import { untilOf } from "../core/act.js";
|
|
15
14
|
import { attachedFor } from "./access.js";
|
|
16
15
|
/**
|
|
17
16
|
* A source connected to a place, once the person said yes; a lock is no source, and adopting one as a parent is refused
|
|
@@ -24,8 +23,10 @@ import { attachedFor } from "./access.js";
|
|
|
24
23
|
* its pins rest in, its pin held to the package's; one that differs is refused by name, and the place holds what it
|
|
25
24
|
* compiled. Nothing the sharing place was granted comes with it: access is asked here, issuer by issuer.
|
|
26
25
|
*/
|
|
27
|
-
export const connected = async (asked, context, covered = false) => (asked['
|
|
28
|
-
:
|
|
26
|
+
export const connected = async (asked, context, covered = false) => ((asked['document'] ?? '').startsWith(valueOf(context.lock, 'link/prefix')) ? linked(asked, context)
|
|
27
|
+
: ((got) => ('said' in got ? got.said : connectedAs(got.asked, context, covered)))(await resolved(asked, context)));
|
|
28
|
+
const connectedAs = async (asked, context, covered = false) => (asked['source'] && !(await placeOf(asked, context)) ? firstRun(asked, context, (dir, covered) => connected({ ...asked, place: dir }, context, covered))
|
|
29
|
+
: ((started) => inPlace(asked, context, async (place) => {
|
|
29
30
|
const say = sayIn(context.lock), digest = digestIn(context.lock), { documentOf, fetchedAs, kindOf } = context.files.read;
|
|
30
31
|
const [source, document] = [(asked['source'] ?? '').toLowerCase().replace(/[^a-z0-9-]+/g, '-'), asked['document'] ?? ''];
|
|
31
32
|
const [via, named, accept, lock] = [asked['via'] ?? '', asked['digest'] ?? '', /^[\w.+-]+\/[\w.+-]+$/.test(asked['accept'] ?? '') ? asked['accept'] : undefined, `.${kindOf(document)}` === EXTENSION];
|
|
@@ -54,38 +55,8 @@ export const connected = async (asked, context, covered = false) => (asked['sour
|
|
|
54
55
|
return [say('document-unread', { document })];
|
|
55
56
|
if (named && digest(read.bytes) !== named)
|
|
56
57
|
return [say('document-digest', { document, digest: named, got: digest(read.bytes) })];
|
|
57
|
-
return `.${read.kind}` === EXTENSION ? [say('parent-lock', { place, parent: source })] : laidIn(context, place, root.account)(source, document, read, overlay, started);
|
|
58
|
+
return `.${read.kind}` === EXTENSION ? [say('parent-lock', { place, parent: source })] : (await laidIn(context, place, root.account)(source, document, read, overlay, started)).said;
|
|
58
59
|
}))(context.machine()));
|
|
59
|
-
const used = async (asked, context) => {
|
|
60
|
-
const [say, word, until] = [sayIn(context.lock), (k) => valueOf(context.lock, k), untilOf(valueOf(context.lock, 'access/period'), context.now())], at = /^[a-z]+:\/\//.test(asked['document'] ?? '') ? asked['document'] : `${word('use/scheme')}//${asked['document'] ?? ''}`;
|
|
61
|
-
if (!URL.canParse(at))
|
|
62
|
-
return [say('document-unread', { document: asked['document'] ?? '' })];
|
|
63
|
-
const [origin, source] = [new URL(at).origin, new URL(at).hostname.replace(/[^a-z0-9-]+/g, '-')], dir = join(fill(word('use/folder'), { home: context.home() }), source);
|
|
64
|
-
if (context.files.has(dir, LOCK))
|
|
65
|
-
return [say('use-held', { origin, dir })];
|
|
66
|
-
const refused = await unless(context, say('elicit-use', { origin, dir, until }));
|
|
67
|
-
if (refused)
|
|
68
|
-
return refused;
|
|
69
|
-
const born = await bornAt(context, dir, source), root = rootIn(context, dir), tried = [];
|
|
70
|
-
if (born[0] !== say('born', { name: source, dir }) || 'said' in root)
|
|
71
|
-
return 'said' in root ? [...born, ...root.said] : born;
|
|
72
|
-
for (const document of await context.files.read.describedAt(context, origin)) {
|
|
73
|
-
const read = await context.files.read.documentOf(context, dir, document);
|
|
74
|
-
if (read === undefined || `.${read.kind}` === EXTENSION) {
|
|
75
|
-
tried.push(document);
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
const said = await laidIn(context, dir, root.account)(source, document, read, undefined, context.machine()), held = await context.policy(dir);
|
|
79
|
-
if ('refused' in held || !held.lines.some((l) => l.scope === `uses/${source}`)) {
|
|
80
|
-
tried.push(document);
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
const line = (scope, measure, value, form = 'alphabet') => canonical({ at: 'policy:head/use', by: 'target', form, measure, role: 'writes', scope, value });
|
|
84
|
-
const landed = await context.landRoot(dir, [line(`${word('allow/family')}/${source}`, 'calls', '0..0', 'interval'), line(word('grant/family'), 'sources', source), line(word('grant/family'), 'until', until)], root.account);
|
|
85
|
-
return [say('used', { origin, dir, source, until }), ...said, ...('refused' in landed ? [say('not-landed', { why: landed.refused })] : landed.lines)];
|
|
86
|
-
}
|
|
87
|
-
return [...born, say('use-none', { origin, tried: tried.join(', ') || '—' })];
|
|
88
|
-
};
|
|
89
60
|
const received = async (context, place, document, bytes, named, laid, account) => {
|
|
90
61
|
const [say, got] = [sayIn(context.lock), unpacked(context, document, bytes, await context.files.read.besideOf(context, document, linesOf(bytes)))];
|
|
91
62
|
if (!got.sources)
|
|
@@ -95,20 +66,20 @@ const received = async (context, place, document, bytes, named, laid, account) =
|
|
|
95
66
|
if (refused)
|
|
96
67
|
return refused;
|
|
97
68
|
for (const s of got.sources)
|
|
98
|
-
said.push(...(await laid(s.source, s.document.at, s.document, s.overlay, context.machine())));
|
|
69
|
+
said.push(...(await laid(s.source, s.document.at, s.document, s.overlay, context.machine())).said);
|
|
99
70
|
const held = await context.policy(place), [pinOf, owed] = [(source) => ('refused' in held ? '' : held.lines.find((l) => l.scope === `uses/${source}`)?.value ?? ''), owedIn(context, 'refused' in held ? [] : held.lines, got.sources, got.open ?? [], named)];
|
|
100
71
|
const [off, landed] = [got.sources.filter((s) => pinOf(s.source) !== s.pin), owed.length ? await context.landRoot(place, owed, account) : undefined];
|
|
101
72
|
return [...said, ...(off.length ? off.map((s) => say('package-pin', { source: s.source, pin: s.pin, got: pinOf(s.source) || 'none' })) : names ? [say('received', { place, package: named, sources: names })] : []),
|
|
102
73
|
...(got.open ?? []).map((o) => say('received-open', { source: o.source, waits: o.waits })), ...(landed && 'refused' in landed ? [say('not-landed', { why: landed.refused })] : [])];
|
|
103
74
|
};
|
|
104
|
-
const laidIn = (context, place, account) => (source, document, doc, overlay, started) => (async () => {
|
|
75
|
+
export const laidIn = (context, place, account) => (source, document, doc, overlay, started) => (async () => {
|
|
105
76
|
const say = sayIn(context.lock), v = vocabOf(context.lock), word = (k) => valueOf(context.lock, k), digest = digestIn(context.lock);
|
|
106
77
|
const lands = (lot) => context.landRoot(place, lot, account);
|
|
107
78
|
const line = (scope, measure, value, more = {}) => canonical({ at: 'policy:head/connect', by: 'target', form: 'alphabet', measure, role: 'writes', scope, value, ...more });
|
|
108
79
|
const pin = pinIn(context, place, lands, line);
|
|
109
80
|
const worlds = await adopted(context, place, lands, line);
|
|
110
81
|
if ('refused' in worlds)
|
|
111
|
-
return [say('not-landed', { why: worlds.refused })];
|
|
82
|
+
return { said: [say('not-landed', { why: worlds.refused })], connected: false };
|
|
112
83
|
const laid = [[`${word('place/connected')}/${source}.${doc.kind}`, doc.bytes], ...(overlay ? [[`${word('place/connected')}/${source}.overlay.${overlay.kind}`, overlay.bytes]] : [])];
|
|
113
84
|
for (const [at, bytes] of laid)
|
|
114
85
|
context.files.lay(place, at, bytes);
|
|
@@ -117,14 +88,14 @@ const laidIn = (context, place, account) => (source, document, doc, overlay, sta
|
|
|
117
88
|
if (pinned.open || pinned.dropped)
|
|
118
89
|
for (const [at] of laid)
|
|
119
90
|
context.files.drop(place, at);
|
|
120
|
-
return pinned.open ? [say('no-world', { kind: doc.kind, document }), line(`${word('place/needs')}/${doc.kind}`, 'id', valueOf(context.lock, 'birth/view/place'), { role: 'demands' })] : pinned.said;
|
|
91
|
+
return { said: pinned.open ? [say('no-world', { kind: doc.kind, document }), line(`${word('place/needs')}/${doc.kind}`, 'id', valueOf(context.lock, 'birth/view/place'), { role: 'demands' })] : pinned.said, connected: false };
|
|
121
92
|
}
|
|
122
93
|
const declared = fieldsOf(pinned.lines, `${v.region}/${source}`)[v.origins] ?? [], fetched = URL.canParse(document) && !declared.some((o) => URL.canParse(o)) && ((held) => 'lines' in held && !held.lines.some((l) => l.scope === `${v.pick}/${source}`))(await context.policy(place));
|
|
123
94
|
const picked = fetched ? await lands([line(`${v.pick}/${source}`, 'id', new URL(declared[0] ?? '/', document).href.replace(/\/$/, ''))]) : undefined;
|
|
124
95
|
const [owed, paid] = [pinned.lines.filter((l) => l.measure === v.unresolved).map((l) => l.value ?? ''), await paidBy(context, place, pin, laid[0][0])];
|
|
125
96
|
const classes = pinned.lines.filter((l) => l.measure === v.class && l.scope?.startsWith(`${v.offers}/${source}/`) && l.scope.split('/').length === 3);
|
|
126
|
-
return [say('connected', { source, place, offers: String(classes.length), reads: String(classes.filter((l) => l.value === v.callable).length), pin: pinned.pin }),
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
})().then((
|
|
97
|
+
return { connected: picked === undefined || !('refused' in picked), said: [say('connected', { source, place, offers: String(classes.length), reads: String(classes.filter((l) => l.value === v.callable).length), pin: pinned.pin }),
|
|
98
|
+
...(owed.length ? [say('unresolved', { source, refs: owed.join(', ') })] : []), ...paid.flatMap((p) => ('said' in p.pinned ? p.pinned.said : [say('resolved', { source: p.source, by: source, pin: p.pinned.pin })])),
|
|
99
|
+
...pinned.lines.filter((l) => l.scope === `${v.region}/${source}` || (l.scope?.startsWith(`${v.keys}/${source}/`) && l.measure === v.scheme)).map((l) => canonical(l)),
|
|
100
|
+
...(picked === undefined ? [] : 'refused' in picked ? [say('not-landed', { why: picked.refused })] : picked.lines)] };
|
|
101
|
+
})().then((result) => ({ ...result, said: [...result.said, ...redIn(context.lock)('connect/seconds', `connect/${source}`, started, context.machine())] }));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Context } from '../core/answer.ts';
|
|
2
|
+
import { type Said } from '../core/lock.ts';
|
|
3
|
+
import type { Call } from '../core/read.ts';
|
|
4
|
+
/** A read refused with the access head attached: its secret kept marked spent, never in a line, so the next call obtains it once; the refusal lands as a receipt without it. */
|
|
5
|
+
export declare const refusedAccess: (context: Context, place: string, lines: readonly Said[], plan: Call, operation: string) => Promise<readonly string[]>;
|
|
6
|
+
/** The access a place was refused and has not obtained since, by its newest receipt: what its card shows as open. */
|
|
7
|
+
export declare const pendingIn: (context: Context, place: string) => Promise<readonly string[]>;
|