@lapxo/head 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/TARGET.bound +392 -49
  2. package/dist/src/access/contract.d.ts +2 -0
  3. package/dist/src/access/obtain.js +2 -2
  4. package/dist/src/adopt/adopt.js +5 -2
  5. package/dist/src/compile/pin.js +5 -5
  6. package/dist/src/core/act.d.ts +1 -0
  7. package/dist/src/core/act.js +1 -0
  8. package/dist/src/core/answer.d.ts +16 -4
  9. package/dist/src/core/answer.js +7 -4
  10. package/dist/src/core/lock.d.ts +2 -0
  11. package/dist/src/core/lock.js +2 -0
  12. package/dist/src/core/package.d.ts +54 -0
  13. package/dist/src/core/package.js +65 -0
  14. package/dist/src/core/read.d.ts +3 -0
  15. package/dist/src/core/read.js +8 -4
  16. package/dist/src/core/surface.d.ts +1 -40
  17. package/dist/src/core/surface.js +4 -29
  18. package/dist/src/core/trust.d.ts +7 -0
  19. package/dist/src/core/trust.js +19 -0
  20. package/dist/src/flows/device.js +1 -6
  21. package/dist/src/flows/read.js +18 -8
  22. package/dist/src/flows/world.js +3 -3
  23. package/dist/src/index.js +37 -18
  24. package/dist/src/onboard/first.d.ts +7 -0
  25. package/dist/src/onboard/first.js +21 -0
  26. package/dist/src/places/admit.js +1 -1
  27. package/dist/src/places/found.d.ts +1 -0
  28. package/dist/src/places/found.js +8 -7
  29. package/dist/src/ports/access/authorizer.d.ts +10 -0
  30. package/dist/src/ports/access/authorizer.js +34 -0
  31. package/dist/src/ports/access/flow.js +13 -3
  32. package/dist/src/ports/access/person.d.ts +3 -2
  33. package/dist/src/ports/access/person.js +12 -5
  34. package/dist/src/ports/adapters/request.d.ts +3 -0
  35. package/dist/src/ports/adapters/request.js +5 -3
  36. package/dist/src/ports/bound.d.ts +12 -2
  37. package/dist/src/ports/bound.js +30 -13
  38. package/dist/src/ports/documents.d.ts +34 -0
  39. package/dist/src/ports/documents.js +64 -0
  40. package/dist/src/ports/keychain.d.ts +2 -1
  41. package/dist/src/ports/keychain.js +3 -2
  42. package/dist/src/ports/place.d.ts +1 -0
  43. package/dist/src/ports/place.js +7 -1
  44. package/dist/src/ports/process.d.ts +9 -0
  45. package/dist/src/ports/process.js +5 -3
  46. package/dist/src/ports/spare.d.ts +1 -0
  47. package/dist/src/ports/spare.js +33 -0
  48. package/dist/src/ports/surface.d.ts +126 -0
  49. package/dist/src/ports/surface.js +48 -0
  50. package/dist/src/ports/warm.d.ts +19 -0
  51. package/dist/src/ports/warm.js +46 -0
  52. package/dist/src/signer.js +1 -1
  53. package/dist/src/sources/access.d.ts +10 -1
  54. package/dist/src/sources/access.js +15 -10
  55. package/dist/src/sources/connect.d.ts +5 -2
  56. package/dist/src/sources/connect.js +98 -36
  57. package/dist/src/tools/card.js +1 -1
  58. package/dist/src/tools/look.d.ts +2 -2
  59. package/dist/src/tools/look.js +4 -3
  60. package/dist/src/tools/places.d.ts +7 -0
  61. package/dist/src/tools/places.js +32 -0
  62. package/dist/src/tools/share.d.ts +1 -0
  63. package/dist/src/tools/share.js +1 -0
  64. package/package.json +6 -5
  65. /package/dist/src/{core → ports}/keys.d.ts +0 -0
  66. /package/dist/src/{core → ports}/keys.js +0 -0
@@ -10,25 +10,30 @@ import { standingFor } from "../flows/world.js";
10
10
  * Access to a source obtained ahead of any call, at the origin the person picked once where several are declared: the
11
11
  * way its document says access is proven, run by the adapter of
12
12
  * that kind — the person's own page, or the provider they are sent to with a client registered for the visit — its
13
- * secret kept in this keychain and its obtaining landed as a receipt the device key signs. Nothing is called.
13
+ * secret kept in this keychain and its obtaining landed as a receipt the device key signs. Nothing is called. What it
14
+ * attaches is what a fetch made as that source carries, to that source's own origin alone.
14
15
  */
15
16
  export const accessed = (asked, context) => inPlace(asked, context, async (place) => {
16
- const [say, v, source] = [sayIn(context.lock), vocabOf(context.lock), asked['source'] ?? ''];
17
+ const got = await attachedFor(context, place, asked['source'] ?? '', asked['operation']);
18
+ return 'said' in got ? got.said : [sayIn(context.lock)(got.plan.access.account === undefined ? 'access-anonymous' : 'access-kept', { source: got.plan.source })];
19
+ });
20
+ export const attachedFor = async (context, place, source, asked) => {
21
+ const [say, v] = [sayIn(context.lock), vocabOf(context.lock)];
17
22
  const held = await standingFor(context, place);
18
23
  if ('unsettled' in held)
19
- return [say('unsettled', { source, why: held.unsettled })];
20
- const operation = asked['operation'] || held.lines.filter((l) => l.measure === v.class && l.value === v.callable && l.scope?.startsWith(`${v.offers}/${source}/`)).map((l) => l.scope.split('/').slice(2).join('/')).sort()[0];
24
+ return { said: [say('unsettled', { source, why: held.unsettled })] };
25
+ const operation = asked || held.lines.filter((l) => l.measure === v.class && l.value === v.callable && l.scope?.startsWith(`${v.offers}/${source}/`)).map((l) => l.scope.split('/').slice(2).join('/')).sort()[0];
21
26
  if (operation === undefined)
22
- return [say('unread', { source })];
27
+ return { said: [say('unread', { source })] };
23
28
  const picked = await pickedOrigin(context, place, held.lines, source, planRead(held.lines, context.lock, source, operation, placeNameOf(place)));
24
29
  if ('said' in picked)
25
- return picked.said;
30
+ return picked;
26
31
  const plan = planRead(picked.lines, context.lock, source, operation, placeNameOf(place));
27
32
  if (plan.kind === 'said')
28
- return [say(plan.key, plan.fields)];
33
+ return { said: [say(plan.key, plan.fields)] };
29
34
  const stopped = uncoveredIn(context.lock, held.lines, context.device('head'), place, [], [`${valueOf(context.lock, 'access/family')}/${source}/${plan.access.scheme || v.anonymous}/obtained`]);
30
35
  if (stopped)
31
- return stopped;
36
+ return { said: stopped };
32
37
  const got = await attachmentFor(context, place, picked.lines, plan);
33
- return 'said' in got ? [say(got.said, { source, operation, kinds: plan.access.kind, input: got.input })] : [say(plan.access.account === undefined ? 'access-anonymous' : 'access-kept', { source })];
34
- });
38
+ return 'said' in got ? { said: [say(got.said, { source, operation, kinds: plan.access.kind, input: got.input })] } : { plan, attachment: got.attachment };
39
+ };
@@ -5,6 +5,9 @@ import type { Asked, Context } from '../core/answer.ts';
5
5
  * kind, with every world head carries adopted, and the fold decides which reads it: what no world compiles is open, the
6
6
  * bytes taken back and the line that pays it handed on. What one does is compiled once per digest of what it read — the
7
7
  * same bytes again find their compiled standing by name — and pinned from the place's store: a new pin lands with the
8
- * old one's withdraw, said at its own time, since a withdrawn saying stays withdrawn.
8
+ * old one's withdraw, said at its own time, since a withdrawn saying stays withdrawn. A lock named by its digest is a
9
+ * package another place shared: checked whole, then — on one yes to all it names — each source connected in the order
10
+ * its pins rest in, its pin held to the package's; one that differs is refused by name, and the place holds what it
11
+ * compiled. Nothing the sharing place was granted comes with it: access is asked here, issuer by issuer.
9
12
  */
10
- export declare const connected: (asked: Asked, context: Context) => Promise<readonly string[]>;
13
+ export declare const connected: (asked: Asked, context: Context, covered?: boolean) => Promise<readonly string[]>;
@@ -1,52 +1,111 @@
1
- import { extname, isAbsolute, resolve, sep } from 'node:path';
2
- import { canonical, EXTENSION } from '@lapxo/topos/wire';
3
- import { inPlace, unless } from "../core/answer.js";
1
+ import { join } from 'node:path';
2
+ import { canonical, EXTENSION, LOCK } from '@lapxo/topos/wire';
3
+ import { inPlace, placeOf, unless } from "../core/answer.js";
4
4
  import { valueOf } from "../core/lock.js";
5
- import { vocabOf } from "../core/read.js";
6
- import { redIn, sayIn } from "../core/surface.js";
7
- import { digestIn } from "../core/worlds.js";
5
+ import { fieldsOf, vocabOf } from "../core/read.js";
6
+ import { fill, redIn, sayIn } from "../core/surface.js";
7
+ import { digestIn, linesOf } from "../core/worlds.js";
8
+ import { coveredIn, owedIn, unpacked } from "../core/package.js";
8
9
  import { paidBy, pinIn } from "../compile/pin.js";
9
10
  import { adopted } from "../adopt/adopt.js";
11
+ import { firstRun } from "../onboard/first.js";
10
12
  import { rootIn } from "../flows/device.js";
11
- /** The kind a document is named by, never read for: its file's extension, its address's, or the media type it was served as; bytes that say none are octet-stream. */
12
- const kindOf = (at, type = '') => (extname(URL.canParse(at) ? new URL(at).pathname : at).slice(1)
13
- || (type.split(';')[0].split('/')[1] ?? '').split('+').at(-1).replace(/^x-/, '') || 'octet-stream').toLowerCase();
14
- /** A document the person names: an encrypted address, or a file inside the place or a folder the client shares; anything else is not read. */
15
- const documentOf = async (context, place, at) => {
16
- if (URL.canParse(at))
17
- return ((got) => got && { bytes: got.bytes, kind: kindOf(at, got.type) })(await (await context.adapter(at))?.get(at, Number(valueOf(context.lock, 'adapter/request/wait').split('..')[1])));
18
- const inside = [place, ...(await context.roots())].some((root) => resolve(at).startsWith(`${resolve(root)}${sep}`));
19
- return ((bytes) => bytes && { bytes, kind: kindOf(at) })(isAbsolute(at) && inside ? context.files.take(at) : undefined);
20
- };
13
+ import { bornAt } from "../places/found.js";
14
+ import { untilOf } from "../core/act.js";
15
+ import { attachedFor } from "./access.js";
21
16
  /**
22
17
  * A source connected to a place, once the person said yes; a lock is no source, and adopting one as a parent is refused
23
18
  * by name, since composing two places' policies is bound's. A document — and an overlay beside it — is laid whatever its
24
19
  * kind, with every world head carries adopted, and the fold decides which reads it: what no world compiles is open, the
25
20
  * bytes taken back and the line that pays it handed on. What one does is compiled once per digest of what it read — the
26
21
  * same bytes again find their compiled standing by name — and pinned from the place's store: a new pin lands with the
27
- * old one's withdraw, said at its own time, since a withdrawn saying stays withdrawn.
22
+ * old one's withdraw, said at its own time, since a withdrawn saying stays withdrawn. A lock named by its digest is a
23
+ * package another place shared: checked whole, then — on one yes to all it names — each source connected in the order
24
+ * 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
+ * compiled. Nothing the sharing place was granted comes with it: access is asked here, issuer by issuer.
28
26
  */
29
- export const connected = (asked, context) => ((started) => inPlace(asked, context, async (place) => {
30
- const say = sayIn(context.lock), v = vocabOf(context.lock), word = (k) => valueOf(context.lock, k), digest = digestIn(context.lock);
31
- const [source, document] = [(asked['source'] ?? '').toLowerCase().replace(/[^a-z0-9-]+/g, '-'), asked['document'] ?? ''];
32
- if (!source)
33
- return [say('missing-param', { operation: 'connect', param: 'source' })];
34
- if (`.${kindOf(document)}` === EXTENSION)
35
- return [say('parent-lock', { place, parent: source })];
36
- const root = rootIn(context, place);
37
- if ('said' in root)
38
- return root.said;
39
- const refused = await unless(context, say('elicit-connect', { source, document, place, overlay: asked['overlay'] ?? '' }));
27
+ export const connected = async (asked, context, covered = false) => (asked['source'] && !(await placeOf(asked, context)) ? firstRun(asked, context, (dir, covered) => connected({ ...asked, place: dir }, context, covered))
28
+ : !asked['source'] && /^([a-z]+:\/\/)?[^/\s]+\/?$/.test(asked['document'] ?? '') ? used(asked, context) : ((started) => inPlace(asked, context, async (place) => {
29
+ const say = sayIn(context.lock), digest = digestIn(context.lock), { documentOf, fetchedAs, kindOf } = context.files.read;
30
+ const [source, document] = [(asked['source'] ?? '').toLowerCase().replace(/[^a-z0-9-]+/g, '-'), asked['document'] ?? ''];
31
+ const [via, named, accept, lock] = [asked['via'] ?? '', asked['digest'] ?? '', /^[\w.+-]+\/[\w.+-]+$/.test(asked['accept'] ?? '') ? asked['accept'] : undefined, `.${kindOf(document)}` === EXTENSION];
32
+ if (lock && !named)
33
+ return [say('parent-lock', { place, parent: source || document })];
34
+ if (!source && !lock)
35
+ return [say('missing-param', { operation: 'connect', param: 'source' })];
36
+ const root = rootIn(context, place);
37
+ if ('said' in root)
38
+ return root.said;
39
+ if (named && !/^sha256:[0-9a-f]{64}$/.test(named))
40
+ return [say('document-digest', { document, digest: named, got: '' })];
41
+ if (lock)
42
+ return ((read) => (read === undefined ? [say('document-unread', { document })] : digest(read.bytes) !== named ? [say('document-digest', { document, digest: named, got: digest(read.bytes) })]
43
+ : received(context, place, document, read.bytes, named, laidIn(context, place, root.account), root.account)))(await documentOf(context, place, document, named));
44
+ const refused = covered ? undefined : await unless(context, say('elicit-connect', { source, document, place, overlay: asked['overlay'] ?? '' }));
45
+ if (refused)
46
+ return refused;
47
+ const got = via ? await attachedFor(context, place, via) : undefined;
48
+ if (got && 'said' in got)
49
+ return got.said;
50
+ const [read, overlay] = [got ? await fetchedAs(context, document, via, got, named, accept) : await documentOf(context, place, document, named), asked['overlay'] ? await documentOf(context, place, asked['overlay']) : undefined];
51
+ if (read !== undefined && 'said' in read)
52
+ return read.said;
53
+ if (read === undefined || (asked['overlay'] && overlay === undefined))
54
+ return [say('document-unread', { document })];
55
+ if (named && digest(read.bytes) !== named)
56
+ 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
+ }))(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
+ const received = async (context, place, document, bytes, named, laid, account) => {
90
+ const [say, got] = [sayIn(context.lock), unpacked(context, document, bytes, await context.files.read.besideOf(context, document, linesOf(bytes)))];
91
+ if (!got.sources)
92
+ return got.said ?? [];
93
+ const [names, said, before] = [got.sources.map((s) => s.source).join(', '), [], await context.policy(place)];
94
+ const refused = coveredIn(context, 'refused' in before ? [] : before.lines, got.sources, named) ? undefined : await unless(context, say('elicit-receive', { place, package: named, sources: names || '—', open: (got.open ?? []).map((o) => o.source).join(', ') || '—' }));
40
95
  if (refused)
41
96
  return refused;
42
- const [doc, overlay] = [await documentOf(context, place, document), asked['overlay'] ? await documentOf(context, place, asked['overlay']) : undefined];
43
- if (doc === undefined || (asked['overlay'] && overlay === undefined))
44
- return [say('document-unread', { document })];
45
- const lands = (lot) => context.landRoot(place, lot, root.account);
97
+ for (const s of got.sources)
98
+ said.push(...(await laid(s.source, s.document.at, s.document, s.overlay, context.machine())));
99
+ 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
+ const [off, landed] = [got.sources.filter((s) => pinOf(s.source) !== s.pin), owed.length ? await context.landRoot(place, owed, account) : undefined];
101
+ 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
+ ...(got.open ?? []).map((o) => say('received-open', { source: o.source, waits: o.waits })), ...(landed && 'refused' in landed ? [say('not-landed', { why: landed.refused })] : [])];
103
+ };
104
+ const laidIn = (context, place, account) => (source, document, doc, overlay, started) => (async () => {
105
+ const say = sayIn(context.lock), v = vocabOf(context.lock), word = (k) => valueOf(context.lock, k), digest = digestIn(context.lock);
106
+ const lands = (lot) => context.landRoot(place, lot, account);
46
107
  const line = (scope, measure, value, more = {}) => canonical({ at: 'policy:head/connect', by: 'target', form: 'alphabet', measure, role: 'writes', scope, value, ...more });
47
108
  const pin = pinIn(context, place, lands, line);
48
- if (`.${doc.kind}` === EXTENSION)
49
- return [say('parent-lock', { place, parent: source })];
50
109
  const worlds = await adopted(context, place, lands, line);
51
110
  if ('refused' in worlds)
52
111
  return [say('not-landed', { why: worlds.refused })];
@@ -60,9 +119,12 @@ export const connected = (asked, context) => ((started) => inPlace(asked, contex
60
119
  context.files.drop(place, at);
61
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;
62
121
  }
122
+ 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
+ const picked = fetched ? await lands([line(`${v.pick}/${source}`, 'id', new URL(declared[0] ?? '/', document).href.replace(/\/$/, ''))]) : undefined;
63
124
  const [owed, paid] = [pinned.lines.filter((l) => l.measure === v.unresolved).map((l) => l.value ?? ''), await paidBy(context, place, pin, laid[0][0])];
64
125
  const classes = pinned.lines.filter((l) => l.measure === v.class && l.scope?.startsWith(`${v.offers}/${source}/`) && l.scope.split('/').length === 3);
65
126
  return [say('connected', { source, place, offers: String(classes.length), reads: String(classes.filter((l) => l.value === v.callable).length), pin: pinned.pin }),
66
127
  ...(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 })])),
67
- ...pinned.lines.filter((l) => l.scope === `${v.region}/${source}` || (l.scope?.startsWith(`${v.keys}/${source}/`) && l.measure === v.scheme)).map((l) => canonical(l))];
68
- }).then((lines) => [...lines, ...redIn(context.lock)('connect/seconds', `connect/${asked['source'] ?? ''}`, started, context.machine())]))(context.machine());
128
+ ...pinned.lines.filter((l) => l.scope === `${v.region}/${source}` || (l.scope?.startsWith(`${v.keys}/${source}/`) && l.measure === v.scheme)).map((l) => canonical(l)),
129
+ ...(picked === undefined ? [] : 'refused' in picked ? [say('not-landed', { why: picked.refused })] : picked.lines)];
130
+ })().then((lines) => [...lines, ...redIn(context.lock)('connect/seconds', `connect/${source}`, started, context.machine())]);
@@ -3,7 +3,7 @@ import { aboutOf, members, valueOf } from "../core/lock.js";
3
3
  /** What is open for a key: the cells view the place's standing declares, as bound folded it, narrowed to the key; a refused fold is said, never read as no view. */
4
4
  export const answer = (asked, context) => inPlace(asked, context, async (place) => {
5
5
  const word = (scope) => valueOf(context.lock, `bound/${scope}`);
6
- const held = await standingOr(context, place);
6
+ const held = await standingOr(context, place, context.policy);
7
7
  if ('said' in held)
8
8
  return held.said;
9
9
  const view = held.lines.find((f) => f.scope?.startsWith('view/') && !f.shape && members(f.value ?? '').includes(word('cells')));
@@ -1,8 +1,8 @@
1
1
  import type { Answer } from '../core/answer.ts';
2
2
  /**
3
3
  * One view of a place, folded by bound at the resolution asked; or, of a source, what it offers as typed lines — at the
4
- * first resolution where it is reached and each offer's class, by default its forms and how access is proven too, never
5
- * what only a transport reads — and, at the call resolution, one read of an offer of the class head reads. Each answer
4
+ * first resolution where it is reached and each offer's class, by default its forms and how access is proven too, with
5
+ * its flows in the order of trust, never what only a transport reads — and, at the call resolution, one read of an offer of the class head reads. Each answer
6
6
  * is measured against answer/seconds and reads red past it, with the question it answered as its coordinate.
7
7
  */
8
8
  export declare const answer: Answer;
@@ -5,10 +5,11 @@ import { vocabOf } from "../core/read.js";
5
5
  import { redIn, sayIn } from "../core/surface.js";
6
6
  import { readSource } from "../flows/read.js";
7
7
  import { standingFor } from "../flows/world.js";
8
+ import { trustOf } from "../core/trust.js";
8
9
  /**
9
10
  * One view of a place, folded by bound at the resolution asked; or, of a source, what it offers as typed lines — at the
10
- * first resolution where it is reached and each offer's class, by default its forms and how access is proven too, never
11
- * what only a transport reads — and, at the call resolution, one read of an offer of the class head reads. Each answer
11
+ * first resolution where it is reached and each offer's class, by default its forms and how access is proven too, with
12
+ * its flows in the order of trust, never what only a transport reads — and, at the call resolution, one read of an offer of the class head reads. Each answer
12
13
  * is measured against answer/seconds and reads red past it, with the question it answered as its coordinate.
13
14
  */
14
15
  export const answer = (asked, context) => ((started) => inPlace(asked, context, async (place) => {
@@ -33,7 +34,7 @@ export const answer = (asked, context) => ((started) => inPlace(asked, context,
33
34
  return false;
34
35
  return !first || (!scope.endsWith(`/${v.form}`) && shallow.includes(l.measure ?? ''));
35
36
  });
36
- return shown.length ? shown.map((l) => canonical(l)).sort() : [sayIn(context.lock)('unread', { source })];
37
+ return shown.length ? [...shown.map((l) => canonical(l)).sort(), ...(first ? [] : trustOf(context.lock, held.lines, source))] : [sayIn(context.lock)('unread', { source })];
37
38
  }
38
39
  const view = `${asked['view'] || word('bound/standing')}${asked['at'] ? `${word('bound/at')}${asked['at']}` : ''}`;
39
40
  return printedOr(context, await context.bound(place, [word('bound/read'), word('bound/view'), view]));
@@ -0,0 +1,7 @@
1
+ import type { Answer } from '../core/answer.ts';
2
+ /**
3
+ * What this device has connected, read off each place's lines as bound folds them and the receipts its own keys signed:
4
+ * every place head's index names — or the one asked — the sources it pinned, the grants that stand today and the last
5
+ * read it kept a receipt of. Nothing is asked, called or signed.
6
+ */
7
+ export declare const answer: Answer;
@@ -0,0 +1,32 @@
1
+ import { LOCK } from '@lapxo/topos/wire';
2
+ import { standingOr } from "../core/answer.js";
3
+ import { under, valueOf } from "../core/lock.js";
4
+ import { sayIn } from "../core/surface.js";
5
+ /**
6
+ * What this device has connected, read off each place's lines as bound folds them and the receipts its own keys signed:
7
+ * every place head's index names — or the one asked — the sources it pinned, the grants that stand today and the last
8
+ * read it kept a receipt of. Nothing is asked, called or signed.
9
+ */
10
+ export const answer = async (asked, context) => {
11
+ const [say, word, home, today] = [sayIn(context.lock), (k) => valueOf(context.lock, k), context.home(), new Date(context.now()).toISOString().slice(0, 10)];
12
+ const index = context.files.has(home, LOCK) ? await standingOr(context, home, context.policy) : { lines: [] };
13
+ if ('said' in index)
14
+ return index.said;
15
+ const places = index.lines.filter((l) => l.scope?.startsWith(`${word('home/family')}/`) && (!asked['place'] || l.value === asked['place']));
16
+ if (!places.length)
17
+ return [say('places-none', { place: asked['place'] ?? '' })];
18
+ const said = [];
19
+ for (const p of places) {
20
+ const held = await standingOr(context, p.value);
21
+ if ('said' in held) {
22
+ said.push(...held.said);
23
+ continue;
24
+ }
25
+ const at = word('receipt/at'), last = held.lines.filter((l) => l.scope?.startsWith(`${word('receipt/family')}/`) && l.at?.startsWith(`${at}:`)).sort((a, b) => (a.at < b.at ? 1 : -1))[0];
26
+ said.push(say('place-held', { name: p.scope.split('/')[1], dir: p.value,
27
+ sources: held.lines.filter((l) => l.scope?.startsWith('uses/') && under(context.lock, 'place/compiled', l.needs)).map((l) => l.scope.slice('uses/'.length)).join(', ') || '—',
28
+ grants: held.lines.filter((l) => l.scope?.startsWith('allow/') && !(l.measure === 'until' && (l.value ?? '') < today)).map((l) => `${l.scope} ${l.measure} ${l.value}`).join('; ') || '—',
29
+ last: last ? `${last.scope.split('/').slice(1, 3).join('/')} at ${last.at.slice(at.length + 1)}` : '—' }));
30
+ }
31
+ return said;
32
+ };
@@ -0,0 +1 @@
1
+ export { shared as answer } from '../core/package.ts';
@@ -0,0 +1 @@
1
+ export { shared as answer } from "../core/package.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lapxo/head",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "An MCP server that folds: it runs bound as a process, hands a model what bound printed, and lands what it read as receipts.",
6
6
  "license": "MIT",
@@ -22,15 +22,16 @@
22
22
  ],
23
23
  "scripts": {
24
24
  "build": "tsc -p . --incremental false --tsBuildInfoFile null",
25
- "prepack": "npm run build",
26
- "test": "node --test test/*.test.ts"
25
+ "prepack": "npm run build && node --test test/pack.test.ts",
26
+ "test": "node --test test/*.test.ts && node --test --test-concurrency=1 test/time/*.test.ts"
27
27
  },
28
28
  "dependencies": {
29
- "@lapxo/bound": "0.1.6",
30
- "@lapxo/topos": "0.1.12",
29
+ "@lapxo/bound": "0.2.1",
30
+ "@lapxo/topos": "0.2.0",
31
31
  "@modelcontextprotocol/sdk": "1.32.1"
32
32
  },
33
33
  "devDependencies": {
34
+ "@anthropic-ai/mcpb": "2.1.2",
34
35
  "@types/node": "22.20.5",
35
36
  "typescript": "5.9.3"
36
37
  }
File without changes
File without changes