@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,117 @@
1
+ import { type ReadingIdentityContext } from '@lapxo/topos/contract';
2
+ import { type ContentReadPort, type SigningPort, type PortLimits } from '../engine/ports.ts';
3
+ import { type HostInstrumentRuntime } from './call-context.ts';
4
+ import type { LocalActBundle } from './ports/local-act.ts';
5
+ import type { PublicationStorage } from './ports/publication.ts';
6
+ export interface RootLotContext {
7
+ readonly place: string;
8
+ readonly snapshot: string;
9
+ readonly source: string;
10
+ readonly digest: string;
11
+ readonly reading: ReadingIdentityContext;
12
+ readonly limits: PortLimits & {
13
+ readonly records: number;
14
+ };
15
+ readonly signal?: AbortSignal;
16
+ }
17
+ /** The receiver selects the actual native place, instrument and signer. The lot
18
+ * supplies only canonical unsigned Root configuration, never host capabilities. */
19
+ export interface RootLotPorts {
20
+ readonly root: string;
21
+ readonly instrumentName: string;
22
+ readonly runtime: HostInstrumentRuntime;
23
+ readonly read: ContentReadPort;
24
+ readonly signer: {
25
+ readonly keyId: string;
26
+ readonly name: string;
27
+ readonly sign: SigningPort;
28
+ };
29
+ readonly storage?: PublicationStorage;
30
+ readonly prepared?: (identity: string) => void;
31
+ readonly limits: RootLotContext['limits'] & {
32
+ readonly diagnostics: number;
33
+ };
34
+ }
35
+ /** Snapshot identity, not a new wire, authority algorithm or current permit. */
36
+ export declare const rootLotSnapshot: (root: string) => string;
37
+ export declare function landRootLotInProcess(context: RootLotContext, ports: RootLotPorts): Promise<{
38
+ kind: "refuse";
39
+ code: string;
40
+ diagnostics: {
41
+ stream: "out" | "error";
42
+ text: string;
43
+ }[];
44
+ value?: undefined;
45
+ identity?: undefined;
46
+ diagnostic?: undefined;
47
+ } | {
48
+ kind: "fact";
49
+ value: {
50
+ result: string;
51
+ commitment: string;
52
+ conformance: "refused" | "complete";
53
+ reading: string;
54
+ identity?: string | undefined;
55
+ act?: import("@lapxo/topos/wire").ActResult | undefined;
56
+ place: string;
57
+ status: "once" | "landed" | "already-held";
58
+ };
59
+ diagnostics: {
60
+ stream: "out" | "error";
61
+ text: string;
62
+ }[];
63
+ code?: undefined;
64
+ identity?: undefined;
65
+ diagnostic?: undefined;
66
+ } | {
67
+ kind: "uncertain";
68
+ identity: string;
69
+ code: string;
70
+ diagnostic: string;
71
+ diagnostics: {
72
+ stream: "out" | "error";
73
+ text: string;
74
+ }[];
75
+ value?: undefined;
76
+ } | {
77
+ kind: "fact";
78
+ value: {
79
+ place: string;
80
+ status: "once" | "landed";
81
+ identity: string;
82
+ conformance: "incomplete";
83
+ };
84
+ diagnostic: string;
85
+ diagnostics: {
86
+ stream: "out" | "error";
87
+ text: string;
88
+ }[];
89
+ code?: undefined;
90
+ identity?: undefined;
91
+ } | {
92
+ kind: "refuse";
93
+ code: string;
94
+ diagnostic: string;
95
+ diagnostics: {
96
+ stream: "out" | "error";
97
+ text: string;
98
+ }[];
99
+ value?: undefined;
100
+ identity?: undefined;
101
+ }>;
102
+ /** Authenticated historical query only; never revives authority or signs. */
103
+ export declare function recoverRootActInProcess(identity: string, ports: Pick<RootLotPorts, 'root' | 'instrumentName' | 'runtime'>): {
104
+ kind: "fact";
105
+ value: {
106
+ historical: true;
107
+ act: import("@lapxo/topos/wire").ActResult;
108
+ bundle: LocalActBundle;
109
+ };
110
+ code?: undefined;
111
+ diagnostic?: undefined;
112
+ } | {
113
+ kind: "refuse";
114
+ code: string;
115
+ diagnostic: string;
116
+ value?: undefined;
117
+ };
@@ -0,0 +1,84 @@
1
+ import { canonical, parse } from '@lapxo/topos/wire';
2
+ import { readingIdentity, readingReceiptIdentity } from '@lapxo/topos/contract';
3
+ import { readContent } from "../engine/ports.js";
4
+ import { acceptRootRecords } from "../cli/accept.js";
5
+ import { verifiedLegacyActResult } from "../cli/act-result.js";
6
+ import { admissionHistoryAt, admissionAnchor } from "../fold/admission-history.js";
7
+ import { isConfig } from "../fold/claims.js";
8
+ import { sha256, isSha256 } from "./hash.js";
9
+ import { BoundRefusal } from "./refusals.js";
10
+ import { refuseHost as fail } from "./host-controls.js";
11
+ import { ActCommitUnknown } from "./commit-result.js";
12
+ import { requireEffects } from "./read-only.js";
13
+ import { withHostCall } from "./call-context.js";
14
+ import { withActHost } from "../land/act.js";
15
+ /** Snapshot identity, not a new wire, authority algorithm or current permit. */
16
+ export const rootLotSnapshot = (root) => sha256(admissionHistoryAt(root, Number.MAX_SAFE_INTEGER).join('\n') + '\n');
17
+ export async function landRootLotInProcess(context, ports) {
18
+ const diagnostics = [];
19
+ let size = 0;
20
+ let committed;
21
+ const runtime = structuredClone(ports.runtime), limits = { ...context.limits };
22
+ return withHostCall({ instrumentName: ports.instrumentName, runtime, output: (stream, text) => { size += new TextEncoder().encode(text).length; if (size > ports.limits.diagnostics)
23
+ fail('budget'); diagnostics.push({ stream, text }); } }, () => withActHost({ name: ports.instrumentName, report: text => { size += new TextEncoder().encode(text).length; if (size > ports.limits.diagnostics)
24
+ fail('budget'); diagnostics.push({ stream: 'error', text }); }, signal: context.signal }, async () => {
25
+ try {
26
+ requireEffects('Root lot admission');
27
+ if (!context.place || !ports.root || !ports.instrumentName || !runtime.root || !runtime.entry || !runtime.directory || !['.ts', '.js'].includes(runtime.extension) || !Array.isArray(runtime.lock) || !runtime.lock.length)
28
+ fail('context');
29
+ if (limits.records > ports.limits.records || limits.maxBytes > ports.limits.maxBytes || limits.timeoutMs > ports.limits.timeoutMs || !Number.isSafeInteger(limits.records) || limits.records < 1 || !Number.isSafeInteger(ports.limits.diagnostics) || ports.limits.diagnostics < 1)
30
+ fail('budget');
31
+ const { place, snapshot, source, digest, signal } = context, reading = structuredClone(context.reading);
32
+ if (!isSha256(snapshot) || rootLotSnapshot(ports.root) !== snapshot)
33
+ fail('stale');
34
+ const anchor = admissionAnchor(ports.root);
35
+ const identity = readingIdentity({ ...reading, inputs: [...reading.inputs, ['host/place', sha256(place)], ['host/snapshot', snapshot], ['host/anchor', sha256(JSON.stringify(anchor))], ['host/draft', digest], ['host/principal', sha256(ports.signer.keyId)], ['host/operation', sha256(JSON.stringify(['root-lot', limits]))]] }, sha256);
36
+ const bytes = await readContent(source, digest, { read: ports.read, digestBytes: sha256 }, limits, signal);
37
+ let text;
38
+ try {
39
+ text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
40
+ }
41
+ catch {
42
+ return fail('snapshot-format');
43
+ }
44
+ const records = text.endsWith('\n') ? text.slice(0, -1).split('\n') : [];
45
+ if (!records.length || records.length > limits.records)
46
+ fail('budget');
47
+ for (const line of records) {
48
+ const parsed = parse(line);
49
+ if (parsed.kind !== 'fact' || canonical(parsed.value.fields, parsed.value.version) !== line || !isConfig(line) || parsed.value.fields.by !== 'target' || parsed.value.fields.epoch !== undefined || parsed.value.fields.sig !== undefined)
50
+ fail('root-lot-format');
51
+ }
52
+ if (rootLotSnapshot(ports.root) !== snapshot)
53
+ fail('stale');
54
+ const code = await acceptRootRecords(ports.root, { records, keyId: ports.signer.keyId, signer: { name: ports.signer.name, sign: ports.signer.sign, signal }, signal, storage: ports.storage, prepared: ports.prepared, committed: (identity, bundle, status) => { committed = { identity, bundle, status }; } });
55
+ if (code !== 0 && !committed)
56
+ return { kind: 'refuse', code: 'host/act', diagnostics };
57
+ const value = { place, status: committed?.status ?? 'already-held', ...(committed ? { identity: committed.identity, act: verifiedLegacyActResult(ports.root, committed.identity).result } : {}), conformance: code === 0 ? 'complete' : 'refused', reading: identity };
58
+ const result = sha256(JSON.stringify(value) + '\n');
59
+ return { kind: 'fact', value: { ...value, result, commitment: readingReceiptIdentity(identity, result, sha256) }, diagnostics };
60
+ }
61
+ catch (error) {
62
+ if (error instanceof ActCommitUnknown)
63
+ return { kind: 'uncertain', identity: error.identity, code: error.code, diagnostic: error.message, diagnostics };
64
+ if (committed)
65
+ return { kind: 'fact', value: { place: context.place, status: committed.status, identity: committed.identity, conformance: 'incomplete' }, diagnostic: error instanceof Error ? error.message : 'post-commit failure', diagnostics };
66
+ return { kind: 'refuse', code: error instanceof BoundRefusal ? error.code : 'host/failed', diagnostic: error instanceof Error ? error.message : 'REFUSE·host/failed', diagnostics };
67
+ }
68
+ }));
69
+ }
70
+ /** Authenticated historical query only; never revives authority or signs. */
71
+ export function recoverRootActInProcess(identity, ports) {
72
+ return withHostCall({ instrumentName: ports.instrumentName, runtime: structuredClone(ports.runtime), output: () => { } }, () => {
73
+ try {
74
+ if (!isSha256(identity))
75
+ fail('context');
76
+ const recovered = verifiedLegacyActResult(ports.root, identity);
77
+ return { kind: 'fact', value: { historical: true, act: recovered.result, bundle: recovered.bundle } };
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,41 @@
1
+ import type { Signer } from '@lapxo/topos/wire';
2
+ import type { ReadingIdentityContext } from '@lapxo/topos/contract';
3
+ import type { ContentReadPort, PortLimits } from '../engine/ports.ts';
4
+ /** Initial read-only host slice. The receiver supplies the original anchor and
5
+ * verified implementation/policy references; the offered bytes choose none of them.
6
+ * Context references commit identity, not authority or conformance certification.
7
+ */
8
+ export interface RootStandingHostContext {
9
+ readonly place: string;
10
+ readonly source: string;
11
+ readonly snapshot: string;
12
+ readonly anchor: Signer;
13
+ readonly reading: ReadingIdentityContext;
14
+ readonly limits: PortLimits & {
15
+ readonly records: number;
16
+ };
17
+ readonly signal?: AbortSignal;
18
+ }
19
+ export type RootStandingHostResult = {
20
+ readonly kind: 'fact';
21
+ readonly value: {
22
+ readonly place: string;
23
+ readonly rootStanding: readonly string[];
24
+ readonly reading: string;
25
+ readonly result: string;
26
+ readonly commitment: string;
27
+ };
28
+ } | {
29
+ readonly kind: 'refuse';
30
+ readonly code: string;
31
+ readonly diagnostic: string;
32
+ };
33
+ /** Uses the existing bounded content port, authority evaluator and projection.
34
+ * It projects the Root component of a complete signed Root wire snapshot; it has no persistence callback,
35
+ * current-root global, console output, process entry point or CLI subprocess.
36
+ * Contextual places and their carriers are not this Root-only projection.
37
+ * The returned commitment is unsigned and is not a paid native receipt.
38
+ */
39
+ export declare function readRootStandingInProcess(context: RootStandingHostContext, ports: {
40
+ readonly read: ContentReadPort;
41
+ }): Promise<RootStandingHostResult>;
@@ -0,0 +1,55 @@
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, isSha256 } from "./hash.js";
5
+ import { BoundRefusal } from "./refusals.js";
6
+ import { refuseHost as refuse } from "./host-controls.js";
7
+ import { assertStandingView, publicStandingProjection } from "./standing-projection.js";
8
+ /** Uses the existing bounded content port, authority evaluator and projection.
9
+ * It projects the Root component of a complete signed Root wire snapshot; it has no persistence callback,
10
+ * current-root global, console output, process entry point or CLI subprocess.
11
+ * Contextual places and their carriers are not this Root-only projection.
12
+ * The returned commitment is unsigned and is not a paid native receipt.
13
+ */
14
+ export async function readRootStandingInProcess(context, ports) {
15
+ try {
16
+ if (!context || typeof context.place !== 'string' || !context.place || typeof context.source !== 'string' || !context.source || !isSha256(context.snapshot))
17
+ refuse('context');
18
+ if (context.signal?.aborted)
19
+ refuse('cancelled');
20
+ const { place, source, snapshot, signal } = context, limits = { ...context.limits };
21
+ if (!Number.isSafeInteger(limits.records) || limits.records < 1)
22
+ refuse('budget');
23
+ const anchor = structuredClone(context.anchor), reading = structuredClone(context.reading);
24
+ if (!anchor || anchor.keyClass !== 'authorize' || !anchor.id || !anchor.publicKey || !Array.isArray(anchor.coverage) || !anchor.coverage.length)
25
+ refuse('context');
26
+ // Validate the complete declared environment before invoking a host port.
27
+ readingIdentity(reading, sha256);
28
+ const identity = readingIdentity({ ...reading, inputs: [...reading.inputs, ['host/place', sha256(place)], ['host/anchor', sha256(JSON.stringify(anchor))], ['host/snapshot', snapshot]] }, sha256);
29
+ const bytes = await readContent(source, snapshot, { read: ports.read, digestBytes: sha256 }, limits, signal);
30
+ let text;
31
+ try {
32
+ text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
33
+ }
34
+ catch {
35
+ return refuse('snapshot-format');
36
+ }
37
+ const history = text.split('\n').filter(line => line.length > 0);
38
+ if (history.length > limits.records)
39
+ refuse('budget');
40
+ if (!history.length || history.some(line => parse(line).kind !== 'fact'))
41
+ refuse('snapshot-format');
42
+ if (signal?.aborted)
43
+ refuse('cancelled');
44
+ const standing = publicStandingProjection(history, anchor);
45
+ assertStandingView(standing);
46
+ if (signal?.aborted)
47
+ refuse('cancelled');
48
+ const result = sha256(standing.join('\n') + (standing.length ? '\n' : ''));
49
+ return { kind: 'fact', value: { place, rootStanding: standing, reading: identity, result, commitment: readingReceiptIdentity(identity, result, sha256) } };
50
+ }
51
+ catch (error) {
52
+ return { kind: 'refuse', code: error instanceof BoundRefusal ? error.code : 'host/failed', diagnostic: error instanceof Error ? error.message : 'REFUSE·host/failed' };
53
+ }
54
+ ;
55
+ }
@@ -0,0 +1,15 @@
1
+ /** Native host boundary. Uses the same admission and projection as the CLI. */
2
+ export { readRootStandingInProcess } from './in-process.ts';
3
+ export type { RootStandingHostContext, RootStandingHostResult } from './in-process.ts';
4
+ export { readContextualStandingInProcess } from './in-process-context.ts';
5
+ export { landPlaceActInProcess, recoverPlaceActInProcess } from './in-process-act.ts';
6
+ export type { PlaceActHostContext, PlaceActHostPorts } from './in-process-act.ts';
7
+ export { landRootLotInProcess, recoverRootActInProcess, rootLotSnapshot } from './in-process-root.ts';
8
+ export type { RootLotContext, RootLotPorts } from './in-process-root.ts';
9
+ export { nativeHostInstrument, nativePlaceActReceiver } from './native-receiver.ts';
10
+ export type { NativeHostInstrument } from './native-receiver.ts';
11
+ export type { HostInstrumentRuntime } from './call-context.ts';
12
+ export { nativePlaceActPersistence } from './ports/place-act.ts';
13
+ export type { PlaceActTrust, PlaceActBudget } from './ports/place-act.ts';
14
+ export type { PlaceActPersistence } from './place-act-transaction.ts';
15
+ export type { PublicationStorage } from './ports/publication.ts';
@@ -0,0 +1,7 @@
1
+ /** Native host boundary. Uses the same admission and projection as the CLI. */
2
+ export { readRootStandingInProcess } from "./in-process.js";
3
+ export { readContextualStandingInProcess } from "./in-process-context.js";
4
+ export { landPlaceActInProcess, recoverPlaceActInProcess } from "./in-process-act.js";
5
+ export { landRootLotInProcess, recoverRootActInProcess, rootLotSnapshot } from "./in-process-root.js";
6
+ export { nativeHostInstrument, nativePlaceActReceiver } from "./native-receiver.js";
7
+ export { nativePlaceActPersistence } from "./ports/place-act.js";
@@ -0,0 +1,19 @@
1
+ import { type HostInstrumentRuntime } from './call-context.ts';
2
+ export interface NativeHostInstrument {
3
+ readonly name: string;
4
+ readonly runtime: HostInstrumentRuntime;
5
+ }
6
+ /** Explicit installed/source ESM selection, never consumer argv or packet data.
7
+ * Identity and public configuration do not certify implementation correctness. */
8
+ export declare function nativeHostInstrument(root: string, entry: string): NativeHostInstrument;
9
+ /** Bind existing native authority construction to an explicit instrument for
10
+ * every later current/historical observation, not only the constructor call. */
11
+ export declare function nativePlaceActReceiver(root: string, instrument: NativeHostInstrument): {
12
+ selection: import("./place-clock-selection.ts").HostClockSelection;
13
+ trust: {
14
+ current: () => import("./ports/place-act.ts").PlaceActSnapshot;
15
+ historical: (encoded: string) => import("@lapxo/topos/wire").TrustedClockContract;
16
+ contract: import("@lapxo/topos/wire").TrustedClockContract;
17
+ initialHeads: readonly import("./ports/place-act.ts").PlaceActHead[];
18
+ };
19
+ };
@@ -0,0 +1,25 @@
1
+ import { readFileSync, realpathSync } from 'node:fs';
2
+ import { basename, dirname, extname, isAbsolute, join, relative } from 'node:path';
3
+ import { publicLock } from '@lapxo/topos/wire';
4
+ import { foldClaims, wireLinesOf } from "../fold/claims.js";
5
+ import { declaredRootClockSource } from "./root-clock-source.js";
6
+ import { withHostCall } from "./call-context.js";
7
+ import { BoundRefusal } from "./refusals.js";
8
+ /** Explicit installed/source ESM selection, never consumer argv or packet data.
9
+ * Identity and public configuration do not certify implementation correctness. */
10
+ export function nativeHostInstrument(root, entry) {
11
+ const at = realpathSync(root), file = realpathSync(entry), inside = relative(at, file), extension = extname(file);
12
+ if (!isAbsolute(root) || !isAbsolute(entry) || inside.startsWith('..') || isAbsolute(inside) || !['.ts', '.js'].includes(extension))
13
+ throw new BoundRefusal('host/context', 'REFUSE·host/context');
14
+ const published = publicLock(wireLinesOf(readFileSync(join(at, 'TARGET.bound'), 'utf8')));
15
+ if (!published)
16
+ throw new BoundRefusal('host/context', 'REFUSE·host requires a public instrument lock');
17
+ return { name: basename(at), runtime: { root: at, entry: file, directory: dirname(dirname(file)), extension: extension, lock: foldClaims(published).standing } };
18
+ }
19
+ /** Bind existing native authority construction to an explicit instrument for
20
+ * every later current/historical observation, not only the constructor call. */
21
+ export function nativePlaceActReceiver(root, instrument) {
22
+ const call = { instrumentName: instrument.name, runtime: structuredClone(instrument.runtime), output: () => { } };
23
+ const selected = withHostCall(call, () => declaredRootClockSource(root)), original = selected.trust;
24
+ return { selection: selected.selection, trust: { ...original, current: () => withHostCall(call, () => original.current()), historical: (encoded) => withHostCall(call, () => original.historical(encoded)) } };
25
+ }
@@ -0,0 +1,24 @@
1
+ import { type PlaceReceipt } from '@lapxo/topos/core';
2
+ import type { PlaceActTrust, PlaceActBudget, PlaceActBundle, PlaceActHead } from './ports/place-act.ts';
3
+ export { ActCommitUnknown as PlaceActCommitUnknown } from './commit-result.ts';
4
+ /** Host-selected persistence, never supplied by offered content. exclusive must
5
+ * share the CLI's region/authority/projection protocol. publish is synchronous
6
+ * with exactly one atomic visibility point; a thrown reply may follow commit. */
7
+ export interface PlaceActPersistence {
8
+ readonly exclusive: <T>(regions: readonly string[], run: () => Promise<T>) => Promise<T>;
9
+ readonly result: (identity: string, trust: PlaceActTrust, limits: PlaceActBudget) => PlaceActBundle | undefined;
10
+ readonly inventory: (limits: PlaceActBudget) => {
11
+ readonly names: readonly string[];
12
+ readonly bytes: number;
13
+ };
14
+ readonly heads: (trust: PlaceActTrust, limits: PlaceActBudget) => readonly PlaceActHead[];
15
+ readonly publish: (identity: string, files: Readonly<Record<string, string>>) => void;
16
+ }
17
+ export type PlaceActReceiptSource = readonly PlaceReceipt[] | (() => Promise<readonly PlaceReceipt[]>);
18
+ /** Shared transaction, including historical replay, current authorization and
19
+ * atomic publication. Signing may await; current Root and receiver heads are
20
+ * checked again afterwards while the same exclusion remains held. */
21
+ export declare function transactPlaceAct(encoded: string, source: PlaceActReceiptSource, trust: PlaceActTrust, limits: PlaceActBudget, persistence: PlaceActPersistence, signal?: AbortSignal): Promise<PlaceActBundle & {
22
+ readonly kind: 'landed' | 'once';
23
+ readonly identity: string;
24
+ }>;
@@ -0,0 +1,78 @@
1
+ import { readPlaceActContext, verifyPlaceActContext, placeActReceiptsOf } from '@lapxo/topos/core';
2
+ import { requireEffects } from "./read-only.js";
3
+ import { BoundRefusal } from "./refusals.js";
4
+ import { ActCommitUnknown } from "./commit-result.js";
5
+ import { assertHostNotCancelled as cancelled, receiverClock, placeRegionOf } from "./host-controls.js";
6
+ export { ActCommitUnknown as PlaceActCommitUnknown } from "./commit-result.js";
7
+ /** The visibility point may have happened. Only an authenticated result query
8
+ * or replay can resolve this; the error never asserts rollback. */
9
+ const fail = (why) => { throw new BoundRefusal('host/act', 'REFUSE·place act ' + why); };
10
+ const rootBytes = (r) => JSON.stringify([r.namespace, r.snapshot, r.epoch, r.prefix, r.head, r.policies]);
11
+ /** Shared transaction, including historical replay, current authorization and
12
+ * atomic publication. Signing may await; current Root and receiver heads are
13
+ * checked again afterwards while the same exclusion remains held. */
14
+ export async function transactPlaceAct(encoded, source, trust, limits, persistence, signal) {
15
+ requireEffects('place act commitment');
16
+ cancelled(signal);
17
+ if (!limits || !Number.isSafeInteger(limits.acts) || limits.acts < 1 || !Number.isSafeInteger(limits.bytes) || limits.bytes < 1)
18
+ fail('missing storage budget');
19
+ const decoded = readPlaceActContext(encoded, trust.contract.budget), id = trust.contract.digest(encoded);
20
+ const regions = decoded.places.map(p => placeRegionOf(p.place));
21
+ return persistence.exclusive(regions, async () => {
22
+ cancelled(signal);
23
+ const prior = persistence.result(id, trust, limits);
24
+ if (prior) {
25
+ if (prior.context !== encoded)
26
+ fail('replay bytes differ');
27
+ // A signing callback is never invoked for a historically authenticated replay.
28
+ if (typeof source !== 'function')
29
+ placeActReceiptsOf(encoded, source, trust.historical(encoded));
30
+ return { ...prior, identity: id, kind: 'once' };
31
+ }
32
+ const verified = verifyPlaceActContext(encoded, trust.contract);
33
+ const originals = decoded.places.map(p => p.records);
34
+ const capacity = (size) => {
35
+ const inventory = persistence.inventory(limits);
36
+ if (inventory.bytes + size > limits.bytes)
37
+ fail('publication byte budget');
38
+ if (inventory.names.length >= limits.acts)
39
+ fail('publication act budget');
40
+ };
41
+ const sizeOf = (files) => Object.values(files).reduce((n, v) => n + new TextEncoder().encode(v).length, 0);
42
+ capacity(sizeOf({ 'context.json': encoded, 'originals.json': JSON.stringify(originals) + '\n' }));
43
+ const assertCurrent = () => {
44
+ const current = trust.current();
45
+ if (rootBytes(current.root) !== rootBytes(verified.dependencies))
46
+ fail('authority changed before publication');
47
+ if (new Set(current.heads.map(h => h.place)).size !== current.heads.length)
48
+ fail('ambiguous receiver heads');
49
+ const stored = persistence.heads(trust, limits);
50
+ for (const p of decoded.places) {
51
+ const actual = stored.find(h => h.place === p.place), selected = current.heads.find(h => h.place === p.place);
52
+ if (!actual || !selected || actual.identity !== selected.identity || actual.epoch !== selected.epoch)
53
+ fail('receiver snapshot is not committed head ' + p.place);
54
+ if (selected.identity !== p.previous)
55
+ fail('receiver head moved ' + p.place);
56
+ const next = receiverClock(selected.epoch, fail) + 1;
57
+ if (!Number.isSafeInteger(next) || receiverClock(p.epoch, fail) !== next)
58
+ fail('receiver epoch not next ' + p.place);
59
+ }
60
+ };
61
+ assertCurrent();
62
+ cancelled(signal);
63
+ const receipts = structuredClone(typeof source === 'function' ? await source() : source);
64
+ cancelled(signal);
65
+ placeActReceiptsOf(encoded, receipts, trust.contract);
66
+ const files = { 'context.json': encoded, 'receipts.json': JSON.stringify(receipts) + '\n', 'originals.json': JSON.stringify(originals) + '\n' };
67
+ capacity(sizeOf(files));
68
+ assertCurrent();
69
+ cancelled(signal);
70
+ try {
71
+ persistence.publish(id, files);
72
+ }
73
+ catch (error) {
74
+ throw new ActCommitUnknown(id, error);
75
+ }
76
+ return { context: encoded, receipts, originals, identity: id, kind: 'landed' };
77
+ });
78
+ }
@@ -0,0 +1,55 @@
1
+ import { PLACE_ACT_CONTEXT, type ClockBudget, type TrustedClockContract } from '@lapxo/topos/core';
2
+ import { type Signer } from '@lapxo/topos/wire';
3
+ import { type PlaceActTrust, type PlaceActBudget, type PlaceActHead } from './ports/place-act.ts';
4
+ export interface HostClockSelection {
5
+ readonly profile: typeof PLACE_ACT_CONTEXT;
6
+ readonly namespace: string;
7
+ readonly regionFamily: string;
8
+ readonly receiptSigner: string;
9
+ readonly receiptScope: string;
10
+ readonly digestAlgorithm: 'sha256';
11
+ readonly budget: ClockBudget;
12
+ readonly storageBudget: PlaceActBudget;
13
+ readonly initial: readonly {
14
+ place: string;
15
+ receiver: {
16
+ identity: string;
17
+ epoch: string;
18
+ };
19
+ origin: {
20
+ prefix: string;
21
+ epoch: string;
22
+ };
23
+ }[];
24
+ }
25
+ export interface HostClockInput {
26
+ readonly store: string;
27
+ readonly selector: string;
28
+ readonly profileSelector: string;
29
+ readonly history: readonly string[];
30
+ readonly anchor: Signer;
31
+ readonly algorithms: readonly string[];
32
+ readonly legacyOriginals: readonly string[];
33
+ /** Actual protected Root authority cursor: an authenticated signed Root config
34
+ * checkpoint, never a sibling receipt/evidence max epoch. */
35
+ readonly authorityCheckpoint: string;
36
+ /** Fixed host historical predecessor proof, never taken from a packet. */
37
+ readonly authenticatedReceiverHeads?: readonly PlaceActHead[];
38
+ readonly readCurrent: () => Pick<HostClockInput, 'history' | 'anchor' | 'algorithms' | 'legacyOriginals' | 'authorityCheckpoint'>;
39
+ readonly resources: {
40
+ bytes: number;
41
+ lines: number;
42
+ places: number;
43
+ };
44
+ readonly inherited: {
45
+ clock: ClockBudget;
46
+ storage: PlaceActBudget;
47
+ };
48
+ readonly historical: (encoded: string) => TrustedClockContract;
49
+ }
50
+ /** Root chooses selector and anchors/resources from its authenticated standing.
51
+ * Selection must be ROOT signed; a world's signature grants no host capability. */
52
+ export declare function hostClockSelection(input: HostClockInput): {
53
+ selection: HostClockSelection;
54
+ trust: PlaceActTrust;
55
+ };
@@ -4,7 +4,7 @@ import { createHash } from 'node:crypto';
4
4
  import { PLACE_ACT_CONTEXT } from '@lapxo/topos/core';
5
5
  import { canonical, signedBytes, wireAt } from '@lapxo/topos/wire';
6
6
  import { foldClaims, isConfig, fieldOf, parsedLine as parse } from "../fold/claims.js";
7
- import { eraOf } from "../fold/digests.js";
7
+ import { signatureEraOf as eraOf } from "./signature-era.js";
8
8
  import { authorityFor } from "../fold/signers.js";
9
9
  import { verifyBytes } from "./adapters/keys/ed25519.js";
10
10
  import { verifiesOwnerLine } from "../land/sign.js";
@@ -0,0 +1,44 @@
1
+ import type { PublicationStorage } from './publication.ts';
2
+ /** Prepared and verified by the selected Topos admission/result contract. */
3
+ export interface LocalActBundle {
4
+ readonly records: string;
5
+ readonly receipt: string;
6
+ readonly placements?: readonly {
7
+ place: string;
8
+ records: string;
9
+ }[];
10
+ }
11
+ export type LocalActVerification = (identity: string, bundle: LocalActBundle) => boolean;
12
+ export declare const actLines: (bytes: string) => readonly string[];
13
+ /** Journal integrity is not authority. Signature/coverage checks remain the
14
+ * same admission boundary; the hash algorithm here is the stored seal's named
15
+ * algorithm, not a new selection of the place's wire.
16
+ */
17
+ export declare function intactLocalAct(identity: string, bundle: LocalActBundle): boolean;
18
+ /** Internal integrity key only, not an act identity or a wire profile. */
19
+ export declare function committedInventoryFingerprint(rows: readonly {
20
+ identity: string;
21
+ bundle: LocalActBundle;
22
+ }[]): string;
23
+ /** Committed segments are part of this ledger, not a second authority source. */
24
+ export declare function committedLocalActs(store: string): readonly {
25
+ identity: string;
26
+ bundle: LocalActBundle;
27
+ }[];
28
+ /** Fixed host observation only: one synchronous, coherent committed inventory.
29
+ * It supplies no authority, verifier or async lifetime. Each outer invocation
30
+ * rereads exact bodies before entering; current authorization still runs inside.
31
+ */
32
+ export declare function withCommittedLocalActObservation<T>(store: string, body: () => T): T;
33
+ /** A public result is available only as a verified committed pair.
34
+ * No writer-tail guess, newest-result selection or implicit authority.
35
+ */
36
+ export declare function localActResult(store: string, identity: string, verify: LocalActVerification): LocalActBundle | undefined;
37
+ /** One publication point for records and their native result. Re-delivery
38
+ * reads the original pair without preparation, signing or a new local epoch.
39
+ * The caller must also integrate this boundary into admission-history readers;
40
+ * this storage port alone does not activate a grammar or land authority.
41
+ */
42
+ export declare function commitLocalAct(store: string, identity: string, regions: readonly string[], prepare: () => Promise<LocalActBundle>, verify: LocalActVerification, storage?: PublicationStorage, signal?: AbortSignal): Promise<LocalActBundle & {
43
+ readonly kind: 'landed' | 'once';
44
+ }>;
@@ -1,4 +1,6 @@
1
1
  import { hashText } from "../hash.js";
2
+ import { ActCommitUnknown } from "../commit-result.js";
3
+ import { assertHostNotCancelled } from "../host-controls.js";
2
4
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
3
5
  import { join } from 'node:path';
4
6
  import { createHash } from 'node:crypto';
@@ -154,19 +156,28 @@ export function localActResult(store, identity, verify) {
154
156
  * The caller must also integrate this boundary into admission-history readers;
155
157
  * this storage port alone does not activate a grammar or land authority.
156
158
  */
157
- export async function commitLocalAct(store, identity, regions, prepare, verify, storage = nativePublicationStorage) {
159
+ export async function commitLocalAct(store, identity, regions, prepare, verify, storage = nativePublicationStorage, signal) {
158
160
  requireEffects('local act commitment');
161
+ assertHostNotCancelled(signal);
159
162
  return underTheRegions(store, regions, 'land', async () => {
163
+ assertHostNotCancelled(signal);
160
164
  const prior = localActResult(store, identity, verify);
161
165
  if (prior)
162
166
  return { kind: 'once', ...prior };
163
167
  const bundle = await prepare();
168
+ assertHostNotCancelled(signal);
164
169
  if (!bundle.records || !bundle.receipt || verify(identity, bundle) !== true)
165
170
  throw Error('REFUSE·act prepared result does not verify');
166
171
  const files = { 'records.bound': bundle.records, 'receipt.bound': bundle.receipt };
167
172
  for (const p of bundle.placements ?? [])
168
173
  files['place-' + encodeURIComponent(p.place) + '.bound'] = p.records;
169
- publishBundle(atOf(store, identity), files, storage);
174
+ assertHostNotCancelled(signal);
175
+ try {
176
+ publishBundle(atOf(store, identity), files, storage);
177
+ }
178
+ catch (error) {
179
+ throw new ActCommitUnknown(identity, error);
180
+ }
170
181
  return { kind: 'landed', ...bundle };
171
182
  });
172
183
  }