@githolon/testing 0.16.3 → 0.18.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.16.3",
3
+ "version": "0.18.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",
@@ -1001,6 +1001,9 @@ export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains
1001
1001
  const sessions = new Map();
1002
1002
  const deadLetters = [];
1003
1003
  let admittedTotal = 0;
1004
+ // BORN: the G3 birth offer-effect's child heads any `.births()` offer in this batch produced (the kernel
1005
+ // surfaced them on the admit verdict). Accumulated across the batch and relayed out for the host to persist.
1006
+ const born = [];
1004
1007
  const timings = { spans: [], intents: 0, sessions: 0 };
1005
1008
  // THE §5.2 SUMMARY CAPTURE on the OFFER LANE (offers are the only source of intents — this lane is the one
1006
1009
  // admit, routed workspaces included). A shard (selfLabel set) with scoped reads captures the per-batch
@@ -1080,6 +1083,7 @@ export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains
1080
1083
  main.oids.add(res.head); main.head = res.head;
1081
1084
  rec.admitted.push({ oid, head: res.head });
1082
1085
  admittedTotal += 1;
1086
+ if (Array.isArray(res.born) && res.born.length) born.push(...res.born);
1083
1087
  if (cap) finishSummaryCapture(eng, ws, summaryCtx, cap, res.head, intentId);
1084
1088
  } else {
1085
1089
  const error = `${String(res.error || "").slice(0, 300)} — dead-lettered; fix the law (or the payload), then POST /v1/workspaces/${ws}/dead-letters/retry`;
@@ -1133,7 +1137,7 @@ export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains
1133
1137
  } catch { /* fall back to the client's own GET /pack */ }
1134
1138
  }
1135
1139
  log("admit-intent-offers", { ws, sessions: sessions.size, admitted: admittedTotal, rejected: [...sessions.values()].reduce((n, s) => n + s.rejected.length, 0), resultPack: resultPackB64 ? "inline" : "none" });
1136
- return { sessions: [...sessions.values()].map((s) => ({ ...s, ...(s.skipped.length ? {} : { skipped: undefined }) })), main: head, deadLetters, timings, ...(summary ? { summary } : {}), ...(resultPackB64 ? { resultPackB64, resultBase: clientBase } : {}), ...(deferDurability && admittedTotal && head ? { deferred: true, head } : {}) };
1140
+ return { sessions: [...sessions.values()].map((s) => ({ ...s, ...(s.skipped.length ? {} : { skipped: undefined }) })), main: head, deadLetters, timings, ...(summary ? { summary } : {}), ...(born.length ? { born } : {}), ...(resultPackB64 ? { resultPackB64, resultBase: clientBase } : {}), ...(deferDurability && admittedTotal && head ? { deferred: true, head } : {}) };
1137
1141
  }
1138
1142
 
1139
1143
  /**
@@ -1171,7 +1175,9 @@ export async function authorOn(eng, ws, ledger, domain, directiveId, payload, la
1171
1175
  }
1172
1176
  if (res && res.ok === false) return { ok: false, error: String(res.error || "the law refused this intent").slice(0, 500) };
1173
1177
  await commitAndPush(eng, ws, ledger);
1174
- return { ok: true, head: res.head };
1178
+ // KEEP `born`: a `.births()` directive authored here ran the G3 birth offer-effect; surface the child
1179
+ // heads so the container author case → worker out.born relays them (the host persists the child custody).
1180
+ return { ok: true, head: res.head, ...(res.born ? { born: res.born } : {}) };
1175
1181
  }
1176
1182
 
1177
1183
  /**
@@ -1378,7 +1384,22 @@ export function author(eng, ws, domain, directiveId, payload, controllerHash, op
1378
1384
  // costs 19 draws — see NOMOS_API_SHIM). 64 is the per-write default (3 mints of slack);
1379
1385
  // a BATCH-creating directive needs 19×creates. COMMIT-ONLY-CONSUMED: only the consumed
1380
1386
  // prefix is sealed onto the intent, so a generous buffer is request weight, not ledger weight.
1381
- const envelope = { payload: { domain, directiveId, payload }, captured_ports: { clock: { physical: Date.now(), logical: 0, replica: eng.replica }, rng: osEntropyBuffer(opts.rngDraws ?? 64) }, policy_version: 1, policy_domain: "Nomos", policy_gas: 0, policy_memory: 0 };
1387
+ // opts.validFromEpoch (VA Phase 4, TEST/ADVERSARIAL ONLY): force a CLAIMED signer epoch onto the
1388
+ // captured ports. The author lane preserves a NON-ZERO carried value (it only auto-stamps the prefix
1389
+ // epoch when 0) and signs over it (captured_ports ⊂ authored_core_bytes), so a smoke can mint a signed
1390
+ // intent whose claim is a deliberate backdate/forward-date — the kernel's epoch arm must refuse it.
1391
+ const capPorts = { clock: { physical: Date.now(), logical: 0, replica: eng.replica }, rng: osEntropyBuffer(opts.rngDraws ?? 64) };
1392
+ if (opts.validFromEpoch !== undefined) capPorts.valid_from_epoch = opts.validFromEpoch;
1393
+ // opts.principalVerified (VA — the verified-birth lane): stamp the offer's principal + verified verdict onto
1394
+ // the captured ports, exactly as the cloud offer gate does from a verified AuthProvider token. The birthHome
1395
+ // offer-effect reads `captured_ports.principal`/`principal_verified` as the GATE VERDICT and requires the
1396
+ // device-signed homeCert's `verified` to MATCH it (CAS-as-law). A test driving the verified lane sets this
1397
+ // so the kernel accepts a `verified:true` home cert; absent ⇒ the unverified lane (verified=false).
1398
+ if (opts.principalVerified !== undefined) {
1399
+ capPorts.principal = opts.actor ?? "";
1400
+ capPorts.principal_verified = !!opts.principalVerified;
1401
+ }
1402
+ const envelope = { payload: { domain, directiveId, payload }, captured_ports: capPorts, policy_version: 1, policy_domain: "Nomos", policy_gas: 0, policy_memory: 0 };
1382
1403
  writeWork(eng, `payload-${seq}.json`, enc.encode(JSON.stringify(payload)));
1383
1404
  writeWork(eng, `envelope-${seq}.json`, enc.encode(stringifyBig(envelope)));
1384
1405
  const genesis = !controllerHash;
@@ -1398,14 +1419,36 @@ export function author(eng, ws, domain, directiveId, payload, controllerHash, op
1398
1419
  // DRY-RUN (offer-kernel): `offer` with the commit withheld — run the plan + full gate, get the verdict,
1399
1420
  // commit nothing. This is the home of the old `evolve_dryrun` verb (see `evolveDryRun`). Never defers
1400
1421
  // projection (no commit to flush).
1401
- const v = JSON.parse(call(eng.ex, "offer", { 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 } : {}), ...(opts.dryRun ? { dryRun: true } : (defer ? { deferProjection: true } : {})) }, eng.STDERR));
1402
- const res = v.outcome === "admitted" ? { ok: true, head: v.head, intentOut: v.intentOut }
1422
+ const v = JSON.parse(call(eng.ex, "offer", { repoArg: repoArgOf(ws), workspace: ws, domain, directiveId, payloadFile: `/work/payload-${seq}.json`, envelopeFile: `/work/envelope-${seq}.json`, seq, actor: opts.actor ?? "", ...(opts.authToken ? { authToken: opts.authToken } : {}), domainFile: genesis ? "/work/domain.package.usda" : "", domainHash: genesis ? "" : controllerHash, branch: BRANCH, ...(opts.authorSecret ? { authorSecret: opts.authorSecret } : {}), ...(opts.dryRun ? { dryRun: true } : (defer ? { deferProjection: true } : {})) }, eng.STDERR));
1423
+ // KEEP `born`: the wasm offer ran the G3 birth offer-effect and surfaced the child heads; thread them
1424
+ // through the normalized shape so the relay (container author case → worker out.born) sees them.
1425
+ const res = v.outcome === "admitted" ? { ok: true, head: v.head, intentOut: v.intentOut, ...(v.born ? { born: v.born } : {}) }
1403
1426
  : v.outcome === "refused" ? { ok: false, error: v.verdict?.reason ?? v.error }
1404
1427
  : v; // defensive: a non-verdict shape passes through unchanged
1405
1428
  if (defer && res.ok) (eng.pendingProjection ??= new Set()).add(ws);
1406
1429
  return res;
1407
1430
  }
1408
1431
 
1432
+ /** BIRTH SLICE 2 — the KERNEL produces the genesis. Fold a parent-authored genesis chain
1433
+ * (`[{domain,directiveId,payload,actor}]`) into `ws` in ONE atomic custody op (the kernel owns the fold;
1434
+ * the adapter delivered the opaque recipe + the impurity capability). `clockMs`/`rngSeed` are the
1435
+ * DETERMINISTIC injection — derive `rngSeed` per-birth-unique (e.g. from the child identity) so minted ids
1436
+ * never collide. Replaces the adapter's old per-step genesis-author loop. */
1437
+ export function materializeGenesis(eng, ws, genesisChain, opts = {}) {
1438
+ return JSON.parse(call(eng.ex, "materialize_genesis", {
1439
+ repoArg: repoArgOf(ws), workspace: ws, genesisChain,
1440
+ clockMs: opts.clockMs ?? 1700000000000, replica: eng.replica, rngSeed: opts.rngSeed ?? 1,
1441
+ branch: BRANCH,
1442
+ }, eng.STDERR));
1443
+ }
1444
+
1445
+ /** TEST/HARNESS-ONLY (the `test-mint` wasm): pin the root-of-trust anchor `K_root` to a public key the
1446
+ * harness controls, so a parent-attested-birth refusal can be driven THROUGH the real kernel verb against a
1447
+ * real test anchor. `kRootPublicB64` = base64 of the 32 raw ed25519 public-key bytes of the harness's S_root. */
1448
+ export function setTestKRoot(eng, kRootPublicB64) {
1449
+ return JSON.parse(call(eng.ex, "set_test_k_root", { kRootPublicB64 }, eng.STDERR));
1450
+ }
1451
+
1409
1452
  /** Catch one workspace's read projection up to head NOW (the deferred-ack flush). P3: rides the
1410
1453
  * `query` verb (op:"refresh") — a non-effectful maintenance read, not a top-level adapter verb. */
1411
1454
  export function refreshProjection(eng, ws) {
@@ -1437,8 +1480,11 @@ export function exportCheckpoint(eng, ws) {
1437
1480
  /** Restore a checkpoint blob into the pool BEFORE the first read on a cold mount (no-op if the pool
1438
1481
  * is already warm). The ADAPTER restores ONLY a blob whose stored lawHash matches the workspace's
1439
1482
  * CURRENT law (schema parity); a mismatched/absent blob costs at most a cold fold, never a wrong read. */
1440
- export function importCheckpoint(eng, ws, blobB64, frontierB64) {
1441
- return JSON.parse(call(eng.ex, "checkpoint_import", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH, blob: blobB64, ...(frontierB64 ? { frontier: frontierB64 } : {}) }, eng.STDERR));
1483
+ export function importCheckpoint(eng, ws, blobB64, frontierB64, stateRoot) {
1484
+ // `stateRoot` (the kernel's deterministic projection state-root stamped at export) is passed back so the
1485
+ // kernel RE-DERIVES the restored blob's root and byte-compares — a corrupted/swapped blob is refused
1486
+ // (restored:false), the caller cold-folds. Absent (a pre-this-era checkpoint) ⇒ no check, cold-fold fallback.
1487
+ return JSON.parse(call(eng.ex, "checkpoint_import", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH, blob: blobB64, ...(frontierB64 ? { frontier: frontierB64 } : {}), ...(stateRoot ? { stateRoot } : {}) }, eng.STDERR));
1442
1488
  }
1443
1489
 
1444
1490
  // ── CHECKPOINT IN CUSTODY (the adapter-agnostic cold-mount; compute placement is beneath the contract) ──
@@ -1478,19 +1524,27 @@ async function gunzip(bytes) {
1478
1524
  * object layer is verifiable offline. */
1479
1525
  export async function writeCheckpointObjects(eng, ws, ex, lawHash) {
1480
1526
  const gitdir = gitdirOf(ws);
1481
- const gz = await gzip(enc.encode(ex.blob));
1482
- // THE FOLD FRONTIER (the gate's Workspace state @cursor) rides ALONGSIDE the projection so a cold mount
1483
- // can AUTHOR on a shallow clone (the projection alone is a read-collapsed view the gate can't fold from).
1484
- // Era-safe: pre-this-era exports carry no `ex.frontier`, so the `frontier` file is simply absent and a
1485
- // reader falls back to a full clone (writes need full history). Gzipped like the projection blob.
1486
- const files = [{ path: "checkpoint", bytes: gz }];
1527
+ // THE CHECKPOINT IS THE VERIFIED FOLD: the kernel now emits ONLY `ex.frontier` (the fold), NOT a
1528
+ // SQLite `ex.blob` (that image is non-deterministic). The host is a transparent byte-shuttle it
1529
+ // stores whatever the kernel returns; a legacy export still carrying `ex.blob` is shuttled too (the
1530
+ // `checkpoint` file is absent when the kernel omits the blob). Gzipped.
1531
+ const files = [];
1532
+ let rawLen = 0, gzLen = 0;
1533
+ if (ex.blob) {
1534
+ const gz = await gzip(enc.encode(ex.blob));
1535
+ rawLen = ex.blob.length; gzLen = gz.length;
1536
+ files.push({ path: "checkpoint", bytes: gz });
1537
+ }
1538
+ // THE FOLD FRONTIER (the gate's Workspace state @cursor) IS the cold-mount artifact: a cold mount
1539
+ // re-projects the read model from it (kernel-verified against the oid-pinned trailer) and AUTHORS
1540
+ // forward on a shallow clone. Era-safe: an export without `ex.frontier` is shuttled as-is.
1487
1541
  let fgzLen = 0;
1488
1542
  if (ex.frontier) {
1489
1543
  const fgz = await gzip(enc.encode(ex.frontier));
1490
1544
  fgzLen = fgz.length;
1491
1545
  files.push({ path: "frontier", bytes: fgz });
1492
1546
  }
1493
- files.push({ path: "meta", bytes: enc.encode(JSON.stringify({ cursor: ex.cursor, bytes: ex.bytes ?? 0, lawHash, codec: "gzip", raw: ex.blob.length, stored: gz.length, frontierStored: fgzLen })) });
1547
+ files.push({ path: "meta", bytes: enc.encode(JSON.stringify({ cursor: ex.cursor, bytes: ex.bytes ?? 0, lawHash, codec: "gzip", raw: rawLen, stored: gzLen, frontierStored: fgzLen, ...(ex.stateRoot ? { stateRoot: ex.stateRoot } : {}) })) });
1494
1548
  const ref = CKPT_REF(lawHash);
1495
1549
  // ONE kernel call: blobs→tree→parentless commit→force ref. Fixed timeSecs ⇒ deterministic (idempotent).
1496
1550
  const commitOid = kgit.writeMetaCommit(eng, gitdir, { parents: [], files, message: `checkpoint ${ws} @${String(ex.cursor).slice(0, 16)}`, timeSecs: 0, ref });
@@ -1503,13 +1557,17 @@ export async function writeCheckpointObjects(eng, ws, ex, lawHash) {
1503
1557
  * pre-this-era checkpoint (no `frontier` file). */
1504
1558
  export async function readCheckpointBlob(eng, ws, commitOid) {
1505
1559
  const gitdir = gitdirOf(ws);
1506
- const blob = kgit.readBlob(eng, gitdir, commitOid, "checkpoint");
1507
1560
  let codec = "gzip";
1508
- try { codec = JSON.parse(dec.decode(kgit.readBlob(eng, gitdir, commitOid, "meta"))).codec ?? "gzip"; } catch {}
1561
+ let stateRoot = null;
1562
+ try { const m = JSON.parse(dec.decode(kgit.readBlob(eng, gitdir, commitOid, "meta"))); codec = m.codec ?? "gzip"; stateRoot = m.stateRoot ?? null; } catch {}
1509
1563
  const dezip = async (b) => dec.decode(codec === "gzip" ? await gunzip(b) : b);
1564
+ // THE FOLD FRONTIER is the cold-mount artifact ("the checkpoint IS the verified fold"); the legacy
1565
+ // SQLite `checkpoint` file is absent on current exports. Both reads are tolerant (a missing file ⇒ null).
1566
+ let blob = null;
1567
+ try { const b = kgit.readBlob(eng, gitdir, commitOid, "checkpoint"); if (b) blob = await dezip(b); } catch { /* current-era: no SQLite blob */ }
1510
1568
  let frontier = null;
1511
1569
  try { const f = kgit.readBlob(eng, gitdir, commitOid, "frontier"); if (f) frontier = await dezip(f); } catch { /* pre-this-era: no frontier */ }
1512
- return { blob: await dezip(blob), frontier };
1570
+ return { blob, frontier, stateRoot };
1513
1571
  }
1514
1572
 
1515
1573
  /** The checkpoint's schema-parity key: a hash of the workspace's CURRENT law map (every installed
@@ -1618,8 +1676,8 @@ export async function restoreCheckpointFromCustody(eng, ws, ledger, lawHash) {
1618
1676
  if (!foundOid) return { restored: false, reason: "absent" };
1619
1677
  // Fetch ONLY the checkpoint commit (parentless → O(checkpoint), never O(chain)) into ws's repo + apply_pack.
1620
1678
  await custodyFetch(eng, ws, ledger, { remoteRef: ref });
1621
- const { blob, frontier } = await readCheckpointBlob(eng, ws, foundOid);
1622
- const r = importCheckpoint(eng, ws, blob, frontier);
1679
+ const { blob, frontier, stateRoot } = await readCheckpointBlob(eng, ws, foundOid);
1680
+ const r = importCheckpoint(eng, ws, blob, frontier, stateRoot);
1623
1681
  if (r && r.restored === true && r.cursor) {
1624
1682
  eng._ckptPushed ??= new Map();
1625
1683
  eng._ckptPushed.set(ws, `${lawHash}:${r.cursor}`);
@@ -1775,12 +1833,31 @@ export const cryptoKeygen = (eng) => JSON.parse(call(eng.ex, "query", { queryByt
1775
1833
  // on the author call). Returns { ok, secret, public } (both base64). `author_sign` is the lower-level
1776
1834
  // standalone signer; the author DOOR signs internally from the injected secret, so callers use `author`.
1777
1835
  export const authorKeygen = (eng) => JSON.parse(call(eng.ex, "query", { queryBytes: b64Json({ op: "authorKeygen" }) }, eng.STDERR));
1836
+
1837
+ // DESCRIBE (#43) — the workspace's self-describing API, DERIVED BY THE KERNEL from the installed law at
1838
+ // the FRONTIER (not a from-genesis fold): rpc_describe reads the active-domain set off the projection at
1839
+ // head and runs each active package's GENERIC `describe` verb over its OWN registered modules, merging
1840
+ // them (byte-identical to the compiler's <name>.openapi.json for a single package) + tagging
1841
+ // `x-nomos-described` (the hashes covered, for the host's thin staged-overlay fallback on a pre-describe
1842
+ // lump). The kernel returns the merged JSON. MUST live HERE (the adapter source) — deploy.sh syncs this
1843
+ // into container/src, so editing only the synced copy gets clobbered (the "unknown container op describe").
1844
+ export function describe(eng, ws) {
1845
+ return JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "describe", workspace: ws }), branch: BRANCH }, eng.STDERR));
1846
+ }
1778
1847
  // THE CROSS-CUSTODY ATTESTATION SIGNER (home_holons §3/§5) — the asserter's client produces a SIGNED QUOTE
1779
1848
  // over the edge it is sharing (`attestation_sign`; the bytes are derived IN-wasm — the `author_sign` parity
1780
1849
  // contract). The device `secret` (base64) is injected for THIS call only. Returns { ok, signature, publicKey }
1781
1850
  // (base64) — the parts a `recordShare` payload carries; the subject's home gate verifies them (§3 gate-arm).
1782
1851
  export const attestationSign = (eng, { secret, asserterWorkspace, object, relation, subject, attestedAt }) =>
1783
1852
  JSON.parse(call(eng.ex, "query", { queryBytes: b64Json({ op: "attestationSign", secret, asserterWorkspace, object, relation, subject, attestedAt }) }, eng.STDERR));
1853
+ // THE BIRTH-CERT SIGNER (VA root-of-trust) — the SINGLE SOURCE OF TRUTH for the cert byte format. The
1854
+ // signer's client produces a SIGNED birth cert IN-wasm (`cert_sign` reuses `BirthCert::signed_bytes`/`sign`,
1855
+ // the SIBLING of `attestation_sign`) so no client re-implements the layout (drift-proof). The device
1856
+ // `secret` (base64) is injected for THIS call only; `parentKey` is the base64 ed25519 PUBLIC half matching
1857
+ // it (as the cert names it). Returns { ok, cert } where `cert` is the signed cert JSON (snake_case,
1858
+ // signature as a byte array) — the EXACT shape `verify_to_root` parses.
1859
+ export const certSign = (eng, { child, founder, verified, parentKey, parentId, epoch, secret }) =>
1860
+ JSON.parse(call(eng.ex, "query", { queryBytes: b64Json({ op: "certSign", child, founder, verified, parentKey, parentId, epoch, secret }) }, eng.STDERR)).cert;
1784
1861
  export const cryptoWrapKey = (eng, { recipientPub, scopeKey }) => JSON.parse(call(eng.ex, "query", { queryBytes: b64Json({ op: "cryptoWrapKey", recipientPub, scopeKey }) }, eng.STDERR));
1785
1862
  export const cryptoUnwrapKey = (eng, { secret, hpkeEpk, ct }) => JSON.parse(call(eng.ex, "query", { queryBytes: b64Json({ op: "cryptoUnwrapKey", secret, hpkeEpk, ct }) }, eng.STDERR)).scopeKey;
1786
1863
  // THE READ-POSTURE PROBE (slice 5.1): the githolon's own verdict on whether the
@@ -1847,8 +1924,10 @@ export function offerIntent(eng, ws, bytes, opts) {
1847
1924
  */
1848
1925
  export function offerAdmit(eng, ws, bytes, opts) {
1849
1926
  const v = offerIntent(eng, ws, bytes, opts);
1927
+ // KEEP `born`: a SEALED `.births()` offer ran the G3 birth offer-effect in admit_core and surfaced the
1928
+ // child heads on the verdict; carry them through so the admit lane (admitIntentOffers) relays them.
1850
1929
  return v.outcome === "admitted"
1851
- ? { ok: true, head: v.head }
1930
+ ? { ok: true, head: v.head, ...(v.born ? { born: v.born } : {}) }
1852
1931
  : { ok: false, error: v.verdict?.reason ?? v.error ?? "the offer was refused" };
1853
1932
  }
1854
1933