@lapxo/bound 0.1.7 → 0.1.9

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 (131) hide show
  1. package/README.md +56 -20
  2. package/TARGET.bound +27 -27
  3. package/dist/cli/accept.js +83 -59
  4. package/dist/cli/act-result.d.ts +9 -0
  5. package/dist/cli/act-result.js +68 -13
  6. package/dist/cli/args.d.ts +1 -0
  7. package/dist/cli/args.js +5 -2
  8. package/dist/cli/custodian.d.ts +1 -0
  9. package/dist/cli/custodian.js +29 -0
  10. package/dist/cli/native-descendant.d.ts +1 -0
  11. package/dist/cli/native-descendant.js +19 -0
  12. package/dist/cli/native.d.ts +1 -0
  13. package/dist/cli/native.js +60 -0
  14. package/dist/cli/observe.js +9 -2
  15. package/dist/cli/pass.js +6 -1
  16. package/dist/cli/standing-result.js +7 -26
  17. package/dist/cli/takes.js +5 -2
  18. package/dist/cli/verb.js +24 -5
  19. package/dist/fold/admission-history.js +2 -2
  20. package/dist/fold/claims.d.ts +4 -0
  21. package/dist/fold/claims.js +23 -3
  22. package/dist/fold/closed.d.ts +1 -1
  23. package/dist/fold/closed.js +16 -4
  24. package/dist/fold/digests.d.ts +1 -1
  25. package/dist/fold/digests.js +3 -2
  26. package/dist/fold/evidence.d.ts +1 -1
  27. package/dist/fold/kept.js +2 -1
  28. package/dist/fold/observed.d.ts +1 -0
  29. package/dist/fold/observed.js +54 -9
  30. package/dist/fold/readers.d.ts +4 -3
  31. package/dist/fold/readers.js +18 -13
  32. package/dist/fold/resolved.js +11 -1
  33. package/dist/fold/signers.js +2 -2
  34. package/dist/host/adapters/modules/javascript.d.ts +2 -0
  35. package/dist/host/adapters/modules/javascript.js +8 -6
  36. package/dist/host/admitted-place.d.ts +5 -0
  37. package/dist/host/admitted-place.js +54 -0
  38. package/dist/host/capsule-process.d.ts +1 -1
  39. package/dist/host/capsule-process.js +4 -1
  40. package/dist/host/capsule.d.ts +4 -0
  41. package/dist/host/capsule.js +16 -2
  42. package/dist/host/contextual-place.d.ts +59 -0
  43. package/dist/host/contextual-place.js +77 -0
  44. package/dist/host/hash.d.ts +7 -0
  45. package/dist/host/hash.js +6 -0
  46. package/dist/host/io.d.ts +2 -0
  47. package/dist/host/io.js +2 -0
  48. package/dist/host/lifetime.js +61 -22
  49. package/dist/host/native/bounded-file.d.ts +8 -0
  50. package/dist/host/native/bounded-file.js +32 -0
  51. package/dist/host/native/command.d.ts +36 -0
  52. package/dist/host/native/command.js +91 -0
  53. package/dist/host/native/control-bridge-worker.d.ts +1 -0
  54. package/dist/host/native/control-bridge-worker.js +88 -0
  55. package/dist/host/native/control-bridge.d.ts +30 -0
  56. package/dist/host/native/control-bridge.js +110 -0
  57. package/dist/host/native/control-selection.d.ts +29 -0
  58. package/dist/host/native/control-selection.js +32 -0
  59. package/dist/host/native/custody.d.ts +5 -0
  60. package/dist/host/native/custody.js +91 -0
  61. package/dist/host/native/deadline.d.ts +12 -0
  62. package/dist/host/native/deadline.js +28 -0
  63. package/dist/host/native/descendants.d.ts +1 -0
  64. package/dist/host/native/descendants.js +528 -0
  65. package/dist/host/native/generated-custodian.d.ts +122 -0
  66. package/dist/host/native/generated-custodian.js +267 -0
  67. package/dist/host/native/generated.d.ts +217 -0
  68. package/dist/host/native/generated.js +379 -0
  69. package/dist/host/native/image.d.ts +36 -0
  70. package/dist/host/native/image.js +45 -0
  71. package/dist/host/native/jobs.d.ts +75 -0
  72. package/dist/host/native/jobs.js +148 -0
  73. package/dist/host/native/loader.d.ts +80 -0
  74. package/dist/host/native/loader.js +269 -0
  75. package/dist/host/native/materialize.d.ts +22 -0
  76. package/dist/host/native/materialize.js +175 -0
  77. package/dist/host/native/module-closure.d.ts +27 -0
  78. package/dist/host/native/module-closure.js +99 -0
  79. package/dist/host/native/module-syntax.d.ts +8 -0
  80. package/dist/host/native/module-syntax.js +63 -0
  81. package/dist/host/native/native-binary.d.ts +54 -0
  82. package/dist/host/native/native-binary.js +115 -0
  83. package/dist/host/native/native-program.d.ts +56 -0
  84. package/dist/host/native/native-program.js +72 -0
  85. package/dist/host/native/node-jobs.d.ts +84 -0
  86. package/dist/host/native/node-jobs.js +187 -0
  87. package/dist/host/native/node-launch.d.ts +72 -0
  88. package/dist/host/native/node-launch.js +159 -0
  89. package/dist/host/native/preload.d.ts +1 -0
  90. package/dist/host/native/preload.js +45 -0
  91. package/dist/host/native/reading.d.ts +91 -0
  92. package/dist/host/native/reading.js +249 -0
  93. package/dist/host/native/receipt-environment.d.ts +3 -0
  94. package/dist/host/native/receipt-environment.js +41 -0
  95. package/dist/host/native/references.d.ts +5 -0
  96. package/dist/host/native/references.js +30 -0
  97. package/dist/host/native/root.d.ts +17 -0
  98. package/dist/host/native/root.js +37 -0
  99. package/dist/host/native/route.d.ts +66 -0
  100. package/dist/host/native/route.js +216 -0
  101. package/dist/host/native/runtime.d.ts +4 -0
  102. package/dist/host/native/runtime.js +9 -0
  103. package/dist/host/native-reader-context.d.ts +3 -0
  104. package/dist/host/native-reader-context.js +8 -0
  105. package/dist/host/place-clock-selection.d.ts +55 -0
  106. package/dist/host/place-clock-selection.js +124 -0
  107. package/dist/host/ports/local-act.d.ts +10 -0
  108. package/dist/host/ports/local-act.js +78 -6
  109. package/dist/host/ports/place-act.d.ts +46 -0
  110. package/dist/host/ports/place-act.js +169 -0
  111. package/dist/host/process-lifetime.js +8 -3
  112. package/dist/host/provide.d.ts +25 -0
  113. package/dist/host/provide.js +115 -13
  114. package/dist/host/reading-closures.d.ts +15 -1
  115. package/dist/host/reading-closures.js +88 -6
  116. package/dist/host/reading-context.d.ts +29 -0
  117. package/dist/host/reading-context.js +53 -0
  118. package/dist/host/root-clock-source.d.ts +85 -0
  119. package/dist/host/root-clock-source.js +305 -0
  120. package/dist/host/selected-capsules.js +3 -2
  121. package/dist/judge/take.d.ts +2 -0
  122. package/dist/judge/take.js +1 -1
  123. package/dist/land/act.d.ts +26 -0
  124. package/dist/land/act.js +96 -3
  125. package/dist/land/ledger.d.ts +4 -0
  126. package/dist/land/ledger.js +20 -2
  127. package/dist/observe/runner.d.ts +4 -0
  128. package/dist/observe/runner.js +9 -2
  129. package/docs/contracts/cli.md +13 -0
  130. package/package.json +2 -2
  131. package/receipts.bound +607 -567
@@ -0,0 +1,216 @@
1
+ import { fieldNames } from "./reading.js";
2
+ import { sha256 as hash } from "../hash.js";
3
+ /** Instrument-only native route. The caller holds the existing Root lease
4
+ * across selection, cache authorization and this complete bounded child act.
5
+ */
6
+ import { dirname } from 'node:path';
7
+ import { realpathSync } from 'node:fs';
8
+ import { nativeBinaryOrigin } from "./native-binary.js";
9
+ import { selectedHostControl } from "./control-selection.js";
10
+ import { authorityAtEpoch } from '@lapxo/topos/wire';
11
+ import { parsedLine as parse } from "../../fold/claims.js";
12
+ import { authorityFor } from "../../fold/signers.js";
13
+ import { authenticatedRootStanding } from "../admitted-place.js";
14
+ import { nativeExecutorInputs } from "./materialize.js";
15
+ export { materializeNativeRoute } from "./materialize.js";
16
+ import { assertNativeRuntime } from "./runtime.js";
17
+ import { nativeReferenceInputs } from "./references.js";
18
+ import { rootAuthorityProtection } from "../../land/act.js";
19
+ import { currentNativeRoot, inheritedNativeRoot } from "./root.js";
20
+ import { authenticateSignedPort, authenticateNativeProfile, canonicalJson } from "./reading.js";
21
+ import { boundedProcess } from "../process-lifetime.js";
22
+ import { validResponse, CapsuleProcessError } from "../capsule-process.js";
23
+ import { processOf } from "../../observe/runner.js";
24
+ const fail = (why) => { throw Error('REFUSE·native route ' + why); };
25
+ const routes = new WeakMap();
26
+ /** Shared origins are one exact input; individual signed selectors remain separate. */
27
+ export function nativeBinaryOriginInputs(binaries) {
28
+ const seen = new Map(), inputs = [];
29
+ for (const binary of binaries) {
30
+ if (!binary.origin)
31
+ continue;
32
+ const coordinate = 'native/binary-origin/' + binary.origin.coordinate, digest = hash(binary.origin.line), prior = seen.get(coordinate);
33
+ if (prior !== undefined) {
34
+ if (prior !== digest)
35
+ fail('conflicting binary origin input ' + coordinate);
36
+ continue;
37
+ }
38
+ seen.set(coordinate, digest);
39
+ inputs.push([coordinate, digest]);
40
+ }
41
+ return Object.freeze(inputs);
42
+ }
43
+ /** Inputs are semantic logical references. Fixed instrument/host graph and
44
+ * runtime-supervisor chain must also be supplied by the calling identity builder.
45
+ * Locations and lock-token/PID are operational custody, never these inputs.
46
+ */
47
+ export function prepareNativeRoute(store, coordinate, readerMapping) {
48
+ const proof = rootAuthorityProtection(store), current = currentNativeRoot(store, process.pid, proof);
49
+ return prepareAtCurrentRoot(store, coordinate, readerMapping, current);
50
+ }
51
+ /** Private same-call reuse only: callers cannot supply a standing as authority. */
52
+ function prepareAtCurrentRoot(store, coordinate, readerMapping, current) {
53
+ const { root, context } = current, selected = authenticateNativeProfile(root, coordinate), profile = selected.profile;
54
+ // The Root cursor authorizes today's act under the held lease. It is not a
55
+ // semantic input to every program: unrelated Root acts do not reopen it.
56
+ // Exact selected grants and the authenticated reader mapping remain inputs.
57
+ const binaries = authenticatedNativeBinaries(root, profile), control = selectedHostControl(root, profile), identity = hash(canonicalJson([coordinate, selected.line, profile, binaries, readerMapping ?? null, ...(control ? [control] : [])])), inputs = [['native/selection', identity], ...(readerMapping ? [['native/reader-mapping', hash(readerMapping)]] : []), ...profile.selection.members.map(m => ['native/' + m.coordinate, m.digest]), ...binaries.map(b => ['native/binary/' + b.coordinate, hash(b.line)])];
58
+ inputs.push(...nativeBinaryOriginInputs(binaries));
59
+ if (control)
60
+ inputs.push(['native/control-profile', hash(control.line)], ['native/control-worker', control.profile.worker.digest], ['native/control-runtime', control.profile.runtime.digest]);
61
+ const route = { identity, profile, binaries, ...(control ? { control } : {}), inputs: inputs };
62
+ routes.set(route, { store, coordinate, context, profile });
63
+ inputs.push(...nativeExecutorInputs(route));
64
+ if (control && inputs.find(([name]) => name === control.profile.worker.coordinate)?.[1] !== control.profile.worker.digest)
65
+ fail('control worker differs from sealed executor');
66
+ Object.freeze(inputs);
67
+ return Object.freeze(route);
68
+ }
69
+ /** Recheck authority before both a current retained answer and fresh execution.
70
+ * Historical public --check uses the existing receipt-inspection path, not this
71
+ * command authorization route.
72
+ */
73
+ export function assertNativeRouteCurrent(route) {
74
+ const held = routes.get(route);
75
+ if (!held)
76
+ return fail('unknown prepared route');
77
+ const proof = rootAuthorityProtection(held.store), root = inheritedNativeRoot({ store: held.store, parent: process.pid, expected: held.context }, proof);
78
+ const profile = authenticateSignedPort(root, held.coordinate).profile;
79
+ if (canonicalJson(profile) !== canonicalJson(held.profile) || canonicalJson(authenticatedNativeBinaries(root, held.profile)) !== canonicalJson(route.binaries) || canonicalJson(selectedHostControl(root, held.profile) ?? null) !== canonicalJson(route.control ?? null))
80
+ fail('current port changed');
81
+ }
82
+ export function invokeNativeRoute(route, input) {
83
+ const held = routes.get(route);
84
+ if (!held)
85
+ return fail('unknown prepared route');
86
+ assertNativeRouteCurrent(route);
87
+ assertNativeRuntime(input.execution.env, input.execution.flags);
88
+ if (input.execution.context.implementation !== held.profile.selection.archive)
89
+ fail('reading implementation differs');
90
+ nativeReferenceInputs(input.execution.context.inputs, route.inputs);
91
+ if (input.limits.timeoutMs > held.profile.port.timeoutMs || input.limits.responseBytes > held.profile.port.responseBytes)
92
+ fail('reader limits exceed current command grant');
93
+ const proof = rootAuthorityProtection(held.store), frame = { proof, archive: input.archive, locations: input.locations, requests: input.requests };
94
+ const args = [processOf('native'), held.store, held.coordinate, JSON.stringify(held.context), 'reserved', String(held.profile.selection.budget.bytes), 'act', String(process.pid), JSON.stringify(input.execution)];
95
+ const result = boundedProcess(process.execPath, args, JSON.stringify(frame), input.limits, input.execution.cwd, input.execution.env);
96
+ if (result.signal !== null || result.status !== 0)
97
+ throw new CapsuleProcessError('native child exited ' + String(result.status ?? result.signal), result);
98
+ let values;
99
+ try {
100
+ values = JSON.parse(result.stdout.trimEnd());
101
+ }
102
+ catch {
103
+ return fail('native child malformed batch');
104
+ }
105
+ if (!Array.isArray(values) || values.length !== input.requests.length || !values.every((value, index) => validResponse(value, input.requests[index])))
106
+ fail('native child invalid batch');
107
+ assertNativeRouteCurrent(route);
108
+ return values;
109
+ }
110
+ /** This predicate only schedules an outer lease. It grants no command. */
111
+ export function declaredNativeReaderPorts(lines) { return lines.some(line => { const got = parse(line); return got.kind === 'fact' && got.value.fields.scope.startsWith('reader/') && got.value.fields.scope.split('/').at(-1) === 'port' && got.value.fields.value !== 'withdraw'; }); }
112
+ export function prepareNativeReaderRoute(store, reader) {
113
+ const expectedScope = ['reader', reader.module, 'port'].join('/');
114
+ const declared = authenticatedRootStanding(dirname(store));
115
+ if (!declared.some(line => { const got = parse(line); return got.kind === 'fact' && got.value.fields.scope === expectedScope && got.value.fields.value !== 'withdraw'; }))
116
+ return undefined;
117
+ const proof = rootAuthorityProtection(store), currentRoot = currentNativeRoot(store, process.pid, proof), root = currentRoot.root;
118
+ const rows = root.live.filter(line => { const got = parse(line); return got.kind === 'fact' && got.value.fields.scope === expectedScope; });
119
+ if (!rows.length)
120
+ return undefined;
121
+ if (rows.length !== 1)
122
+ fail('ambiguous reader port ' + expectedScope);
123
+ const got = parse(rows[0]);
124
+ if (got.kind !== 'fact')
125
+ return fail('invalid reader port ' + expectedScope);
126
+ const f = got.value.fields;
127
+ if (f.role !== 'writes' || f.form !== 'alphabet' || f.measure !== 'id' || typeof f.value !== 'string' || !/^port\/[^\s]+$/.test(f.value))
128
+ fail('malformed reader port ' + expectedScope);
129
+ const auth = authorityFor(root.history, root.signer, root.eras), signed = auth.of(rows[0]);
130
+ if (signed.kind !== 'admitted' || signed.signer !== root.signer.id)
131
+ fail('reader port is not Root signed ' + expectedScope);
132
+ if (signed.kind !== 'admitted')
133
+ return fail('reader port signature not admitted');
134
+ const signer = auth.admitted.find(one => one.id === signed.signer);
135
+ if (!signer)
136
+ return fail('reader port signer absent');
137
+ const current = authorityAtEpoch(f, signer, root.epoch, expectedScope, 'authority');
138
+ if (current.kind !== 'admitted')
139
+ fail('reader port current eligibility ' + current.why);
140
+ return prepareAtCurrentRoot(store, f.value, rows[0], currentRoot);
141
+ }
142
+ export function nativeRouteStore(route) { const held = routes.get(route); if (!held)
143
+ return fail('unknown prepared route'); return held.store; }
144
+ /** Quiet-pass gate: authenticate grants/cursor only, no image/materialization or
145
+ * program execution. Historical --check remains its separate receipt path. */
146
+ export function assertNativeReaderGrantsCurrent(store) { currentNativeReaderSelections(store); }
147
+ /** Executable kinds come only from current authenticated Root declarations. A
148
+ * matching JSON hint identifies a candidate, but is never permission to execute. */
149
+ export function authenticatedNativeBinaries(root, profile) {
150
+ const out = [], names = new Set();
151
+ const keys = fieldNames;
152
+ for (const line of root.live) {
153
+ const row = parse(line);
154
+ if (row.kind !== 'fact' || !row.value.fields.scope.startsWith('port/'))
155
+ continue;
156
+ let hint;
157
+ try {
158
+ hint = JSON.parse(row.value.fields.about ?? '');
159
+ }
160
+ catch {
161
+ continue;
162
+ }
163
+ if (!['host-native-binary@1', 'host-native-binary-origin@1'].includes(hint?.profile) || hint.selector?.port !== profile.port.coordinate)
164
+ continue;
165
+ const coordinate = row.value.fields.scope, auth = authenticateSignedPort(root, coordinate), p = auth.profile;
166
+ if (keys(p) !== (p.profile === 'host-native-binary-origin@1' ? 'alias,condition,coordinate,entry,limits,origin,profile,selector' : 'alias,condition,coordinate,entry,limits,profile,selector') || p.coordinate !== coordinate || typeof p.alias !== 'string' || !p.alias.length || p.alias.includes('\0') || typeof p.condition !== 'string' || !p.condition.length || keys(p.selector ?? {}) !== 'binding,implementation,port' || p.selector.implementation !== profile.port.implementation || !profile.selection.bindings.some(b => b.id === p.selector.binding))
167
+ fail('binary declaration differs from selected parent ' + coordinate);
168
+ const entry = p.entry, member = profile.selection.members.find(m => m.coordinate === entry?.path);
169
+ if (keys(entry ?? {}) !== 'bytes,digest,path' || !member || entry.path === profile.selection.runtime || member.digest !== entry.digest || !Number.isSafeInteger(entry.bytes) || entry.bytes < 1 || keys(p.limits ?? {}) !== 'bytes' || !Number.isSafeInteger(p.limits.bytes) || p.limits.bytes < entry.bytes || p.limits.bytes > profile.selection.budget.bytes)
170
+ fail('binary outside selected image resources ' + coordinate);
171
+ if (names.has(coordinate))
172
+ fail('ambiguous binary declaration ' + coordinate);
173
+ names.add(coordinate);
174
+ if (entry.digest === profile.selection.members.find(m => m.coordinate === profile.selection.runtime)?.digest)
175
+ fail('binary aliases selected runtime');
176
+ const origin = nativeBinaryOrigin(root, p, auth.line);
177
+ if (origin?.path === realpathSync(process.execPath))
178
+ fail('binary origin aliases actual runtime');
179
+ out.push(Object.freeze({ line: auth.line, coordinate, profile: p, ...(origin ? { origin } : {}) }));
180
+ }
181
+ return Object.freeze(out);
182
+ }
183
+ /** Same semantic image identity for preparation, retained mount and quiet checks. */
184
+ export function nativeMountIdentity(profile, binaries, control) { return hash(canonicalJson(['logical-native-image/2', profile.selection, binaries, ...(control ? [control] : [])])); }
185
+ /** Current eligibility only: no executor graph, image load or materialization. */
186
+ export function currentNativeReaderSelections(store) {
187
+ const declared = authenticatedRootStanding(dirname(store));
188
+ if (!declaredNativeReaderPorts(declared))
189
+ return [];
190
+ const proof = rootAuthorityProtection(store), currentRoot = currentNativeRoot(store, process.pid, proof), root = currentRoot.root;
191
+ const mappings = root.live.flatMap(line => { const row = parse(line); return row.kind === 'fact' && row.value.fields.scope.startsWith('reader/') && row.value.fields.scope.split('/').at(-1) === 'port' ? [{ line, fields: row.value.fields }] : []; });
192
+ const seen = new Set(), auth = authorityFor(root.history, root.signer, root.eras), out = [];
193
+ for (const { line, fields: f } of mappings) {
194
+ if (seen.has(f.scope))
195
+ fail('ambiguous reader port ' + f.scope);
196
+ seen.add(f.scope);
197
+ if (f.role !== 'writes' || f.form !== 'alphabet' || f.measure !== 'id' || typeof f.value !== 'string' || !/^port\/[^\s]+$/.test(f.value))
198
+ fail('malformed reader port ' + f.scope);
199
+ const signed = auth.of(line);
200
+ if (signed.kind !== 'admitted' || signed.signer !== root.signer.id)
201
+ fail('reader port is not Root signed ' + f.scope);
202
+ if (signed.kind !== 'admitted')
203
+ return fail('reader mapping not admitted');
204
+ const signer = auth.admitted.find(one => one.id === signed.signer);
205
+ if (!signer)
206
+ return fail('reader mapping signer absent');
207
+ const current = authorityAtEpoch(f, signer, root.epoch, f.scope, 'authority');
208
+ if (current.kind !== 'admitted')
209
+ fail('reader port current eligibility ' + current.why);
210
+ const { profile } = authenticateNativeProfile(root, f.value);
211
+ const control = selectedHostControl(root, profile);
212
+ out.push({ profile, binaries: authenticatedNativeBinaries(root, profile), ...(control ? { control } : {}) });
213
+ }
214
+ return out;
215
+ }
216
+ export function currentNativeReaderProfiles(store) { return currentNativeReaderSelections(store).map(selection => selection.profile); }
@@ -0,0 +1,4 @@
1
+ /** Environment identity is not permission to bootstrap arbitrary executable
2
+ * preloads. The fixed native entry currently has no ambient loader contract.
3
+ */
4
+ export declare function assertNativeRuntime(environment: Readonly<Record<string, string | undefined>>, flags: readonly string[]): void;
@@ -0,0 +1,9 @@
1
+ /** Environment identity is not permission to bootstrap arbitrary executable
2
+ * preloads. The fixed native entry currently has no ambient loader contract.
3
+ */
4
+ export function assertNativeRuntime(environment, flags) {
5
+ if ((environment.NODE_OPTIONS ?? '').trim() !== '')
6
+ throw Error('REFUSE·native runtime NODE_OPTIONS has no fixed verified loader configuration');
7
+ if (flags.length)
8
+ throw Error('REFUSE·native runtime startup flags differ from the fixed dispatcher');
9
+ }
@@ -0,0 +1,3 @@
1
+ import type { NativeRoute } from './native/route.ts';
2
+ export declare const nativeReaderRoute: () => NativeRoute | undefined;
3
+ export declare function withNativeReaderRoute<T>(route: NativeRoute | undefined, run: () => T): T;
@@ -0,0 +1,8 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ // An opaque instrument selection travels with one observation. A request or a
3
+ // world's response cannot select its executor or replace another reader's port.
4
+ const active = new AsyncLocalStorage();
5
+ export const nativeReaderRoute = active.getStore.bind(active);
6
+ export function withNativeReaderRoute(route, run) {
7
+ return route === undefined ? run() : active.run(route, run);
8
+ }
@@ -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
+ };
@@ -0,0 +1,124 @@
1
+ /** Fixed HOST construction for the opt-in SDK clock profile. No CLI activation,
2
+ * inferred genesis, new wire fields, or packet-supplied signature verifier. */
3
+ import { createHash } from 'node:crypto';
4
+ import { PLACE_ACT_CONTEXT } from '@lapxo/topos/core';
5
+ import { canonical, signedBytes, wireAt } from '@lapxo/topos/wire';
6
+ import { foldClaims, isConfig, fieldOf, parsedLine as parse } from "../fold/claims.js";
7
+ import { eraOf } from "../fold/digests.js";
8
+ import { authorityFor } from "../fold/signers.js";
9
+ import { verifyBytes } from "./adapters/keys/ed25519.js";
10
+ import { verifiesOwnerLine } from "../land/sign.js";
11
+ import { placeActHeads } from "./ports/place-act.js";
12
+ const fail = (why) => { throw Error('REFUSE·clock selection ' + why); };
13
+ const digest = (s) => 'sha256:' + createHash('sha256').update(s).digest('hex');
14
+ const text = (s) => typeof s === 'string' && s.length > 0;
15
+ const clock = (s) => { if (typeof s !== 'string' || !/^(0|[1-9][0-9]*)$/.test(s) || !Number.isSafeInteger(Number(s)))
16
+ fail('unknown clock'); return Number(s); };
17
+ // Exact-byte parsing returns frozen values. Retain only this pure syntax verdict;
18
+ // history, signatures, authority windows, clocks and protected rereads stay fresh.
19
+ const canonicalValues = new WeakSet();
20
+ const f = (line) => { const got = parse(line); if (got.kind !== 'fact')
21
+ return fail('noncanonical history'); if (!canonicalValues.has(got.value)) {
22
+ if (canonical(got.value.fields, got.value.version) !== line)
23
+ fail('noncanonical history');
24
+ canonicalValues.add(got.value);
25
+ } return got.value.fields; };
26
+ /** Root chooses selector and anchors/resources from its authenticated standing.
27
+ * Selection must be ROOT signed; a world's signature grants no host capability. */
28
+ export function hostClockSelection(input) {
29
+ const b = input.resources;
30
+ if (!b || Object.keys(b).sort().join(',') !== 'bytes,lines,places' || Object.values(b).some(n => !Number.isSafeInteger(n) || n < 1))
31
+ fail('missing input resources');
32
+ const inherited = input.inherited;
33
+ if (!inherited?.clock || Object.keys(inherited.clock).sort().join(',') !== 'bytes,placements,policies,proofs,records,signers' || !inherited.storage || Object.keys(inherited.storage).sort().join(',') !== 'acts,bytes' || [...Object.values(inherited.clock), ...Object.values(inherited.storage)].some(n => !Number.isSafeInteger(n) || n < 1))
34
+ fail('missing inherited resource authority');
35
+ if (input.history.length + input.legacyOriginals.length > b.lines)
36
+ fail('input line budget');
37
+ let bytes = 0;
38
+ for (const line of [...input.history, ...input.legacyOriginals]) {
39
+ if (typeof line !== 'string' || line.length > b.bytes || (bytes += Buffer.byteLength(line)) > b.bytes)
40
+ fail('input byte budget');
41
+ }
42
+ const snapshots = new Map();
43
+ for (const original of input.history) {
44
+ const epoch = clock(f(original).epoch ?? fail('history clock absent'));
45
+ let authority = snapshots.get(epoch);
46
+ if (!authority) {
47
+ const before = input.history.filter(l => !isConfig(l) || clock(f(l).epoch ?? fail('history clock absent')) <= epoch);
48
+ authority = authorityFor(before, input.anchor, eraOf(foldClaims(before.filter(l => fieldOf(l, 'scope').startsWith('wire/'))).standing).admitted);
49
+ snapshots.set(epoch, authority);
50
+ }
51
+ if (authority.of(original).kind !== 'admitted')
52
+ fail('history not authenticated at admission');
53
+ }
54
+ const live = foldClaims(input.history.filter(isConfig)).standing.filter(l => fieldOf(l, 'value') !== 'withdraw');
55
+ const ids = live.filter(l => { const p = f(l); return p.scope === input.profileSelector && p.measure === 'id'; });
56
+ if (ids.length !== 1 || f(ids[0]).value !== PLACE_ACT_CONTEXT || f(ids[0]).role !== 'writes' || f(ids[0]).form !== 'alphabet' || f(ids[0]).by !== input.anchor.id)
57
+ fail('missing/ambiguous clock profile id');
58
+ const rows = live.filter(l => { const p = f(l); return p.scope === input.selector && p.measure === 'text'; });
59
+ if (rows.length !== 1)
60
+ fail('missing/ambiguous Root selector');
61
+ const line = rows[0], fields = f(line);
62
+ if (fields.by !== input.anchor.id || fields.role !== 'writes' || fields.form !== 'alphabet' || fields.value !== 'lock' || !verifiesOwnerLine(line, input.anchor.publicKey, input.algorithms))
63
+ fail('selector is not Root signed');
64
+ if (!fields.about || fields.about.length > b.bytes)
65
+ fail('missing selector body');
66
+ let selection;
67
+ try {
68
+ selection = JSON.parse(fields.about);
69
+ }
70
+ catch {
71
+ return fail('selector JSON');
72
+ }
73
+ if (!selection || Object.keys(selection).sort().join(',') !== 'budget,digestAlgorithm,initial,namespace,profile,receiptScope,receiptSigner,regionFamily,storageBudget' || selection.profile !== PLACE_ACT_CONTEXT || selection.digestAlgorithm !== 'sha256' || !text(selection.namespace) || !text(selection.regionFamily) || !text(selection.receiptSigner) || !text(selection.receiptScope) || !Array.isArray(selection.initial) || !selection.initial.length || selection.initial.length > b.places)
74
+ fail('unsupported selector shape');
75
+ if (JSON.stringify(selection) !== fields.about)
76
+ fail('noncanonical selector JSON');
77
+ if (!selection.budget || Object.keys(selection.budget).sort().join(',') !== 'bytes,placements,policies,proofs,records,signers' || Object.values(selection.budget).some(n => !Number.isSafeInteger(n) || n < 1) || !selection.storageBudget || Object.keys(selection.storageBudget).sort().join(',') !== 'acts,bytes' || Object.values(selection.storageBudget).some(n => !Number.isSafeInteger(n) || n < 1))
78
+ fail('missing selected resources');
79
+ for (const name of ['bytes', 'records', 'placements', 'policies', 'proofs', 'signers'])
80
+ if (selection.budget[name] > inherited.clock[name])
81
+ fail('clock resource exceeds inherited ' + name);
82
+ for (const name of ['acts', 'bytes'])
83
+ if (selection.storageBudget[name] > inherited.storage[name])
84
+ fail('storage resource exceeds inherited ' + name);
85
+ if (selection.budget.bytes > b.bytes || selection.storageBudget.bytes > b.bytes || selection.budget.records > b.lines || selection.budget.placements > b.places)
86
+ fail('selected resources exceed input authority');
87
+ const wire = wireAt(live.map(f), Number.MAX_SAFE_INTEGER);
88
+ if (!wire || !wire.digests.has(selection.digestAlgorithm))
89
+ fail('digest algorithm not declared');
90
+ const authority = authorityFor(input.history, input.anchor, input.algorithms);
91
+ if (!input.history.includes(input.authorityCheckpoint) || !isConfig(input.authorityCheckpoint))
92
+ fail('missing authenticated Root authority checkpoint');
93
+ const checkpointEpoch = clock(f(input.authorityCheckpoint).epoch ?? fail('Root authority clock absent'));
94
+ if (snapshots.get(checkpointEpoch)?.of(input.authorityCheckpoint).kind !== 'admitted')
95
+ fail('Root checkpoint not admitted at original epoch');
96
+ const epoch = clock(f(input.authorityCheckpoint).epoch ?? fail('Root authority clock absent'));
97
+ if (input.history.filter(isConfig).some(l => clock(f(l).epoch ?? fail('history clock absent')) > epoch))
98
+ fail('authority checkpoint precedes Root configuration');
99
+ const prefix = digest(input.history.join('\n') + '\n'), head = digest(live.join('\n') + '\n');
100
+ const policies = [...new Set(live.map(digest))].sort();
101
+ if (policies.length > selection.budget.policies)
102
+ fail('actual dependency budget');
103
+ const root = { namespace: selection.namespace, snapshot: digest(JSON.stringify([prefix, head, String(epoch)])), epoch: String(epoch), prefix, head, policies };
104
+ const initialHeads = selection.initial.map(i => { if (!i || !i.receiver || !i.origin || !text(i.receiver.identity) || !text(i.origin.prefix) || !text(i.place))
105
+ fail('unknown initial namespace'); const parts = i.place.split('/'); if (i.place !== '.' && (parts.length < 2 || parts.at(-1) !== 'TARGET.bound' || parts.some(s => s === '' || s === '.' || s === '..')))
106
+ fail('unknown initial namespace'); clock(i.receiver.epoch); clock(i.origin.epoch); return { place: i.place, identity: i.receiver.identity, epoch: i.receiver.epoch }; });
107
+ if (new Set(initialHeads.map(h => h.place)).size !== initialHeads.length)
108
+ fail('duplicate namespace');
109
+ const legacy = new Set(input.legacyOriginals.map(l => digest(signedBytes(f(l)))));
110
+ let heads = [];
111
+ const contract = { budget: selection.budget, root, signers: authority.admitted, places: initialHeads.map(h => h.place), regionFamily: selection.regionFamily, receiptSigner: selection.receiptSigner, receiptScope: selection.receiptScope, digest,
112
+ verifiesRootContext: c => JSON.stringify(c) === JSON.stringify(root),
113
+ verifiesNamespaceAdmission: p => { if (p === '.')
114
+ return true; const parts = p.split('/'); if (parts.length < 2 || parts.pop() !== 'TARGET.bound' || parts.some(s => s === '' || s === '.' || s === '..'))
115
+ return false; const n = parts.join('/'), rows = live.filter(l => f(l).scope === selection.regionFamily + '/' + n); if (rows.length !== 1)
116
+ return false; const v = f(rows[0]); return v.role === 'writes' && v.form === 'alphabet' && v.measure === 'coordinates' && v.value === n + '/**'; },
117
+ verifiesNewLocalPrefix: (p, prefix, epoch, originals) => { const h = heads.find(h => h.place === p), i = selection.initial.find(i => i.place === p); if (!h || !i || originals.some(l => legacy.has(digest(signedBytes(f(l))))))
118
+ return false; return h.identity === i.receiver.identity ? prefix === i.origin.prefix && epoch === i.origin.epoch : prefix === h.identity && clock(epoch) === clock(h.epoch) + 1; },
119
+ verifiesOriginal: (l, s) => verifiesOwnerLine(l, s.publicKey, input.algorithms),
120
+ verifiesProof: (text, signature, s) => verifyBytes(text, s.publicKey, signature, input.algorithms), };
121
+ heads = input.authenticatedReceiverHeads ?? placeActHeads(input.store, initialHeads, { contract, historical: input.historical }, selection.storageBudget);
122
+ const trust = { contract, initialHeads, current: () => { const fresh = hostClockSelection({ ...input, ...input.readCurrent(), authenticatedReceiverHeads: undefined }); return { root: fresh.trust.contract.root, heads: placeActHeads(input.store, fresh.trust.initialHeads, fresh.trust, fresh.selection.storageBudget) }; }, historical: input.historical };
123
+ return { selection, trust };
124
+ }
@@ -15,11 +15,21 @@ export declare const actLines: (bytes: string) => readonly string[];
15
15
  * algorithm, not a new selection of the place's wire.
16
16
  */
17
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;
18
23
  /** Committed segments are part of this ledger, not a second authority source. */
19
24
  export declare function committedLocalActs(store: string): readonly {
20
25
  identity: string;
21
26
  bundle: LocalActBundle;
22
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;
23
33
  /** A public result is available only as a verified committed pair.
24
34
  * No writer-tail guess, newest-result selection or implicit authority.
25
35
  */
@@ -1,5 +1,7 @@
1
+ import { hashText } from "../hash.js";
1
2
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
3
  import { join } from 'node:path';
4
+ import { createHash } from 'node:crypto';
3
5
  import { requireEffects } from "../read-only.js";
4
6
  import { underTheRegions } from "../../land/act.js";
5
7
  import { publishBundle, nativePublicationStorage } from "./publication.js";
@@ -50,17 +52,78 @@ export function intactLocalAct(identity, bundle) {
50
52
  placements: bundle.placements.map(p => ({ place: p.place, records: actLines(p.records) })),
51
53
  }) === identity;
52
54
  }
55
+ /** One last integrity result only. Every call rereads all exact current bytes;
56
+ * signatures, coverage and current authority are never retained here. */
57
+ let lastIntactInventory;
58
+ /** Internal integrity key only, not an act identity or a wire profile. */
59
+ export function committedInventoryFingerprint(rows) {
60
+ const hash = createHash('sha256');
61
+ const text = hashText.bind(undefined, hash);
62
+ text('committed-integrity-inventory');
63
+ text(String(rows.length));
64
+ for (const { identity, bundle } of rows) {
65
+ text(identity);
66
+ text(bundle.records);
67
+ text(bundle.receipt);
68
+ if (bundle.placements === undefined) {
69
+ text('absent');
70
+ continue;
71
+ }
72
+ text('present');
73
+ text(String(bundle.placements.length));
74
+ for (const placement of bundle.placements) {
75
+ text(placement.place);
76
+ text(placement.records);
77
+ }
78
+ }
79
+ return hash.digest('hex');
80
+ }
53
81
  /** Committed segments are part of this ledger, not a second authority source. */
54
82
  export function committedLocalActs(store) {
83
+ const observed = observationAt(store);
84
+ if (observed)
85
+ return observed.rows;
86
+ return readCommittedLocalActs(store);
87
+ }
88
+ let committedObservation;
89
+ function observationAt(store) { for (let one = committedObservation; one; one = one.parent)
90
+ if (one.store === store)
91
+ return one; return undefined; }
92
+ /** Fixed host observation only: one synchronous, coherent committed inventory.
93
+ * It supplies no authority, verifier or async lifetime. Each outer invocation
94
+ * rereads exact bodies before entering; current authorization still runs inside.
95
+ */
96
+ export function withCommittedLocalActObservation(store, body) {
97
+ const prior = committedObservation, existing = observationAt(store);
98
+ const rows = existing?.rows ?? Object.freeze(readCommittedLocalActs(store).map(row => Object.freeze({ identity: row.identity, bundle: Object.freeze({ ...row.bundle, ...(row.bundle.placements ? { placements: Object.freeze(row.bundle.placements.map(p => Object.freeze({ ...p }))) } : {}) }) })));
99
+ committedObservation = { store, rows, parent: prior };
100
+ try {
101
+ const result = body();
102
+ if (result !== null && (typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function')
103
+ throw Error('REFUSE·act committed observation must be synchronous');
104
+ return result;
105
+ }
106
+ finally {
107
+ committedObservation = prior;
108
+ }
109
+ }
110
+ function readCommittedLocalActs(store) {
55
111
  const root = join(store, 'ledger', 'acts');
56
112
  if (!existsSync(root))
57
113
  return [];
58
- return readdirSync(root).filter(name => name.startsWith('committed-')).map(name => {
59
- const identity = decodeURIComponent(name.slice(10)), bundle = read(join(root, name));
60
- if (!intactLocalAct(identity, bundle))
61
- throw Error('REFUSE·act committed journal seal does not verify');
62
- return { identity, bundle };
63
- }).sort((a, b) => {
114
+ const rows = readdirSync(root).filter(name => name.startsWith('committed-')).map(name => ({
115
+ identity: decodeURIComponent(name.slice(10)), bundle: read(join(root, name)),
116
+ }));
117
+ // Ordered tuple framing commits every record, receipt and placement. No path,
118
+ // mtime, authority snapshot or caller-supplied seal substitutes for bytes.
119
+ const fingerprint = committedInventoryFingerprint(rows);
120
+ if (fingerprint !== lastIntactInventory) {
121
+ for (const { identity, bundle } of rows)
122
+ if (!intactLocalAct(identity, bundle))
123
+ throw Error('REFUSE·act committed journal seal does not verify');
124
+ lastIntactInventory = fingerprint;
125
+ }
126
+ return rows.sort((a, b) => {
64
127
  const epoch = (one) => { const p = parse(one.receipt.trim()); return p.kind === 'fact' ? Number(p.value.fields['epoch']) : 0; };
65
128
  return epoch(a.bundle) - epoch(b.bundle);
66
129
  });
@@ -70,6 +133,15 @@ export function committedLocalActs(store) {
70
133
  */
71
134
  export function localActResult(store, identity, verify) {
72
135
  const at = atOf(store, identity);
136
+ const observed = observationAt(store);
137
+ if (observed) {
138
+ const bundle = observed.rows.find(row => row.identity === identity)?.bundle;
139
+ if (!bundle)
140
+ return undefined;
141
+ if (verify(identity, bundle) !== true)
142
+ throw Error('REFUSE·act committed result does not verify');
143
+ return bundle;
144
+ }
73
145
  if (!existsSync(at))
74
146
  return undefined;
75
147
  const bundle = read(at);
@@ -0,0 +1,46 @@
1
+ import { type TrustedClockContract, type PlaceReceipt, type RootContext } from '@lapxo/topos/core';
2
+ import { type PublicationStorage } from './publication.ts';
3
+ export interface PlaceActBudget {
4
+ readonly acts: number;
5
+ readonly bytes: number;
6
+ }
7
+ export interface PlaceActHead {
8
+ readonly place: string;
9
+ readonly identity: string;
10
+ readonly epoch: string;
11
+ }
12
+ export interface PlaceActSnapshot {
13
+ readonly root: RootContext;
14
+ readonly heads: readonly PlaceActHead[];
15
+ }
16
+ /** Fixed instrument construction, never supplied by world JSON. Historical
17
+ * authentication permits inspection only; current() is read under Root guards. */
18
+ export interface PlaceActTrust {
19
+ readonly contract: TrustedClockContract;
20
+ readonly initialHeads: readonly PlaceActHead[];
21
+ readonly current: () => PlaceActSnapshot;
22
+ readonly historical: (encoded: string) => TrustedClockContract;
23
+ }
24
+ export interface PlaceActBundle {
25
+ readonly context: string;
26
+ readonly receipts: readonly PlaceReceipt[];
27
+ readonly originals: readonly (readonly string[])[];
28
+ }
29
+ /** Historical result reads never produce a current authorization permit. */
30
+ export declare function placeActResult(store: string, id: string, trust: Pick<PlaceActTrust, 'historical'>, limits: PlaceActBudget): PlaceActBundle | undefined;
31
+ /** Each receiver chain advances independently. Initial heads are explicit,
32
+ * authenticated host inputs, not inferred zero or legacy global epochs. */
33
+ export declare function placeActHeads(store: string, initial: readonly PlaceActHead[], trust: Pick<PlaceActTrust, 'historical' | 'contract'>, limits: PlaceActBudget): readonly PlaceActHead[];
34
+ /** Context, exact original bytes and complete signed receipts have one visibility
35
+ * point. Real Root guard exclusion spans final authority/head comparison and
36
+ * publication; this does not activate a CLI profile or reinterpret local-act@1. */
37
+ export declare function commitPlaceAct(store: string, encoded: string, receipts: readonly PlaceReceipt[], trust: PlaceActTrust, limits: PlaceActBudget, storage?: PublicationStorage): Promise<PlaceActBundle & {
38
+ readonly kind: 'landed' | 'once';
39
+ readonly identity: string;
40
+ }>;
41
+ /** Enumerates only committed, historically authenticated carriers. Namespace
42
+ * projection is a caller's separate pure operation, never current permission. */
43
+ export declare function committedPlaceActResults(store: string, trust: Pick<PlaceActTrust, 'historical'>, limits: PlaceActBudget): readonly {
44
+ identity: string;
45
+ bundle: PlaceActBundle;
46
+ }[];