@lapxo/head 0.1.6 → 0.1.7

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 (53) hide show
  1. package/README.md +1 -1
  2. package/TARGET.bound +205 -112
  3. package/dist/src/access/contract.d.ts +0 -2
  4. package/dist/src/access/obtain.js +3 -3
  5. package/dist/src/ahead/budget.d.ts +4 -0
  6. package/dist/src/ahead/budget.js +8 -0
  7. package/dist/src/claims/claim.d.ts +1 -0
  8. package/dist/src/claims/claim.js +15 -15
  9. package/dist/src/claims/view.d.ts +3 -0
  10. package/dist/src/claims/view.js +11 -0
  11. package/dist/src/compile/pin.d.ts +7 -0
  12. package/dist/src/compile/pin.js +6 -7
  13. package/dist/src/core/answer.d.ts +1 -0
  14. package/dist/src/core/answer.js +1 -0
  15. package/dist/src/core/device.d.ts +2 -2
  16. package/dist/src/core/device.js +5 -5
  17. package/dist/src/core/lock.d.ts +1 -0
  18. package/dist/src/core/lock.js +2 -1
  19. package/dist/src/core/read.js +1 -2
  20. package/dist/src/core/worlds.js +2 -3
  21. package/dist/src/doors/guard.d.ts +18 -0
  22. package/dist/src/doors/guard.js +63 -0
  23. package/dist/src/doors/head.d.ts +60 -0
  24. package/dist/src/doors/head.js +136 -0
  25. package/dist/src/doors/http.d.ts +3 -0
  26. package/dist/src/doors/http.js +34 -0
  27. package/dist/src/doors/lines.d.ts +3 -0
  28. package/dist/src/doors/lines.js +21 -0
  29. package/dist/src/flows/device.js +4 -6
  30. package/dist/src/flows/plan.js +7 -10
  31. package/dist/src/flows/read.js +4 -1
  32. package/dist/src/index.js +17 -116
  33. package/dist/src/links/inspect.js +7 -9
  34. package/dist/src/places/found.js +1 -1
  35. package/dist/src/ports/bound.d.ts +9 -10
  36. package/dist/src/ports/bound.js +25 -20
  37. package/dist/src/ports/host.d.ts +35 -0
  38. package/dist/src/ports/host.js +108 -0
  39. package/dist/src/ports/keychain.d.ts +14 -2
  40. package/dist/src/ports/keychain.js +19 -7
  41. package/dist/src/ports/scratch.d.ts +4 -0
  42. package/dist/src/ports/scratch.js +10 -0
  43. package/dist/src/signer.js +8 -7
  44. package/dist/src/tools/card.js +3 -6
  45. package/dist/src/verbs/ask.js +2 -1
  46. package/dist/src/walk/context.js +3 -6
  47. package/package.json +3 -3
  48. package/dist/src/ports/signers.d.ts +0 -6
  49. package/dist/src/ports/signers.js +0 -14
  50. package/dist/src/ports/spare.d.ts +0 -1
  51. package/dist/src/ports/spare.js +0 -33
  52. package/dist/src/ports/warm.d.ts +0 -19
  53. package/dist/src/ports/warm.js +0 -45
@@ -52,10 +52,8 @@ export interface Want {
52
52
  readonly values: readonly string[];
53
53
  readonly host: Readonly<Record<string, string>>;
54
54
  readonly enter: (title: string, fields: readonly Field[]) => Promise<Held | undefined>;
55
- readonly consent: (message: string) => Promise<boolean>;
56
55
  readonly choose: (message: string, choices: readonly Choice[]) => Promise<Held | undefined>;
57
56
  readonly visit: (message: string, to: (back: string) => string | undefined | Promise<string | undefined>) => Promise<Held | undefined>;
58
- readonly form: (source: string, operation: string) => Fields;
59
57
  readonly call: (source: string, operation: string, body: unknown, attachment?: Attachment) => Promise<Answered>;
60
58
  readonly post: (url: string, body: Readonly<Record<string, unknown>>, as?: 'form' | 'json') => Promise<Answered>;
61
59
  }
@@ -1,7 +1,7 @@
1
1
  import { canonical } from '@lapxo/topos/wire';
2
2
  import { jsonOf } from "./contract.js";
3
3
  import { placeNameOf } from "../core/answer.js";
4
- import { aboutOf, members, valueOf } from "../core/lock.js";
4
+ import { members, valueOf } from "../core/lock.js";
5
5
  import { fieldsOf, stepOf, vocabOf } from "../core/read.js";
6
6
  export const sealed = (got) => Buffer.from(JSON.stringify(got)).toString('base64url');
7
7
  export const opened = (kept) => { try {
@@ -55,8 +55,8 @@ export const attachmentFor = async (context, place, lines, plan) => {
55
55
  values: members(valueOf(lines, 'wire/access-values') || valueOf(context.lock, 'wire/access-values')),
56
56
  host: Object.fromEntries(context.lock.filter((l) => l.scope?.startsWith('host/')).map((l) => [l.scope.slice('host/'.length), l.value ?? ''])),
57
57
  scopes: [...new Set(lines.filter((l) => reads.has(l.scope) && l.measure === 'scopes').flatMap((l) => (l.value ?? '').split('|')))].sort(),
58
- enter: (title, fields) => context.person.enter(title, fields), consent: async (message) => (await context.person.consent(message, aboutOf(context.lock, 'said/elicit/yes'))) === 'yes', choose: (message, choices) => context.person.choose(message, choices), visit: (message, to) => context.person.visit(message, to),
59
- form: (source, operation) => fieldsOf(lines, `${v.offers}/${source}/${operation}/${v.form}`), call: step,
58
+ enter: (title, fields) => context.person.enter(title, fields), choose: (message, choices) => context.person.choose(message, choices), visit: (message, to) => context.person.visit(message, to),
59
+ call: step,
60
60
  post: async (url, body, as) => ((transport) => (transport === undefined ? undefined : transport.post(url, body, most, Number(valueOf(context.lock, 'adapter/request/wait').split('..')[1]), as)))(await context.adapter(url)),
61
61
  };
62
62
  const got = await adapter.obtain(want);
@@ -4,3 +4,7 @@ import type { Said } from '../core/lock.ts';
4
4
  export declare const overOf: (lines: readonly Said[], [family, at, mark]: readonly [string, string, string], source: string, now: number, rate: number, share: number, asked: Asked) => boolean;
5
5
  /** The mark a read made ahead lands beside its receipt, under the receipt's own at, so what reads ahead spent is counted apart from what questions spent. */
6
6
  export declare const markOf: (asked: Asked, [at, mark]: readonly [string, string], scope: string, digest: string, when: string) => readonly string[];
7
+ /** When the window a ceiling's own measure names, `<unit>/R/<start>/P<n>D` or `…/PT<n>H` (ISO 8601), began around an instant handed in; undefined where the measure names no window. */
8
+ export declare const windowOf: (window: string, now: number) => number | undefined;
9
+ /** The calls a ceiling's window holds at an instant: its receipts since that window began, a read ahead's mark apart; undefined where the measure names no window. */
10
+ export declare const calledIn: (lines: readonly Said[], [family, at, mark]: readonly [string, string, string], source: string, window: string, now: number) => number | undefined;
@@ -9,3 +9,11 @@ export const overOf = (lines, [family, at, mark], source, now, rate, share, aske
9
9
  };
10
10
  /** The mark a read made ahead lands beside its receipt, under the receipt's own at, so what reads ahead spent is counted apart from what questions spent. */
11
11
  export const markOf = (asked, [at, mark], scope, digest, when) => early(asked) ? [canonical({ at: `${at}:${when}`, by: 'target', form: 'alphabet', measure: 'digest', role: 'writes', scope: `${scope}/${mark}`, value: digest })] : [];
12
+ /** When the window a ceiling's own measure names, `<unit>/R/<start>/P<n>D` or `…/PT<n>H` (ISO 8601), began around an instant handed in; undefined where the measure names no window. */
13
+ export const windowOf = (window, now) => {
14
+ const [, start, days, hours] = /^[^/]+\/R\/([^/]+)\/P(?:(\d+)D|T(\d+)H)$/.exec(window) ?? [], [from, period] = [Date.parse(start ?? ''), (Number(days ?? 0) * 24 + Number(hours ?? 0)) * 3_600_000];
15
+ return Number.isNaN(from) || !period ? undefined : from + Math.floor((now - from) / period) * period;
16
+ };
17
+ /** The calls a ceiling's window holds at an instant: its receipts since that window began, a read ahead's mark apart; undefined where the measure names no window. */
18
+ export const calledIn = (lines, [family, at, mark], source, window, now) => ((since) => (since === undefined ? undefined
19
+ : lines.filter((l) => l.scope?.startsWith(`${family}/${source}/`) && !l.scope.endsWith(`/${mark}`) && l.at?.startsWith(`${at}:`) && Date.parse(l.at.slice(at.length + 1)) >= since).length))(windowOf(window, now));
@@ -5,5 +5,6 @@ import { type Asked, type Context } from '../core/answer.ts';
5
5
  * it, with who carried it here as the client says it is. Taken back by the same words: what a person attests is evidence, and
6
6
  * stands for good, so taking it back is attesting that, beside it. A place whose lines admit no carrier is handed the line that does.
7
7
  * Only this action keeps one, never a cell, an answer or a read ahead, so nothing the model concluded becomes the person's.
8
+ * What an agent states is kept as an agent's, never asked as the person's: head's key signs it, at an agent's origin, where the root admitted one.
8
9
  */
9
10
  export declare const claimed: (asked: Asked, context: Context) => Promise<readonly string[]>;
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { canonical } from '@lapxo/topos/wire';
3
- import { inPlace, standingOr, unless } from "../core/answer.js";
4
- import { uncoveredIn } from "../core/device.js";
3
+ import { inStanding, unless } from "../core/answer.js";
4
+ import { idIn, uncoveredIn } from "../core/device.js";
5
5
  import { aboutOf, members, valueOf } from "../core/lock.js";
6
6
  import { fill, sayIn } from "../core/surface.js";
7
7
  /** Whether the person said, in this conversation, that what they state in a place is kept as theirs: asked once a place, a no kept as a yes is. */
@@ -12,29 +12,29 @@ const answered = new Map();
12
12
  * it, with who carried it here as the client says it is. Taken back by the same words: what a person attests is evidence, and
13
13
  * stands for good, so taking it back is attesting that, beside it. A place whose lines admit no carrier is handed the line that does.
14
14
  * Only this action keeps one, never a cell, an answer or a read ahead, so nothing the model concluded becomes the person's.
15
+ * What an agent states is kept as an agent's, never asked as the person's: head's key signs it, at an agent's origin, where the root admitted one.
15
16
  */
16
- export const claimed = (asked, context) => inPlace(asked, context, async (place) => {
17
- const [say, word, said, held] = [sayIn(context.lock), (k) => valueOf(context.lock, k), (asked['said'] ?? '').trim(), await standingOr(context, place)];
18
- if ('said' in held)
19
- return held.said;
17
+ export const claimed = (asked, context) => inStanding(asked, context, async (place, lines) => {
18
+ const [say, word, said] = [sayIn(context.lock), (k) => valueOf(context.lock, k), (asked['said'] ?? '').trim()];
20
19
  if (!said)
21
20
  return [say('missing-param', { operation: 'claim', param: 'said' })];
22
21
  const [family, withdraw, back] = [`${word('claim/family')}/${createHash('sha256').update(said).digest('hex').slice(0, 16)}`, asked['withdraw'] === 'true', word('claim/withdrawn')];
23
- const standing = held.lines.filter((l) => l.scope?.startsWith(`${family}/`) && !l.scope.endsWith(`/${back}`) && !held.lines.some((w) => w.scope === `${l.scope}/${back}`)).map((l) => l.scope);
22
+ const [agent, at, role] = asked['origin'] === word('claim/agent') ? [true, word('claim/agent/at'), 'head'] : [false, word('claim/at'), 'person'];
23
+ const standing = lines.filter((l) => l.at === at && l.scope?.startsWith(`${family}/`) && !l.scope.endsWith(`/${back}`) && !lines.some((w) => w.scope === `${l.scope}/${back}`)).map((l) => l.scope);
24
24
  if (withdraw && !standing.length)
25
25
  return [say('claim-none', { place, said })];
26
- const wire = held.lines.find((l) => l.scope === 'wire/fields'), via = word('claim/via');
26
+ const wire = lines.find((l) => l.scope === 'wire/fields'), via = word('claim/via');
27
27
  const bare = wire && Object.fromEntries(Object.entries(wire).filter(([k]) => !['sig', 'epoch', 'about'].includes(k)));
28
28
  if (bare && !members(wire.value ?? '').includes(via))
29
29
  return [say('claims-unadmitted', { place, lines: [canonical({ ...bare, by: 'target', value: 'withdraw' }), canonical({ ...bare, by: 'target', at: `policy:head/wire/${new Date(context.now()).toISOString()}`, value: `${wire.value}|${via}` })].join('\n') })];
30
- const line = (scope) => canonical({ about: said, at: word('claim/at'), by: 'target', form: 'alphabet', measure: 'text', role: 'writes', scope, value: 'lock', [via]: context.client() ?? word('claim/via-unknown') });
31
- const lot = withdraw ? standing.map((scope) => line(`${scope}/${back}`)) : [line(`${family}/${new Date(context.now()).toISOString().replace(/\D/g, '')}`)], outside = uncoveredIn(context.lock, held.lines, context.device('person'), place, lot);
30
+ const line = (scope) => canonical({ about: said, at, by: 'target', form: 'alphabet', measure: 'text', role: 'writes', scope, value: 'lock', [via]: context.client() ?? word('claim/via-unknown') });
31
+ const lot = withdraw ? standing.map((scope) => line(`${scope}/${back}`)) : [line(`${family}/${new Date(context.now()).toISOString().replace(/\D/g, '')}`)], outside = uncoveredIn(context.lock, lines, context.device(role), place, lot);
32
32
  if (outside)
33
- return outside;
34
- if (!withdraw && !answered.has(place))
33
+ return agent ? [say('agent-unadmitted', { place, lines: canonical({ at: word('claim/agent/admit'), by: 'target', form: 'alphabet', measure: 'coverage', role: 'writes', scope: `keys/${idIn(lines, context.device(role))}`, value: `${word('claim/family')}/**` }) })] : outside;
34
+ if (!agent && !withdraw && !answered.has(place))
35
35
  answered.set(place, (await unless(context, fill(aboutOf(context.lock, 'said/elicit-claims'), { place }))) === undefined);
36
- if (!withdraw && !answered.get(place))
36
+ if (!agent && !withdraw && !answered.get(place))
37
37
  return [say('claims-declined', { place, said })];
38
- const landed = await context.landAs(place, lot, 'person', held.lines);
39
- return 'refused' in landed ? [say('not-landed', { why: landed.refused })] : [say(withdraw ? 'unclaimed' : 'claimed', { place, said }), ...landed.lines];
38
+ const landed = await context.landAs(place, lot, role, lines);
39
+ return 'refused' in landed ? [say('not-landed', { why: landed.refused })] : [say(withdraw ? 'unclaimed' : agent ? 'agent-claimed' : 'claimed', { place, said }), ...landed.lines];
40
40
  });
@@ -0,0 +1,3 @@
1
+ import { type Asked, type Context } from '../core/answer.ts';
2
+ /** What a place keeps of what was stated, each standing statement with its origin and who else stated the same words: nobody, where only the one who carried it did. */
3
+ export declare const viewed: (asked: Asked, context: Context) => Promise<readonly string[]>;
@@ -0,0 +1,11 @@
1
+ import { inStanding } from "../core/answer.js";
2
+ import { valueOf } from "../core/lock.js";
3
+ import { sayIn } from "../core/surface.js";
4
+ /** What a place keeps of what was stated, each standing statement with its origin and who else stated the same words: nobody, where only the one who carried it did. */
5
+ export const viewed = (asked, context) => inStanding(asked, context, async (place, lines) => {
6
+ const [say, word] = [sayIn(context.lock), (k) => valueOf(context.lock, k)], [family, back, via] = [word('claim/family'), word('claim/withdrawn'), word('claim/via')];
7
+ const origin = (l) => (l.at === word('claim/agent/at') ? word('claim/agent') : l.at === word('claim/at') ? word('claim/person') : undefined), words = (l) => l.scope.split('/')[1];
8
+ const kept = lines.filter((l) => origin(l) !== undefined && l.scope?.startsWith(`${family}/`) && !l.scope.endsWith(`/${back}`) && !lines.some((w) => w.scope === `${l.scope}/${back}`));
9
+ const by = (l) => [...new Set(kept.filter((o) => words(o) === words(l) && origin(o) !== origin(l)).map(origin))].join(', ');
10
+ return kept.length ? kept.map((l) => say(by(l) ? 'claim-backed' : 'claim-alone', { origin: origin(l), said: l.about, carrier: l[via] ?? word('claim/via-unknown'), backers: by(l) })) : [say('claims-none', { place })];
11
+ });
@@ -9,6 +9,13 @@ export type Pinned = {
9
9
  readonly open?: true;
10
10
  readonly dropped?: true;
11
11
  };
12
+ export declare const pinnedAs: (context: Context, place: string, file: string, lines: readonly string[]) => Promise<{
13
+ readonly pin: string;
14
+ readonly bytes: string;
15
+ readonly lines: readonly Said[];
16
+ } | {
17
+ readonly refused: string;
18
+ } | undefined>;
12
19
  /**
13
20
  * A source pinned in a place: compiled once per digest of what it read — the place's other pins and what was laid for it
14
21
  * — so the same input finds its compiled standing by name, and pinned from the place's store: a new pin lands with the old
@@ -20,17 +20,16 @@ const compiledOf = async (context, place, source, file) => {
20
20
  return { said: [say(`document-${refused}`, { source, why: fieldsOf(compiled.lines, `${v.region}/${source}`)[v[refused]].join('; ') })], dropped: true };
21
21
  if (!mine.some((l) => l.measure === v.class && l.scope?.split('/').length === 3))
22
22
  return { said: [say('nothing-offered', { source })] };
23
- return drafted(context, place, source, file, [...mine.map((l) => canonical(l)), canonical({ at: 'policy:head/connect', by: 'target', form: 'alphabet', measure: 'reads', role: 'render', scope: `${v.region}/${source}`, value: [`${v.keys}/${source}/**`, ...issued.flatMap((at) => [at, `${at}/**`]), `${v.offers}/${source}/**|read/**|${v.region}/${source}|write/**`].join('|') })]);
23
+ return ((pinned) => (pinned === undefined || 'refused' in pinned ? { said: [pinned === undefined ? say('nothing-offered', { source }) : say('unsettled', { source, why: pinned.refused })] } : { pin: pinned.pin, lines: pinned.lines }))(await pinnedAs(context, place, file, [...mine.map((l) => canonical(l)), canonical({ at: 'policy:head/connect', by: 'target', form: 'alphabet', measure: 'reads', role: 'render', scope: `${v.region}/${source}`, value: [`${v.keys}/${source}/**`, ...issued.flatMap((at) => [at, `${at}/**`]), `${v.offers}/${source}/**|read/**|${v.region}/${source}|write/**`].join('|') })]));
24
24
  };
25
- const drafted = async (context, place, source, file, lines) => {
26
- const say = sayIn(context.lock), draft = file.replace(new RegExp(`${EXTENSION.replace('.', '\\.')}$`), `.draft${EXTENSION}`);
25
+ export const pinnedAs = async (context, place, file, lines) => {
26
+ const draft = file.replace(new RegExp(`${EXTENSION.replace('.', '\\.')}$`), `.draft${EXTENSION}`);
27
27
  context.files.lay(place, draft, new TextEncoder().encode(`${lines.join('\n')}\n`));
28
28
  const pinned = await context.pinned(place, draft);
29
29
  context.files.drop(place, draft);
30
- if (pinned === undefined || 'refused' in pinned)
31
- return { said: [pinned === undefined ? say('nothing-offered', { source }) : say('unsettled', { source, why: pinned.refused })] };
32
- context.files.lay(place, file, new TextEncoder().encode(pinned.bytes));
33
- return { pin: pinned.pin, lines: pinned.lines };
30
+ if (pinned !== undefined && !('refused' in pinned))
31
+ context.files.lay(place, file, new TextEncoder().encode(pinned.bytes));
32
+ return pinned;
34
33
  };
35
34
  /**
36
35
  * A source pinned in a place: compiled once per digest of what it read — the place's other pins and what was laid for it
@@ -100,6 +100,7 @@ export declare const standingOr: (context: Context, place: string, of?: Context[
100
100
  } | {
101
101
  readonly said: readonly string[];
102
102
  }>;
103
+ export declare const inStanding: (asked: Asked, context: Context, then: (place: string, lines: readonly Said[]) => Promise<readonly string[]>, of?: Context["standing"]) => Promise<readonly string[]>;
103
104
  export declare const baseOf: (lines: readonly Said[]) => string;
104
105
  export declare const placeNameOf: (place: string) => string;
105
106
  export declare const within: (context: Context, place: string, at: string) => Promise<boolean>;
@@ -21,6 +21,7 @@ export const unless = async (context, message) => ((consent) => (consent === 'ye
21
21
  export const printedOr = (context, printed) => (printed === undefined ? [sayIn(context.lock)('unreachable')] : [...printed.out, ...printed.err]);
22
22
  /** The lines a place stands on, or what head says when bound refused to fold them: a refusal is never an empty standing. */
23
23
  export const standingOr = async (context, place, of = context.standing) => ((held) => ('refused' in held ? { said: [sayIn(context.lock)('unfolded', { place, why: held.refused })] } : held))(await of(place));
24
+ export const inStanding = (asked, context, then, of = context.standing) => inPlace(asked, context, async (place) => ((held) => ('said' in held ? held.said : then(place, held.lines)))(await standingOr(context, place, of)));
24
25
  export const baseOf = (lines) => createHash('sha256').update(lines.map((l) => canonical(l)).sort().join('\n')).digest('hex').slice(0, 16);
25
26
  export const placeNameOf = (place) => basename(place);
26
27
  export const within = async (context, place, at) => isAbsolute(at) && [place, ...(await context.roots())].some((root) => resolve(at).startsWith(`${resolve(root)}${sep}`));
@@ -5,17 +5,17 @@ export interface Device {
5
5
  readonly signer: string;
6
6
  }
7
7
  export declare const deviceOf: (lock: readonly Said[], role?: string) => Device;
8
+ /** The line of the lock that names a keychain account's signing key, if it keeps one: a device's by its role, or a place's root by its template. */
9
+ export declare const signingOf: (lock: readonly Said[]) => (account: string) => string | undefined;
8
10
  /** Derive device authority from the admitted lock. */
9
11
  export declare const admissionOf: (lock: readonly Said[], publicKey: string, role?: string, as?: {
10
12
  readonly id?: string;
11
13
  readonly coverage?: string;
12
14
  readonly at?: string;
13
15
  }) => readonly string[];
14
- export declare const algorithmOf: (lines: readonly Said[]) => string;
15
16
  /** The id a place admits a public key under, as its own lines say: a key is who the place says it is, and a key it never admitted is no one there. */
16
17
  export declare const idIn: (lines: readonly Said[], publicKey: string | undefined) => string | undefined;
17
18
  export declare const coverageOf: (lines: readonly Said[], id: string | undefined) => readonly string[];
18
- export declare const covers: (lines: readonly Said[], id: string | undefined, scope: string) => boolean;
19
19
  /**
20
20
  * What head says instead of asking, when this device's key may not sign every line it would ask for in a place: what the
21
21
  * place admits it for and the first scope outside that, then the lines whoever configures the place can sign themselves.
@@ -3,22 +3,22 @@ import { members, said, valueOf } from "./lock.js";
3
3
  import { sayIn } from "./surface.js";
4
4
  const HEAD = 'head';
5
5
  export const deviceOf = (lock, role = HEAD) => ({ account: valueOf(lock, `device/${role}/account`), service: valueOf(lock, 'device/service'), signer: valueOf(lock, `device/${role}/signer`) });
6
+ /** The line of the lock that names a keychain account's signing key, if it keeps one: a device's by its role, or a place's root by its template. */
7
+ export const signingOf = (lock) => ((devices, [pre = '', post = ''] = valueOf(lock, 'founding/root-account').split('{place}')) => (account) => devices.find((l) => l.value === account)?.scope ?? (account.startsWith(pre) && account.endsWith(post) && !account.slice(pre.length, account.length - post.length).includes('/') ? 'founding/root-account' : undefined))(lock.filter((l) => /^device\/[^/]+\/account$/.test(l.scope ?? '')));
6
8
  /** Derive device authority from the admitted lock. */
7
9
  export const admissionOf = (lock, publicKey, role = HEAD, as = {}) => ((id, at) => [
8
10
  ...['class', 'coverage', 'resolution', 'signer'].flatMap((measure) => ((line) => (line === undefined ? [] : [canonical({ at, by: 'target', form: line.form ?? 'alphabet', measure, role: 'writes', scope: `keys/${id}`, value: measure === 'coverage' && as.coverage ? as.coverage : line.value ?? '' })]))(said(lock, `device/${role}/${measure}`))),
9
11
  canonical({ at, by: 'target', form: 'alphabet', measure: 'public-key', role: 'writes', scope: `keys/${id}`, value: publicKey }),
10
12
  ])(as.id ?? valueOf(lock, `device/${role}/account`), as.at ?? 'policy:person/device');
11
- export const algorithmOf = (lines) => ((era) => (lines.find((l) => l.scope === 'wire/signature-algorithms')?.value ?? '').split('|').find((one) => one.endsWith(`:${era}`)) ?? '')(valueOf(lines, 'wire/era'));
12
13
  /** The id a place admits a public key under, as its own lines say: a key is who the place says it is, and a key it never admitted is no one there. */
13
14
  export const idIn = (lines, publicKey) => publicKey === undefined ? undefined : lines.find((l) => l.scope?.startsWith('keys/') && l.measure === 'public-key' && l.value === publicKey)?.scope.slice('keys/'.length);
14
15
  export const coverageOf = (lines, id) => id === undefined ? [] : lines.filter((l) => l.scope === `keys/${id}` && l.measure === 'coverage').flatMap((l) => members(l.value ?? ''));
15
- export const covers = (lines, id, scope) => coverageOf(lines, id).some((glob) => matches(glob, scope));
16
16
  /**
17
17
  * What head says instead of asking, when this device's key may not sign every line it would ask for in a place: what the
18
18
  * place admits it for and the first scope outside that, then the lines whoever configures the place can sign themselves.
19
19
  */
20
20
  export const uncoveredIn = (lock, lines, publicKey, place, lot, scopes = []) => {
21
- const id = idIn(lines, publicKey);
22
- const outside = [...scopes, ...lot.map((line) => ((got) => (got.kind === 'fact' ? got.value.fields['scope'] ?? '' : ''))(parse(line)))].find((scope) => !covers(lines, id, scope));
23
- return outside === undefined ? undefined : [sayIn(lock)('not-covered', { place, coverage: coverageOf(lines, id).join('|') || 'nothing', scope: outside }), ...lot];
21
+ const coverage = coverageOf(lines, idIn(lines, publicKey));
22
+ const outside = [...scopes, ...lot.map((line) => ((got) => (got.kind === 'fact' ? got.value.fields['scope'] ?? '' : ''))(parse(line)))].find((scope) => !coverage.some((glob) => matches(glob, scope)));
23
+ return outside === undefined ? undefined : [sayIn(lock)('not-covered', { place, coverage: coverage.join('|') || 'nothing', scope: outside }), ...lot];
24
24
  };
@@ -1,5 +1,6 @@
1
1
  export type Said = Readonly<Record<string, string>>;
2
2
  /** The lines head's own lock declares, decoded and nothing more: a withdraw has no place in it, and one there is refused, never folded here. */
3
+ export declare const factsOf: (lines: readonly string[]) => readonly Said[];
3
4
  export declare const declaredOf: (text: string) => readonly Said[];
4
5
  export declare const said: (lock: readonly Said[], scope: string) => Said | undefined;
5
6
  export declare const valueOf: (lock: readonly Said[], scope: string) => string;
@@ -1,6 +1,7 @@
1
1
  import { alphabet, parse, steps } from '@lapxo/topos/wire';
2
2
  /** The lines head's own lock declares, decoded and nothing more: a withdraw has no place in it, and one there is refused, never folded here. */
3
- export const declaredOf = (text) => text.split('\n').flatMap((line) => ((got) => (got.kind === 'fact' ? [got.value.fields] : []))(parse(line)))
3
+ export const factsOf = (lines) => lines.flatMap((line) => ((got) => (got.kind === 'fact' ? [got.value.fields] : []))(parse(line)));
4
+ export const declaredOf = (text) => factsOf(text.split('\n'))
4
5
  .map((f) => { if (f.value === 'withdraw')
5
6
  throw Error(`REFUSE·lock head's own lock withdraws ${f.scope}: its lines are declared, never folded by head`); return f; });
6
7
  export const said = (lock, scope) => lock.find((f) => f.scope === scope);
@@ -1,8 +1,7 @@
1
1
  import { alphabet } from '@lapxo/topos/wire';
2
2
  import { valueOf } from "./lock.js";
3
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) ?? []]));
4
+ export const fieldsOf = (lines, scope) => Object.fromEntries(lines.filter((l) => l.scope === scope && l.measure).map((l) => [l.measure, alphabet(l.value ?? '').members]));
6
5
  export const vocabOf = (lock) => ((w) => ({
7
6
  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
7
  scheme: w('scheme'), form: w('form'), carry: w('carry'), anonymous: w('anonymous'), unstated: w('unstated'), callable: w('callable'), split: w('split'), pick: w('pick'),
@@ -1,8 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { parse } from '@lapxo/topos/wire';
3
- import { valueOf } from "./lock.js";
2
+ import { factsOf, valueOf } from "./lock.js";
4
3
  /** 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)));
4
+ export const linesOf = (bytes) => factsOf(new TextDecoder().decode(bytes).split('\n'));
6
5
  /** Bytes named by their digest, under the algorithm the wire names: a pinned standing is exactly the bytes its pin hashes. */
7
6
  export const digestIn = (lock) => (bytes) => ((algorithm) => `${algorithm}:${createHash(algorithm).update(bytes).digest('hex')}`)(valueOf(lock, 'wire/digest-algorithms'));
8
7
  /** 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. */
@@ -0,0 +1,18 @@
1
+ import { type Said } from '../core/lock.ts';
2
+ import type { headOf } from './head.ts';
3
+ type Asked = (verb: string, args: Record<string, unknown>) => Promise<readonly string[] | undefined>;
4
+ export declare const digestOf: (verb: string, args: unknown) => string;
5
+ /** Whatever head refuses through a door is written in the place's evidence by head's key, by the request's digest (digestOf) and never its body, once a request a window, its count when the door closes, and no more than the door's own line allows in that window, at the instant head's clock hands in. */
6
+ export declare const refusalsOf: (lock: readonly Said[], head: {
7
+ readonly write: (place: string, lot: readonly string[]) => Promise<unknown>;
8
+ readonly now: () => number;
9
+ }, door: string) => {
10
+ write: (place: string | undefined, verb: string, digest: string, code: string) => Promise<void>;
11
+ settle: () => Promise<void>;
12
+ };
13
+ /** What a door that shows no form asks first: a call carries its place's card, but the ask for that card, which only reads it, and the connect that founds head's first place; what head refuses is written by head's own refusals, and a refusal head wrote already is not written again. */
14
+ export declare const guardOf: (lock: readonly Said[], head: ReturnType<typeof headOf>, asked: Asked) => {
15
+ call: (verb: string, args: Readonly<Record<string, unknown>> | undefined) => Promise<readonly string[] | undefined>;
16
+ settle: () => Promise<void>;
17
+ };
18
+ export {};
@@ -0,0 +1,63 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonical } from '@lapxo/topos/wire';
3
+ import { members, valueOf } from "../core/lock.js";
4
+ import { sayIn } from "../core/surface.js";
5
+ import { shapeOf } from "../ports/surface.js";
6
+ import { windowOf } from "../ahead/budget.js";
7
+ export const digestOf = (verb, args) => createHash('sha256').update(JSON.stringify([verb, args])).digest('hex').slice(0, 16);
8
+ /** Whatever head refuses through a door is written in the place's evidence by head's key, by the request's digest (digestOf) and never its body, once a request a window, its count when the door closes, and no more than the door's own line allows in that window, at the instant head's clock hands in. */
9
+ export const refusalsOf = (lock, head, door) => {
10
+ const word = (k) => valueOf(lock, k), [family, at, ceiling] = [word('doors/refused/family'), word('doors/refused/at'), lock.find((l) => l.scope === `${word('refusals/family')}/${door}`)], most = Number(ceiling?.value?.split('..')[1] ?? 0);
11
+ const seen = new Map(), windows = new Map();
12
+ const write = async (place, verb, digest, code) => {
13
+ const instant = head.now(), since = windowOf(ceiling?.measure ?? '', instant), now = new Date(instant).toISOString(), known = seen.get(`${digest}\0${since}`), window = `${place}\0${since}`, n = (windows.get(window) ?? 0) + 1;
14
+ if (known !== undefined) {
15
+ known.count += 1;
16
+ return;
17
+ }
18
+ if (place === undefined || since === undefined || n > most + 1)
19
+ return;
20
+ windows.set(window, n);
21
+ const line = { at: `${at}:${now}`, by: 'target', form: 'alphabet', measure: 'count', role: 'writes', value: '1', ...(n > most ? { about: `ceiling of ${most} reached at ${now.slice(11, 16)}`, scope: `${family}/ceiling/${new Date(since).toISOString().replace(/\D/g, '')}` } : { about: `${verb} ${code}`, scope: `${family}/${digest}` }) };
22
+ if (n <= most)
23
+ seen.set(`${digest}\0${since}`, { place, line, count: 1 });
24
+ await head.write(place, [canonical(line)]);
25
+ };
26
+ const settle = async () => { for (const [key, { place, line, count }] of seen)
27
+ if (count > 1) {
28
+ seen.delete(key);
29
+ await head.write(place, [canonical({ ...((({ about: _about, ...bare }) => bare)(line)), value: 'withdraw' }), canonical({ ...line, at: `${at}:${new Date(head.now()).toISOString()}`, value: String(count) })]);
30
+ } };
31
+ return { write, settle };
32
+ };
33
+ /** What a door that shows no form asks first: a call carries its place's card, but the ask for that card, which only reads it, and the connect that founds head's first place; what head refuses is written by head's own refusals, and a refusal head wrote already is not written again. */
34
+ export const guardOf = (lock, head, asked) => {
35
+ const [say, word, { write, settle }] = [sayIn(lock), (k) => valueOf(lock, k), head.refusals], [no, written] = [word('said/door-uncarded/state'), shapeOf([say('signing-key-refused', {})]).cells[0].code];
36
+ const call = async (verb, args) => {
37
+ const a = { ...args }, place = typeof a['place'] === 'string' ? a['place'] : undefined, digest = digestOf(verb, a);
38
+ const refuse = async (cell, into = place) => (await write(into ?? await head.placed(), verb, digest, shapeOf([cell]).cells[0].code), [cell]);
39
+ if (verb === word('doors/first') && place === undefined) {
40
+ if (await head.hasPlace())
41
+ return refuse(say('door-founded', {}));
42
+ const [lines, founded] = [await asked(verb, a), await head.placed()], card = founded === undefined ? undefined : await head.cardOf(founded);
43
+ return [...(lines ?? []), ...(card !== undefined && 'card' in card ? [say('card', { place: founded, card: card.card, from: verb })] : [])];
44
+ }
45
+ if (verb === word('doors/card') && String(a['card']) === 'true')
46
+ return asked(verb, a);
47
+ if (typeof a['card'] !== 'string' || place === undefined)
48
+ return refuse(say('door-uncarded', { place: place ?? '' }));
49
+ if (!members(word('doors/checks')).includes(verb)) {
50
+ const now = await head.cardOf(place);
51
+ if (!('card' in now) || now.card !== a['card'])
52
+ return refuse(say('card-stale', { action: verb, place, card: a['card'] }));
53
+ delete a['card'];
54
+ }
55
+ if (verb === word('doors/carrier') && a['action'] === word('doors/claim') && a['origin'] !== word('claim/agent'))
56
+ return refuse(say('door-unoriginated', { place }));
57
+ const lines = await asked(verb, a), refused = lines === undefined ? undefined : shapeOf(lines).cells.find((c) => c.state === no && c.code !== written);
58
+ if (refused !== undefined)
59
+ await write(place, verb, digest, refused.code);
60
+ return lines;
61
+ };
62
+ return { call, settle };
63
+ };
@@ -0,0 +1,60 @@
1
+ import type { Asked, Outcome } from '../core/answer.ts';
2
+ import { type Said } from '../core/lock.ts';
3
+ import { type Client } from '../ports/access/person.ts';
4
+ export interface Door extends Client {
5
+ readonly roots: () => Promise<readonly string[]>;
6
+ readonly client: () => string | undefined;
7
+ readonly name: string;
8
+ }
9
+ export declare const askedOf: (raw: Readonly<Record<string, unknown>> | undefined) => Asked;
10
+ /** head behind a door: its lock read, every call answered one at a time and each answer's next read ahead, the same for every door. */
11
+ export declare const headOf: (lock: readonly Said[], root: string, ext: string, door: Door) => {
12
+ surface: import("../ports/surface.ts").Surface;
13
+ call: (name: string, args: Readonly<Record<string, unknown>> | undefined, signal: AbortSignal) => Promise<readonly string[] | undefined>;
14
+ resource: (uri: string, signal: AbortSignal) => Promise<{
15
+ readonly mime: string;
16
+ readonly lines: readonly string[];
17
+ } | undefined>;
18
+ idle: () => Promise<unknown>;
19
+ cardOf: (place: string) => Promise<{
20
+ readonly card: string;
21
+ } | {
22
+ readonly said: readonly string[];
23
+ }>;
24
+ placed: () => Promise<string | undefined>;
25
+ hasPlace: () => Promise<boolean>;
26
+ write: (place: string, lot: readonly string[]) => Promise<Outcome>;
27
+ refusals: {
28
+ write: (place: string | undefined, verb: string, digest: string, code: string) => Promise<void>;
29
+ settle: () => Promise<void>;
30
+ };
31
+ now: () => number;
32
+ };
33
+ /** A door that shows no form: its verbs, each answered by its tool through the guard every such door asks, a verb that is no tool carried as its carrier's action; undefined for a verb it lacks. */
34
+ export declare const plainOf: (lock: readonly Said[], root: string, ext: string, name: string) => {
35
+ head: {
36
+ surface: import("../ports/surface.ts").Surface;
37
+ call: (name: string, args: Readonly<Record<string, unknown>> | undefined, signal: AbortSignal) => Promise<readonly string[] | undefined>;
38
+ resource: (uri: string, signal: AbortSignal) => Promise<{
39
+ readonly mime: string;
40
+ readonly lines: readonly string[];
41
+ } | undefined>;
42
+ idle: () => Promise<unknown>;
43
+ cardOf: (place: string) => Promise<{
44
+ readonly card: string;
45
+ } | {
46
+ readonly said: readonly string[];
47
+ }>;
48
+ placed: () => Promise<string | undefined>;
49
+ hasPlace: () => Promise<boolean>;
50
+ write: (place: string, lot: readonly string[]) => Promise<Outcome>;
51
+ refusals: {
52
+ write: (place: string | undefined, verb: string, digest: string, code: string) => Promise<void>;
53
+ settle: () => Promise<void>;
54
+ };
55
+ now: () => number;
56
+ };
57
+ verbs: readonly string[];
58
+ settle: () => Promise<void>;
59
+ asked: (verb: string, args: Readonly<Record<string, unknown>> | undefined) => Promise<readonly string[] | undefined>;
60
+ };
@@ -0,0 +1,136 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { basename, join } from 'node:path';
10
+ import { answerOf, placeOf } from "../core/answer.js";
11
+ import { LOCK, parse } from '@lapxo/topos/wire';
12
+ import { aboutOf, members, valueOf } from "../core/lock.js";
13
+ import { sayIn } from "../core/surface.js";
14
+ import { matchTemplate, refusedOn, shapeOf, surfaceOf } from "../ports/surface.js";
15
+ import { birth, closing, founded, land, landAct, limits, pinnedAt, policyAt, run, settledView, sign, standingAt, told, words } from "../ports/bound.js";
16
+ import { configureHost } from "../ports/host.js";
17
+ import { held, keep } from "../ports/cache.js";
18
+ import { dropAt, hasAt, layAt, listAt, takeAt } from "../ports/place.js";
19
+ import * as documents from "../ports/documents.js";
20
+ import { keychain, portOf } from "../ports/keychain.js";
21
+ import { deviceOf, idIn, signingOf } from "../core/device.js";
22
+ import { personOf } from "../ports/access/person.js";
23
+ import { readAhead } from "../ahead/read.js";
24
+ import { cardAt } from "../verbs/card.js";
25
+ import { digestOf, guardOf, refusalsOf } from "./guard.js";
26
+ export const askedOf = (raw) => Object.fromEntries(Object.entries(raw ?? {}).map(([k, v]) => [k, v === undefined ? undefined : typeof v === 'object' ? JSON.stringify(v) : String(v)]));
27
+ /** head behind a door: its lock read, every call answered one at a time and each answer's next read ahead, the same for every door. */
28
+ export const headOf = (lock, root, ext, door) => {
29
+ const surface = surfaceOf(lock), runs = valueOf(lock, 'runs'), refused = refusedOn(lock, process.platform);
30
+ Object.assign(words, Object.fromEntries(['read', 'view', 'sign', 'land', 'key', 'signer', 'pin', 'topos', 'standing', 'evidence', 'act', 'profile', 'receipts', 'refusal', 'identity'].map((w) => [w, valueOf(lock, `bound/${w}`)])));
31
+ Object.assign(limits, { wait: Number(valueOf(lock, 'bound/wait').split('..')[1]), most: Number(valueOf(lock, 'bound/printed').split('..')[1]) });
32
+ Object.assign(birth, { at: valueOf(lock, 'founding/person-at'), file: valueOf(lock, 'founding/birth-file') });
33
+ configureHost(lock, limits);
34
+ /** One call at a time, each after the one before it and never on a timer, so one asked while another lands sees its receipt; a place's reads go to its fold host, which outlives the call and never holds a signer. */
35
+ let [last, ahead] = [Promise.resolve(), new AbortController()];
36
+ const turn = (act) => ((next) => { last = next.catch(() => undefined); return next; })(last.then(act));
37
+ /** What head refuses through this door, written as every door writes it; a door that reaches for a signing key is one such refusal, named by the lock's line for that key and never by what the door asked. */
38
+ const [refusals, keyRefused] = [refusalsOf(lock, { write: (place, lot) => write(place, lot), now: () => Date.now() }, door.name), shapeOf([sayIn(lock)('signing-key-refused', {})]).cells[0].code];
39
+ for (const end of ['SIGTERM', 'SIGINT'])
40
+ process.once(end, () => void last.then(refusals.settle).finally(() => process.exit(0)));
41
+ /** The person, asked as the client lets head ask; every wait on them is counted, so a time ceiling measures head's work and never the person's. */
42
+ let waited = 0;
43
+ const timed = (ask) => async (...args) => { const t = Date.now(); try {
44
+ return await ask(...args);
45
+ }
46
+ finally {
47
+ waited += Date.now() - t;
48
+ } };
49
+ const asked = personOf(lock, door);
50
+ const person = { consent: timed(asked.consent), enter: timed(asked.enter), choose: timed(asked.choose), visit: timed(asked.visit) };
51
+ /** head is its home: the folder of its index of places, and the keychain service its keys live under, one per home, so two heads on one keychain never share a key. A place's root is told to bound by the key id its lock names, its secret kept there under the place's account. */
52
+ const home = process.env[valueOf(lock, 'home/env')] || join(process.env[valueOf(lock, `platform/${process.platform}/home`)] ?? '', valueOf(lock, 'home/folder'));
53
+ const service = `${deviceOf(lock).service}/${basename(home).replace(/[^\w.-]+/g, '-')}`;
54
+ const store = valueOf(lock, `platform/${process.platform}/store`);
55
+ const keys = keychain(service, store, signingOf(lock));
56
+ const devices = new Map();
57
+ const device = (role) => devices.get(role) ?? ((key) => (key === undefined ? undefined : (devices.set(role, key), key)))(keys.held?.(deviceOf(lock, role).account));
58
+ const asRoot = (account) => ((device, key) => [key, device.signer, portOf(service, account, store, limits.wait)])(deviceOf(lock), valueOf(lock, 'founding/root').split('/')[1]);
59
+ const asRole = (role, lines) => ((one, id) => [id, one.signer, portOf(service, one.account, store, limits.wait)])(deviceOf(lock, role), idIn(lines, device(role)) ?? deviceOf(lock, role).account);
60
+ /** What a call is answered with, one call at a time as when bound blocked head: every bound process it starts is stopped when the client cancels it, and a call cancelled while it waits its turn is never begun. */
61
+ const contextOf = (signal) => {
62
+ /** One call's folds of a place, kept until something lands in it or is laid there; the places its root acted in, whose receipts close once, when the call ends; and those where anything but a read's receipts landed, as bound printed it. */
63
+ const [folds, touched, moved] = [new Map(), new Set(), new Set()], forget = (place) => { for (const k of [...folds.keys()])
64
+ if (k.endsWith(`\0${place}`))
65
+ folds.delete(k); };
66
+ const read = (at) => at?.startsWith(`${valueOf(lock, 'receipt/at')}:`) === true;
67
+ const once = (key, place, fold) => (folds.get(`${key}\0${place}`) ?? ((p) => (folds.set(`${key}\0${place}`, p), p))(fold()));
68
+ const acted = (place, root, done) => done.then((got) => { if ('lines' in got && !got.lines.every((l) => ((f) => f.kind === 'fact' && read(f.value.fields['at']))(parse(l))))
69
+ moved.add(place); return got; }).finally(() => { forget(place); if (root)
70
+ touched.add(place); });
71
+ return {
72
+ lock,
73
+ bound: (cwd, args) => run(runs, cwd, args, {}, signal),
74
+ standing: (place) => once('standing', place, () => standingAt(runs, place, (lines) => ['head', 'person'].flatMap((role) => ((id) => (id === undefined ? [] : [id]))(idIn(lines, device(role)))), (line) => read(line.at), signal)),
75
+ policy: (place) => once('policy', place, () => policyAt(runs, place, signal)),
76
+ world: (place, view) => once(`world/${view}`, place, () => settledView(runs, place, view, signal)),
77
+ adapter: async (origin) => ((name) => (!/^[a-z-]+$/.test(name) ? undefined : import(__rewriteRelativeImportExtension(new URL(`../ports/adapters/${name}${ext}`, import.meta.url).href))))(URL.canParse(origin) ? valueOf(lock, `adapter/${new URL(origin).protocol.slice(0, -1)}`) : ''),
78
+ files: { lay: (place, rel, bytes) => { forget(place); layAt(place, rel, bytes); }, drop: (place, rel) => { forget(place); dropAt(place, rel); }, has: hasAt, list: listAt, read: documents, take: takeAt, own: (rel) => takeAt(join(root, rel)) },
79
+ home: () => home,
80
+ personal: () => process.env[valueOf(lock, `platform/${process.platform}/home`)] ?? '',
81
+ found: (place, account) => acted(place, true, founded(runs, place, ...asRoot(account), signal)),
82
+ signRoot: (place, lot, account) => sign(runs, place, lot, ...asRoot(account), signal),
83
+ landRoot: (place, lot, account) => acted(place, true, landAct(runs, place, lot, ...asRoot(account), signal)),
84
+ moved: (place) => moved.has(place),
85
+ settle: async () => { const red = []; for (const place of touched) {
86
+ const why = await closing(runs, place, signal);
87
+ if (why !== undefined)
88
+ red.push(sayIn(lock)('receipts-open', { place, why }));
89
+ } touched.clear(); return red; },
90
+ pinned: (place, file) => pinnedAt(runs, place, file, signal),
91
+ access: async (kind) => (kind !== valueOf(lock, 'access/adapter') || !/^[a-z0-9-]+$/.test(kind) ? undefined : import(__rewriteRelativeImportExtension(new URL(`../ports/access/${kind}${ext}`, import.meta.url).href)).then((m) => m.adapter)),
92
+ person,
93
+ cache: { keep, held },
94
+ signAs: (place, lot, role, lines) => sign(runs, place, lot, ...asRole(role, lines), signal),
95
+ landAs: (place, lot, role, lines) => acted(place, false, landAct(runs, place, lot, ...asRole(role, lines), signal)),
96
+ land: (place, signed) => acted(place, true, land(runs, place, signed, signal)),
97
+ told: (place, signed) => told(runs, place, signed, words.standing, signal),
98
+ elicit: (message) => person.consent(message, aboutOf(lock, 'said/elicit/yes')),
99
+ now: () => Date.now(),
100
+ machine: () => Date.now() - waited,
101
+ begun: Date.now() - waited,
102
+ keys,
103
+ device,
104
+ roots: door.roots, client: door.client,
105
+ };
106
+ };
107
+ const call = (name, args, signal) => (ahead.abort(), turn(async () => {
108
+ const tool = surface.tools.find((one) => one.name === name), place = typeof args?.['place'] === 'string' ? args['place'] : '', keyed = (e) => ((key) => { if (key === undefined || !lock.some((l) => l.scope === key))
109
+ throw e; return [sayIn(lock)('signing-key-refused', { key, place })]; })(/^REFUSE·keychain (\S+) names a signing key/.exec(e.message)?.[1]);
110
+ if (tool === undefined)
111
+ return undefined;
112
+ const context = contextOf(signal), answer = refused !== undefined ? [refused] : signal.aborted ? [] : [...await (await answerOf('tools', tool.name))(askedOf(args), context).catch(keyed), ...await context.settle()];
113
+ void ((early) => turn(() => readAhead(shapeOf(answer).cells, contextOf(), early.signal)))(ahead = new AbortController());
114
+ return answer;
115
+ }).then(async (answer) => { if (answer !== undefined && shapeOf(answer).cells.some((c) => c.code === keyRefused))
116
+ await refusals.write(typeof args?.['place'] === 'string' ? args['place'] : await placed(), name, digestOf(name, args ?? {}), keyRefused); return answer; }));
117
+ const resource = (uri, signal) => turn(async () => {
118
+ const found = surface.resources.flatMap((r) => ((vars) => (vars === undefined ? [] : [{ r, vars }]))(matchTemplate(r.template, uri)))[0];
119
+ if (found === undefined)
120
+ return undefined;
121
+ if (refused !== undefined)
122
+ return { mime: found.r.mime, lines: [refused] };
123
+ return { mime: found.r.mime, lines: await (await answerOf('resources', found.r.name))(askedOf(found.vars), contextOf(signal)) };
124
+ });
125
+ /** A place's card now, as do reads it; whether head's index names a place yet, and the one it names; lines head's own key lands in a place, on its standing. */
126
+ const [cardOf, placed] = [(place) => turn(() => cardAt(contextOf(), place)), () => turn(() => placeOf({}, contextOf()))];
127
+ const hasPlace = () => turn(async () => ((c) => (!c.files.has(home, LOCK) ? false : c.policy(home).then((held) => !('refused' in held) && held.lines.some((l) => l.scope?.startsWith(`${valueOf(lock, 'home/family')}/`) && l.value !== 'withdraw'))))(contextOf()));
128
+ const write = (place, lot) => turn(async () => ((c) => c.standing(place).then((held) => ('refused' in held ? held : c.landAs(place, lot, 'head', held.lines))))(contextOf()));
129
+ return { surface, call, resource, idle: () => last, cardOf, placed, hasPlace, write, refusals, now: () => contextOf().now() };
130
+ };
131
+ /** A door that shows no form: its verbs, each answered by its tool through the guard every such door asks, a verb that is no tool carried as its carrier's action; undefined for a verb it lacks. */
132
+ export const plainOf = (lock, root, ext, name) => {
133
+ const [head, verbs, carrier] = [headOf(lock, root, ext, { can: () => ({ url: false, form: false }), elicit: async () => ({ action: 'decline' }), roots: async () => [], client: () => undefined, name }), members(valueOf(lock, 'doors/verbs')), valueOf(lock, 'doors/carrier')];
134
+ const guard = guardOf(lock, head, (verb, args) => ((tool) => head.call(tool ? verb : carrier, tool ? args : { ...args, action: verb }, new AbortController().signal))(head.surface.tools.some((t) => t.name === verb)));
135
+ return { head, verbs, settle: guard.settle, asked: async (verb, args) => (verbs.includes(verb) ? guard.call(verb, args) : undefined) };
136
+ };
@@ -0,0 +1,3 @@
1
+ import { type Said } from '../core/lock.ts';
2
+ /** The http door, on this device's loopback alone: one POST a verb, its body the args, its answer the cells every door gives; a Host not its own, or any Origin, is refused by name. It ends when its input does. */
3
+ export declare const open: (lock: readonly Said[], root: string, ext: string, name: string) => Promise<void>;