@lapxo/head 0.1.7 → 0.2.0

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.
@@ -4,7 +4,7 @@ import type { Cell } from '../ports/surface.ts';
4
4
  * What an answer leaves to do before the next call, done after it is answered; another call coming gives up what is not
5
5
  * begun, never what is, so no land is cut halfway:
6
6
  * the reads a known next asks for, read in a context that asks the person nothing — a read only, never an act, nothing it
7
- * would need a yes or a secret for — and then every place the answer names folded again where it moved, so the next
8
- * question finds it folded. A fold calls no source, so it spends nothing of a place's rate.
7
+ * would need a yes or a secret for. Each question obtains a fresh native snapshot; speculative recapture cannot validate it.
8
+ * Reading ahead spends only the source-call budget the place declares.
9
9
  */
10
10
  export declare const readAhead: (cells: readonly Cell[], context: Context, signal: AbortSignal) => Promise<void>;
@@ -4,8 +4,8 @@ const none = async () => 'none', nothing = async () => undefined;
4
4
  * What an answer leaves to do before the next call, done after it is answered; another call coming gives up what is not
5
5
  * begun, never what is, so no land is cut halfway:
6
6
  * the reads a known next asks for, read in a context that asks the person nothing — a read only, never an act, nothing it
7
- * would need a yes or a secret for — and then every place the answer names folded again where it moved, so the next
8
- * question finds it folded. A fold calls no source, so it spends nothing of a place's rate.
7
+ * would need a yes or a secret for. Each question obtains a fresh native snapshot; speculative recapture cannot validate it.
8
+ * Reading ahead spends only the source-call budget the place declares.
9
9
  */
10
10
  export const readAhead = async (cells, context, signal) => {
11
11
  const quiet = { ...context, elicit: none, person: { consent: none, enter: nothing, choose: nothing, visit: nothing } };
@@ -13,6 +13,4 @@ export const readAhead = async (cells, context, signal) => {
13
13
  if (!signal.aborted && 'tool' in next && next.tool === 'ask' && next.args['source'] && next.args['operation'] && !next.args['why'] && !next.args['history'])
14
14
  await readSource({ ...next.args, ahead: 'true' }, quiet);
15
15
  await quiet.settle();
16
- for (const place of new Set(cells.flatMap((c) => [c.fields['place'] ?? '', 'tool' in c.next ? c.next.args['place'] ?? '' : '']).filter((p) => p && !signal.aborted)))
17
- await Promise.all([context.standing(place), context.policy(place)]).catch(() => undefined);
18
16
  };
@@ -1,4 +1,5 @@
1
1
  import { type Said } from './lock.ts';
2
+ import type { authorityAt } from '../ports/host.ts';
2
3
  import type { Printed } from '../ports/bound.ts';
3
4
  export type Outcome = {
4
5
  readonly lines: readonly string[];
@@ -23,6 +24,7 @@ export interface Context {
23
24
  } | {
24
25
  readonly refused: string;
25
26
  }>;
27
+ readonly authority: (place: string, key: string, kind: string, records: readonly string[], presented?: string) => ReturnType<typeof authorityAt>;
26
28
  readonly policy: (place: string) => Promise<{
27
29
  readonly lines: readonly Said[];
28
30
  } | {
@@ -5,6 +5,7 @@ import { members, under, valueOf } from "./lock.js";
5
5
  import { fieldsOf, vocabOf } from "./read.js";
6
6
  import { sayIn } from "./surface.js";
7
7
  import { digestIn, linesOf } from "./worlds.js";
8
+ import { sharedBirth } from "../links/birth.js";
8
9
  import { linkOf } from "../links/link.js";
9
10
  /** 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. */
10
11
  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)
@@ -34,7 +35,8 @@ export const shared = (asked, context) => inPlace(asked, context, async (place)
34
35
  catch {
35
36
  return {};
36
37
  } })()), pack = packed(context, place, held.lines, urls), bytes = new TextEncoder().encode(`${pack.lines.join('\n')}\n`);
37
- 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) }), ...linkOf(context, to, pack.lines, urls), ...pack.lines]);
38
+ const birth = sharedBirth(context, to, held.lines);
39
+ return !pack.names.length ? [say('share-none', { place }), ...birth] : ([...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) }), ...linkOf(context, to, pack.lines, urls), ...birth, ...pack.lines]);
38
40
  });
39
41
  /** 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. */
40
42
  export const unpacked = (context, document, bytes, read) => {
@@ -13,7 +13,7 @@ import { aboutOf, members, valueOf } from "../core/lock.js";
13
13
  import { sayIn } from "../core/surface.js";
14
14
  import { matchTemplate, refusedOn, shapeOf, surfaceOf } from "../ports/surface.js";
15
15
  import { birth, closing, founded, land, landAct, limits, pinnedAt, policyAt, run, settledView, sign, standingAt, told, words } from "../ports/bound.js";
16
- import { configureHost } from "../ports/host.js";
16
+ import { configureHost, authorityAt } from "../ports/host.js";
17
17
  import { held, keep } from "../ports/cache.js";
18
18
  import { dropAt, hasAt, layAt, listAt, takeAt } from "../ports/place.js";
19
19
  import * as documents from "../ports/documents.js";
@@ -68,10 +68,15 @@ export const headOf = (lock, root, ext, door) => {
68
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
69
  moved.add(place); return got; }).finally(() => { forget(place); if (root)
70
70
  touched.add(place); });
71
+ const covered = async (place, lot, then) => {
72
+ const checked = await authorityAt(place, valueOf(lock, 'founding/root').split('/')[1], 'record-coverage', lot, Date.now(), signal);
73
+ return 'refused' in checked ? { refused: checked.refused } : checked.decision !== 'allow' ? { refused: `REFUSE·authority-preflight/${checked.decision} ${checked.needs.join('|')}` } : then();
74
+ };
71
75
  return {
72
76
  lock,
73
77
  bound: (cwd, args) => run(runs, cwd, args, {}, signal),
74
78
  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)),
79
+ authority: (place, key, kind, records, presented) => authorityAt(place, key, kind, records, Date.now(), signal, presented),
75
80
  policy: (place) => once('policy', place, () => policyAt(runs, place, signal)),
76
81
  world: (place, view) => once(`world/${view}`, place, () => settledView(runs, place, view, signal)),
77
82
  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)}`) : ''),
@@ -79,8 +84,8 @@ export const headOf = (lock, root, ext, door) => {
79
84
  home: () => home,
80
85
  personal: () => process.env[valueOf(lock, `platform/${process.platform}/home`)] ?? '',
81
86
  found: (place, account) => acted(place, true, founded(runs, place, ...asRoot(account), signal)),
82
- signRoot: (place, lot, account) => sign(runs, place, lot, ...asRoot(account), signal),
83
- landRoot: (place, lot, account) => acted(place, true, landAct(runs, place, lot, ...asRoot(account), signal)),
87
+ signRoot: (place, lot, account) => covered(place, lot, () => sign(runs, place, lot, ...asRoot(account), signal)),
88
+ landRoot: (place, lot, account) => acted(place, true, covered(place, lot, () => landAct(runs, place, lot, ...asRoot(account), signal))),
84
89
  moved: (place) => moved.has(place),
85
90
  settle: async () => { const red = []; for (const place of touched) {
86
91
  const why = await closing(runs, place, signal);
@@ -17,6 +17,9 @@ export declare const rootIn: (context: Context, place: string) => {
17
17
  * lock as its parent has nothing signed here: what the parent would cut is bound's to judge.
18
18
  */
19
19
  type By = {
20
+ readonly key: string;
21
+ readonly root: boolean;
22
+ readonly records: (lot: readonly string[]) => readonly string[];
20
23
  readonly sign: (lot: readonly string[]) => Promise<Outcome>;
21
24
  readonly land: (lot: readonly string[]) => Promise<Outcome>;
22
25
  };
@@ -17,7 +17,7 @@ export const widening = (context, place, lines, lot) => {
17
17
  const witnessed = (signed) => signed.map((line) => ((got) => (got.kind === 'fact' ? canonical({ ...got.value.fields, [field]: witness }) : line))(parse(line)));
18
18
  if ('account' in root && witness !== undefined) {
19
19
  if (admits || lot.every((line) => / scope=wire\/fields /.test(line)))
20
- return { sign: (signed) => context.signRoot(place, admits ? witnessed(signed) : signed, root.account), land: (signed) => context.landRoot(place, admits ? witnessed(signed) : signed, root.account) };
20
+ return { key: valueOf(context.lock, 'founding/root').split('/')[1], root: true, records: (signed) => admits ? witnessed(signed) : signed, sign: (signed) => context.signRoot(place, admits ? witnessed(signed) : signed, root.account), land: (signed) => context.landRoot(place, admits ? witnessed(signed) : signed, root.account) };
21
21
  const [named, bare] = [[...new Set([...wire.flatMap((l) => members(l.value ?? '')), field])].join('|'), (l) => Object.fromEntries(Object.entries(l).filter(([k]) => !['sig', 'epoch'].includes(k)))];
22
22
  return { said: [sayIn(context.lock)('unwitnessed', { place }), ...wire.map((l) => canonical({ ...bare(l), by: 'target', value: 'withdraw' })),
23
23
  canonical({ at: `policy:head/wire/${new Date(context.now()).toISOString()}`, by: 'target', form: 'alphabet', measure: 'id', role: 'reads', scope: 'wire/fields', value: named })] };
@@ -26,5 +26,5 @@ export const widening = (context, place, lines, lot) => {
26
26
  if (stopped)
27
27
  return { said: stopped };
28
28
  const again = lot.flatMap((line) => ((got) => (got.kind === 'fact' && lines.some((l) => l.scope === got.value.fields.scope && l.measure === got.value.fields.measure) ? [got.value.fields.scope ?? ''] : []))(parse(line)))[0];
29
- return again !== undefined ? { said: [sayIn(context.lock)('attested-again', { place, scope: again })] } : { sign: (signed) => context.signAs(place, signed, 'person', lines), land: (signed) => context.landAs(place, signed, 'person', lines) };
29
+ return again !== undefined ? { said: [sayIn(context.lock)('attested-again', { place, scope: again })] } : { key: witness, root: false, records: (signed) => signed, sign: (signed) => context.signAs(place, signed, 'person', lines), land: (signed) => context.landAs(place, signed, 'person', lines) };
30
30
  };
@@ -1,4 +1,6 @@
1
1
  import type { Asked, Context } from '../core/answer.ts';
2
+ /** Bound observes exact configuration records; Q4 domain checks remain in Head's existing card. */
3
+ export declare const authorityFor: (context: Context, place: string, key: string, kind: string, records: readonly string[], observe?: boolean, presented?: string) => Promise<readonly string[] | undefined>;
2
4
  /**
3
5
  * A proposal the person signs: drafted lines shown to the person with the place and the base they are made against; on
4
6
  * their yes to exactly those, signed by their key; what bound prints folding it as if landed comes back as it printed it. Nothing lands; the lot is kept by its digest.
@@ -3,6 +3,13 @@ import { sayIn } from "../core/surface.js";
3
3
  import { widening } from "./device.js";
4
4
  import { parse } from '@lapxo/topos/wire';
5
5
  const moved = async (context, place, base) => ((now) => ('said' in now ? now.said : baseOf(now.lines) !== base ? [sayIn(context.lock)('base-moved', { base })] : undefined))(await standingOr(context, place));
6
+ /** Bound observes exact configuration records; Q4 domain checks remain in Head's existing card. */
7
+ export const authorityFor = async (context, place, key, kind, records, observe = false, presented) => {
8
+ const result = await context.authority(place, key, kind, records, presented), say = sayIn(context.lock);
9
+ if ('refused' in result)
10
+ return [say('authority-refused', { place, key, kind, why: result.refused, code: result.code })];
11
+ return observe || result.decision !== 'allow' ? [say(observe ? 'authority-card' : result.decision === 'deny' ? 'authority-refused' : 'authority-needed', { place, key, kind, decision: result.decision, authority: result.authority, request: result.request, needs: result.needs.join('|'), why: result.decision })] : undefined;
12
+ };
6
13
  /**
7
14
  * A proposal the person signs: drafted lines shown to the person with the place and the base they are made against; on
8
15
  * their yes to exactly those, signed by their key; what bound prints folding it as if landed comes back as it printed it. Nothing lands; the lot is kept by its digest.
@@ -19,6 +26,9 @@ export const planLot = (asked, context) => inPlace(asked, context, async (place)
19
26
  const by = widening(context, place, held.lines, lot);
20
27
  if ('said' in by)
21
28
  return by.said;
29
+ const initial = by.root ? await authorityFor(context, place, by.key, 'record-coverage', by.records(lot)) : undefined;
30
+ if (initial)
31
+ return initial;
22
32
  const consent = await context.elicit(say('elicit', { count: String(lot.length), place, base, lines: lot.join('\n') }));
23
33
  if (consent === 'none')
24
34
  return [say('sign-yourself', { place }), ...lot];
@@ -30,7 +40,7 @@ export const planLot = (asked, context) => inPlace(asked, context, async (place)
30
40
  const stale = await moved(context, place, base);
31
41
  if (stale)
32
42
  return stale;
33
- const kept = context.cache.keep(Buffer.from(JSON.stringify({ place, base, signed: signed.lines })));
43
+ const kept = context.cache.keep(Buffer.from(JSON.stringify({ place, base, signed: signed.lines, root: by.root })));
34
44
  return [say('planned', { lot: kept, base }), ...(await context.told(place, signed.lines))];
35
45
  });
36
46
  /** A planned lot landed by its digest, only while the place stands on the base it was planned against. */
@@ -43,6 +53,10 @@ export const landLot = async (asked, context) => {
43
53
  const stale = await moved(context, kept.place, kept.base);
44
54
  if (stale)
45
55
  return stale;
56
+ const author = parse(kept.signed[0] ?? '');
57
+ const checked = kept.root ? await authorityFor(context, kept.place, author.kind === 'fact' ? author.value.fields.by ?? '' : '', 'admission', kept.signed) : undefined;
58
+ if (checked)
59
+ return checked;
46
60
  const landed = await context.land(kept.place, kept.signed);
47
61
  return 'refused' in landed ? [landed.refused] : [say('landed', { count: String(landed.lines.length) }), ...landed.lines];
48
62
  };
@@ -0,0 +1,6 @@
1
+ import { type Asked, type Context } from '../core/answer.ts';
2
+ import { type Said } from '../core/lock.ts';
3
+ /** A separate public birth artifact. The source package, authority and personal birth journal are not its payload. */
4
+ export declare const sharedBirth: (context: Context, to: string, standing: readonly Said[]) => readonly string[];
5
+ /** Inline or client-shared local birth bytes are inspected only; no transport, signer, file write or admission is acquired. */
6
+ export declare const receivedBirth: (asked: Asked, context: Context) => Promise<readonly string[] | undefined>;
@@ -0,0 +1,48 @@
1
+ import { basename, join } from 'node:path';
2
+ import { canonical, parse } from '@lapxo/topos/wire';
3
+ import { PORTABLE_BIRTH_PROFILE, decodePortableBirth, encodePortableBirth, inspectPortableBirth } from '@lapxo/topos/contract';
4
+ import { semanticStandingBytes } from '@lapxo/topos/standing';
5
+ import { inStanding } from "../core/answer.js";
6
+ import { members, valueOf } from "../core/lock.js";
7
+ import { sayIn } from "../core/surface.js";
8
+ import { digestIn } from "../core/worlds.js";
9
+ const budget = (context) => ({ bytes: Number(valueOf(context.lock, 'bound/printed').split('..')[1]), items: Number(valueOf(context.lock, 'share/birth-items').split('..')[1]) });
10
+ const refused = (context, error) => [sayIn(context.lock)('birth-refused', { why: error instanceof Error ? error.message : String(error) })];
11
+ /** A separate public birth artifact. The source package, authority and personal birth journal are not its payload. */
12
+ export const sharedBirth = (context, to, standing) => {
13
+ const say = sayIn(context.lock), scopes = members(valueOf(context.lock, 'share/birth-scopes')), file = valueOf(context.lock, 'share/birth-file');
14
+ const declarations = standing.filter(line => scopes.includes(line.scope ?? '')).map(({ by: _by, sig: _sig, epoch: _epoch, ...line }) => canonical(line));
15
+ if (!declarations.length)
16
+ return [say('birth-empty')];
17
+ try {
18
+ const record = encodePortableBirth({ origin: digestIn(context.lock)(semanticStandingBytes(standing.map(line => canonical(line)))), declarations }, budget(context)), bytes = new TextEncoder().encode(record);
19
+ context.files.lay(to, file, bytes);
20
+ return [say('birth-shared', { document: join(to, file), digest: digestIn(context.lock)(bytes), record })];
21
+ }
22
+ catch (error) {
23
+ return refused(context, error);
24
+ }
25
+ };
26
+ /** Inline or client-shared local birth bytes are inspected only; no transport, signer, file write or admission is acquired. */
27
+ export const receivedBirth = async (asked, context) => {
28
+ const document = asked['document'] ?? '', inline = document.startsWith(PORTABLE_BIRTH_PROFILE.split('/')[0] + '/');
29
+ if (!inline && !(document.startsWith('/') && basename(document) === valueOf(context.lock, 'share/birth-file')))
30
+ return undefined;
31
+ return inStanding(asked, context, async (place, standing) => {
32
+ const say = sayIn(context.lock), bytes = inline ? new TextEncoder().encode(document) : (await context.files.read.documentOf(context, place, document))?.bytes;
33
+ if (!bytes)
34
+ return [say('birth-needs', { document })];
35
+ if (asked['digest'] && digestIn(context.lock)(bytes) !== asked['digest'])
36
+ return [say('document-digest', { document: inline ? PORTABLE_BIRTH_PROFILE : document, digest: asked['digest'], got: digestIn(context.lock)(bytes) })];
37
+ try {
38
+ const pack = decodePortableBirth(new TextDecoder().decode(bytes), budget(context)), allowed = members(valueOf(context.lock, 'share/birth-scopes'));
39
+ if (pack.declarations.some(line => ((p) => p.kind !== 'fact' || !allowed.includes(p.value.fields.scope ?? ''))(parse(line))))
40
+ return [say('birth-refused', { why: 'declaration outside the public birth selection' })];
41
+ const inspected = inspectPortableBirth(pack, [...new Set(standing.flatMap(line => line.scope ?? []))], budget(context));
42
+ return [say('birth-inspected', { inspection: inspected.status, collisions: inspected.collisions.join(', ') || 'none', origin: pack.origin, declarations: pack.declarations.join('\n') })];
43
+ }
44
+ catch (error) {
45
+ return refused(context, error);
46
+ }
47
+ }, context.policy);
48
+ };
@@ -1,6 +1,6 @@
1
1
  import { type Asked, type Context } from '../core/answer.ts';
2
2
  /**
3
- * A #bound: link connected: inspected by the instrument in the person's place, never authenticated — its bytes are the
3
+ * A legacy or located reference connected: inspected by the instrument in the person's place, never authenticated — its bytes are the
4
4
  * link's, and nothing shows who shared it — and what it names is shown; nothing is connected or granted, since a package
5
5
  * is received by the digest its sharer sent apart. A place that admits no content transport is handed the lines that admit
6
6
  * one, to sign; bytes that do not arrive are owed by the link until they do; bytes that are not what it names are refused.
@@ -1,4 +1,5 @@
1
1
  import { decodeReference } from '@lapxo/bound/reference';
2
+ import { decodeLocatedReference, locatedReferenceIdentity } from '@lapxo/topos/contract';
2
3
  import { canonical } from '@lapxo/topos/wire';
3
4
  import { inStanding } from "../core/answer.js";
4
5
  import { members, valueOf } from "../core/lock.js";
@@ -6,7 +7,7 @@ import { sayIn } from "../core/surface.js";
6
7
  import { linesOf } from "../core/worlds.js";
7
8
  import { rootIn } from "../flows/device.js";
8
9
  /**
9
- * A #bound: link connected: inspected by the instrument in the person's place, never authenticated — its bytes are the
10
+ * A legacy or located reference connected: inspected by the instrument in the person's place, never authenticated — its bytes are the
10
11
  * link's, and nothing shows who shared it — and what it names is shown; nothing is connected or granted, since a package
11
12
  * is received by the digest its sharer sent apart. A place that admits no content transport is handed the lines that admit
12
13
  * one, to sign; bytes that do not arrive are owed by the link until they do; bytes that are not what it names are refused.
@@ -18,25 +19,35 @@ export const linked = (asked, context) => inStanding(asked, context, async (plac
18
19
  return [...standing.filter((l) => admits.includes(l.scope ?? '') || l.scope?.startsWith('transport/')).map((l) => canonical(l)), say('why-transport', { place })];
19
20
  const lines = context.lock.filter((l) => admits.some((s) => l.scope === `birth/${s}`)).map(({ about: _about, scope, ...l }) => canonical({ ...l, scope: scope.slice('birth/'.length), at: 'policy:person/transport', by: 'target' }));
20
21
  let reference;
22
+ const legacy = link.startsWith(word('link/prefix'));
21
23
  try {
22
- reference = decodeReference(link);
24
+ reference = legacy ? (({ source, ...held }) => ({ ...held, location: source, 'location-class': 'holder' }))(decodeReference(link)) : decodeLocatedReference(link);
23
25
  }
24
26
  catch (refused) {
25
27
  return [say('link-refused', { link, why: refused instanceof Error ? refused.message : String(refused) })];
26
28
  }
27
- if (![admits[0], `transport/${new URL(reference.source).protocol.slice(0, -1)}`].every((s) => standing.some((l) => l.scope === s)))
29
+ const identity = legacy ? link : locatedReferenceIdentity(reference), source = reference.location;
30
+ if (!source || reference['location-class'] === 'viewer')
31
+ return [say('reference-needs', { identity, needs: 'bytes/holder-location' })];
32
+ if (![admits[0], `transport/${new URL(source).protocol.slice(0, -1)}`].every((s) => standing.some((l) => l.scope === s)))
28
33
  return [say('transport-missing', { place, lines: lines.join('\n') })];
29
- const [printed, scope] = [await context.bound(place, [word('bound/read'), link]), `${word('place/needs')}/bytes/${reference.blob.slice(reference.algorithm.length + 1, reference.algorithm.length + 17)}`];
30
- const [owed, root, why] = [standing.find((l) => l.scope === scope && l.role === 'demands' && l.value === link), rootIn(context, place), printed?.err.filter((l) => !l.startsWith('{')).at(-1) ?? ''];
31
- if ('said' in root)
32
- return root.said;
34
+ const [printed, scope] = [await context.bound(place, [word('bound/read'), link]), `${word('place/needs')}/bytes/${reference.blob.slice(reference.algorithm.length + 1, legacy ? reference.algorithm.length + 17 : undefined)}`];
35
+ const [owed, why, absent] = [standing.find((l) => l.scope === scope && l.role === 'demands' && l.value === identity), printed?.err.filter((l) => !l.startsWith('{')).at(-1) ?? '', printed?.out.some((line) => line.startsWith('NEEDS '))];
33
36
  if (printed?.code !== 0 && !why.includes(word('link/unarrived')))
34
37
  return [say('link-refused', { link, why })];
35
- const settled = (lot) => (lot.length ? context.landRoot(place, lot, root.account).then((got) => ('refused' in got ? say('not-landed', { why: got.refused }) : got.lines)) : Promise.resolve([]));
36
- const due = { at: 'policy:head/link', by: 'target', form: 'alphabet', measure: 'id', role: 'demands', scope, value: link };
37
- if (printed?.code !== 0)
38
- return [say('link-owed', { place, source: reference.source }), ...await settled(owed ? [] : [canonical(due)])];
38
+ const settled = async (lot) => {
39
+ if (!lot.length)
40
+ return [];
41
+ const root = rootIn(context, place);
42
+ if ('said' in root)
43
+ return root.said;
44
+ const got = await context.landRoot(place, lot, root.account);
45
+ return 'refused' in got ? [say('not-landed', { why: got.refused })] : got.lines;
46
+ };
47
+ const due = { at: 'policy:head/link', by: 'target', form: 'alphabet', measure: 'id', role: 'demands', scope, value: identity };
48
+ if (printed?.code !== 0 || absent)
49
+ return [say('link-owed', { place, source }), ...await settled(owed ? [] : [canonical(due)])];
39
50
  const named = linesOf(new TextEncoder().encode(printed.out.filter((l) => l.startsWith('TOPOS ')).map((l) => l.slice('TOPOS '.length)).join('\n')));
40
51
  const sources = named.filter((l) => /^sources\/[^/]+$/.test(l.scope ?? '')).map((l) => l.scope.split('/')[1]);
41
- return [say('inspected', { source: reference.source, sources: sources.join(', ') || '—', digest: reference.digest }), say('sharer-unverified', {}), ...await settled(owed ? [canonical({ ...due, value: 'withdraw' })] : [])];
52
+ return [say('inspected', { source, sources: sources.join(', ') || '—', digest: reference.digest }), say('sharer-unverified', {}), ...await settled(owed ? [canonical({ ...due, value: 'withdraw' })] : [])];
42
53
  }, context.policy);
@@ -1,7 +1,7 @@
1
1
  import type { Context } from '../core/answer.ts';
2
2
  /**
3
3
  * A package's link: its lines as one standing document, laid in its folder as it is to be published — the link names that
4
- * document's bytes, never the package's files — and the #bound: link to it at the address the person publishes it at.
5
- * With no address there is no link, said so: a link needs where its bytes are published, and the digest still goes apart.
4
+ * document's bytes, never the package's files. The separate located profile keeps retrieval apart from content identity.
5
+ * Without a location the reference still names its content; it does not invent a holder or transfer authority.
6
6
  */
7
7
  export declare const linkOf: (context: Context, to: string, lines: readonly string[], urls: Readonly<Record<string, string>>) => readonly string[];
@@ -1,25 +1,22 @@
1
- import { createHash } from 'node:crypto';
2
1
  import { join } from 'node:path';
3
- import { encodeReference } from '@lapxo/bound/reference';
2
+ import { encodeLocatedReference } from '@lapxo/topos/contract';
4
3
  import { semanticStandingBytes } from '@lapxo/topos/standing';
5
4
  import { members, valueOf } from "../core/lock.js";
6
5
  import { sayIn } from "../core/surface.js";
7
- const sha = (bytes) => createHash('sha256').update(bytes).digest('hex');
6
+ import { digestIn } from "../core/worlds.js";
8
7
  /**
9
8
  * A package's link: its lines as one standing document, laid in its folder as it is to be published — the link names that
10
- * document's bytes, never the package's files — and the #bound: link to it at the address the person publishes it at.
11
- * With no address there is no link, said so: a link needs where its bytes are published, and the digest still goes apart.
9
+ * document's bytes, never the package's files. The separate located profile keeps retrieval apart from content identity.
10
+ * Without a location the reference still names its content; it does not invent a holder or transfer authority.
12
11
  */
13
12
  export const linkOf = (context, to, lines, urls) => {
14
- const say = sayIn(context.lock), [name, profile, [kind, algorithm]] = [valueOf(context.lock, 'link/document'), valueOf(context.lock, 'link/profile'), members(valueOf(context.lock, 'link/reference'))];
13
+ const say = sayIn(context.lock), digest = digestIn(context.lock), [name, profile, [kind, algorithm]] = [valueOf(context.lock, 'link/document'), valueOf(context.lock, 'link/profile'), members(valueOf(context.lock, 'link/reference'))];
15
14
  const standing = semanticStandingBytes(lines), bytes = new TextEncoder().encode(JSON.stringify({ profile, standing })), at = urls[name], document = join(to, name);
16
15
  context.files.lay(to, name, bytes);
17
- if (at === undefined || !URL.canParse(at))
18
- return [say('shared-unlinked', { document })];
19
16
  try {
20
- return [say('shared-link', { document, at, link: encodeReference({ class: kind, algorithm, digest: sha(standing), blob: `${algorithm}:${sha(bytes)}`, source: new URL(at).href }) })];
17
+ return [say(at === undefined ? 'shared-unlinked' : 'shared-link', { document, at: at ?? '', link: encodeLocatedReference({ class: kind, algorithm, digest: digest(standing).slice(7), blob: digest(bytes), ...(at === undefined ? {} : { location: new URL(at).href, 'location-class': 'holder' }) }) })];
21
18
  }
22
19
  catch (refused) {
23
- return [say('link-refused', { link: at, why: refused instanceof Error ? refused.message : String(refused) })];
20
+ return [say('link-refused', { link: name, why: refused instanceof Error ? refused.message : String(refused) })];
24
21
  }
25
22
  };
@@ -14,7 +14,6 @@ export type Folded = {
14
14
  } | {
15
15
  readonly refused: string;
16
16
  };
17
- /** Read normative standing plus authenticated device evidence; ambiguous device history refuses. */
18
17
  export declare const policyAt: (name: string, place: string, signal?: AbortSignal) => Promise<Folded>;
19
18
  export declare const standingAt: (name: string, place: string, devicesOf: (lines: readonly Said[]) => readonly string[], isHistory: (line: Said) => boolean, signal?: AbortSignal) => Promise<Folded>;
20
19
  /** A sign or a land: in a host of its own for this one call, signed by head's signer through the port of the key it names, the lot handed in as the host's input. */
@@ -4,10 +4,9 @@ import { dirname, join } from 'node:path';
4
4
  import { canonical, LOCK, parse } from '@lapxo/topos/wire';
5
5
  import { factsOf } from "../core/lock.js";
6
6
  import { spawned } from "./process.js";
7
- import { hosted, hosting, signedIn } from "./host.js";
7
+ import { capturedViews, hosted, hosting, signedIn } from "./host.js";
8
8
  import { cleared, scratchOf } from "./scratch.js";
9
9
  import { folded } from "../ahead/fold.js";
10
- import { readingOf } from "./reading.js";
11
10
  const binOf = (name) => {
12
11
  try {
13
12
  const manifest = createRequire(import.meta.url).resolve(join(name, 'package.json'));
@@ -40,21 +39,21 @@ const isRecord = (line) => line.startsWith('{') && recordOf(line) !== undefined;
40
39
  const apart = (printed) => ((record) => (record === undefined ? printed : { ...printed, refusal: recordOf(record) }))([...printed.err].reverse().find(isRecord));
41
40
  const why = (printed) => printed?.err.filter((line) => !isRecord(line)).at(-1);
42
41
  const readOf = (printed) => (printed === undefined || printed.code !== 0 ? { refused: why(printed) ?? '' } : { lines: factsOf(printed.out) });
43
- /** Native identities and expected-identity results come from the same public CLI contract. Without it, read fresh. */
44
- const nativeView = (name, place, view, signal) => {
45
- const read = async (expected) => {
46
- if (expected === undefined)
47
- return readOf(await run(name, place, [words.read, words.view, view], {}, signal));
48
- const result = readingOf(await run(name, place, [words.read, words.view, `${words.identity}@${view}@${expected}`], {}, signal), view, expected);
49
- return 'refused' in result ? result : { lines: factsOf(result.text.split('\n')) };
50
- };
51
- return !words.identity ? read() : folded(`${name}/${view}`, place, async () => { const result = readingOf(await run(name, place, [words.read, words.view, `${words.identity}@${view}`], {}, signal), view); return 'refused' in result ? undefined : result.identity; }, read);
42
+ /** Only the requested views are captured by Bound; every result binds its text to the actual current native identity. */
43
+ const nativeViews = async (name, place, views, signal) => {
44
+ const captured = capturedViews(place, views, signal);
45
+ if (captured?.kind === 'refuse' && captured.code === 'host/instrument-pin')
46
+ return views.map(() => ({ refused: captured.diagnostic }));
47
+ return Promise.all(views.map(async (view) => {
48
+ const result = captured?.kind === 'fact' ? captured.views.find((one) => one.view === view) : undefined;
49
+ const read = result ? await folded(`${name}/${view}`, place, async () => result.identity, async () => ({ lines: factsOf(result.text.split('\n')) })) : readOf(await run(name, place, [words.read, words.view, view], {}, signal));
50
+ return 'refused' in read || view !== words.standing || read.lines.some((line) => line.scope === words.profile) ? read
51
+ : { refused: `REFUSE·act the place declares no ${words.profile}: land would print nothing of what it admits, so head lands nothing there; its root can land the local-act@1 profile and view/act` };
52
+ }));
52
53
  };
53
- /** Read normative standing plus authenticated device evidence; ambiguous device history refuses. */
54
- export const policyAt = async (name, place, signal) => ((folded) => ('refused' in folded || folded.lines.some((line) => line.scope === words.profile) ? folded
55
- : { refused: `REFUSE·act the place declares no ${words.profile}: land would print nothing of what it admits, so head lands nothing there; its root can land the local-act@1 profile and view/act` }))(await nativeView(name, place, words.standing, signal));
54
+ export const policyAt = async (name, place, signal) => (await nativeViews(name, place, [words.standing], signal))[0];
56
55
  export const standingAt = async (name, place, devicesOf, isHistory, signal) => {
57
- const [folded, evidence] = await Promise.all([policyAt(name, place, signal), nativeView(name, place, words.evidence, signal)]);
56
+ const [folded, evidence] = await nativeViews(name, place, [words.standing, words.evidence], signal);
58
57
  if ('refused' in folded)
59
58
  return folded;
60
59
  if ('refused' in evidence)
@@ -10,6 +10,11 @@ export declare const hosting: {
10
10
  bytes: number;
11
11
  heap: number;
12
12
  queued: number;
13
+ authority: {
14
+ clock: string;
15
+ unit: string;
16
+ items: number;
17
+ };
13
18
  http: NativeHostOptions["http"];
14
19
  };
15
20
  /** Receiver permissions and work budgets are declared by head's own lock, never inferred from a redirect. */
@@ -21,6 +26,8 @@ export declare const configureHost: (lock: readonly Said[], bounds: {
21
26
  export declare const cooled: () => void;
22
27
  /** The process a place's fold host runs as, while one is held for it. */
23
28
  export declare const hostOf: (place: string) => number | undefined;
29
+ /** Native read-only snapshots use Bound's actual caller environment; their identities are not borrowed from a different CLI context. */
30
+ export declare const capturedViews: (place: string, views: readonly string[], signal?: AbortSignal, from?: string) => import("@lapxo/bound/host").NativeViewIdentitiesResult | undefined;
24
31
  /**
25
32
  * A read of a place's view through the place's own fold host, which only folds and never holds a signer: started on the first
26
33
  * read, kept while it is asked within its idle and its life, the installed bound checked file by file before every call.
@@ -33,3 +40,14 @@ export declare const hosted: (place: string, args: readonly string[], signal?: A
33
40
  * host at a time; the lot goes in as the host's input, never a file of head's.
34
41
  */
35
42
  export declare const signedIn: (place: string, args: readonly string[], lot: readonly string[] | undefined, signers: NonNullable<NativeHostOptions["signers"]>, keyId?: string, signal?: AbortSignal, from?: string) => Promise<Printed>;
43
+ export type AuthorityObservation = {
44
+ readonly refused: string;
45
+ readonly code: string;
46
+ } | {
47
+ readonly authority: string;
48
+ readonly request: string;
49
+ readonly decision: 'allow' | 'ask' | 'deny' | 'open';
50
+ readonly needs: readonly string[];
51
+ };
52
+ /** Configuration-only observations at an explicit receiver clock; no signing, admission or API execution occurs here. */
53
+ export declare const authorityAt: (place: string, key: string, kind: string, records: readonly string[], now: number, signal?: AbortSignal, presented?: string) => Promise<AuthorityObservation>;
@@ -1,20 +1,21 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, statSync } from 'node:fs';
2
+ import { mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync } from 'node:fs';
3
3
  import { createRequire } from 'node:module';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { dirname, join, relative, sep } from 'node:path';
6
- import { installedHostInstrument, nativeHostEnvironment, startNativeHost } from '@lapxo/bound/host';
6
+ import { installedHostInstrument, nativeHostEnvironment, prepareNativeViewIdentities, prepareNativeAuthorityCard, answerNativeAuthorityCard, nativeAuthorityRequestIdentity, startNativeHost } from '@lapxo/bound/host';
7
+ import { decodeAuthorityCard } from '@lapxo/topos/contract';
7
8
  import { members, valueOf } from "../core/lock.js";
8
9
  /** What a place's fold host is held to, from head's lock: the installed instrument it must be, how long it stays idle and lives, and its limits. */
9
- export const hosting = { installed: '', idle: 0, life: 0, wait: 0, bytes: 0, heap: 0, queued: 0, http: undefined };
10
+ export const hosting = { installed: '', idle: 0, life: 0, wait: 0, bytes: 0, heap: 0, queued: 0, authority: { clock: '', unit: '', items: 0 }, http: undefined };
10
11
  /** Receiver permissions and work budgets are declared by head's own lock, never inferred from a redirect. */
11
- export const configureHost = (lock, bounds) => { Object.assign(hosting, { installed: valueOf(lock, 'dep/bound/installed'), wait: bounds.wait, bytes: bounds.most, ...Object.fromEntries(['idle', 'life', 'heap', 'queued'].map((k) => [k, Number(valueOf(lock, `bound/host/${k}`).split('..')[1])])), http: { origins: members(valueOf(lock, 'bound/receiver/origins')), redirects: { maxRedirects: Number(valueOf(lock, 'bound/receiver/redirects').split('..')[1]), headerBytes: Number(valueOf(lock, 'bound/receiver/header-bytes').split('..')[1]) } } }); };
12
+ export const configureHost = (lock, bounds) => { Object.assign(hosting, { installed: valueOf(lock, 'dep/bound/installed'), wait: bounds.wait, bytes: bounds.most, ...Object.fromEntries(['idle', 'life', 'heap', 'queued'].map((k) => [k, Number(valueOf(lock, `bound/host/${k}`).split('..')[1])])), authority: { clock: valueOf(lock, 'bound/authority/clock'), unit: valueOf(lock, 'bound/authority/unit'), items: Number(valueOf(lock, 'bound/authority/items').split('..')[1]) }, http: { origins: members(valueOf(lock, 'bound/receiver/origins')), redirects: { maxRedirects: Number(valueOf(lock, 'bound/receiver/redirects').split('..')[1]), headerBytes: Number(valueOf(lock, 'bound/receiver/header-bytes').split('..')[1]) } } }); };
12
13
  const root = dirname(createRequire(import.meta.url).resolve('@lapxo/bound/package.json'));
13
- const walk = (at) => (statSync(at).isDirectory() ? readdirSync(at).flatMap((e) => walk(join(at, e))) : [at]);
14
+ const walk = (at) => readdirSync(at, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? walk(join(at, e.name)) : [join(at, e.name)]);
14
15
  /** Every file of the installed bound by its coordinate and digest: what its identity is computed from, and what is checked again before each call. */
15
16
  const inventory = (at) => walk(at).map((f) => ({ coordinate: relative(at, f).split(sep).join('/'), digest: `sha256:${createHash('sha256').update(readFileSync(f)).digest('hex')}` })).sort((a, b) => (a.coordinate < b.coordinate ? -1 : 1));
16
- /** The hosts held, those starting, those closing — a place gets its next host once its last one is gone — and the folders head made for their scratch, each removed once its host is. */
17
- const [live, starting, closing, scratches] = [new Map(), new Map(), new Map(), new Set()];
17
+ /** Held, starting and closing hosts, their owned scratch, and one instrument descriptor reused only after its complete current byte inventory matches. */
18
+ const [live, starting, closing, scratches, selected] = [new Map(), new Map(), new Map(), new Set(), { root: '', members: '', instrument: undefined }];
18
19
  const cleared = (dir) => { rmSync(dir, { recursive: true, force: true }); scratches.delete(dir); };
19
20
  const shut = (at) => { const one = live.get(at); live.delete(at); if (one) {
20
21
  clearTimeout(one.idle);
@@ -27,13 +28,34 @@ export const cooled = () => { for (const at of [...live.keys()])
27
28
  export const hostOf = (place) => live.get(realpathSync(place))?.host.pid;
28
29
  process.once('exit', () => { cooled(); for (const dir of [...scratches])
29
30
  cleared(dir); });
31
+ /** One verified installed descriptor, reused only after hashing every current installed member. */
32
+ const instrumentAt = (from) => {
33
+ const members = inventory(from), current = JSON.stringify(members);
34
+ const instrument = selected.root === from && selected.members === current ? selected.instrument : Object.assign(selected, { root: from, members: current, instrument: installedHostInstrument({ directory: from, shape: 'package', members, exports: [['exports', './engine', 'default']], pins: [], limits: { members: 4096, bytes: 1 << 27, responseBytes: 1 << 22 } }, join(from, 'dist/cli/verb.js')) }).instrument;
35
+ return { members: current, instrument };
36
+ };
37
+ const pinnedInstrument = (from) => { const { instrument } = instrumentAt(from); if (instrument.installed?.digest !== hosting.installed)
38
+ throw Error('REFUSE·host/instrument-pin'); return instrument; };
39
+ /** Native read-only snapshots use Bound's actual caller environment; their identities are not borrowed from a different CLI context. */
40
+ export const capturedViews = (place, views, signal, from = root) => {
41
+ try {
42
+ if (signal?.aborted)
43
+ return undefined;
44
+ const instrument = pinnedInstrument(from);
45
+ return prepareNativeViewIdentities({ place, views, instrument });
46
+ }
47
+ catch (error) {
48
+ cooled();
49
+ return { kind: 'refuse', code: 'host/instrument-pin', diagnostic: error.message };
50
+ }
51
+ };
30
52
  /** A host started on a place, on the installed instrument the lock pins and in a scratch folder of head's own; with signers only for one call's sign or land. */
31
53
  const opened = async (at, from, signers) => {
32
- const members = inventory(from), instrument = installedHostInstrument({ directory: from, shape: 'package', members, exports: [['exports', './engine', 'default']], pins: [], limits: { members: 4096, bytes: 1 << 27, responseBytes: 1 << 22 } }, join(from, 'dist/cli/verb.js'));
54
+ const { members, instrument } = instrumentAt(from);
33
55
  const scratch = mkdtempSync(join(tmpdir(), 'head-host-'));
34
56
  scratches.add(scratch);
35
57
  const host = await startNativeHost({ place: at, instrument, expectedInstrument: hosting.installed, environment: nativeHostEnvironment({ ...process.env }), scratchParent: scratch, ...(hosting.http ? { http: hosting.http } : {}), ...(signers ? { signers } : {}), limits: { requestBytes: hosting.bytes, responseBytes: hosting.bytes, timeoutMs: hosting.wait, heapMiB: hosting.heap, queuedRequests: hosting.queued } }).catch((e) => { cleared(scratch); throw e; });
36
- return { host, members: JSON.stringify(members), born: Date.now(), scratch, busy: 0 };
58
+ return { host, members, born: Date.now(), scratch, busy: 0 };
37
59
  };
38
60
  const started = async (at, from) => ((one) => (live.set(at, one), one))(await opened(at, from));
39
61
  const lines = (text) => text.split('\n').filter(Boolean), refused = (why) => ({ code: -1, out: [], err: [why] });
@@ -106,3 +128,30 @@ export const signedIn = async (place, args, lot, signers, keyId, signal, from =
106
128
  cleared(one.scratch);
107
129
  }
108
130
  };
131
+ /** Configuration-only observations at an explicit receiver clock; no signing, admission or API execution occurs here. */
132
+ export const authorityAt = async (place, key, kind, records, now, signal, presented) => {
133
+ const current = () => { if (signal?.aborted)
134
+ throw Error('REFUSE·authority-preflight/cancelled'); };
135
+ try {
136
+ current();
137
+ const instrument = pinnedInstrument(root);
138
+ const action = { kind, records }, clock = { name: hosting.authority.clock, unit: hosting.authority.unit, now };
139
+ const context = { root: realpathSync(place), place: '.', key, instrument, clock, horizon: { clock: clock.name, unit: clock.unit, from: now, through: now }, limits: { bytes: hosting.bytes, items: hosting.authority.items }, actions: ['record-coverage', 'admission'].includes(kind) ? [action] : [] };
140
+ if (!clock.name || !clock.unit || !Number.isSafeInteger(now) || hosting.authority.items < 1)
141
+ throw Error('REFUSE·authority-preflight/context');
142
+ const prepared = await prepareNativeAuthorityCard(context);
143
+ if (prepared.kind !== 'fact')
144
+ return { refused: prepared.diagnostic, code: prepared.code };
145
+ const request = nativeAuthorityRequestIdentity(action), card = presented ? decodeAuthorityCard(presented, context.limits) : prepared.value.card;
146
+ const answer = await answerNativeAuthorityCard(card, request, context);
147
+ current();
148
+ if ('kind' in answer)
149
+ return { refused: answer.diagnostic, code: answer.code };
150
+ if (answer.status === 'refused')
151
+ return { refused: `REFUSE·${answer.code}`, code: answer.code };
152
+ return { authority: prepared.value.line, request, decision: answer.status === 'observed' ? answer.decision : 'open', needs: answer.status === 'open' ? answer.needs : prepared.value.needs };
153
+ }
154
+ catch (error) {
155
+ return { refused: error instanceof Error ? error.message : String(error), code: 'authority-preflight/context' };
156
+ }
157
+ };
@@ -1,6 +1,6 @@
1
1
  import { canonical, EXTENSION } from '@lapxo/topos/wire';
2
2
  import { inPlace, placeOf, unless } from "../core/answer.js";
3
- import { valueOf } from "../core/lock.js";
3
+ import { members, valueOf } from "../core/lock.js";
4
4
  import { fieldsOf, vocabOf } from "../core/read.js";
5
5
  import { redIn, sayIn } from "../core/surface.js";
6
6
  import { digestIn, linesOf } from "../core/worlds.js";
@@ -9,6 +9,7 @@ import { paidBy, pinIn } from "../compile/pin.js";
9
9
  import { adopted } from "../adopt/adopt.js";
10
10
  import { firstRun } from "../onboard/first.js";
11
11
  import { resolved } from "../onboard/site.js";
12
+ import { receivedBirth } from "../links/birth.js";
12
13
  import { linked } from "../links/inspect.js";
13
14
  import { rootIn } from "../flows/device.js";
14
15
  import { attachedFor } from "./access.js";
@@ -23,7 +24,7 @@ import { attachedFor } from "./access.js";
23
24
  * its pins rest in, its pin held to the package's; one that differs is refused by name, and the place holds what it
24
25
  * compiled. Nothing the sharing place was granted comes with it: access is asked here, issuer by issuer.
25
26
  */
26
- export const connected = async (asked, context, covered = false) => ((asked['document'] ?? '').startsWith(valueOf(context.lock, 'link/prefix')) ? linked(asked, context)
27
+ export const connected = async (asked, context, covered = false) => (await receivedBirth(asked, context)) ?? (members(valueOf(context.lock, 'link/prefixes')).some(prefix => (asked['document'] ?? '').startsWith(prefix)) ? linked(asked, context)
27
28
  : ((got) => ('said' in got ? got.said : connectedAs(got.asked, context, covered)))(await resolved(asked, context)));
28
29
  const connectedAs = async (asked, context, covered = false) => (asked['source'] && !(await placeOf(asked, context)) ? firstRun(asked, context, (dir, covered) => connected({ ...asked, place: dir }, context, covered))
29
30
  : ((started) => inPlace(asked, context, async (place) => {