@githolon/testing 0.7.1 → 0.9.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/package.json +1 -1
- package/vendor/engine/engine.mjs +177 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "@githolon/testing — law TDD for Nomos domains: boot the REAL engine plane in-process (the exact machinery every cloud DO, heavy container and web client runs), dispatch directives, assert rows, assert TYPED REFUSALS, fork for what-ifs — fully offline inside vitest.",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
package/vendor/engine/engine.mjs
CHANGED
|
@@ -53,7 +53,11 @@ function seedManifests(m, strings) {
|
|
|
53
53
|
m.set("identity-manifests.json", new File(enc.encode(strings.identity)));
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
// (#39 BAILIFF: no host-chosen `authorityScope` literal. It was a dead, hardcoded "workspace/root" stamped
|
|
57
|
+
// onto EVERY install — incl. non-root tenants — that NOTHING in the kernel ever reads for a decision
|
|
58
|
+
// (domain_lifecycle.rs folds it to an Option<String> and never branches on it). The host carries the
|
|
59
|
+
// package bytes; it does not author an authority-scoping fact the law never declared. Absent ⇒ folded None.)
|
|
60
|
+
export const installPayload = (hash, usda, installedBy, dispositions) => ({ domainHash: hash, packageUsda: usda, installedBy, dependencies: [], finalizers: [], ...(dispositions ? { dispositions } : {}) });
|
|
57
61
|
|
|
58
62
|
/**
|
|
59
63
|
* Boot the resident wasm + /work tree. `wasmModule` is a PRECOMPILED WebAssembly.Module
|
|
@@ -1190,7 +1194,10 @@ export function author(eng, ws, domain, directiveId, payload, controllerHash, op
|
|
|
1190
1194
|
// (`warmEngine`, which every host already calls) flushes it eagerly. Pass
|
|
1191
1195
|
// `opts.deferProjection: false` to keep the pre-#47 synchronous refresh.
|
|
1192
1196
|
const defer = opts.deferProjection !== false;
|
|
1193
|
-
|
|
1197
|
+
// authorSecret (warrant-flip): the author's client-held ed25519 device secret (b64), injected for THIS
|
|
1198
|
+
// local author ONLY so the wasm author door mints the signed-author attestation + self-verifies. Absent
|
|
1199
|
+
// on a legacy/unwarranted author (the door's legacy fast path); a warranted workspace REQUIRES it.
|
|
1200
|
+
const res = JSON.parse(call(eng.ex, "author", { repoArg: repoArgOf(ws), workspace: ws, domain, directiveId, payloadFile: `/work/payload-${seq}.json`, envelopeFile: `/work/envelope-${seq}.json`, seq, actor: opts.actor ?? "", domainFile: genesis ? "/work/domain.package.usda" : "", domainHash: genesis ? "" : controllerHash, branch: BRANCH, ...(opts.authorSecret ? { authorSecret: opts.authorSecret } : {}), ...(defer ? { deferProjection: true } : {}) }, eng.STDERR));
|
|
1194
1201
|
if (defer && res.ok) (eng.pendingProjection ??= new Set()).add(ws);
|
|
1195
1202
|
return res;
|
|
1196
1203
|
}
|
|
@@ -1227,6 +1234,115 @@ export function importCheckpoint(eng, ws, blobB64) {
|
|
|
1227
1234
|
return JSON.parse(call(eng.ex, "checkpoint_import", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH, blob: blobB64 }, eng.STDERR));
|
|
1228
1235
|
}
|
|
1229
1236
|
|
|
1237
|
+
// ── CHECKPOINT IN CUSTODY (the host-agnostic cold-mount; compute placement is beneath the contract) ──
|
|
1238
|
+
// The folded projection is a fact ABOUT the chain — so carry it IN custody (a git ref), not in some
|
|
1239
|
+
// host's private storage the NEXT host can't reach. The ephemeral heavy container has no ctx.storage;
|
|
1240
|
+
// a DO checkpoint is invisible to it; so root (heavy) re-folds O(chain) on every cold start. Put the
|
|
1241
|
+
// checkpoint where every peer already looks — the ledger — and ANY host restores it O(delta): the git
|
|
1242
|
+
// carries its own folded state. Keyed by the CURRENT law hash (schema parity): a law upgrade orphans
|
|
1243
|
+
// the old checkpoint (the next reader cold-folds once, re-publishes under the new hash). Best-effort &
|
|
1244
|
+
// post-ack — a missing/stale checkpoint costs at most one cold fold, never a wrong read; main is truth.
|
|
1245
|
+
const CKPT_REF = (lawHash) => `refs/meta/checkpoint/${lawHash}`;
|
|
1246
|
+
const CKPT_AUTHOR = { name: "nomos", email: "nomos@holon", timestamp: 0, timezoneOffset: 0 };
|
|
1247
|
+
|
|
1248
|
+
// A serialized SQLite projection is mostly FREE PAGES (the fold DELETEs+reinserts per commit) — it
|
|
1249
|
+
// gzips ~30×. We compress the b64 export host-side before it enters custody: the ledger meters pushed
|
|
1250
|
+
// bytes against the workspace quota, and a cold peer fetches less. Runtime-neutral (CompressionStream
|
|
1251
|
+
// is in Node 18+ and workerd). The `meta` blob records the codec so a future format stays readable.
|
|
1252
|
+
async function streamAll(stream) {
|
|
1253
|
+
const chunks = []; const reader = stream.getReader();
|
|
1254
|
+
for (;;) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); }
|
|
1255
|
+
let len = 0; for (const c of chunks) len += c.length;
|
|
1256
|
+
const out = new Uint8Array(len); let at = 0; for (const c of chunks) { out.set(c, at); at += c.length; }
|
|
1257
|
+
return out;
|
|
1258
|
+
}
|
|
1259
|
+
async function gzip(bytes) {
|
|
1260
|
+
const cs = new CompressionStream("gzip");
|
|
1261
|
+
return streamAll(new Response(bytes).body.pipeThrough(cs));
|
|
1262
|
+
}
|
|
1263
|
+
async function gunzip(bytes) {
|
|
1264
|
+
const ds = new DecompressionStream("gzip");
|
|
1265
|
+
return streamAll(new Response(bytes).body.pipeThrough(ds));
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
/** Wrap an exported checkpoint into git objects under `refs/meta/checkpoint/<lawHash>` (LOCAL only —
|
|
1269
|
+
* no network). The ref points at a parentless commit whose tree carries `checkpoint` (the b64 blob)
|
|
1270
|
+
* and `meta` ({cursor,bytes,lawHash}). Returns {ref, commitOid}. Separated from the push so the
|
|
1271
|
+
* object layer is verifiable offline. */
|
|
1272
|
+
export async function writeCheckpointObjects(eng, ws, ex, lawHash) {
|
|
1273
|
+
const gitdir = gitdirOf(ws);
|
|
1274
|
+
const gz = await gzip(enc.encode(ex.blob));
|
|
1275
|
+
const blobOid = await git.writeBlob({ fs: eng.fs, gitdir, blob: gz });
|
|
1276
|
+
const metaOid = await git.writeBlob({ fs: eng.fs, gitdir, blob: enc.encode(JSON.stringify({ cursor: ex.cursor, bytes: ex.bytes ?? 0, lawHash, codec: "gzip", raw: ex.blob.length, stored: gz.length })) });
|
|
1277
|
+
const tree = await git.writeTree({ fs: eng.fs, gitdir, tree: [
|
|
1278
|
+
{ mode: "100644", path: "checkpoint", oid: blobOid, type: "blob" },
|
|
1279
|
+
{ mode: "100644", path: "meta", oid: metaOid, type: "blob" },
|
|
1280
|
+
] });
|
|
1281
|
+
const commitOid = await git.writeCommit({ fs: eng.fs, gitdir, commit: { tree, parent: [], author: CKPT_AUTHOR, committer: CKPT_AUTHOR, message: `checkpoint ${ws} @${String(ex.cursor).slice(0, 16)}` } });
|
|
1282
|
+
const ref = CKPT_REF(lawHash);
|
|
1283
|
+
await git.writeRef({ fs: eng.fs, gitdir, ref, value: commitOid, force: true });
|
|
1284
|
+
return { ref, commitOid };
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/** Read a checkpoint blob back out of a commit oid (LOCAL only — the objects must already be present,
|
|
1288
|
+
* via a prior write or a shallow fetch). Returns the b64 string importCheckpoint accepts. */
|
|
1289
|
+
export async function readCheckpointBlob(eng, ws, commitOid) {
|
|
1290
|
+
const gitdir = gitdirOf(ws);
|
|
1291
|
+
const { blob } = await git.readBlob({ fs: eng.fs, gitdir, oid: commitOid, filepath: "checkpoint" });
|
|
1292
|
+
let codec = "gzip";
|
|
1293
|
+
try { codec = JSON.parse(dec.decode((await git.readBlob({ fs: eng.fs, gitdir, oid: commitOid, filepath: "meta" })).blob)).codec ?? "gzip"; } catch {}
|
|
1294
|
+
return dec.decode(codec === "gzip" ? await gunzip(blob) : blob);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/** The checkpoint's schema-parity key: a hash of the workspace's CURRENT law map (every installed
|
|
1298
|
+
* domain). The HOST passes nothing — the engine derives it from the chain (bailiff). A law upgrade
|
|
1299
|
+
* changes the map → a new key → the old checkpoint is orphaned (the next reader cold-folds once and
|
|
1300
|
+
* re-publishes). An explicit `lawHash` (the DO's existing govHash callers, tests) overrides. */
|
|
1301
|
+
export async function lawParity(eng, ws, lawHash) {
|
|
1302
|
+
if (lawHash) return lawHash;
|
|
1303
|
+
try {
|
|
1304
|
+
const map = currentLaw(eng, ws) || {};
|
|
1305
|
+
return await sha256hex(JSON.stringify(Object.entries(map).sort()));
|
|
1306
|
+
} catch { return null; }
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/** Export ws's folded projection-at-head and PUBLISH it to custody (`refs/meta/checkpoint/<lawHash>`).
|
|
1310
|
+
* Skips an unchanged head (dedups dup acks). Best-effort; returns {pushed, cursor?, bytes?, reason?}. */
|
|
1311
|
+
export async function pushCheckpoint(eng, ws, ledger, lawHash) {
|
|
1312
|
+
try {
|
|
1313
|
+
lawHash = await lawParity(eng, ws, lawHash);
|
|
1314
|
+
if (!lawHash) return { pushed: false, reason: "no-law" };
|
|
1315
|
+
const ex = exportCheckpoint(eng, ws);
|
|
1316
|
+
if (!ex || ex.empty || !ex.ok || !ex.blob) return { pushed: false, reason: ex && ex.empty ? "empty" : "no-blob" };
|
|
1317
|
+
eng._ckptPushed ??= new Map();
|
|
1318
|
+
const tag = `${lawHash}:${ex.cursor}`;
|
|
1319
|
+
if (eng._ckptPushed.get(ws) === tag) return { pushed: false, reason: "unchanged" };
|
|
1320
|
+
const { ref } = await writeCheckpointObjects(eng, ws, ex, lawHash);
|
|
1321
|
+
await git.push({ fs: eng.fs, http, gitdir: gitdirOf(ws), url: ledger.remote, ref, remoteRef: ref, force: true, headers: ledger.headers });
|
|
1322
|
+
eng._ckptPushed.set(ws, tag);
|
|
1323
|
+
return { pushed: true, cursor: ex.cursor, bytes: ex.bytes ?? 0 };
|
|
1324
|
+
} catch (e) { return { pushed: false, reason: String((e && e.message) || e).slice(0, 160) }; }
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/** Restore ws's projection from custody BEFORE the first read on a cold mount: ls-remote the law's
|
|
1328
|
+
* checkpoint ref, shallow-fetch ONLY that commit (O(checkpoint), never O(chain)), import it. No-op if
|
|
1329
|
+
* absent or the law drifted. Returns {restored, cursor?, reason?}. */
|
|
1330
|
+
export async function restoreCheckpointFromCustody(eng, ws, ledger, lawHash) {
|
|
1331
|
+
try {
|
|
1332
|
+
lawHash = await lawParity(eng, ws, lawHash);
|
|
1333
|
+
if (!lawHash) return { restored: false, reason: "no-law" };
|
|
1334
|
+
const ref = CKPT_REF(lawHash);
|
|
1335
|
+
const serverRefs = await git.listServerRefs({ http, url: ledger.remote, headers: ledger.headers, prefix: ref, protocolVersion: 2 });
|
|
1336
|
+
const found = (serverRefs || []).find((r) => r.ref === ref);
|
|
1337
|
+
if (!found || !found.oid) return { restored: false, reason: "absent" };
|
|
1338
|
+
const gitdir = gitdirOf(ws);
|
|
1339
|
+
await git.fetch({ fs: eng.fs, http, gitdir, url: ledger.remote, ref, singleBranch: true, depth: 1, headers: ledger.headers });
|
|
1340
|
+
const b64 = await readCheckpointBlob(eng, ws, found.oid);
|
|
1341
|
+
const r = importCheckpoint(eng, ws, b64);
|
|
1342
|
+
return { restored: r && r.restored === true, cursor: r && r.cursor, reason: r && r.reason };
|
|
1343
|
+
} catch (e) { return { restored: false, reason: String((e && e.message) || e).slice(0, 160) }; }
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1230
1346
|
// DURABILITY-BEFORE-ACK: only acked once the write LANDED in the ledger; a failed push
|
|
1231
1347
|
// unmounts (drops local state) so the next access re-restores from custody.
|
|
1232
1348
|
export async function commitAndPush(eng, ws, ledger) {
|
|
@@ -1238,6 +1354,47 @@ export async function commitAndPush(eng, ws, ledger) {
|
|
|
1238
1354
|
}
|
|
1239
1355
|
}
|
|
1240
1356
|
|
|
1357
|
+
// ── COMPUTE-PLACEMENT SHAPE (the ledger carries its own shape; compute placement is beneath the contract) ──
|
|
1358
|
+
// A workspace's PROJECTION SIZE (sqliteBytes from projection_stats) decides its tier: a projection that
|
|
1359
|
+
// fits the 128 MB edge box stays edge; one that outgrows it goes to the 12 GB container. Fold TIME no
|
|
1360
|
+
// longer gates the tier — the custody checkpoint (above) makes a cold mount O(delta) at ANY chain length;
|
|
1361
|
+
// only the RAM to HOLD the folded projection does. The size is recorded IN CUSTODY as the ref NAME
|
|
1362
|
+
// `refs/meta/shape/b<bytes>` (the ref points at head → ships NO objects, a pure ref update); ANY peer
|
|
1363
|
+
// reads it fold-free via ls-remote (the number is in the name — no fetch, immune to a fat head intent).
|
|
1364
|
+
// Best-effort, post-ack: a stale/missing shape self-heals (wrong box → re-measure); main is truth.
|
|
1365
|
+
export async function pushShape(eng, ws, ledger) {
|
|
1366
|
+
try {
|
|
1367
|
+
const st = projectionStats(eng, ws);
|
|
1368
|
+
const bytes = st && (st.sqliteBytes ?? st.bytes);
|
|
1369
|
+
if (bytes == null) return;
|
|
1370
|
+
const gitdir = gitdirOf(ws);
|
|
1371
|
+
const head = await git.resolveRef({ fs: eng.fs, gitdir, ref: "refs/heads/main" }).catch(() => null);
|
|
1372
|
+
if (!head) return;
|
|
1373
|
+
const newRef = `refs/meta/shape/b${bytes}`;
|
|
1374
|
+
eng._lastShapeRef ??= new Map();
|
|
1375
|
+
if (eng._lastShapeRef.get(ws) === newRef) return;
|
|
1376
|
+
await git.writeRef({ fs: eng.fs, gitdir, ref: newRef, value: head, force: true });
|
|
1377
|
+
await git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref: newRef, remoteRef: newRef, force: true, headers: ledger.headers });
|
|
1378
|
+
const prev = eng._lastShapeRef.get(ws);
|
|
1379
|
+
if (prev && prev !== newRef) { try { await git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref: prev, remoteRef: prev, delete: true, headers: ledger.headers }); } catch { /* leftover refs are harmless: the reader takes max(bytes) */ } }
|
|
1380
|
+
eng._lastShapeRef.set(ws, newRef);
|
|
1381
|
+
} catch { /* best-effort; placement self-heals */ }
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// Read the placement shape from custody, FOLD-FREE: ls-remote refs/meta/shape/b<bytes> and parse the name.
|
|
1385
|
+
// Returns {bytes} (the max, in case a restart left a stale ref) or null (no shape yet).
|
|
1386
|
+
export async function readShape(ledger) {
|
|
1387
|
+
try {
|
|
1388
|
+
const refs = await git.listServerRefs({ http, url: ledger.remote, headers: ledger.headers, prefix: "refs/meta/shape/", protocolVersion: 2 });
|
|
1389
|
+
let best = null;
|
|
1390
|
+
for (const r of refs || []) {
|
|
1391
|
+
const m = /refs\/meta\/shape\/b(\d+)$/.exec(r.ref || "");
|
|
1392
|
+
if (m) { const bytes = +m[1]; if (!best || bytes > best.bytes) best = { bytes }; }
|
|
1393
|
+
}
|
|
1394
|
+
return best;
|
|
1395
|
+
} catch { return null; }
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1241
1398
|
// READ VISIBILITY (slice 5.1): the host STAMPS the presented `principal` onto the
|
|
1242
1399
|
// read RPC (an impure identity read — never a host DECISION); the githolon gates the
|
|
1243
1400
|
// read against its OWN folded role-bindings and returns `{ok:false, error:"read-
|
|
@@ -1247,6 +1404,11 @@ export async function commitAndPush(eng, ws, ledger) {
|
|
|
1247
1404
|
// host-side (X25519 unwrap). Threaded into the read RPC; the wasm AEAD-opens envelope fields.
|
|
1248
1405
|
export const qById = (eng, ws, id, principal = "", keys = []) => JSON.parse(call(eng.ex, "query_by_id", { repoArg: repoArgOf(ws), workspace: ws, aggregateId: id, principal, keys, branch: BRANCH }, eng.STDERR));
|
|
1249
1406
|
export const query = (eng, ws, queryId, paramsJson, principal = "", keys = []) => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryId, paramsJson, principal, keys, branch: BRANCH }, eng.STDERR));
|
|
1407
|
+
// THE ZANZIBAR CHECK/EXPAND (governance authz spine): a pure, replay-deterministic authority probe
|
|
1408
|
+
// over the projected relations table. `check` → { ok, allowed:bool }; `expand` → { ok, subjects:[...] }.
|
|
1409
|
+
// No principal/keys: relation facts are public authority state (admin/creator/platformCreator/capability).
|
|
1410
|
+
export const check = (eng, ws, object, relation, subject) => JSON.parse(call(eng.ex, "check", { repoArg: repoArgOf(ws), workspace: ws, object, relation, subject, branch: BRANCH }, eng.STDERR));
|
|
1411
|
+
export const expand = (eng, ws, object, relation) => JSON.parse(call(eng.ex, "expand", { repoArg: repoArgOf(ws), workspace: ws, object, relation, branch: BRANCH }, eng.STDERR));
|
|
1250
1412
|
// THE AUTHOR-SIDE SEAL (slice 6): encrypt a plaintext field value into the on-chain envelope
|
|
1251
1413
|
// (XChaCha isn't in WebCrypto, so the client calls the wasm; the scope key is injected, never on chain).
|
|
1252
1414
|
// The SLOT IDENTITY is the #58 STABLE SID (rename-safety): the caller resolves (type,field)→sid
|
|
@@ -1257,6 +1419,18 @@ export const cryptoSealField = (eng, { scopeKey, scope, epoch, aggSid, fieldSid,
|
|
|
1257
1419
|
// The client X25519 crypto (slice 6): keygen (device identity), wrap (committer → recipient pubkey),
|
|
1258
1420
|
// unwrap (reader's device secret → scope key). The secret stays client/host-side, never on chain.
|
|
1259
1421
|
export const cryptoKeygen = (eng) => JSON.parse(call(eng.ex, "crypto_keygen", {}, eng.STDERR));
|
|
1422
|
+
|
|
1423
|
+
// AUTHOR DEVICE KEY (warrant-flip, identity_keys.md) — mint an ed25519 signing keypair IN the holon
|
|
1424
|
+
// (`author_keygen`); the secret stays client-side (here: the test/host injects it back as `authorSecret`
|
|
1425
|
+
// on the author call). Returns { ok, secret, public } (both base64). `author_sign` is the lower-level
|
|
1426
|
+
// standalone signer; the author DOOR signs internally from the injected secret, so callers use `author`.
|
|
1427
|
+
export const authorKeygen = (eng) => JSON.parse(call(eng.ex, "author_keygen", {}, eng.STDERR));
|
|
1428
|
+
// THE CROSS-CUSTODY ATTESTATION SIGNER (home_holons §3/§5) — the asserter's client produces a SIGNED QUOTE
|
|
1429
|
+
// over the edge it is sharing (`attestation_sign`; the bytes are derived IN-wasm — the `author_sign` parity
|
|
1430
|
+
// contract). The device `secret` (base64) is injected for THIS call only. Returns { ok, signature, publicKey }
|
|
1431
|
+
// (base64) — the parts a `recordShare` payload carries; the subject's home gate verifies them (§3 gate-arm).
|
|
1432
|
+
export const attestationSign = (eng, { secret, asserterWorkspace, object, relation, subject, attestedAt }) =>
|
|
1433
|
+
JSON.parse(call(eng.ex, "attestation_sign", { secret, asserterWorkspace, object, relation, subject, attestedAt }, eng.STDERR));
|
|
1260
1434
|
export const cryptoWrapKey = (eng, { recipientPub, scopeKey }) => JSON.parse(call(eng.ex, "crypto_wrap_key", { recipientPub, scopeKey }, eng.STDERR));
|
|
1261
1435
|
export const cryptoUnwrapKey = (eng, { secret, hpkeEpk, ct }) => JSON.parse(call(eng.ex, "crypto_unwrap_key", { secret, hpkeEpk, ct }, eng.STDERR)).scopeKey;
|
|
1262
1436
|
// THE READ-POSTURE PROBE (slice 5.1): the githolon's own verdict on whether the
|
|
@@ -1650,7 +1824,7 @@ export function stats(eng) {
|
|
|
1650
1824
|
}
|
|
1651
1825
|
|
|
1652
1826
|
/** Per-branch projection observables (cursor, projectCalls, sqliteBytes — see #37). */
|
|
1653
|
-
export const projectionStats = (eng) => JSON.parse(call(eng.ex, "projection_stats", {}, eng.STDERR));
|
|
1827
|
+
export const projectionStats = (eng, ws) => JSON.parse(call(eng.ex, "projection_stats", ws ? { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH } : {}, eng.STDERR));
|
|
1654
1828
|
|
|
1655
1829
|
/**
|
|
1656
1830
|
* THE POST-ACK WARM LANE (#34): replenish the wasm engine's pristine-sandbox pool
|