@lapxo/bound 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +67 -0
- package/.github/workflows/test.yml +35 -0
- package/CITATION.cff +19 -0
- package/TARGET.bound +80 -21
- package/dist/cli/accept.js +103 -71
- package/dist/cli/place.js +3 -2
- package/dist/cli/standing-result.js +3 -5
- package/dist/cli/takes.js +4 -3
- package/dist/cli/verb.js +2 -1
- package/dist/engine/ports.d.ts +6 -1
- package/dist/engine/ports.js +23 -9
- package/dist/fold/claims.d.ts +29 -0
- package/dist/fold/claims.js +2 -1
- package/dist/fold/closed.js +2 -1
- package/dist/fold/digests.js +7 -7
- package/dist/host/admitted-place.js +3 -31
- package/dist/host/call-context.d.ts +20 -0
- package/dist/host/call-context.js +14 -0
- package/dist/host/capsule-process.js +2 -1
- package/dist/host/capsule.js +7 -4
- package/dist/host/commit-result.d.ts +6 -0
- package/dist/host/commit-result.js +5 -0
- package/dist/host/contextual-place.js +9 -14
- package/dist/host/host-controls.js +19 -0
- package/dist/host/in-process-act.d.ts +82 -0
- package/dist/host/in-process-act.js +84 -0
- package/dist/host/in-process-context.d.ts +46 -0
- package/dist/host/in-process-context.js +91 -0
- package/dist/host/in-process-root.d.ts +117 -0
- package/dist/host/in-process-root.js +84 -0
- package/dist/host/in-process.d.ts +41 -0
- package/dist/host/in-process.js +55 -0
- package/dist/host/index.d.ts +15 -0
- package/dist/host/index.js +7 -0
- package/dist/host/native/loader.js +1 -1
- package/dist/host/native/module-closure.d.ts +35 -0
- package/dist/host/native/module-closure.js +34 -3
- package/dist/host/native/module-delivery.d.ts +62 -0
- package/dist/host/native/module-delivery.js +151 -0
- package/dist/host/native/module-syntax.js +3 -1
- package/dist/host/native-receiver.d.ts +19 -0
- package/dist/host/native-receiver.js +25 -0
- package/dist/host/order.js +2 -0
- package/dist/host/place-act-transaction.d.ts +24 -0
- package/dist/host/place-act-transaction.js +78 -0
- package/dist/host/place-clock-selection.d.ts +55 -0
- package/dist/host/place-clock-selection.js +1 -1
- package/dist/host/ports/local-act.d.ts +44 -0
- package/dist/host/ports/local-act.js +13 -2
- package/dist/host/ports/place-act.d.ts +49 -0
- package/dist/host/ports/place-act.js +19 -54
- package/dist/host/ports/publication.d.ts +10 -0
- package/dist/host/root-clock-source.d.ts +86 -0
- package/dist/host/root-clock-source.js +7 -5
- package/dist/host/signature-era.js +12 -0
- package/dist/host/signing.js +1 -1
- package/dist/host/source-content.js +2 -1
- package/dist/host/standing-carrier.js +18 -0
- package/dist/host/standing-projection.js +50 -0
- package/dist/land/act.js +60 -26
- package/dist/observe/run.js +2 -1
- package/dist/observe/runner.js +16 -8
- package/docs/contracts/closure-delivery.md +23 -0
- package/docs/contracts/closure-delivery.publication.json +25 -0
- package/docs/contracts/native-host.md +21 -0
- package/docs/contracts/native-host.publication.json +25 -0
- package/docs/contracts/native-publication.inventory.json +2 -1
- package/docs/contracts/publication.inventory.json +29 -0
- package/docs/guide.md +10 -0
- package/docs/reference.md +39 -0
- package/docs/why.md +109 -0
- package/package.json +43 -2
- package/receipts.bound +10405 -9625
- package/samples/module-delivery/inputs/delivery.json +1 -0
- package/samples/native-host/no.json +10 -0
- package/samples/native-host/yes.json +20 -0
- package/samples/no/module-delivery/delivery.json +1 -0
- package/vectors/native/module-delivery.vector.json +32 -0
- package/vectors/native/native-host.vector.json +62 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type PlaceReceipt, type ClockBudget } from '@lapxo/topos/core';
|
|
2
|
+
import type { PlaceActBudget } from './ports/place-act.ts';
|
|
3
|
+
export interface RootClockSourceSelection {
|
|
4
|
+
readonly selector: string;
|
|
5
|
+
readonly profileSelector: string;
|
|
6
|
+
readonly bootstrapCheckpoint: {
|
|
7
|
+
scope: string;
|
|
8
|
+
measure: string;
|
|
9
|
+
};
|
|
10
|
+
readonly resources: {
|
|
11
|
+
bytes: number;
|
|
12
|
+
lines: number;
|
|
13
|
+
places: number;
|
|
14
|
+
acts: number;
|
|
15
|
+
};
|
|
16
|
+
readonly inherited: {
|
|
17
|
+
clock: ClockBudget;
|
|
18
|
+
storage: PlaceActBudget;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export declare function rootClockSource(root: string, selected: RootClockSourceSelection): {
|
|
22
|
+
observation: Readonly<{
|
|
23
|
+
history: readonly string[];
|
|
24
|
+
anchor: import("@lapxo/topos/wire").Signer;
|
|
25
|
+
algorithms: readonly string[];
|
|
26
|
+
}>;
|
|
27
|
+
resources: Readonly<{
|
|
28
|
+
bytes: number;
|
|
29
|
+
lines: number;
|
|
30
|
+
places: number;
|
|
31
|
+
acts: number;
|
|
32
|
+
}>;
|
|
33
|
+
authority: (encoded: string) => {
|
|
34
|
+
anchor: import("@lapxo/topos/wire").Signer;
|
|
35
|
+
history: readonly string[];
|
|
36
|
+
checkpoint: string;
|
|
37
|
+
};
|
|
38
|
+
selection: import("./place-clock-selection.ts").HostClockSelection;
|
|
39
|
+
trust: import("./ports/place-act.ts").PlaceActTrust;
|
|
40
|
+
};
|
|
41
|
+
/** Source mapping is an explicit Root declaration; absence never means a zero
|
|
42
|
+
* clock or an inferred cursor. Existing legacy callers do not request it. */
|
|
43
|
+
export declare function rootClockSelectionOf(live: readonly string[], anchor: import('@lapxo/topos/wire').Signer): RootClockSourceSelection;
|
|
44
|
+
export declare function declaredRootClockSource(root: string): {
|
|
45
|
+
observation: Readonly<{
|
|
46
|
+
history: readonly string[];
|
|
47
|
+
anchor: import("@lapxo/topos/wire").Signer;
|
|
48
|
+
algorithms: readonly string[];
|
|
49
|
+
}>;
|
|
50
|
+
resources: Readonly<{
|
|
51
|
+
bytes: number;
|
|
52
|
+
lines: number;
|
|
53
|
+
places: number;
|
|
54
|
+
acts: number;
|
|
55
|
+
}>;
|
|
56
|
+
authority: (encoded: string) => {
|
|
57
|
+
anchor: import("@lapxo/topos/wire").Signer;
|
|
58
|
+
history: readonly string[];
|
|
59
|
+
checkpoint: string;
|
|
60
|
+
};
|
|
61
|
+
selection: import("./place-clock-selection.ts").HostClockSelection;
|
|
62
|
+
trust: import("./ports/place-act.ts").PlaceActTrust;
|
|
63
|
+
};
|
|
64
|
+
export declare function hasRootClockSource(root: string): boolean;
|
|
65
|
+
export interface PortablePlaceContext {
|
|
66
|
+
readonly identity: string;
|
|
67
|
+
readonly context: string;
|
|
68
|
+
readonly receipts: readonly PlaceReceipt[];
|
|
69
|
+
readonly authority: {
|
|
70
|
+
anchor: import('@lapxo/topos/wire').Signer;
|
|
71
|
+
history: readonly string[];
|
|
72
|
+
checkpoint: string;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** Historical inspection ONLY. The expected acting Root anchor and resource
|
|
76
|
+
* selection come from the consumer's selected place, never from this packet.
|
|
77
|
+
* This function returns data and cannot construct a current command permit. */
|
|
78
|
+
export declare function inspectPortablePlaceContexts(encoded: string, expectedAnchor: import('@lapxo/topos/wire').Signer, selected: RootClockSourceSelection): {
|
|
79
|
+
identity: string;
|
|
80
|
+
context: import("@lapxo/topos/core").PlaceAct;
|
|
81
|
+
}[];
|
|
82
|
+
/** Prospective configuration is checked as authenticated Root history, never
|
|
83
|
+
* against a prose birth condition. Initial heads are explicit typed selections;
|
|
84
|
+
* stored committed chains cannot disappear when a selection is activated or
|
|
85
|
+
* replaced. Preview reads only; the real admission repeats under its Root lock. */
|
|
86
|
+
export declare function assertProspectiveRootClockSelection(root: string, signed: readonly string[]): void;
|
|
@@ -6,7 +6,7 @@ import { committedLocalActs, actLines } from "./ports/local-act.js";
|
|
|
6
6
|
import { verifiedLegacyActResult } from "../cli/act-result.js";
|
|
7
7
|
import { admissionAnchor, admissionHistoryAt } from "../fold/admission-history.js";
|
|
8
8
|
import { authorityFor } from "../fold/signers.js";
|
|
9
|
-
import { eraOf } from "
|
|
9
|
+
import { signatureEraOf as eraOf } from "./signature-era.js";
|
|
10
10
|
import { fieldOf, foldClaims, isConfig, selfName } from "../fold/claims.js";
|
|
11
11
|
import { authenticatedRootStanding } from "./admitted-place.js";
|
|
12
12
|
import { verifiesOwnerLine } from "../land/sign.js";
|
|
@@ -158,11 +158,11 @@ export function rootClockSource(root, selected) {
|
|
|
158
158
|
}
|
|
159
159
|
/** Source mapping is an explicit Root declaration; absence never means a zero
|
|
160
160
|
* clock or an inferred cursor. Existing legacy callers do not request it. */
|
|
161
|
-
export function
|
|
162
|
-
const
|
|
161
|
+
export function rootClockSelectionOf(live, anchor) {
|
|
162
|
+
const rows = live.filter(l => fieldOf(l, 'scope') === 'wire/place-act-context-source' && fieldOf(l, 'measure') === 'text' && fieldOf(l, 'value') !== 'withdraw');
|
|
163
163
|
if (rows.length !== 1)
|
|
164
164
|
fail('requires one admitted source mapping');
|
|
165
|
-
const line = rows[0],
|
|
165
|
+
const line = rows[0], algorithms = eraOf(live).admitted;
|
|
166
166
|
if (fieldOf(line, 'by') !== anchor.id || fieldOf(line, 'role') !== 'writes' || fieldOf(line, 'form') !== 'alphabet' || fieldOf(line, 'value') !== 'lock' || !verifiesOwnerLine(line, anchor.publicKey, algorithms))
|
|
167
167
|
fail('mapping is not Root signed');
|
|
168
168
|
const about = fieldOf(line, 'about');
|
|
@@ -175,8 +175,10 @@ export function declaredRootClockSource(root) {
|
|
|
175
175
|
}
|
|
176
176
|
if (!value || Object.keys(value).sort().join(',') !== 'bootstrapCheckpoint,inherited,profile,profileSelector,resources,selector' || value.profile !== 'place-act-context@1' || JSON.stringify(value) !== about)
|
|
177
177
|
fail('unknown/noncanonical mapping');
|
|
178
|
-
|
|
178
|
+
sourceSelection(value);
|
|
179
|
+
return value;
|
|
179
180
|
}
|
|
181
|
+
export function declaredRootClockSource(root) { return rootClockSource(root, rootClockSelectionOf(authenticatedRootStanding(root), admissionAnchor(root))); }
|
|
180
182
|
export function hasRootClockSource(root) { return authenticatedRootStanding(root).some(l => fieldOf(l, 'scope') === 'wire/place-act-context-source' && fieldOf(l, 'measure') === 'text' && fieldOf(l, 'value') !== 'withdraw'); }
|
|
181
183
|
/** Historical inspection ONLY. The expected acting Root anchor and resource
|
|
182
184
|
* selection come from the consumer's selected place, never from this packet.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { wireLine } from "../fold/wire.js";
|
|
2
|
+
import { fieldOf } from "../fold/claims.js";
|
|
3
|
+
/** Pure signature vocabulary; no instrument discovery for diagnostics. */
|
|
4
|
+
export function signatureEraOf(standing) {
|
|
5
|
+
const said = wireLine(standing, 'signature-algorithms');
|
|
6
|
+
const admitted = (said === undefined ? [] : fieldOf(said, 'value').split('|')).filter(Boolean);
|
|
7
|
+
const current = wireLine(standing, 'era');
|
|
8
|
+
const era = (current === undefined ? undefined : admitted.find((one) => one.endsWith(`:${fieldOf(current, 'value')}`))) ?? admitted.find((one) => one.includes(':'));
|
|
9
|
+
if (era === undefined)
|
|
10
|
+
throw new Error(`REFUSE·runtime the lock names no signature algorithm by digest: nothing can be signed here`);
|
|
11
|
+
return { admitted, era };
|
|
12
|
+
}
|
package/dist/host/signing.js
CHANGED
|
@@ -40,7 +40,7 @@ export function signerFor(lines, keyId, selected, keyFile, injected) {
|
|
|
40
40
|
if (injected) {
|
|
41
41
|
if (injected.name !== declaration.name || keyFile)
|
|
42
42
|
throw new SigningFailure('REFUSE·signer injected host binding conflicts with declaration or key file');
|
|
43
|
-
return (lot) => invokeSigningPort(lot, injected.sign, { timeoutMs: declaration.timeoutMs, maxBytes: declaration.responseBytes });
|
|
43
|
+
return (lot) => invokeSigningPort(lot, injected.sign, { timeoutMs: declaration.timeoutMs, maxBytes: declaration.responseBytes }, injected.signal);
|
|
44
44
|
}
|
|
45
45
|
const mechanism = binding(declaration.name, keyFile);
|
|
46
46
|
const protocol = { request: signerRequestBytes, response: signerResponseBytes, reply: signerResponseOf };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hostOutput } from "./call-context.js";
|
|
1
2
|
import { transportBytes } from "./ports/transport.js";
|
|
2
3
|
import { contentStore, verifiedContent } from "./content-store.js";
|
|
3
4
|
import { ContentNeeded } from "./content-needed.js";
|
|
@@ -91,7 +92,7 @@ export async function resolveSources(standing, requested) {
|
|
|
91
92
|
fetch: async (url, digest) => transportBytes(url, digest, [...ownLock(), ...standing]),
|
|
92
93
|
}, requested);
|
|
93
94
|
for (const digest of received)
|
|
94
|
-
|
|
95
|
+
hostOutput('out', `FETCHED ${digest} · verified\n`);
|
|
95
96
|
}
|
|
96
97
|
/** A changed verified store is the only reason to retry. A repeated miss never loops or changes a named pin. */
|
|
97
98
|
export async function resolveRequested(resolve, fold) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PLACE_ACT_CONTEXT } from '@lapxo/topos/core';
|
|
2
|
+
import { foldClaims, fieldOf } from "../fold/claims.js";
|
|
3
|
+
/** Projection only, after the caller authenticates the complete carrier. The
|
|
4
|
+
* CLI and in-process reader use this same fold and output shape. */
|
|
5
|
+
export function projectContextualStanding(root, legacy, contexts, decoded) {
|
|
6
|
+
const groups = new Map();
|
|
7
|
+
for (const act of contexts) {
|
|
8
|
+
const context = decoded.find(one => one.identity === act.identity).context;
|
|
9
|
+
for (const p of context.places) {
|
|
10
|
+
const key = JSON.stringify([context.root.namespace, p.place]), group = groups.get(key) ?? { namespace: context.root.namespace, place: p.place, lines: [], contexts: [] };
|
|
11
|
+
group.lines.push(...p.records);
|
|
12
|
+
group.contexts.push({ identity: act.identity, context: act.context, receipts: act.receipts });
|
|
13
|
+
groups.set(key, group);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const legacyPlaces = legacy.map(place => ({ namespace: 'legacy-global', place: place.place, standing: foldClaims(place.history).standing.filter(line => fieldOf(line, 'value') !== 'withdraw'), history: place.history }));
|
|
17
|
+
return { profile: PLACE_ACT_CONTEXT, root, legacyPlaces, contexts, places: [...groups.values()].map(group => { const folded = foldClaims(group.lines); return { namespace: group.namespace, place: group.place, standing: folded.standing.filter(line => fieldOf(line, 'value') !== 'withdraw'), retracted: folded.retracted, superseded: folded.superseded, forks: folded.forks, contexts: group.contexts }; }) };
|
|
18
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { viewsOf } from "../fold/views.js";
|
|
2
|
+
import { BoundRefusal } from "./refusals.js";
|
|
3
|
+
import { authorityFor } from "../fold/signers.js";
|
|
4
|
+
import { signatureEraOf as eraOf } from "./signature-era.js";
|
|
5
|
+
import { fieldOf, foldClaims, isConfig } from "../fold/claims.js";
|
|
6
|
+
import { foldedEvidence } from '@lapxo/topos/contract';
|
|
7
|
+
/** Project the supplied signed history against the host's original trust anchor.
|
|
8
|
+
* No current-root selection, storage access, effects or retained authority.
|
|
9
|
+
* The file host and the in-process host use this same admission-snapshot fold. */
|
|
10
|
+
export function projectAuthenticatedStanding(history, anchor) {
|
|
11
|
+
const snapshots = new Map();
|
|
12
|
+
const admitted = history.filter(line => {
|
|
13
|
+
const epoch = Number(fieldOf(line, 'epoch')) || 0;
|
|
14
|
+
let authority = snapshots.get(epoch);
|
|
15
|
+
if (!authority) {
|
|
16
|
+
// This invocation has freshly read the complete signed inventory. Derive
|
|
17
|
+
// the same historical cutoff without reopening it for every epoch; the
|
|
18
|
+
// next invocation still rereads every authority input.
|
|
19
|
+
const snapshot = history.filter(record => !isConfig(record) || Number(fieldOf(record, 'epoch')) <= epoch);
|
|
20
|
+
// The signature vocabulary is configuration, not the as-yet
|
|
21
|
+
// unauthenticated evidence whose values are about to be folded.
|
|
22
|
+
const wire = snapshot.filter(record => {
|
|
23
|
+
const scope = fieldOf(record, 'scope');
|
|
24
|
+
return scope.startsWith('wire/') || scope.startsWith('audit/wire/');
|
|
25
|
+
});
|
|
26
|
+
authority = authorityFor(snapshot, anchor, eraOf(foldClaims(wire).standing).admitted);
|
|
27
|
+
snapshots.set(epoch, authority);
|
|
28
|
+
}
|
|
29
|
+
return authority.of(line).kind === 'admitted';
|
|
30
|
+
});
|
|
31
|
+
// The host authenticates against historical authority; Topos projects the
|
|
32
|
+
// authenticated history using the instrument's existing fold.
|
|
33
|
+
const { standing } = foldedEvidence(admitted, {
|
|
34
|
+
classification: () => 'local',
|
|
35
|
+
fold: lines => foldClaims(lines).standing,
|
|
36
|
+
});
|
|
37
|
+
return [...standing];
|
|
38
|
+
}
|
|
39
|
+
/** One check shared by the CLI and the read-only native host. */
|
|
40
|
+
export function assertStandingView(lines) {
|
|
41
|
+
const view = viewsOf(lines).get('standing');
|
|
42
|
+
if (!view || view.shape || !view.regions.some(region => region.name === 'standing'))
|
|
43
|
+
throw new BoundRefusal('standing/view', 'REFUSE·view standing requires a declared standing view');
|
|
44
|
+
}
|
|
45
|
+
export function publicStandingProjection(history, anchor) {
|
|
46
|
+
return publishedStandingLines(projectAuthenticatedStanding(history, anchor));
|
|
47
|
+
}
|
|
48
|
+
export function publishedStandingLines(standing) {
|
|
49
|
+
return standing.filter(line => !isConfig(line) || fieldOf(line, 'value') !== 'withdraw');
|
|
50
|
+
}
|
package/dist/land/act.js
CHANGED
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { basename, dirname, existsSync, join, mkdirSync, rmSync, writeFileSync } from "../host/io.js";
|
|
3
|
+
import { basename, dirname, existsSync, join, mkdirSync, rmSync, writeFileSync, realpathSync, resolve } from "../host/io.js";
|
|
4
|
+
import { BoundRefusal } from "../host/refusals.js";
|
|
4
5
|
import { selfName } from "../fold/claims.js";
|
|
5
6
|
import { placesIn } from "../fold/places.js";
|
|
6
7
|
import { coordinatesUnder, observeText } from "../observe/files.js";
|
|
8
|
+
const hostContext = new AsyncLocalStorage();
|
|
9
|
+
export const withActHost = (host, run) => hostContext.run(host, run);
|
|
10
|
+
const actName = () => hostContext.getStore()?.name ?? selfName();
|
|
11
|
+
const report = (text) => { const host = hostContext.getStore(); if (host)
|
|
12
|
+
host.report(text);
|
|
13
|
+
else
|
|
14
|
+
process.stderr.write(text); };
|
|
15
|
+
const checkCancellation = () => { if (hostContext.getStore()?.signal?.aborted)
|
|
16
|
+
throw new BoundRefusal('host/cancelled', 'REFUSE·host/cancelled'); };
|
|
17
|
+
/** Resolve aliases before acquiring the same native lock protocol as the CLI. */
|
|
18
|
+
export function canonicalActStore(store) {
|
|
19
|
+
let at = resolve(store);
|
|
20
|
+
const tail = [];
|
|
21
|
+
while (!existsSync(at)) {
|
|
22
|
+
const parent = dirname(at);
|
|
23
|
+
if (parent === at)
|
|
24
|
+
break;
|
|
25
|
+
tail.unshift(basename(at));
|
|
26
|
+
at = parent;
|
|
27
|
+
}
|
|
28
|
+
return join(realpathSync(at), ...tail);
|
|
29
|
+
}
|
|
7
30
|
const context = new AsyncLocalStorage();
|
|
8
31
|
const held = new Map();
|
|
9
32
|
const pauseFor = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); });
|
|
@@ -20,8 +43,8 @@ function overAMinute(at, began, holder) {
|
|
|
20
43
|
if (Date.now() - began < MINUTE)
|
|
21
44
|
return;
|
|
22
45
|
const who = holder === undefined ? 'a process' : `${holder.pid} · ${holder.what} since ${holder.since}`;
|
|
23
|
-
|
|
24
|
-
throw new Error(`${
|
|
46
|
+
report(`RED wait · ${named(at)} is held by ${who} · a wait over a minute is a red, not a pause\n`);
|
|
47
|
+
throw new Error(`${actName()}: REFUSE·stale nothing landed · a wait over a minute is a red, not a pause`);
|
|
25
48
|
}
|
|
26
49
|
const alive = (pid) => {
|
|
27
50
|
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
@@ -37,7 +60,7 @@ const alive = (pid) => {
|
|
|
37
60
|
const regionAt = (store, region) => {
|
|
38
61
|
const coordinate = region.replace(/\/+$/, '');
|
|
39
62
|
if ((coordinate && coordinate.split('/').some((step) => step === '..' || step === '.' || step === '')) || coordinate.startsWith('/'))
|
|
40
|
-
throw new Error(`${
|
|
63
|
+
throw new Error(`${actName()}: REFUSE·lock invalid region coordinate`);
|
|
41
64
|
return join(store, 'locks', coordinate ? encodeURIComponent(coordinate).replace(/^\./, '%2E').replace(/^_root$/, '%5Froot') : ROOT);
|
|
42
65
|
};
|
|
43
66
|
const coordinateOf = (file) => {
|
|
@@ -77,12 +100,13 @@ async function hold(at, what) {
|
|
|
77
100
|
return;
|
|
78
101
|
const frame = context.getStore();
|
|
79
102
|
if (frame === undefined)
|
|
80
|
-
throw new Error(`${
|
|
103
|
+
throw new Error(`${actName()}: REFUSE·lock no act owns this request`);
|
|
81
104
|
const began = Date.now();
|
|
82
105
|
const token = randomUUID();
|
|
83
106
|
let told = false;
|
|
84
107
|
mkdirSync(dirname(at), { recursive: true });
|
|
85
108
|
for (;;) {
|
|
109
|
+
checkCancellation();
|
|
86
110
|
try {
|
|
87
111
|
writeFileSync(at, `${process.pid} ${what.replace(/\s+/g, '_')} ${new Date().toISOString()} ${token}\n`, { flag: 'wx' });
|
|
88
112
|
break;
|
|
@@ -97,17 +121,17 @@ async function hold(at, what) {
|
|
|
97
121
|
const holder = holderOf(at);
|
|
98
122
|
// Removing a dead owner's lock here would race with another process's recovery. Recovery is a separate host act.
|
|
99
123
|
if (holder !== undefined && !alive(holder.pid)) {
|
|
100
|
-
throw new Error(`${
|
|
124
|
+
throw new Error(`${actName()}: REFUSE·stale ${named(at)} belongs to exited process ${holder.pid}; host recovery required`);
|
|
101
125
|
}
|
|
102
126
|
overAMinute(at, began, holder);
|
|
103
127
|
if (!told && holder !== undefined) {
|
|
104
|
-
|
|
128
|
+
report(`WAIT ${named(at)} is held by ${holder.pid} · ${holder.what} since ${holder.since}\n`);
|
|
105
129
|
told = true;
|
|
106
130
|
}
|
|
107
131
|
await pauseFor(250);
|
|
108
132
|
}
|
|
109
133
|
if (told)
|
|
110
|
-
|
|
134
|
+
report(`HELD ${named(at)} after ${Date.now() - began} ms waiting\n`);
|
|
111
135
|
held.set(at, token);
|
|
112
136
|
frame.locks.set(at, token);
|
|
113
137
|
if (!process.listeners('exit').includes(releaseAll))
|
|
@@ -125,16 +149,17 @@ function releaseAll() {
|
|
|
125
149
|
for (const [at, token] of [...held])
|
|
126
150
|
release(at, token);
|
|
127
151
|
}
|
|
128
|
-
process.
|
|
129
|
-
|
|
130
|
-
releaseAll();
|
|
131
|
-
|
|
132
|
-
});
|
|
152
|
+
/** Only the CLI installs process termination behavior. Importing a host does not. */
|
|
153
|
+
export function installActSignalHandler() {
|
|
154
|
+
process.once('SIGTERM', () => { report(`${actName()}: REFUSE·crash signal\n`); releaseAll(); process.exit(1); });
|
|
155
|
+
}
|
|
133
156
|
async function framed(takes, act) {
|
|
134
157
|
const frame = { parent: context.getStore(), locks: new Map() };
|
|
135
158
|
return context.run(frame, async () => {
|
|
136
159
|
try {
|
|
160
|
+
checkCancellation();
|
|
137
161
|
await takes();
|
|
162
|
+
checkCancellation();
|
|
138
163
|
return await act();
|
|
139
164
|
}
|
|
140
165
|
finally {
|
|
@@ -149,14 +174,14 @@ function guardedRegions(store, regions, what, act, rootAuthority) {
|
|
|
149
174
|
const tree = treeAt(store);
|
|
150
175
|
const asked = [...new Set((regions.length ? regions : ['']).map((one) => one.replace(/\/+$/, '')))];
|
|
151
176
|
if (rootAuthority && (asked.includes('') || (ownsRegion(store) && !owns(tree) && !ownsAuthority(store))))
|
|
152
|
-
throw new Error(`${
|
|
177
|
+
throw new Error(`${actName()}: REFUSE·lock authority dependencies and child regions must be declared together`);
|
|
153
178
|
if (owns(tree) || asked.every((region) => owns(regionAt(store, region)))) {
|
|
154
179
|
for (const region of asked)
|
|
155
180
|
await hold(regionAt(store, region), what);
|
|
156
181
|
return;
|
|
157
182
|
}
|
|
158
183
|
if (ownsRegion(store))
|
|
159
|
-
throw new Error(`${
|
|
184
|
+
throw new Error(`${actName()}: REFUSE·lock declare every region before entering an act`);
|
|
160
185
|
const already = owns(tree);
|
|
161
186
|
// The tree lock is the acquisition gate. A whole-tree act cannot enter between checking it and taking regions.
|
|
162
187
|
await hold(tree, what);
|
|
@@ -172,17 +197,18 @@ function guardedRegions(store, regions, what, act, rootAuthority) {
|
|
|
172
197
|
const began = Date.now();
|
|
173
198
|
let told = false;
|
|
174
199
|
for (let holder = holderOf(at); holder !== undefined; holder = holderOf(at)) {
|
|
200
|
+
checkCancellation();
|
|
175
201
|
if (!alive(holder.pid))
|
|
176
|
-
throw new Error(`${
|
|
202
|
+
throw new Error(`${actName()}: REFUSE·stale ${named(at)} belongs to exited process ${holder.pid}; host recovery required`);
|
|
177
203
|
overAMinute(at, began, holder);
|
|
178
204
|
if (!told) {
|
|
179
|
-
|
|
205
|
+
report(`WAIT ${named(at)} is held by ${holder.pid} · ${holder.what} since ${holder.since}\n`);
|
|
180
206
|
told = true;
|
|
181
207
|
}
|
|
182
208
|
await pauseFor(250);
|
|
183
209
|
}
|
|
184
210
|
if (told)
|
|
185
|
-
|
|
211
|
+
report(`HELD ${named(at)} after ${Date.now() - began} ms waiting\n`);
|
|
186
212
|
}
|
|
187
213
|
// Registered under the same acquisition gate as every legacy writer.
|
|
188
214
|
// Root writes overlap all locks; siblings keep distinct region locks.
|
|
@@ -207,12 +233,14 @@ function guardedRegions(store, regions, what, act, rootAuthority) {
|
|
|
207
233
|
}, act);
|
|
208
234
|
}
|
|
209
235
|
export function underTheRegions(store, regions, what, act) {
|
|
236
|
+
store = canonicalActStore(store);
|
|
210
237
|
return guardedRegions(store, regions, what, act, false);
|
|
211
238
|
}
|
|
212
239
|
/** Protect the root authority/policy read through child publication. This is
|
|
213
240
|
* exclusion only: callers must authenticate contexts and compare prepared heads
|
|
214
241
|
* while held. It creates no grant, clock advance, lease or automatic recovery. */
|
|
215
242
|
export function underAuthorityRegions(store, regions, what, act) {
|
|
243
|
+
store = canonicalActStore(store);
|
|
216
244
|
return guardedRegions(store, regions, what, act, true);
|
|
217
245
|
}
|
|
218
246
|
/** Protect an authority snapshot without taking a child region. The acquisition
|
|
@@ -220,12 +248,13 @@ export function underAuthorityRegions(store, regions, what, act) {
|
|
|
220
248
|
* shared read; existing child acts continue independently. This is exclusion,
|
|
221
249
|
* never authorization or a clock advance. */
|
|
222
250
|
export function underRootAuthority(store, what, act) {
|
|
251
|
+
store = canonicalActStore(store);
|
|
223
252
|
return framed(async () => {
|
|
224
253
|
const tree = treeAt(store), root = regionAt(store, '');
|
|
225
254
|
if (owns(tree) || owns(root) || ownsAuthority(store))
|
|
226
255
|
return;
|
|
227
256
|
if (ownsRegion(store))
|
|
228
|
-
throw new Error(`${
|
|
257
|
+
throw new Error(`${actName()}: REFUSE·lock authority dependencies must be declared before entering a region act`);
|
|
229
258
|
await hold(tree, what);
|
|
230
259
|
try {
|
|
231
260
|
await hold(root, what);
|
|
@@ -246,49 +275,54 @@ export function underRootAuthority(store, what, act) {
|
|
|
246
275
|
/** The fixed host may hand its child evidence of an actual held exclusion.
|
|
247
276
|
* It is an operational envelope, not a grant, signer or receipt identity. */
|
|
248
277
|
export function rootAuthorityProtection(store) {
|
|
278
|
+
store = canonicalActStore(store);
|
|
249
279
|
const locks = [treeAt(store), regionAt(store, ''), ...held.keys()].filter(at => at === treeAt(store) || at === regionAt(store, '') || dirname(at) === join(store, 'locks') && basename(at).startsWith('@authority-'));
|
|
250
280
|
const lock = locks.find(owns);
|
|
251
281
|
if (lock === undefined)
|
|
252
|
-
throw Error(`${
|
|
282
|
+
throw Error(`${actName()}: REFUSE·lock root authority is not protected`);
|
|
253
283
|
const holder = holderOf(lock);
|
|
254
284
|
if (holder?.pid !== process.pid || holder.token !== held.get(lock))
|
|
255
|
-
throw Error(`${
|
|
285
|
+
throw Error(`${actName()}: REFUSE·lock authority protection changed`);
|
|
256
286
|
return Object.freeze({ store, lock, pid: holder.pid, token: holder.token });
|
|
257
287
|
}
|
|
258
288
|
/** expectedStore and expectedParent come from the fixed host's inheritance
|
|
259
289
|
* channel, never from world data. Matching a live lock proves exclusion only;
|
|
260
290
|
* signature, coverage and current context still require verification. */
|
|
261
291
|
export function assertRootAuthorityProtection(proof, expectedStore, expectedParent) {
|
|
292
|
+
expectedStore = canonicalActStore(expectedStore);
|
|
262
293
|
if (!proof || proof.store !== expectedStore || !Number.isSafeInteger(expectedParent) || expectedParent <= 0 || proof.pid !== expectedParent
|
|
263
294
|
|| typeof proof.lock !== 'string' || typeof proof.token !== 'string' || !proof.token
|
|
264
295
|
|| !(proof.lock === treeAt(expectedStore) || proof.lock === regionAt(expectedStore, '')
|
|
265
296
|
|| dirname(proof.lock) === join(expectedStore, 'locks') && basename(proof.lock).startsWith('@authority-'))) {
|
|
266
|
-
throw Error(`${
|
|
297
|
+
throw Error(`${actName()}: REFUSE·lock invalid inherited authority protection`);
|
|
267
298
|
}
|
|
268
299
|
const holder = holderOf(proof.lock);
|
|
269
300
|
if (!alive(expectedParent) || holder?.pid !== proof.pid || holder.token !== proof.token)
|
|
270
|
-
throw Error(`${
|
|
301
|
+
throw Error(`${actName()}: REFUSE·lock inherited authority protection ended`);
|
|
271
302
|
}
|
|
272
303
|
/** A child can publish independently, but the joined root projection and its
|
|
273
304
|
* derived ledger appends have one writer. Authority protection already excludes
|
|
274
305
|
* root mutations, so this lock needs no acquisition-gate upgrade. */
|
|
275
306
|
export function underRootProjection(store, what, act) {
|
|
307
|
+
store = canonicalActStore(store);
|
|
276
308
|
return framed(async () => {
|
|
277
309
|
if (owns(treeAt(store)) || owns(regionAt(store, '')))
|
|
278
310
|
return;
|
|
279
311
|
if (!ownsAuthority(store))
|
|
280
|
-
throw new Error(`${
|
|
312
|
+
throw new Error(`${actName()}: REFUSE·lock root projection requires authority protection`);
|
|
281
313
|
await hold(projectionAt(store), what);
|
|
282
314
|
}, act);
|
|
283
315
|
}
|
|
284
316
|
export const holdRoot = async (store, what) => {
|
|
317
|
+
store = canonicalActStore(store);
|
|
285
318
|
if (!owns(treeAt(store)))
|
|
286
319
|
await hold(regionAt(store, ''), what);
|
|
287
320
|
};
|
|
288
321
|
export function underTheLock(store, what, act) {
|
|
322
|
+
store = canonicalActStore(store);
|
|
289
323
|
return framed(async () => {
|
|
290
324
|
if (!owns(treeAt(store)) && ownsRegion(store))
|
|
291
|
-
throw new Error(`${
|
|
325
|
+
throw new Error(`${actName()}: REFUSE·lock a region act cannot upgrade to the tree; declare the tree before entering`);
|
|
292
326
|
await hold(treeAt(store), what);
|
|
293
327
|
const locks = join(store, 'locks');
|
|
294
328
|
const active = lockFiles(locks);
|
|
@@ -299,4 +333,4 @@ export function underTheLock(store, what, act) {
|
|
|
299
333
|
await hold(regionAt(store, ''), what);
|
|
300
334
|
}, act);
|
|
301
335
|
}
|
|
302
|
-
export const holds = (store, region) => [treeAt(store), regionAt(store, region)].some(owns);
|
|
336
|
+
export const holds = (store, region) => { store = canonicalActStore(store); return [treeAt(store), regionAt(store, region)].some(owns); };
|
package/dist/observe/run.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hostOutput } from "../host/call-context.js";
|
|
1
2
|
import { boundedProcess } from "../host/process-lifetime.js";
|
|
2
3
|
import { spawnSync } from 'node:child_process';
|
|
3
4
|
import { existsSync } from 'node:fs';
|
|
@@ -31,7 +32,7 @@ export function runReader(module, root, inputs, limits) {
|
|
|
31
32
|
if (ran.status !== 0)
|
|
32
33
|
throw new Error(`${module}: reader exited ${ran.status ?? `with signal ${ran.signal}`}\n${ran.stderr ?? ''}${ran.stdout ?? ''}`);
|
|
33
34
|
if (ran.stderr)
|
|
34
|
-
|
|
35
|
+
hostOutput('error', ran.stderr);
|
|
35
36
|
return responsesOf(ran.stdout ?? '', inputs.length).map((answer) => {
|
|
36
37
|
if (answer.kind !== 'fact')
|
|
37
38
|
throw new Error(`REFUSE·reader ${module} · ${answer.why ?? 'no reading answered'}`);
|
package/dist/observe/runner.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hostInstrumentRuntime } from "../host/call-context.js";
|
|
1
2
|
import { spawnSync } from 'node:child_process';
|
|
2
3
|
import { sha } from "../host/hash.js";
|
|
3
4
|
import { existsSync, mkdirSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
@@ -10,17 +11,21 @@ import { landBlob } from "../land/ledger.js";
|
|
|
10
11
|
import { EXTENSION, LOCK, publicLock } from '@lapxo/topos/wire';
|
|
11
12
|
import { foldClaims, isWire } from "../fold/claims.js";
|
|
12
13
|
const self = () => process.argv[1] ?? '';
|
|
13
|
-
const fromBlob = () => extname(existsSync(self()) ? realpathSync(self()) : self()) === '';
|
|
14
|
+
const fromBlob = () => hostInstrumentRuntime() !== undefined ? false : extname(existsSync(self()) ? realpathSync(self()) : self()) === '';
|
|
14
15
|
/**
|
|
15
16
|
* Where the instrument lies and what it is run from: from source or compiled modules, bound is two steps above this runner module; as a blob, the command is a blob of the tree's store, named by the digest bound's own lock names for
|
|
16
17
|
* it, and bound lies beside that store. Other processes use the same runtime directory and extension; only source readers receive source conditions. A name that resolves to no blob beside it is refused by name
|
|
17
18
|
* before anything is started, never handed to the runtime as the store's own directory.
|
|
18
19
|
*/
|
|
19
20
|
// Blob bundles have no module URL; source and compiled modules carry their own entry location.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
21
|
+
// Native library imports do not inspect the embedding application's argv.
|
|
22
|
+
// The CLI discovers its default runtime only when a CLI operation requests it.
|
|
23
|
+
const moduleAt = () => fromBlob() ? undefined : fileURLToPath(import.meta.url);
|
|
24
|
+
const runtimeAt = () => { const explicit = hostInstrumentRuntime(); if (explicit)
|
|
25
|
+
return explicit.directory; const at = moduleAt(); return at === undefined ? '' : resolve(dirname(at), '..'); };
|
|
26
|
+
const runtimeExtension = () => { const explicit = hostInstrumentRuntime(); if (explicit)
|
|
27
|
+
return explicit.extension; const at = moduleAt(); return at === undefined ? '' : extname(at); };
|
|
28
|
+
export const ownRoot = () => hostInstrumentRuntime()?.root ?? (fromBlob() ? join(resolve(dirname(self()), '..', '..', '..'), 'bound') : resolve(runtimeAt(), '..'));
|
|
24
29
|
/** A published instrument carries its own content store even before any ledger
|
|
25
30
|
* exists. Public-lock recognition grants no authority; legacy signed delivery
|
|
26
31
|
* trees retain their historical colocated-ledger or sibling-store layout. */
|
|
@@ -34,6 +39,9 @@ export const ownStore = () => instrumentStoreAt(ownRoot());
|
|
|
34
39
|
// An act is judged by the instrument it started with, even when that act changes its colocated lock.
|
|
35
40
|
let instrumentLock;
|
|
36
41
|
export const ownLock = () => {
|
|
42
|
+
const explicit = hostInstrumentRuntime();
|
|
43
|
+
if (explicit)
|
|
44
|
+
return explicit.lock;
|
|
37
45
|
if (instrumentLock !== undefined)
|
|
38
46
|
return instrumentLock;
|
|
39
47
|
const lines = (observeText(join(ownRoot(), LOCK)) ?? '').split('\n').filter(isWire);
|
|
@@ -43,10 +51,10 @@ export const ownLock = () => {
|
|
|
43
51
|
: foldClaims(published).standing;
|
|
44
52
|
return instrumentLock;
|
|
45
53
|
};
|
|
46
|
-
export const sourceOf = () => (fromBlob() ? join(ownRoot(), 'src', 'cli', 'verb.ts') : self());
|
|
54
|
+
export const sourceOf = () => hostInstrumentRuntime()?.entry ?? (fromBlob() ? join(ownRoot(), 'src', 'cli', 'verb.ts') : self());
|
|
47
55
|
export function processOf(name, folder) {
|
|
48
56
|
if (!fromBlob())
|
|
49
|
-
return join(runtimeAt, folder ?? 'cli', `${name}${runtimeExtension}`);
|
|
57
|
+
return join(runtimeAt(), folder ?? 'cli', `${name}${runtimeExtension()}`);
|
|
50
58
|
const named = ownLock().find((line) => fieldOf(line, 'scope') === `runner/${name}`);
|
|
51
59
|
const blob = fieldOf(named ?? '', 'value').replace(/^[^:]*:/, '');
|
|
52
60
|
if (!blob || !existsSync(join(dirname(self()), blob))) {
|
|
@@ -87,7 +95,7 @@ export function bundleOf(module) {
|
|
|
87
95
|
}
|
|
88
96
|
/** A reader run by a blob is a blob itself: its bundle, with everything it imports but the compiler. */
|
|
89
97
|
export function readerOf(module) {
|
|
90
|
-
const source = { path: module, flags: runtimeExtension === '.ts' ? ['--disable-warning=ExperimentalWarning', '--disable-warning=MODULE_TYPELESS_PACKAGE_JSON'] : [] };
|
|
98
|
+
const source = { path: module, flags: runtimeExtension() === '.ts' ? ['--disable-warning=ExperimentalWarning', '--disable-warning=MODULE_TYPELESS_PACKAGE_JSON'] : [] };
|
|
91
99
|
const hex = fromBlob() ? bundleOf(module) : undefined;
|
|
92
100
|
return hex === undefined ? source : { path: join(ownStore(), 'cas', 'blobs', hex), flags: [] };
|
|
93
101
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Verified executable closure delivery
|
|
2
|
+
|
|
3
|
+
Needs are arbitrary declared names, not a kernel profile enumeration.
|
|
4
|
+
|
|
5
|
+
All requested entries share the existing moduleClosure traversal; no second walker, parser or algebra.
|
|
6
|
+
|
|
7
|
+
Host requires already authenticated snapshot identities; a bundle digest establishes content identity, not authority or correctness.
|
|
8
|
+
|
|
9
|
+
Delivery preserves each reached JS/JSON member and explicitly pinned notices byte for byte. Separate digests identify the closure manifest and the exact UTF-8 JSON container.
|
|
10
|
+
|
|
11
|
+
Canonical delivery sorts needs, reached modules, library bindings and notices; duplicate needs do not duplicate code; unsupported executable forms refuse by name.
|
|
12
|
+
|
|
13
|
+
Mandatory member, source byte and response byte budgets apply before delivery. Pre-aborted or observed cancellation refuses. Synchronous work is not a preemptible execution sandbox.
|
|
14
|
+
|
|
15
|
+
Portable ESM delivery refuses host builtins, runtime loader access, CommonJS loads, calculated/escaped loader expressions and unsupported URL spellings before entry execution.
|
|
16
|
+
|
|
17
|
+
Consumer verifies the expected container digest and repeats the same closure preflight over exactly the delivered members. Derived import maps are never trusted as authority.
|
|
18
|
+
|
|
19
|
+
The host must verify actual mounted bytes again before execution; the test host demonstrates this but is not a public browser host.
|
|
20
|
+
|
|
21
|
+
Existing native, program and worker custody remain unchanged. Acorn routes stay explicitly retained under A-F gate.
|
|
22
|
+
|
|
23
|
+
No external fetch or general demand resolver is introduced. Missing bytes remain outside completed delivery and must be resolved by the existing host contract.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scope": "contract/published/closure-delivery",
|
|
3
|
+
"members": [
|
|
4
|
+
{
|
|
5
|
+
"role": "contract",
|
|
6
|
+
"path": "docs/contracts/closure-delivery.md",
|
|
7
|
+
"digest": "sha256:bdadeefcf8042749e642a453adc7d07944ceab3fbd62de46209ea3ed25d05c2e"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"role": "yes",
|
|
11
|
+
"path": "samples/module-delivery/inputs/delivery.json",
|
|
12
|
+
"digest": "sha256:a6df1d11537a0792c9772b1874c7e127e0fda22203f756d6f0eb104d072a9df1"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"role": "vector",
|
|
16
|
+
"path": "vectors/native/module-delivery.vector.json",
|
|
17
|
+
"digest": "sha256:3de9f160f89764e1153a2719ab04447142fe187bf476b3060d860e81e9c37c02"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"role": "no",
|
|
21
|
+
"path": "samples/no/module-delivery/delivery.json",
|
|
22
|
+
"digest": "sha256:f95b1c3129896baaecb4ae7860afc11dcaaef0a2e47221b73044bc5a4681181f"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Native in-process host
|
|
2
|
+
|
|
3
|
+
The public `@lapxo/bound/host` entry offers authenticated Root and contextual standing reads, Root-lot and contextual-act admission, historical recovery, and explicit native instrument and receiver adapters. Node is required for this native host. The pure `/engine` and reference backends retain their separate contracts.
|
|
4
|
+
|
|
5
|
+
## Classic Root lot
|
|
6
|
+
|
|
7
|
+
`landRootLotInProcess(context, ports)` accepts LF-framed, canonical, unsigned target configuration records. The receiver supplies the admitted place, exact history snapshot, implementation runtime, reading identity, byte/record/time bounds, content port and canonical-line signer. The lot cannot choose those capabilities. It calls the same admission judges and atomic result implementation as the CLI; a signed line is not automatically an admitted line.
|
|
8
|
+
|
|
9
|
+
The snapshot is checked before acquisition and before admission. Coverage is checked before invoking the signer. A change while signing refuses instead of rebasing. A fresh repeated held lot does not acquire another signature. `rootLotSnapshot` names complete admission history; it is not a signature or permission.
|
|
10
|
+
|
|
11
|
+
## Effects and recovery
|
|
12
|
+
|
|
13
|
+
Cancellation before visibility leaves no partial act. A publication error whose visibility is unknown returns `uncertain` with the native act identity. A failure after commit retains that identity and reports incomplete conformance, not rollback. `recoverRootActInProcess` authenticates the historical result without signing or reviving current grants. Root and contextual admission remain distinct operations; the latter consumes already signed contextual originals and acquires receiver receipts.
|
|
14
|
+
|
|
15
|
+
The host selects context explicitly on every call. Calls do not interpret consumer argv or print to its console. Diagnostics are bounded returned data. Native storage shares the CLI exclusion protocol. Returned reading commitments are unsigned data and do not pay release demands.
|
|
16
|
+
|
|
17
|
+
## Public quartet
|
|
18
|
+
|
|
19
|
+
The two positive places are a seed accession library and a coastal tide survey. Each installs the public host boundary, bootstraps disposable fixture authority through the real CLI, admits a domain-specific Root lot, compares native result recovery and repeats the held lot without signing again. Negatives alter bytes, remove signer coverage, cancel before commit and offer a noncanonical lot; no negative may create a visible act. Test keys are created only for the disposable fixture, never shipped.
|
|
20
|
+
|
|
21
|
+
The executable vector captures the instrument output. The quartet proves this declared public boundary; it does not certify all remaining host views, another machine, a release policy or an owner admission. Head, browser hosts, algebra, wire and ceilings are outside this change.
|