@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,54 @@
1
+ import { type Asked, type Context } from './answer.ts';
2
+ import { type Said } from './lock.ts';
3
+ /** A line no package carries: one that is not a world, or a source's document, overlay or pin, said once by digest — with, beside a digest that names bytes, the encrypted address they are read at. */
4
+ export declare const foreignIn: (lines: readonly Said[]) => Said | undefined;
5
+ /** A place packed as its published lock would name it, into a folder inside it or one the client shares, apart from what head and bound keep: each world its sources were compiled under, and each source — document, overlay, and compiled pin resting on the sources whose issuers it names — by digest, beside the bytes those digests name, and where the person says they are also published, at that encrypted address. No line of access, key or grant: a package grants nothing, and nothing is asked, signed or landed. */
6
+ export declare const shared: (asked: Asked, context: Context) => Promise<readonly string[]>;
7
+ /** What a package names, checked whole before anything is asked: every line a world or a source's document, overlay or pin, by digest; each world the one this head pins; an order its pins rest in. A source whose bytes did not arrive, or did not hash to their line, is open, and so is every source resting on it: absence is not a refusal, and the rest arrives. */
8
+ export declare const unpacked: (context: Context, document: string, bytes: Uint8Array, read: ReadonlyMap<string, Uint8Array | undefined>) => {
9
+ said: string[];
10
+ sources?: undefined;
11
+ open?: undefined;
12
+ } | {
13
+ sources: ({
14
+ overlay: {
15
+ at: string;
16
+ bytes: Uint8Array<ArrayBufferLike>;
17
+ kind: string;
18
+ };
19
+ source: string;
20
+ pin: string;
21
+ document: {
22
+ at: string;
23
+ bytes: Uint8Array<ArrayBufferLike>;
24
+ kind: string;
25
+ };
26
+ } | {
27
+ overlay?: undefined;
28
+ source: string;
29
+ pin: string;
30
+ document: {
31
+ at: string;
32
+ bytes: Uint8Array<ArrayBufferLike>;
33
+ kind: string;
34
+ };
35
+ })[];
36
+ open: {
37
+ source: string;
38
+ owed: string;
39
+ waits: string;
40
+ }[];
41
+ said?: undefined;
42
+ };
43
+ /** Whether the person-s yes to a package already covers what arrives now: every source either pinned as the package names it, or owed by a demand that yes to this same package landed. */
44
+ export declare const coveredIn: (context: Context, held: readonly Said[], arrived: readonly {
45
+ readonly source: string;
46
+ readonly pin: string;
47
+ }[], named: string) => boolean;
48
+ /** What a received package leaves owed in the place: a demand per open source naming what it waits on, and the withdraw of a demand a source that arrived has paid, or one a new package names otherwise. */
49
+ export declare const owedIn: (context: Context, held: readonly Said[], arrived: readonly {
50
+ readonly source: string;
51
+ }[], open: readonly {
52
+ readonly source: string;
53
+ readonly owed: string;
54
+ }[], named: string) => readonly string[];
@@ -0,0 +1,65 @@
1
+ import { dirname, extname, join, relative, sep } from 'node:path';
2
+ import { canonical, LOCK, parse } from '@lapxo/topos/wire';
3
+ import { inPlace, standingOr, within } from "./answer.js";
4
+ import { members, under, valueOf } from "./lock.js";
5
+ import { fieldsOf, vocabOf } from "./read.js";
6
+ import { sayIn } from "./surface.js";
7
+ import { digestIn, linesOf } from "./worlds.js";
8
+ /** A line no package carries: one that is not a world, or a source's document, overlay or pin, said once by digest — with, beside a digest that names bytes, the encrypted address they are read at. */
9
+ export const foreignIn = (lines) => lines.find((l) => !/^(sources\/[a-z0-9-]+(\/overlay)?|uses\/[a-z0-9-]+)$/.test(l.scope ?? '') || lines.some((o) => o !== l && o.scope === l.scope && o.measure === l.measure)
10
+ || (l.measure !== 'digest' && !(l.measure === 'id' && URL.canParse(l.value ?? '') && lines.some((o) => o.scope === l.scope && o.measure === 'digest' && o.needs))));
11
+ const orderOf = (all, out = []) => ((next) => (next === undefined ? (out.length === all.length ? out : undefined) : orderOf(all, [...out, next])))(all.find((s) => !out.includes(s) && s.rests.every((r) => out.some((o) => o.source === r))));
12
+ const packed = (context, place, own, urls) => {
13
+ const [word, v, digest, held] = [(k) => valueOf(context.lock, k), vocabOf(context.lock), digestIn(context.lock), (rel) => context.files.take(join(place, rel)) ?? new Uint8Array()];
14
+ const row = (scope, value, more = {}) => [canonical({ at: 'policy:head/share', by: 'target', form: 'alphabet', measure: 'digest', role: 'writes', scope, value, ...more }),
15
+ ...(more['needs'] && URL.canParse(urls[more['needs']] ?? '') ? [canonical({ at: 'policy:head/share', by: 'target', form: 'alphabet', measure: 'id', role: 'writes', scope, value: urls[more['needs']] })] : [])];
16
+ const pins = own.filter((l) => l.scope?.startsWith('uses/') && under(context.lock, 'place/compiled', l.needs)).map((l) => ((source) => ({ source, pin: l.value, needs: l.needs, lines: linesOf(held(l.needs)), laid: context.files.read.laidOf(context, place, source) }))(l.scope.slice('uses/'.length)));
17
+ const declares = new Map(pins.flatMap((p) => (fieldsOf(p.lines, `${v.region}/${p.source}`)[v.issuers] ?? []).map((issuer) => [issuer, p.source])));
18
+ const rested = pins.map((p) => ({ ...p, rests: [...new Set(p.lines.filter((l) => l.measure === 'issuer').flatMap((l) => declares.get(l.value ?? '') ?? []))].filter((s) => s !== p.source).sort() })), sources = orderOf(rested) ?? rested;
19
+ return { names: sources.map((s) => s.source), files: sources.flatMap((s) => [...s.laid, [s.needs, held(s.needs)]]), lines: [...own.filter((l) => members(word('world/adopts')).some((w) => l.scope === `uses/${w}`)).flatMap((l) => row(l.scope, l.value)),
20
+ ...sources.flatMap((s) => [...s.laid.flatMap(([rel, bytes]) => row(/\.overlay\.[^.]+$/.test(rel) ? `sources/${s.source}/overlay` : `sources/${s.source}`, digest(bytes), { needs: rel })),
21
+ ...row(`uses/${s.source}`, s.pin, { needs: s.needs, ...(s.rests.length ? { restsOn: s.rests.map((r) => `uses/${r}`).join('|') } : {}) })])] };
22
+ };
23
+ /** A place packed as its published lock would name it, into a folder inside it or one the client shares, apart from what head and bound keep: each world its sources were compiled under, and each source — document, overlay, and compiled pin resting on the sources whose issuers it names — by digest, beside the bytes those digests name, and where the person says they are also published, at that encrypted address. No line of access, key or grant: a package grants nothing, and nothing is asked, signed or landed. */
24
+ export const shared = (asked, context) => inPlace(asked, context, async (place) => {
25
+ const [say, to, held] = [sayIn(context.lock), asked['to'] ?? '', await standingOr(context, place, context.policy)], [rel, there] = [relative(place, to), linesOf(context.files.take(join(to, LOCK)) ?? new Uint8Array())];
26
+ if (!(await within(context, place, to)) || (!rel.startsWith('..') && (rel === '' || rel.startsWith('.') || [valueOf(context.lock, 'place/connected'), ...members(valueOf(context.lock, 'place/compiled')), valueOf(context.lock, 'walk/context/file'), valueOf(context.lock, 'place/worlds')].some((at) => rel.split(sep)[0] === at.split('/')[0]))) || foreignIn(there))
27
+ return [say('share-to', { place, to })];
28
+ if ('said' in held)
29
+ return held.said;
30
+ const pack = packed(context, place, held.lines, ((u) => (u && typeof u === 'object' ? u : {}))((() => { try {
31
+ return JSON.parse(asked['urls'] || '{}');
32
+ }
33
+ catch {
34
+ return {};
35
+ } })())), bytes = new TextEncoder().encode(`${pack.lines.join('\n')}\n`);
36
+ return !pack.names.length ? [say('share-none', { place })] : ([...pack.files, [LOCK, bytes]].forEach(([at, laid]) => context.files.lay(to, at, laid)), [say('shared', { place, to, sources: pack.names.join(', '), digest: digestIn(context.lock)(bytes) }), ...pack.lines]);
37
+ });
38
+ /** What a package names, checked whole before anything is asked: every line a world or a source's document, overlay or pin, by digest; each world the one this head pins; an order its pins rest in. A source whose bytes did not arrive, or did not hash to their line, is open, and so is every source resting on it: absence is not a refusal, and the rest arrives. */
39
+ export const unpacked = (context, document, bytes, read) => {
40
+ const [say, word, digest, lines] = [sayIn(context.lock), (k) => valueOf(context.lock, k), digestIn(context.lock), linesOf(bytes)], said = lines.filter((l) => l.measure === 'digest'), at = (scope) => said.find((l) => l.scope === scope);
41
+ const unread = new TextDecoder().decode(bytes).split('\n').find((l) => l && parse(l).kind !== 'fact'), name = (l) => l.scope.split('/')[1];
42
+ const odd = unread === undefined ? foreignIn(lines) ?? said.find((l) => (l.scope.startsWith('uses/') ? (l.needs ? !at(`sources/${name(l)}`)?.needs : !word(`world/${name(l)}/standing`))
43
+ : !l.needs || !at(`uses/${name(l)}`)?.needs || l.needs.split('/').includes('..') || l.needs.startsWith('/'))) : { scope: unread.slice(0, 80) };
44
+ if (odd)
45
+ return { said: [say('package-line', { document, scope: odd.scope ?? '' })] };
46
+ const world = said.find((l) => !l.needs && l.value !== word(`world/${name(l)}/standing`)), where = (l) => lines.find((o) => o.scope === l.scope && o.measure === 'id')?.value ?? (URL.canParse(document) ? new URL(l.needs, document).href : join(dirname(document), l.needs));
47
+ if (world)
48
+ return { said: [say('package-world', { document, world: name(world), pin: world.value, held: word(`world/${name(world)}/standing`) })] };
49
+ const off = (l) => l.needs !== undefined && digest(read.get(l.needs) ?? '') !== l.value, of = (scope) => ((l) => l && { at: where(l), bytes: read.get(l.needs), kind: extname(l.needs).slice(1) })(at(scope));
50
+ const order = orderOf(said.filter((l) => l.scope.startsWith('uses/') && l.needs).map((l) => ({ source: name(l), pin: l.value, rests: (l.restsOn ?? '').split('|').filter(Boolean).map((r) => r.slice('uses/'.length)) })));
51
+ if (order === undefined)
52
+ return { said: [say('package-line', { document, scope: 'restsOn' })] };
53
+ const [open, missing] = [order.reduce((o, s) => (said.some((l) => name(l) === s.source && off(l)) || s.rests.some((r) => o.has(r)) ? o.add(s.source) : o), new Set()), (source) => said.filter((l) => name(l) === source && off(l))];
54
+ return { sources: order.filter((s) => !open.has(s.source)).map((s) => ({ source: s.source, pin: s.pin, document: of(`sources/${s.source}`), ...((o) => (o ? { overlay: o } : {}))(of(`sources/${s.source}/overlay`)) })),
55
+ open: order.filter((s) => open.has(s.source)).map((s) => ({ source: s.source, owed: missing(s.source).map((l) => l.value).join('|') || s.rests.filter((r) => open.has(r)).map((r) => `uses/${r}`).join('|'), waits: missing(s.source).map((l) => `${l.value} at ${where(l)}`).join(', ') || s.rests.filter((r) => open.has(r)).join(', ') })) };
56
+ };
57
+ /** Whether the person-s yes to a package already covers what arrives now: every source either pinned as the package names it, or owed by a demand that yes to this same package landed. */
58
+ export const coveredIn = (context, held, arrived, named) => arrived.length > 0 && arrived.every((s) => held.some((l) => l.value !== 'withdraw'
59
+ && ((l.scope === `uses/${s.source}` && l.value === s.pin) || (l.scope === `${valueOf(context.lock, 'place/needs')}/bytes/${s.source}` && l.role === 'demands' && l.at === `policy:head/receive/${named.slice(7, 23)}`))));
60
+ /** What a received package leaves owed in the place: a demand per open source naming what it waits on, and the withdraw of a demand a source that arrived has paid, or one a new package names otherwise. */
61
+ export const owedIn = (context, held, arrived, open, named) => {
62
+ const scope = (source) => `${valueOf(context.lock, 'place/needs')}/bytes/${source}`, live = (source) => held.find((l) => l.scope === scope(source) && l.role === 'demands' && l.value !== 'withdraw');
63
+ const withdrawn = [...arrived.map((s) => live(s.source)), ...open.map((o) => ((l) => (l?.value !== o.owed ? l : undefined))(live(o.source)))].flatMap((l) => (l ? [canonical({ ...Object.fromEntries(Object.entries(l).filter(([k]) => !['sig', 'epoch'].includes(k))), by: 'target', value: 'withdraw' })] : []));
64
+ return [...withdrawn, ...open.filter((o) => live(o.source)?.value !== o.owed).map((o) => canonical({ at: `policy:head/receive/${named.slice(7, 23)}`, by: 'target', form: 'alphabet', measure: 'digest', role: 'demands', scope: scope(o.source), value: o.owed }))];
65
+ };
@@ -0,0 +1,54 @@
1
+ import type { Said } from './lock.ts';
2
+ export type Plan = {
3
+ readonly kind: 'said';
4
+ readonly key: string;
5
+ readonly fields: Readonly<Record<string, string>>;
6
+ };
7
+ export type Fields = Readonly<Record<string, readonly string[]>>;
8
+ export interface Access {
9
+ readonly kind: string;
10
+ readonly scheme: string;
11
+ readonly params: Fields;
12
+ readonly account: string | undefined;
13
+ readonly flow: string;
14
+ readonly probe?: true;
15
+ readonly from?: string;
16
+ }
17
+ export interface Call {
18
+ readonly kind: 'call';
19
+ readonly source: string;
20
+ readonly operation: string;
21
+ readonly origin: string;
22
+ readonly carry: Fields;
23
+ readonly form: Fields;
24
+ readonly classes: readonly string[];
25
+ readonly access: Access;
26
+ }
27
+ export declare const fieldsOf: (lines: readonly Said[], scope: string) => Fields;
28
+ export declare const vocabOf: (lock: readonly Said[]) => {
29
+ offers: string;
30
+ keys: string;
31
+ region: string;
32
+ class: string;
33
+ access: string;
34
+ origin: string;
35
+ origins: string;
36
+ issuers: string;
37
+ unresolved: string;
38
+ unparsed: string;
39
+ unsupported: string;
40
+ collision: string;
41
+ scheme: string;
42
+ form: string;
43
+ carry: string;
44
+ anonymous: string;
45
+ unstated: string;
46
+ callable: string;
47
+ split: string;
48
+ pick: string;
49
+ };
50
+ export type Vocab = ReturnType<typeof vocabOf>;
51
+ export declare const observedAt: (lock: readonly Said[], source: string, operation: string) => string;
52
+ export declare const stepOf: (lines: readonly Said[], lock: readonly Said[], source: string, operation: string) => Call | Plan;
53
+ export declare const planRead: (lines: readonly Said[], lock: readonly Said[], source: string, operation: string, place: string) => Call | Plan;
54
+ export declare const planAct: (lines: readonly Said[], lock: readonly Said[], source: string, operation: string, place: string) => Call | Plan;
@@ -0,0 +1,64 @@
1
+ import { alphabet } from '@lapxo/topos/wire';
2
+ import { valueOf } from "./lock.js";
3
+ const said = (key, fields = {}) => ({ kind: 'said', key, fields });
4
+ const membersOf = (line) => (line === undefined ? undefined : alphabet(line.value ?? '').members);
5
+ export const fieldsOf = (lines, scope) => Object.fromEntries(lines.filter((l) => l.scope === scope && l.measure).map((l) => [l.measure, membersOf(l) ?? []]));
6
+ export const vocabOf = (lock) => ((w) => ({
7
+ offers: w('offers'), keys: w('keys'), region: w('region'), class: w('class'), access: w('access'), origin: w('origin'), origins: w('origins'), issuers: w('issuers'), unresolved: w('unresolved'), unparsed: w('unparsed'), unsupported: w('unsupported'), collision: w('collision'),
8
+ scheme: w('scheme'), form: w('form'), carry: w('carry'), anonymous: w('anonymous'), unstated: w('unstated'), callable: w('callable'), split: w('split'), pick: w('pick'),
9
+ }))((k) => valueOf(lock, `vocab/${k}`));
10
+ /** Where a source is reached: the origin the person picked, or the one its document declares when it declares one; several and none picked is no call, and none is ever a default. */
11
+ const originOf = (lines, v, source) => {
12
+ const at = fieldsOf(lines, `${v.region}/${source}`);
13
+ const picked = lines.find((l) => l.scope === `${v.pick}/${source}`)?.value;
14
+ if (picked !== undefined)
15
+ return picked;
16
+ const declared = at[v.origins] ?? [];
17
+ return declared.length === 1 ? declared[0] : said(declared.length ? 'pick-origin' : 'no-origin', { source, origins: declared.join(', ') });
18
+ };
19
+ export const observedAt = (lock, source, operation) => `${valueOf(lock, 'access/family')}/${source}/${operation}/${valueOf(lock, 'access/observed')}`;
20
+ /** How access is proven for an offer: its first declared alternative that says how what it obtains rides a request — anonymous, or a scheme whose flow the one adapter runs, its own or its issuer's, kept once per issuer; none that says is no call, named. Unstated, it is a read with nothing attached: probed once, and anonymous once head observed it answer. */
21
+ const accessOf = (lines, lock, v, source, operation, place) => {
22
+ const ways = fieldsOf(lines, `${v.offers}/${source}/${operation}`)[v.access] ?? [v.unstated];
23
+ const by = `${valueOf(lock, 'access/family')}/${source}/${valueOf(lock, 'access/authorizer')}`, account = (issuer) => valueOf(lock, issuer ? 'access/issuer-account' : 'access/account').split('{place}').join(place).split('{source}').join(source).split('{issuer}').join(issuer ?? '');
24
+ if (ways.includes(v.unstated) && lines.some((l) => l.scope === by))
25
+ return { kind: valueOf(lock, 'access/adapter'), scheme: valueOf(lock, 'access/authorizer'), params: fieldsOf(lines, by), flow: `${source}/${valueOf(lock, 'access/authorizer')}`, account: account(), from: by };
26
+ if (ways.includes(v.unstated))
27
+ return ((seen) => ({ kind: v.anonymous, scheme: '', params: {}, account: undefined, flow: '', ...(seen ? {} : { probe: true }) }))(lines.some((l) => l.scope === observedAt(lock, source, operation) && l.value === v.anonymous));
28
+ const kindOf = (way) => {
29
+ if (way === v.anonymous)
30
+ return [way, v.anonymous, {}, '', undefined];
31
+ const issuer = fieldsOf(lines, `${v.keys}/${source}/${way}`)['issuer']?.[0];
32
+ const flow = issuer ?? `${source}/${way}`;
33
+ const params = fieldsOf(lines, `${v.keys}/${flow}`);
34
+ return [way, params[v.scheme] ? valueOf(lock, 'access/adapter') : '', params, flow, issuer];
35
+ };
36
+ const kinds = ways.map(kindOf);
37
+ const usable = kinds.find(([, kind]) => kind !== '');
38
+ if (usable === undefined)
39
+ return said('no-adapter', { source, operation, kinds: ways.join(', ') });
40
+ const [scheme, kind, params, flow, issuer] = usable;
41
+ return { kind, scheme, params, flow, account: kind === v.anonymous ? undefined : account(issuer) };
42
+ };
43
+ export const stepOf = (lines, lock, source, operation) => {
44
+ const v = vocabOf(lock);
45
+ const offer = `${v.offers}/${source}/${operation}`;
46
+ const classes = fieldsOf(lines, offer)[v.class];
47
+ if (classes === undefined)
48
+ return said('unknown-operation', { source, operation });
49
+ const origin = originOf(lines, v, source);
50
+ return typeof origin !== 'string' ? origin : { kind: 'call', source, operation, origin, carry: fieldsOf(lines, `${offer}/${v.carry}`), form: fieldsOf(lines, `${offer}/${v.form}`), classes, access: { kind: v.anonymous, scheme: '', params: {}, account: undefined, flow: '' } };
51
+ };
52
+ /** The call an offer would make, from the place's lines alone, with the access it is proven by; or why there is none. An offer read two ways is never called. */
53
+ const callOf = (lines, lock, source, operation, place) => {
54
+ const step = stepOf(lines, lock, source, operation);
55
+ if (step.kind === 'said')
56
+ return step;
57
+ if (step.classes.includes(vocabOf(lock).split))
58
+ return said('split-offer', { source, operation });
59
+ const access = accessOf(lines, lock, vocabOf(lock), source, operation, place);
60
+ return 'key' in access ? access : { ...step, access };
61
+ };
62
+ const isCallable = (lock, classes) => classes.length === 1 && classes[0] === vocabOf(lock).callable;
63
+ export const planRead = (lines, lock, source, operation, place) => ((call) => (call.kind === 'call' && !isCallable(lock, call.classes) ? said('not-callable', { source, operation, class: call.classes.join(', ') }) : call))(callOf(lines, lock, source, operation, place));
64
+ export const planAct = (lines, lock, source, operation, place) => ((call) => (call.kind === 'call' && isCallable(lock, call.classes) ? said('is-read', { source, operation }) : call.kind === 'call' && call.access.probe ? said('access-unstated', { source, operation }) : call))(callOf(lines, lock, source, operation, place));
@@ -0,0 +1,13 @@
1
+ import type { Said } from './lock.ts';
2
+ /** The scope a read is kept under: the source, the operation, and the first steps of the digest of the request and whom it was made as, so only the same request made as the same principal finds it again. The drafted receipt of one read names what was asked, the digest of what came back, and when; the device key signs it. */
3
+ export declare const scopeOf: (family: string, api: string, operation: string, request: string, principal: string) => string;
4
+ export declare const draftOf: (family: string, at: string, scope: string, digest: string, when: string) => string;
5
+ /** What a typed answer stands on besides its body: the worlds the place uses and the code that interprets them, and what the source compiles to; what is kept under it holds only while these stand. */
6
+ export declare const basisOf: (lines: readonly Said[], word: (scope: string) => string, source: string) => string;
7
+ /** Head's own receipts under a scope, or under any scope below it, newest first: each once, by when it was made and what it names; a duplicate delivery is the same receipt. */
8
+ export declare const receiptsOf: (lines: readonly Said[], at: string, scope: string, below?: boolean) => readonly {
9
+ readonly scope: string;
10
+ readonly at: string;
11
+ readonly digest: string;
12
+ }[];
13
+ export declare const spentOf: (lines: readonly Said[], family: string, at: string, api: string, now: number) => number;
@@ -0,0 +1,12 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonical } from '@lapxo/topos/wire';
3
+ /** The scope a read is kept under: the source, the operation, and the first steps of the digest of the request and whom it was made as, so only the same request made as the same principal finds it again. The drafted receipt of one read names what was asked, the digest of what came back, and when; the device key signs it. */
4
+ export const scopeOf = (family, api, operation, request, principal) => `${family}/${api}/${operation}/${createHash('sha256').update(`${request}\n${principal}`).digest('hex').slice(0, 16)}`;
5
+ export const draftOf = (family, at, scope, digest, when) => canonical({ at: `${at}:${when}`, by: 'target', form: 'alphabet', measure: 'digest', role: 'writes', scope, value: digest });
6
+ /** What a typed answer stands on besides its body: the worlds the place uses and the code that interprets them, and what the source compiles to; what is kept under it holds only while these stand. */
7
+ export const basisOf = (lines, word, source) => createHash('sha256').update(lines.filter((l) => l.scope?.startsWith('uses/') || l.scope?.startsWith('dep/')
8
+ || l.scope === `${word('vocab/region')}/${source}` || [word('vocab/offers'), word('vocab/keys')].some((family) => l.scope?.startsWith(`${family}/${source}/`))).map((l) => canonical(l)).sort().join('\n')).digest('hex').slice(0, 16);
9
+ /** Head's own receipts under a scope, or under any scope below it, newest first: each once, by when it was made and what it names; a duplicate delivery is the same receipt. */
10
+ export const receiptsOf = (lines, at, scope, below = false) => [...new Map(lines.filter((l) => (l.scope === scope || (below && l.scope?.startsWith(`${scope}/`))) && l.measure === 'digest' && l.at?.startsWith(`${at}:`) && l.value?.startsWith('sha256:'))
11
+ .map((l) => [`${l.at} ${l.value}`, { scope: l.scope, at: l.at.slice(at.length + 1), digest: l.value }])).values()].sort((a, b) => (a.at < b.at ? 1 : a.at > b.at ? -1 : 0));
12
+ export const spentOf = (lines, family, at, api, now) => lines.filter((l) => l.scope?.startsWith(`${family}/${api}/`) && l.at?.startsWith(`${at}:`) && now - Date.parse(l.at.slice(at.length + 1)) < 3_600_000).length;
@@ -0,0 +1,6 @@
1
+ import type { Said } from './lock.ts';
2
+ export declare const fill: (text: string, asked: Readonly<Record<string, string | undefined>>) => string;
3
+ /** What head says: a cell, its code with its state and next; only a question the person reads is said in its lock words. */
4
+ export declare const sayIn: (lock: readonly Said[]) => (key: string, fields?: Readonly<Record<string, string | undefined>>) => string;
5
+ /** A red, when what head measured ran past a ceiling of its lock in whole seconds: said with the coordinate it ran at; within it, nothing. */
6
+ export declare const redIn: (lock: readonly Said[]) => (scope: string, coordinate: string, started: number, now: number) => readonly string[];
@@ -0,0 +1,7 @@
1
+ import { aboutOf, valueOf } from "./lock.js";
2
+ import { cellLine, cellOf } from "../ports/surface.js";
3
+ export const fill = (text, asked) => Object.entries(asked).reduce((at, [name, value]) => (value === undefined ? at : at.split(`{${name}}`).join(value)), text);
4
+ /** What head says: a cell, its code with its state and next; only a question the person reads is said in its lock words. */
5
+ export const sayIn = (lock) => (key, fields = {}) => (valueOf(lock, `said/${key}/state`) === 'asked' ? fill(aboutOf(lock, `said/${key}`), fields) : cellLine(cellOf(lock, key, fields)));
6
+ /** A red, when what head measured ran past a ceiling of its lock in whole seconds: said with the coordinate it ran at; within it, nothing. */
7
+ export const redIn = (lock) => (scope, coordinate, started, now) => ((seconds, ceiling) => (seconds > Number(ceiling.split('..')[1]) ? [sayIn(lock)('red', { coordinate, seconds: String(seconds), scope, ceiling })] : []))(Math.floor((now - started) / 1000), valueOf(lock, scope));
@@ -0,0 +1,7 @@
1
+ import type { Said } from './lock.ts';
2
+ /**
3
+ * How a source proves access, the most trusted first: each flow by the least trusted kind of input it asks, in the order
4
+ * head's lock names — a flow that asks none of them asks no secret — and how many secrets the place's root lets a flow
5
+ * ask on head's page, none unless it signed a ceiling. A source that proves no access says nothing.
6
+ */
7
+ export declare const trustOf: (lock: readonly Said[], lines: readonly Said[], source: string) => readonly string[];
@@ -0,0 +1,19 @@
1
+ import { aboutOf, members, valueOf } from "./lock.js";
2
+ import { vocabOf } from "./read.js";
3
+ import { fill, sayIn } from "./surface.js";
4
+ /**
5
+ * How a source proves access, the most trusted first: each flow by the least trusted kind of input it asks, in the order
6
+ * head's lock names — a flow that asks none of them asks no secret — and how many secrets the place's root lets a flow
7
+ * ask on head's page, none unless it signed a ceiling. A source that proves no access says nothing.
8
+ */
9
+ export const trustOf = (lock, lines, source) => {
10
+ const [v, order, asks, root] = [vocabOf(lock), members(valueOf(lock, 'access/trust-order')), valueOf(lock, 'vocab/asks'), valueOf(lock, 'founding/root').split('/')[1]];
11
+ const flows = [...new Set(lines.flatMap((l) => ((parts) => (l.scope?.startsWith(`${v.keys}/${source}/`) && parts.length === v.keys.split('/').length + 2 ? [parts.at(-1)] : []))(l.scope?.split('/') ?? [])))];
12
+ if (!flows.length)
13
+ return [];
14
+ const kind = (flow) => ((asked) => order.find((k) => asked.includes(k)) ?? 'none')(members(lines.find((l) => l.scope === `${v.keys}/${source}/${flow}` && l.measure === asks)?.value ?? ''));
15
+ const rank = (k) => (k === 'none' ? 0 : order.length - order.indexOf(k));
16
+ const ranked = flows.map((flow) => ({ flow, kind: kind(flow) })).sort((a, b) => rank(a.kind) - rank(b.kind) || a.flow.localeCompare(b.flow));
17
+ const allowed = (lines.find((l) => l.scope === valueOf(lock, 'access/secrets') && l.by === root)?.value ?? '0..0').split('..')[1];
18
+ return [sayIn(lock)('trust', { source, flows: ranked.map((f) => fill(aboutOf(lock, `said/trust-${f.kind}`), { flow: f.flow })).join('; '), allowed: allowed ?? '0' })];
19
+ };
@@ -0,0 +1,10 @@
1
+ import type { Said } from './lock.ts';
2
+ /** The facts bytes hold, in their order. */
3
+ export declare const linesOf: (bytes: Uint8Array) => readonly Said[];
4
+ /** Bytes named by their digest, under the algorithm the wire names: a pinned standing is exactly the bytes its pin hashes. */
5
+ export declare const digestIn: (lock: readonly Said[]) => (bytes: Uint8Array | string) => string;
6
+ /** A compiled standing as a place holds it: its pin, the digest of its bytes, and its lines; none when the place holds no such file. */
7
+ export declare const pinnedOf: (lock: readonly Said[], bytes: Uint8Array | undefined) => {
8
+ readonly pin: string;
9
+ readonly lines: readonly Said[];
10
+ } | undefined;
@@ -0,0 +1,9 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { parse } from '@lapxo/topos/wire';
3
+ import { valueOf } from "./lock.js";
4
+ /** The facts bytes hold, in their order. */
5
+ export const linesOf = (bytes) => new TextDecoder().decode(bytes).split('\n').flatMap((line) => ((got) => (got.kind === 'fact' ? [got.value.fields] : []))(parse(line)));
6
+ /** Bytes named by their digest, under the algorithm the wire names: a pinned standing is exactly the bytes its pin hashes. */
7
+ export const digestIn = (lock) => (bytes) => ((algorithm) => `${algorithm}:${createHash(algorithm).update(bytes).digest('hex')}`)(valueOf(lock, 'wire/digest-algorithms'));
8
+ /** A compiled standing as a place holds it: its pin, the digest of its bytes, and its lines; none when the place holds no such file. */
9
+ export const pinnedOf = (lock, bytes) => (bytes === undefined ? undefined : { pin: digestIn(lock)(bytes), lines: linesOf(bytes) });
@@ -0,0 +1,7 @@
1
+ import type { Asked, Context } from '../core/answer.ts';
2
+ /**
3
+ * One offer that is not of the class head reads: within a cap the person signed it is called as far as the cap goes;
4
+ * without one, each call asks the person once, and their yes is signed for exactly that request. The intent lands before
5
+ * the call, the receipt after; a call that never answered stays open until a look of what it would have changed closes it.
6
+ */
7
+ export declare const act: (asked: Asked, context: Context) => Promise<readonly string[]>;
@@ -0,0 +1,43 @@
1
+ import { actsOf, allowedOf, askedOf, cappedOf } from "../core/act.js";
2
+ import { planAct } from "../core/read.js";
3
+ import { draftOf } from "../core/receipt.js";
4
+ import { sayIn } from "../core/surface.js";
5
+ import { canonical } from '@lapxo/topos/wire';
6
+ import { called } from "./call.js";
7
+ /**
8
+ * One offer that is not of the class head reads: within a cap the person signed it is called as far as the cap goes;
9
+ * without one, each call asks the person once, and their yes is signed for exactly that request. The intent lands before
10
+ * the call, the receipt after; a call that never answered stays open until a look of what it would have changed closes it.
11
+ */
12
+ export const act = (asked, context) => called(asked, context, planAct, async ({ word, say, lines, source, operation, prepared, typed, attach, send, lands, widen, uncovered }) => {
13
+ const [family, done] = [word('act/family'), word('act/done')];
14
+ const allowed = allowedOf(lines, word('allow/family'), source, operation);
15
+ const scope = `${family}/${source}/${operation}/${prepared.key.slice(0, 16)}`;
16
+ const acts = actsOf(lines, family, [done, word('act/closed')], source, operation, prepared.key);
17
+ if (acts.open)
18
+ return say('open', { source, operation, intent: scope });
19
+ const stopped = uncovered('head', [scope, `${scope}/${done}`]);
20
+ if (stopped)
21
+ return stopped;
22
+ const capped = cappedOf(lines, word('allow/family'), source, operation);
23
+ if (capped && acts.done >= allowed)
24
+ return say('not-allowed', { source, operation, allowed: String(allowed), done: String(acts.done) });
25
+ if (!capped || askedOf(lines, word('ask/family'), source, operation)) {
26
+ const yes = await widen(sayIn(context.lock)('elicit-act', { source, operation, call: prepared.describe }), [canonical({ at: 'policy:person/allows', by: 'target', form: 'alphabet', measure: 'status', role: 'writes', scope: `${word('allow/family')}/${source}/${operation}/${prepared.key.slice(0, 16)}`, value: 'present' })]);
27
+ if ('said' in yes)
28
+ return yes.said;
29
+ }
30
+ const attached = await attach();
31
+ if ('said' in attached)
32
+ return attached.said;
33
+ const when = () => new Date(context.now()).toISOString();
34
+ const intended = await lands(canonical({ at: `${word('receipt/at')}:${when()}`, by: 'target', form: 'alphabet', measure: 'status', role: 'writes', scope, value: 'present' }));
35
+ if ('refused' in intended)
36
+ return say('not-landed', { why: intended.refused });
37
+ const got = await send(attached.attachment);
38
+ if (got.kind !== 'got')
39
+ return say('act-failed', { source, operation, intent: scope });
40
+ const landed = await lands(draftOf(family, word('receipt/at'), `${scope}/${done}`, context.cache.keep(Buffer.concat([Buffer.from(`${got.status}\n`), got.bytes])), when()));
41
+ const answer = await typed(String(got.status), got.bytes);
42
+ return 'refused' in landed ? [...answer, ...say('not-landed', { why: landed.refused })] : [...answer, ...intended.lines, ...landed.lines];
43
+ });
@@ -0,0 +1,12 @@
1
+ import type { Context } from '../core/answer.ts';
2
+ import type { Said } from '../core/lock.ts';
3
+ /**
4
+ * What a source answered, as the world types it: the body is laid in the place's answers for as long as bound reads it,
5
+ * then taken away, and only the lines the world answers for this request come back — a value of each declared field, the
6
+ * counts, what it held back. A place that names no view to type it gets no answer at all, never the body.
7
+ */
8
+ export declare const typedAnswer: (context: Context, place: string, lines: readonly Said[], source: string, operation: string, key: string, body: Uint8Array) => Promise<{
9
+ readonly lines: readonly string[];
10
+ } | {
11
+ readonly untyped: string;
12
+ }>;
@@ -0,0 +1,25 @@
1
+ import { canonical } from '@lapxo/topos/wire';
2
+ import { valueOf } from "../core/lock.js";
3
+ /**
4
+ * What a source answered, as the world types it: the body is laid in the place's answers for as long as bound reads it,
5
+ * then taken away, and only the lines the world answers for this request come back — a value of each declared field, the
6
+ * counts, what it held back. A place that names no view to type it gets no answer at all, never the body.
7
+ */
8
+ export const typedAnswer = async (context, place, lines, source, operation, key, body) => {
9
+ const view = valueOf(context.lock, 'place/answer');
10
+ if (!lines.some((line) => line.scope === `view/${view}`))
11
+ return { untyped: '' };
12
+ const [request, family] = [key.slice(0, 16), valueOf(context.lock, 'vocab/answer')];
13
+ const at = `${valueOf(context.lock, 'place/answers')}/${source}/${encodeURIComponent(operation).replaceAll('.', '%2E')}/${request}.json`;
14
+ context.files.lay(place, at, body);
15
+ try {
16
+ const got = await context.world(place, view);
17
+ if ('refused' in got)
18
+ return { untyped: got.refused };
19
+ const mine = got.lines.filter((line) => line.scope === `${family}/${request}` || line.scope?.startsWith(`${family}/${request}/`));
20
+ return mine.length ? { lines: mine.map((line) => canonical(line)).sort() } : { untyped: '' };
21
+ }
22
+ finally {
23
+ context.files.drop(place, at);
24
+ }
25
+ };
@@ -0,0 +1,50 @@
1
+ import type { Asked, Context, Outcome } from '../core/answer.ts';
2
+ import type { Said } from '../core/lock.ts';
3
+ import type { Call, Plan } from '../core/read.ts';
4
+ import type { Attachment } from '../access/contract.ts';
5
+ import type { Fetched, Prepared } from '../ports/adapters/request.ts';
6
+ /**
7
+ * The origin of a source a person picks once for a place, when its document declares several: offered as a choice among
8
+ * them, none a default, and kept as the person's line; the lines a call stands on, with it.
9
+ */
10
+ export declare const pickedOrigin: (context: Context, place: string, lines: readonly Said[], source: string, plan: Call | Plan) => Promise<{
11
+ readonly lines: readonly Said[];
12
+ } | {
13
+ readonly said: readonly string[];
14
+ }>;
15
+ /** Calls of one place take turns, so one asked while another lands sees its receipt; nothing waits on a timer. */
16
+ export declare const inTurn: <T>(place: string, act: () => Promise<T>) => Promise<T>;
17
+ export interface Frame {
18
+ readonly say: (key: string, fields?: Readonly<Record<string, string | undefined>>) => readonly string[];
19
+ readonly lands: (...lot: readonly string[]) => Promise<Outcome>;
20
+ readonly most: (scope: string) => number;
21
+ readonly place: string;
22
+ readonly lines: readonly Said[];
23
+ readonly source: string;
24
+ readonly operation: string;
25
+ readonly plan: Call;
26
+ readonly prepared: Extract<Prepared, {
27
+ key: string;
28
+ }>;
29
+ readonly widen: (message: string, lot: readonly string[]) => Promise<{
30
+ readonly lines: readonly string[];
31
+ } | {
32
+ readonly said: readonly string[];
33
+ }>;
34
+ readonly word: (scope: string) => string;
35
+ readonly typed: (status: string, bytes: Uint8Array) => Promise<readonly string[]>;
36
+ readonly attach: () => Promise<{
37
+ readonly attachment: Attachment | undefined;
38
+ readonly principal: string | undefined;
39
+ } | {
40
+ readonly said: readonly string[];
41
+ }>;
42
+ readonly send: (attachment: Attachment | undefined) => Promise<Fetched>;
43
+ readonly uncovered: (role: string, scopes: readonly string[]) => readonly string[] | undefined;
44
+ }
45
+ /**
46
+ * The steps a read and an act share: the place, its turn, the lines it stands on once bound settled them, the offer's
47
+ * plan, the transport its origin names and the request it prepares; then what is particular, given a frame that says
48
+ * by the lock, attaches access when asked, types what came back and lands a line under head's key.
49
+ */
50
+ export declare const called: (asked: Asked, context: Context, planOf: (lines: readonly Said[], lock: readonly Said[], source: string, operation: string, place: string) => Call | Plan, then: (frame: Frame) => Promise<readonly string[]>) => Promise<readonly string[]>;
@@ -0,0 +1,76 @@
1
+ import { inPlace, placeNameOf, unless } from "../core/answer.js";
2
+ import { fieldsOf, vocabOf } from "../core/read.js";
3
+ import { canonical } from '@lapxo/topos/wire';
4
+ import { sayIn } from "../core/surface.js";
5
+ import { valueOf } from "../core/lock.js";
6
+ import { uncoveredIn } from "../core/device.js";
7
+ import { widening } from "./device.js";
8
+ import { attachmentFor } from "../access/obtain.js";
9
+ import { typedAnswer } from "./answer.js";
10
+ import { standingFor } from "./world.js";
11
+ /**
12
+ * The origin of a source a person picks once for a place, when its document declares several: offered as a choice among
13
+ * them, none a default, and kept as the person's line; the lines a call stands on, with it.
14
+ */
15
+ export const pickedOrigin = async (context, place, lines, source, plan) => {
16
+ if (plan.kind !== 'said' || plan.key !== 'pick-origin')
17
+ return { lines };
18
+ const v = vocabOf(context.lock);
19
+ const origins = fieldsOf(lines, `${v.region}/${source}`)[v.origins] ?? [];
20
+ const line = (value) => ({ at: 'policy:person/picks', by: 'target', form: 'alphabet', measure: 'id', role: 'writes', scope: `${v.pick}/${source}`, value });
21
+ const by = widening(context, place, lines, origins.map((o) => canonical(line(o))));
22
+ if ('said' in by)
23
+ return { said: by.said };
24
+ const chosen = (await context.person.choose(sayIn(context.lock)('pick', { source }), [{ name: v.origin, options: origins.map((o) => ({ value: o, label: o })) }]))?.[v.origin];
25
+ if (chosen === undefined || !origins.includes(chosen))
26
+ return { said: [sayIn(context.lock)(plan.key, plan.fields)] };
27
+ const landed = await by.land([canonical(line(chosen))]);
28
+ return 'refused' in landed ? { said: [sayIn(context.lock)('not-landed', { why: landed.refused })] } : { lines: [...lines, line(chosen)] };
29
+ };
30
+ const turns = new Map();
31
+ /** Calls of one place take turns, so one asked while another lands sees its receipt; nothing waits on a timer. */
32
+ export const inTurn = (place, act) => { const next = (turns.get(place) ?? Promise.resolve()).then(act, act); turns.set(place, next.catch(() => undefined)); return next; };
33
+ /**
34
+ * The steps a read and an act share: the place, its turn, the lines it stands on once bound settled them, the offer's
35
+ * plan, the transport its origin names and the request it prepares; then what is particular, given a frame that says
36
+ * by the lock, attaches access when asked, types what came back and lands a line under head's key.
37
+ */
38
+ export const called = async (asked, context, planOf, then) => {
39
+ const say = (key, fields = {}) => [sayIn(context.lock)(key, fields)];
40
+ const [source, operation] = [asked['source'] ?? '', asked['operation'] ?? ''];
41
+ return inPlace(asked, context, (place) => inTurn(place, async () => {
42
+ const held = await standingFor(context, place);
43
+ if ('unsettled' in held)
44
+ return say('unsettled', { source, why: held.unsettled });
45
+ const widen = async (message, lot) => {
46
+ const by = widening(context, place, held.lines, lot);
47
+ if ('said' in by)
48
+ return { said: by.said };
49
+ const refused = await unless(context, message);
50
+ if (refused)
51
+ return { said: refused };
52
+ const landed = await by.land(lot);
53
+ return 'refused' in landed ? { said: say('not-landed', { why: landed.refused }) } : { lines: landed.lines };
54
+ };
55
+ const picked = await pickedOrigin(context, place, held.lines, source, planOf(held.lines, context.lock, source, operation, placeNameOf(place)));
56
+ if ('said' in picked)
57
+ return picked.said;
58
+ const lines = picked.lines;
59
+ const plan = planOf(lines, context.lock, source, operation, placeNameOf(place));
60
+ if (plan.kind === 'said')
61
+ return say(plan.key, plan.fields);
62
+ const prepared = ((transport) => transport?.prepare(plan, JSON.parse(asked['args'] || '{}'), context.lock))(await context.adapter(plan.origin));
63
+ if (prepared === undefined)
64
+ return say('no-transport', { source, origin: plan.origin });
65
+ if ('missing' in prepared)
66
+ return say('missing-param', { operation, param: prepared.missing });
67
+ if ('refused' in prepared)
68
+ return say(prepared.refused, { source, origin: plan.origin });
69
+ const most = (scope) => Number((lines.find((l) => l.scope === scope)?.value ?? '0..0').split('..')[1]);
70
+ const typed = async (status, bytes) => ((got) => ['lines' in got ? say('answered', { source, operation, status })[0] : say('untyped', { source, operation, status, why: got.untyped })[0], ...('lines' in got ? got.lines : [])])(await typedAnswer(context, place, lines, source, operation, prepared.key, bytes));
71
+ const lands = (...lot) => context.landAs(place, lot, 'head', lines);
72
+ const attach = async () => ((got) => ('said' in got ? { said: say(got.said, { source, operation, kinds: plan.access.kind, input: got.input }) } : got))(await attachmentFor(context, place, lines, plan));
73
+ const uncovered = (role, scopes) => uncoveredIn(context.lock, lines, context.device(role), place, [], scopes);
74
+ return then({ widen, word: (scope) => valueOf(context.lock, scope), say, place, lines, source, operation, plan, prepared, most, typed, attach, send: (attachment) => prepared.send(attachment, most('read/response-bytes')), lands, uncovered });
75
+ }));
76
+ };
@@ -0,0 +1,26 @@
1
+ import type { Context, Outcome } from '../core/answer.ts';
2
+ import type { Said } from '../core/lock.ts';
3
+ /** A key of this device by its role: read from the keychain, or made there once; only its public half is ever handed on. */
4
+ export declare const deviceKey: (context: Context, role?: string) => string | undefined;
5
+ /** The keychain account of a place's root, when this keychain holds it — who configures the place is here; else what head says of that. */
6
+ export declare const rootIn: (context: Context, place: string) => {
7
+ readonly account: string;
8
+ } | {
9
+ readonly said: readonly string[];
10
+ };
11
+ /**
12
+ * Who signs a widening of a place: its root, when this keychain holds it and the place admits this device's person to
13
+ * witness it — the line is then the place's own policy, in its lock, binding every device it admits, and names as its
14
+ * witness the person who said yes, by the key the place knows them by; else this device's person key, when the place
15
+ * admits it for every line — an attestation only this device reads, once per scope and measure the place does not state,
16
+ * since a second needs a fold bound does not publish; else no one here, and the lines are handed on. A place that pins a
17
+ * lock as its parent has nothing signed here: what the parent would cut is bound's to judge.
18
+ */
19
+ type By = {
20
+ readonly sign: (lot: readonly string[]) => Promise<Outcome>;
21
+ readonly land: (lot: readonly string[]) => Promise<Outcome>;
22
+ };
23
+ export declare const widening: (context: Context, place: string, lines: readonly Said[], lot: readonly string[]) => By | {
24
+ readonly said: readonly string[];
25
+ };
26
+ export {};