@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
package/dist/land/ledger.js
CHANGED
|
@@ -40,12 +40,30 @@ function journalLines(store, writer, place, legacy) {
|
|
|
40
40
|
journalMemo.set(key, lines);
|
|
41
41
|
return lines;
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
function legacyWriters(store) {
|
|
44
44
|
const at = join(store, 'ledger');
|
|
45
|
-
|
|
45
|
+
return existsSync(at) ? readdirSync(at).filter(name => name.endsWith(EXTENSION)).map(name => name.slice(0, -EXTENSION.length)) : [];
|
|
46
|
+
}
|
|
47
|
+
export function ledgerWriters(store) {
|
|
48
|
+
const legacy = legacyWriters(store);
|
|
46
49
|
const committed = committedLocalActs(store).flatMap(act => act.bundle.placements?.filter(p => p.place === '.').flatMap(p => actLines(p.records).map(line => fieldOf(line, 'by'))) ?? []);
|
|
47
50
|
return [...new Set([...legacy, ...committed])];
|
|
48
51
|
}
|
|
52
|
+
/** One synchronous root-history read, not a retained authority snapshot.
|
|
53
|
+
* Preserve writer order and each writer's legacy-before-committed ordering.
|
|
54
|
+
* Every call rereads the complete committed inventory exactly once. */
|
|
55
|
+
export function rootLedgerLines(store) {
|
|
56
|
+
const legacy = legacyWriters(store), acts = committedLocalActs(store);
|
|
57
|
+
const fresh = acts.flatMap(act => act.bundle.placements?.filter(p => p.place === '.').flatMap(p => actLines(p.records)) ?? []);
|
|
58
|
+
const writers = [...new Set([...legacy, ...fresh.map(line => fieldOf(line, 'by'))])];
|
|
59
|
+
return writers.flatMap(writer => {
|
|
60
|
+
// Authority-history identity needs actual bytes. A writer's size and mtime
|
|
61
|
+
// cannot distinguish a same-size edit or stand in for an admission proof.
|
|
62
|
+
const at = writerFile(store, writer), prior = existsSync(at) ? readFileSync(at, 'utf8').split('\n').filter(line => line.startsWith(PROTOCOL)) : [];
|
|
63
|
+
const committed = fresh.filter(line => fieldOf(line, 'by') === writer);
|
|
64
|
+
return committed.length ? [...new Set([...prior, ...committed])] : prior;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
49
67
|
export function landShard(store, writer, region, lines) {
|
|
50
68
|
return appendNew(shardFile(store, writer, region), lines);
|
|
51
69
|
}
|
|
@@ -27,3 +27,7 @@ type Ask = (declaration: Declaration, one: Asking, verb: 'render' | 'read', shap
|
|
|
27
27
|
*/
|
|
28
28
|
export declare const askingOf: (fold: Placed) => Ask;
|
|
29
29
|
export declare function capsuleReadingsOver(fold: Placed): readonly string[];
|
|
30
|
+
/** Current judgement excludes authenticated outcomes of the selected producer batches; retained evidence stays in its carrier. */
|
|
31
|
+
export declare const currentReceiptObservations: (fold: Parameters<typeof capsulesOf>[0]) => readonly string[];
|
|
32
|
+
/** Preserve prior producer outcomes in the owning place's public carrier, independently of current judgement. */
|
|
33
|
+
export declare const retiredReceiptHistory: (fold: Parameters<typeof capsulesOf>[0]) => readonly string[];
|
package/dist/observe/regions.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { childFlags } from "../host/capsule.js";
|
|
2
|
+
import { readingProfile, readingExecution, withReadingBoundary } from "../host/reading-context.js";
|
|
3
|
+
import { readingIdentityBytes } from '@lapxo/topos/contract';
|
|
4
|
+
import { portableReadingReceipt, verifiedReadingReceipts } from "../host/reading-closures.js";
|
|
5
|
+
import { bytesDigest, instrumentCoordinates } from "../fold/digests.js";
|
|
6
|
+
import { ownRoot, processOf } from "./runner.js";
|
|
7
|
+
import { contentPolicy } from "../host/content-store.js";
|
|
8
|
+
import { carriedFrom } from "../fold/resolved.js";
|
|
1
9
|
import { selectedCapsules } from "../host/selected-capsules.js";
|
|
2
10
|
import { selectionLines, projectionLines } from "../fold/reach.js";
|
|
3
11
|
export { selectionLines } from "../fold/reach.js";
|
|
@@ -22,6 +30,7 @@ import { ownLock } from "./runner.js";
|
|
|
22
30
|
import { nameOf } from "../render/page.js";
|
|
23
31
|
import { placeLines, takenFor } from "../fold/place-inputs.js";
|
|
24
32
|
import { isRegionDeclaration } from "../fold/region-declarations.js";
|
|
33
|
+
const receiptHasher = (store) => (text) => bytesDigest(store, new TextEncoder().encode(text));
|
|
25
34
|
const fact = (scope, value, form = 'alphabet', measure = 'fact') => canonical({ scope, role: 'writes', form, measure, value, by: 'fold', at: 'place:fold' });
|
|
26
35
|
const described = (local = []) => {
|
|
27
36
|
const lock = ownLock();
|
|
@@ -164,12 +173,30 @@ function capsuleReadings(fold) {
|
|
|
164
173
|
return known.get(place) ?? known.set(place, readingsAt(fold, place)).get(place);
|
|
165
174
|
}
|
|
166
175
|
function readingsAt(fold, place) {
|
|
167
|
-
const asking = askingOf(fold);
|
|
168
176
|
const speaker = keyFor(fold.store, 'read');
|
|
169
177
|
return capsulesOf(fold).flatMap((capsule) => {
|
|
170
178
|
const writing = new Set(capsule.lines.filter((line) => fieldOf(line, 'scope').startsWith('region/') && fieldOf(line, 'measure') === 'writes').map((line) => fieldOf(line, 'scope').slice('region/'.length)));
|
|
171
179
|
const receipts = Object.keys(capsule.declaration.writes).filter((name) => capsule.declaration.writes[name] === 'receipt' && !writing.has(name)).sort();
|
|
180
|
+
const target = modernReceiptInputs(fold, capsule.selection ?? capsule.digest);
|
|
181
|
+
const asking = askingOf({ ...fold, observed: target.current });
|
|
172
182
|
const requests = receipts.flatMap((name) => ((request) => (request === undefined ? [] : [request]))(asking(capsule.declaration, { name, at: 3 }, 'read', '')));
|
|
183
|
+
const modern = readingProfile(fold.standing, fieldOf);
|
|
184
|
+
if (modern && requests.length) {
|
|
185
|
+
const hash = receiptHasher(fold.store);
|
|
186
|
+
const batch = hash(JSON.stringify([capsule.selection ?? capsule.digest, capsule.digest, receipts]));
|
|
187
|
+
const coordinates = instrumentCoordinates(fold.store, processOf('verb'), [], ownRoot(), ownLock(), true), instrument = hash([...coordinates.values()].sort().join('\n'));
|
|
188
|
+
const legacy = target.legacy;
|
|
189
|
+
return requests.flatMap(request => {
|
|
190
|
+
const execution = readingExecution({ instrument, implementation: capsule.digest, policy: hash(JSON.stringify([contentPolicy(), capsule.selection ?? null, capsule.lines])), inputs: [['world', capsule.selection ?? capsule.digest], ['batch', batch], ['request', hash(JSON.stringify(request))], ...legacy, ...[...coordinates].map(([name, id]) => ['host/' + relative(ownRoot(), name), id])] }, hash, { env: process.env, cwd: fold.root, flags: childFlags() });
|
|
191
|
+
const closure = new TextDecoder().decode(readingIdentityBytes(execution.context)), input = hash(closure), said = withReadingBoundary(input, execution, () => capsule.ask([request]))[0];
|
|
192
|
+
if (said?.kind !== 'fact')
|
|
193
|
+
throw Error(`REFUSE·capsule ${capsule.digest} · ${request.region} · ${said?.why ?? 'no answer'}`);
|
|
194
|
+
const by = speaker + ':' + batch.slice(7), at = 'place:' + input;
|
|
195
|
+
const lines = (said.claims ?? []).map((row) => canonical({ scope: row.scope, role: row.role, measure: row.measure, form: row.bound.kind === 'interval' ? 'interval' : 'alphabet', value: row.bound.kind === 'interval' ? `${row.bound.lo ?? 0}..${row.bound.hi ?? 0}` : (row.bound.values ?? []).join('|'), by, at }));
|
|
196
|
+
const output = lines.join('\n') + (lines.length ? '\n' : ''), scope = 'receipts/readings/' + hash(request.region ?? '').slice(7) + '/' + input.slice(7);
|
|
197
|
+
return [canonical({ scope: place, role: 'writes', form: 'alphabet', measure: 'observed', value: input.slice(7, 23), by, at }), ...lines, ...portableReadingReceipt(scope, by, closure, output, hash)];
|
|
198
|
+
});
|
|
199
|
+
}
|
|
173
200
|
const answers = capsule.ask(requests);
|
|
174
201
|
const refused = requests.filter((_, i) => answers[i]?.kind !== 'fact');
|
|
175
202
|
if (refused.length)
|
|
@@ -196,3 +223,84 @@ export function capsuleReadingsOver(fold) {
|
|
|
196
223
|
return [...capsuleReadings(fold), ...places.flatMap((place) => capsuleReadings({ ...fold, under: `${place}/`,
|
|
197
224
|
standing: [...new Set([...said.filter((one) => one.place === place).map((one) => one.line), ...fold.standing])] }))];
|
|
198
225
|
}
|
|
226
|
+
/** Current judgement excludes authenticated outcomes of the selected producer batches; retained evidence stays in its carrier. */
|
|
227
|
+
export const currentReceiptObservations = (fold) => modernReceiptInputs(fold).current;
|
|
228
|
+
/** Preserve prior producer outcomes in the owning place's public carrier, independently of current judgement. */
|
|
229
|
+
export const retiredReceiptHistory = (fold) => { const carried = carriedFrom(fold.store, (observeText(join(fold.root, fold.under ?? '', 'receipts.bound')) ?? '').split('\n').filter(line => line.startsWith('bound-lock/'))) ?? []; const all = [...new Set([...fold.observed, ...carried])], current = new Set(modernReceiptInputs({ ...fold, observed: all }).current); return all.filter(line => !current.has(line)); };
|
|
230
|
+
/** A modern capsule batch cannot consume its own retained outcomes. Full context units bind history; legacy conversion never supplies a current answer. */
|
|
231
|
+
function modernReceiptInputs(fold, target) {
|
|
232
|
+
if (!readingProfile(fold.standing, fieldOf))
|
|
233
|
+
return { current: fold.observed, legacy: [] };
|
|
234
|
+
const observed = [...new Set(fold.observed)];
|
|
235
|
+
const hash = receiptHasher(fold.store), allSelected = capsulesOf(fold), selected = allSelected.filter(c => { const writing = new Set(c.lines.filter(l => fieldOf(l, 'measure') === 'writes').map(l => fieldOf(l, 'scope').slice(7))); return Object.keys(c.declaration.writes).some(n => c.declaration.writes[n] === 'receipt' && !writing.has(n)); });
|
|
236
|
+
if (!selected.length)
|
|
237
|
+
return { current: fold.observed, legacy: [] };
|
|
238
|
+
const speaker = keyFor(fold.store, 'read'), removed = new Set(), refs = [];
|
|
239
|
+
const units = observed.filter(l => fieldOf(l, 'scope').startsWith('receipts/readings/') && fieldOf(l, 'measure') === 'bytes' && fieldOf(l, 'role') === 'writes');
|
|
240
|
+
if (verifiedReadingReceipts(fold.store, observed, hash, undefined, true) === undefined)
|
|
241
|
+
throw Error('REFUSE·capsule receipt carrier context invalid');
|
|
242
|
+
for (const capsule of selected) {
|
|
243
|
+
if (target !== undefined && (capsule.selection ?? capsule.digest) !== target)
|
|
244
|
+
continue;
|
|
245
|
+
const writing = new Set(capsule.lines.filter(l => fieldOf(l, 'measure') === 'writes').map(l => fieldOf(l, 'scope').slice(7))), receipts = Object.keys(capsule.declaration.writes).filter(n => capsule.declaration.writes[n] === 'receipt' && !writing.has(n)).sort();
|
|
246
|
+
if (!receipts.length)
|
|
247
|
+
continue;
|
|
248
|
+
const batch = hash(JSON.stringify([capsule.selection ?? capsule.digest, capsule.digest, receipts])), knownLegacy = new Set();
|
|
249
|
+
for (const unit of units) {
|
|
250
|
+
const about = fieldOf(unit, 'about');
|
|
251
|
+
if (!about)
|
|
252
|
+
continue;
|
|
253
|
+
let c;
|
|
254
|
+
try {
|
|
255
|
+
c = JSON.parse(about);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (!Array.isArray(c) || c[0] !== 'reading-context@1' || c[2] !== capsule.digest || !Array.isArray(c[4]) || !c[4].some(([n, id]) => n === 'batch' && id === batch) || !c[4].some(([n, id]) => n === 'world' && id === (capsule.selection ?? capsule.digest)))
|
|
261
|
+
continue;
|
|
262
|
+
const expectedBy = speaker + ':' + batch.slice(7);
|
|
263
|
+
if (fieldOf(unit, 'by') !== expectedBy)
|
|
264
|
+
continue;
|
|
265
|
+
if (fold.standing.some(line => fieldOf(line, 'scope') === 'keys/' + expectedBy && fieldOf(line, 'value') !== 'withdraw'))
|
|
266
|
+
throw Error('REFUSE·modern capsule receipt producer ambiguous');
|
|
267
|
+
const namedScope = fieldOf(unit, 'scope'), inputIdentity = fieldOf(unit, 'value');
|
|
268
|
+
if (!receipts.some(name => namedScope === 'receipts/readings/' + hash(name).slice(7) + '/' + inputIdentity.slice(7)))
|
|
269
|
+
throw Error('REFUSE·modern capsule receipt scope differs from declared region');
|
|
270
|
+
for (const [n, id] of c[4])
|
|
271
|
+
if (typeof n === 'string' && n.startsWith('legacy/output/') && n === 'legacy/output/' + id) {
|
|
272
|
+
knownLegacy.add(id);
|
|
273
|
+
refs.push([n, id]);
|
|
274
|
+
}
|
|
275
|
+
const by = fieldOf(unit, 'by'), input = fieldOf(unit, 'value'), scope = fieldOf(unit, 'scope'), output = observed.find(l => fieldOf(l, 'scope') === scope && fieldOf(l, 'by') === by && fieldOf(l, 'measure') === 'count');
|
|
276
|
+
const payload = output ? fieldOf(output, 'at') : '';
|
|
277
|
+
for (const l of observed)
|
|
278
|
+
if (fieldOf(l, 'by') === by && (fieldOf(l, 'at') === 'place:' + input || fieldOf(l, 'at') === payload || fieldOf(l, 'scope') === scope || fieldOf(l, 'scope') === [scope, 'identity'].join('/')))
|
|
279
|
+
removed.add(l);
|
|
280
|
+
}
|
|
281
|
+
const alias = capsule.digest.slice(7, 19), by = speaker + ':' + alias, groups = new Map();
|
|
282
|
+
for (const l of observed)
|
|
283
|
+
if (fieldOf(l, 'by') === by) {
|
|
284
|
+
const at = fieldOf(l, 'at');
|
|
285
|
+
groups.set(at, [...(groups.get(at) ?? []), l]);
|
|
286
|
+
}
|
|
287
|
+
if (groups.size) {
|
|
288
|
+
if (selected.filter(c => c.digest === capsule.digest).length !== 1 || receipts.length !== 1 || new Set(allSelected.filter(c => c.digest.slice(7, 19) === alias).map(c => c.digest)).size !== 1 || fold.standing.some(l => fieldOf(l, 'scope') === 'keys/' + by && fieldOf(l, 'value') !== 'withdraw'))
|
|
289
|
+
throw Error('REFUSE·legacy capsule receipt producer ambiguous');
|
|
290
|
+
for (const [at, group] of groups) {
|
|
291
|
+
if (!/^place:sha256:[a-f0-9]{64}$/.test(at) || group.filter(l => fieldOf(l, 'measure') === 'observed' && fieldOf(l, 'scope') === (fold.under || './')).length !== 1)
|
|
292
|
+
throw Error('REFUSE·legacy capsule receipt occurrence ambiguous');
|
|
293
|
+
const id = hash(group.join('\n'));
|
|
294
|
+
if (!knownLegacy.has(id)) {
|
|
295
|
+
const candidate = observed.filter(l => !removed.has(l) && !group.includes(l)), request = askingOf({ ...fold, observed: candidate })(capsule.declaration, { name: receipts[0], at: 3 }, 'read', '');
|
|
296
|
+
if (!request || at !== 'place:' + hash(JSON.stringify(request)))
|
|
297
|
+
throw Error('REFUSE·legacy capsule receipt context unavailable · expected ' + at + ' · current ' + (request ? hash(JSON.stringify(request)) : 'absent'));
|
|
298
|
+
}
|
|
299
|
+
refs.push(['legacy/output/' + id, id]);
|
|
300
|
+
for (const l of group)
|
|
301
|
+
removed.add(l);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return { current: observed.filter(l => !removed.has(l)), legacy: [...new Map(refs.map(([n, id]) => [n + ' ' + id, [n, id]])).values()] };
|
|
306
|
+
}
|
package/dist/observe/runner.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export declare const ownRoot: () => string;
|
|
2
|
+
/** A published instrument carries its own content store even before any ledger
|
|
3
|
+
* exists. Public-lock recognition grants no authority; legacy signed delivery
|
|
4
|
+
* trees retain their historical colocated-ledger or sibling-store layout. */
|
|
5
|
+
export declare function instrumentStoreAt(root: string): string;
|
|
2
6
|
export declare const ownStore: () => string;
|
|
3
7
|
export declare const ownLock: () => readonly string[];
|
|
4
8
|
export declare const sourceOf: () => string;
|
package/dist/observe/runner.js
CHANGED
|
@@ -21,8 +21,15 @@ const moduleAt = fromBlob() ? undefined : fileURLToPath(import.meta.url);
|
|
|
21
21
|
const runtimeAt = moduleAt === undefined ? '' : resolve(dirname(moduleAt), '..');
|
|
22
22
|
const runtimeExtension = moduleAt === undefined ? '' : extname(moduleAt);
|
|
23
23
|
export const ownRoot = () => (fromBlob() ? join(resolve(dirname(self()), '..', '..', '..'), 'bound') : resolve(runtimeAt, '..'));
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
/** A published instrument carries its own content store even before any ledger
|
|
25
|
+
* exists. Public-lock recognition grants no authority; legacy signed delivery
|
|
26
|
+
* trees retain their historical colocated-ledger or sibling-store layout. */
|
|
27
|
+
export function instrumentStoreAt(root) {
|
|
28
|
+
const lines = (observeText(join(root, LOCK)) ?? '').split('\n').filter(isWire);
|
|
29
|
+
const published = lines.length > 0 && publicLock(lines) !== undefined;
|
|
30
|
+
return published || existsSync(join(root, EXTENSION, 'ledger')) ? join(root, EXTENSION) : join(root, '..', EXTENSION);
|
|
31
|
+
}
|
|
32
|
+
export const ownStore = () => instrumentStoreAt(ownRoot());
|
|
26
33
|
/** The colocated public artifact is read without an unrelated parent's authority store. */
|
|
27
34
|
// An act is judged by the instrument it started with, even when that act changes its colocated lock.
|
|
28
35
|
let instrumentLock;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface WasmLimits {
|
|
2
|
+
readonly memoryBytes: number;
|
|
3
|
+
readonly stackBytes: number;
|
|
4
|
+
readonly executionMs: number;
|
|
5
|
+
}
|
|
6
|
+
export interface WasmTrust {
|
|
7
|
+
readonly runtime: string;
|
|
8
|
+
readonly wasm: string;
|
|
9
|
+
readonly modules: Readonly<Record<string, string>>;
|
|
10
|
+
readonly entry: string;
|
|
11
|
+
readonly aliases: Readonly<Record<string, string>>;
|
|
12
|
+
readonly limits: WasmLimits;
|
|
13
|
+
readonly byteLengths: {
|
|
14
|
+
readonly runtime: number;
|
|
15
|
+
readonly wasm: number;
|
|
16
|
+
readonly modules: Readonly<Record<string, number>>;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface WasmRequest {
|
|
20
|
+
readonly trusted: WasmTrust;
|
|
21
|
+
readonly assets: {
|
|
22
|
+
readonly runtime: Uint8Array | ArrayBuffer;
|
|
23
|
+
readonly wasm: Uint8Array | ArrayBuffer;
|
|
24
|
+
};
|
|
25
|
+
readonly modules: Readonly<Record<string, string>>;
|
|
26
|
+
readonly entry: string;
|
|
27
|
+
readonly aliases?: Readonly<Record<string, string>>;
|
|
28
|
+
readonly limits: WasmLimits;
|
|
29
|
+
}
|
|
30
|
+
export interface WasmResult {
|
|
31
|
+
readonly output: string;
|
|
32
|
+
readonly initializedMs: number;
|
|
33
|
+
readonly executionMs: number;
|
|
34
|
+
}
|
|
35
|
+
export interface WorkerPort {
|
|
36
|
+
postMessage(message: WasmRequest): void;
|
|
37
|
+
terminate(): unknown;
|
|
38
|
+
addEventListener?(type: string, listener: (event: {
|
|
39
|
+
data?: unknown;
|
|
40
|
+
message?: string;
|
|
41
|
+
}) => void): void;
|
|
42
|
+
on?(type: string, listener: (value: unknown) => void): void;
|
|
43
|
+
}
|
|
44
|
+
export declare function digest(bytes: Uint8Array): Promise<string>;
|
|
45
|
+
export declare function preflight(request: WasmRequest): void;
|
|
46
|
+
export declare function runWasmRequest(request: WasmRequest): Promise<WasmResult>;
|
|
47
|
+
export declare function evaluateWasm(request: WasmRequest, { workerFactory, signal, lifetimeMs }: {
|
|
48
|
+
readonly workerFactory?: () => WorkerPort;
|
|
49
|
+
readonly signal?: AbortSignal;
|
|
50
|
+
readonly lifetimeMs: number;
|
|
51
|
+
}): Promise<WasmResult>;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
const owns = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
|
|
10
|
+
const assetNames = ['runtime', 'wasm'], limitNames = ['memoryBytes', 'stackBytes', 'executionMs'];
|
|
11
|
+
// Host adapter only. Source integrity grants neither admission nor authority.
|
|
12
|
+
function refuse(cause) { throw Error('REFUSE·wasm ' + cause); }
|
|
13
|
+
export async function digest(bytes) { return 'sha256:' + Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', bytes.slice().buffer))).map(x => x.toString(16).padStart(2, '0')).join(''); }
|
|
14
|
+
async function verified(bytes, expected) { if (typeof SharedArrayBuffer !== 'undefined' && (bytes instanceof SharedArrayBuffer || (ArrayBuffer.isView(bytes) && bytes.buffer instanceof SharedArrayBuffer)))
|
|
15
|
+
refuse('shared assets unsupported'); const copy = (bytes instanceof Uint8Array ? new Uint8Array(bytes) : new Uint8Array(bytes)).slice(); if (!/^sha256:[a-f0-9]{64}$/.test(expected) || await digest(copy) !== expected)
|
|
16
|
+
refuse('integrity'); return copy; }
|
|
17
|
+
const text = new TextEncoder();
|
|
18
|
+
// Exact host-declared sizes bound copies, hashing and worker handoff before work.
|
|
19
|
+
export function preflight(request) {
|
|
20
|
+
if (!request || Object.keys(request).some(k => !['trusted', 'assets', 'modules', 'aliases', 'entry', 'limits'].includes(k)))
|
|
21
|
+
refuse('request fields');
|
|
22
|
+
const { trusted, assets, modules, entry, aliases = {}, limits } = request, sizes = trusted?.byteLengths;
|
|
23
|
+
if (!sizes || !modules || !assets)
|
|
24
|
+
refuse('declared byte sizes');
|
|
25
|
+
if (Object.keys(assets).length !== 2 || Object.keys(assets).some(k => !['runtime', 'wasm'].includes(k)))
|
|
26
|
+
refuse('asset fields');
|
|
27
|
+
if (!limits || Object.keys(limits).length !== 3 || Object.keys(limits).some(k => !['memoryBytes', 'stackBytes', 'executionMs'].includes(k)))
|
|
28
|
+
refuse('limit fields');
|
|
29
|
+
for (const name of limitNames)
|
|
30
|
+
if (!Number.isSafeInteger(limits[name]) || limits[name] <= 0 || limits[name] !== trusted.limits?.[name])
|
|
31
|
+
refuse('limit ' + name);
|
|
32
|
+
if (typeof entry !== 'string' || entry !== trusted.entry || !aliases || Object.values(aliases).some(v => typeof v !== 'string') || JSON.stringify(Object.entries(aliases).sort()) !== JSON.stringify(Object.entries(trusted.aliases ?? {}).sort()))
|
|
33
|
+
refuse('entry or aliases differ from trusted context');
|
|
34
|
+
const names = Object.keys(modules), expected = Object.keys(trusted.modules ?? {});
|
|
35
|
+
if (names.length !== expected.length || names.some(n => !owns(trusted.modules, n)))
|
|
36
|
+
refuse('module count');
|
|
37
|
+
const size = (n, want) => { if (!Number.isSafeInteger(want) || want < 0)
|
|
38
|
+
refuse('declared size ' + n); return want; };
|
|
39
|
+
for (const name of assetNames) {
|
|
40
|
+
const v = assets[name];
|
|
41
|
+
if (typeof SharedArrayBuffer !== 'undefined' && (v instanceof SharedArrayBuffer || (ArrayBuffer.isView(v) && v.buffer instanceof SharedArrayBuffer)))
|
|
42
|
+
refuse('shared assets unsupported');
|
|
43
|
+
if (!(v instanceof Uint8Array || v instanceof ArrayBuffer) || v.byteLength !== size(name, sizes[name]))
|
|
44
|
+
refuse('asset bytes ' + name);
|
|
45
|
+
}
|
|
46
|
+
for (const name of names) {
|
|
47
|
+
const source = modules[name], want = size(name, sizes.modules?.[name]);
|
|
48
|
+
if (typeof source !== 'string' || source.length > want)
|
|
49
|
+
refuse('module bytes ' + name);
|
|
50
|
+
let bytes = 0;
|
|
51
|
+
for (let i = 0; i < source.length; i++) {
|
|
52
|
+
const c = source.charCodeAt(i);
|
|
53
|
+
bytes += c < 128 ? 1 : c < 2048 ? 2 : c >= 55296 && c <= 56319 && i + 1 < source.length && source.charCodeAt(i + 1) >= 56320 && source.charCodeAt(i + 1) <= 57343 ? (i++, 4) : 3;
|
|
54
|
+
if (bytes > want)
|
|
55
|
+
refuse('module bytes ' + name);
|
|
56
|
+
}
|
|
57
|
+
if (bytes !== want)
|
|
58
|
+
refuse('module bytes ' + name);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export async function runWasmRequest(request) {
|
|
62
|
+
preflight(request);
|
|
63
|
+
try {
|
|
64
|
+
request = structuredClone(request);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
refuse('request is not cloneable');
|
|
68
|
+
}
|
|
69
|
+
const { trusted, assets, modules, aliases = {}, entry, limits } = request;
|
|
70
|
+
for (const name of limitNames)
|
|
71
|
+
if (!Number.isSafeInteger(limits?.[name]) || limits[name] <= 0 || limits[name] !== trusted?.limits?.[name])
|
|
72
|
+
refuse('limit ' + name);
|
|
73
|
+
if (entry !== trusted.entry || JSON.stringify(Object.entries(aliases).sort()) !== JSON.stringify(Object.entries(trusted.aliases ?? {}).sort()))
|
|
74
|
+
refuse('entry or aliases differ from trusted context');
|
|
75
|
+
const runtime = await verified(assets.runtime, trusted.runtime), wasm = await verified(assets.wasm, trusted.wasm), sources = {};
|
|
76
|
+
for (const [id, source] of Object.entries(modules)) {
|
|
77
|
+
if (!owns(trusted.modules, id))
|
|
78
|
+
refuse('undeclared module ' + id);
|
|
79
|
+
sources[id] = new TextDecoder('utf-8', { fatal: true }).decode(await verified(text.encode(source), trusted.modules[id]));
|
|
80
|
+
}
|
|
81
|
+
if (Object.keys(trusted.modules).some(id => !owns(sources, id)))
|
|
82
|
+
refuse('missing module');
|
|
83
|
+
if (!owns(sources, entry))
|
|
84
|
+
refuse('entry');
|
|
85
|
+
// The runtime is a closed esbuild ESM bundle: no imports and no second fetch.
|
|
86
|
+
const url = 'data:text/javascript;base64,' + bytesBase64(runtime), factory = await import(__rewriteRelativeImportExtension(url));
|
|
87
|
+
const started = performance.now(), quick = await factory.createQuickJS(wasm);
|
|
88
|
+
const rt = quick.newRuntime();
|
|
89
|
+
let context;
|
|
90
|
+
try {
|
|
91
|
+
context = rt.newContext();
|
|
92
|
+
const memory = rt.computeMemoryUsage();
|
|
93
|
+
let initializedMemory;
|
|
94
|
+
try {
|
|
95
|
+
initializedMemory = context.dump(memory);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
memory.dispose();
|
|
99
|
+
}
|
|
100
|
+
const used = typeof initializedMemory === 'object' && initializedMemory !== null ? initializedMemory.memory_used_size : undefined;
|
|
101
|
+
if (typeof used !== 'number' || !Number.isSafeInteger(used) || used < 0)
|
|
102
|
+
refuse('initialized memory measurement unavailable');
|
|
103
|
+
if (used > limits.memoryBytes)
|
|
104
|
+
refuse('initialized memory ' + used + ' exceeds declared memory ' + limits.memoryBytes);
|
|
105
|
+
const initializedMs = performance.now() - started;
|
|
106
|
+
rt.setMemoryLimit(limits.memoryBytes);
|
|
107
|
+
rt.setMaxStackSize(limits.stackBytes);
|
|
108
|
+
const start = performance.now();
|
|
109
|
+
rt.setInterruptHandler(() => performance.now() - start > limits.executionMs);
|
|
110
|
+
rt.setModuleLoader(id => { if (!owns(sources, id))
|
|
111
|
+
refuse('undeclared import ' + id); return sources[id]; }, (parent, name) => { if (owns(aliases, name))
|
|
112
|
+
return aliases[name]; if (name.startsWith('.'))
|
|
113
|
+
return new URL(name, new URL(parent, 'file:///')).pathname; if (owns(sources, name))
|
|
114
|
+
return name; refuse('undeclared import ' + name); });
|
|
115
|
+
const result = context.evalCode(sources[entry], entry, { type: 'module' });
|
|
116
|
+
if (result.error) {
|
|
117
|
+
const e = context.dump(result.error);
|
|
118
|
+
result.error.dispose();
|
|
119
|
+
refuse('guest ' + JSON.stringify(e));
|
|
120
|
+
}
|
|
121
|
+
result.value.dispose();
|
|
122
|
+
const value = context.evalCode('globalThis.result');
|
|
123
|
+
if (value.error) {
|
|
124
|
+
const e = context.dump(value.error);
|
|
125
|
+
value.error.dispose();
|
|
126
|
+
refuse('result ' + JSON.stringify(e));
|
|
127
|
+
}
|
|
128
|
+
const output = context.dump(value.value);
|
|
129
|
+
value.value.dispose();
|
|
130
|
+
if (typeof output !== 'string')
|
|
131
|
+
refuse('result must be declared text');
|
|
132
|
+
return { output, initializedMs, executionMs: performance.now() - start };
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
context?.dispose();
|
|
136
|
+
rt.dispose();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function bytesBase64(bytes) { let binary = ''; for (const b of bytes)
|
|
140
|
+
binary += String.fromCharCode(b); return btoa(binary); }
|
|
141
|
+
// A separate worker makes external abort possible while synchronous WASM executes.
|
|
142
|
+
export async function evaluateWasm(request, { workerFactory = () => new Worker(new URL('./worker.js', import.meta.url), { type: 'module' }), signal, lifetimeMs }) {
|
|
143
|
+
if (!Number.isSafeInteger(lifetimeMs) || lifetimeMs <= 0)
|
|
144
|
+
refuse('worker lifetime');
|
|
145
|
+
if (signal?.aborted)
|
|
146
|
+
refuse('cancelled');
|
|
147
|
+
preflight(request);
|
|
148
|
+
return new Promise((resolve, reject) => { const worker = workerFactory(); let done = false; const finish = (error, value) => { if (done)
|
|
149
|
+
return; done = true; clearTimeout(timer); signal?.removeEventListener('abort', abort); worker.terminate(); error ? reject(error) : resolve(value); }; const abort = () => finish(Error('REFUSE·wasm cancelled')); const timer = setTimeout(() => finish(Error('REFUSE·wasm worker lifetime')), lifetimeMs); signal?.addEventListener('abort', abort, { once: true }); const message = (raw) => { const data = raw; if (!data || typeof data !== 'object' || typeof data.ok !== 'boolean') {
|
|
150
|
+
finish(Error('REFUSE·wasm malformed worker result'));
|
|
151
|
+
return;
|
|
152
|
+
} if (data.ok) {
|
|
153
|
+
if (!data.result || typeof data.result.output !== 'string') {
|
|
154
|
+
finish(Error('REFUSE·wasm malformed worker output'));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
finish(null, data.result);
|
|
158
|
+
}
|
|
159
|
+
else
|
|
160
|
+
finish(Error(typeof data.error === 'string' ? data.error : 'REFUSE·wasm worker failure')); }; if (typeof worker.addEventListener === 'function') {
|
|
161
|
+
worker.addEventListener('message', e => message(e.data));
|
|
162
|
+
worker.addEventListener('error', e => finish(Error('REFUSE·wasm worker ' + e.message)));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
if (!worker.on) {
|
|
166
|
+
finish(Error('REFUSE·wasm worker event port'));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
worker.on('message', message);
|
|
170
|
+
worker.on('error', e => finish(e instanceof Error ? e : Error(String(e))));
|
|
171
|
+
} if (signal?.aborted) {
|
|
172
|
+
abort();
|
|
173
|
+
return;
|
|
174
|
+
} try {
|
|
175
|
+
worker.postMessage(request);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
finish(Error('REFUSE·wasm request is not cloneable'));
|
|
179
|
+
} });
|
|
180
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { parentPort } from 'node:worker_threads';
|
|
2
|
+
import { runWasmRequest } from "./adapter.js";
|
|
3
|
+
if (!parentPort)
|
|
4
|
+
throw Error('REFUSE·wasm worker port missing');
|
|
5
|
+
const port = parentPort;
|
|
6
|
+
port.once('message', async (request) => {
|
|
7
|
+
try {
|
|
8
|
+
port.postMessage({ ok: true, result: await runWasmRequest(request) });
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
port.postMessage({ ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
12
|
+
}
|
|
13
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ObjectRecord, ObjectContext } from '@lapxo/topos/wire';
|
|
2
|
+
import type { ObjectReading } from '../engine/index.ts';
|
|
3
|
+
type Fields = Readonly<Record<string, string>>;
|
|
4
|
+
/** Configuration is the caller's already authenticated, live standing; selection grants no authority. */
|
|
5
|
+
export declare function usesWasmObject(configuration: readonly Fields[]): boolean;
|
|
6
|
+
/** The installed instrument owns the asset/form closure; place input cannot choose executable bytes. */
|
|
7
|
+
export declare function foldObjectsWithWasm(root: string, records: readonly ObjectRecord[], context: ObjectContext, configuration: readonly Fields[], signal?: AbortSignal): Promise<readonly ObjectReading[]>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { Worker } from 'node:worker_threads';
|
|
3
|
+
import { sha256 } from "../host/hash.js";
|
|
4
|
+
import { toposAt } from "../host/selected-topos.js";
|
|
5
|
+
import { storeOf } from "../land/ledger.js";
|
|
6
|
+
import { evaluateWasm } from "./adapter.js";
|
|
7
|
+
function refuse(cause) { throw Error('REFUSE·wasm object ' + cause); }
|
|
8
|
+
/** Configuration is the caller's already authenticated, live standing; selection grants no authority. */
|
|
9
|
+
export function usesWasmObject(configuration) {
|
|
10
|
+
const rows = configuration.filter(f => f.scope === 'engine/object' && f.value !== 'withdraw');
|
|
11
|
+
if (!rows.length)
|
|
12
|
+
return false;
|
|
13
|
+
if (rows.length !== 1 || rows[0].role !== 'writes' || rows[0].measure !== 'id' || rows[0].form !== 'alphabet' || rows[0].value !== 'wasm@1')
|
|
14
|
+
refuse('unique declared backend required');
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
function limit(configuration, name, measure) {
|
|
18
|
+
const rows = configuration.filter(f => f.scope === 'engine/wasm/' + name && f.value !== 'withdraw');
|
|
19
|
+
if (rows.length !== 1 || rows[0].form !== 'interval' || rows[0].measure !== measure || rows[0].role !== 'writes')
|
|
20
|
+
refuse('missing or ambiguous ' + name + ' limit');
|
|
21
|
+
const span = /^(0|[1-9][0-9]*)\.\.(0|[1-9][0-9]*)$/.exec(rows[0].value ?? '');
|
|
22
|
+
if (!span || !Number.isSafeInteger(Number(span[1])) || !Number.isSafeInteger(Number(span[2])) || Number(span[1]) > Number(span[2]) || Number(span[2]) < 1)
|
|
23
|
+
refuse('invalid ' + name + ' limit');
|
|
24
|
+
return Number(span[2]);
|
|
25
|
+
}
|
|
26
|
+
/** The installed instrument owns the asset/form closure; place input cannot choose executable bytes. */
|
|
27
|
+
export async function foldObjectsWithWasm(root, records, context, configuration, signal) {
|
|
28
|
+
if (!usesWasmObject(configuration))
|
|
29
|
+
refuse('backend not selected');
|
|
30
|
+
const limits = { memoryBytes: limit(configuration, 'memory', 'bytes'), stackBytes: limit(configuration, 'stack', 'bytes'), executionMs: limit(configuration, 'execution', 'milliseconds') }, lifetimeMs = limit(configuration, 'worker-lifetime', 'milliseconds'), responseBytes = limit(configuration, 'response', 'bytes');
|
|
31
|
+
const assets = new URL('../../assets/wasm/', import.meta.url), manifest = JSON.parse(readFileSync(new URL('manifest.json', assets), 'utf8'));
|
|
32
|
+
if (!manifest.forms)
|
|
33
|
+
refuse('installed form closure missing');
|
|
34
|
+
const codecs = {};
|
|
35
|
+
for (const record of records.filter(r => r.record === 'cell')) {
|
|
36
|
+
const pin = record.fields.topos, binding = manifest.forms[pin];
|
|
37
|
+
if (!binding || !['intervalForm', 'alphabetOfForm'].includes(binding.export) || binding.form !== record.fields.form)
|
|
38
|
+
refuse('unsupported form standing ' + pin);
|
|
39
|
+
const standing = toposAt(pin, storeOf(root)), selector = standing.parts.filter(f => f.scope === 'wire/topos/context-view');
|
|
40
|
+
if (selector.length !== 1)
|
|
41
|
+
refuse('context selection ' + pin);
|
|
42
|
+
const offers = standing.parts.filter(f => f.kind === 'check' && f.view === selector[0].value);
|
|
43
|
+
if (offers.length !== 1 || offers[0].value !== binding.contextArtifact || !(offers[0].restsOn ?? '').split('|').includes(binding.contextArtifact))
|
|
44
|
+
refuse('context implementation differs ' + pin);
|
|
45
|
+
const selected = context.resolve(record);
|
|
46
|
+
if (selected.digest !== pin || selected.form.id !== binding.form)
|
|
47
|
+
refuse('prepared form differs ' + record.fields.scope);
|
|
48
|
+
codecs[record.fields.scope] = { digest: pin, export: binding.export, params: selected.params };
|
|
49
|
+
}
|
|
50
|
+
const guestNames = Object.keys(manifest.modules);
|
|
51
|
+
if (guestNames.length !== 1)
|
|
52
|
+
refuse('installed guest module map');
|
|
53
|
+
const guestName = guestNames[0];
|
|
54
|
+
let program;
|
|
55
|
+
try {
|
|
56
|
+
program = `import{objectFold,intervalForm,alphabetOfForm}from${JSON.stringify(guestName)};const forms={intervalForm,alphabetOfForm},codecs=${JSON.stringify(codecs)},records=${JSON.stringify(records)};const context={resolve:cell=>{const c=codecs[cell.fields.scope];if(!c)throw Error('REFUSE unknown prepared cell');return{digest:c.digest,form:forms[c.export],params:c.params}}};globalThis.result=JSON.stringify(objectFold(records,context));`;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return refuse('context is not serializable');
|
|
60
|
+
}
|
|
61
|
+
const entry = guestName + '.entry', programDigest = sha256(program);
|
|
62
|
+
const guest = readFileSync(new URL('guest.js', assets), 'utf8'), modules = { [guestName]: guest, [entry]: program }, request = { trusted: { ...manifest, modules: { ...manifest.modules, [entry]: programDigest }, entry, aliases: {}, limits, byteLengths: { ...manifest.byteLengths, modules: { ...manifest.byteLengths.modules, [entry]: Buffer.byteLength(program) } } }, assets: { runtime: new Uint8Array(readFileSync(new URL('runtime.js', assets))), wasm: new Uint8Array(readFileSync(new URL('runtime.wasm', assets))) }, modules, entry, aliases: {}, limits };
|
|
63
|
+
const result = await evaluateWasm(request, { workerFactory: () => new Worker(new URL('./node-worker.js', import.meta.url)), lifetimeMs, ...(signal ? { signal } : {}) });
|
|
64
|
+
if (Buffer.byteLength(result.output) > responseBytes)
|
|
65
|
+
refuse('response bytes limit');
|
|
66
|
+
let readings;
|
|
67
|
+
try {
|
|
68
|
+
readings = JSON.parse(result.output);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return refuse('invalid projection text');
|
|
72
|
+
}
|
|
73
|
+
if (!Array.isArray(readings) || readings.some(r => !r || typeof r.cell !== 'string' || typeof r.state !== 'string'))
|
|
74
|
+
refuse('invalid projection shape');
|
|
75
|
+
return readings;
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scope": "contract/published/authenticated-reference",
|
|
3
|
+
"members": [
|
|
4
|
+
{
|
|
5
|
+
"role": "contract",
|
|
6
|
+
"path": "docs/contracts/reference-port.md",
|
|
7
|
+
"digest": "sha256:ae500aaec7e94dc9bc7c4d45e3294b212b0ad5ade7647e3ce24807ca7a0ea7cc"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"role": "yes",
|
|
11
|
+
"path": "samples/authenticated-reference/yes/weather-reading/inputs/yes.json",
|
|
12
|
+
"digest": "sha256:f285a014cc9e9453aebd4c4ed94354fba5cac55c05929f4ad7cdd1b3348a9717"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"role": "vector",
|
|
16
|
+
"path": "vectors/native/authenticated-reference.vector.json",
|
|
17
|
+
"digest": "sha256:297c4942a847c42eb9d2cf000ab38d8a5f38b64e2e4a469919f0dfc88c9d8bd1"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"role": "no",
|
|
21
|
+
"path": "samples/authenticated-reference/yes/weather-reading/inputs/signature.json",
|
|
22
|
+
"digest": "sha256:2049bbbf88077727911a0cfec3193ab6ce7bccb8d4e3bb22fc537c59f5addf58"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
package/docs/contracts/cli.md
CHANGED
|
@@ -25,3 +25,16 @@ The query verifies the record signatures and coverage against the authority snap
|
|
|
25
25
|
## Standing admission
|
|
26
26
|
|
|
27
27
|
A uses/ selection admits canonical standing bytes, never an archive. The bytes must already be held under their verified digest or in the single local coordinate explicitly named by needs. Admission checks identity and canonical form without fetching, executing or writing the content store. Missing or corrupt bytes refuse the whole lot. A corrupt held copy cannot be hidden by another source. Archives remain separate dependency declarations.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## Declared reading identity
|
|
31
|
+
|
|
32
|
+
A place selects both `wire/reading-context value=reading-context@1` and `wire/reading-receipt value=reading-receipt@1`. Partial or conflicting selections refuse. Every reader under this profile declares an input projection; a legacy reader is refused by name instead of receiving a new identity without its closure.
|
|
33
|
+
|
|
34
|
+
The host commits the full instrument reference, the verified capsule implementation, effective reader policy, declared transitive references, each input's actual bytes and dependent receipt identities. The process environment is captured explicitly: inherited variable values (hashed, never printed), the place working directory, child flags, runtime bytes and platform conditions. The same captured boundary is handed to execution and keys the capsule cache. These references grant neither authority nor independence and cannot prove undeclared inputs absent.
|
|
35
|
+
|
|
36
|
+
An unchanged cross-process fold reads retained receipts with zero opened readings. Changed input reopens that reading and its dependents; changing shared implementation, policy or execution conditions invalidates the readings that depend on them. The semantic receipt names the reading and result together; elapsed time and signature envelopes remain separate. Old receipts are not promoted.
|
|
37
|
+
|
|
38
|
+
Retained evidence may be inspected without asserting a current response. Reusing a current response also requires the existing snapshot-access contract: matching principal, authorization, request, implementation and snapshot, declared freshness, and both inspection and reuse permission. Unknown or expired freshness, withdrawn permission or changed context refuse before running a provider. A new observation needs its own admitted act.
|
|
39
|
+
|
|
40
|
+
`fold --check` verifies the retained published evidence and instrument. It opens no reader and grants no permission to assert an old response today. A live fold additionally compares the current captured execution boundary.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scope": "contract/published/engine-object-projection",
|
|
3
|
+
"members": [
|
|
4
|
+
{
|
|
5
|
+
"role": "contract",
|
|
6
|
+
"path": "docs/contracts/engine.md",
|
|
7
|
+
"digest": "sha256:2d54ddc75386a0706a3bc500e36deedc85ca9e7dc965eff5c795f02b1a716078"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"role": "yes",
|
|
11
|
+
"path": "samples/engine-interval/inputs/sample.json",
|
|
12
|
+
"digest": "sha256:ef476bda8c15b4967a88dc27a188ab25cfac42bdf381fb82f4e57a482d2fd27d"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"role": "no",
|
|
16
|
+
"path": "samples/no/engine-order/inputs/sample.json",
|
|
17
|
+
"digest": "sha256:f2104a661541baf7f8e6d9224590210b272e24519dafce94ca7c588e5a78a20d"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"role": "vector",
|
|
21
|
+
"path": "vectors/engine-object.cli.json",
|
|
22
|
+
"digest": "sha256:191920f597e2d8bafc9aeeff13934645c64a790dcad94b22c562b30c5fe6baa0"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|