@githolon/testing 0.7.1 → 0.8.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/testing",
3
- "version": "0.7.1",
3
+ "version": "0.8.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",
@@ -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
- export const installPayload = (hash, usda, installedBy, dispositions) => ({ domainHash: hash, packageUsda: usda, installedBy, authorityScope: "workspace/root", dependencies: [], finalizers: [], ...(dispositions ? { dispositions } : {}) });
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
- 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, ...(defer ? { deferProjection: true } : {}) }, eng.STDERR));
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,12 @@ 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));
1260
1428
  export const cryptoWrapKey = (eng, { recipientPub, scopeKey }) => JSON.parse(call(eng.ex, "crypto_wrap_key", { recipientPub, scopeKey }, eng.STDERR));
1261
1429
  export const cryptoUnwrapKey = (eng, { secret, hpkeEpk, ct }) => JSON.parse(call(eng.ex, "crypto_unwrap_key", { secret, hpkeEpk, ct }, eng.STDERR)).scopeKey;
1262
1430
  // THE READ-POSTURE PROBE (slice 5.1): the githolon's own verdict on whether the
@@ -1650,7 +1818,7 @@ export function stats(eng) {
1650
1818
  }
1651
1819
 
1652
1820
  /** Per-branch projection observables (cursor, projectCalls, sqliteBytes — see #37). */
1653
- export const projectionStats = (eng) => JSON.parse(call(eng.ex, "projection_stats", {}, eng.STDERR));
1821
+ export const projectionStats = (eng, ws) => JSON.parse(call(eng.ex, "projection_stats", ws ? { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH } : {}, eng.STDERR));
1654
1822
 
1655
1823
  /**
1656
1824
  * THE POST-ACK WARM LANE (#34): replenish the wasm engine's pristine-sandbox pool