@lapxo/head 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/TARGET.bound +392 -49
  2. package/dist/src/access/contract.d.ts +2 -0
  3. package/dist/src/access/obtain.js +2 -2
  4. package/dist/src/adopt/adopt.js +5 -2
  5. package/dist/src/compile/pin.js +5 -5
  6. package/dist/src/core/act.d.ts +1 -0
  7. package/dist/src/core/act.js +1 -0
  8. package/dist/src/core/answer.d.ts +16 -4
  9. package/dist/src/core/answer.js +7 -4
  10. package/dist/src/core/lock.d.ts +2 -0
  11. package/dist/src/core/lock.js +2 -0
  12. package/dist/src/core/package.d.ts +54 -0
  13. package/dist/src/core/package.js +65 -0
  14. package/dist/src/core/read.d.ts +3 -0
  15. package/dist/src/core/read.js +8 -4
  16. package/dist/src/core/surface.d.ts +1 -40
  17. package/dist/src/core/surface.js +4 -29
  18. package/dist/src/core/trust.d.ts +7 -0
  19. package/dist/src/core/trust.js +19 -0
  20. package/dist/src/flows/device.js +1 -6
  21. package/dist/src/flows/read.js +18 -8
  22. package/dist/src/flows/world.js +3 -3
  23. package/dist/src/index.js +37 -18
  24. package/dist/src/onboard/first.d.ts +7 -0
  25. package/dist/src/onboard/first.js +21 -0
  26. package/dist/src/places/admit.js +1 -1
  27. package/dist/src/places/found.d.ts +1 -0
  28. package/dist/src/places/found.js +8 -7
  29. package/dist/src/ports/access/authorizer.d.ts +10 -0
  30. package/dist/src/ports/access/authorizer.js +34 -0
  31. package/dist/src/ports/access/flow.js +13 -3
  32. package/dist/src/ports/access/person.d.ts +3 -2
  33. package/dist/src/ports/access/person.js +12 -5
  34. package/dist/src/ports/adapters/request.d.ts +3 -0
  35. package/dist/src/ports/adapters/request.js +5 -3
  36. package/dist/src/ports/bound.d.ts +12 -2
  37. package/dist/src/ports/bound.js +30 -13
  38. package/dist/src/ports/documents.d.ts +34 -0
  39. package/dist/src/ports/documents.js +64 -0
  40. package/dist/src/ports/keychain.d.ts +2 -1
  41. package/dist/src/ports/keychain.js +3 -2
  42. package/dist/src/ports/place.d.ts +1 -0
  43. package/dist/src/ports/place.js +7 -1
  44. package/dist/src/ports/process.d.ts +9 -0
  45. package/dist/src/ports/process.js +5 -3
  46. package/dist/src/ports/spare.d.ts +1 -0
  47. package/dist/src/ports/spare.js +33 -0
  48. package/dist/src/ports/surface.d.ts +126 -0
  49. package/dist/src/ports/surface.js +48 -0
  50. package/dist/src/ports/warm.d.ts +19 -0
  51. package/dist/src/ports/warm.js +46 -0
  52. package/dist/src/signer.js +1 -1
  53. package/dist/src/sources/access.d.ts +10 -1
  54. package/dist/src/sources/access.js +15 -10
  55. package/dist/src/sources/connect.d.ts +5 -2
  56. package/dist/src/sources/connect.js +98 -36
  57. package/dist/src/tools/card.js +1 -1
  58. package/dist/src/tools/look.d.ts +2 -2
  59. package/dist/src/tools/look.js +4 -3
  60. package/dist/src/tools/places.d.ts +7 -0
  61. package/dist/src/tools/places.js +32 -0
  62. package/dist/src/tools/share.d.ts +1 -0
  63. package/dist/src/tools/share.js +1 -0
  64. package/package.json +6 -5
  65. /package/dist/src/{core → ports}/keys.d.ts +0 -0
  66. /package/dist/src/{core → ports}/keys.js +0 -0
@@ -45,6 +45,7 @@ export interface Want {
45
45
  readonly params: Fields;
46
46
  readonly scopes: readonly string[];
47
47
  readonly now: number;
48
+ readonly renew?: string;
48
49
  readonly declared: Readonly<Record<string, Fields>>;
49
50
  readonly origin: string;
50
51
  readonly secrets: number;
@@ -65,5 +66,6 @@ export interface AccessAdapter {
65
66
  readonly refused: string;
66
67
  readonly input?: string;
67
68
  }>;
69
+ readonly discover?: (resource: string, challenge: string, get: (url: string) => Promise<unknown>) => Promise<Readonly<Record<string, Fields>> | undefined>;
68
70
  }
69
71
  export declare const jsonOf: (body: Uint8Array | string) => unknown;
@@ -47,9 +47,9 @@ export const attachmentFor = async (context, place, lines, plan) => {
47
47
  return got?.kind === 'got' ? { status: got.status, json: jsonOf(got.bytes) } : undefined;
48
48
  };
49
49
  const reads = new Set(lines.filter((l) => l.scope?.startsWith(`${v.offers}/${plan.source}/`) && l.measure === v.class && l.value === v.callable).map((l) => l.scope));
50
- const [scheme, root] = [`${v.keys}/${access.flow}`, valueOf(context.lock, 'founding/root').split('/')[1]];
50
+ const [scheme, root] = [access.from ?? `${v.keys}/${access.flow}`, valueOf(context.lock, 'founding/root').split('/')[1]];
51
51
  const want = {
52
- place: placeNameOf(place), source: plan.source, scheme: access.scheme, params: access.params, now: context.now(), origin: plan.origin,
52
+ place: placeNameOf(place), source: plan.source, scheme: access.scheme, params: access.params, now: context.now(), origin: plan.origin, renew: held?.renew,
53
53
  declared: Object.fromEntries([...new Set(lines.filter((l) => l.scope === scheme || l.scope?.startsWith(`${scheme}/`)).map((l) => l.scope))].map((at) => [at.slice(scheme.length + 1), fieldsOf(lines, at)])),
54
54
  secrets: Number((lines.find((l) => l.scope === valueOf(context.lock, 'access/secrets') && l.by === root)?.value ?? '0..0').split('..')[1]),
55
55
  values: members(valueOf(lines, 'wire/access-values') || valueOf(context.lock, 'wire/access-values')),
@@ -1,3 +1,4 @@
1
+ import { EXTENSION } from '@lapxo/topos/wire';
1
2
  import { members, valueOf } from "../core/lock.js";
2
3
  import { linesOf } from "../core/worlds.js";
3
4
  /**
@@ -19,7 +20,7 @@ const readersOf = (lock, standing) => {
19
20
  * standing cannot be read so is no adoption.
20
21
  */
21
22
  export const adopted = async (context, place, lands, line) => {
22
- const [word, held] = [(k) => valueOf(context.lock, k), await context.standing(place)];
23
+ const [word, held] = [(k) => valueOf(context.lock, k), await context.policy(place)];
23
24
  if ('refused' in held)
24
25
  return held;
25
26
  const own = held.lines, [wait, most] = ['adapter/request/wait', 'world/standing-bytes'].map((k) => Number(word(k).split('..')[1]));
@@ -29,7 +30,9 @@ export const adopted = async (context, place, lands, line) => {
29
30
  const bytes = context.cache.held(standing) ?? ((got) => (got === undefined ? undefined : (context.cache.keep(got), got)))(await (await context.adapter(at))?.published(at, standing, most, wait));
30
31
  if (bytes === undefined)
31
32
  return { refused: `REFUSE·world ${w}: ${at} answered no bytes that hash to its pin ${standing}` };
32
- lot.push(line(`uses/${w}`, 'digest', standing), line(`sources/${w}`, 'id', at), line(`dep/${w}-blob`, 'digest', blob, { shape: word('world/shape') }), line(`sources/${w}-blob`, 'id', blobAt), ...readersOf(context.lock, linesOf(bytes)).map((r) => line(r.scope, r.measure, r.value, { role: 'reads' })));
33
+ const laid = `${word('place/worlds')}/${w}${EXTENSION}`;
34
+ context.files.lay(place, laid, bytes);
35
+ lot.push(line(`uses/${w}`, 'digest', standing, { needs: laid }), line(`sources/${w}`, 'id', at), line(`dep/${w}-blob`, 'digest', blob, { shape: word('world/shape') }), line(`sources/${w}-blob`, 'id', blobAt), ...readersOf(context.lock, linesOf(bytes)).map((r) => line(r.scope, r.measure, r.value, { role: 'reads' })));
33
36
  }
34
37
  return lot.length ? lands(lot) : { lines: [] };
35
38
  };
@@ -1,6 +1,6 @@
1
1
  import { basename, join } from 'node:path';
2
2
  import { canonical, EXTENSION } from '@lapxo/topos/wire';
3
- import { valueOf } from "../core/lock.js";
3
+ import { members, under, valueOf } from "../core/lock.js";
4
4
  import { fieldsOf, vocabOf } from "../core/read.js";
5
5
  import { sayIn } from "../core/surface.js";
6
6
  import { digestIn, linesOf, pinnedOf } from "../core/worlds.js";
@@ -42,12 +42,12 @@ const drafted = async (context, place, source, file, lines) => {
42
42
  * one's withdraw, said at its own time, since a withdrawn saying stays withdrawn.
43
43
  */
44
44
  export const pinIn = (context, place, lands, line) => async (source, read) => {
45
- const [say, digest, held] = [sayIn(context.lock), digestIn(context.lock), await context.standing(place)];
45
+ const [say, digest, held] = [sayIn(context.lock), digestIn(context.lock), await context.policy(place)];
46
46
  if ('refused' in held)
47
47
  return { said: [say('unsettled', { source, why: held.refused })] };
48
48
  const own = held.lines;
49
49
  const input = digest([...own.filter((l) => l.scope?.startsWith('uses/') && l.scope !== `uses/${source}`).map((l) => `${l.scope} ${l.value}`), ...read].sort().join('\n'));
50
- const file = `${valueOf(context.lock, 'place/compiled')}/${source}/${input.split(':')[1].slice(0, 16)}${EXTENSION}`;
50
+ const file = `${members(valueOf(context.lock, 'place/compiled'))[0]}/${source}/${input.split(':')[1].slice(0, 16)}${EXTENSION}`;
51
51
  const old = own.find((l) => l.scope === `uses/${source}`);
52
52
  const pinned = ((kept) => (kept !== undefined && kept.pin === old?.value ? kept : undefined))(pinnedOf(context.lock, old?.needs === file ? context.files.take(join(place, file)) : undefined)) ?? await compiledOf(context, place, source, file);
53
53
  if ('said' in pinned || (old?.needs === file && old.value === pinned.pin))
@@ -62,10 +62,10 @@ export const pinIn = (context, place, lands, line) => async (source, read) => {
62
62
  * never reconnected. What each answers is said by the pin; one still unresolved stays a demand.
63
63
  */
64
64
  export const paidBy = async (context, place, pin, laid) => {
65
- const [v, held] = [vocabOf(context.lock), await context.standing(place)];
65
+ const [v, held] = [vocabOf(context.lock), await context.policy(place)];
66
66
  if ('refused' in held)
67
67
  return [];
68
- const owed = held.lines.filter((l) => l.scope?.startsWith('uses/') && l.needs?.startsWith(`${valueOf(context.lock, 'place/compiled')}/`)
68
+ const owed = held.lines.filter((l) => l.scope?.startsWith('uses/') && under(context.lock, 'place/compiled', l.needs)
69
69
  && linesOf(context.files.take(join(place, l.needs)) ?? new Uint8Array()).some((r) => r.measure === v.unresolved && basename((r.value ?? '').split('#')[0]) === basename(laid)));
70
70
  const paid = [];
71
71
  for (const l of owed)
@@ -3,6 +3,7 @@ import type { Said } from './lock.ts';
3
3
  export declare const allowedOf: (lines: readonly Said[], family: string, api: string, operation: string) => number;
4
4
  /** Whether the person granted head to read a source, by the one sentence they signed for the place: the sources it names, until the day it names. */
5
5
  export declare const grantedOf: (lines: readonly Said[], family: string, source: string, now: number) => boolean;
6
+ export declare const untilOf: (period: string, now: number) => string;
6
7
  export declare const cappedOf: (lines: readonly Said[], family: string, source: string, operation: string) => boolean;
7
8
  export declare const askedOf: (lines: readonly Said[], family: string, api: string, operation: string) => boolean;
8
9
  export declare const isOpen: (lines: readonly Said[], intent: string, ends: readonly string[]) => boolean;
@@ -3,6 +3,7 @@ const hi = (lines, scope) => ((line) => (line === undefined ? undefined : Number
3
3
  export const allowedOf = (lines, family, api, operation) => Math.min(hi(lines, `${family}/${api}/${operation}`) ?? 0, hi(lines, `${family}/${api}`) ?? Infinity);
4
4
  /** Whether the person granted head to read a source, by the one sentence they signed for the place: the sources it names, until the day it names. */
5
5
  export const grantedOf = (lines, family, source, now) => (lines.find((l) => l.scope === family && l.measure === 'sources')?.value ?? '').split('|').includes(source) && Date.parse(lines.find((l) => l.scope === family && l.measure === 'until')?.value ?? '') > now;
6
+ export const untilOf = (period, now) => new Date(now + Number(period) * 86_400_000).toISOString().slice(0, 10);
6
7
  export const cappedOf = (lines, family, source, operation) => hi(lines, `${family}/${source}/${operation}`) !== undefined || hi(lines, `${family}/${source}`) !== undefined;
7
8
  export const askedOf = (lines, family, api, operation) => lines.some((l) => l.scope === `${family}/${api}/${operation}` && l.value === 'present');
8
9
  export const isOpen = (lines, intent, ends) => lines.filter((l) => l.scope === intent && l.value === 'present').length > lines.filter((l) => ends.some((end) => l.scope === `${intent}/${end}`)).length;
@@ -1,4 +1,4 @@
1
- import type { Said } from './lock.ts';
1
+ import { type Said } from './lock.ts';
2
2
  import type { Printed } from '../ports/bound.ts';
3
3
  export type Outcome = {
4
4
  readonly lines: readonly string[];
@@ -6,9 +6,10 @@ export type Outcome = {
6
6
  readonly refused: string;
7
7
  };
8
8
  import type { KeyStore } from '../ports/keychain.ts';
9
- import type { Prepared } from '../ports/adapters/request.ts';
9
+ import type { Fetched, Prepared } from '../ports/adapters/request.ts';
10
+ import type * as Documents from '../ports/documents.ts';
10
11
  import type { Call } from './read.ts';
11
- import type { AccessAdapter, Answered, Choice, Field, Held } from '../access/contract.ts';
12
+ import type { AccessAdapter, Answered, Attachment, Choice, Field, Held } from '../access/contract.ts';
12
13
  export type Asked = Readonly<Record<string, string | undefined>>;
13
14
  export interface Context {
14
15
  readonly lock: readonly Said[];
@@ -21,6 +22,11 @@ export interface Context {
21
22
  } | {
22
23
  readonly refused: string;
23
24
  }>;
25
+ readonly policy: (place: string) => Promise<{
26
+ readonly lines: readonly Said[];
27
+ } | {
28
+ readonly refused: string;
29
+ }>;
24
30
  readonly world: (place: string, view: string) => Promise<{
25
31
  readonly lines: readonly Said[];
26
32
  } | {
@@ -30,6 +36,7 @@ export interface Context {
30
36
  readonly prepare: (call: Call, args: Readonly<Record<string, unknown>>, lock: readonly Said[]) => Prepared;
31
37
  readonly post: (url: string, body: Readonly<Record<string, unknown>>, most: number, wait: number, as?: 'form' | 'json') => Promise<Answered>;
32
38
  readonly published: (url: string, digest: string, most: number, wait: number) => Promise<Uint8Array | undefined>;
39
+ readonly fetched: (url: string, attachment: Attachment | undefined, accept: string | undefined, most: number, wait: number) => Promise<Fetched>;
33
40
  readonly get: (url: string, wait: number) => Promise<{
34
41
  readonly bytes: Uint8Array;
35
42
  readonly type: string;
@@ -46,13 +53,17 @@ export interface Context {
46
53
  readonly lay: (place: string, rel: string, bytes: Uint8Array) => void;
47
54
  readonly drop: (place: string, rel: string) => void;
48
55
  readonly has: (place: string, rel: string) => boolean;
56
+ readonly list: (place: string, rel: string) => readonly string[];
57
+ readonly read: typeof Documents;
49
58
  readonly take: (path: string) => Uint8Array | undefined;
50
59
  readonly own: (rel: string) => Uint8Array | undefined;
51
60
  };
52
61
  readonly home: () => string;
62
+ readonly personal: () => string;
53
63
  readonly found: (place: string, account: string) => Promise<Outcome>;
54
64
  readonly signRoot: (place: string, lot: readonly string[], account: string) => Promise<Outcome>;
55
65
  readonly landRoot: (place: string, lot: readonly string[], account: string) => Promise<Outcome>;
66
+ readonly settle: () => Promise<readonly string[]>;
56
67
  readonly pinned: (place: string, file: string) => Promise<{
57
68
  readonly pin: string;
58
69
  readonly bytes: string;
@@ -81,10 +92,11 @@ export declare const inPlace: (asked: Asked, context: Context, then: (place: str
81
92
  export declare const unless: (context: Context, message: string) => Promise<readonly string[] | undefined>;
82
93
  export declare const printedOr: (context: Context, printed: Printed | undefined) => readonly string[];
83
94
  /** The lines a place stands on, or what head says when bound refused to fold them: a refusal is never an empty standing. */
84
- export declare const standingOr: (context: Context, place: string) => Promise<{
95
+ export declare const standingOr: (context: Context, place: string, of?: Context["standing"]) => Promise<{
85
96
  readonly lines: readonly Said[];
86
97
  } | {
87
98
  readonly said: readonly string[];
88
99
  }>;
89
100
  export declare const baseOf: (lines: readonly Said[]) => string;
90
101
  export declare const placeNameOf: (place: string) => string;
102
+ export declare const within: (context: Context, place: string, at: string) => Promise<boolean>;
@@ -7,17 +7,20 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
7
7
  return path;
8
8
  };
9
9
  import { createHash } from 'node:crypto';
10
- import { canonical } from '@lapxo/topos/wire';
11
- import { basename } from 'node:path';
10
+ import { canonical, LOCK } from '@lapxo/topos/wire';
11
+ import { valueOf } from "./lock.js";
12
+ import { basename, isAbsolute, resolve, sep } from 'node:path';
12
13
  import { sayIn } from "./surface.js";
13
14
  /** What answers a line of the surface: the module named as the line, in the folder of its family. */
14
15
  export const answerOf = async (family, name) => (await import(__rewriteRelativeImportExtension(new URL(`../${family}/${name}${import.meta.url.slice(import.meta.url.lastIndexOf('.'))}`, import.meta.url).href))).answer;
15
- export const placeOf = async (asked, context) => asked['place'] || (await context.roots())[0];
16
+ export const placeOf = async (asked, context) => asked['place'] || (await context.roots())[0] || ((held) => ('refused' in held ? undefined
17
+ : ((named) => (named.length === 1 ? named[0] : undefined))(held.lines.filter((l) => l.scope?.startsWith(`${valueOf(context.lock, 'home/family')}/`) && l.value !== 'withdraw').map((l) => l.value))))(context.files.has(context.home(), LOCK) ? await context.policy(context.home()) : { refused: '' });
16
18
  export const inPlace = async (asked, context, then) => ((place) => (place === undefined ? [sayIn(context.lock)('no-place')] : then(place)))(await placeOf(asked, context));
17
19
  /** The person's yes to exactly what a message says; anything else is what head says of it, and nothing is done. */
18
20
  export const unless = async (context, message) => ((consent) => (consent === 'yes' ? undefined : [sayIn(context.lock)(consent === 'none' ? 'cannot-ask' : 'no-yes')]))(await context.elicit(message));
19
21
  export const printedOr = (context, printed) => (printed === undefined ? [sayIn(context.lock)('unreachable')] : [...printed.out, ...printed.err]);
20
22
  /** The lines a place stands on, or what head says when bound refused to fold them: a refusal is never an empty standing. */
21
- export const standingOr = async (context, place) => ((held) => ('refused' in held ? { said: [sayIn(context.lock)('unfolded', { place, why: held.refused })] } : held))(await context.standing(place));
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));
22
24
  export const baseOf = (lines) => createHash('sha256').update(lines.map((l) => canonical(l)).sort().join('\n')).digest('hex').slice(0, 16);
23
25
  export const placeNameOf = (place) => basename(place);
26
+ export const within = async (context, place, at) => isAbsolute(at) && [place, ...(await context.roots())].some((root) => resolve(at).startsWith(`${resolve(root)}${sep}`));
@@ -5,5 +5,7 @@ export declare const said: (lock: readonly Said[], scope: string) => Said | unde
5
5
  export declare const valueOf: (lock: readonly Said[], scope: string) => string;
6
6
  export declare const aboutOf: (lock: readonly Said[], scope: string) => string;
7
7
  export declare const members: (value: string) => readonly string[];
8
+ /** Whether a path a line needs lies under one of the folders a lock word names: a place's own, or one an older head kept. */
9
+ export declare const under: (lock: readonly Said[], scope: string, path: string | undefined) => boolean;
8
10
  /** The lines exactly one step below a family: tool/look under tool, never tool/look/place. */
9
11
  export declare const below: (lock: readonly Said[], family: string) => readonly Said[];
@@ -7,5 +7,7 @@ export const said = (lock, scope) => lock.find((f) => f.scope === scope);
7
7
  export const valueOf = (lock, scope) => said(lock, scope)?.value ?? '';
8
8
  export const aboutOf = (lock, scope) => said(lock, scope)?.about ?? '';
9
9
  export const members = (value) => (value === '' ? [] : alphabet(value).members);
10
+ /** Whether a path a line needs lies under one of the folders a lock word names: a place's own, or one an older head kept. */
11
+ export const under = (lock, scope, path) => members(valueOf(lock, scope)).some((at) => path?.startsWith(`${at}/`) === true);
10
12
  /** The lines exactly one step below a family: tool/look under tool, never tool/look/place. */
11
13
  export const below = (lock, family) => lock.filter((f) => ((at) => at.length === 2 && at[0] === family)(steps(f.scope ?? '')));
@@ -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
+ };
@@ -11,6 +11,8 @@ export interface Access {
11
11
  readonly params: Fields;
12
12
  readonly account: string | undefined;
13
13
  readonly flow: string;
14
+ readonly probe?: true;
15
+ readonly from?: string;
14
16
  }
15
17
  export interface Call {
16
18
  readonly kind: 'call';
@@ -46,6 +48,7 @@ export declare const vocabOf: (lock: readonly Said[]) => {
46
48
  pick: string;
47
49
  };
48
50
  export type Vocab = ReturnType<typeof vocabOf>;
51
+ export declare const observedAt: (lock: readonly Said[], source: string, operation: string) => string;
49
52
  export declare const stepOf: (lines: readonly Said[], lock: readonly Said[], source: string, operation: string) => Call | Plan;
50
53
  export declare const planRead: (lines: readonly Said[], lock: readonly Said[], source: string, operation: string, place: string) => Call | Plan;
51
54
  export declare const planAct: (lines: readonly Said[], lock: readonly Said[], source: string, operation: string, place: string) => Call | Plan;
@@ -16,11 +16,15 @@ const originOf = (lines, v, source) => {
16
16
  const declared = at[v.origins] ?? [];
17
17
  return declared.length === 1 ? declared[0] : said(declared.length ? 'pick-origin' : 'no-origin', { source, origins: declared.join(', ') });
18
18
  };
19
- /** 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; unstated, or none that says, is no call, named. */
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. */
20
21
  const accessOf = (lines, lock, v, source, operation, place) => {
21
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 };
22
26
  if (ways.includes(v.unstated))
23
- return said('access-unstated', { source, operation });
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));
24
28
  const kindOf = (way) => {
25
29
  if (way === v.anonymous)
26
30
  return [way, v.anonymous, {}, '', undefined];
@@ -34,7 +38,7 @@ const accessOf = (lines, lock, v, source, operation, place) => {
34
38
  if (usable === undefined)
35
39
  return said('no-adapter', { source, operation, kinds: ways.join(', ') });
36
40
  const [scheme, kind, params, flow, issuer] = usable;
37
- return { kind, scheme, params, flow, account: kind === v.anonymous ? undefined : valueOf(lock, issuer ? 'access/issuer-account' : 'access/account').split('{place}').join(place).split('{source}').join(source).split('{issuer}').join(issuer ?? '') };
41
+ return { kind, scheme, params, flow, account: kind === v.anonymous ? undefined : account(issuer) };
38
42
  };
39
43
  export const stepOf = (lines, lock, source, operation) => {
40
44
  const v = vocabOf(lock);
@@ -57,4 +61,4 @@ const callOf = (lines, lock, source, operation, place) => {
57
61
  };
58
62
  const isCallable = (lock, classes) => classes.length === 1 && classes[0] === vocabOf(lock).callable;
59
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));
60
- export const planAct = (lines, lock, source, operation, place) => ((call) => (call.kind === 'call' && isCallable(lock, call.classes) ? said('is-read', { source, operation }) : 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));
@@ -1,45 +1,6 @@
1
1
  import type { Said } from './lock.ts';
2
- export interface Field {
3
- readonly name: string;
4
- readonly about: string;
5
- readonly type: string;
6
- readonly required: boolean;
7
- }
8
- export interface Tool {
9
- readonly name: string;
10
- readonly about: string;
11
- readonly fields: readonly Field[];
12
- readonly hints: readonly string[];
13
- }
14
- export interface Resource {
15
- readonly name: string;
16
- readonly about: string;
17
- readonly template: string;
18
- readonly mime: string;
19
- }
20
- export interface Prompt {
21
- readonly name: string;
22
- readonly about: string;
23
- readonly text: string;
24
- readonly line: string;
25
- readonly fields: readonly Field[];
26
- }
27
- export interface Surface {
28
- readonly name: string;
29
- readonly version: string;
30
- readonly tools: readonly Tool[];
31
- readonly resources: readonly Resource[];
32
- readonly prompts: readonly Prompt[];
33
- }
34
- /** Everything a client is offered, read off the lock alone: its tools, resources and prompts, each with the words a model sees. */
35
- export declare const surfaceOf: (lock: readonly Said[]) => Surface;
36
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. */
37
4
  export declare const sayIn: (lock: readonly Said[]) => (key: string, fields?: Readonly<Record<string, string | undefined>>) => string;
38
- export declare const refusedOn: (lock: readonly Said[], platform: string) => string | undefined;
39
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. */
40
6
  export declare const redIn: (lock: readonly Said[]) => (scope: string, coordinate: string, started: number, now: number) => readonly string[];
41
- /**
42
- * The fields a URI gives a resource template, or none when it is not one of its URIs: {name} matches one path step,
43
- * {?a,b} names query fields any of which may be absent, in any order, each decoded.
44
- */
45
- export declare const matchTemplate: (template: string, uri: string) => Readonly<Record<string, string>> | undefined;
@@ -1,32 +1,7 @@
1
- import { aboutOf, below, members, valueOf } from "./lock.js";
2
- const nameOf = (line) => line.scope.split('/')[1];
3
- const fieldsOf = (lock, scope) => ((required) => members(valueOf(lock, scope)).map((name) => ({
4
- name, about: aboutOf(lock, `${scope}/${name}`), type: valueOf(lock, `${scope}/${name}`), required: required.includes(name)
5
- })))(members(valueOf(lock, `${scope}/required`)));
6
- /** Everything a client is offered, read off the lock alone: its tools, resources and prompts, each with the words a model sees. */
7
- export const surfaceOf = (lock) => ({
8
- name: valueOf(lock, 'name'), version: valueOf(lock, 'version'),
9
- tools: below(lock, 'tool').map((line) => ({ name: nameOf(line), about: line.about ?? '', fields: fieldsOf(lock, line.scope), hints: members(valueOf(lock, `${line.scope}/hints`)) })),
10
- resources: below(lock, 'resource').map((line) => ({ name: nameOf(line), about: line.about ?? '', template: line.value ?? '', mime: valueOf(lock, `${line.scope}/mime`) })),
11
- prompts: below(lock, 'prompt').map((line) => ({ name: nameOf(line), about: line.about ?? '', text: aboutOf(lock, `${line.scope}/text`), line: aboutOf(lock, `${line.scope}/line`), fields: fieldsOf(lock, line.scope) })),
12
- });
1
+ import { aboutOf, valueOf } from "./lock.js";
2
+ import { cellLine, cellOf } from "../ports/surface.js";
13
3
  export const fill = (text, asked) => Object.entries(asked).reduce((at, [name, value]) => (value === undefined ? at : at.split(`{${name}}`).join(value)), text);
14
- export const sayIn = (lock) => (key, fields = {}) => fill(aboutOf(lock, `said/${key}`), fields);
15
- export const refusedOn = (lock, platform) => ((supported) => (supported.includes(platform) ? undefined : sayIn(lock)('platform', { platform, supported: supported.join('|') })))(members(valueOf(lock, 'platform/supported')));
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)));
16
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. */
17
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));
18
- /**
19
- * The fields a URI gives a resource template, or none when it is not one of its URIs: {name} matches one path step,
20
- * {?a,b} names query fields any of which may be absent, in any order, each decoded.
21
- */
22
- export const matchTemplate = (template, uri) => {
23
- const [path = '', query = ''] = template.split('{?');
24
- const names = [...path.matchAll(/\{(\w+)\}/g)].map((m) => m[1]);
25
- const pattern = new RegExp(`^${path.split(/\{\w+\}/).map((part) => part.replace(/[.*+?^$()|[\]\\]/g, '\\$&')).join('([^/?#]+)')}(?:\\?(.*))?$`);
26
- const got = pattern.exec(uri);
27
- if (got === null)
28
- return undefined;
29
- const params = new URLSearchParams(got[names.length + 1] ?? '');
30
- return { ...Object.fromEntries(names.map((name, i) => [name, decodeURIComponent(got[i + 1])])),
31
- ...Object.fromEntries(query.replace('}', '').split(',').filter(Boolean).flatMap((name) => ((v) => (v === null ? [] : [[name, v]]))(params.get(name)))) };
32
- };
@@ -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
+ };
@@ -1,16 +1,11 @@
1
1
  import { placeNameOf } from "../core/answer.js";
2
2
  import { canonical, parse } from '@lapxo/topos/wire';
3
3
  import { deviceOf, idIn, uncoveredIn } from "../core/device.js";
4
- import { newKey, publicOf } from "../core/keys.js";
5
4
  import { members, valueOf } from "../core/lock.js";
6
5
  import { fill, sayIn } from "../core/surface.js";
7
6
  import { adoptsIn } from "../adopt/adopt.js";
8
7
  /** A key of this device by its role: read from the keychain, or made there once; only its public half is ever handed on. */
9
- export const deviceKey = (context, role) => ((account) => {
10
- const held = context.keys.read(account);
11
- const secret = held ?? ((made) => { context.keys.write(account, made.secret); return context.keys.read(account) === made.secret ? made.secret : undefined; })(newKey());
12
- return secret === undefined ? undefined : publicOf(secret);
13
- })(deviceOf(context.lock, role).account);
8
+ export const deviceKey = (context, role) => context.keys.made?.(deviceOf(context.lock, role).account);
14
9
  /** 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. */
15
10
  export const rootIn = (context, place) => ((account) => (context.keys.read(account) === undefined ? { said: [sayIn(context.lock)('no-root', { place })] } : { account }))(fill(valueOf(context.lock, 'founding/root-account'), { place: placeNameOf(place) }));
16
11
  export const widening = (context, place, lines, lot) => {