@lapxo/bound 0.2.4 → 0.2.5
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/CITATION.cff +1 -1
- package/TARGET.bound +44 -39
- package/dist/cli/accept.js +72 -590
- package/dist/cli/act-result.js +3 -103
- package/dist/cli/args.js +3 -44
- package/dist/cli/custodian.js +2 -29
- package/dist/cli/host.js +4 -32
- package/dist/cli/names.js +1 -12
- package/dist/cli/native-descendant.js +1 -19
- package/dist/cli/native.js +3 -60
- package/dist/cli/observe.js +1 -13
- package/dist/cli/owner.js +3 -139
- package/dist/cli/pass.js +21 -321
- package/dist/cli/place.js +6 -282
- package/dist/cli/release-result.js +3 -80
- package/dist/cli/retained-judgment.js +1 -25
- package/dist/cli/standing-result.js +4 -19
- package/dist/cli/takes.js +10 -112
- package/dist/cli/verb.d.ts +1 -2
- package/dist/cli/verb.js +83 -684
- package/dist/engine/computation.js +2 -0
- package/dist/engine/conformance.js +4 -0
- package/dist/engine/encoding.js +1 -2
- package/dist/engine/index.d.ts +1 -13
- package/dist/engine/index.js +2 -11
- package/dist/engine/object.js +1 -38
- package/dist/engine/ports.js +2 -117
- package/dist/fold/admission-history.js +1 -17
- package/dist/fold/bootstrap-anchor.js +1 -28
- package/dist/fold/ceilings.js +1 -142
- package/dist/fold/claims.js +2 -132
- package/dist/fold/closed.js +24 -235
- package/dist/fold/configures.js +1 -25
- package/dist/fold/digests.js +10 -226
- package/dist/fold/evidence.js +1 -22
- package/dist/fold/fold-worker.js +1 -13
- package/dist/fold/kept.js +12 -173
- package/dist/fold/keys.js +3 -91
- package/dist/fold/laid.js +3 -90
- package/dist/fold/nos.js +1 -48
- package/dist/fold/object.js +1 -2
- package/dist/fold/observed.js +27 -534
- package/dist/fold/opened.js +1 -33
- package/dist/fold/own.js +3 -217
- package/dist/fold/paid.js +1 -92
- package/dist/fold/place-inputs.js +1 -70
- package/dist/fold/places.js +3 -100
- package/dist/fold/rank.js +1 -9
- package/dist/fold/reach.js +1 -83
- package/dist/fold/readers.js +6 -142
- package/dist/fold/receipts.js +1 -47
- package/dist/fold/region-declarations.js +1 -13
- package/dist/fold/region.js +8 -286
- package/dist/fold/release-policy.js +1 -101
- package/dist/fold/resolved.js +6 -145
- package/dist/fold/roles.js +2 -59
- package/dist/fold/second.js +4 -71
- package/dist/fold/shards.js +12 -100
- package/dist/fold/signed.js +3 -81
- package/dist/fold/signers.js +1 -54
- package/dist/fold/stranger.js +6 -24
- package/dist/fold/views.js +1 -66
- package/dist/fold/wire.js +1 -46
- package/dist/fold/zoom.js +3 -81
- package/dist/host/act-cost.js +1 -25
- package/dist/host/adapters/keys/ed25519.js +1 -30
- package/dist/host/adapters/modules/javascript.js +1 -50
- package/dist/host/adapters/transport/http.js +1 -28
- package/dist/host/admitted-place.js +1 -35
- package/dist/host/archive.js +1 -152
- package/dist/host/call-context.js +1 -14
- package/dist/host/capsule-process.js +2 -62
- package/dist/host/capsule.js +9 -171
- package/dist/host/cell-receipts.js +12 -128
- package/dist/host/commit-result.js +1 -5
- package/dist/host/content-needed.js +1 -8
- package/dist/host/content-store.js +1 -31
- package/dist/host/contextual-place.js +2 -72
- package/dist/host/digest.js +2 -15
- package/dist/host/hash.js +1 -9
- package/dist/host/host-controls.js +1 -19
- package/dist/host/in-process-act.js +3 -84
- package/dist/host/in-process-context.js +3 -91
- package/dist/host/in-process-root.js +6 -84
- package/dist/host/in-process.js +4 -55
- package/dist/host/index.d.ts +1 -15
- package/dist/host/index.js +1 -7
- package/dist/host/io.js +1 -11
- package/dist/host/lifetime.js +1 -79
- package/dist/host/located.js +6 -105
- package/dist/host/native/bounded-file.js +1 -32
- package/dist/host/native/command.js +1 -91
- package/dist/host/native/control-bridge-worker.js +2 -88
- package/dist/host/native/control-bridge.js +1 -110
- package/dist/host/native/control-selection.js +1 -32
- package/dist/host/native/custody.js +1 -91
- package/dist/host/native/deadline.js +1 -28
- package/dist/host/native/descendants.js +1 -528
- package/dist/host/native/generated-custodian.js +5 -267
- package/dist/host/native/generated.js +1 -379
- package/dist/host/native/image.js +1 -45
- package/dist/host/native/jobs.js +2 -148
- package/dist/host/native/loader.js +1 -269
- package/dist/host/native/materialize.js +1 -175
- package/dist/host/native/module-closure.js +1 -130
- package/dist/host/native/module-delivery.d.ts +1 -62
- package/dist/host/native/module-delivery.js +1 -151
- package/dist/host/native/module-syntax.js +1 -69
- package/dist/host/native/native-binary.js +1 -115
- package/dist/host/native/native-program.js +1 -72
- package/dist/host/native/node-jobs.js +3 -187
- package/dist/host/native/node-launch.js +1 -159
- package/dist/host/native/preload.js +1 -45
- package/dist/host/native/reading.js +1 -249
- package/dist/host/native/receipt-environment.js +1 -41
- package/dist/host/native/references.js +1 -30
- package/dist/host/native/root.js +1 -37
- package/dist/host/native/route.js +1 -216
- package/dist/host/native/runtime.js +1 -9
- package/dist/host/native-reader-context.js +1 -8
- package/dist/host/native-receiver.js +1 -25
- package/dist/host/objects.js +1 -38
- package/dist/host/order.js +1 -2
- package/dist/host/place-act-transaction.js +4 -78
- package/dist/host/place-clock-selection.js +5 -124
- package/dist/host/ports/ingress.js +2 -57
- package/dist/host/ports/local-act.js +5 -183
- package/dist/host/ports/place-act.js +1 -134
- package/dist/host/ports/publication.js +1 -56
- package/dist/host/ports/signing.js +1 -74
- package/dist/host/ports/store.js +1 -18
- package/dist/host/ports/transport.js +1 -48
- package/dist/host/process-lifetime.js +1 -17
- package/dist/host/provide.js +1 -183
- package/dist/host/read-only.js +1 -9
- package/dist/host/reading-closures.js +7 -227
- package/dist/host/reading-context.js +1 -53
- package/dist/host/reading-identity-types.js +0 -0
- package/dist/host/reading-identity.js +3 -0
- package/dist/host/reading-preparation.js +6 -0
- package/dist/host/reference-authenticated.d.ts +1 -40
- package/dist/host/reference-authenticated.js +3 -100
- package/dist/host/reference-contracts.js +1 -6
- package/dist/host/reference-export.js +6 -81
- package/dist/host/reference-native-api.d.ts +1 -3
- package/dist/host/reference-native-api.js +1 -3
- package/dist/host/reference-native.js +1 -74
- package/dist/host/reference-profile.js +1 -63
- package/dist/host/reference-standing.js +2 -18
- package/dist/host/reference.js +1 -24
- package/dist/host/refusals.js +2 -43
- package/dist/host/release.js +4 -256
- package/dist/host/root-clock-source.js +1 -307
- package/dist/host/selected-capsules.js +1 -50
- package/dist/host/selected-topos.js +1 -80
- package/dist/host/signature-era.js +1 -12
- package/dist/host/signing.js +2 -48
- package/dist/host/source-content.js +2 -111
- package/dist/host/standing-carrier.js +1 -18
- package/dist/host/standing-projection.js +1 -50
- package/dist/host/timing.js +1 -7
- package/dist/host/walk.js +20 -193
- package/dist/judge/late.js +1 -33
- package/dist/judge/take.js +2 -75
- package/dist/judge/widening.js +1 -25
- package/dist/land/act.js +8 -336
- package/dist/land/alias.js +1 -41
- package/dist/land/held.js +1 -23
- package/dist/land/inert.js +1 -33
- package/dist/land/leaving.js +1 -55
- package/dist/land/ledger.js +7 -188
- package/dist/land/owned.js +4 -66
- package/dist/land/sign.js +1 -53
- package/dist/land/standing-pin.js +1 -49
- package/dist/land/vouched.js +1 -112
- package/dist/land/withdraw.js +1 -24
- package/dist/observe/files.js +1 -88
- package/dist/observe/identity.js +1 -61
- package/dist/observe/match.js +1 -8
- package/dist/observe/regions.js +7 -306
- package/dist/observe/run.js +2 -41
- package/dist/observe/runner.js +4 -101
- package/dist/public-types.d.ts +1114 -0
- package/dist/render/capsule.js +1 -30
- package/dist/render/field.js +1 -40
- package/dist/render/names.js +1 -4
- package/dist/render/open.js +1 -61
- package/dist/render/page.js +1 -7
- package/dist/render/receipts.js +1 -47
- package/dist/render/shape.js +1 -74
- package/dist/render/target.js +4 -52
- package/dist/render/view.js +7 -197
- package/dist/vendor/50086958553b9376d600583f7dff354196bfeb41170fc6d1482ca7bea7c493e2.LICENSE +21 -0
- package/dist/vendor/b3e938d28f88a1851af303a40c749e12ebd5a686daac4f0b343b5cdd2e040419.LICENSE +21 -0
- package/dist/vendor/bundle.json +1 -0
- package/dist/vendor/library11.js +1 -0
- package/dist/vendor/object-provider.js +42 -0
- package/dist/vendor/runtime.js +38 -0
- package/dist/vendor/sources.json +1 -0
- package/dist/wasm/adapter.d.ts +1 -51
- package/dist/wasm/adapter.js +1 -180
- package/dist/wasm/node-worker.js +1 -13
- package/dist/wasm/object.js +1 -76
- package/dist/wasm/worker.js +1 -7
- package/docs/contracts/closure-delivery.md +1 -1
- package/docs/contracts/closure-delivery.publication.json +1 -1
- package/docs/contracts/conformance.json +142 -0
- package/docs/contracts/determinism.bound +9 -0
- package/docs/contracts/engine-object-projection.publication.json +1 -1
- package/docs/contracts/engine.md +21 -1
- package/docs/contracts/native-publication.inventory.json +2 -1
- package/docs/contracts/optional-wasm-adapter.publication.json +1 -1
- package/docs/contracts/reading-identity.md +37 -0
- package/docs/contracts/reading-identity.publication.json +25 -0
- package/docs/contracts/refusals.bound +51 -0
- package/docs/contracts/refusals.md +18 -3
- package/docs/contracts/refusals.publication.json +2 -2
- package/docs/contracts/views.md +8 -0
- package/docs/reference.md +1 -0
- package/package.json +23 -34
- package/receipts.bound +11627 -10323
- package/samples/reading-identity/no.json +20 -0
- package/samples/reading-identity/yes.json +46 -0
- package/vectors/native/reading-identity.vector.json +101 -0
- package/vectors/native/refusals.vector.json +104 -0
- package/vendor/syntax/LICENSE.md +7 -0
- package/vendor/syntax/parser.js +1 -0
- package/.github/workflows/release.yml +0 -67
- package/.github/workflows/test.yml +0 -35
- package/dist/engine/object.d.ts +0 -17
- package/dist/engine/ports.d.ts +0 -37
- package/dist/fold/claims.d.ts +0 -29
- package/dist/host/call-context.d.ts +0 -20
- package/dist/host/commit-result.d.ts +0 -6
- package/dist/host/in-process-act.d.ts +0 -82
- package/dist/host/in-process-context.d.ts +0 -46
- package/dist/host/in-process-root.d.ts +0 -117
- package/dist/host/in-process.d.ts +0 -41
- package/dist/host/native/module-closure.d.ts +0 -35
- package/dist/host/native-receiver.d.ts +0 -19
- package/dist/host/place-act-transaction.d.ts +0 -24
- package/dist/host/place-clock-selection.d.ts +0 -55
- package/dist/host/ports/local-act.d.ts +0 -44
- package/dist/host/ports/place-act.d.ts +0 -49
- package/dist/host/ports/publication.d.ts +0 -10
- package/dist/host/reference-export.d.ts +0 -33
- package/dist/host/reference-native.d.ts +0 -30
- package/dist/host/reference-profile.d.ts +0 -23
- package/dist/host/refusals.d.ts +0 -14
- package/dist/host/root-clock-source.d.ts +0 -86
package/dist/wasm/adapter.js
CHANGED
|
@@ -1,180 +1 @@
|
|
|
1
|
-
var
|
|
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
|
-
}
|
|
1
|
+
var N=Object.defineProperty;var m=(t,s)=>N(t,"name",{value:s,configurable:!0});var z=function(t,s){return typeof t=="string"&&/^\.\.?\//.test(t)?t.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(a,y,p,l,n){return y?s?".jsx":".js":p&&(!l||!n)?a:p+l+"."+n.toLowerCase()+"js"}):t};const S=m((t,s)=>Object.prototype.hasOwnProperty.call(t,s),"owns"),C=["runtime","wasm"],U=["memoryBytes","stackBytes","executionMs"];function o(t){throw Error("REFUSE\xB7wasm "+t)}m(o,"refuse");async function F(t){return"sha256:"+Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256",t.slice().buffer))).map(s=>s.toString(16).padStart(2,"0")).join("")}m(F,"digest");async function O(t,s){typeof SharedArrayBuffer<"u"&&(t instanceof SharedArrayBuffer||ArrayBuffer.isView(t)&&t.buffer instanceof SharedArrayBuffer)&&o("shared assets unsupported");const a=(t instanceof Uint8Array?new Uint8Array(t):new Uint8Array(t)).slice();return(!/^sha256:[a-f0-9]{64}$/.test(s)||await F(a)!==s)&&o("integrity"),a}m(O,"verified");const M=new TextEncoder;function R(t){(!t||Object.keys(t).some(e=>!["trusted","assets","modules","aliases","entry","limits"].includes(e)))&&o("request fields");const{trusted:s,assets:a,modules:y,entry:p,aliases:l={},limits:n}=t,g=s?.byteLengths;(!g||!y||!a)&&o("declared byte sizes"),(Object.keys(a).length!==2||Object.keys(a).some(e=>!["runtime","wasm"].includes(e)))&&o("asset fields"),(!n||Object.keys(n).length!==3||Object.keys(n).some(e=>!["memoryBytes","stackBytes","executionMs"].includes(e)))&&o("limit fields");for(const e of U)(!Number.isSafeInteger(n[e])||n[e]<=0||n[e]!==s.limits?.[e])&&o("limit "+e);(typeof p!="string"||p!==s.entry||!l||Object.values(l).some(e=>typeof e!="string")||JSON.stringify(Object.entries(l).sort())!==JSON.stringify(Object.entries(s.aliases??{}).sort()))&&o("entry or aliases differ from trusted context");const f=Object.keys(y),d=Object.keys(s.modules??{});(f.length!==d.length||f.some(e=>!S(s.modules,e)))&&o("module count");const h=m((e,r)=>((!Number.isSafeInteger(r)||r<0)&&o("declared size "+e),r),"size");for(const e of C){const r=a[e];typeof SharedArrayBuffer<"u"&&(r instanceof SharedArrayBuffer||ArrayBuffer.isView(r)&&r.buffer instanceof SharedArrayBuffer)&&o("shared assets unsupported"),(!(r instanceof Uint8Array||r instanceof ArrayBuffer)||r.byteLength!==h(e,g[e]))&&o("asset bytes "+e)}for(const e of f){const r=y[e],u=h(e,g.modules?.[e]);(typeof r!="string"||r.length>u)&&o("module bytes "+e);let w=0;for(let c=0;c<r.length;c++){const i=r.charCodeAt(c);w+=i<128?1:i<2048?2:i>=55296&&i<=56319&&c+1<r.length&&r.charCodeAt(c+1)>=56320&&r.charCodeAt(c+1)<=57343?(c++,4):3,w>u&&o("module bytes "+e)}w!==u&&o("module bytes "+e)}}m(R,"preflight");async function _(t){R(t);try{t=structuredClone(t)}catch{o("request is not cloneable")}const{trusted:s,assets:a,modules:y,aliases:p={},entry:l,limits:n}=t;for(const i of U)(!Number.isSafeInteger(n?.[i])||n[i]<=0||n[i]!==s?.limits?.[i])&&o("limit "+i);(l!==s.entry||JSON.stringify(Object.entries(p).sort())!==JSON.stringify(Object.entries(s.aliases??{}).sort()))&&o("entry or aliases differ from trusted context");const g=await O(a.runtime,s.runtime),f=await O(a.wasm,s.wasm),d={};for(const[i,k]of Object.entries(y))S(s.modules,i)||o("undeclared module "+i),d[i]=new TextDecoder("utf-8",{fatal:!0}).decode(await O(M.encode(k),s.modules[i]));Object.keys(s.modules).some(i=>!S(d,i))&&o("missing module"),S(d,l)||o("entry");const h="data:text/javascript;base64,"+I(g),e=await import(z(h)),r=performance.now(),w=(await e.createQuickJS(f)).newRuntime();let c;try{c=w.newContext();const i=w.computeMemoryUsage();let k;try{k=c.dump(i)}finally{i.dispose()}const j=typeof k=="object"&&k!==null?k.memory_used_size:void 0;(typeof j!="number"||!Number.isSafeInteger(j)||j<0)&&o("initialized memory measurement unavailable"),j>n.memoryBytes&&o("initialized memory "+j+" exceeds declared memory "+n.memoryBytes);const L=performance.now()-r;w.setMemoryLimit(n.memoryBytes),w.setMaxStackSize(n.stackBytes);const A=performance.now();w.setInterruptHandler(()=>performance.now()-A>n.executionMs),w.setModuleLoader(b=>(S(d,b)||o("undeclared import "+b),d[b]),(b,E)=>{if(S(p,E))return p[E];if(E.startsWith("."))return new URL(E,new URL(b,"file:///")).pathname;if(S(d,E))return E;o("undeclared import "+E)});const x=c.evalCode(d[l],l,{type:"module"});if(x.error){const b=c.dump(x.error);x.error.dispose(),o("guest "+JSON.stringify(b))}x.value.dispose();const v=c.evalCode("globalThis.result");if(v.error){const b=c.dump(v.error);v.error.dispose(),o("result "+JSON.stringify(b))}const B=c.dump(v.value);return v.value.dispose(),typeof B!="string"&&o("result must be declared text"),{output:B,initializedMs:L,executionMs:performance.now()-A}}finally{c?.dispose(),w.dispose()}}m(_,"runWasmRequest");function I(t){let s="";for(const a of t)s+=String.fromCharCode(a);return btoa(s)}m(I,"bytesBase64");async function T(t,{workerFactory:s=m(()=>new Worker(new URL("./worker.js",import.meta.url),{type:"module"}),"workerFactory"),signal:a,lifetimeMs:y}){return(!Number.isSafeInteger(y)||y<=0)&&o("worker lifetime"),a?.aborted&&o("cancelled"),R(t),new Promise((p,l)=>{const n=s();let g=!1;const f=m((r,u)=>{g||(g=!0,clearTimeout(h),a?.removeEventListener("abort",d),n.terminate(),r?l(r):p(u))},"finish"),d=m(()=>f(Error("REFUSE\xB7wasm cancelled")),"abort"),h=setTimeout(()=>f(Error("REFUSE\xB7wasm worker lifetime")),y);a?.addEventListener("abort",d,{once:!0});const e=m(r=>{const u=r;if(!u||typeof u!="object"||typeof u.ok!="boolean"){f(Error("REFUSE\xB7wasm malformed worker result"));return}if(u.ok){if(!u.result||typeof u.result.output!="string"){f(Error("REFUSE\xB7wasm malformed worker output"));return}f(null,u.result)}else f(Error(typeof u.error=="string"?u.error:"REFUSE\xB7wasm worker failure"))},"message");if(typeof n.addEventListener=="function")n.addEventListener("message",r=>e(r.data)),n.addEventListener("error",r=>f(Error("REFUSE\xB7wasm worker "+r.message)));else{if(!n.on){f(Error("REFUSE\xB7wasm worker event port"));return}n.on("message",e),n.on("error",r=>f(r instanceof Error?r:Error(String(r))))}if(a?.aborted){d();return}try{n.postMessage(t)}catch{f(Error("REFUSE\xB7wasm request is not cloneable"))}})}m(T,"evaluateWasm");export{F as digest,T as evaluateWasm,R as preflight,_ as runWasmRequest};
|
package/dist/wasm/node-worker.js
CHANGED
|
@@ -1,13 +1 @@
|
|
|
1
|
-
import
|
|
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
|
-
});
|
|
1
|
+
import{parentPort as s}from"node:worker_threads";import{runWasmRequest as t}from"./adapter.js";if(!s)throw Error("REFUSE\xB7wasm worker port missing");const o=s;o.once("message",async e=>{try{o.postMessage({ok:!0,result:await t(e)})}catch(r){o.postMessage({ok:!1,error:r instanceof Error?r.message:String(r)})}});
|
package/dist/wasm/object.js
CHANGED
|
@@ -1,76 +1 @@
|
|
|
1
|
-
import {
|
|
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' || !['esm@1', 'wasm@1'].includes(rows[0].value ?? ''))
|
|
14
|
-
refuse('unique declared backend required');
|
|
15
|
-
return rows[0].value === 'wasm@1';
|
|
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
|
-
}
|
|
1
|
+
var J=Object.defineProperty;var c=(o,e)=>J(o,"name",{value:e,configurable:!0});import{readFileSync as y}from"node:fs";import{Worker as B}from"node:worker_threads";import{sha256 as W}from"../host/hash.js";import{toposAt as $}from"../host/selected-topos.js";import{storeOf as q}from"../land/ledger.js";import{evaluateWasm as I}from"./adapter.js";function t(o){throw Error("REFUSE\xB7wasm object "+o)}c(t,"refuse");function M(o){const e=o.filter(a=>a.scope==="engine/object"&&a.value!=="withdraw");return e.length?((e.length!==1||e[0].role!=="writes"||e[0].measure!=="id"||e[0].form!=="alphabet"||!["esm@1","wasm@1"].includes(e[0].value??""))&&t("unique declared backend required"),e[0].value==="wasm@1"):!1}c(M,"usesWasmObject");function d(o,e,a){const s=o.filter(f=>f.scope==="engine/wasm/"+e&&f.value!=="withdraw");(s.length!==1||s[0].form!=="interval"||s[0].measure!==a||s[0].role!=="writes")&&t("missing or ambiguous "+e+" limit");const r=/^(0|[1-9][0-9]*)\.\.(0|[1-9][0-9]*)$/.exec(s[0].value??"");return(!r||!Number.isSafeInteger(Number(r[1]))||!Number.isSafeInteger(Number(r[2]))||Number(r[1])>Number(r[2])||Number(r[2])<1)&&t("invalid "+e+" limit"),Number(r[2])}c(d,"limit");async function P(o,e,a,s,r){M(s)||t("backend not selected");const f={memoryBytes:d(s,"memory","bytes"),stackBytes:d(s,"stack","bytes"),executionMs:d(s,"execution","milliseconds")},L=d(s,"worker-lifetime","milliseconds"),S=d(s,"response","bytes"),p=new URL("../../assets/wasm/",import.meta.url),l=JSON.parse(y(new URL("manifest.json",p),"utf8"));l.forms||t("installed form closure missing");const N={};for(const i of e.filter(n=>n.record==="cell")){const n=i.fields.topos,m=l.forms[n];(!m||!["intervalForm","alphabetOfForm"].includes(m.export)||m.form!==i.fields.form)&&t("unsupported form standing "+n);const k=$(n,q(o)),F=k.parts.filter(w=>w.scope==="wire/topos/context-view");F.length!==1&&t("context selection "+n);const v=k.parts.filter(w=>w.kind==="check"&&w.view===F[0].value);(v.length!==1||v[0].value!==m.contextArtifact||!(v[0].restsOn??"").split("|").includes(m.contextArtifact))&&t("context implementation differs "+n);const x=a.resolve(i);(x.digest!==n||x.form.id!==m.form)&&t("prepared form differs "+i.fields.scope),N[i.fields.scope]={digest:n,export:m.export,params:x.params}}const O=Object.keys(l.modules);O.length!==1&&t("installed guest module map");const h=O[0];let g;try{g=`import{objectFold,intervalForm,alphabetOfForm}from${JSON.stringify(h)};const forms={intervalForm,alphabetOfForm},codecs=${JSON.stringify(N)},records=${JSON.stringify(e)};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));`}catch{return t("context is not serializable")}const u=h+".entry",U=W(g),R=y(new URL("guest.js",p),"utf8"),A={[h]:R,[u]:g},E={trusted:{...l,modules:{...l.modules,[u]:U},entry:u,aliases:{},limits:f,byteLengths:{...l.byteLengths,modules:{...l.byteLengths.modules,[u]:Buffer.byteLength(g)}}},assets:{runtime:new Uint8Array(y(new URL("runtime.js",p))),wasm:new Uint8Array(y(new URL("runtime.wasm",p)))},modules:A,entry:u,aliases:{},limits:f},j=await I(E,{workerFactory:c(()=>new B(new URL("./node-worker.js",import.meta.url)),"workerFactory"),lifetimeMs:L,...r?{signal:r}:{}});Buffer.byteLength(j.output)>S&&t("response bytes limit");let b;try{b=JSON.parse(j.output)}catch{return t("invalid projection text")}return(!Array.isArray(b)||b.some(i=>!i||typeof i.cell!="string"||typeof i.state!="string"))&&t("invalid projection shape"),b}c(P,"foldObjectsWithWasm");export{P as foldObjectsWithWasm,M as usesWasmObject};
|
package/dist/wasm/worker.js
CHANGED
|
@@ -1,7 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
self.onmessage = async ({ data }) => { try {
|
|
3
|
-
self.postMessage({ ok: true, result: await runWasmRequest(data) });
|
|
4
|
-
}
|
|
5
|
-
catch (error) {
|
|
6
|
-
self.postMessage({ ok: false, error: String(error) });
|
|
7
|
-
} };
|
|
1
|
+
import{runWasmRequest as r}from"./adapter.js";self.onmessage=async({data:e})=>{try{self.postMessage({ok:!0,result:await r(e)})}catch(s){self.postMessage({ok:!1,error:String(s)})}};
|
|
@@ -18,6 +18,6 @@ Consumer verifies the expected container digest and repeats the same closure pre
|
|
|
18
18
|
|
|
19
19
|
The host must verify actual mounted bytes again before execution; the test host demonstrates this but is not a public browser host.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Native, program and worker custody remains in force. A pinned syntax analyzer detects unsupported loading expressions before execution; Node inventories and links the executable modules. Authenticated world loads require a declared closure and verified mounted bytes. This preflight is not a JavaScript execution sandbox.
|
|
22
22
|
|
|
23
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.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
{
|
|
5
5
|
"role": "contract",
|
|
6
6
|
"path": "docs/contracts/closure-delivery.md",
|
|
7
|
-
"digest": "sha256:
|
|
7
|
+
"digest": "sha256:e670a83d76a7c9d0558ff1f4f830c313497c9e809da409cece1caa5d2a079c8e"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"role": "yes",
|