@lapxo/head 0.0.0-stage → 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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/TARGET.bound +786 -0
  3. package/dist/src/access/contract.d.ts +71 -0
  4. package/dist/src/access/contract.js +6 -0
  5. package/dist/src/access/obtain.d.ts +19 -0
  6. package/dist/src/access/obtain.js +70 -0
  7. package/dist/src/adopt/adopt.d.ts +12 -0
  8. package/dist/src/adopt/adopt.js +40 -0
  9. package/dist/src/compile/pin.d.ts +26 -0
  10. package/dist/src/compile/pin.js +74 -0
  11. package/dist/src/core/act.d.ts +18 -0
  12. package/dist/src/core/act.js +18 -0
  13. package/dist/src/core/answer.d.ts +102 -0
  14. package/dist/src/core/answer.js +26 -0
  15. package/dist/src/core/device.d.ts +26 -0
  16. package/dist/src/core/device.js +27 -0
  17. package/dist/src/core/lock.d.ts +11 -0
  18. package/dist/src/core/lock.js +13 -0
  19. package/dist/src/core/package.d.ts +54 -0
  20. package/dist/src/core/package.js +65 -0
  21. package/dist/src/core/read.d.ts +54 -0
  22. package/dist/src/core/read.js +64 -0
  23. package/dist/src/core/receipt.d.ts +13 -0
  24. package/dist/src/core/receipt.js +12 -0
  25. package/dist/src/core/surface.d.ts +6 -0
  26. package/dist/src/core/surface.js +7 -0
  27. package/dist/src/core/trust.d.ts +7 -0
  28. package/dist/src/core/trust.js +19 -0
  29. package/dist/src/core/worlds.d.ts +10 -0
  30. package/dist/src/core/worlds.js +9 -0
  31. package/dist/src/flows/act.d.ts +7 -0
  32. package/dist/src/flows/act.js +43 -0
  33. package/dist/src/flows/answer.d.ts +12 -0
  34. package/dist/src/flows/answer.js +25 -0
  35. package/dist/src/flows/call.d.ts +50 -0
  36. package/dist/src/flows/call.js +76 -0
  37. package/dist/src/flows/device.d.ts +26 -0
  38. package/dist/src/flows/device.js +32 -0
  39. package/dist/src/flows/plan.d.ts +8 -0
  40. package/dist/src/flows/plan.js +51 -0
  41. package/dist/src/flows/read.d.ts +9 -0
  42. package/dist/src/flows/read.js +95 -0
  43. package/dist/src/flows/world.d.ts +12 -0
  44. package/dist/src/flows/world.js +29 -0
  45. package/dist/src/index.d.ts +2 -0
  46. package/dist/src/index.js +138 -0
  47. package/dist/src/onboard/first.d.ts +7 -0
  48. package/dist/src/onboard/first.js +21 -0
  49. package/dist/src/places/admit.d.ts +7 -0
  50. package/dist/src/places/admit.js +32 -0
  51. package/dist/src/places/found.d.ts +7 -0
  52. package/dist/src/places/found.js +60 -0
  53. package/dist/src/ports/access/authorizer.d.ts +10 -0
  54. package/dist/src/ports/access/authorizer.js +34 -0
  55. package/dist/src/ports/access/flow.d.ts +13 -0
  56. package/dist/src/ports/access/flow.js +130 -0
  57. package/dist/src/ports/access/page.d.ts +12 -0
  58. package/dist/src/ports/access/page.js +50 -0
  59. package/dist/src/ports/access/person.d.ts +26 -0
  60. package/dist/src/ports/access/person.js +41 -0
  61. package/dist/src/ports/adapters/request.d.ts +41 -0
  62. package/dist/src/ports/adapters/request.js +69 -0
  63. package/dist/src/ports/bound.d.ts +61 -0
  64. package/dist/src/ports/bound.js +85 -0
  65. package/dist/src/ports/cache.d.ts +4 -0
  66. package/dist/src/ports/cache.js +14 -0
  67. package/dist/src/ports/documents.d.ts +34 -0
  68. package/dist/src/ports/documents.js +64 -0
  69. package/dist/src/ports/keychain.d.ts +8 -0
  70. package/dist/src/ports/keychain.js +12 -0
  71. package/dist/src/ports/keys.d.ts +14 -0
  72. package/dist/src/ports/keys.js +20 -0
  73. package/dist/src/ports/place.d.ts +11 -0
  74. package/dist/src/ports/place.js +32 -0
  75. package/dist/src/ports/process.d.ts +29 -0
  76. package/dist/src/ports/process.js +51 -0
  77. package/dist/src/ports/signers.d.ts +6 -0
  78. package/dist/src/ports/signers.js +14 -0
  79. package/dist/src/ports/spare.d.ts +1 -0
  80. package/dist/src/ports/spare.js +33 -0
  81. package/dist/src/ports/stores/credential-manager.d.ts +3 -0
  82. package/dist/src/ports/stores/credential-manager.js +22 -0
  83. package/dist/src/ports/stores/keychain.d.ts +3 -0
  84. package/dist/src/ports/stores/keychain.js +16 -0
  85. package/dist/src/ports/stores/secret-tool.d.ts +3 -0
  86. package/dist/src/ports/stores/secret-tool.js +16 -0
  87. package/dist/src/ports/surface.d.ts +126 -0
  88. package/dist/src/ports/surface.js +48 -0
  89. package/dist/src/ports/warm.d.ts +19 -0
  90. package/dist/src/ports/warm.js +46 -0
  91. package/dist/src/resources/view.d.ts +1 -0
  92. package/dist/src/resources/view.js +1 -0
  93. package/dist/src/signer.d.ts +2 -0
  94. package/dist/src/signer.js +14 -0
  95. package/dist/src/sources/access.d.ts +17 -0
  96. package/dist/src/sources/access.js +39 -0
  97. package/dist/src/sources/connect.d.ts +13 -0
  98. package/dist/src/sources/connect.js +130 -0
  99. package/dist/src/tools/access.d.ts +1 -0
  100. package/dist/src/tools/access.js +1 -0
  101. package/dist/src/tools/act.d.ts +1 -0
  102. package/dist/src/tools/act.js +1 -0
  103. package/dist/src/tools/admit.d.ts +1 -0
  104. package/dist/src/tools/admit.js +1 -0
  105. package/dist/src/tools/card.d.ts +3 -0
  106. package/dist/src/tools/card.js +13 -0
  107. package/dist/src/tools/connect.d.ts +1 -0
  108. package/dist/src/tools/connect.js +1 -0
  109. package/dist/src/tools/key.d.ts +3 -0
  110. package/dist/src/tools/key.js +11 -0
  111. package/dist/src/tools/land.d.ts +1 -0
  112. package/dist/src/tools/land.js +1 -0
  113. package/dist/src/tools/look.d.ts +8 -0
  114. package/dist/src/tools/look.js +41 -0
  115. package/dist/src/tools/place.d.ts +1 -0
  116. package/dist/src/tools/place.js +1 -0
  117. package/dist/src/tools/places.d.ts +7 -0
  118. package/dist/src/tools/places.js +32 -0
  119. package/dist/src/tools/plan.d.ts +1 -0
  120. package/dist/src/tools/plan.js +1 -0
  121. package/dist/src/tools/share.d.ts +1 -0
  122. package/dist/src/tools/share.js +1 -0
  123. package/dist/src/walk/context.d.ts +7 -0
  124. package/dist/src/walk/context.js +23 -0
  125. package/package.json +36 -4
  126. package/README.md +0 -3
@@ -0,0 +1,46 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { grouped, watched } from "./process.js";
3
+ /** How many processes head keeps started ahead of each program while it answers a call, and the variables node reads as it starts: a process started under other values of them is never asked. */
4
+ export const warmth = { spares: 0, boot: [] };
5
+ const spares = new Map();
6
+ const alive = (one) => one.child.exitCode === null && one.child.signalCode === null;
7
+ const entry = fileURLToPath(new URL(`./spare${import.meta.url.slice(import.meta.url.lastIndexOf('.'))}`, import.meta.url));
8
+ const bootOf = (env) => JSON.stringify(Object.entries(env).filter(([name]) => warmth.boot.some((p) => name.startsWith(p))).sort(([a], [b]) => (a < b ? -1 : 1)));
9
+ const stop = (one) => { try {
10
+ process.kill(-one.child.pid, 'SIGKILL');
11
+ }
12
+ catch {
13
+ one.child.kill('SIGKILL');
14
+ } };
15
+ const started = (program) => {
16
+ const env = { ...process.env }, child = grouped(process.execPath, [entry, program], { env }, 4);
17
+ child.stdin.on('error', () => undefined).end('');
18
+ const spare = { child, boot: bootOf(env), ready: new Promise((resolve) => { child.stdio[3].on('error', () => undefined).once('data', () => { spare.loaded = true; resolve(); }); child.once('close', () => resolve()); }), loaded: false };
19
+ return spare;
20
+ };
21
+ /** Keeps as many processes started ahead of a program as head's lock says; resolved when each has loaded what it runs. */
22
+ export const kept = (program) => {
23
+ const live = (spares.get(program) ?? []).filter(alive);
24
+ while (live.length < warmth.spares)
25
+ live.push(started(program));
26
+ spares.set(program, live);
27
+ return Promise.all(live.map((one) => one.ready));
28
+ };
29
+ /** Every process still waiting to be asked is stopped: head keeps none past the call it started them for. */
30
+ export const cooled = () => { for (const one of [...spares.values()].flat())
31
+ stop(one); spares.clear(); };
32
+ process.once('exit', cooled);
33
+ /**
34
+ * A program run in a process started ahead under the same starting variables, handed the folder, arguments and
35
+ * variables a process started now would get, and watched within the same bounds; undefined when none is held ready,
36
+ * so the caller starts one as before and never waits on one still loading. Each process answers one ask and ends.
37
+ */
38
+ export const warmed = (program, args, at, bounds) => {
39
+ const live = spares.get(program) ?? [], index = live.findIndex((one) => one.loaded && alive(one) && one.boot === bootOf(at.env));
40
+ const spare = index < 0 ? undefined : live.splice(index, 1)[0];
41
+ if (spare === undefined)
42
+ return undefined;
43
+ void kept(program);
44
+ spare.child.stdio[3].end(`${JSON.stringify({ cwd: at.cwd, args, env: at.env })}\n`);
45
+ return watched(spare.child, bounds);
46
+ };
@@ -0,0 +1 @@
1
+ export { answer } from '../tools/look.ts';
@@ -0,0 +1 @@
1
+ export { answer } from "../tools/look.js";
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { signLot } from "./ports/keys.js";
4
+ import { keychain } from "./ports/keychain.js";
5
+ const [service = '', account = '', key = '', algorithm = '', store = 'keychain'] = process.argv.slice(2);
6
+ const secret = keychain(service, store).read(account);
7
+ if (secret === undefined)
8
+ process.exit(1);
9
+ try {
10
+ process.stdout.write(signLot(readFileSync(0, 'utf8'), secret, key, algorithm));
11
+ }
12
+ catch {
13
+ process.exit(1);
14
+ }
@@ -0,0 +1,17 @@
1
+ import type { Asked, Context } from '../core/answer.ts';
2
+ import type { Call } from '../core/read.ts';
3
+ import type { Attachment } from '../access/contract.ts';
4
+ /**
5
+ * Access to a source obtained ahead of any call, at the origin the person picked once where several are declared: the
6
+ * way its document says access is proven, run by the adapter of
7
+ * that kind — the person's own page, or the provider they are sent to with a client registered for the visit — its
8
+ * secret kept in this keychain and its obtaining landed as a receipt the device key signs. Nothing is called. What it
9
+ * attaches is what a fetch made as that source carries, to that source's own origin alone.
10
+ */
11
+ export declare const accessed: (asked: Asked, context: Context) => Promise<readonly string[]>;
12
+ export declare const attachedFor: (context: Context, place: string, source: string, asked?: string) => Promise<{
13
+ readonly plan: Call;
14
+ readonly attachment: Attachment | undefined;
15
+ } | {
16
+ readonly said: readonly string[];
17
+ }>;
@@ -0,0 +1,39 @@
1
+ import { inPlace, placeNameOf } from "../core/answer.js";
2
+ import { uncoveredIn } from "../core/device.js";
3
+ import { valueOf } from "../core/lock.js";
4
+ import { planRead, vocabOf } from "../core/read.js";
5
+ import { sayIn } from "../core/surface.js";
6
+ import { attachmentFor } from "../access/obtain.js";
7
+ import { pickedOrigin } from "../flows/call.js";
8
+ import { standingFor } from "../flows/world.js";
9
+ /**
10
+ * Access to a source obtained ahead of any call, at the origin the person picked once where several are declared: the
11
+ * way its document says access is proven, run by the adapter of
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. What it
14
+ * attaches is what a fetch made as that source carries, to that source's own origin alone.
15
+ */
16
+ export const accessed = (asked, context) => inPlace(asked, context, async (place) => {
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)];
22
+ const held = await standingFor(context, place);
23
+ if ('unsettled' in held)
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];
26
+ if (operation === undefined)
27
+ return { said: [say('unread', { source })] };
28
+ const picked = await pickedOrigin(context, place, held.lines, source, planRead(held.lines, context.lock, source, operation, placeNameOf(place)));
29
+ if ('said' in picked)
30
+ return picked;
31
+ const plan = planRead(picked.lines, context.lock, source, operation, placeNameOf(place));
32
+ if (plan.kind === 'said')
33
+ return { said: [say(plan.key, plan.fields)] };
34
+ const stopped = uncoveredIn(context.lock, held.lines, context.device('head'), place, [], [`${valueOf(context.lock, 'access/family')}/${source}/${plan.access.scheme || v.anonymous}/obtained`]);
35
+ if (stopped)
36
+ return { said: stopped };
37
+ const got = await attachmentFor(context, place, picked.lines, plan);
38
+ return 'said' in got ? { said: [say(got.said, { source, operation, kinds: plan.access.kind, input: got.input })] } : { plan, attachment: got.attachment };
39
+ };
@@ -0,0 +1,13 @@
1
+ import type { Asked, Context } from '../core/answer.ts';
2
+ /**
3
+ * 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
+ * by name, since composing two places' policies is bound's. A document — and an overlay beside it — is laid whatever its
5
+ * kind, with every world head carries adopted, and the fold decides which reads it: what no world compiles is open, the
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
+ * 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. 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.
12
+ */
13
+ export declare const connected: (asked: Asked, context: Context, covered?: boolean) => Promise<readonly string[]>;
@@ -0,0 +1,130 @@
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
+ import { valueOf } from "../core/lock.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";
9
+ import { paidBy, pinIn } from "../compile/pin.js";
10
+ import { adopted } from "../adopt/adopt.js";
11
+ import { firstRun } from "../onboard/first.js";
12
+ import { rootIn } from "../flows/device.js";
13
+ import { bornAt } from "../places/found.js";
14
+ import { untilOf } from "../core/act.js";
15
+ import { attachedFor } from "./access.js";
16
+ /**
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
18
+ * by name, since composing two places' policies is bound's. A document — and an overlay beside it — is laid whatever its
19
+ * kind, with every world head carries adopted, and the fold decides which reads it: what no world compiles is open, the
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
21
+ * same bytes again find their compiled standing by name — and pinned from the place's store: a new pin lands with the
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.
26
+ */
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(', ') || '—' }));
95
+ if (refused)
96
+ return refused;
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);
107
+ const line = (scope, measure, value, more = {}) => canonical({ at: 'policy:head/connect', by: 'target', form: 'alphabet', measure, role: 'writes', scope, value, ...more });
108
+ const pin = pinIn(context, place, lands, line);
109
+ const worlds = await adopted(context, place, lands, line);
110
+ if ('refused' in worlds)
111
+ return [say('not-landed', { why: worlds.refused })];
112
+ const laid = [[`${word('place/connected')}/${source}.${doc.kind}`, doc.bytes], ...(overlay ? [[`${word('place/connected')}/${source}.overlay.${overlay.kind}`, overlay.bytes]] : [])];
113
+ for (const [at, bytes] of laid)
114
+ context.files.lay(place, at, bytes);
115
+ const pinned = await pin(source, laid.map(([at, bytes]) => `${at} ${digest(bytes)}`));
116
+ if ('said' in pinned) {
117
+ if (pinned.open || pinned.dropped)
118
+ for (const [at] of laid)
119
+ 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;
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;
124
+ const [owed, paid] = [pinned.lines.filter((l) => l.measure === v.unresolved).map((l) => l.value ?? ''), await paidBy(context, place, pin, laid[0][0])];
125
+ 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
+ ...(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 })])),
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())]);
@@ -0,0 +1 @@
1
+ export { accessed as answer } from '../sources/access.ts';
@@ -0,0 +1 @@
1
+ export { accessed as answer } from "../sources/access.js";
@@ -0,0 +1 @@
1
+ export { act as answer } from '../flows/act.ts';
@@ -0,0 +1 @@
1
+ export { act as answer } from "../flows/act.js";
@@ -0,0 +1 @@
1
+ export { admitted as answer } from '../places/admit.ts';
@@ -0,0 +1 @@
1
+ export { admitted as answer } from "../places/admit.js";
@@ -0,0 +1,3 @@
1
+ import type { Answer } from '../core/answer.ts';
2
+ /** 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. */
3
+ export declare const answer: Answer;
@@ -0,0 +1,13 @@
1
+ import { inPlace, printedOr, standingOr } from "../core/answer.js";
2
+ import { aboutOf, members, valueOf } from "../core/lock.js";
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
+ export const answer = (asked, context) => inPlace(asked, context, async (place) => {
5
+ const word = (scope) => valueOf(context.lock, `bound/${scope}`);
6
+ const held = await standingOr(context, place, context.policy);
7
+ if ('said' in held)
8
+ return held.said;
9
+ const view = held.lines.find((f) => f.scope?.startsWith('view/') && !f.shape && members(f.value ?? '').includes(word('cells')));
10
+ if (view === undefined)
11
+ return [aboutOf(context.lock, 'said/no-cells')];
12
+ return printedOr(context, await context.bound(place, [word('read'), word('view'), view.scope.split('/')[1], ...(asked['key'] ? [word('key'), asked['key']] : [])]));
13
+ });
@@ -0,0 +1 @@
1
+ export { connected as answer } from '../sources/connect.ts';
@@ -0,0 +1 @@
1
+ export { connected as answer } from "../sources/connect.js";
@@ -0,0 +1,3 @@
1
+ import type { Answer } from '../core/answer.ts';
2
+ /** The lines that admit this device: its key is read from the keychain, or made there once; only its public half goes out. */
3
+ export declare const answer: Answer;
@@ -0,0 +1,11 @@
1
+ import { admissionOf, deviceOf } from "../core/device.js";
2
+ import { aboutOf } from "../core/lock.js";
3
+ import { deviceKey } from "../flows/device.js";
4
+ /** The lines that admit this device: its key is read from the keychain, or made there once; only its public half goes out. */
5
+ export const answer = async (asked, context) => {
6
+ const role = asked['who'] || undefined;
7
+ if (!deviceOf(context.lock, role).account)
8
+ return [aboutOf(context.lock, 'said/no-role')];
9
+ const key = deviceKey(context, role);
10
+ return key === undefined ? [aboutOf(context.lock, 'said/no-keychain')] : admissionOf(context.lock, key, role);
11
+ };
@@ -0,0 +1 @@
1
+ export { landLot as answer } from '../flows/plan.ts';
@@ -0,0 +1 @@
1
+ export { landLot as answer } from "../flows/plan.js";
@@ -0,0 +1,8 @@
1
+ import type { Answer } from '../core/answer.ts';
2
+ /**
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, 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
+ * is measured against answer/seconds and reads red past it, with the question it answered as its coordinate.
7
+ */
8
+ export declare const answer: Answer;
@@ -0,0 +1,41 @@
1
+ import { canonical } from '@lapxo/topos/wire';
2
+ import { inPlace, printedOr } from "../core/answer.js";
3
+ import { valueOf } from "../core/lock.js";
4
+ import { vocabOf } from "../core/read.js";
5
+ import { redIn, sayIn } from "../core/surface.js";
6
+ import { readSource } from "../flows/read.js";
7
+ import { standingFor } from "../flows/world.js";
8
+ import { trustOf } from "../core/trust.js";
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
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
13
+ * is measured against answer/seconds and reads red past it, with the question it answered as its coordinate.
14
+ */
15
+ export const answer = (asked, context) => ((started) => inPlace(asked, context, async (place) => {
16
+ const word = (scope) => valueOf(context.lock, scope);
17
+ const source = asked['source'];
18
+ if (source) {
19
+ if (asked['operation'] && (asked['at'] === word('look/call') || asked['history'] === 'true'))
20
+ return readSource(asked, context);
21
+ const held = await standingFor(context, place);
22
+ if ('unsettled' in held)
23
+ return [sayIn(context.lock)('unsettled', { source, why: held.unsettled })];
24
+ const [v, first, shallow] = [vocabOf(context.lock), (asked['at'] ?? word('look/default')) === '0', word('look/first').split('|')];
25
+ const offer = `${v.offers}/${source}/${asked['operation'] ?? ''}`;
26
+ const shown = held.lines.filter((l) => {
27
+ const scope = l.scope ?? '';
28
+ if (scope === `${v.region}/${source}`)
29
+ return true;
30
+ if (scope.startsWith(`${v.keys}/${source}/`))
31
+ return !first;
32
+ const ofOffer = asked['operation'] ? scope === offer || scope.startsWith(`${offer}/`) : scope.startsWith(offer);
33
+ if (!ofOffer || scope.endsWith(`/${v.carry}`))
34
+ return false;
35
+ return !first || (!scope.endsWith(`/${v.form}`) && shallow.includes(l.measure ?? ''));
36
+ });
37
+ return shown.length ? [...shown.map((l) => canonical(l)).sort(), ...(first ? [] : trustOf(context.lock, held.lines, source))] : [sayIn(context.lock)('unread', { source })];
38
+ }
39
+ const view = `${asked['view'] || word('bound/standing')}${asked['at'] ? `${word('bound/at')}${asked['at']}` : ''}`;
40
+ return printedOr(context, await context.bound(place, [word('bound/read'), word('bound/view'), view]));
41
+ }).then((lines) => [...lines, ...redIn(context.lock)('answer/seconds', `answer/${asked['source'] ? `${asked['source']}/${asked['operation'] ?? ''}` : asked['view'] || valueOf(context.lock, 'bound/standing')}@${asked['at'] ?? valueOf(context.lock, 'look/default')}`, started, context.machine())]))(context.machine());
@@ -0,0 +1 @@
1
+ export { bornPlace as answer } from '../places/found.ts';
@@ -0,0 +1 @@
1
+ export { bornPlace as answer } from "../places/found.js";
@@ -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 { planLot as answer } from '../flows/plan.ts';
@@ -0,0 +1 @@
1
+ export { planLot as answer } from "../flows/plan.js";
@@ -0,0 +1 @@
1
+ export { shared as answer } from '../core/package.ts';
@@ -0,0 +1 @@
1
+ export { shared as answer } from "../core/package.js";
@@ -0,0 +1,7 @@
1
+ import type { Context, Outcome } from '../core/answer.ts';
2
+ /**
3
+ * A place made walkable once it is founded: the context another place authenticates it by — its wire, its keys and its
4
+ * origin, read off the lines it was born with — folded by bound into a standing, laid beside its lock and pinned by its
5
+ * root, so a peer selects it by digest. Its history is offered only as its walk view declares, and only when its own key walks it.
6
+ */
7
+ export declare const walkable: (context: Context, dir: string, account: string) => Promise<Outcome>;
@@ -0,0 +1,23 @@
1
+ import { join } from 'node:path';
2
+ import { canonical, LOCK } from '@lapxo/topos/wire';
3
+ import { members, valueOf } from "../core/lock.js";
4
+ import { linesOf } from "../core/worlds.js";
5
+ /**
6
+ * A place made walkable once it is founded: the context another place authenticates it by — its wire, its keys and its
7
+ * origin, read off the lines it was born with — folded by bound into a standing, laid beside its lock and pinned by its
8
+ * root, so a peer selects it by digest. Its history is offered only as its walk view declares, and only when its own key walks it.
9
+ */
10
+ export const walkable = async (context, dir, account) => {
11
+ const [word, at] = [(k) => valueOf(context.lock, k), valueOf(context.lock, 'walk/context/file')];
12
+ const reads = members(word('walk/context/reads'));
13
+ const born = linesOf(context.files.take(join(dir, LOCK)) ?? new Uint8Array()).filter((l) => reads.some((r) => (r.endsWith('/**') ? l.scope?.startsWith(r.slice(0, -2)) : l.scope === r)) && l.value !== 'withdraw');
14
+ const draft = at.replace(/\.bound$/, '.draft.bound');
15
+ context.files.lay(dir, draft, new TextEncoder().encode(`${[...born.map(({ sig: _s, epoch: _e, ...l }) => canonical({ ...l, by: 'target' })),
16
+ canonical({ at: 'policy:head/walk', by: 'target', form: 'alphabet', measure: 'reads', role: 'writes', scope: word('walk/context/region'), value: reads.join('|') })].join('\n')}\n`));
17
+ const pinned = await context.pinned(dir, draft);
18
+ context.files.drop(dir, draft);
19
+ if (pinned === undefined || 'refused' in pinned)
20
+ return { refused: pinned === undefined ? 'walk context folded nothing' : pinned.refused };
21
+ context.files.lay(dir, at, new TextEncoder().encode(pinned.bytes));
22
+ return context.landRoot(dir, [canonical({ at: 'policy:head/walk', by: 'target', form: 'alphabet', measure: 'digest', needs: at, role: 'writes', scope: 'walk/context', value: pinned.pin })], account);
23
+ };
package/package.json CHANGED
@@ -1,6 +1,38 @@
1
1
  {
2
2
  "name": "@lapxo/head",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.3",
4
+ "type": "module",
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
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Lapxo/head.git"
10
+ },
11
+ "author": "Lapxo",
12
+ "engines": {
13
+ "node": ">=22.18"
14
+ },
15
+ "bin": {
16
+ "head": "./dist/src/index.js"
17
+ },
18
+ "files": [
19
+ "dist/src",
20
+ "TARGET.bound",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc -p . --incremental false --tsBuildInfoFile null",
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
+ },
28
+ "dependencies": {
29
+ "@lapxo/bound": "0.2.1",
30
+ "@lapxo/topos": "0.2.0",
31
+ "@modelcontextprotocol/sdk": "1.32.1"
32
+ },
33
+ "devDependencies": {
34
+ "@anthropic-ai/mcpb": "2.1.2",
35
+ "@types/node": "22.20.5",
36
+ "typescript": "5.9.3"
37
+ }
38
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.