@lapxo/bound 0.2.3 → 0.2.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.
Files changed (67) hide show
  1. package/.github/workflows/release.yml +67 -0
  2. package/.github/workflows/test.yml +35 -0
  3. package/CITATION.cff +19 -0
  4. package/TARGET.bound +71 -20
  5. package/dist/cli/accept.js +103 -71
  6. package/dist/cli/place.js +3 -2
  7. package/dist/cli/standing-result.js +3 -5
  8. package/dist/cli/takes.js +4 -3
  9. package/dist/cli/verb.js +2 -1
  10. package/dist/engine/ports.d.ts +6 -1
  11. package/dist/engine/ports.js +23 -9
  12. package/dist/fold/claims.d.ts +29 -0
  13. package/dist/fold/claims.js +2 -1
  14. package/dist/fold/closed.js +2 -1
  15. package/dist/fold/digests.js +7 -7
  16. package/dist/host/admitted-place.js +3 -31
  17. package/dist/host/call-context.d.ts +20 -0
  18. package/dist/host/call-context.js +14 -0
  19. package/dist/host/capsule-process.js +2 -1
  20. package/dist/host/capsule.js +7 -4
  21. package/dist/host/commit-result.d.ts +6 -0
  22. package/dist/host/commit-result.js +5 -0
  23. package/dist/host/contextual-place.js +9 -14
  24. package/dist/host/host-controls.js +19 -0
  25. package/dist/host/in-process-act.d.ts +82 -0
  26. package/dist/host/in-process-act.js +84 -0
  27. package/dist/host/in-process-context.d.ts +46 -0
  28. package/dist/host/in-process-context.js +91 -0
  29. package/dist/host/in-process-root.d.ts +117 -0
  30. package/dist/host/in-process-root.js +84 -0
  31. package/dist/host/in-process.d.ts +41 -0
  32. package/dist/host/in-process.js +55 -0
  33. package/dist/host/index.d.ts +15 -0
  34. package/dist/host/index.js +7 -0
  35. package/dist/host/native-receiver.d.ts +19 -0
  36. package/dist/host/native-receiver.js +25 -0
  37. package/dist/host/place-act-transaction.d.ts +24 -0
  38. package/dist/host/place-act-transaction.js +78 -0
  39. package/dist/host/place-clock-selection.d.ts +55 -0
  40. package/dist/host/place-clock-selection.js +1 -1
  41. package/dist/host/ports/local-act.d.ts +44 -0
  42. package/dist/host/ports/local-act.js +13 -2
  43. package/dist/host/ports/place-act.d.ts +49 -0
  44. package/dist/host/ports/place-act.js +19 -54
  45. package/dist/host/ports/publication.d.ts +10 -0
  46. package/dist/host/root-clock-source.d.ts +86 -0
  47. package/dist/host/root-clock-source.js +7 -5
  48. package/dist/host/signature-era.js +12 -0
  49. package/dist/host/signing.js +1 -1
  50. package/dist/host/source-content.js +2 -1
  51. package/dist/host/standing-carrier.js +18 -0
  52. package/dist/host/standing-projection.js +50 -0
  53. package/dist/land/act.js +60 -26
  54. package/dist/observe/run.js +2 -1
  55. package/dist/observe/runner.js +16 -8
  56. package/docs/contracts/native-host.md +21 -0
  57. package/docs/contracts/native-host.publication.json +25 -0
  58. package/docs/contracts/native-publication.inventory.json +2 -1
  59. package/docs/contracts/publication.inventory.json +29 -0
  60. package/docs/guide.md +10 -0
  61. package/docs/reference.md +39 -0
  62. package/docs/why.md +109 -0
  63. package/package.json +32 -2
  64. package/receipts.bound +10369 -9723
  65. package/samples/native-host/no.json +10 -0
  66. package/samples/native-host/yes.json +20 -0
  67. package/vectors/native/native-host.vector.json +62 -0
@@ -0,0 +1,29 @@
1
+ import { parse } from '@lapxo/topos/wire';
2
+ export { CAPSULE, EXTENSION, LOCK, RECEIPTS } from '@lapxo/topos/wire';
3
+ /** Pure syntax is retained by the exact record bytes, never by a path, epoch or
4
+ * claimed digest. This does not retain admission, coverage or current grants. */
5
+ export declare function parsedLine(line: string): ReturnType<typeof parse>;
6
+ export declare const fieldOf: (line: string, name: string) => string;
7
+ export declare const fieldsOf: (line: string) => Record<string, string>;
8
+ export declare function selfName(): string;
9
+ export declare function isWire(line: string): boolean;
10
+ export declare const wireLinesOf: (text: string | undefined) => readonly string[];
11
+ export declare const isConfig: (line: string) => boolean;
12
+ export declare const sayingOf: (line: string) => string;
13
+ /** One key per claim: scope, role and measure; what a line says, its envelope set aside, is one however often it landed. */
14
+ export declare function claimKey(line: string): string;
15
+ export interface Fork {
16
+ readonly key: string;
17
+ readonly lines: readonly string[];
18
+ readonly state?: string;
19
+ }
20
+ export declare function foldClaims(lines: readonly string[], receipts?: readonly {
21
+ readonly scope: string;
22
+ readonly moved: readonly string[];
23
+ }[], _placed?: boolean): {
24
+ readonly standing: readonly string[];
25
+ readonly superseded: readonly string[];
26
+ readonly retracted: readonly string[];
27
+ readonly forks: readonly Fork[];
28
+ readonly vacuous: readonly string[];
29
+ };
@@ -3,6 +3,7 @@ import { contracts, exclusive, point, region } from '@lapxo/obligations/field';
3
3
  import { ENVELOPE, PROTOCOL, recordKind, signedBytes, parse } from '@lapxo/topos/wire';
4
4
  export { CAPSULE, EXTENSION, LOCK, RECEIPTS } from '@lapxo/topos/wire';
5
5
  import { ownRoot } from "../observe/runner.js";
6
+ import { hostInstrumentName } from "../host/call-context.js";
6
7
  const decoded = new Map();
7
8
  const parsed = new Map();
8
9
  const kinds = new Map();
@@ -35,7 +36,7 @@ function wireFields(line) {
35
36
  }
36
37
  export const fieldOf = (line, name) => wireFields(line)[name] ?? '';
37
38
  export const fieldsOf = (line) => ({ ...wireFields(line) });
38
- export function selfName() { return basename(ownRoot()); }
39
+ export function selfName() { return hostInstrumentName() ?? basename(ownRoot()); }
39
40
  export function isWire(line) { return line.startsWith(PROTOCOL); }
40
41
  export const wireLinesOf = (text) => (text ?? '').split('\n').filter(isWire);
41
42
  export const isConfig = (line) => { const held = kinds.get(line); if (held !== undefined)
@@ -1,3 +1,4 @@
1
+ import { hostOutput } from "../host/call-context.js";
1
2
  import { actCosts } from "../host/act-cost.js";
2
3
  import { verifiedReadingReceipts } from "../host/reading-closures.js";
3
4
  import { readingProfile } from "../host/reading-context.js";
@@ -215,7 +216,7 @@ export function meets(root, store, place, current = true, active) {
215
216
  }
216
217
  }
217
218
  if (closed && readings)
218
- process.stderr.write(`RECEIPTS ${readings} read · 0 opened · reading closures\n`);
219
+ hostOutput('error', `RECEIPTS ${readings} read · 0 opened · reading closures\n`);
219
220
  }
220
221
  seen = { count: named.size + (own.length > 0 ? 1 : 0), ms: Date.now() - at };
221
222
  return closed;
@@ -1,3 +1,4 @@
1
+ import { signatureEraOf } from "../host/signature-era.js";
1
2
  import { ledgerLines, ledgerWriters } from "../land/ledger.js";
2
3
  import { readOnly } from "../host/read-only.js";
3
4
  import { appendFileSync, dirname, join, mkdirSync, readdirSync, realpathSync, relative, resolve } from "../host/io.js";
@@ -93,13 +94,12 @@ export function signaturesOf(store, told = []) {
93
94
  return eraOf(foldClaims([...signedLockLines(store, told), ...told]).standing);
94
95
  }
95
96
  export function eraOf(standing) {
96
- const said = wireLine(standing, 'signature-algorithms');
97
- const admitted = (said === undefined ? [] : fieldOf(said, 'value').split('|')).filter(Boolean);
98
- const current = wireLine(standing, 'era');
99
- const era = (current === undefined ? undefined : admitted.find((one) => one.endsWith(`:${fieldOf(current, 'value')}`))) ?? admitted.find((one) => one.includes(':'));
100
- if (era === undefined)
101
- throw new Error(`${selfName()}: REFUSE·runtime the lock names no signature algorithm by digest: nothing can be signed here`);
102
- return { admitted, era };
97
+ try {
98
+ return signatureEraOf(standing);
99
+ }
100
+ catch (error) {
101
+ throw new Error(`${selfName()}: ${error instanceof Error ? error.message : String(error)}`);
102
+ }
103
103
  }
104
104
  export const bytesDigest = (store, bytes) => bytesWith(bytes, algorithmOf(store));
105
105
  export const fullDigest = (store, input) => fullWith(input, algorithmOf(store));
@@ -1,9 +1,6 @@
1
1
  import { hashText } from "./hash.js";
2
2
  import { admissionAnchor, admissionHistoryAt } from "../fold/admission-history.js";
3
- import { authorityFor } from "../fold/signers.js";
4
- import { eraOf } from "../fold/digests.js";
5
- import { fieldOf, foldClaims, isConfig } from "../fold/claims.js";
6
- import { foldedEvidence } from '@lapxo/topos/contract';
3
+ import { projectAuthenticatedStanding, publishedStandingLines } from "./standing-projection.js";
7
4
  import { createHash } from 'node:crypto';
8
5
  // One bounded historical projection, never a current authority/lease verdict.
9
6
  // Its complete inputs are reread before lookup, including original signatures.
@@ -24,32 +21,7 @@ export function authenticatedRootStanding(root) {
24
21
  const identity = historicalIdentity(history, anchor);
25
22
  if (historicalProjection?.identity === identity)
26
23
  return [...historicalProjection.standing];
27
- const snapshots = new Map();
28
- const admitted = history.filter(line => {
29
- const epoch = Number(fieldOf(line, 'epoch')) || 0;
30
- let authority = snapshots.get(epoch);
31
- if (!authority) {
32
- // This invocation has freshly read the complete signed inventory. Derive
33
- // the same historical cutoff without reopening it for every epoch; the
34
- // next invocation still rereads every authority input.
35
- const snapshot = history.filter(record => !isConfig(record) || Number(fieldOf(record, 'epoch')) <= epoch);
36
- // The signature vocabulary is configuration, not the as-yet
37
- // unauthenticated evidence whose values are about to be folded.
38
- const wire = snapshot.filter(record => {
39
- const scope = fieldOf(record, 'scope');
40
- return scope.startsWith('wire/') || scope.startsWith('audit/wire/');
41
- });
42
- authority = authorityFor(snapshot, anchor, eraOf(foldClaims(wire).standing).admitted);
43
- snapshots.set(epoch, authority);
44
- }
45
- return authority.of(line).kind === 'admitted';
46
- });
47
- // The host authenticates against historical authority; Topos projects the
48
- // authenticated history using the instrument's existing fold.
49
- const { standing } = foldedEvidence(admitted, {
50
- classification: () => 'local',
51
- fold: lines => foldClaims(lines).standing,
52
- });
24
+ const standing = projectAuthenticatedStanding(history, anchor);
53
25
  historicalProjection = { identity, standing: Object.freeze([...standing]) };
54
26
  return [...standing];
55
27
  }
@@ -59,5 +31,5 @@ export const authenticatedStanding = authenticatedRootStanding;
59
31
  * remains intact for the existing typed-object projection. Internal readers keep
60
32
  * authenticatedStanding, including its historical markers. */
61
33
  export function publicStandingLines(root) {
62
- return authenticatedRootStanding(root).filter(line => !isConfig(line) || fieldOf(line, 'value') !== 'withdraw');
34
+ return publishedStandingLines(authenticatedRootStanding(root));
63
35
  }
@@ -0,0 +1,20 @@
1
+ /** Native operational selection from the receiver, never the offered content. */
2
+ export interface HostInstrumentRuntime {
3
+ readonly root: string;
4
+ readonly entry: string;
5
+ readonly directory: string;
6
+ readonly extension: '.ts' | '.js';
7
+ readonly lock: readonly string[];
8
+ }
9
+ interface HostCall {
10
+ readonly instrumentName: string;
11
+ readonly runtime?: HostInstrumentRuntime;
12
+ readonly output?: (stream: 'out' | 'error', text: string) => void;
13
+ }
14
+ export declare const hostInstrumentName: () => string | undefined;
15
+ export declare const hostInstrumentRuntime: () => HostInstrumentRuntime | undefined;
16
+ export declare const withHostInstrument: <T>(instrumentName: string, run: () => T) => T;
17
+ export declare const withHostCall: <T>(call: HostCall, run: () => T) => T;
18
+ /** No stream monkey-patching: diagnostics follow only this asynchronous call. */
19
+ export declare function hostOutput(stream: 'out' | 'error', text: string): void;
20
+ export {};
@@ -0,0 +1,14 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ const calls = new AsyncLocalStorage();
3
+ export const hostInstrumentName = () => calls.getStore()?.instrumentName;
4
+ export const hostInstrumentRuntime = () => calls.getStore()?.runtime;
5
+ export const withHostInstrument = (instrumentName, run) => calls.run({ ...calls.getStore(), instrumentName }, run);
6
+ export const withHostCall = (call, run) => calls.run(call, run);
7
+ /** No stream monkey-patching: diagnostics follow only this asynchronous call. */
8
+ export function hostOutput(stream, text) {
9
+ const output = calls.getStore()?.output;
10
+ if (output)
11
+ output(stream, text);
12
+ else
13
+ (stream === 'out' ? process.stdout : process.stderr).write(text);
14
+ }
@@ -1,3 +1,4 @@
1
+ import { hostOutput } from "./call-context.js";
1
2
  import { actCosts } from "./act-cost.js";
2
3
  import { fileURLToPath } from 'node:url';
3
4
  import { boundedProcess } from "./process-lifetime.js";
@@ -55,7 +56,7 @@ export function executeCapsule(command, args, requests, limits = {}, lifetime) {
55
56
  throw new CapsuleProcessError(`returned an invalid batch; expected ${requests.length} responses`, run);
56
57
  }
57
58
  if (run.stderr)
58
- process.stderr.write(run.stderr);
59
+ hostOutput('error', run.stderr);
59
60
  return values;
60
61
  });
61
62
  }
@@ -1,3 +1,4 @@
1
+ import { hostOutput } from "./call-context.js";
1
2
  import { readerLifetime, parse } from '@lapxo/topos/wire';
2
3
  import { contentStore, verifiedContent, contentPolicy } from "./content-store.js";
3
4
  import { readOnly } from "./read-only.js";
@@ -21,8 +22,8 @@ import { ownLock, ownRoot, ownStore, processOf } from "../observe/runner.js";
21
22
  import { said } from "../fold/wire.js";
22
23
  const declared = new Map();
23
24
  const laid = new Map();
24
- process.on('exit', () => { for (const at of laid.values())
25
- rmSync(at, { recursive: true, force: true }); });
25
+ const releaseLaid = () => { for (const at of laid.values())
26
+ rmSync(at, { recursive: true, force: true }); };
26
27
  /** A blob is unpacked once in a process, where every run of it the process asks for reads it. */
27
28
  function unpacked(digest, bytes) {
28
29
  const held = laid.get(digest);
@@ -37,6 +38,8 @@ function unpacked(digest, bytes) {
37
38
  throw error;
38
39
  }
39
40
  laid.set(digest, at);
41
+ if (!process.listeners('exit').includes(releaseLaid))
42
+ process.once('exit', releaseLaid);
40
43
  return at;
41
44
  }
42
45
  /**
@@ -99,7 +102,7 @@ function answered(digest, bytes, requests, names, tree, cacheStore = ownStore(),
99
102
  }
100
103
  const child = entry === undefined ? spawnOf(tree ?? unpacked(digest, bytes), names) : [entry];
101
104
  if (!child.length) {
102
- process.stderr.write(`LOAD regions · none of ${names.length} under the directories release/layout names\n`);
105
+ hostOutput('error', `LOAD regions · none of ${names.length} under the directories release/layout names\n`);
103
106
  return requests.map(() => undefined);
104
107
  }
105
108
  const loader = entry !== undefined ? `${createHash('sha256').update(`${relative(tree, entry)}\n`).digest('hex')}\n` : child.length > 1 ? `${createHash('sha256').update(observeFile(child[0]) ?? new Uint8Array()).digest('hex')}\n` : '';
@@ -124,7 +127,7 @@ function answered(digest, bytes, requests, names, tree, cacheStore = ownStore(),
124
127
  if (missing.length && readOnly())
125
128
  throw Error(`REFUSE·preview capsule ${digest} has no verified answer for the proposed inputs; fold the declared inputs first`);
126
129
  if (missing.length)
127
- process.stderr.write(`CAPSULE ${digest.slice(7, 19)} · ${requests.length} asked · ${missing.length} run · ${missing.map((one) => `${one.rootScope}${one.region}`).join(' ')}\n`);
130
+ hostOutput('error', `CAPSULE ${digest.slice(7, 19)} · ${requests.length} asked · ${missing.length} run · ${missing.map((one) => `${one.rootScope}${one.region}`).join(' ')}\n`);
128
131
  // Execute and validate the entire batch before committing any answer to the cache.
129
132
  if (native && !lifetime)
130
133
  throw Error('REFUSE·native reader missing declared lifetime');
@@ -0,0 +1,6 @@
1
+ import { BoundRefusal } from './refusals.ts';
2
+ /** Visibility may precede a failed publication response; never asserts rollback. */
3
+ export declare class ActCommitUnknown extends BoundRefusal {
4
+ readonly identity: string;
5
+ constructor(identity: string, cause: unknown);
6
+ }
@@ -0,0 +1,5 @@
1
+ import { BoundRefusal } from "./refusals.js";
2
+ /** Visibility may precede a failed publication response; never asserts rollback. */
3
+ export class ActCommitUnknown extends BoundRefusal {
4
+ constructor(identity, cause) { super('host/commit-unknown', `REFUSE·act publication uncertain · ${cause instanceof Error ? cause.message : 'storage failure'}`); this.identity = identity; }
5
+ }
@@ -6,7 +6,8 @@ import { placesIn } from "../fold/places.js";
6
6
  import { appliedHistoryIn } from "../fold/signed.js";
7
7
  import { storeOf } from "../land/ledger.js";
8
8
  import { publicStandingLines } from "./admitted-place.js";
9
- import { foldClaims, fieldOf } from "../fold/claims.js";
9
+ import { projectContextualStanding } from "./standing-carrier.js";
10
+ import { admissionHistoryAt } from "../fold/admission-history.js";
10
11
  const fail = (s) => { throw Error('REFUSE·contextual place ' + s); };
11
12
  function read(path, limit) {
12
13
  const fd = openSync(path, 'r');
@@ -60,18 +61,12 @@ export function contextualAct(root, identity) {
60
61
  }
61
62
  /** Carriers retain exact originals and authenticated context/receipts. Each
62
63
  * namespace folds independently; labels assert no statistical independence. */
63
- export function contextualStanding(root) {
64
+ /** Native snapshot collection preserves every existing authentication boundary. */
65
+ export function contextualStandingSnapshot(root) {
64
66
  const selected = declaredRootClockSource(root), acts = committedPlaceActResults(storeOf(root), selected.trust, selected.selection.storageBudget);
65
- const groups = new Map();
66
- for (const act of acts) {
67
- const decoded = readPlaceActContext(act.bundle.context, selected.selection.budget);
68
- for (const p of decoded.places) {
69
- const key = JSON.stringify([decoded.root.namespace, p.place]), group = groups.get(key) ?? { namespace: decoded.root.namespace, place: p.place, lines: [], contexts: [] };
70
- group.lines.push(...p.records);
71
- group.contexts.push({ identity: act.identity, context: act.bundle.context, receipts: act.bundle.receipts });
72
- groups.set(key, group);
73
- }
74
- }
75
- const legacyPlaces = placesIn(root).map(place => { const history = appliedHistoryIn(root, place), folded = foldClaims(history); return { namespace: 'legacy-global', place: [place, 'TARGET.bound'].join('/'), standing: folded.standing.filter(line => fieldOf(line, 'value') !== 'withdraw'), history }; });
76
- return { profile: PLACE_ACT_CONTEXT, root: publicStandingLines(root), legacyPlaces, contexts: acts.map(a => ({ identity: a.identity, context: a.bundle.context, receipts: a.bundle.receipts, authority: selected.authority(a.bundle.context) })), places: [...groups.values()].map(group => { const folded = foldClaims(group.lines); return { namespace: group.namespace, place: group.place, standing: folded.standing.filter(line => fieldOf(line, 'value') !== 'withdraw'), retracted: folded.retracted, superseded: folded.superseded, forks: folded.forks, contexts: group.contexts }; }) };
67
+ return { rootHistory: admissionHistoryAt(root, Number.MAX_SAFE_INTEGER), legacyPlaces: placesIn(root).map(place => ({ place: [place, 'TARGET.bound'].join('/'), history: appliedHistoryIn(root, place) })), contexts: acts.map(a => ({ identity: a.identity, context: a.bundle.context, receipts: a.bundle.receipts, authority: selected.authority(a.bundle.context) })) };
68
+ }
69
+ export function contextualStanding(root) {
70
+ const snapshot = contextualStandingSnapshot(root), selected = declaredRootClockSource(root);
71
+ return projectContextualStanding(publicStandingLines(root), snapshot.legacyPlaces, snapshot.contexts, snapshot.contexts.map(a => ({ identity: a.identity, context: readPlaceActContext(a.context, selected.selection.budget) })));
77
72
  }
@@ -0,0 +1,19 @@
1
+ import { LOCK } from '@lapxo/topos/wire';
2
+ import { BoundRefusal } from "./refusals.js";
3
+ /** Shared host refusals preserve the public code and diagnostic at every port. */
4
+ export function refuseHost(code) {
5
+ throw new BoundRefusal('host/' + code, 'REFUSE·host/' + code);
6
+ }
7
+ export function assertHostNotCancelled(signal) {
8
+ if (signal?.aborted)
9
+ refuseHost('cancelled');
10
+ }
11
+ /** Clock validation is shared; each boundary keeps its own refusal producer. */
12
+ export function receiverClock(text, refuse) {
13
+ if (!/^(0|[1-9][0-9]*)$/.test(text) || !Number.isSafeInteger(Number(text)))
14
+ refuse('unknown receiver clock');
15
+ return Number(text);
16
+ }
17
+ /** Coordinates use the wire's lock name, never a second spelling in a host. */
18
+ export const isPlaceLockCoordinate = (coordinate) => typeof coordinate === 'string' && coordinate.endsWith('/' + LOCK) && !coordinate.startsWith('/') && !coordinate.split('/').some(part => !part || part === '.' || part === '..');
19
+ export const placeRegionOf = (coordinate) => coordinate === '.' ? '' : coordinate.slice(0, -(LOCK.length + 1));
@@ -0,0 +1,82 @@
1
+ import { type ReadingIdentityContext } from '@lapxo/topos/contract';
2
+ import { type ContentReadPort, type SigningPort, type PortLimits } from '../engine/ports.ts';
3
+ import { type PlaceActPersistence } from './place-act-transaction.ts';
4
+ import type { PlaceActTrust, PlaceActBudget } from './ports/place-act.ts';
5
+ export interface PlaceActHostContext {
6
+ readonly place: string;
7
+ readonly source: string;
8
+ readonly identity: string;
9
+ readonly rootSnapshot: string;
10
+ readonly reading: ReadingIdentityContext;
11
+ readonly algorithm: string;
12
+ readonly contentLimits: PortLimits;
13
+ readonly signingLimits: PortLimits;
14
+ readonly storageLimits: PlaceActBudget;
15
+ readonly signal?: AbortSignal;
16
+ }
17
+ /** These are receiver-selected host ports, not carrier fields or an offered
18
+ * verifier. Trust must be constructed from the authenticated receiver snapshot. */
19
+ export interface PlaceActHostPorts {
20
+ readonly instrumentName: string;
21
+ readonly read: ContentReadPort;
22
+ readonly sign: SigningPort;
23
+ readonly trust: () => PlaceActTrust;
24
+ readonly persistence: PlaceActPersistence;
25
+ readonly limits: {
26
+ readonly storage: PlaceActBudget;
27
+ readonly signing: PortLimits;
28
+ readonly content: PortLimits;
29
+ };
30
+ }
31
+ /** Signs receiver receipts through the bounded existing signer protocol. Exact
32
+ * original declarations/proofs remain unchanged. Replay is historical recovery,
33
+ * never a new permission. A refusal after visibility is not a rollback claim. */
34
+ export declare function landPlaceActInProcess(context: PlaceActHostContext, ports: PlaceActHostPorts): Promise<{
35
+ kind: "fact";
36
+ value: {
37
+ place: string;
38
+ act: import("./ports/place-act.ts").PlaceActBundle & {
39
+ readonly kind: "landed" | "once";
40
+ readonly identity: string;
41
+ };
42
+ reading: string;
43
+ result: string;
44
+ commitment: string;
45
+ };
46
+ identity?: undefined;
47
+ code?: undefined;
48
+ diagnostic?: undefined;
49
+ } | {
50
+ kind: "uncertain";
51
+ identity: string;
52
+ code: string;
53
+ diagnostic: string;
54
+ value?: undefined;
55
+ } | {
56
+ kind: "refuse";
57
+ code: string;
58
+ diagnostic: string;
59
+ value?: undefined;
60
+ identity?: undefined;
61
+ }>;
62
+ /** Historical recovery after a lost response. No signer, current authorization
63
+ * or publication callback is invoked and the result grants no new act. */
64
+ export declare function recoverPlaceActInProcess(context: Pick<PlaceActHostContext, 'place' | 'identity' | 'reading' | 'storageLimits' | 'signal'>, ports: Pick<PlaceActHostPorts, 'instrumentName' | 'trust' | 'persistence' | 'limits'>): {
65
+ kind: "fact";
66
+ value: {
67
+ place: string;
68
+ identity: string;
69
+ act: import("./ports/place-act.ts").PlaceActBundle;
70
+ historical: true;
71
+ reading: string;
72
+ result: string;
73
+ commitment: string;
74
+ };
75
+ code?: undefined;
76
+ diagnostic?: undefined;
77
+ } | {
78
+ kind: "refuse";
79
+ code: string;
80
+ diagnostic: string;
81
+ value?: undefined;
82
+ };
@@ -0,0 +1,84 @@
1
+ import { authorityAtEpoch } from '@lapxo/topos/wire';
2
+ import { placeReceiptBytes, readPlaceActContext } from '@lapxo/topos/core';
3
+ import { readingIdentity, readingReceiptIdentity } from '@lapxo/topos/contract';
4
+ import { readContent, invokeFrameSigningPort } from "../engine/ports.js";
5
+ import { transactPlaceAct, PlaceActCommitUnknown } from "./place-act-transaction.js";
6
+ import { withActHost } from "../land/act.js";
7
+ import { sha256, isSha256 } from "./hash.js";
8
+ import { BoundRefusal } from "./refusals.js";
9
+ import { refuseHost as fail } from "./host-controls.js";
10
+ import { withHostInstrument } from "./call-context.js";
11
+ import { requireEffects } from "./read-only.js";
12
+ /** Signs receiver receipts through the bounded existing signer protocol. Exact
13
+ * original declarations/proofs remain unchanged. Replay is historical recovery,
14
+ * never a new permission. A refusal after visibility is not a rollback claim. */
15
+ export async function landPlaceActInProcess(context, ports) {
16
+ return withHostInstrument(ports.instrumentName, () => withActHost({ name: ports.instrumentName, report: () => { }, signal: context.signal }, async () => {
17
+ try {
18
+ requireEffects('contextual host admission');
19
+ const { place, source, identity, rootSnapshot, algorithm, signal } = context, reading = structuredClone(context.reading), contentLimits = { ...context.contentLimits }, signingLimits = { ...context.signingLimits }, storageLimits = { ...context.storageLimits };
20
+ if (typeof ports.instrumentName !== 'string' || !ports.instrumentName || !/[a-zA-Z0-9]/.test(ports.instrumentName) || !/^[a-zA-Z0-9._-]+$/.test(ports.instrumentName) || typeof place !== 'string' || !place || typeof source !== 'string' || !source || typeof algorithm !== 'string' || !algorithm)
21
+ fail('context');
22
+ if (storageLimits.acts > ports.limits.storage.acts || storageLimits.bytes > ports.limits.storage.bytes || signingLimits.timeoutMs > ports.limits.signing.timeoutMs || signingLimits.maxBytes > ports.limits.signing.maxBytes || contentLimits.timeoutMs > ports.limits.content.timeoutMs || contentLimits.maxBytes > ports.limits.content.maxBytes)
23
+ fail('budget');
24
+ const key = readingIdentity({ ...reading, inputs: [...reading.inputs, ['host/place', sha256(place)], ['host/snapshot', rootSnapshot], ['host/act', identity], ['host/operation', sha256(JSON.stringify([algorithm, contentLimits, signingLimits, storageLimits]))]] }, sha256);
25
+ const trust = ports.trust();
26
+ if (trust.contract.root.snapshot !== rootSnapshot)
27
+ fail('stale');
28
+ if (contentLimits.maxBytes > trust.contract.budget.bytes)
29
+ fail('budget');
30
+ const bytes = await readContent(source, identity, { read: ports.read, digestBytes: sha256 }, contentLimits, signal);
31
+ let encoded;
32
+ try {
33
+ encoded = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
34
+ }
35
+ catch {
36
+ return fail('snapshot-format');
37
+ }
38
+ const decoded = readPlaceActContext(encoded, trust.contract.budget);
39
+ if (trust.contract.digest(encoded) !== identity)
40
+ fail('integrity');
41
+ const acquire = async () => {
42
+ const by = trust.contract.receiptSigner, signer = trust.contract.signers.find(s => s.id === by);
43
+ if (!signer || authorityAtEpoch({ scope: trust.contract.receiptScope, by }, signer, Number(trust.contract.root.epoch), trust.contract.receiptScope, 'authority').kind !== 'admitted')
44
+ fail('receiver-authority');
45
+ const signed = await invokeFrameSigningPort({ keyId: by, algorithm, bytes: decoded.places.map(p => placeReceiptBytes(identity, p, decoded.root, by, trust.contract.receiptScope, trust.contract.budget)) }, ports.sign, signingLimits, signal);
46
+ return decoded.places.map((p, i) => ({ place: p.place, by, signature: signed[i].signature }));
47
+ };
48
+ const act = await transactPlaceAct(encoded, acquire, trust, storageLimits, ports.persistence, signal);
49
+ const result = sha256(JSON.stringify(act) + '\n');
50
+ return { kind: 'fact', value: { place, act, reading: key, result, commitment: readingReceiptIdentity(key, result, sha256) } };
51
+ }
52
+ catch (error) {
53
+ if (error instanceof PlaceActCommitUnknown)
54
+ return { kind: 'uncertain', identity: error.identity, code: error.code, diagnostic: error.message };
55
+ return { kind: 'refuse', code: error instanceof BoundRefusal ? error.code : 'host/failed', diagnostic: error instanceof Error ? error.message : 'REFUSE·host/failed' };
56
+ }
57
+ ;
58
+ }));
59
+ }
60
+ /** Historical recovery after a lost response. No signer, current authorization
61
+ * or publication callback is invoked and the result grants no new act. */
62
+ export function recoverPlaceActInProcess(context, ports) {
63
+ return withHostInstrument(ports.instrumentName, () => withActHost({ name: ports.instrumentName, report: () => { }, signal: context.signal }, () => {
64
+ try {
65
+ if (context.signal?.aborted)
66
+ fail('cancelled');
67
+ if (!context.place || !isSha256(context.identity))
68
+ fail('context');
69
+ const limits = { ...context.storageLimits };
70
+ if (limits.acts > ports.limits.storage.acts || limits.bytes > ports.limits.storage.bytes)
71
+ fail('budget');
72
+ const reading = readingIdentity({ ...structuredClone(context.reading), inputs: [...context.reading.inputs, ['host/place', sha256(context.place)], ['host/act', context.identity], ['host/operation', sha256('historical-recovery')]] }, sha256);
73
+ const act = ports.persistence.result(context.identity, ports.trust(), limits);
74
+ if (!act)
75
+ fail('act-unavailable');
76
+ const result = sha256(JSON.stringify(act) + '\n');
77
+ return { kind: 'fact', value: { place: context.place, identity: context.identity, act, historical: true, reading, result, commitment: readingReceiptIdentity(reading, result, sha256) } };
78
+ }
79
+ catch (error) {
80
+ return { kind: 'refuse', code: error instanceof BoundRefusal ? error.code : 'host/failed', diagnostic: error instanceof Error ? error.message : 'REFUSE·host/failed' };
81
+ }
82
+ ;
83
+ }));
84
+ }
@@ -0,0 +1,46 @@
1
+ import type { ContentReadPort } from '../engine/ports.ts';
2
+ import type { RootStandingHostContext } from './in-process.ts';
3
+ /** Full contextual carrier, authenticated against receiver inputs only. It is a
4
+ * data projection, not a current admission permit or a signed receipt. */
5
+ export declare function readContextualStandingInProcess(context: RootStandingHostContext, ports: {
6
+ readonly read: ContentReadPort;
7
+ }): Promise<{
8
+ kind: "fact";
9
+ value: {
10
+ place: string;
11
+ standing: {
12
+ profile: string;
13
+ root: readonly string[];
14
+ legacyPlaces: {
15
+ namespace: string;
16
+ place: string;
17
+ standing: string[];
18
+ history: readonly string[];
19
+ }[];
20
+ contexts: readonly import("./root-clock-source.ts").PortablePlaceContext[];
21
+ places: {
22
+ namespace: string;
23
+ place: string;
24
+ standing: string[];
25
+ retracted: readonly string[];
26
+ superseded: readonly string[];
27
+ forks: readonly import("../fold/claims.ts").Fork[];
28
+ contexts: {
29
+ identity: string;
30
+ context: string;
31
+ receipts: readonly import("@lapxo/topos/wire").PlaceReceipt[];
32
+ }[];
33
+ }[];
34
+ };
35
+ reading: string;
36
+ result: string;
37
+ commitment: string;
38
+ };
39
+ code?: undefined;
40
+ diagnostic?: undefined;
41
+ } | {
42
+ kind: "refuse";
43
+ code: string;
44
+ diagnostic: string;
45
+ value?: undefined;
46
+ }>;
@@ -0,0 +1,91 @@
1
+ import { parse } from '@lapxo/topos/wire';
2
+ import { readingIdentity, readingReceiptIdentity } from '@lapxo/topos/contract';
3
+ import { readContent } from "../engine/ports.js";
4
+ import { sha256 } from "./hash.js";
5
+ import { BoundRefusal } from "./refusals.js";
6
+ import { refuseHost as fail, isPlaceLockCoordinate, placeRegionOf } from "./host-controls.js";
7
+ import { publicStandingProjection, assertStandingView } from "./standing-projection.js";
8
+ import { rootClockSelectionOf, inspectPortablePlaceContexts } from "./root-clock-source.js";
9
+ import { projectContextualStanding } from "./standing-carrier.js";
10
+ import { authorityFor } from "../fold/signers.js";
11
+ import { ownRecordAdmitted } from "../fold/signed.js";
12
+ import { wordsOf } from "../fold/wire.js";
13
+ import { fieldOf, isConfig } from "../fold/claims.js";
14
+ import { signatureEraOf } from "./signature-era.js";
15
+ function authenticatedCarrier(snapshot, anchor, records) {
16
+ if (!snapshot || Object.keys(snapshot).sort().join(',') !== 'contexts,legacyPlaces,rootHistory' || !Array.isArray(snapshot.rootHistory) || !Array.isArray(snapshot.legacyPlaces) || !Array.isArray(snapshot.contexts))
17
+ fail('snapshot-format');
18
+ let count = snapshot.rootHistory.length;
19
+ if (count > records)
20
+ fail('budget');
21
+ const validLine = (line) => typeof line === 'string' && parse(line).kind === 'fact';
22
+ if (!snapshot.rootHistory.length || snapshot.rootHistory.some(l => !validLine(l)))
23
+ fail('snapshot-format');
24
+ const root = publicStandingProjection(snapshot.rootHistory, anchor), families = wordsOf(root, 'families');
25
+ // Unsupported vocabulary resolution must not discover a provider in the CLI host.
26
+ if (families.length && !families.includes('view'))
27
+ fail('vocabulary-unavailable');
28
+ assertStandingView(root);
29
+ const selection = rootClockSelectionOf(root, anchor), algorithms = signatureEraOf(root).admitted;
30
+ if (snapshot.legacyPlaces.length > selection.resources.places || snapshot.contexts.length > selection.resources.acts)
31
+ fail('budget');
32
+ const signers = authorityFor(snapshot.rootHistory, anchor, algorithms).admitted;
33
+ if (signers.some(s => !s.coverage.includes('*')) && !families.includes('region'))
34
+ fail('vocabulary-unavailable');
35
+ const names = new Set();
36
+ for (const place of snapshot.legacyPlaces) {
37
+ if (!place || !isPlaceLockCoordinate(place.place) || names.has(place.place) || !Array.isArray(place.history))
38
+ fail('snapshot-format');
39
+ names.add(place.place);
40
+ count += place.history.length;
41
+ if (count > records)
42
+ fail('budget');
43
+ const path = placeRegionOf(place.place);
44
+ if (place.history.some((l) => !validLine(l) || !ownRecordAdmitted(l, path, 'region', signers, algorithms)))
45
+ fail('legacy-authority');
46
+ }
47
+ for (const row of snapshot.contexts) {
48
+ if (!row || !row.authority || !Array.isArray(row.authority.history))
49
+ fail('snapshot-format');
50
+ count += row.authority.history.length;
51
+ if (count > records)
52
+ fail('budget');
53
+ // Historical authority must belong to this exact Root history, not a second
54
+ // signed Root fork silently substituted in the carrier.
55
+ const epoch = fieldOf(row.authority.checkpoint, 'epoch');
56
+ const expected = snapshot.rootHistory.filter(l => !isConfig(l) || Number(fieldOf(l, 'epoch')) <= Number(epoch));
57
+ if (JSON.stringify(expected) !== JSON.stringify(row.authority.history))
58
+ fail('historical-snapshot');
59
+ }
60
+ const decoded = inspectPortablePlaceContexts(JSON.stringify(snapshot.contexts), anchor, selection);
61
+ return projectContextualStanding(root, snapshot.legacyPlaces, snapshot.contexts, decoded);
62
+ }
63
+ /** Full contextual carrier, authenticated against receiver inputs only. It is a
64
+ * data projection, not a current admission permit or a signed receipt. */
65
+ export async function readContextualStandingInProcess(context, ports) {
66
+ try {
67
+ const { place, source, snapshot, signal } = context, anchor = structuredClone(context.anchor), reading = structuredClone(context.reading), limits = { ...context.limits };
68
+ if (!place || typeof place !== 'string' || !source || typeof source !== 'string' || !Number.isSafeInteger(limits.records) || limits.records < 1)
69
+ fail('context');
70
+ const identity = readingIdentity({ ...reading, inputs: [...reading.inputs, ['host/place', sha256(place)], ['host/anchor', sha256(JSON.stringify(anchor))], ['host/snapshot', snapshot]] }, sha256);
71
+ const bytes = await readContent(source, snapshot, { read: ports.read, digestBytes: sha256 }, limits, signal);
72
+ let text, value;
73
+ try {
74
+ text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
75
+ value = JSON.parse(text);
76
+ }
77
+ catch {
78
+ return fail('snapshot-format');
79
+ }
80
+ if (JSON.stringify(value) + '\n' !== text)
81
+ fail('snapshot-format');
82
+ if (signal?.aborted)
83
+ fail('cancelled');
84
+ const standing = authenticatedCarrier(value, anchor, limits.records), result = sha256(JSON.stringify(standing) + '\n');
85
+ return { kind: 'fact', value: { place, standing, reading: identity, result, commitment: readingReceiptIdentity(identity, result, sha256) } };
86
+ }
87
+ catch (error) {
88
+ return { kind: 'refuse', code: error instanceof BoundRefusal ? error.code : 'host/failed', diagnostic: error instanceof Error ? error.message : 'REFUSE·host/failed' };
89
+ }
90
+ ;
91
+ }