@lapxo/head 0.1.3 → 0.1.4

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.
@@ -1,7 +1,12 @@
1
- import type { Attachment } from './contract.ts';
1
+ import type { Attachment, Obtained } from './contract.ts';
2
2
  import type { Context } from '../core/answer.ts';
3
3
  import type { Said } from '../core/lock.ts';
4
4
  import type { Call } from '../core/read.ts';
5
+ type Kept = Obtained & {
6
+ readonly at?: string;
7
+ };
8
+ export declare const sealed: (got: Kept) => string;
9
+ export declare const opened: (kept: string | undefined) => Kept | undefined;
5
10
  /** Whom a call is made as, never its secret: anonymous, or the access account and when the secret it holds now was obtained — still holding, unless what was made as it is only being looked back on; none while no secret is kept, or one is kept without saying when. */
6
11
  export declare const principalOf: (context: Context, plan: Call, current?: boolean) => string | undefined;
7
12
  /**
@@ -17,3 +22,4 @@ export declare const attachmentFor: (context: Context, place: string, lines: rea
17
22
  readonly said: string;
18
23
  readonly input?: string;
19
24
  }>;
25
+ export {};
@@ -1,10 +1,10 @@
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 { members, valueOf } from "../core/lock.js";
4
+ import { aboutOf, members, valueOf } from "../core/lock.js";
5
5
  import { fieldsOf, stepOf, vocabOf } from "../core/read.js";
6
- const sealed = (got) => Buffer.from(JSON.stringify(got)).toString('base64url');
7
- const opened = (kept) => { try {
6
+ export const sealed = (got) => Buffer.from(JSON.stringify(got)).toString('base64url');
7
+ export const opened = (kept) => { try {
8
8
  return kept === undefined ? undefined : JSON.parse(Buffer.from(kept, 'base64url').toString('utf8'));
9
9
  }
10
10
  catch {
@@ -55,7 +55,7 @@ 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, valueOf(context.lock, 'said/elicit/yes'))) === 'yes', choose: (message, choices) => context.person.choose(message, choices), visit: (message, to) => context.person.visit(message, to),
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
59
  form: (source, operation) => fieldsOf(lines, `${v.offers}/${source}/${operation}/${v.form}`), 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
  };
@@ -64,6 +64,7 @@ export interface Context {
64
64
  readonly signRoot: (place: string, lot: readonly string[], account: string) => Promise<Outcome>;
65
65
  readonly landRoot: (place: string, lot: readonly string[], account: string) => Promise<Outcome>;
66
66
  readonly settle: () => Promise<readonly string[]>;
67
+ readonly moved: (place: string) => boolean;
67
68
  readonly pinned: (place: string, file: string) => Promise<{
68
69
  readonly pin: string;
69
70
  readonly bytes: string;
@@ -82,6 +83,7 @@ export interface Context {
82
83
  readonly elicit: (message: string) => Promise<'yes' | 'no' | 'none'>;
83
84
  readonly now: () => number;
84
85
  readonly machine: () => number;
86
+ readonly begun: number;
85
87
  }
86
88
  export type Answer = (asked: Asked, context: Context) => Promise<readonly string[]>;
87
89
  /** What answers a line of the surface: the module named as the line, in the folder of its family. */
@@ -61,4 +61,4 @@ const callOf = (lines, lock, source, operation, place) => {
61
61
  };
62
62
  const isCallable = (lock, classes) => classes.length === 1 && classes[0] === vocabOf(lock).callable;
63
63
  export const planRead = (lines, lock, source, operation, place) => ((call) => (call.kind === 'call' && !isCallable(lock, call.classes) ? said('not-callable', { source, operation, class: call.classes.join(', ') }) : call))(callOf(lines, lock, source, operation, place));
64
- export const planAct = (lines, lock, source, operation, place) => ((call) => (call.kind === 'call' && isCallable(lock, call.classes) ? said('is-read', { source, operation }) : call.kind === 'call' && call.access.probe ? said('access-unstated', { source, operation }) : call))(callOf(lines, lock, source, operation, place));
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, method: call.carry['method']?.[0], path: call.carry['path']?.[0] }) : call))(callOf(lines, lock, source, operation, place));
@@ -12,8 +12,6 @@ export declare const pickedOrigin: (context: Context, place: string, lines: read
12
12
  } | {
13
13
  readonly said: readonly string[];
14
14
  }>;
15
- /** Calls of one place take turns, so one asked while another lands sees its receipt; nothing waits on a timer. */
16
- export declare const inTurn: <T>(place: string, act: () => Promise<T>) => Promise<T>;
17
15
  export interface Frame {
18
16
  readonly say: (key: string, fields?: Readonly<Record<string, string | undefined>>) => readonly string[];
19
17
  readonly lands: (...lot: readonly string[]) => Promise<Outcome>;
@@ -43,7 +41,7 @@ export interface Frame {
43
41
  readonly uncovered: (role: string, scopes: readonly string[]) => readonly string[] | undefined;
44
42
  }
45
43
  /**
46
- * The steps a read and an act share: the place, its turn, the lines it stands on once bound settled them, the offer's
44
+ * The steps a read and an act share: the place, the lines it stands on once bound settled them, the offer's
47
45
  * plan, the transport its origin names and the request it prepares; then what is particular, given a frame that says
48
46
  * by the lock, attaches access when asked, types what came back and lands a line under head's key.
49
47
  */
@@ -27,18 +27,15 @@ export const pickedOrigin = async (context, place, lines, source, plan) => {
27
27
  const landed = await by.land([canonical(line(chosen))]);
28
28
  return 'refused' in landed ? { said: [sayIn(context.lock)('not-landed', { why: landed.refused })] } : { lines: [...lines, line(chosen)] };
29
29
  };
30
- const turns = new Map();
31
- /** Calls of one place take turns, so one asked while another lands sees its receipt; nothing waits on a timer. */
32
- export const inTurn = (place, act) => { const next = (turns.get(place) ?? Promise.resolve()).then(act, act); turns.set(place, next.catch(() => undefined)); return next; };
33
30
  /**
34
- * The steps a read and an act share: the place, its turn, the lines it stands on once bound settled them, the offer's
31
+ * The steps a read and an act share: the place, the lines it stands on once bound settled them, the offer's
35
32
  * plan, the transport its origin names and the request it prepares; then what is particular, given a frame that says
36
33
  * by the lock, attaches access when asked, types what came back and lands a line under head's key.
37
34
  */
38
35
  export const called = async (asked, context, planOf, then) => {
39
36
  const say = (key, fields = {}) => [sayIn(context.lock)(key, fields)];
40
37
  const [source, operation] = [asked['source'] ?? '', asked['operation'] ?? ''];
41
- return inPlace(asked, context, (place) => inTurn(place, async () => {
38
+ return inPlace(asked, context, async (place) => {
42
39
  const held = await standingFor(context, place);
43
40
  if ('unsettled' in held)
44
41
  return say('unsettled', { source, why: held.unsettled });
@@ -72,5 +69,5 @@ export const called = async (asked, context, planOf, then) => {
72
69
  const attach = async () => ((got) => ('said' in got ? { said: say(got.said, { source, operation, kinds: plan.access.kind, input: got.input }) } : got))(await attachmentFor(context, place, lines, plan));
73
70
  const uncovered = (role, scopes) => uncoveredIn(context.lock, lines, context.device(role), place, [], scopes);
74
71
  return then({ widen, word: (scope) => valueOf(context.lock, scope), say, place, lines, source, operation, plan, prepared, most, typed, attach, send: (attachment) => prepared.send(attachment, most('read/response-bytes')), lands, uncovered });
75
- }));
72
+ });
76
73
  };
@@ -7,6 +7,7 @@ import { canonical } from '@lapxo/topos/wire';
7
7
  import { called } from "./call.js";
8
8
  import { principalOf } from "../access/obtain.js";
9
9
  import { jsonOf } from "../access/contract.js";
10
+ import { refusedAccess } from "../sources/refused.js";
10
11
  /** An intent an earlier act left open, closed by the read that looked at what it would have changed: a line naming that read-s digest, signed by head. */
11
12
  const closeWith = async (intent, source, digest, { word, lines, lands }, context) => {
12
13
  const say = sayIn(context.lock);
@@ -24,7 +25,7 @@ const closeWith = async (intent, source, digest, { word, lines, lands }, context
24
25
  * the reuse the place allows, it answers from what was kept: nothing called. Asked for history, it shows what was kept
25
26
  * of that request as of when it was read — evidence of that read, never a current answer — where the place allows it.
26
27
  */
27
- export const readSource = (asked, context) => ((started) => called(asked, context, planRead, async ({ word, say, place, lines, source, operation, plan, prepared, most, typed, attach, send, lands, widen, uncovered }) => {
28
+ export const readSource = (asked, context) => called(asked, context, planRead, async ({ word, say, place, lines, source, operation, plan, prepared, most, typed, attach, send, lands, widen, uncovered }) => {
28
29
  const [family, at, when] = [word('receipt/family'), word('receipt/at'), new Date(context.now()).toISOString()];
29
30
  const closing = (digest) => closeWith(asked['closes'], source, digest, { word, lines, lands }, context);
30
31
  const scopes = (principal) => [scopeOf(family, source, operation, prepared.key, principal), `${scopeOf(word('receipt/kept'), source, operation, prepared.key, principal)}/${basisOf(lines, word, source)}`];
@@ -66,7 +67,7 @@ export const readSource = (asked, context) => ((started) => called(asked, contex
66
67
  const [scope, kept] = scopes(principal);
67
68
  const answered = held(receiptsOf(lines, at, kept).find((k) => k.at >= newest.at)?.digest);
68
69
  if (answered !== undefined)
69
- return [...new TextDecoder().decode(answered.bytes).split('\n'), ...say('reused', { scope: kept, digest: answered.digest, at: newest.at }), ...(await closing(newest.digest)), ...redIn(context.lock)('answer/kept', `answer/${source}/${operation}@${word('look/call')}`, started, context.machine())];
70
+ return [...new TextDecoder().decode(answered.bytes).split('\n'), ...say('reused', { scope: kept, digest: answered.digest, at: newest.at }), ...(await closing(newest.digest)), ...redIn(context.lock)('answer/kept', `answer/${source}/${operation}`, context.begun, context.machine())];
70
71
  const body = held(newest.digest);
71
72
  if (body !== undefined) {
72
73
  const cut = body.bytes.indexOf(10);
@@ -85,6 +86,8 @@ export const readSource = (asked, context) => ((started) => called(asked, contex
85
86
  const got = await send(attached.attachment);
86
87
  if (got.kind !== 'got')
87
88
  return say(got.kind === 'over' ? 'too-large' : 'unreachable-source', { source });
89
+ if (got.status === 401 && attached.principal !== 'anonymous')
90
+ return refusedAccess(context, place, lines, plan, operation);
88
91
  const digest = context.cache.keep(Buffer.concat([Buffer.from(`${got.status}\n`), got.bytes]));
89
92
  const answer = await typed(String(got.status), got.bytes);
90
93
  const observed = plan.access.probe && got.status < 300 ? [canonical({ at: `${at}:${when}`, by: 'target', form: 'alphabet', measure: word('vocab/access'), role: 'writes', scope: observedAt(context.lock, source, operation), value: word('vocab/anonymous') })] : [];
@@ -92,4 +95,4 @@ export const readSource = (asked, context) => ((started) => called(asked, contex
92
95
  const by = `${word('access/family')}/${source}/${word('access/authorizer')}`, authorizer = Object.entries(found ?? {}).flatMap(([k, f]) => Object.entries(f).map(([measure, all]) => canonical({ at: `${at}:${when}`, by: 'target', form: 'alphabet', measure, role: 'writes', scope: k ? `${by}/${k}` : by, value: all.join('|') })));
93
96
  const landed = await lands(draftOf(family, at, scope, digest, when), ...(attached.principal === undefined ? [] : keeping(kept, answer)), ...observed, ...authorizer);
94
97
  return [...answer, ...('lines' in landed ? [...landed.lines, ...(await closing(digest))] : say('not-landed', { why: landed.refused })), ...(found?.['']?.['issuer'] && 'lines' in landed ? say('authorizer-observed', { source, issuer: found['']['issuer'][0] }) : [])];
95
- }))(context.machine());
98
+ });
package/dist/src/index.js CHANGED
@@ -13,7 +13,8 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
13
13
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
14
14
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
15
15
  import { answerOf } from "./core/answer.js";
16
- import { valueOf } from "./core/lock.js";
16
+ import { parse } from '@lapxo/topos/wire';
17
+ import { aboutOf, valueOf } from "./core/lock.js";
17
18
  import { fill, sayIn } from "./core/surface.js";
18
19
  import { matchTemplate, refusedOn, shapeOf, shapeSchema, surfaceOf } from "./ports/surface.js";
19
20
  import { closing, cooled, founded, land, landAct, limits, pinnedAt, policyAt, run, settledView, sign, standingAt, told, warm, words } from "./ports/bound.js";
@@ -26,7 +27,6 @@ import { keychain } from "./ports/keychain.js";
26
27
  import { algorithmOf, deviceOf, idIn } from "./core/device.js";
27
28
  import { publicOf } from "./ports/keys.js";
28
29
  import { personOf } from "./ports/access/person.js";
29
- import { inTurn } from "./flows/call.js";
30
30
  const [ext, root] = ((self) => [self.slice(self.lastIndexOf('.')), fileURLToPath(new URL(self.endsWith('.ts') ? '..' : '../..', self))])(import.meta.url);
31
31
  const lock = lockAt(root);
32
32
  const surface = surfaceOf(lock);
@@ -34,8 +34,9 @@ const runs = valueOf(lock, 'runs');
34
34
  Object.assign(words, Object.fromEntries(['read', 'view', 'sign', 'land', 'key', 'signer', 'pin', 'topos', 'standing', 'evidence', 'act', 'profile', 'receipts', 'refusal'].map((w) => [w, valueOf(lock, `bound/${w}`)])));
35
35
  Object.assign(limits, { wait: Number(valueOf(lock, 'bound/wait').split('..')[1]), most: Number(valueOf(lock, 'bound/printed').split('..')[1]) });
36
36
  Object.assign(warmth, { spares: Number(valueOf(lock, 'bound/spares').split('..')[1]), boot: valueOf(lock, 'bound/spare-env').split('|') });
37
- /** One call at a time, with bound started ahead for it and nothing of it kept once it is answered. */
38
- const turn = (act) => inTurn('', () => { void warm(runs); return act().finally(cooled); });
37
+ /** 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; bound started ahead for it, and nothing of it kept once it is answered. */
38
+ let last = Promise.resolve();
39
+ const turn = (act) => ((next) => { last = next.catch(() => undefined); return next; })(last.then(() => { void warm(runs); return act().finally(cooled); }));
39
40
  const offered = (n) => (n ? { listChanged: false } : undefined);
40
41
  const server = new Server({ name: surface.name, version: surface.version }, { capabilities: { tools: offered(surface.tools.length), resources: offered(surface.resources.length), prompts: offered(surface.prompts.length) } });
41
42
  /** 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. */
@@ -58,19 +59,21 @@ const device = (role) => devices.get(role) ?? ((secret) => (secret === undefined
58
59
  const asRoot = (account) => ((device, key) => [key, device.signer, { BOUND_SIGNERS: registryFor(device.signer, service, account, key, valueOf(lock, 'founding/algorithm'), store) }])(deviceOf(lock), valueOf(lock, 'founding/root').split('/')[1]);
59
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. */
60
61
  const contextOf = (signal) => {
61
- /** One call's folds of a place, kept until something lands in it or is laid there; and the places its root acted in, whose receipts close once, when the call ends. */
62
- const [folds, touched] = [new Map(), new Set()], forget = (place) => { for (const k of [...folds.keys()])
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()])
63
64
  if (k.endsWith(`\0${place}`))
64
65
  folds.delete(k); };
66
+ const read = (at) => at?.startsWith(`${valueOf(lock, 'receipt/at')}:`) === true;
65
67
  const once = (key, place, fold) => (folds.get(`${key}\0${place}`) ?? ((p) => (folds.set(`${key}\0${place}`, p), p))(fold()));
66
- const acted = (place, root, done) => done.finally(() => { forget(place); if (root)
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)
67
70
  touched.add(place); });
68
71
  const signRoot = (place, lot, account) => sign(runs, place, lot, ...asRoot(account), signal);
69
72
  const signAs = (place, lot, role, lines) => ((one, id) => sign(runs, place, lot, id, one.signer, { BOUND_SIGNERS: registryFor(one.signer, service, one.account, id, algorithmOf(lines), store) }, signal))(deviceOf(lock, role), idIn(lines, device(role)) ?? deviceOf(lock, role).account);
70
73
  return {
71
74
  lock,
72
75
  bound: (cwd, args) => run(runs, cwd, args, {}, signal),
73
- standing: (place) => once('standing', place, () => standingAt(runs, place, (lines) => ['head', 'person'].flatMap((role) => ((id) => (id === undefined ? [] : [id]))(idIn(lines, device(role)))), (line) => line.at?.startsWith(`${valueOf(lock, 'receipt/at')}:`) === true, signal)),
76
+ 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)),
74
77
  policy: (place) => once('policy', place, () => policyAt(runs, place, signal)),
75
78
  world: (place, view) => once(`world/${view}`, place, () => settledView(runs, place, view, signal)),
76
79
  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)}`) : ''),
@@ -80,6 +83,7 @@ const contextOf = (signal) => {
80
83
  found: (place, account) => acted(place, true, founded(runs, place, ...asRoot(account), signal)),
81
84
  signRoot,
82
85
  landRoot: (place, lot, account) => acted(place, true, landAct(runs, place, lot, ...asRoot(account), signal)),
86
+ moved: (place) => moved.has(place),
83
87
  settle: async () => { const red = []; for (const place of touched) {
84
88
  const why = await closing(runs, place, signal);
85
89
  if (why !== undefined)
@@ -93,9 +97,10 @@ const contextOf = (signal) => {
93
97
  landAs: (place, lot, role, lines) => acted(place, false, ((one, id) => landAct(runs, place, lot, id, one.signer, { BOUND_SIGNERS: registryFor(one.signer, service, one.account, id, algorithmOf(lines), store) }, signal))(deviceOf(lock, role), idIn(lines, device(role)) ?? deviceOf(lock, role).account)),
94
98
  land: (place, signed) => acted(place, true, land(runs, place, signed, signal)),
95
99
  told: (place, signed) => told(runs, place, signed, words.standing, signal),
96
- elicit: (message) => person.consent(message, valueOf(lock, 'said/elicit/yes')),
100
+ elicit: (message) => person.consent(message, aboutOf(lock, 'said/elicit/yes')),
97
101
  now: () => Date.now(),
98
102
  machine: () => Date.now() - waited,
103
+ begun: Date.now() - waited,
99
104
  keys,
100
105
  device,
101
106
  roots: async () => (server.getClientCapabilities()?.roots ? (await server.listRoots()).roots.flatMap((root) => folderOf(root.uri) ?? []) : []),
@@ -1,7 +1,9 @@
1
1
  import { type Asked, type Context } from '../core/answer.ts';
2
2
  /**
3
- * The first source a person connects with no place named and none to read from: their place is founded in the folder
4
- * the lock names under their own home, and the source connected to it, on one yes for both. A folder that already holds
5
- * a place is used as it stands, never founded again, and its connect asks its own yes.
3
+ * The first source a person connects with no place named and none to read from, on one yes that says all it grants:
4
+ * their place founded in the folder the lock names under their own home, the source connected to it, and reads of it
5
+ * granted until the access period ends. A document that does not arrive is no refusal: the place is founded owing it
6
+ * by its address. Either way the question asked comes back as what to ask next. A folder that already holds a place is
7
+ * used as it stands, never founded again, and its connect asks its own yes.
6
8
  */
7
9
  export declare const firstRun: (asked: Asked, context: Context, then: (place: string, covered: boolean) => Promise<readonly string[]>) => Promise<readonly string[]>;
@@ -1,21 +1,38 @@
1
1
  import { basename, join } from 'node:path';
2
- import { LOCK } from '@lapxo/topos/wire';
2
+ import { canonical, LOCK } from '@lapxo/topos/wire';
3
3
  import { unless } from "../core/answer.js";
4
+ import { untilOf } from "../core/act.js";
4
5
  import { valueOf } from "../core/lock.js";
5
6
  import { sayIn } from "../core/surface.js";
7
+ import { rootIn } from "../flows/device.js";
6
8
  import { bornAt } from "../places/found.js";
9
+ import { attachedFor } from "../sources/access.js";
7
10
  /**
8
- * The first source a person connects with no place named and none to read from: their place is founded in the folder
9
- * the lock names under their own home, and the source connected to it, on one yes for both. A folder that already holds
10
- * a place is used as it stands, never founded again, and its connect asks its own yes.
11
+ * The first source a person connects with no place named and none to read from, on one yes that says all it grants:
12
+ * their place founded in the folder the lock names under their own home, the source connected to it, and reads of it
13
+ * granted until the access period ends. A document that does not arrive is no refusal: the place is founded owing it
14
+ * by its address. Either way the question asked comes back as what to ask next. A folder that already holds a place is
15
+ * used as it stands, never founded again, and its connect asks its own yes.
11
16
  */
12
17
  export const firstRun = async (asked, context, then) => {
13
- const say = sayIn(context.lock), dir = join(context.personal(), valueOf(context.lock, 'first-run/folder')), name = basename(dir);
18
+ const say = sayIn(context.lock), word = (k) => valueOf(context.lock, k), dir = join(context.personal(), word('first-run/folder')), name = basename(dir);
14
19
  if (context.files.has(dir, LOCK))
15
20
  return then(dir, false);
16
- const refused = await unless(context, say('elicit-first-run', { dir, source: asked['source'] ?? '', document: asked['document'] ?? '' }));
21
+ const [source, document, until] = [(asked['source'] ?? '').toLowerCase().replace(/[^a-z0-9-]+/g, '-'), asked['document'] ?? '', untilOf(word('access/period'), context.now())];
22
+ const refused = await unless(context, say('elicit-first-run', { dir, source, document, until }));
17
23
  if (refused)
18
24
  return refused;
19
- const born = await bornAt(context, dir, name);
20
- return born[0] === say('born', { name, dir }) ? [...born, ...(await then(dir, true))] : born;
25
+ const born = await bornAt(context, dir, name), root = rootIn(context, dir);
26
+ if (born[0] !== say('born', { name, dir }) || 'said' in root)
27
+ return 'said' in root ? [...born, ...root.said] : born;
28
+ const connected = await then(dir, true), unread = connected.some((l) => l.startsWith('said/head/document-unread ')), held = unread ? undefined : await context.policy(dir);
29
+ if (held !== undefined && ('refused' in held || !held.lines.some((l) => l.scope === `uses/${source}`)))
30
+ return [...born, ...connected];
31
+ const line = (scope, measure, value, role = 'writes') => canonical({ at: 'policy:head/first-run', by: 'target', form: 'alphabet', measure, role, scope, value });
32
+ const owed = unread ? [line(`${word('place/needs')}/bytes/${source}`, 'id', document, 'demands')] : [];
33
+ const landed = await context.landRoot(dir, [line(word('grant/family'), 'sources', source), line(word('grant/family'), 'until', until), ...owed], root.account);
34
+ if ('refused' in landed)
35
+ return [...born, ...connected, say('not-landed', { why: landed.refused })];
36
+ const access = unread ? undefined : await attachedFor(context, dir, source);
37
+ return [...born, ...(unread ? [] : connected), ...landed.lines, ...(access !== undefined && 'said' in access ? access.said : []), say(unread ? 'bytes-owed' : 'ready', { source, place: dir, document, operation: asked['operation'], args: asked['args'] })];
21
38
  };
@@ -0,0 +1,10 @@
1
+ import { type Asked, type Context } from '../core/answer.ts';
2
+ /**
3
+ * What a place founded on a first connect still owes: its source's document, by the address the person gave. Asked again,
4
+ * it is read there; once it arrives, the source is connected from those bytes and pinned by their digest, the demand is
5
+ * withdrawn and the question is answered. Until then it is not: what is owed is said, and nothing else is asked.
6
+ */
7
+ export declare const paidIn: (asked: Asked, context: Context, place: string) => Promise<{
8
+ readonly said: readonly string[];
9
+ readonly answer: boolean;
10
+ }>;
@@ -0,0 +1,28 @@
1
+ import { canonical } from '@lapxo/topos/wire';
2
+ import { standingOr } from "../core/answer.js";
3
+ import { valueOf } from "../core/lock.js";
4
+ import { sayIn } from "../core/surface.js";
5
+ import { rootIn } from "../flows/device.js";
6
+ import { laidIn } from "../sources/connect.js";
7
+ /**
8
+ * What a place founded on a first connect still owes: its source's document, by the address the person gave. Asked again,
9
+ * it is read there; once it arrives, the source is connected from those bytes and pinned by their digest, the demand is
10
+ * withdrawn and the question is answered. Until then it is not: what is owed is said, and nothing else is asked.
11
+ */
12
+ export const paidIn = async (asked, context, place) => {
13
+ const say = sayIn(context.lock), word = (k) => valueOf(context.lock, k), source = asked['source'] ?? '';
14
+ const held = await standingOr(context, place), scope = `${word('place/needs')}/bytes/${source}`;
15
+ const owed = 'said' in held ? undefined : held.lines.find((l) => l.scope === scope && l.role === 'demands' && l.value !== 'withdraw');
16
+ if (owed === undefined)
17
+ return { said: [], answer: true };
18
+ const document = owed.value ?? '', read = await context.files.read.documentOf(context, place, document), root = rootIn(context, place);
19
+ if ('said' in root)
20
+ return { said: root.said, answer: false };
21
+ if (read === undefined)
22
+ return { said: [say('bytes-owed', { source, place, document })], answer: false };
23
+ const laid = await laidIn(context, place, root.account)(source, document, read, undefined, context.machine());
24
+ if (!laid.connected)
25
+ return { said: laid.said, answer: false };
26
+ const landed = await context.landRoot(place, [canonical({ ...Object.fromEntries(Object.entries(owed).filter(([k]) => !['sig', 'epoch'].includes(k))), by: 'target', value: 'withdraw' })], root.account);
27
+ return 'refused' in landed ? { said: [...laid.said, say('not-landed', { why: landed.refused })], answer: false } : { said: [...laid.said, ...landed.lines], answer: true };
28
+ };
@@ -0,0 +1,12 @@
1
+ import type { Asked, Context } from '../core/answer.ts';
2
+ /**
3
+ * What a person names as a source's document, made a document head can read, before anything is founded or connected:
4
+ * an address or a file is as given; a site alone is looked for where it says its documents are (its API catalog) and at
5
+ * the paths the lock names, the first that reads taken; a folder, at the same names inside it. A source the person names
6
+ * none for is named after the site or the folder. A site where nothing reads is said, with what was tried.
7
+ */
8
+ export declare const resolved: (asked: Asked, context: Context) => Promise<{
9
+ readonly asked: Asked;
10
+ } | {
11
+ readonly said: readonly string[];
12
+ }>;
@@ -0,0 +1,22 @@
1
+ import { basename, dirname, join } from 'node:path';
2
+ import { EXTENSION } from '@lapxo/topos/wire';
3
+ import { members, valueOf } from "../core/lock.js";
4
+ import { sayIn } from "../core/surface.js";
5
+ /**
6
+ * What a person names as a source's document, made a document head can read, before anything is founded or connected:
7
+ * an address or a file is as given; a site alone is looked for where it says its documents are (its API catalog) and at
8
+ * the paths the lock names, the first that reads taken; a folder, at the same names inside it. A source the person names
9
+ * none for is named after the site or the folder. A site where nothing reads is said, with what was tried.
10
+ */
11
+ export const resolved = async (asked, context) => {
12
+ const [at, word] = [asked['document'] ?? '', (k) => valueOf(context.lock, k)], named = (name) => asked['source'] || name.toLowerCase().replace(/[^a-z0-9-]+/g, '-');
13
+ if (/^([a-z]+:\/\/)?[^/\s]+\.[^/\s]+\/?$/.test(at) && URL.canParse(/^[a-z]+:\/\//.test(at) ? at : `${word('use/scheme')}//${at}`)) {
14
+ const site = new URL(/^[a-z]+:\/\//.test(at) ? at : `${word('use/scheme')}//${at}`), tried = await context.files.read.describedAt(context, site.origin);
15
+ for (const document of tried)
16
+ if (((read) => read !== undefined && `.${read.kind}` !== EXTENSION)(await context.files.read.documentOf(context, '', document)))
17
+ return { asked: { ...asked, document, source: named(site.hostname) } };
18
+ return { said: [sayIn(context.lock)('use-none', { origin: site.origin, tried: tried.join(', ') || '—' })] };
19
+ }
20
+ const inside = at.startsWith('/') && context.files.list(dirname(at), basename(at)).length ? members(word('use/documents')).map((p) => basename(p)).find((n) => context.files.has(at, n)) : undefined;
21
+ return { asked: inside === undefined ? asked : { ...asked, document: join(at, inside), source: named(basename(at)) } };
22
+ };
@@ -28,10 +28,14 @@ export interface Surface {
28
28
  readonly name: string;
29
29
  readonly version: string;
30
30
  readonly tools: readonly Tool[];
31
+ readonly catalogue: readonly Tool[];
31
32
  readonly resources: readonly Resource[];
32
33
  readonly prompts: readonly Prompt[];
33
34
  }
34
- /** Everything a client is offered, read off the lock alone: its tools, resources and prompts, each with the words a model sees. */
35
+ /**
36
+ * Everything a client is offered, read off the lock alone, each with the words a model sees: the tools surface/tools
37
+ * names, out of the catalogue the tool lines are (every one of them where it names none), its resources and prompts.
38
+ */
35
39
  export declare const surfaceOf: (lock: readonly Said[]) => Surface;
36
40
  export declare const refusedOn: (lock: readonly Said[], platform: string) => string | undefined;
37
41
  /**
@@ -4,13 +4,15 @@ const nameOf = (line) => line.scope.split('/')[1];
4
4
  const fieldsOf = (lock, scope) => ((required) => members(valueOf(lock, scope)).map((name) => ({
5
5
  name, about: aboutOf(lock, `${scope}/${name}`), type: valueOf(lock, `${scope}/${name}`), required: required.includes(name)
6
6
  })))(members(valueOf(lock, `${scope}/required`)));
7
- /** Everything a client is offered, read off the lock alone: its tools, resources and prompts, each with the words a model sees. */
8
- export const surfaceOf = (lock) => ({
9
- name: valueOf(lock, 'name'), version: valueOf(lock, 'version'),
10
- tools: below(lock, 'tool').map((line) => ({ name: nameOf(line), about: line.about ?? '', fields: fieldsOf(lock, line.scope), hints: members(valueOf(lock, `${line.scope}/hints`)) })),
7
+ /**
8
+ * Everything a client is offered, read off the lock alone, each with the words a model sees: the tools surface/tools
9
+ * names, out of the catalogue the tool lines are (every one of them where it names none), its resources and prompts.
10
+ */
11
+ export const surfaceOf = (lock) => ((catalogue, offered) => ({
12
+ name: valueOf(lock, 'name'), version: valueOf(lock, 'version'), catalogue, tools: offered.length ? catalogue.filter((t) => offered.includes(t.name)) : catalogue,
11
13
  resources: below(lock, 'resource').map((line) => ({ name: nameOf(line), about: line.about ?? '', template: line.value ?? '', mime: valueOf(lock, `${line.scope}/mime`) })),
12
14
  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) })),
13
- });
15
+ }))(below(lock, 'tool').map((line) => ({ name: nameOf(line), about: line.about ?? '', fields: fieldsOf(lock, line.scope), hints: members(valueOf(lock, `${line.scope}/hints`)) })), members(valueOf(lock, 'surface/tools')));
14
16
  export const refusedOn = (lock, platform) => ((supported) => (supported.includes(platform) ? undefined : sayIn(lock)('platform', { platform, supported: supported.join('|') })))(members(valueOf(lock, 'platform/supported')));
15
17
  /**
16
18
  * The fields a URI gives a resource template, or none when it is not one of its URIs: {name} matches one path step,
@@ -1,4 +1,5 @@
1
1
  import type { Asked, Context } from '../core/answer.ts';
2
+ import type { Read } from '../ports/documents.ts';
2
3
  /**
3
4
  * A source connected to a place, once the person said yes; a lock is no source, and adopting one as a parent is refused
4
5
  * by name, since composing two places' policies is bound's. A document — and an overlay beside it — is laid whatever its
@@ -11,3 +12,10 @@ import type { Asked, Context } from '../core/answer.ts';
11
12
  * compiled. Nothing the sharing place was granted comes with it: access is asked here, issuer by issuer.
12
13
  */
13
14
  export declare const connected: (asked: Asked, context: Context, covered?: boolean) => Promise<readonly string[]>;
15
+ type Connection = {
16
+ readonly said: readonly string[];
17
+ readonly connected: boolean;
18
+ };
19
+ type Laid = (source: string, document: string, doc: Read, overlay: Read | undefined, started: number) => Promise<Connection>;
20
+ export declare const laidIn: (context: Context, place: string, account: string) => Laid;
21
+ export {};
@@ -1,17 +1,15 @@
1
- import { join } from 'node:path';
2
- import { canonical, EXTENSION, LOCK } from '@lapxo/topos/wire';
1
+ import { canonical, EXTENSION } from '@lapxo/topos/wire';
3
2
  import { inPlace, placeOf, unless } from "../core/answer.js";
4
3
  import { valueOf } from "../core/lock.js";
5
4
  import { fieldsOf, vocabOf } from "../core/read.js";
6
- import { fill, redIn, sayIn } from "../core/surface.js";
5
+ import { redIn, sayIn } from "../core/surface.js";
7
6
  import { digestIn, linesOf } from "../core/worlds.js";
8
7
  import { coveredIn, owedIn, unpacked } from "../core/package.js";
9
8
  import { paidBy, pinIn } from "../compile/pin.js";
10
9
  import { adopted } from "../adopt/adopt.js";
11
10
  import { firstRun } from "../onboard/first.js";
11
+ import { resolved } from "../onboard/site.js";
12
12
  import { rootIn } from "../flows/device.js";
13
- import { bornAt } from "../places/found.js";
14
- import { untilOf } from "../core/act.js";
15
13
  import { attachedFor } from "./access.js";
16
14
  /**
17
15
  * A source connected to a place, once the person said yes; a lock is no source, and adopting one as a parent is refused
@@ -24,8 +22,9 @@ import { attachedFor } from "./access.js";
24
22
  * its pins rest in, its pin held to the package's; one that differs is refused by name, and the place holds what it
25
23
  * compiled. Nothing the sharing place was granted comes with it: access is asked here, issuer by issuer.
26
24
  */
27
- export const connected = async (asked, context, covered = false) => (asked['source'] && !(await placeOf(asked, context)) ? firstRun(asked, context, (dir, covered) => connected({ ...asked, place: dir }, context, covered))
28
- : !asked['source'] && /^([a-z]+:\/\/)?[^/\s]+\/?$/.test(asked['document'] ?? '') ? used(asked, context) : ((started) => inPlace(asked, context, async (place) => {
25
+ export const connected = async (asked, context, covered = false) => ((got) => ('said' in got ? got.said : connectedAs(got.asked, context, covered)))(await resolved(asked, context));
26
+ const connectedAs = async (asked, context, covered = false) => (asked['source'] && !(await placeOf(asked, context)) ? firstRun(asked, context, (dir, covered) => connected({ ...asked, place: dir }, context, covered))
27
+ : ((started) => inPlace(asked, context, async (place) => {
29
28
  const say = sayIn(context.lock), digest = digestIn(context.lock), { documentOf, fetchedAs, kindOf } = context.files.read;
30
29
  const [source, document] = [(asked['source'] ?? '').toLowerCase().replace(/[^a-z0-9-]+/g, '-'), asked['document'] ?? ''];
31
30
  const [via, named, accept, lock] = [asked['via'] ?? '', asked['digest'] ?? '', /^[\w.+-]+\/[\w.+-]+$/.test(asked['accept'] ?? '') ? asked['accept'] : undefined, `.${kindOf(document)}` === EXTENSION];
@@ -54,38 +53,8 @@ export const connected = async (asked, context, covered = false) => (asked['sour
54
53
  return [say('document-unread', { document })];
55
54
  if (named && digest(read.bytes) !== named)
56
55
  return [say('document-digest', { document, digest: named, got: digest(read.bytes) })];
57
- return `.${read.kind}` === EXTENSION ? [say('parent-lock', { place, parent: source })] : laidIn(context, place, root.account)(source, document, read, overlay, started);
56
+ return `.${read.kind}` === EXTENSION ? [say('parent-lock', { place, parent: source })] : (await laidIn(context, place, root.account)(source, document, read, overlay, started)).said;
58
57
  }))(context.machine()));
59
- const used = async (asked, context) => {
60
- const [say, word, until] = [sayIn(context.lock), (k) => valueOf(context.lock, k), untilOf(valueOf(context.lock, 'access/period'), context.now())], at = /^[a-z]+:\/\//.test(asked['document'] ?? '') ? asked['document'] : `${word('use/scheme')}//${asked['document'] ?? ''}`;
61
- if (!URL.canParse(at))
62
- return [say('document-unread', { document: asked['document'] ?? '' })];
63
- const [origin, source] = [new URL(at).origin, new URL(at).hostname.replace(/[^a-z0-9-]+/g, '-')], dir = join(fill(word('use/folder'), { home: context.home() }), source);
64
- if (context.files.has(dir, LOCK))
65
- return [say('use-held', { origin, dir })];
66
- const refused = await unless(context, say('elicit-use', { origin, dir, until }));
67
- if (refused)
68
- return refused;
69
- const born = await bornAt(context, dir, source), root = rootIn(context, dir), tried = [];
70
- if (born[0] !== say('born', { name: source, dir }) || 'said' in root)
71
- return 'said' in root ? [...born, ...root.said] : born;
72
- for (const document of await context.files.read.describedAt(context, origin)) {
73
- const read = await context.files.read.documentOf(context, dir, document);
74
- if (read === undefined || `.${read.kind}` === EXTENSION) {
75
- tried.push(document);
76
- continue;
77
- }
78
- const said = await laidIn(context, dir, root.account)(source, document, read, undefined, context.machine()), held = await context.policy(dir);
79
- if ('refused' in held || !held.lines.some((l) => l.scope === `uses/${source}`)) {
80
- tried.push(document);
81
- continue;
82
- }
83
- const line = (scope, measure, value, form = 'alphabet') => canonical({ at: 'policy:head/use', by: 'target', form, measure, role: 'writes', scope, value });
84
- const landed = await context.landRoot(dir, [line(`${word('allow/family')}/${source}`, 'calls', '0..0', 'interval'), line(word('grant/family'), 'sources', source), line(word('grant/family'), 'until', until)], root.account);
85
- return [say('used', { origin, dir, source, until }), ...said, ...('refused' in landed ? [say('not-landed', { why: landed.refused })] : landed.lines)];
86
- }
87
- return [...born, say('use-none', { origin, tried: tried.join(', ') || '—' })];
88
- };
89
58
  const received = async (context, place, document, bytes, named, laid, account) => {
90
59
  const [say, got] = [sayIn(context.lock), unpacked(context, document, bytes, await context.files.read.besideOf(context, document, linesOf(bytes)))];
91
60
  if (!got.sources)
@@ -95,20 +64,20 @@ const received = async (context, place, document, bytes, named, laid, account) =
95
64
  if (refused)
96
65
  return refused;
97
66
  for (const s of got.sources)
98
- said.push(...(await laid(s.source, s.document.at, s.document, s.overlay, context.machine())));
67
+ said.push(...(await laid(s.source, s.document.at, s.document, s.overlay, context.machine())).said);
99
68
  const held = await context.policy(place), [pinOf, owed] = [(source) => ('refused' in held ? '' : held.lines.find((l) => l.scope === `uses/${source}`)?.value ?? ''), owedIn(context, 'refused' in held ? [] : held.lines, got.sources, got.open ?? [], named)];
100
69
  const [off, landed] = [got.sources.filter((s) => pinOf(s.source) !== s.pin), owed.length ? await context.landRoot(place, owed, account) : undefined];
101
70
  return [...said, ...(off.length ? off.map((s) => say('package-pin', { source: s.source, pin: s.pin, got: pinOf(s.source) || 'none' })) : names ? [say('received', { place, package: named, sources: names })] : []),
102
71
  ...(got.open ?? []).map((o) => say('received-open', { source: o.source, waits: o.waits })), ...(landed && 'refused' in landed ? [say('not-landed', { why: landed.refused })] : [])];
103
72
  };
104
- const laidIn = (context, place, account) => (source, document, doc, overlay, started) => (async () => {
73
+ export const laidIn = (context, place, account) => (source, document, doc, overlay, started) => (async () => {
105
74
  const say = sayIn(context.lock), v = vocabOf(context.lock), word = (k) => valueOf(context.lock, k), digest = digestIn(context.lock);
106
75
  const lands = (lot) => context.landRoot(place, lot, account);
107
76
  const line = (scope, measure, value, more = {}) => canonical({ at: 'policy:head/connect', by: 'target', form: 'alphabet', measure, role: 'writes', scope, value, ...more });
108
77
  const pin = pinIn(context, place, lands, line);
109
78
  const worlds = await adopted(context, place, lands, line);
110
79
  if ('refused' in worlds)
111
- return [say('not-landed', { why: worlds.refused })];
80
+ return { said: [say('not-landed', { why: worlds.refused })], connected: false };
112
81
  const laid = [[`${word('place/connected')}/${source}.${doc.kind}`, doc.bytes], ...(overlay ? [[`${word('place/connected')}/${source}.overlay.${overlay.kind}`, overlay.bytes]] : [])];
113
82
  for (const [at, bytes] of laid)
114
83
  context.files.lay(place, at, bytes);
@@ -117,14 +86,14 @@ const laidIn = (context, place, account) => (source, document, doc, overlay, sta
117
86
  if (pinned.open || pinned.dropped)
118
87
  for (const [at] of laid)
119
88
  context.files.drop(place, at);
120
- return pinned.open ? [say('no-world', { kind: doc.kind, document }), line(`${word('place/needs')}/${doc.kind}`, 'id', valueOf(context.lock, 'birth/view/place'), { role: 'demands' })] : pinned.said;
89
+ return { said: pinned.open ? [say('no-world', { kind: doc.kind, document }), line(`${word('place/needs')}/${doc.kind}`, 'id', valueOf(context.lock, 'birth/view/place'), { role: 'demands' })] : pinned.said, connected: false };
121
90
  }
122
91
  const declared = fieldsOf(pinned.lines, `${v.region}/${source}`)[v.origins] ?? [], fetched = URL.canParse(document) && !declared.some((o) => URL.canParse(o)) && ((held) => 'lines' in held && !held.lines.some((l) => l.scope === `${v.pick}/${source}`))(await context.policy(place));
123
92
  const picked = fetched ? await lands([line(`${v.pick}/${source}`, 'id', new URL(declared[0] ?? '/', document).href.replace(/\/$/, ''))]) : undefined;
124
93
  const [owed, paid] = [pinned.lines.filter((l) => l.measure === v.unresolved).map((l) => l.value ?? ''), await paidBy(context, place, pin, laid[0][0])];
125
94
  const classes = pinned.lines.filter((l) => l.measure === v.class && l.scope?.startsWith(`${v.offers}/${source}/`) && l.scope.split('/').length === 3);
126
- return [say('connected', { source, place, offers: String(classes.length), reads: String(classes.filter((l) => l.value === v.callable).length), pin: pinned.pin }),
127
- ...(owed.length ? [say('unresolved', { source, refs: owed.join(', ') })] : []), ...paid.flatMap((p) => ('said' in p.pinned ? p.pinned.said : [say('resolved', { source: p.source, by: source, pin: p.pinned.pin })])),
128
- ...pinned.lines.filter((l) => l.scope === `${v.region}/${source}` || (l.scope?.startsWith(`${v.keys}/${source}/`) && l.measure === v.scheme)).map((l) => canonical(l)),
129
- ...(picked === undefined ? [] : 'refused' in picked ? [say('not-landed', { why: picked.refused })] : picked.lines)];
130
- })().then((lines) => [...lines, ...redIn(context.lock)('connect/seconds', `connect/${source}`, started, context.machine())]);
95
+ return { connected: picked === undefined || !('refused' in picked), said: [say('connected', { source, place, offers: String(classes.length), reads: String(classes.filter((l) => l.value === v.callable).length), pin: pinned.pin }),
96
+ ...(owed.length ? [say('unresolved', { source, refs: owed.join(', ') })] : []), ...paid.flatMap((p) => ('said' in p.pinned ? p.pinned.said : [say('resolved', { source: p.source, by: source, pin: p.pinned.pin })])),
97
+ ...pinned.lines.filter((l) => l.scope === `${v.region}/${source}` || (l.scope?.startsWith(`${v.keys}/${source}/`) && l.measure === v.scheme)).map((l) => canonical(l)),
98
+ ...(picked === undefined ? [] : 'refused' in picked ? [say('not-landed', { why: picked.refused })] : picked.lines)] };
99
+ })().then((result) => ({ ...result, said: [...result.said, ...redIn(context.lock)('connect/seconds', `connect/${source}`, started, context.machine())] }));
@@ -0,0 +1,7 @@
1
+ import { type Context } from '../core/answer.ts';
2
+ import { type Said } from '../core/lock.ts';
3
+ import type { Call } from '../core/read.ts';
4
+ /** A read refused with the access head attached: its secret kept marked spent, never in a line, so the next call obtains it once; the refusal lands as a receipt without it. */
5
+ export declare const refusedAccess: (context: Context, place: string, lines: readonly Said[], plan: Call, operation: string) => Promise<readonly string[]>;
6
+ /** The access a place was refused and has not obtained since, by its newest receipt: what its card shows as open. */
7
+ export declare const pendingIn: (context: Context, place: string) => Promise<readonly string[]>;
@@ -0,0 +1,16 @@
1
+ import { canonical } from '@lapxo/topos/wire';
2
+ import { standingOr } from "../core/answer.js";
3
+ import { valueOf } from "../core/lock.js";
4
+ import { sayIn } from "../core/surface.js";
5
+ import { opened, sealed } from "../access/obtain.js";
6
+ /** A read refused with the access head attached: its secret kept marked spent, never in a line, so the next call obtains it once; the refusal lands as a receipt without it. */
7
+ export const refusedAccess = async (context, place, lines, plan, operation) => {
8
+ const [account, word] = [plan.access.account ?? '', (k) => valueOf(context.lock, k)], kept = opened(context.keys.read(account));
9
+ if (kept !== undefined)
10
+ context.keys.write(account, sealed({ ...kept, expires: 0 }));
11
+ const landed = await context.landAs(place, [canonical({ at: `${word('receipt/at')}:${new Date(context.now()).toISOString()}`, by: 'target', form: 'alphabet', measure: 'status', role: 'writes', scope: `${word('access/family')}/${plan.access.flow}/refused`, value: 'present' })], 'head', lines);
12
+ return [sayIn(context.lock)('access-refused', { source: plan.source, operation, place }), ...('lines' in landed ? landed.lines : [])];
13
+ };
14
+ /** The access a place was refused and has not obtained since, by its newest receipt: what its card shows as open. */
15
+ export const pendingIn = async (context, place) => ((held, family) => [...new Map(('said' in held ? [] : held.lines).filter((l) => l.scope?.startsWith(family) && /\/(refused|obtained|renewed|entered)$/.test(l.scope)).sort((a, b) => ((a.at ?? '') < (b.at ?? '') ? -1 : 1))
16
+ .map((l) => [l.scope.slice(family.length).replace(/\/[^/]+$/, ''), l.scope.endsWith('/refused')]))].filter(([, refused]) => refused).map(([flow]) => sayIn(context.lock)('access-pending', { source: flow.split('/')[0], flow, place })))(await standingOr(context, place), `${valueOf(context.lock, 'access/family')}/`);
@@ -0,0 +1 @@
1
+ export { asked as answer } from '../verbs/ask.ts';