@lapxo/bound 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -20
- package/TARGET.bound +70 -27
- package/assets/wasm/guest.js +4 -0
- package/assets/wasm/manifest.json +27 -0
- package/assets/wasm/runtime.js +6 -0
- package/assets/wasm/runtime.wasm +0 -0
- package/dist/cli/accept.js +83 -59
- package/dist/cli/act-result.d.ts +9 -0
- package/dist/cli/act-result.js +68 -13
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.js +5 -2
- package/dist/cli/custodian.d.ts +1 -0
- package/dist/cli/custodian.js +29 -0
- package/dist/cli/native-descendant.d.ts +1 -0
- package/dist/cli/native-descendant.js +19 -0
- package/dist/cli/native.d.ts +1 -0
- package/dist/cli/native.js +60 -0
- package/dist/cli/observe.js +9 -2
- package/dist/cli/pass.js +9 -4
- package/dist/cli/place.d.ts +2 -0
- package/dist/cli/place.js +7 -4
- package/dist/cli/standing-result.js +7 -26
- package/dist/cli/verb.js +65 -9
- package/dist/engine/index.d.ts +11 -0
- package/dist/engine/index.js +9 -0
- package/dist/engine/object.d.ts +17 -0
- package/dist/engine/object.js +38 -0
- package/dist/fold/admission-history.js +2 -2
- package/dist/fold/claims.d.ts +4 -0
- package/dist/fold/claims.js +23 -3
- package/dist/fold/closed.d.ts +1 -1
- package/dist/fold/closed.js +36 -7
- package/dist/fold/digests.d.ts +1 -1
- package/dist/fold/digests.js +6 -4
- package/dist/fold/evidence.d.ts +1 -1
- package/dist/fold/kept.js +2 -1
- package/dist/fold/object.d.ts +3 -17
- package/dist/fold/object.js +2 -38
- package/dist/fold/observed.d.ts +1 -0
- package/dist/fold/observed.js +54 -9
- package/dist/fold/readers.d.ts +4 -3
- package/dist/fold/readers.js +18 -13
- package/dist/fold/resolved.js +12 -2
- package/dist/fold/signers.js +2 -2
- package/dist/host/adapters/modules/javascript.d.ts +2 -0
- package/dist/host/adapters/modules/javascript.js +8 -6
- package/dist/host/admitted-place.d.ts +5 -0
- package/dist/host/admitted-place.js +57 -0
- package/dist/host/capsule-process.d.ts +1 -1
- package/dist/host/capsule-process.js +4 -1
- package/dist/host/capsule.d.ts +4 -0
- package/dist/host/capsule.js +16 -2
- package/dist/host/cell-receipts.js +1 -1
- package/dist/host/contextual-place.d.ts +59 -0
- package/dist/host/contextual-place.js +77 -0
- package/dist/host/hash.d.ts +7 -0
- package/dist/host/hash.js +6 -0
- package/dist/host/io.d.ts +2 -0
- package/dist/host/io.js +2 -0
- package/dist/host/lifetime.js +61 -22
- package/dist/host/native/bounded-file.d.ts +8 -0
- package/dist/host/native/bounded-file.js +32 -0
- package/dist/host/native/command.d.ts +36 -0
- package/dist/host/native/command.js +91 -0
- package/dist/host/native/control-bridge-worker.d.ts +1 -0
- package/dist/host/native/control-bridge-worker.js +88 -0
- package/dist/host/native/control-bridge.d.ts +30 -0
- package/dist/host/native/control-bridge.js +110 -0
- package/dist/host/native/control-selection.d.ts +29 -0
- package/dist/host/native/control-selection.js +32 -0
- package/dist/host/native/custody.d.ts +5 -0
- package/dist/host/native/custody.js +91 -0
- package/dist/host/native/deadline.d.ts +12 -0
- package/dist/host/native/deadline.js +28 -0
- package/dist/host/native/descendants.d.ts +1 -0
- package/dist/host/native/descendants.js +528 -0
- package/dist/host/native/generated-custodian.d.ts +122 -0
- package/dist/host/native/generated-custodian.js +267 -0
- package/dist/host/native/generated.d.ts +217 -0
- package/dist/host/native/generated.js +379 -0
- package/dist/host/native/image.d.ts +36 -0
- package/dist/host/native/image.js +45 -0
- package/dist/host/native/jobs.d.ts +75 -0
- package/dist/host/native/jobs.js +148 -0
- package/dist/host/native/loader.d.ts +80 -0
- package/dist/host/native/loader.js +269 -0
- package/dist/host/native/materialize.d.ts +22 -0
- package/dist/host/native/materialize.js +175 -0
- package/dist/host/native/module-closure.d.ts +27 -0
- package/dist/host/native/module-closure.js +99 -0
- package/dist/host/native/module-syntax.d.ts +8 -0
- package/dist/host/native/module-syntax.js +63 -0
- package/dist/host/native/native-binary.d.ts +54 -0
- package/dist/host/native/native-binary.js +115 -0
- package/dist/host/native/native-program.d.ts +56 -0
- package/dist/host/native/native-program.js +72 -0
- package/dist/host/native/node-jobs.d.ts +84 -0
- package/dist/host/native/node-jobs.js +187 -0
- package/dist/host/native/node-launch.d.ts +72 -0
- package/dist/host/native/node-launch.js +159 -0
- package/dist/host/native/preload.d.ts +1 -0
- package/dist/host/native/preload.js +45 -0
- package/dist/host/native/reading.d.ts +91 -0
- package/dist/host/native/reading.js +249 -0
- package/dist/host/native/receipt-environment.d.ts +3 -0
- package/dist/host/native/receipt-environment.js +41 -0
- package/dist/host/native/references.d.ts +5 -0
- package/dist/host/native/references.js +30 -0
- package/dist/host/native/root.d.ts +17 -0
- package/dist/host/native/root.js +37 -0
- package/dist/host/native/route.d.ts +66 -0
- package/dist/host/native/route.js +216 -0
- package/dist/host/native/runtime.d.ts +4 -0
- package/dist/host/native/runtime.js +9 -0
- package/dist/host/native-reader-context.d.ts +3 -0
- package/dist/host/native-reader-context.js +8 -0
- package/dist/host/objects.d.ts +2 -1
- package/dist/host/objects.js +24 -4
- package/dist/host/place-clock-selection.d.ts +55 -0
- package/dist/host/place-clock-selection.js +124 -0
- package/dist/host/ports/local-act.d.ts +10 -0
- package/dist/host/ports/local-act.js +78 -6
- package/dist/host/ports/place-act.d.ts +46 -0
- package/dist/host/ports/place-act.js +169 -0
- package/dist/host/process-lifetime.js +8 -3
- package/dist/host/provide.d.ts +25 -0
- package/dist/host/provide.js +115 -13
- package/dist/host/reading-closures.d.ts +17 -1
- package/dist/host/reading-closures.js +98 -18
- package/dist/host/reading-context.d.ts +29 -0
- package/dist/host/reading-context.js +53 -0
- package/dist/host/reference-authenticated.d.ts +28 -0
- package/dist/host/reference-authenticated.js +84 -0
- package/dist/host/reference-native.d.ts +10 -0
- package/dist/host/reference-native.js +50 -0
- package/dist/host/reference-profile.d.ts +23 -0
- package/dist/host/reference-profile.js +62 -0
- package/dist/host/reference.d.ts +6 -0
- package/dist/host/reference.js +24 -0
- package/dist/host/root-clock-source.d.ts +85 -0
- package/dist/host/root-clock-source.js +305 -0
- package/dist/host/selected-capsules.js +3 -2
- package/dist/host/selected-topos.d.ts +1 -1
- package/dist/host/selected-topos.js +9 -1
- package/dist/land/act.d.ts +26 -0
- package/dist/land/act.js +96 -3
- package/dist/land/ledger.d.ts +4 -0
- package/dist/land/ledger.js +20 -2
- package/dist/observe/regions.d.ts +4 -0
- package/dist/observe/regions.js +109 -1
- package/dist/observe/runner.d.ts +4 -0
- package/dist/observe/runner.js +9 -2
- package/dist/wasm/adapter.d.ts +51 -0
- package/dist/wasm/adapter.js +180 -0
- package/dist/wasm/node-worker.d.ts +1 -0
- package/dist/wasm/node-worker.js +13 -0
- package/dist/wasm/object.d.ts +8 -0
- package/dist/wasm/object.js +76 -0
- package/dist/wasm/worker.d.ts +1 -0
- package/dist/wasm/worker.js +7 -0
- package/docs/contracts/authenticated-reference.publication.json +25 -0
- package/docs/contracts/cli.md +13 -0
- package/docs/contracts/engine-object-projection.publication.json +25 -0
- package/docs/contracts/engine.md +39 -0
- package/docs/contracts/native-publication.inventory.json +8 -0
- package/docs/contracts/optional-wasm-adapter.publication.json +25 -0
- package/docs/contracts/publication-additions.inventory.json +8 -0
- package/docs/contracts/reference-port.md +33 -0
- package/docs/contracts/reference-port.publication.json +25 -0
- package/licenses/wasm/bound.txt +21 -0
- package/licenses/wasm/fast-text-encoding.txt +201 -0
- package/licenses/wasm/jitl-quickjs-ffi-types.txt +21 -0
- package/licenses/wasm/jitl-quickjs-wasmfile-release-sync.txt +47 -0
- package/licenses/wasm/obligations.txt +21 -0
- package/licenses/wasm/quickjs-emscripten-core.txt +21 -0
- package/licenses/wasm/topos.txt +21 -0
- package/package.json +94 -4
- package/receipts.bound +7672 -573
- package/samples/authenticated-reference/no.json +31 -0
- package/samples/authenticated-reference/yes/editorial-state/TARGET.bound +57 -0
- package/samples/authenticated-reference/yes/editorial-state/inputs/arrival.json +1 -0
- package/samples/authenticated-reference/yes/editorial-state/inputs/coverage.json +1 -0
- package/samples/authenticated-reference/yes/editorial-state/inputs/era.json +1 -0
- package/samples/authenticated-reference/yes/editorial-state/inputs/order.json +1 -0
- package/samples/authenticated-reference/yes/editorial-state/inputs/signature.json +1 -0
- package/samples/authenticated-reference/yes/editorial-state/inputs/transport.arrival.json +1 -0
- package/samples/authenticated-reference/yes/editorial-state/inputs/transport.coverage.json +1 -0
- package/samples/authenticated-reference/yes/editorial-state/inputs/transport.era.json +1 -0
- package/samples/authenticated-reference/yes/editorial-state/inputs/transport.order.json +1 -0
- package/samples/authenticated-reference/yes/editorial-state/inputs/transport.signature.json +1 -0
- package/samples/authenticated-reference/yes/editorial-state/inputs/transport.yes.json +1 -0
- package/samples/authenticated-reference/yes/editorial-state/inputs/yes.json +1 -0
- package/samples/authenticated-reference/yes/weather-reading/TARGET.bound +57 -0
- package/samples/authenticated-reference/yes/weather-reading/inputs/arrival.json +1 -0
- package/samples/authenticated-reference/yes/weather-reading/inputs/coverage.json +1 -0
- package/samples/authenticated-reference/yes/weather-reading/inputs/era.json +1 -0
- package/samples/authenticated-reference/yes/weather-reading/inputs/order.json +1 -0
- package/samples/authenticated-reference/yes/weather-reading/inputs/signature.json +1 -0
- package/samples/authenticated-reference/yes/weather-reading/inputs/transport.arrival.json +1 -0
- package/samples/authenticated-reference/yes/weather-reading/inputs/transport.coverage.json +1 -0
- package/samples/authenticated-reference/yes/weather-reading/inputs/transport.era.json +1 -0
- package/samples/authenticated-reference/yes/weather-reading/inputs/transport.order.json +1 -0
- package/samples/authenticated-reference/yes/weather-reading/inputs/transport.signature.json +1 -0
- package/samples/authenticated-reference/yes/weather-reading/inputs/transport.yes.json +1 -0
- package/samples/authenticated-reference/yes/weather-reading/inputs/yes.json +1 -0
- package/samples/authenticated-reference/yes.json +19 -0
- package/samples/engine-alphabet/TARGET.bound +29 -0
- package/samples/engine-alphabet/inputs/sample.json +323 -0
- package/samples/engine-alphabet/package.json +1 -0
- package/samples/engine-alphabet/reader.js +1457 -0
- package/samples/engine-alphabet/receipts.bound +12 -0
- package/samples/engine-interval/TARGET.bound +29 -0
- package/samples/engine-interval/inputs/sample.json +277 -0
- package/samples/engine-interval/package.json +1 -0
- package/samples/engine-interval/reader.js +1457 -0
- package/samples/engine-interval/receipts.bound +12 -0
- package/samples/no/engine-order/TARGET.bound +29 -0
- package/samples/no/engine-order/inputs/sample.json +277 -0
- package/samples/no/engine-order/package.json +1 -0
- package/samples/no/engine-order/reader.js +1457 -0
- package/samples/reference-port/no/catalog/standing.json +1 -0
- package/samples/reference-port/no/catalog/transport.json +1 -0
- package/samples/reference-port/no/route-notes/standing.json +1 -0
- package/samples/reference-port/no/route-notes/transport.json +1 -0
- package/samples/reference-port/yes/catalog/TARGET.bound +33 -0
- package/samples/reference-port/yes/catalog/standing.json +1 -0
- package/samples/reference-port/yes/catalog/transport.json +1 -0
- package/samples/reference-port/yes/route-notes/TARGET.bound +33 -0
- package/samples/reference-port/yes/route-notes/standing.json +1 -0
- package/samples/reference-port/yes/route-notes/transport.json +1 -0
- package/samples/wasm/editorial-notes/TARGET.bound +65 -0
- package/samples/wasm/editorial-notes/esm.bound +59 -0
- package/samples/wasm/editorial-notes/no-deadline.bound +65 -0
- package/samples/wasm/editorial-notes/no-memory.bound +65 -0
- package/samples/wasm/editorial-notes/no-missing-limit.bound +64 -0
- package/samples/wasm/editorial-notes/receipts.bound +1 -0
- package/samples/wasm/water-level/TARGET.bound +65 -0
- package/samples/wasm/water-level/esm.bound +59 -0
- package/samples/wasm/water-level/no-deadline.bound +65 -0
- package/samples/wasm/water-level/no-memory.bound +65 -0
- package/samples/wasm/water-level/no-missing-limit.bound +64 -0
- package/samples/wasm/water-level/receipts.bound +1 -0
- package/vectors/engine-object.cli.json +548 -0
- package/vectors/native/authenticated-reference-formation.vector.json +53 -0
- package/vectors/native/authenticated-reference.cli.json +157 -0
- package/vectors/native/authenticated-reference.vector.json +440 -0
- package/vectors/reference-port.cli.json +688 -0
- package/vectors/wasm-object.cli.json +452 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Signer } from '@lapxo/topos/wire';
|
|
2
|
+
import type { Request, Response } from '@lapxo/topos/contract';
|
|
3
|
+
import type { Asked } from '@lapxo/topos/capsule';
|
|
4
|
+
import type { CommandPort } from '@lapxo/topos/contract';
|
|
5
|
+
import { type NodeImage, type RootJobCustody } from './node-jobs.ts';
|
|
6
|
+
import type { Handoff, Proposal } from './jobs.ts';
|
|
7
|
+
export declare const canonicalJson: (value: unknown) => string;
|
|
8
|
+
export interface RootStanding {
|
|
9
|
+
readonly history: readonly string[];
|
|
10
|
+
readonly live: readonly string[];
|
|
11
|
+
readonly signer: Signer;
|
|
12
|
+
readonly eras: readonly string[];
|
|
13
|
+
readonly epoch: number;
|
|
14
|
+
readonly budget: {
|
|
15
|
+
readonly lines: number;
|
|
16
|
+
readonly bytes: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface PortProfile {
|
|
20
|
+
readonly profile: 'host-native-reading@1';
|
|
21
|
+
readonly port: CommandPort;
|
|
22
|
+
readonly selection: {
|
|
23
|
+
readonly archive: string;
|
|
24
|
+
readonly entry: string;
|
|
25
|
+
readonly reader: string;
|
|
26
|
+
readonly loaderConfig: string;
|
|
27
|
+
readonly runtime: string;
|
|
28
|
+
readonly preload: string | null;
|
|
29
|
+
readonly nativePreloadMembers: readonly string[];
|
|
30
|
+
readonly condition: string;
|
|
31
|
+
readonly budget: Handoff['budget'];
|
|
32
|
+
readonly members: readonly {
|
|
33
|
+
coordinate: string;
|
|
34
|
+
digest: string;
|
|
35
|
+
}[];
|
|
36
|
+
readonly bindings: Handoff['bindings'];
|
|
37
|
+
readonly loads: Handoff['loads'];
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export interface NativeReadingModule {
|
|
41
|
+
readonly commands?: (asked: Asked) => readonly Proposal[];
|
|
42
|
+
readonly receipt?: (asked: Asked) => readonly unknown[];
|
|
43
|
+
readonly render?: (asked: Asked) => readonly string[];
|
|
44
|
+
}
|
|
45
|
+
export interface NativeReadingHandle {
|
|
46
|
+
readonly identity: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function authenticateSignedPort(root: RootStanding, coordinate: string): {
|
|
49
|
+
line: string;
|
|
50
|
+
profile: unknown;
|
|
51
|
+
};
|
|
52
|
+
export interface AuthenticatedNativeSelection {
|
|
53
|
+
readonly identity: string;
|
|
54
|
+
}
|
|
55
|
+
/** One synchronous authentication supplies both the opaque boundary and its
|
|
56
|
+
* immutable signed profile. Fresh Root checks still run at load and answer. */
|
|
57
|
+
export declare function authenticateNativeProfile(root: RootStanding, coordinate: string): {
|
|
58
|
+
readonly selection: AuthenticatedNativeSelection;
|
|
59
|
+
readonly line: string;
|
|
60
|
+
readonly profile: PortProfile;
|
|
61
|
+
};
|
|
62
|
+
export declare function validateSignedNativeProfile(root: RootStanding, coordinate: string, expected: PortProfile): AuthenticatedNativeSelection;
|
|
63
|
+
/** Root must hold its real authority protection across the callback. A guard
|
|
64
|
+
* method is instrument code, never supplied by a command or its JSON profile. */
|
|
65
|
+
export interface RootAuthorityGuard {
|
|
66
|
+
withCurrent<T>(body: (root: RootStanding) => Promise<T>): Promise<T>;
|
|
67
|
+
}
|
|
68
|
+
/** Fixed instrument loader, never a callback supplied by module/request/profile.
|
|
69
|
+
* Root's REAL await-safe lease must cover verification, load and completion.
|
|
70
|
+
* The handoff image is verified before the fixed loader receives an entry. */
|
|
71
|
+
export declare function loadSignedNativeReading(selection: AuthenticatedNativeSelection, resolved: {
|
|
72
|
+
profile: PortProfile;
|
|
73
|
+
handoff: Handoff;
|
|
74
|
+
image: NodeImage;
|
|
75
|
+
archive: Uint8Array;
|
|
76
|
+
custody?: RootJobCustody;
|
|
77
|
+
}, guard: RootAuthorityGuard): Promise<NativeReadingHandle>;
|
|
78
|
+
export declare function answerNativeReading(handle: NativeReadingHandle, requests: readonly Request[], self: string, options: {
|
|
79
|
+
phase: 'act' | 'selection' | 'help' | 'check' | 'preview' | 'sign';
|
|
80
|
+
signal?: AbortSignal;
|
|
81
|
+
guard: RootAuthorityGuard;
|
|
82
|
+
}): Promise<readonly Response[]>;
|
|
83
|
+
/** Executable resources belong to the currently authenticated parent command
|
|
84
|
+
* declaration, not to the separate authority-history/clock parsing budget. */
|
|
85
|
+
export declare function nativeParentInputBytes(root: RootStanding, parent: {
|
|
86
|
+
port: string;
|
|
87
|
+
}): number;
|
|
88
|
+
/** Exact schema field names, shared by the host's selected native formats. */
|
|
89
|
+
export declare function fieldNames(value: object): string;
|
|
90
|
+
/** Retain each verifier's private map and return a fresh canonical projection. */
|
|
91
|
+
export declare function retainedProjection<H extends object, T>(verified: WeakMap<H, T>, fail: (why: string) => never): (handle: H) => T;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { sha256 as hash } from "../hash.js";
|
|
2
|
+
/** Host profile authentication and bounded executable input handoff. */
|
|
3
|
+
import { extname } from 'node:path';
|
|
4
|
+
import { gunzipSync } from 'node:zlib';
|
|
5
|
+
import { readBoundedFile } from "./bounded-file.js";
|
|
6
|
+
import { members as archiveMembers } from "../archive.js";
|
|
7
|
+
import { installBoundary } from "./loader.js";
|
|
8
|
+
import { nativeRootJobCustody } from "./custody.js";
|
|
9
|
+
import { mountNativeImage } from "./image.js";
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
11
|
+
import { canonical } from '@lapxo/topos/wire';
|
|
12
|
+
import { parsedLine as parse } from "../../fold/claims.js";
|
|
13
|
+
import { answer } from '@lapxo/topos/contract';
|
|
14
|
+
import { authorityFor } from "../../fold/signers.js";
|
|
15
|
+
// Public SDK eligibility implementation; no private SDK module import.
|
|
16
|
+
import { authorityAtEpoch } from '@lapxo/topos/wire';
|
|
17
|
+
import { scheduleDeadline } from "./deadline.js";
|
|
18
|
+
import { prepareNodeImage, plannedJobs, executeJobs } from "./node-jobs.js";
|
|
19
|
+
const fail = (why) => { throw Error('REFUSE·native reading ' + why); };
|
|
20
|
+
export const canonicalJson = (value) => JSON.stringify(order(value));
|
|
21
|
+
const order = (value) => Array.isArray(value) ? value.map(order) : value !== null && typeof value === 'object' ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => [k, order(v)])) : value;
|
|
22
|
+
const prepared = new WeakMap();
|
|
23
|
+
export function authenticateSignedPort(root, coordinate) {
|
|
24
|
+
if (!root || !Number.isSafeInteger(root.epoch) || root.epoch < 0 || !Number.isSafeInteger(root.budget?.lines) || root.budget.lines < 1 || !Number.isSafeInteger(root.budget.bytes) || root.budget.bytes < 1)
|
|
25
|
+
fail('missing authority/budget');
|
|
26
|
+
const retained = [...new Set([...root.history, ...root.live])];
|
|
27
|
+
if (retained.length > root.budget.lines)
|
|
28
|
+
fail('authority line budget');
|
|
29
|
+
let bytes = 0;
|
|
30
|
+
for (const line of retained) {
|
|
31
|
+
if (typeof line !== 'string' || (bytes += Buffer.byteLength(line)) > root.budget.bytes)
|
|
32
|
+
fail('authority byte budget');
|
|
33
|
+
}
|
|
34
|
+
if (!/^port\/[^\s]+$/.test(coordinate))
|
|
35
|
+
fail('selection is not a command port');
|
|
36
|
+
const rows = root.live.filter(line => { const got = parse(line); return got.kind === 'fact' && got.value.fields.scope === coordinate; });
|
|
37
|
+
if (rows.length !== 1)
|
|
38
|
+
fail('missing/ambiguous signed declaration ' + coordinate);
|
|
39
|
+
const line = rows[0];
|
|
40
|
+
if (!root.history.includes(line))
|
|
41
|
+
fail('port absent from authenticated admission history');
|
|
42
|
+
const got = parse(line);
|
|
43
|
+
if (got.kind !== 'fact')
|
|
44
|
+
return fail('port not a wire record');
|
|
45
|
+
const f = got.value.fields;
|
|
46
|
+
if (f.role !== 'writes' || f.form !== 'alphabet' || f.measure !== 'text' || f.value !== 'lock' || typeof f.about !== 'string')
|
|
47
|
+
fail('unknown port projection ' + coordinate);
|
|
48
|
+
const auth = authorityFor(root.history, root.signer, root.eras), verdict = auth.of(line);
|
|
49
|
+
if (verdict.kind !== 'admitted')
|
|
50
|
+
fail('port signature/coverage ' + verdict.why);
|
|
51
|
+
const signerId = verdict.kind === 'admitted' ? verdict.signer : fail('signature not admitted');
|
|
52
|
+
const signers = auth.admitted.filter(s => s.id === signerId);
|
|
53
|
+
if (signers.length !== 1)
|
|
54
|
+
fail('ambiguous port signer');
|
|
55
|
+
if (root.history.some(line => { const p = parse(line); return p.kind === 'fact' && p.value.fields.epoch !== undefined && (!Number.isSafeInteger(Number(p.value.fields.epoch)) || Number(p.value.fields.epoch) > root.epoch); }))
|
|
56
|
+
fail('authority prefix exceeds current epoch');
|
|
57
|
+
const current = authorityAtEpoch(f, signers[0], root.epoch, coordinate, 'authority');
|
|
58
|
+
if (current.kind !== 'admitted')
|
|
59
|
+
fail('current port authority ' + current.why);
|
|
60
|
+
let value;
|
|
61
|
+
try {
|
|
62
|
+
value = JSON.parse(f.about);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return fail('port JSON invalid');
|
|
66
|
+
}
|
|
67
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || canonicalJson(value) !== f.about)
|
|
68
|
+
fail('port profile invalid/noncanonical');
|
|
69
|
+
return { line, profile: value };
|
|
70
|
+
}
|
|
71
|
+
function selected(root, coordinate) {
|
|
72
|
+
const got = authenticateSignedPort(root, coordinate), profile = got.profile;
|
|
73
|
+
const line = got.line;
|
|
74
|
+
if (!profile.port || !profile.selection || typeof profile.selection !== 'object' || Array.isArray(profile.selection))
|
|
75
|
+
fail('missing native selection');
|
|
76
|
+
if (Object.keys(profile.selection).sort().join(',') !== 'archive,bindings,budget,condition,entry,loaderConfig,loads,members,nativePreloadMembers,preload,reader,runtime')
|
|
77
|
+
fail('unknown native selection field');
|
|
78
|
+
if (Object.keys(profile).sort().join(',') !== 'port,profile,selection' || profile.profile !== 'host-native-reading@1' || profile.port?.coordinate !== coordinate)
|
|
79
|
+
fail('unknown/noncanonical native profile');
|
|
80
|
+
return { line, profile };
|
|
81
|
+
}
|
|
82
|
+
const selections = new WeakMap();
|
|
83
|
+
/** This is the BEFORE-LOAD boundary. It authenticates the actual signed profile
|
|
84
|
+
* and checks current authority; a packet cannot mint the opaque result. */
|
|
85
|
+
function freezeProfile(value) { if (value !== null && typeof value === 'object') {
|
|
86
|
+
for (const child of Object.values(value))
|
|
87
|
+
freezeProfile(child);
|
|
88
|
+
Object.freeze(value);
|
|
89
|
+
} return value; }
|
|
90
|
+
function holdSelection(root, coordinate, selection) { const handle = Object.freeze({ identity: hash(canonicalJson([selection.line, root.epoch])) }); selections.set(handle, { root: { ...root, history: [...root.history], live: [...root.live], eras: [...root.eras], budget: { ...root.budget } }, coordinate, profile: freezeProfile(selection.profile) }); return handle; }
|
|
91
|
+
/** One synchronous authentication supplies both the opaque boundary and its
|
|
92
|
+
* immutable signed profile. Fresh Root checks still run at load and answer. */
|
|
93
|
+
export function authenticateNativeProfile(root, coordinate) { const got = selected(root, coordinate), selection = holdSelection(root, coordinate, got); return Object.freeze({ selection, line: got.line, profile: got.profile }); }
|
|
94
|
+
export function validateSignedNativeProfile(root, coordinate, expected) { const got = selected(root, coordinate); if (canonicalJson(got.profile) !== canonicalJson(expected))
|
|
95
|
+
fail('expected profile differs from signature'); return holdSelection(root, coordinate, got); }
|
|
96
|
+
/** Root supplies an authenticated current standing and its selected exact image.
|
|
97
|
+
* The signature authenticates the ENTIRE profile JSON including its selection;
|
|
98
|
+
* matching resolved selection is checked byte-for-byte, never from world data.
|
|
99
|
+
* The real Root guard constructor remains a host-integration prerequisite. */
|
|
100
|
+
function prepareSignedNativeReading(root, coordinate, resolved) {
|
|
101
|
+
const selection = selected(root, coordinate);
|
|
102
|
+
if (canonicalJson(selection.profile) !== canonicalJson(resolved.profile))
|
|
103
|
+
fail('resolved image/profile differs from signed selection');
|
|
104
|
+
if (!(resolved.archive instanceof Uint8Array) || resolved.archive.byteLength > resolved.handoff.budget.bytes)
|
|
105
|
+
fail('archive exceeds declared input budget');
|
|
106
|
+
let entries;
|
|
107
|
+
try {
|
|
108
|
+
gunzipSync(resolved.archive, { maxOutputLength: resolved.handoff.budget.bytes });
|
|
109
|
+
entries = archiveMembers(resolved.archive);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return fail('native implementation archive invalid');
|
|
113
|
+
}
|
|
114
|
+
const entriesAt = entries.filter(e => e.name === resolved.profile.selection.entry);
|
|
115
|
+
if (entriesAt.length !== 1 || 'sha256:' + createHash('sha256').update(entriesAt[0].bytes).digest('hex') !== resolved.profile.port.implementation)
|
|
116
|
+
fail('archive native entry differs from implementation');
|
|
117
|
+
const computedSelection = { archive: 'sha256:' + createHash('sha256').update(resolved.archive).digest('hex'), entry: resolved.profile.selection.entry, reader: resolved.profile.selection.reader, loaderConfig: resolved.profile.selection.loaderConfig, runtime: resolved.image.runtime, preload: resolved.image.preload ?? null, nativePreloadMembers: resolved.image.nativePreloadMembers, condition: resolved.image.condition, budget: resolved.handoff.budget, members: resolved.handoff.members.map(({ coordinate, digest }) => ({ coordinate, digest })), bindings: resolved.handoff.bindings, loads: resolved.handoff.loads };
|
|
118
|
+
if (canonicalJson(computedSelection) !== canonicalJson(resolved.profile.selection))
|
|
119
|
+
fail('resolved host bindings differ from signed selection');
|
|
120
|
+
if (resolved.profile.port.implementation !== resolved.handoff.members.find(m => m.coordinate === resolved.profile.selection.entry)?.digest)
|
|
121
|
+
fail('native implementation differs from handed member');
|
|
122
|
+
if (resolved.handoff.bindings.some(b => b.limits.slots !== resolved.handoff.bindings[0].limits.slots))
|
|
123
|
+
fail('native bindings need one declared parent slots resource');
|
|
124
|
+
if (!Number.isSafeInteger(resolved.profile.port.timeoutMs) || resolved.profile.port.timeoutMs < 1 || !Number.isSafeInteger(resolved.profile.port.responseBytes) || resolved.profile.port.responseBytes < 1)
|
|
125
|
+
fail('missing parent time/bytes');
|
|
126
|
+
const declared = resolved.profile.port;
|
|
127
|
+
if (Object.keys(declared).sort().join(',') !== 'closure,coordinate,environment,implementation,responseBytes,stderr,timeoutMs' || declared.stderr !== 'reject' || new Set(declared.closure).size !== declared.closure.length || canonicalJson([...declared.closure].sort()) !== canonicalJson([...new Set(resolved.handoff.members.map(m => m.digest))].sort()) || resolved.handoff.bindings.some(b => canonicalJson(b.environment.map(([name]) => name).sort()) !== canonicalJson([...declared.environment].sort())))
|
|
128
|
+
fail('command resource/environment/closure projection differs');
|
|
129
|
+
const authorityIdentity = hash(canonicalJson([root.signer.id, root.signer.publicKey, root.eras, root.epoch, root.history, root.live]));
|
|
130
|
+
const contextualHandoff = { ...resolved.handoff, authoritySnapshot: authorityIdentity, policy: hash(canonicalJson([resolved.handoff.policy, selection.line, authorityIdentity])) };
|
|
131
|
+
const image = prepareNodeImage(contextualHandoff, { ...resolved.image, commandPort: declared }), handle = Object.freeze({ identity: hash(canonicalJson([selection.line, root.epoch, image.identity])) });
|
|
132
|
+
prepared.set(handle, { root: { ...root, history: [...root.history], live: [...root.live], eras: [...root.eras], budget: { ...root.budget } }, coordinate, profile: selection.profile, image, module: Object.freeze({ ...resolved.module }) });
|
|
133
|
+
return handle;
|
|
134
|
+
}
|
|
135
|
+
/** Fixed instrument loader, never a callback supplied by module/request/profile.
|
|
136
|
+
* Root's REAL await-safe lease must cover verification, load and completion.
|
|
137
|
+
* The handoff image is verified before the fixed loader receives an entry. */
|
|
138
|
+
export async function loadSignedNativeReading(selection, resolved, guard) {
|
|
139
|
+
const held = selections.get(selection);
|
|
140
|
+
if (!held)
|
|
141
|
+
return fail('unknown pre-load selection');
|
|
142
|
+
return guard.withCurrent(async (current) => {
|
|
143
|
+
const fresh = selected(current, held.coordinate);
|
|
144
|
+
if (current.epoch !== held.root.epoch || canonicalJson(fresh.profile) !== canonicalJson(held.profile) || canonicalJson(current.history) !== canonicalJson(held.root.history))
|
|
145
|
+
fail('authority changed before load');
|
|
146
|
+
// Validate the exact entire image/profile FIRST, without importing reader code.
|
|
147
|
+
prepareSignedNativeReading(current, held.coordinate, { ...resolved, module: {} });
|
|
148
|
+
const reader = resolved.image.locations.find(l => l.member === held.profile.selection.reader), config = resolved.handoff.members.find(m => m.coordinate === held.profile.selection.loaderConfig), location = resolved.image.locations.find(l => l.member === held.profile.selection.loaderConfig);
|
|
149
|
+
if (!reader || reader.kind !== 'module' || !config || !location || !held.profile.selection.nativePreloadMembers.includes(config.coordinate))
|
|
150
|
+
return fail('reader/config not selected');
|
|
151
|
+
const raw = readBoundedFile(location.path, resolved.handoff.budget.bytes, held.profile.selection.loaderConfig);
|
|
152
|
+
if (hash(raw.toString('utf8')) !== config.digest)
|
|
153
|
+
fail('loader configuration changed');
|
|
154
|
+
let declared;
|
|
155
|
+
try {
|
|
156
|
+
declared = JSON.parse(raw.toString('utf8'));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return fail('loader configuration invalid');
|
|
160
|
+
}
|
|
161
|
+
const logical = declared;
|
|
162
|
+
if (logical.platform !== resolved.image.condition)
|
|
163
|
+
fail('selected runtime condition differs from logical platform');
|
|
164
|
+
if (!Array.isArray(logical.members) || logical.members.some(m => resolved.handoff.members.find(member => member.coordinate === m.coordinate)?.digest !== m.digest))
|
|
165
|
+
fail('loader target not covered by exact signed image');
|
|
166
|
+
const mounted = mountNativeImage(logical, resolved.image.locations.filter(location => logical.members.some(member => member.coordinate === location.member)).map(location => ({ coordinate: location.member, path: location.path })), held.profile.selection.reader, resolved.handoff.budget);
|
|
167
|
+
const boundary = installBoundary(mounted.declaration);
|
|
168
|
+
try {
|
|
169
|
+
const module = await boundary.loadEntry();
|
|
170
|
+
boundary.assertClosed();
|
|
171
|
+
const ready = prepareSignedNativeReading(current, held.coordinate, { ...resolved, image: { ...resolved.image, hostLoader: { ...mounted, preload: new URL('./preload' + extname(new URL(import.meta.url).pathname), import.meta.url).href } }, module });
|
|
172
|
+
prepared.get(ready).loader = mounted.declaration;
|
|
173
|
+
prepared.get(ready).custody = resolved.custody ?? nativeRootJobCustody(current, resolved.profile, resolved.handoff, resolved.image, logical);
|
|
174
|
+
return ready;
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
boundary.close();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
export async function answerNativeReading(handle, requests, self, options) {
|
|
182
|
+
const held = prepared.get(handle);
|
|
183
|
+
if (!held)
|
|
184
|
+
return fail('unknown instrument selection');
|
|
185
|
+
if (options.phase !== 'act')
|
|
186
|
+
fail('effects forbidden during ' + options.phase);
|
|
187
|
+
if (options.signal?.aborted)
|
|
188
|
+
fail('interrupted before wrapper');
|
|
189
|
+
const deadline = process.hrtime.bigint() + BigInt(held.profile.port.timeoutMs) * 1000000n, controller = new AbortController(), abort = controller.abort.bind(controller, undefined), cancel = scheduleDeadline(deadline, abort);
|
|
190
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
191
|
+
try {
|
|
192
|
+
return await options.guard.withCurrent(async (current) => {
|
|
193
|
+
const fresh = selected(current, held.coordinate);
|
|
194
|
+
if (canonicalJson(fresh.profile) !== canonicalJson(held.profile) || current.epoch !== held.root.epoch || hash(canonicalJson(current.history)) !== hash(canonicalJson(held.root.history)))
|
|
195
|
+
fail('authority changed since preparation');
|
|
196
|
+
if (!held.loader)
|
|
197
|
+
return fail('reader not loaded by fixed host boundary');
|
|
198
|
+
const boundary = installBoundary(held.loader);
|
|
199
|
+
try {
|
|
200
|
+
const frames = requests.map(request => { let asked; if (request.verb === 'read' && held.module.commands) {
|
|
201
|
+
const extraction = answer({ receipt: a => { asked = a; return []; } }, request, self);
|
|
202
|
+
if (extraction.kind !== 'fact' || !asked)
|
|
203
|
+
fail('Asked extraction refused');
|
|
204
|
+
} const jobs = asked ? held.module.commands(asked) : []; return { request, asked, jobs }; });
|
|
205
|
+
const jobs = frames.flatMap(f => f.jobs), frame = new TextEncoder().encode(JSON.stringify(jobs)), wanted = plannedJobs(held.image, frame), observations = jobs.length ? await executeJobs(held.image, frame, { phase: 'act', signal: controller.signal, custody: held.custody }) : [];
|
|
206
|
+
if (observations.length !== wanted.length || observations.some((got, i) => got.id !== wanted[i].id || got.identity !== wanted[i].identity || got.responses.length !== 1))
|
|
207
|
+
fail('capture identity/count differs');
|
|
208
|
+
const rows = new Map(observations.map(got => [got.id, got]));
|
|
209
|
+
if (rows.size !== observations.length)
|
|
210
|
+
fail('duplicate capture');
|
|
211
|
+
// Capture text is witness provenance, not a new conceptual origin. Actual
|
|
212
|
+
// Topos answer performs its normal handed(reads,lines) projection both times.
|
|
213
|
+
const results = frames.map(({ request, jobs }) => { const captures = jobs.map(job => { const got = rows.get(job.id); if (!got)
|
|
214
|
+
return fail('missing capture ' + job.id); return canonical({ scope: 'command/capture/' + job.id, shape: 'same', role: 'reads', form: 'alphabet', measure: 'text', value: 'lock', by: 'host', at: 'witness:' + got.identity, about: JSON.stringify({ identity: got.identity, responses: got.responses }) }); }); return answer(held.module, { ...request, lines: [...(request.lines ?? []), ...captures] }, self); });
|
|
215
|
+
if (results.length !== requests.length)
|
|
216
|
+
return fail('incomplete pure response');
|
|
217
|
+
if (controller.signal.aborted || process.hrtime.bigint() >= deadline)
|
|
218
|
+
fail('interrupted/timeout wrapper');
|
|
219
|
+
if (Buffer.byteLength(JSON.stringify(results)) > held.profile.port.responseBytes)
|
|
220
|
+
fail('parent response bytes exceeded');
|
|
221
|
+
boundary.assertClosed();
|
|
222
|
+
return results;
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
boundary.close();
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
cancel();
|
|
231
|
+
options.signal?.removeEventListener('abort', abort);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/** Executable resources belong to the currently authenticated parent command
|
|
235
|
+
* declaration, not to the separate authority-history/clock parsing budget. */
|
|
236
|
+
export function nativeParentInputBytes(root, parent) {
|
|
237
|
+
const selected = authenticateSignedPort(root, parent.port).profile;
|
|
238
|
+
const bytes = selected?.selection?.budget?.bytes;
|
|
239
|
+
if (selected?.profile !== 'host-native-reading@1' || !Number.isSafeInteger(bytes) || bytes < 1)
|
|
240
|
+
fail('parent executable input resource absent');
|
|
241
|
+
return bytes;
|
|
242
|
+
}
|
|
243
|
+
/** Exact schema field names, shared by the host's selected native formats. */
|
|
244
|
+
export function fieldNames(value) { return Object.keys(value).sort().join(','); }
|
|
245
|
+
/** Retain each verifier's private map and return a fresh canonical projection. */
|
|
246
|
+
export function retainedProjection(verified, fail) {
|
|
247
|
+
return handle => { const p = verified.get(handle); if (!p)
|
|
248
|
+
return fail('unknown verified selection'); return JSON.parse(canonicalJson(p)); };
|
|
249
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ReadingIdentityContext } from '@lapxo/topos/contract';
|
|
2
|
+
import { type ReadingReference } from '../reading-context.ts';
|
|
3
|
+
export declare function currentReadingEnvironment(store: string, root: string, hash: (text: string) => string, flags: readonly string[]): (context: ReadingIdentityContext) => readonly ReadingReference[] | undefined;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Reconstruct the current execution boundary from authenticated selections.
|
|
2
|
+
* A retained receipt never selects its own cwd or execution flags. Historical
|
|
3
|
+
* inspection does not call this resolver and does not authorize a current act.
|
|
4
|
+
*/
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { readingEnvironment } from "../reading-context.js";
|
|
7
|
+
import { currentNativeReaderSelections, nativeMountIdentity } from "./route.js";
|
|
8
|
+
export function currentReadingEnvironment(store, root, hash, flags) {
|
|
9
|
+
const selections = currentNativeReaderSelections(store);
|
|
10
|
+
const ordinary = readingEnvironment(hash, process.env, root, flags);
|
|
11
|
+
return context => {
|
|
12
|
+
const mounts = [];
|
|
13
|
+
for (const [encoded, digest] of context.inputs) {
|
|
14
|
+
let name = encoded;
|
|
15
|
+
for (;;) {
|
|
16
|
+
let value;
|
|
17
|
+
try {
|
|
18
|
+
value = JSON.parse(name);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
if (!Array.isArray(value) || value.length !== 2 || value[0] !== 'context' || typeof value[1] !== 'string')
|
|
24
|
+
break;
|
|
25
|
+
name = value[1];
|
|
26
|
+
}
|
|
27
|
+
if (name === 'native/mount')
|
|
28
|
+
mounts.push(digest);
|
|
29
|
+
}
|
|
30
|
+
if (!mounts.length)
|
|
31
|
+
return selections.some(one => one.profile.selection.archive === context.implementation) ? undefined : ordinary;
|
|
32
|
+
if (mounts.length !== 1)
|
|
33
|
+
return undefined;
|
|
34
|
+
const selected = selections.filter(one => one.profile.selection.archive === context.implementation && nativeMountIdentity(one.profile, one.binaries, one.control) === mounts[0]);
|
|
35
|
+
if (!selected.length)
|
|
36
|
+
return undefined;
|
|
37
|
+
// Several readers may share this exact image. They have the same execution
|
|
38
|
+
// boundary; their policy and transitive inputs remain separate in the receipt.
|
|
39
|
+
return readingEnvironment(hash, process.env, join(store, 'cas', 'laid', mounts[0].slice(7)), []);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ReadingReference } from '../reading-context.ts';
|
|
2
|
+
/** Existing leaf folds wrap parent names in the context namespace. Resolve that
|
|
3
|
+
* namespace without dropping any inherited identity, and never pick a duplicate.
|
|
4
|
+
*/
|
|
5
|
+
export declare function nativeReferenceInputs(inputs: readonly ReadingReference[], required: readonly ReadingReference[]): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Existing leaf folds wrap parent names in the context namespace. Resolve that
|
|
2
|
+
* namespace without dropping any inherited identity, and never pick a duplicate.
|
|
3
|
+
*/
|
|
4
|
+
export function nativeReferenceInputs(inputs, required) {
|
|
5
|
+
const names = new Map(required);
|
|
6
|
+
const found = new Map();
|
|
7
|
+
for (const [encoded, digest] of inputs) {
|
|
8
|
+
let name = encoded;
|
|
9
|
+
for (;;) {
|
|
10
|
+
let value;
|
|
11
|
+
try {
|
|
12
|
+
value = JSON.parse(name);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
if (!Array.isArray(value) || value.length !== 2 || value[0] !== 'context' || typeof value[1] !== 'string')
|
|
18
|
+
break;
|
|
19
|
+
name = value[1];
|
|
20
|
+
}
|
|
21
|
+
if (!names.has(name))
|
|
22
|
+
continue;
|
|
23
|
+
if (found.has(name))
|
|
24
|
+
throw Error('REFUSE·native reading duplicate context input ' + name);
|
|
25
|
+
found.set(name, digest);
|
|
26
|
+
}
|
|
27
|
+
for (const [name, digest] of required)
|
|
28
|
+
if (found.get(name) !== digest)
|
|
29
|
+
throw Error('REFUSE·native reading context omits or changes ' + name);
|
|
30
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type RootAuthorityProtection } from '../../land/act.ts';
|
|
2
|
+
import type { RootStanding, RootAuthorityGuard } from './reading.ts';
|
|
3
|
+
import type { RootContext } from '@lapxo/topos/core';
|
|
4
|
+
/** Values come from the fixed parent argv chain while it retains actual Root
|
|
5
|
+
* protection. No world packet supplies the store, parent, cursor or verifier.
|
|
6
|
+
*/
|
|
7
|
+
export interface NativeRootInheritance {
|
|
8
|
+
readonly store: string;
|
|
9
|
+
readonly parent: number;
|
|
10
|
+
readonly expected: RootContext;
|
|
11
|
+
}
|
|
12
|
+
export declare function currentNativeRoot(store: string, parent: number, proof: RootAuthorityProtection): {
|
|
13
|
+
readonly context: RootContext;
|
|
14
|
+
readonly root: RootStanding;
|
|
15
|
+
};
|
|
16
|
+
export declare function inheritedNativeRoot(inherited: NativeRootInheritance, proof: RootAuthorityProtection): RootStanding;
|
|
17
|
+
export declare function inheritedNativeGuard(inherited: NativeRootInheritance, proof: RootAuthorityProtection): RootAuthorityGuard;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { declaredRootClockSource } from "../root-clock-source.js";
|
|
4
|
+
import { foldClaims, fieldOf } from "../../fold/claims.js";
|
|
5
|
+
import { assertRootAuthorityProtection } from "../../land/act.js";
|
|
6
|
+
import { withCommittedLocalActObservation } from "../ports/local-act.js";
|
|
7
|
+
const fail = (why) => { throw Error('REFUSE·native Root ' + why); };
|
|
8
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
9
|
+
export function currentNativeRoot(store, parent, proof) {
|
|
10
|
+
assertRootAuthorityProtection(proof, store, parent);
|
|
11
|
+
return withCommittedLocalActObservation(store, () => {
|
|
12
|
+
// The constructor has just authenticated this cursor and its receiver heads.
|
|
13
|
+
// Reuse that result only within this protected synchronous invocation. Calling
|
|
14
|
+
// trust.current() here would repeat the same full construction immediately;
|
|
15
|
+
// subsequent gates still construct their own fresh source under the lease.
|
|
16
|
+
const root = dirname(store), source = declaredRootClockSource(root), current = source.trust.contract.root;
|
|
17
|
+
const { history, anchor: signer, algorithms: eras } = source.observation;
|
|
18
|
+
const live = foldClaims(history).standing.filter(line => fieldOf(line, 'value') !== 'withdraw');
|
|
19
|
+
// The source constructor authenticates the complete history at each original
|
|
20
|
+
// admission snapshot. Current port eligibility stays a separate SDK check.
|
|
21
|
+
const limits = source.resources;
|
|
22
|
+
if (history.length > limits.lines || history.reduce((bytes, line) => bytes + Buffer.byteLength(line), 0) > limits.bytes)
|
|
23
|
+
fail('Root declared history resources exceeded');
|
|
24
|
+
assertRootAuthorityProtection(proof, store, parent);
|
|
25
|
+
return { context: current, root: { history, live, signer, eras, epoch: Number(current.epoch), budget: { lines: limits.lines, bytes: limits.bytes } } };
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function inheritedNativeRoot(inherited, proof) {
|
|
29
|
+
const current = currentNativeRoot(inherited.store, inherited.parent, proof);
|
|
30
|
+
if (!same(current.context, inherited.expected))
|
|
31
|
+
fail('actual authority cursor or dependencies changed');
|
|
32
|
+
return current.root;
|
|
33
|
+
}
|
|
34
|
+
export function inheritedNativeGuard(inherited, proof) {
|
|
35
|
+
return { async withCurrent(body) { const before = inheritedNativeRoot(inherited, proof), result = await body(before), after = inheritedNativeRoot(inherited, proof); if (createHash('sha256').update(JSON.stringify(before)).digest('hex') !== createHash('sha256').update(JSON.stringify(after)).digest('hex'))
|
|
36
|
+
fail('Root changed during native execution'); return result; } };
|
|
37
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type NativeBinaryOrigin } from './native-binary.ts';
|
|
2
|
+
import { type HostControlSelection } from './control-selection.ts';
|
|
3
|
+
export { materializeNativeRoute } from './materialize.ts';
|
|
4
|
+
import { type PortProfile, type RootStanding } from './reading.ts';
|
|
5
|
+
import type { ReadingExecution, ReadingReference } from '../reading-context.ts';
|
|
6
|
+
import type { Request, Response } from '@lapxo/topos/contract';
|
|
7
|
+
import type { ReaderLifetime } from '@lapxo/topos/wire';
|
|
8
|
+
export interface NativeBinaryBinding {
|
|
9
|
+
readonly line: string;
|
|
10
|
+
readonly coordinate: string;
|
|
11
|
+
readonly profile: import('./native-binary.ts').NativeBinaryProfile;
|
|
12
|
+
readonly origin?: NativeBinaryOrigin;
|
|
13
|
+
}
|
|
14
|
+
export interface NativeRoute {
|
|
15
|
+
readonly identity: string;
|
|
16
|
+
readonly profile: PortProfile;
|
|
17
|
+
readonly binaries: readonly NativeBinaryBinding[];
|
|
18
|
+
readonly control?: HostControlSelection;
|
|
19
|
+
readonly inputs: readonly ReadingReference[];
|
|
20
|
+
}
|
|
21
|
+
/** Shared origins are one exact input; individual signed selectors remain separate. */
|
|
22
|
+
export declare function nativeBinaryOriginInputs(binaries: readonly NativeBinaryBinding[]): readonly ReadingReference[];
|
|
23
|
+
/** Inputs are semantic logical references. Fixed instrument/host graph and
|
|
24
|
+
* runtime-supervisor chain must also be supplied by the calling identity builder.
|
|
25
|
+
* Locations and lock-token/PID are operational custody, never these inputs.
|
|
26
|
+
*/
|
|
27
|
+
export declare function prepareNativeRoute(store: string, coordinate: string, readerMapping?: string): NativeRoute;
|
|
28
|
+
/** Recheck authority before both a current retained answer and fresh execution.
|
|
29
|
+
* Historical public --check uses the existing receipt-inspection path, not this
|
|
30
|
+
* command authorization route.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assertNativeRouteCurrent(route: NativeRoute): void;
|
|
33
|
+
export declare function invokeNativeRoute(route: NativeRoute, input: {
|
|
34
|
+
archive: string;
|
|
35
|
+
locations: readonly {
|
|
36
|
+
member: string;
|
|
37
|
+
path: string;
|
|
38
|
+
kind: 'module' | 'data' | 'binary';
|
|
39
|
+
}[];
|
|
40
|
+
requests: readonly Request[];
|
|
41
|
+
execution: ReadingExecution;
|
|
42
|
+
limits: ReaderLifetime;
|
|
43
|
+
}): readonly Response[];
|
|
44
|
+
/** This predicate only schedules an outer lease. It grants no command. */
|
|
45
|
+
export declare function declaredNativeReaderPorts(lines: readonly string[]): boolean;
|
|
46
|
+
export declare function prepareNativeReaderRoute(store: string, reader: {
|
|
47
|
+
id: string;
|
|
48
|
+
module: string;
|
|
49
|
+
}): NativeRoute | undefined;
|
|
50
|
+
export declare function nativeRouteStore(route: NativeRoute): string;
|
|
51
|
+
/** Quiet-pass gate: authenticate grants/cursor only, no image/materialization or
|
|
52
|
+
* program execution. Historical --check remains its separate receipt path. */
|
|
53
|
+
export declare function assertNativeReaderGrantsCurrent(store: string): void;
|
|
54
|
+
/** Executable kinds come only from current authenticated Root declarations. A
|
|
55
|
+
* matching JSON hint identifies a candidate, but is never permission to execute. */
|
|
56
|
+
export declare function authenticatedNativeBinaries(root: RootStanding, profile: PortProfile): readonly NativeBinaryBinding[];
|
|
57
|
+
/** Same semantic image identity for preparation, retained mount and quiet checks. */
|
|
58
|
+
export declare function nativeMountIdentity(profile: PortProfile, binaries: readonly NativeBinaryBinding[], control?: HostControlSelection): string;
|
|
59
|
+
export interface CurrentNativeReaderSelection {
|
|
60
|
+
readonly profile: PortProfile;
|
|
61
|
+
readonly binaries: readonly NativeBinaryBinding[];
|
|
62
|
+
readonly control?: HostControlSelection;
|
|
63
|
+
}
|
|
64
|
+
/** Current eligibility only: no executor graph, image load or materialization. */
|
|
65
|
+
export declare function currentNativeReaderSelections(store: string): readonly CurrentNativeReaderSelection[];
|
|
66
|
+
export declare function currentNativeReaderProfiles(store: string): readonly PortProfile[];
|