@githolon/testing 0.15.0 → 0.16.1

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.
@@ -1,31 +1,41 @@
1
- // AUTO-SYNCED from cloud/holon-host/src/engine.mjs by testing/build.mjs — DO NOT EDIT HERE.
2
- // Nomos Cloud — THE ENGINE PLANE (one machinery, two hosts).
1
+ // AUTO-SYNCED from adapter/engine.mjs by testing/build.mjs — DO NOT EDIT HERE.
2
+ // Nomos — THE CLOUDFLARE SHELL's ENGINE PLANE (the adapter logic that boots + drives the offer-kernel).
3
3
  //
4
- // Everything that touches a resident holon wasm instance + its /work tree lives HERE,
5
- // extracted from the HolonDO so the 12GiB CONTAINER tier runs the IDENTICAL logic the
6
- // edge DO runs (the old container worker was a drifting fork killed). The hosts differ
7
- // only in what they inject:
4
+ // Everything that boots the offer-kernel wasm + drives its /work tree lives HERE (the adapter the
5
+ // Cloudflare shell wraps; "host" is out OFFER_KERNEL_ARCHITECTURE.dot). THE OFFER-KERNEL RUNS IN THE
6
+ // 12GiB CONTAINER that is its only runtime now (we no longer run the kernel on the edge DO). The
7
+ // container (container/server.mjs) is a thin Node wrapper around this file: wasm compiled from disk, replica
8
+ // from the container instance base, ledgers ARRIVE on each op (containers have no Workers bindings — the
9
+ // control-plane DO mints the Artifacts token and sends it).
8
10
  //
9
- // * the EDGE host (worker.mjs HolonDO): wasm via the wrangler CompiledWasm import,
10
- // replica from the DO id, ledgers minted from env.ARTIFACTS, runs in workerd;
11
- // * the HEAVY host (container/holon-worker.mjs): wasm compiled from disk, replica
12
- // from the container instance base, ledgers ARRIVE on each op (containers have no
13
- // Workers bindings — the control-plane DO mints tokens and sends them), runs in Node.
14
- //
15
- // CONTROL PLANE STAYS IN THE DO: ownership secrets, the DLQ store,
11
+ // CONTROL PLANE STAYS IN THE DO (CloudflareShellDO — it does NOT run the kernel): ownership secrets, the DLQ store,
16
12
  // quotas/rates, alarms. The engine RETURNS data (e.g. dead-letter entries with intent
17
- // bytes) and the host persists. Runtime-neutral: workerd + Node 22 (globalThis.crypto,
13
+ // bytes) and the adapter persists. Runtime-neutral: workerd + Node 22 (globalThis.crypto,
18
14
  // fetch, TextEncoder are the only ambient dependencies).
19
15
  import { WASI, File, Directory, OpenFile, ConsoleStdout, PreopenDirectory } from "@bjorn3/browser_wasi_shim";
20
- import git from "isomorphic-git";
21
- import http from "isomorphic-git/http/web";
22
16
  import { makeGitFs } from "./git-fs.mjs";
23
17
  import { serializeTree } from "./tree.mjs";
18
+ // THE DUMB-PIPE CUSTODY TRANSPORT (raw git smart-HTTP over fetch, zero isomorphic-git). Same-dir module,
19
+ // synced into the container context by deploy.sh alongside engine.mjs.
20
+ import { lsRefs, fetchPack, pushPack, ZERO_OID } from "./custody-transport.mjs";
24
21
 
25
22
  const enc = new TextEncoder(), dec = new TextDecoder();
26
23
  const BRANCH = "main";
24
+ // The largest result-delta the offer response will carry inline (settle perf — saves the client a second
25
+ // round-trip). A normal write's delta is a few KiB; a far-behind client exceeds this and falls back to GET /pack.
26
+ const RESULT_PACK_INLINE_MAX = 256 * 1024;
27
27
  export const repoArgOf = (ws) => `/work/ws/${ws}`;
28
28
  export const gitdirOf = (ws) => `/work/ws/${ws}/nomos.git`;
29
+ function b64Bytes(bytes) {
30
+ let s = "";
31
+ for (let i = 0; i < bytes.length; i += 0x8000) {
32
+ s += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
33
+ }
34
+ return btoa(s);
35
+ }
36
+ function b64Json(o) {
37
+ return b64Bytes(enc.encode(JSON.stringify(o)));
38
+ }
29
39
 
30
40
  const unpack = (p) => { const v = typeof p === "bigint" ? p : BigInt(p); return { ptr: Number(v >> 32n), len: Number(v & 0xffffffffn) }; };
31
41
  export async function sha256hex(t) { const b = await crypto.subtle.digest("SHA-256", enc.encode(t)); return [...new Uint8Array(b)].map((x) => x.toString(16).padStart(2, "0")).join(""); }
@@ -55,23 +65,62 @@ function call(ex, mode, fields, STDERR) {
55
65
  } finally { ex.git_holon_dealloc(reqPtr, req.length); }
56
66
  }
57
67
 
68
+ // ── KERNEL GIT PLUMBING (kgit) — the cloud shell speaks NO git; the kernel (gix) owns the odb. ──
69
+ // These wrap the kernel's local-object RPCs (the adapter is a byte pipe, never touches objects/refs).
70
+ // They mirror the isomorphic-git call shapes they replace so the migration is a 1:1 substitution.
71
+ // The repo dir the wasm opens is `${repoArg}/nomos.git`; the adapter names it as `gitdir` everywhere, so
72
+ // derive repoArg back from it (gitdir === gitdirOf(ws) === `${repoArgOf(ws)}/nomos.git`).
73
+ const repoArgOfGitdir = (gitdir) => gitdir.replace(/\/nomos\.git$/, "");
74
+ const bytesFromB64 = (b64) => Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
75
+ const kgit = {
76
+ // resolveRef → tip oid hex, or null if the ref is absent (replaces git.resolveRef(...).catch(()=>null)).
77
+ resolveRef: (eng, gitdir, ref) =>
78
+ JSON.parse(call(eng.ex, "resolve_ref", { repoArg: repoArgOfGitdir(gitdir), ref }, eng.STDERR)).oid,
79
+ // readBlob → the blob bytes at `path` inside commit `oid`, or null if not found (no throw).
80
+ readBlob: (eng, gitdir, oid, path) => {
81
+ const r = JSON.parse(call(eng.ex, "read_blob", { repoArg: repoArgOfGitdir(gitdir), oid, path }, eng.STDERR));
82
+ return r.ok ? bytesFromB64(r.blobB64) : null;
83
+ },
84
+ // intentsAbove → [{ oid, intentB64 }] oldest→newest over (base, head] (replaces log + readBlob intent.json).
85
+ // `limit` (0 = all) caps to the most-recent N intent commits (replaces git.log depth).
86
+ intentsAbove: (eng, gitdir, head, base = "", limit = 0) =>
87
+ JSON.parse(call(eng.ex, "intents_above", { repoArg: repoArgOfGitdir(gitdir), head, base, limit }, eng.STDERR)).intents,
88
+ // listRefs → [{ ref, oid }] under prefix (replaces git.listRefs for LOCAL refs).
89
+ listRefs: (eng, gitdir, prefix) =>
90
+ JSON.parse(call(eng.ex, "list_refs", { repoArg: repoArgOfGitdir(gitdir), prefix }, eng.STDERR)).refs,
91
+ // writeRef → force-point a ref (plain oid or `ref:`-symbolic) (replaces git.writeRef).
92
+ writeRef: (eng, gitdir, ref, value) =>
93
+ JSON.parse(call(eng.ex, "write_ref", { repoArg: repoArgOfGitdir(gitdir), ref, value }, eng.STDERR)),
94
+ // deleteRef → remove a local ref, idempotent (replaces git.deleteRef).
95
+ deleteRef: (eng, gitdir, ref) =>
96
+ JSON.parse(call(eng.ex, "delete_ref", { repoArg: repoArgOfGitdir(gitdir), ref }, eng.STDERR)),
97
+ // writeMetaCommit → blobs→tree→commit→ref for a CUSTODY-META fact (checkpoint/refused/outbox/shape).
98
+ // `files`: [{ path, bytes:Uint8Array }]; returns the commit oid (replaces writeBlob+writeTree+writeCommit[+writeRef]).
99
+ writeMetaCommit: (eng, gitdir, { parents = [], files, message = "nomos custody meta", timeSecs = 0, ref = "" }) =>
100
+ JSON.parse(call(eng.ex, "write_meta_commit", {
101
+ repoArg: repoArgOfGitdir(gitdir), parents,
102
+ files: files.map((f) => ({ path: f.path, blobB64: b64Bytes(f.bytes) })),
103
+ message, timeSecs, ref,
104
+ }, eng.STDERR)).oid,
105
+ };
106
+
58
107
  function seedManifests(_m, _strings) {
59
108
  // Runtime manifests are committed inside USDA packages and derived by the holon
60
109
  // from active installed law. Hosts must not seed admission/projection sidecars.
61
110
  }
62
111
 
63
- // (#39 BAILIFF: no host-chosen `authorityScope` literal. It was a dead, hardcoded "workspace/root" stamped
112
+ // (#39 BAILIFF: no adapter-chosen `authorityScope` literal. It was a dead, hardcoded "workspace/root" stamped
64
113
  // onto EVERY install — incl. non-root tenants — that NOTHING in the kernel ever reads for a decision
65
- // (domain_lifecycle.rs folds it to an Option<String> and never branches on it). The host carries the
114
+ // (domain_lifecycle.rs folds it to an Option<String> and never branches on it). The adapter carries the
66
115
  // package bytes; it does not author an authority-scoping fact the law never declared. Absent ⇒ folded None.)
67
116
  export const installPayload = (hash, usda, installedBy, dispositions) => ({ domainHash: hash, packageUsda: usda, installedBy, dependencies: [], finalizers: [], ...(dispositions ? { dispositions } : {}) });
68
117
 
69
118
  /**
70
119
  * Boot the resident wasm + /work tree. `wasmModule` is a PRECOMPILED WebAssembly.Module
71
- * (the host compiles its way); `replica` = the host's unique 63-bit id.
120
+ * (the adapter compiles its way); `replica` = the adapter's unique 63-bit id.
72
121
  *
73
122
  * Runtime read/admission metadata is sovereign holon state: committed USDA packages
74
- * are folded by the githolon, and the host never stages manifest sidecars.
123
+ * are folded by the githolon, and the adapter never stages manifest sidecars.
75
124
  */
76
125
  export async function createEngine({ wasmModule, bootstrapPkg, nomosPkg, replica, installedBy = "nomos-cloud" }) {
77
126
  const hashes = { bootstrap: await sha256hex(bootstrapPkg), nomos: await sha256hex(nomosPkg) };
@@ -96,11 +145,11 @@ export async function createEngine({ wasmModule, bootstrapPkg, nomosPkg, replica
96
145
  return eng;
97
146
  }
98
147
 
99
- // There is NO host→holon manifest channel. The wasm derives every read/identity
148
+ // There is NO adapter→holon manifest channel. The wasm derives every read/identity
100
149
  // manifest from the FOLDED ledger (installed_read_manifest_bytes_from_state) and
101
- // refuses a package lacking nomosReadProjection. A host cannot provide a manifest:
150
+ // refuses a package lacking nomosReadProjection. A adapter cannot provide a manifest:
102
151
  // the op does not exist (Read Confinement — the kernel's transition function, not a
103
- // host convention). `setManifests` is gone, not no-op'd, so the lie is unrepresentable.
152
+ // adapter convention). `setManifests` is gone, not no-op'd, so the lie is unrepresentable.
104
153
 
105
154
  // ── TRANSPARENT SHARDING: the WRONG-HOME GATE (the edge bailiff — sharding slice 2) ──
106
155
  //
@@ -117,8 +166,8 @@ export async function createEngine({ wasmModule, bootstrapPkg, nomosPkg, replica
117
166
  /** directive routes off the effective identity manifests: `${domain} ${directiveId}` → route. */
118
167
  export function directiveRoutes(eng) {
119
168
  void eng;
120
- // Host-side manifest taxonomy is retired. Shard routing needs a holon-owned
121
- // taxonomy API before it can be re-enabled without leaking law into the host.
169
+ // Adapter-side manifest taxonomy is retired. Shard routing needs a holon-owned
170
+ // taxonomy API before it can be re-enabled without leaking law into the adapter.
122
171
  return new Map();
123
172
  }
124
173
 
@@ -133,7 +182,7 @@ function routeTagHexOfMintedId(id) {
133
182
  }
134
183
 
135
184
  /**
136
- * Judge ONE routed intent against the shard map. `shardRouting` is HOST-INJECTED:
185
+ * Judge ONE routed intent against the shard map. `shardRouting` is ADAPTER-INJECTED:
137
186
  * `{ selfLabel, coordinatorWs, assignments() }` — `selfLabel` is this workspace's shard
138
187
  * label (`s<k>`) or null when it IS the coordinator; `assignments()` resolves the
139
188
  * ACTIVE `NomosShardAssignment` rows from the coordinator's law-state. Returns null to
@@ -187,7 +236,7 @@ export async function wrongHomeVerdict(shardRouting, route, payload) {
187
236
 
188
237
  // ── §5.2 — THE DELTA LANE (sharding slice 3): the shard-side EMITTER machinery ──
189
238
  //
190
- // After a shard's edge admission folds a batch, the HOST authors one coalesced
239
+ // After a shard's edge admission folds a batch, the ADAPTER authors one coalesced
191
240
  // `nomosPropagateSummary` Order into the COORDINATOR's chain: per touched
192
241
  // (scoped read, group) the ABSOLUTE subtotal + the covered range's per-intent
193
242
  // event deltas as content-addressed evidence. The coordinator's ONE gate
@@ -209,7 +258,7 @@ export const NOMOS_SHARD_ROWS_READ = "nomosShardRows";
209
258
  const FRAMEWORK_AGG_TYPES = new Set([
210
259
  "NomosShardAssignment", "NomosShardIdentity", "NomosHomeReceipt", "NomosShardRegistry",
211
260
  "NomosShardPolicy", "NomosSummarySubtotal", "NomosSummaryFrontier", "NomosDeepVerify",
212
- "NomosCheckpointSeal",
261
+ "NomosCheckpointSeal", "NomosMoveObligation", // P6 — the move-leg obligation (coordinator law-state)
213
262
  // CONTROL-PLANE law-state (every chain carries them via installDomain) — never tenant data:
214
263
  "PolicyBundle", "DomainInstallation",
215
264
  ]);
@@ -233,7 +282,7 @@ export function placementDirectives(eng) {
233
282
  /** The ESTATE-SCOPED maintained reads off the effective identity manifests.
234
283
  * SLICE 4: `where` (the canonical predicate, hash-bearing in the manifest) rides
235
284
  * along — the LIVE capture's oracle is the projection's maintained tally (already
236
- * predicate-aware in the kernel); the SUFFIX re-derivation evaluates it host-side
285
+ * predicate-aware in the kernel); the SUFFIX re-derivation evaluates it adapter-side
237
286
  * ({@link evalCanonicalPred}) so both lanes agree byte-for-byte. */
238
287
  export function scopedReads(eng) {
239
288
  void eng;
@@ -242,7 +291,7 @@ export function scopedReads(eng) {
242
291
 
243
292
  /**
244
293
  * Evaluate a canonical count/sum predicate (`predicate.ts` CanonicalPred — eq/ne
245
- * clauses + and/or trees) over a host-folded field map. EXACTLY the kernel's
294
+ * clauses + and/or trees) over a adapter-folded field map. EXACTLY the kernel's
246
295
  * declared semantics: values canonicalize to strings (int fields compare their
247
296
  * decimal form); `eq` over an ABSENT field is FALSE, `ne` over an absent field is
248
297
  * TRUE (unset ≠ any literal — the §7-D symmetry rule). Used ONLY by the suffix
@@ -365,7 +414,7 @@ function finishSummaryCapture(eng, ws, ctx, cap, oid, intentId) {
365
414
  ctx.values.set(key, { read, group, value: after });
366
415
  }
367
416
  // The synthetic row-count read: existence flips, with the projection as oracle.
368
- // The ABSOLUTE rides relative to the coordinator-held value (the HOST closes it
417
+ // The ABSOLUTE rides relative to the coordinator-held value (the ADAPTER closes it
369
418
  // at emit time — a shard never enumerates its own whole projection per batch).
370
419
  // SLICE 8 — EXACT PER-HOME SUBTOTALS: each flip is also attributed to its HOME
371
420
  // (the row id's route tag, or the row id itself for an axis root) so the split
@@ -396,7 +445,7 @@ function finishSummaryCapture(eng, ws, ctx, cap, oid, intentId) {
396
445
 
397
446
  /**
398
447
  * The coalesced delta of one admission batch (null when nothing summarizable moved).
399
- * `rowsDelta` rides separately: the HOST closes the `nomosShardRows` absolute against
448
+ * `rowsDelta` rides separately: the ADAPTER closes the `nomosShardRows` absolute against
400
449
  * the coordinator-held value at emit time (held + Σ deltas — the gate re-checks both
401
450
  * halves). The other subtotals' `prev` is the batch-start maintained tally, which
402
451
  * equals the coordinator-held value exactly when the frontier is contiguous — and the
@@ -431,7 +480,7 @@ function summaryOfCapture(ctx, fromOid, toOid) {
431
480
  subtotals: subtotals.sort((a, b) => (summaryKey(a.readId, a.group) < summaryKey(b.readId, b.group) ? -1 : 1)),
432
481
  events: ctx.events,
433
482
  rowsDelta: ctx.rowsDelta,
434
- // SLICE 8: the exact per-home row deltas — the host closes each against the
483
+ // SLICE 8: the exact per-home row deltas — the adapter closes each against the
435
484
  // coordinator-held per-home subtotal exactly like the "" grand total.
436
485
  rowsByHome: Object.fromEntries([...ctx.rowsByHome.entries()].sort((a, b) => (a[0] < b[0] ? -1 : 1))),
437
486
  };
@@ -439,7 +488,7 @@ function summaryOfCapture(ctx, fromOid, toOid) {
439
488
 
440
489
  /**
441
490
  * THE SUFFIX RE-DERIVATION (the frontier-gap self-heal + /summaries/resync): derive
442
- * the delta payload for (fromOid → main head] from THE CHAIN ITSELF — a host-side
491
+ * the delta payload for (fromOid → main head] from THE CHAIN ITSELF — a adapter-side
443
492
  * pure fold of the scoped reads' group/sum fields over every commit since genesis
444
493
  * (Lww in chain order; scoped reads are predicate-free by the compile gate). Used
445
494
  * when the coordinator's recorded frontier does not match a live capture (a lost
@@ -448,10 +497,9 @@ function summaryOfCapture(ctx, fromOid, toOid) {
448
497
  export async function deriveSummarySuffix(eng, ws, fromOid) {
449
498
  const reads = scopedReads(eng);
450
499
  const gitdir = gitdirOf(ws);
451
- const head = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null);
500
+ const head = kgit.resolveRef(eng, gitdir, BRANCH);
452
501
  if (!head) return { ok: false, error: "no local main to derive from" };
453
- const commits = await git.log({ fs: eng.fs, gitdir, ref: BRANCH });
454
- commits.reverse(); // genesis → head
502
+ const commits = kgit.intentsAbove(eng, gitdir, head); // [{oid, intentB64}] genesis → head
455
503
  const state = new Map(); // aggregateId -> { type, fields: Map }
456
504
  const running = new Map(); // summaryKey -> value (ADDITIVE kinds)
457
505
  const bump = (key, d) => running.set(key, (running.get(key) ?? 0) + d);
@@ -481,7 +529,7 @@ export async function deriveSummarySuffix(eng, ws, fromOid) {
481
529
  let sawFrom = inSuffix;
482
530
  for (const c of commits) {
483
531
  let blob = null;
484
- try { blob = (await git.readBlob({ fs: eng.fs, gitdir, oid: c.oid, filepath: "intent.json" })).blob; } catch {}
532
+ try { blob = bytesFromB64(c.intentB64); } catch {}
485
533
  const deltas = [];
486
534
  let intentId = "";
487
535
  if (blob) {
@@ -628,7 +676,7 @@ export async function deriveSummarySuffix(eng, ws, fromOid) {
628
676
  };
629
677
  }
630
678
 
631
- /** One read's contribution shape over a host-folded aggregate (suffix derivation only).
679
+ /** One read's contribution shape over a adapter-folded aggregate (suffix derivation only).
632
680
  * SLICE 4: predicate-aware — a `where`-bearing scoped read contributes only while the
633
681
  * folded fields satisfy the canonical predicate (same semantics as the kernel's
634
682
  * maintained tally, so the suffix lane and the live capture agree byte-for-byte). */
@@ -658,7 +706,7 @@ function contributionDiff(read, before, after) {
658
706
 
659
707
  // ── §6 — THE SHARD LIFECYCLE (slice 5): migration = re-admission, never trust ──
660
708
  //
661
- // The host legs of a split: the TARGET pulls the moving homes' intents off the
709
+ // The adapter legs of a split: the TARGET pulls the moving homes' intents off the
662
710
  // SOURCE chain and re-admits EACH through its own gate (`apply_intent` — the same
663
711
  // primitive the upload birth and edge admission run: plan re-run, byte-compared,
664
712
  // invariants judged); the SOURCE then seals the move with ONE law intent that
@@ -710,14 +758,14 @@ export async function collectHomedIntents(eng, ws, homeKeys) {
710
758
  for (const hk of homeKeys) tagOf.set(await routeTagHexOfHomeKey(hk), hk);
711
759
  const keySet = new Set(homeKeys);
712
760
  const gitdir = gitdirOf(ws);
713
- const commits = await git.log({ fs: eng.fs, gitdir, ref: BRANCH });
714
- commits.reverse(); // genesis → head
761
+ const head = kgit.resolveRef(eng, gitdir, BRANCH);
762
+ const commits = head ? kgit.intentsAbove(eng, gitdir, head) : []; // [{oid, intentB64}] genesis → head
715
763
  const docs = [];
716
764
  const strikeParity = new Map(); // intentId -> strike count
717
765
  for (const c of commits) {
718
766
  let doc = null;
719
767
  try {
720
- const { blob } = await git.readBlob({ fs: eng.fs, gitdir, oid: c.oid, filepath: "intent.json" });
768
+ const blob = bytesFromB64(c.intentB64);
721
769
  doc = JSON.parse(dec.decode(blob));
722
770
  docs.push({ oid: c.oid, blob, doc });
723
771
  } catch { continue; }
@@ -761,7 +809,7 @@ export async function migrateHomes(eng, ws, ledger, sourceWs, homeKeys, { onlyId
761
809
  for (const e of homed) {
762
810
  if (wanted && !wanted.has(e.id)) continue;
763
811
  if (main.ids.has(e.id)) { skipped.push(e.id); continue; }
764
- const res = applyIntentBytes(eng, ws, e.blob);
812
+ const res = offerAdmit(eng, ws, e.blob);
765
813
  if (res.ok) {
766
814
  main.ids.add(e.id); main.oids.add(res.head); main.head = res.head;
767
815
  migrated.push(e.id);
@@ -811,7 +859,7 @@ export async function sealHomes(eng, ws, ledger, { domain, lawHash, homeKeys, ta
811
859
  * chain head carrying the diffs (deep-verify recomputes from the same oracle).
812
860
  */
813
861
  export async function summaryCloseAgainstHeld(eng, ws, heldRows, heldFrontier) {
814
- const head = await git.resolveRef({ fs: eng.fs, gitdir: gitdirOf(ws), ref: BRANCH }).catch(() => null);
862
+ const head = kgit.resolveRef(eng, gitdirOf(ws), BRANCH);
815
863
  if (!head) return { ok: false, error: "no local main to close against" };
816
864
  if (head === heldFrontier) return { ok: true, upToDate: true, head };
817
865
  const subtotals = [];
@@ -880,15 +928,15 @@ export async function collectGlobalIntents(eng, ws) {
880
928
  const globals = globalAggregates(eng);
881
929
  if (globals.size === 0) return [];
882
930
  const gitdir = gitdirOf(ws);
883
- const commits = await git.log({ fs: eng.fs, gitdir, ref: BRANCH });
884
- commits.reverse(); // genesis → head
931
+ const head = kgit.resolveRef(eng, gitdir, BRANCH);
932
+ const commits = head ? kgit.intentsAbove(eng, gitdir, head) : []; // [{oid, intentB64}] genesis → head
885
933
  const typeOf = new Map(); // aggregateId -> __type (set once at creation — types never change)
886
934
  const strikeParity = new Map();
887
935
  const docs = [];
888
936
  for (const c of commits) {
889
937
  let blob = null, doc = null;
890
938
  try {
891
- blob = (await git.readBlob({ fs: eng.fs, gitdir, oid: c.oid, filepath: "intent.json" })).blob;
939
+ blob = bytesFromB64(c.intentB64);
892
940
  doc = JSON.parse(dec.decode(blob));
893
941
  } catch { continue; }
894
942
  docs.push({ oid: c.oid, blob, doc });
@@ -924,7 +972,7 @@ export async function replicateIntents(eng, ws, ledger, intents, { push = true }
924
972
  const replicated = [], skipped = [], refused = [];
925
973
  for (const e of intents) {
926
974
  if (e.id && main.ids.has(e.id)) { skipped.push(e.id); continue; }
927
- const res = applyIntentBytes(eng, ws, e.blob);
975
+ const res = offerAdmit(eng, ws, e.blob);
928
976
  if (res.ok) {
929
977
  if (e.id) main.ids.add(e.id);
930
978
  main.oids.add(res.head); main.head = res.head;
@@ -934,7 +982,7 @@ export async function replicateIntents(eng, ws, ledger, intents, { push = true }
934
982
  }
935
983
  }
936
984
  // push=false is the OFFLINE proof lane only (bench harness — no ledger remote);
937
- // every host leg keeps durability-before-ack.
985
+ // every adapter leg keeps durability-before-ack.
938
986
  if (replicated.length && push) await commitAndPush(eng, ws, ledger);
939
987
  log("replicate-globals", { ws, replicated: replicated.length, skipped: skipped.length, refused: refused.length });
940
988
  return { replicated, skipped, refused };
@@ -942,18 +990,37 @@ export async function replicateIntents(eng, ws, ledger, intents, { push = true }
942
990
 
943
991
  /**
944
992
  * Open browser/session lane without a client-side Artifacts receive-pack: the
945
- * client offers the exact intent bytes it authored locally; the host applies the
993
+ * client offers the exact intent bytes it authored locally; the adapter applies the
946
994
  * same structural fences as session admission, folds accepted bytes, and seals
947
995
  * main once. This is still untrusted work: owner-only/control-plane/receipt
948
996
  * writes are refused or dead-lettered exactly like session refs.
949
997
  */
950
- export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains = [], receiptFence = null } = {}) {
998
+ export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains = [], receiptFence = null, shardRouting = null, clientBase = null, defer = false } = {}) {
951
999
  const refuse = new Set(refuseDomains);
952
1000
  const main = await mainIntentIds(eng, ws);
953
1001
  const sessions = new Map();
954
1002
  const deadLetters = [];
955
1003
  let admittedTotal = 0;
956
1004
  const timings = { spans: [], intents: 0, sessions: 0 };
1005
+ // THE §5.2 SUMMARY CAPTURE on the OFFER LANE (offers are the only source of intents — this lane is the one
1006
+ // admit, routed workspaces included). A shard (selfLabel set) with scoped reads captures the per-batch
1007
+ // subtotal delta the same way the session-branch lane does; the adapter then closes it onto the coordinator
1008
+ // (`_postAdmissionHooks` → `_emitSummary`). Null on an unsharded/coordinator/read-free batch (zero cost).
1009
+ const summaryCtx = shardRouting && shardRouting.selfLabel && scopedReads(eng).length > 0
1010
+ ? { reads: scopedReads(eng), prevs: new Map(), values: new Map(), events: [], rowsDelta: 0, rederived: new Set(), rowsByHome: new Map(), homeTags: new Map(), homeKeySet: new Set() }
1011
+ : null;
1012
+ if (summaryCtx) {
1013
+ try {
1014
+ for (const r of query(eng, ws, "aggregatesByType", JSON.stringify({ __type: "NomosHomeReceipt" }))) {
1015
+ const hk = r.data && typeof r.data.homeKey === "string" ? r.data.homeKey : null;
1016
+ if (!hk) continue;
1017
+ summaryCtx.homeKeySet.add(hk);
1018
+ summaryCtx.homeTags.set(await routeTagHexOfHomeKey(hk), hk);
1019
+ }
1020
+ } catch (e) { log("home-tag-table-error", { ws, error: String(e).slice(0, 200) }); }
1021
+ }
1022
+ const gitdir0 = gitdirOf(ws);
1023
+ const headBefore = summaryCtx ? kgit.resolveRef(eng, gitdir0, BRANCH) : null;
957
1024
  const t0 = Date.now(); let seq = 0;
958
1025
  const span = (name, kind, depth = 1, extra = {}) => {
959
1026
  const s0 = Date.now();
@@ -977,8 +1044,15 @@ export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains
977
1044
  const dom = typeof doc?.payload?.domain === "string" ? doc.payload.domain : null;
978
1045
  const dirId = typeof doc?.payload?.directiveId === "string" ? doc.payload.directiveId : null;
979
1046
  const oid = String(offer?.oid || intentId || "").slice(0, 10);
980
- if (!dom || dom === "nomos" || dom === "bootstrap" || refuse.has(dom)) {
981
- rec.rejected.push({ oid, error: `session lane refuses ${dom ? `'${dom}'` : "non-authored"} intents law deploys go through POST /v1/workspaces/${ws}/domains with the workspace secret` });
1047
+ // THE GATE DECIDES (offer-kernel doctrine OFFER_KERNEL_ARCHITECTURE.dot): the adapter does NOT
1048
+ // pre-filter control-plane (`nomos`/`bootstrap`) intents by domain string that was the adapter
1049
+ // "choosing admission", which the doctrine forbids. An install/governance offer rides the SAME lane;
1050
+ // the kernel gate refuses an author who lacks the declared relation (installDomain.requiresRelationGate
1051
+ // = {relation:"installDomain", object:"workspace:self"}), so an unauthorized control-plane write is a
1052
+ // TYPED GATE refusal, not a adapter verdict. Only `!dom` (malformed) and the explicit per-workspace
1053
+ // `refuse` fence (root's `workspaces` governance lane) stay — those are structural, not authority.
1054
+ if (!dom || refuse.has(dom)) {
1055
+ rec.rejected.push({ oid, error: `${dom ? `'${dom}' is adapter-fenced on this workspace` : "non-authored intent"} — governance enters its own admin lane` });
982
1056
  continue;
983
1057
  }
984
1058
  if (receiptFence && dirId && Array.isArray(receiptFence[dom]) && receiptFence[dom].includes(dirId)) {
@@ -991,12 +1065,22 @@ export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains
991
1065
  rec.skipped.push({ oid, intent: intentId.slice(0, 12), alreadyOnMain: true });
992
1066
  continue;
993
1067
  }
994
- const res = applyIntentBytes(eng, ws, blob);
1068
+ const cap = summaryCtx ? beginSummaryCapture(eng, ws, summaryCtx, blob) : null;
1069
+ // DEFERRED-PROJECTION ACK on the OFFER lane (#47). The per-intent read-projection refresh
1070
+ // (~220ms on a fat ledger) is the warm-sync hot-path waste: the fold IS the correctness step,
1071
+ // the projection only feeds READS, and every read op self-heals to head. So we ACK on the fold
1072
+ // and let the post-ack warm lane (`flushDeferredProjections`, marked below) catch the projection
1073
+ // up — exactly as the session lane (admitAll) already does. A summaryCtx (§5.2) batch keeps the
1074
+ // synchronous refresh: its scoped-read capture re-derives off committed projection state.
1075
+ const _tOffer = Date.now();
1076
+ const res = offerAdmit(eng, ws, blob, { defer: !summaryCtx });
1077
+ log("offer-admit-ms", { ws, ms: Date.now() - _tOffer, deferred: !summaryCtx, ok: res.ok });
995
1078
  if (res.ok) {
996
1079
  if (intentId) main.ids.add(intentId);
997
1080
  main.oids.add(res.head); main.head = res.head;
998
1081
  rec.admitted.push({ oid, head: res.head });
999
1082
  admittedTotal += 1;
1083
+ if (cap) finishSummaryCapture(eng, ws, summaryCtx, cap, res.head, intentId);
1000
1084
  } else {
1001
1085
  const error = `${String(res.error || "").slice(0, 300)} — dead-lettered; fix the law (or the payload), then POST /v1/workspaces/${ws}/dead-letters/retry`;
1002
1086
  rec.rejected.push({ oid, error, deadLettered: true });
@@ -1010,22 +1094,53 @@ export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains
1010
1094
  }
1011
1095
  timings.sessions = sessions.size;
1012
1096
  timings.intents = admittedTotal;
1013
- if (admittedTotal) {
1097
+ // The deferred projection (above) is flushed by the post-ack warm lane — register this ws so
1098
+ // `flushDeferredProjections` catches it up after we've ACKed. (summaryCtx never deferred.)
1099
+ if (admittedTotal && !summaryCtx) (eng.pendingProjection ??= new Set()).add(ws);
1100
+ // OFFER-KERNEL P1.2b: refusals on the offer lane become git-custody facts too (pushed inline,
1101
+ // before the seal that unmounts on failure). Runs even when nothing admitted — a cycle can be
1102
+ // pure refusals.
1103
+ if (deadLetters.length) { try { await materializeRefused(eng, ws, ledger, deadLetters); } catch (e) { log("refused-materialize-failed", { ws, error: String(e).slice(0, 200) }); } }
1104
+ // GIT OFF THE HOT PATH. The fold above is the whole correctness step — the client said "btw I did
1105
+ // this", we re-executed it, resident main is now truth. We ACK on that. Custody (Artifacts) is a
1106
+ // write-behind MIRROR, never on the hot path: when `defer` is set the adapter backs the captured head
1107
+ // up off-lane (server.mjs custodyWriteBehind → backupHead), coalesced to one push in flight. The
1108
+ // CONTAINER IS THE SOLE WRITER of main, so the mirror needs no pull-back — it just FF-pushes the
1109
+ // snapshot. A summaryCtx (§5.2) batch still seals durable-before-ack (its follow-ups depend on it).
1110
+ const deferDurability = defer && !summaryCtx;
1111
+ if (admittedTotal && !deferDurability) {
1014
1112
  const sealDone = span("seal offered intents", "cseal", 1, { admitted: admittedTotal });
1015
1113
  await commitAndPush(eng, ws, ledger);
1016
1114
  sealDone();
1017
1115
  }
1018
1116
  const gitdir = gitdirOf(ws);
1019
- const head = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => main.head);
1020
- log("admit-intent-offers", { ws, sessions: sessions.size, admitted: admittedTotal, rejected: [...sessions.values()].reduce((n, s) => n + s.rejected.length, 0) });
1021
- return { sessions: [...sessions.values()].map((s) => ({ ...s, ...(s.skipped.length ? {} : { skipped: undefined }) })), main: head, deadLetters, timings };
1117
+ const head = kgit.resolveRef(eng, gitdir, BRANCH) ?? main.head;
1118
+ // The coalesced §5.2 delta of this batch (null when no scoped read moved) the adapter closes it onto the
1119
+ // coordinator (`_postAdmissionHooks` `_emitSummary`), exactly as the session-branch lane does.
1120
+ const summary = summaryCtx ? summaryOfCapture(summaryCtx, headBefore, head) : null;
1121
+ // RESULT-PACK PIGGYBACK (settle perf): the offer RE-SEALS the intent onto main (a NEW commit oid the
1122
+ // client lacks), so the client otherwise makes a SECOND round-trip (GET /pack?have=base) just to pull
1123
+ // what this admit produced — the ~500ms "prefetch" phase. Since we already hold every object, carry that
1124
+ // exact delta (base→head) BACK in the offer response so the client applies it with zero extra network.
1125
+ // Bounded: only inline a small delta (a normal write is one commit + tree); a far-behind client's large
1126
+ // delta falls back to GET /pack (no worse than today). The kernel odb has `head` post-fold even under
1127
+ // deferred durability (custody push is write-behind; the objects are resident), so this is available now.
1128
+ let resultPackB64 = null;
1129
+ if (admittedTotal && head && /^[0-9a-f]{40}$/i.test(clientBase || "") && clientBase !== head) {
1130
+ try {
1131
+ const p = servePack(eng, ws, clientBase);
1132
+ if (p && p.packB64 && p.bytes && p.bytes <= RESULT_PACK_INLINE_MAX) resultPackB64 = p.packB64;
1133
+ } catch { /* fall back to the client's own GET /pack */ }
1134
+ }
1135
+ 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 } : {}) };
1022
1137
  }
1023
1138
 
1024
1139
  /**
1025
1140
  * §5.5 (slice 4) — the CONTENT HASH of the coordinator's committed subtotal state:
1026
1141
  * sha256 over the canonical (row-id-sorted) projection of every
1027
1142
  * `NomosSummarySubtotal` row. The ONE definition every checkpoint party shares —
1028
- * the sealing host claims it, an auditor (deep-verify anchoring, the e2e, any
1143
+ * the sealing adapter claims it, an auditor (deep-verify anchoring, the e2e, any
1029
1144
  * peer that mounts the chain) recomputes it from law-state and byte-compares.
1030
1145
  */
1031
1146
  export async function checkpointStateHash(eng, ws) {
@@ -1064,7 +1179,7 @@ export async function authorOn(eng, ws, ledger, domain, directiveId, payload, la
1064
1179
  * the SHARD's whole chain from genesis (the same wasm `verify_chain` the upload
1065
1180
  * birth runs), then recompute every coordinator-held subtotal for that shard from
1066
1181
  * the shard's OWN maintained tallies and byte-compare. `coordinatorRows` is the
1067
- * coordinator's law-state (`NomosSummarySubtotal` rows for this shard) — the HOST
1182
+ * coordinator's law-state (`NomosSummarySubtotal` rows for this shard) — the ADAPTER
1068
1183
  * resolves and passes them (the engine has no cross-workspace reach). Auditors pay
1069
1184
  * for verification; ordinary reads stay O(1).
1070
1185
  */
@@ -1111,7 +1226,7 @@ export async function deepVerifyShard(eng, shardWs, shardLedger, coordinatorRows
1111
1226
  if (!match) allMatch = false;
1112
1227
  checks.push({ readId: d.readId, group, coordinator: d.value, shard: recomputed, match });
1113
1228
  }
1114
- const head = await git.resolveRef({ fs: eng.fs, gitdir: gitdirOf(shardWs), ref: BRANCH }).catch(() => null);
1229
+ const head = kgit.resolveRef(eng, gitdirOf(shardWs), BRANCH);
1115
1230
  return {
1116
1231
  verdict: allMatch ? "verified" : "mismatch",
1117
1232
  head,
@@ -1131,34 +1246,22 @@ export async function mountWorkspace(eng, ws, ledger) {
1131
1246
  if (eng.mounted.has(ws)) return eng.mounted.get(ws);
1132
1247
  stageWorkspaceDir(eng, ws);
1133
1248
  const gitdir = gitdirOf(ws);
1134
- let restored = false, remoteMain = null, restoreError = null, checkpoint = null;
1249
+ let restored = false, remoteMain = null, restoreError = null;
1135
1250
  try {
1136
- const info = await git.getRemoteInfo({ http, url: ledger.remote, headers: ledger.headers });
1137
- remoteMain = (info.refs && info.refs.heads && info.refs.heads.main) || null;
1251
+ // COLD MOUNT, kernel-native: write the bare skeleton (plain fs), then GET /pack the whole `main` history
1252
+ // via raw smart-HTTP and let the kernel's gix apply_pack ingest it (kept PACKED) zero isomorphic-git.
1253
+ // NO CHECKPOINT. A derived projection snapshot is NEVER trusted — it can corrupt and wedge the holon (it
1254
+ // did), which violates the law: truth is the replayable ledger, nothing derived is load-bearing. The
1255
+ // resident holon folds `main` ONCE on its first read and stays WARM in RAM thereafter — no cold-fold in
1256
+ // the hot path because the bailiff keeps it resident.
1257
+ await custodySkeleton(eng, gitdir);
1258
+ remoteMain = await custodyFetch(eng, ws, ledger, { remoteRef: "refs/heads/main" });
1138
1259
  if (remoteMain) {
1139
- await git.init({ fs: eng.fs, gitdir, bare: true, defaultBranch: "main" });
1140
- await git.addRemote({ fs: eng.fs, gitdir, remote: "origin", url: ledger.remote, force: true });
1141
- const ckpt = await discoverCheckpointFromCustody(ledger).catch(() => null);
1142
- let shallowOk = false;
1143
- if (ckpt?.cursor && ckpt.frontier) {
1144
- const steps = [32, 96, 256, 1024];
1145
- for (let i = 0; i < steps.length; i++) {
1146
- const depth = steps[i], relative = i > 0;
1147
- await git.fetch({ fs: eng.fs, http, gitdir, remote: "origin", ref: "main", singleBranch: true, depth, relative, headers: ledger.headers });
1148
- if (await git.readCommit({ fs: eng.fs, gitdir, oid: ckpt.cursor }).then(() => true, () => false)) { shallowOk = true; break; }
1149
- }
1150
- }
1151
- if (!shallowOk) await git.fetch({ fs: eng.fs, http, gitdir, remote: "origin", ref: "main", singleBranch: true, headers: ledger.headers });
1152
- await git.writeRef({ fs: eng.fs, gitdir, ref: "refs/heads/main", value: remoteMain, force: true });
1153
- await git.writeRef({ fs: eng.fs, gitdir, ref: "HEAD", value: "ref: refs/heads/main", force: true, symbolic: true });
1154
- if (shallowOk && ckpt?.blob) {
1155
- const r = importCheckpoint(eng, ws, ckpt.blob, ckpt.frontier);
1156
- checkpoint = { restored: !!(r && r.restored), frontierSeeded: !!(r && r.frontierSeeded), cursor: r && r.cursor ? r.cursor : ckpt.cursor, ref: ckpt.ref };
1157
- }
1260
+ kgit.writeRef(eng, gitdir, "HEAD", "ref: refs/heads/main");
1158
1261
  restored = true;
1159
1262
  }
1160
1263
  } catch (e) { restoreError = String((e && e.stack) || e).split("\n").slice(0, 5).join(" | "); }
1161
- const m = { restored, remoteMain, restoreError, checkpoint };
1264
+ const m = { restored, remoteMain, restoreError };
1162
1265
  eng.mounted.set(ws, m);
1163
1266
  return m;
1164
1267
  }
@@ -1169,9 +1272,9 @@ function assertAuthored(res, what) {
1169
1272
  }
1170
1273
 
1171
1274
  /**
1172
- * Mount a host-ephemeral workspace. It is still a normal WASI GitHolon under
1173
- * `/work/ws/<name>`; the host behavior is what differs: no custody restore, no
1174
- * Artifacts push, and the whole chain dies with the resident host.
1275
+ * Mount a adapter-ephemeral workspace. It is still a normal WASI GitHolon under
1276
+ * `/work/ws/<name>`; the adapter behavior is what differs: no custody restore, no
1277
+ * Artifacts push, and the whole chain dies with the resident adapter.
1175
1278
  */
1176
1279
  export async function mountEphemeralWorkspace(eng, ws, { domainHash = "", packageUsda = "" } = {}) {
1177
1280
  let m = eng.mounted.get(ws);
@@ -1195,13 +1298,13 @@ export async function mountEphemeralWorkspace(eng, ws, { domainHash = "", packag
1195
1298
  return m;
1196
1299
  }
1197
1300
 
1198
- /** Author into a host-ephemeral workspace and leave the resulting head in memory only. */
1301
+ /** Author into a adapter-ephemeral workspace and leave the resulting head in memory only. */
1199
1302
  export async function authorEphemeral(eng, ws, mountOpts, domain, directiveId, payload, lawHash, opts = {}) {
1200
1303
  await mountEphemeralWorkspace(eng, ws, mountOpts);
1201
1304
  return author(eng, ws, domain, directiveId, payload, lawHash, { deferProjection: false, ...opts });
1202
1305
  }
1203
1306
 
1204
- /** Query a host-ephemeral workspace, mounting/installing its package first if needed. */
1307
+ /** Query a adapter-ephemeral workspace, mounting/installing its package first if needed. */
1205
1308
  export async function queryEphemeral(eng, ws, mountOpts, queryId, paramsJson, principal = "", keys = []) {
1206
1309
  await mountEphemeralWorkspace(eng, ws, mountOpts);
1207
1310
  return query(eng, ws, queryId, paramsJson, principal, keys);
@@ -1220,18 +1323,44 @@ function writeWork(eng, name, bytes) { eng.preopen.dir.contents.set(name, new Fi
1220
1323
  /** THE CURRENT LAW PER DOMAIN KEY — the holon's OWN resolution (`refresh_key_map`,
1221
1324
  * the map the verify walk and every dispatch use: latest Active install per key
1222
1325
  * by install stamp). Returns `{ "<domainKey>": "<currentHash>", … }`. status uses
1223
- * THIS instead of re-deriving recency host-side from deploy order. */
1326
+ * THIS instead of re-deriving recency adapter-side from deploy order. */
1224
1327
  export function currentLaw(eng, ws) {
1225
- const out = JSON.parse(call(eng.ex, "current_law", { repoArg: repoArgOf(ws), branch: BRANCH }, eng.STDERR));
1328
+ const out = JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "currentLaw" }), branch: BRANCH }, eng.STDERR));
1226
1329
  return out.keyMap || {};
1227
1330
  }
1228
1331
 
1332
+ /** THE CAPABILITY DECLARATION READ (Full-A — capability_marketplace.md §4): the holon
1333
+ * surfaces every CURRENTLY-INSTALLED domain's `nomosCapabilities` law key (the impure-
1334
+ * capability quartets sealed into the law). The deploy lane relays these as a "declared —
1335
+ * bind it or obligations wait" notice WITHOUT reaching into root law-state. Returns
1336
+ * `[{capability, domain, aggregateId, obligation, complete, fail, block, deadLetter, tasksQueryId}]`. */
1337
+ export function declaredCapabilities(eng, ws) {
1338
+ const out = JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "declaredCapabilities" }), branch: BRANCH }, eng.STDERR));
1339
+ return out.capabilities || [];
1340
+ }
1341
+
1342
+ /** THE REACTOR SCAN (the kernel's existing "orders to emit"): every impure-capability
1343
+ * obligation at status:'requested', as `[{taskType, requestId, scheduledAt, deadlineAt, params}]`.
1344
+ * The bailiff outbox loop reads this + joins each taskType to its declared endpoint. */
1345
+ export function reactorScan(eng, ws) {
1346
+ // THE OUTBOX VERB (one of the seven): the holon's obligations-to-emit. The adapter reads its emit-queue
1347
+ // through `outbox`, never the pre-doctrine `reactor_scan` (which survives behind legacy-adapter-api for tools).
1348
+ return JSON.parse(call(eng.ex, "outbox", { repoArg: repoArgOf(ws), branch: BRANCH }, eng.STDERR));
1349
+ }
1350
+
1229
1351
  /** THE EVOLVE DRY-RUN (managed_rollout.md slice 1 — "would this upgrade admit?"):
1230
1352
  * the evolve gate's pure judgment over the mounted workspace's folded state and a
1231
1353
  * CANDIDATE package, without installing. Returns {admits} or {admits:false,
1232
1354
  * domain, detail} — byte-identical to the refusal the real install would meet. */
1233
1355
  export function evolveDryRun(eng, ws, usda, domainHash, dispositions = null) {
1234
- return JSON.parse(call(eng.ex, "evolve_dryrun", { repoArg: repoArgOf(ws), branch: BRANCH, bundle: usda, hash: domainHash, ...(dispositions ? { dispositions } : {}) }, eng.STDERR));
1356
+ // "WOULD THIS UPGRADE ADMIT?" is `offer` with the commit withheld (the offer-kernel dry-run): the SAME
1357
+ // `author(nomos/installDomain, <candidate package + dispositions>)` a real deploy runs, with dryRun:true.
1358
+ // The candidate rides the payload (packageUsda) exactly as a live install carries it — no inline-queryBytes
1359
+ // bundle, no separate verb. The full admission gate runs (incl. the #58 evolve gate, which reads the bundle
1360
+ // from the intent's own events); the commit never happens. The verdict normalizes to the {admits} shape the
1361
+ // evolve-dryrun callers consume — `Ok` ⇒ admits; a refusal carries the gate's own reason (the evolve detail).
1362
+ const r = author(eng, ws, "nomos", "installDomain", installPayload(domainHash, usda, eng.installedBy, dispositions), eng.hashes.nomos, { dryRun: true, deferProjection: false });
1363
+ return r.ok ? { admits: true } : { admits: false, detail: r.error };
1235
1364
  }
1236
1365
 
1237
1366
  /** THE FRONT-DOOR MINT (engine plane): reserve a kernel-minted typed id
@@ -1255,26 +1384,39 @@ export function author(eng, ws, domain, directiveId, payload, controllerHash, op
1255
1384
  // THE DEFERRED-PROJECTION ACK (#47, sharding slice 7): the read-projection
1256
1385
  // catch-up (incl. the derive/combine materialize) moves OFF the author ack path
1257
1386
  // by default — every read op self-heals to head, and the post-ack warm lane
1258
- // (`warmEngine`, which every host already calls) flushes it eagerly. Pass
1387
+ // (`warmEngine`, which every adapter already calls) flushes it eagerly. Pass
1259
1388
  // `opts.deferProjection: false` to keep the pre-#47 synchronous refresh.
1260
1389
  const defer = opts.deferProjection !== false;
1261
1390
  // authorSecret (warrant-flip): the author's client-held ed25519 device secret (b64), injected for THIS
1262
1391
  // local author ONLY so the wasm author door mints the signed-author attestation + self-verifies. Absent
1263
1392
  // on a legacy/unwarranted author (the door's legacy fast path); a warranted workspace REQUIRES it.
1264
- 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));
1393
+ // THE ONE EFFECTFUL VERB: authoring is `offer` of a PAYLOAD intent (a `directiveId` present the holon
1394
+ // plans it, drawing fresh captured ports from the envelope). The structured verdict normalizes back to the
1395
+ // {ok, head, intentOut} shape every caller consumes — `author` is no longer a adapter verb (it dissolved into
1396
+ // offer; the wasm `author` arm survives for the bench/CLI behind legacy-adapter-api until P8).
1397
+ // DRY-RUN (offer-kernel): `offer` with the commit withheld — run the plan + full gate, get the verdict,
1398
+ // commit nothing. This is the home of the old `evolve_dryrun` verb (see `evolveDryRun`). Never defers
1399
+ // projection (no commit to flush).
1400
+ 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));
1401
+ const res = v.outcome === "admitted" ? { ok: true, head: v.head, intentOut: v.intentOut }
1402
+ : v.outcome === "refused" ? { ok: false, error: v.verdict?.reason ?? v.error }
1403
+ : v; // defensive: a non-verdict shape passes through unchanged
1265
1404
  if (defer && res.ok) (eng.pendingProjection ??= new Set()).add(ws);
1266
1405
  return res;
1267
1406
  }
1268
1407
 
1269
- /** Catch one workspace's read projection up to head NOW (the deferred-ack flush). */
1408
+ /** Catch one workspace's read projection up to head NOW (the deferred-ack flush). P3: rides the
1409
+ * `query` verb (op:"refresh") — a non-effectful maintenance read, not a top-level adapter verb. */
1270
1410
  export function refreshProjection(eng, ws) {
1271
- return JSON.parse(call(eng.ex, "refresh_projection", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH }, eng.STDERR));
1411
+ return JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "refresh" }), branch: BRANCH }, eng.STDERR));
1272
1412
  }
1273
1413
 
1274
1414
  /** Flush every deferred projection refresh (rides the post-ack warm lane). */
1275
1415
  export function flushDeferredProjections(eng) {
1276
1416
  for (const ws of eng.pendingProjection ?? []) {
1417
+ const _t = Date.now();
1277
1418
  try { refreshProjection(eng, ws); } catch {}
1419
+ log("flush-projection-ms", { ws, ms: Date.now() - _t });
1278
1420
  }
1279
1421
  if (eng.pendingProjection) eng.pendingProjection.clear();
1280
1422
  }
@@ -1285,24 +1427,24 @@ export function flushDeferredProjections(eng) {
1285
1427
  // the DO under load). The checkpoint persists the folded projection (cursor + every table) so a
1286
1428
  // cold mount RESTORES it and the first read folds only tip−cursor (O(delta)).
1287
1429
 
1288
- /** Export `ws`'s projection-at-head to a base64 blob the HOST persists. Returns
1430
+ /** Export `ws`'s projection-at-head to a base64 blob the ADAPTER persists. Returns
1289
1431
  * `{cursor, blob, bytes}` (or `{empty:true}` for an unborn branch). */
1290
1432
  export function exportCheckpoint(eng, ws) {
1291
1433
  return JSON.parse(call(eng.ex, "checkpoint_export", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH }, eng.STDERR));
1292
1434
  }
1293
1435
 
1294
1436
  /** Restore a checkpoint blob into the pool BEFORE the first read on a cold mount (no-op if the pool
1295
- * is already warm). The HOST restores ONLY a blob whose stored lawHash matches the workspace's
1437
+ * is already warm). The ADAPTER restores ONLY a blob whose stored lawHash matches the workspace's
1296
1438
  * CURRENT law (schema parity); a mismatched/absent blob costs at most a cold fold, never a wrong read. */
1297
1439
  export function importCheckpoint(eng, ws, blobB64, frontierB64) {
1298
1440
  return JSON.parse(call(eng.ex, "checkpoint_import", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH, blob: blobB64, ...(frontierB64 ? { frontier: frontierB64 } : {}) }, eng.STDERR));
1299
1441
  }
1300
1442
 
1301
- // ── CHECKPOINT IN CUSTODY (the host-agnostic cold-mount; compute placement is beneath the contract) ──
1443
+ // ── CHECKPOINT IN CUSTODY (the adapter-agnostic cold-mount; compute placement is beneath the contract) ──
1302
1444
  // The folded projection is a fact ABOUT the chain — so carry it IN custody (a git ref), not in some
1303
- // host's private storage the NEXT host can't reach. The ephemeral heavy container has no ctx.storage;
1445
+ // adapter's private storage the NEXT adapter can't reach. The ephemeral heavy container has no ctx.storage;
1304
1446
  // a DO checkpoint is invisible to it; so root (heavy) re-folds O(chain) on every cold start. Put the
1305
- // checkpoint where every peer already looks — the ledger — and ANY host restores it O(delta): the git
1447
+ // checkpoint where every peer already looks — the ledger — and ANY adapter restores it O(delta): the git
1306
1448
  // carries its own folded state. Keyed by the CURRENT law hash (schema parity): a law upgrade orphans
1307
1449
  // the old checkpoint (the next reader cold-folds once, re-publishes under the new hash). Best-effort &
1308
1450
  // post-ack — a missing/stale checkpoint costs at most one cold fold, never a wrong read; main is truth.
@@ -1310,7 +1452,7 @@ const CKPT_REF = (lawHash) => `refs/meta/checkpoint/${lawHash}`;
1310
1452
  const CKPT_AUTHOR = { name: "nomos", email: "nomos@holon", timestamp: 0, timezoneOffset: 0 };
1311
1453
 
1312
1454
  // A serialized SQLite projection is mostly FREE PAGES (the fold DELETEs+reinserts per commit) — it
1313
- // gzips ~30×. We compress the b64 export host-side before it enters custody: the ledger meters pushed
1455
+ // gzips ~30×. We compress the b64 export adapter-side before it enters custody: the ledger meters pushed
1314
1456
  // bytes against the workspace quota, and a cold peer fetches less. Runtime-neutral (CompressionStream
1315
1457
  // is in Node 18+ and workerd). The `meta` blob records the codec so a future format stays readable.
1316
1458
  async function streamAll(stream) {
@@ -1329,36 +1471,6 @@ async function gunzip(bytes) {
1329
1471
  return streamAll(new Response(bytes).body.pipeThrough(ds));
1330
1472
  }
1331
1473
 
1332
- async function discoverCheckpointFromCustody(ledger) {
1333
- const freshHeaders = () => ({ ...(ledger.headers || {}) });
1334
- const refs = ((await git.listServerRefs({ http, url: ledger.remote, headers: freshHeaders(), prefix: "refs/meta/checkpoint/", protocolVersion: 2 })) || [])
1335
- .filter((r) => r.ref && r.ref.startsWith("refs/meta/checkpoint/") && r.oid);
1336
- if (!refs.length) return null;
1337
- const root = new Map();
1338
- root.set("ckpt", new Directory(new Map()));
1339
- const preopen = new PreopenDirectory("/work", root);
1340
- const fs = makeGitFs(preopen, "/work", { File, Directory });
1341
- const gitdir = "/work/ckpt/nomos.git";
1342
- await git.init({ fs, gitdir, bare: true, defaultBranch: BRANCH });
1343
- await git.addRemote({ fs, gitdir, remote: "origin", url: ledger.remote, force: true });
1344
- const readFile = async (oid, filepath) => { try { return (await git.readBlob({ fs, gitdir, oid, filepath })).blob; } catch { return null; } };
1345
- for (let i = refs.length - 1; i >= 0 && i >= refs.length - 3; i--) {
1346
- const { ref, oid } = refs[i];
1347
- try {
1348
- await git.fetch({ fs, http, gitdir, remote: "origin", ref, singleBranch: true, depth: 1, headers: freshHeaders() });
1349
- const gz = await readFile(oid, "checkpoint");
1350
- if (!gz) continue;
1351
- let meta = {}; try { const mb = await readFile(oid, "meta"); if (mb) meta = JSON.parse(dec.decode(mb)); } catch {}
1352
- const codec = meta.codec ?? "gzip";
1353
- const blob = dec.decode(codec === "gzip" ? await gunzip(gz) : gz);
1354
- const fgz = await readFile(oid, "frontier");
1355
- const frontier = fgz ? dec.decode(codec === "gzip" ? await gunzip(fgz) : fgz) : null;
1356
- return { ref, oid, cursor: meta.cursor || null, blob, frontier };
1357
- } catch { /* try an older checkpoint */ }
1358
- }
1359
- return null;
1360
- }
1361
-
1362
1474
  /** Wrap an exported checkpoint into git objects under `refs/meta/checkpoint/<lawHash>` (LOCAL only —
1363
1475
  * no network). The ref points at a parentless commit whose tree carries `checkpoint` (the b64 blob)
1364
1476
  * and `meta` ({cursor,bytes,lawHash}). Returns {ref, commitOid}. Separated from the push so the
@@ -1366,26 +1478,21 @@ async function discoverCheckpointFromCustody(ledger) {
1366
1478
  export async function writeCheckpointObjects(eng, ws, ex, lawHash) {
1367
1479
  const gitdir = gitdirOf(ws);
1368
1480
  const gz = await gzip(enc.encode(ex.blob));
1369
- const blobOid = await git.writeBlob({ fs: eng.fs, gitdir, blob: gz });
1370
1481
  // THE FOLD FRONTIER (the gate's Workspace state @cursor) rides ALONGSIDE the projection so a cold mount
1371
1482
  // can AUTHOR on a shallow clone (the projection alone is a read-collapsed view the gate can't fold from).
1372
1483
  // Era-safe: pre-this-era exports carry no `ex.frontier`, so the `frontier` file is simply absent and a
1373
1484
  // reader falls back to a full clone (writes need full history). Gzipped like the projection blob.
1374
- const tree = [
1375
- { mode: "100644", path: "checkpoint", oid: blobOid, type: "blob" },
1376
- ];
1485
+ const files = [{ path: "checkpoint", bytes: gz }];
1377
1486
  let fgzLen = 0;
1378
1487
  if (ex.frontier) {
1379
1488
  const fgz = await gzip(enc.encode(ex.frontier));
1380
1489
  fgzLen = fgz.length;
1381
- tree.push({ mode: "100644", path: "frontier", oid: await git.writeBlob({ fs: eng.fs, gitdir, blob: fgz }), type: "blob" });
1490
+ files.push({ path: "frontier", bytes: fgz });
1382
1491
  }
1383
- 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, frontierStored: fgzLen })) });
1384
- tree.push({ mode: "100644", path: "meta", oid: metaOid, type: "blob" });
1385
- const tree_ = await git.writeTree({ fs: eng.fs, gitdir, tree });
1386
- const commitOid = await git.writeCommit({ fs: eng.fs, gitdir, commit: { tree: tree_, parent: [], author: CKPT_AUTHOR, committer: CKPT_AUTHOR, message: `checkpoint ${ws} @${String(ex.cursor).slice(0, 16)}` } });
1492
+ 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 })) });
1387
1493
  const ref = CKPT_REF(lawHash);
1388
- await git.writeRef({ fs: eng.fs, gitdir, ref, value: commitOid, force: true });
1494
+ // ONE kernel call: blobs→tree→parentless commit→force ref. Fixed timeSecs deterministic (idempotent).
1495
+ const commitOid = kgit.writeMetaCommit(eng, gitdir, { parents: [], files, message: `checkpoint ${ws} @${String(ex.cursor).slice(0, 16)}`, timeSecs: 0, ref });
1389
1496
  return { ref, commitOid };
1390
1497
  }
1391
1498
 
@@ -1395,17 +1502,17 @@ export async function writeCheckpointObjects(eng, ws, ex, lawHash) {
1395
1502
  * pre-this-era checkpoint (no `frontier` file). */
1396
1503
  export async function readCheckpointBlob(eng, ws, commitOid) {
1397
1504
  const gitdir = gitdirOf(ws);
1398
- const { blob } = await git.readBlob({ fs: eng.fs, gitdir, oid: commitOid, filepath: "checkpoint" });
1505
+ const blob = kgit.readBlob(eng, gitdir, commitOid, "checkpoint");
1399
1506
  let codec = "gzip";
1400
- try { codec = JSON.parse(dec.decode((await git.readBlob({ fs: eng.fs, gitdir, oid: commitOid, filepath: "meta" })).blob)).codec ?? "gzip"; } catch {}
1507
+ try { codec = JSON.parse(dec.decode(kgit.readBlob(eng, gitdir, commitOid, "meta"))).codec ?? "gzip"; } catch {}
1401
1508
  const dezip = async (b) => dec.decode(codec === "gzip" ? await gunzip(b) : b);
1402
1509
  let frontier = null;
1403
- try { frontier = await dezip((await git.readBlob({ fs: eng.fs, gitdir, oid: commitOid, filepath: "frontier" })).blob); } catch { /* pre-this-era: no frontier */ }
1510
+ try { const f = kgit.readBlob(eng, gitdir, commitOid, "frontier"); if (f) frontier = await dezip(f); } catch { /* pre-this-era: no frontier */ }
1404
1511
  return { blob: await dezip(blob), frontier };
1405
1512
  }
1406
1513
 
1407
1514
  /** The checkpoint's schema-parity key: a hash of the workspace's CURRENT law map (every installed
1408
- * domain). The HOST passes nothing — the engine derives it from the chain (bailiff). A law upgrade
1515
+ * domain). The ADAPTER passes nothing — the engine derives it from the chain (bailiff). A law upgrade
1409
1516
  * changes the map → a new key → the old checkpoint is orphaned (the next reader cold-folds once and
1410
1517
  * re-publishes). An explicit `lawHash` (the DO's existing govHash callers, tests) overrides. */
1411
1518
  export async function lawParity(eng, ws, lawHash) {
@@ -1423,7 +1530,7 @@ export async function pushCheckpoint(eng, ws, ledger, lawHash) {
1423
1530
  lawHash = await lawParity(eng, ws, lawHash);
1424
1531
  if (!lawHash) return { pushed: false, reason: "no-law" };
1425
1532
  const gitdir = gitdirOf(ws);
1426
- const head = await git.resolveRef({ fs: eng.fs, gitdir, ref: "refs/heads/main" }).catch(() => null);
1533
+ const head = kgit.resolveRef(eng, gitdir, "refs/heads/main");
1427
1534
  eng._ckptPushed ??= new Map();
1428
1535
  if (head && eng._ckptPushed.get(ws) === `${lawHash}:${head}`) {
1429
1536
  return { pushed: false, reason: "unchanged" };
@@ -1433,12 +1540,66 @@ export async function pushCheckpoint(eng, ws, ledger, lawHash) {
1433
1540
  const tag = `${lawHash}:${ex.cursor}`;
1434
1541
  if (eng._ckptPushed.get(ws) === tag) return { pushed: false, reason: "unchanged" };
1435
1542
  const { ref } = await writeCheckpointObjects(eng, ws, ex, lawHash);
1436
- await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref, remoteRef: ref, force: true, headers: ledger.headers }));
1543
+ await custodyPush(eng, ws, ledger, { remoteRef: ref }); // new checkpoint commit → ships its objects
1437
1544
  eng._ckptPushed.set(ws, tag);
1438
1545
  return { pushed: true, cursor: ex.cursor, bytes: ex.bytes ?? 0 };
1439
1546
  } catch (e) { return { pushed: false, reason: String((e && e.message) || e).slice(0, 160) }; }
1440
1547
  }
1441
1548
 
1549
+ // ── WRITE-BEHIND CHECKPOINT (the cold-fold-on-remount fix) ─────────────────────────────────────────
1550
+ // THE BUG this kills: with write-behind custody on, we mirrored only the LEDGER (the commits), never the
1551
+ // CHECKPOINT (the folded projection). So a re-mounted/evicted holon found no checkpoint and RE-FOLDED the
1552
+ // whole chain on its first admit — O(chain), ~5.7ms/intent, ~9s on a 1635-intent ledger. "Warm" was a lie:
1553
+ // mount restored the git bytes but not the folded truth. These two halves let the write-behind lane persist
1554
+ // the checkpoint the SAME way it persists the ledger — export under the wasm lock (brief, O(delta) on a warm
1555
+ // holon), push off the lock — so a cold mount restores O(1) and admits one intent in O(delta). Cold ≈ warm.
1556
+
1557
+ /** STAGE a checkpoint LOCALLY (wasm export + git object write). The caller MUST hold the wasm serialize
1558
+ * lock (exportCheckpoint re-enters the engine). Returns {ref, tag, cursor, bytes} ready to push, or null
1559
+ * when there is nothing to persist (no law / empty / unchanged since the last push). */
1560
+ export async function stageCheckpoint(eng, ws, lawHash) {
1561
+ try {
1562
+ lawHash = await lawParity(eng, ws, lawHash);
1563
+ if (!lawHash) return null;
1564
+ eng._ckptPushed ??= new Map();
1565
+ const ex = exportCheckpoint(eng, ws);
1566
+ if (!ex || ex.empty || !ex.ok || !ex.blob) return null;
1567
+ const tag = `${lawHash}:${ex.cursor}`;
1568
+ if (eng._ckptPushed.get(ws) === tag) return null; // already mirrored this cursor
1569
+ const { ref } = await writeCheckpointObjects(eng, ws, ex, lawHash);
1570
+ return { ref, tag, cursor: ex.cursor, bytes: ex.bytes ?? 0 };
1571
+ } catch { return null; }
1572
+ }
1573
+
1574
+ /** PUSH a staged checkpoint to custody. Network only — the caller must NOT hold the wasm lock (the staged
1575
+ * git objects are immutable once written, so the push is race-free against concurrent admits). */
1576
+ export async function pushStagedCheckpoint(eng, ws, ledger, staged) {
1577
+ if (!staged || !staged.ref) return { pushed: false };
1578
+ await custodyPush(eng, ws, ledger, { remoteRef: staged.ref });
1579
+ (eng._ckptPushed ??= new Map()).set(ws, staged.tag);
1580
+ return { pushed: true, cursor: staged.cursor, bytes: staged.bytes };
1581
+ }
1582
+
1583
+ /** RECOVERY: delete EVERY `refs/meta/checkpoint/*` on the ledger. A corrupt checkpoint wedges cold
1584
+ * mount (the restore traps before the holon can serve); deleting it makes the next mount re-fold
1585
+ * from `main` (content-addressed, intact) and write a fresh checkpoint on the next write. Pushes the
1586
+ * ref deletion to the RAW Artifacts remote with the ledger's own write token (no per-ref gate there
1587
+ * — the gate is the worker's git PROXY, not custody). Returns the deleted ref names. */
1588
+ export async function clearCheckpointRefs(ledger) {
1589
+ const headers = ledger.headers || {};
1590
+ // A ref deletion ships NO objects + needs NO local repo: the receive-pack advertisement gives each ref's
1591
+ // current oid (the deletion command's old-oid), and pushPack sends the empty pack — pure custody transport.
1592
+ const refs = await lsRefs(ledger.remote, headers, { service: "git-receive-pack", prefix: "refs/meta/checkpoint/" });
1593
+ const deleted = [];
1594
+ for (const ref of Object.keys(refs)) {
1595
+ try {
1596
+ await pushVia(ledger, () => pushPack(ledger.remote, headers, { updates: [{ ref, oldOid: refs[ref], newOid: ZERO_OID }] }));
1597
+ deleted.push(ref);
1598
+ } catch { /* best-effort cleanup */ }
1599
+ }
1600
+ return deleted;
1601
+ }
1602
+
1442
1603
  /** Restore ws's projection from custody BEFORE the first read on a cold mount: ls-remote the law's
1443
1604
  * checkpoint ref, shallow-fetch ONLY that commit (O(checkpoint), never O(chain)), import it. No-op if
1444
1605
  * absent or the law drifted. Returns {restored, cursor?, reason?}. */
@@ -1451,13 +1612,12 @@ export async function restoreCheckpointFromCustody(eng, ws, ledger, lawHash) {
1451
1612
  lawHash = await lawParity(eng, ws, lawHash);
1452
1613
  if (!lawHash) return { restored: false, reason: "no-law" };
1453
1614
  const ref = CKPT_REF(lawHash);
1454
- const freshHeaders = () => ({ ...(ledger.headers || {}) });
1455
- const serverRefs = await git.listServerRefs({ http, url: ledger.remote, headers: freshHeaders(), prefix: ref, protocolVersion: 2 });
1456
- const found = (serverRefs || []).find((r) => r.ref === ref);
1457
- if (!found || !found.oid) return { restored: false, reason: "absent" };
1458
- const gitdir = gitdirOf(ws);
1459
- await git.fetch({ fs: eng.fs, http, gitdir, url: ledger.remote, ref, singleBranch: true, depth: 1, headers: freshHeaders() });
1460
- const { blob, frontier } = await readCheckpointBlob(eng, ws, found.oid);
1615
+ const serverRefs = await custodyLsRefs(ledger, ref);
1616
+ const foundOid = serverRefs[ref];
1617
+ if (!foundOid) return { restored: false, reason: "absent" };
1618
+ // Fetch ONLY the checkpoint commit (parentless O(checkpoint), never O(chain)) into ws's repo + apply_pack.
1619
+ await custodyFetch(eng, ws, ledger, { remoteRef: ref });
1620
+ const { blob, frontier } = await readCheckpointBlob(eng, ws, foundOid);
1461
1621
  const r = importCheckpoint(eng, ws, blob, frontier);
1462
1622
  if (r && r.restored === true && r.cursor) {
1463
1623
  eng._ckptPushed ??= new Map();
@@ -1467,17 +1627,72 @@ export async function restoreCheckpointFromCustody(eng, ws, ledger, lawHash) {
1467
1627
  } catch (e) { return { restored: false, reason: String((e && e.message) || e).slice(0, 160) }; }
1468
1628
  }
1469
1629
 
1470
- // Serialize a push through the host's per-repo lane when present (the DO threads `ledger.pushLane`); the
1630
+ // Serialize a push through the adapter's per-repo lane when present (the DO threads `ledger.pushLane`); the
1471
1631
  // container has none and serializes on its own chain. The point: ONE receive-pack to a given Artifacts repo
1472
1632
  // at a time — a detached checkpoint/shape push can never race the admit's main push (concurrent receive-packs
1473
1633
  // were corrupting/500ing the custody store). All same-repo pushes below funnel through here.
1474
1634
  const pushVia = (ledger, fn) => (ledger && ledger.pushLane ? ledger.pushLane(fn) : fn());
1475
1635
 
1636
+ // ── DUMB-PIPE CUSTODY (raw smart-HTTP via custody-transport; the KERNEL builds/ingests every pack). ──
1637
+ // These replace isomorphic-git's getRemoteInfo/listServerRefs/init/addRemote/fetch/push. The adapter only
1638
+ // shuttles bytes: ls-refs to learn a tip, fetchPack→apply_pack to ingest, packDelta→pushPack to emit.
1639
+ const fullRef = (r) => (r === "HEAD" || r.startsWith("refs/") ? r : `refs/heads/${r}`);
1640
+ const applyPackInto = (eng, ws, pack) => {
1641
+ if (!pack || !pack.length) return;
1642
+ const r = JSON.parse(call(eng.ex, "apply_pack", { repoArg: repoArgOf(ws), packB64: b64Bytes(pack) }, eng.STDERR));
1643
+ if (!r || r.ok !== true) throw new Error(`apply_pack: ${r && r.error}`);
1644
+ };
1645
+ // Build the bare-repo skeleton (plain fs) so gix can open the repo BEFORE any objects land — mirrors the
1646
+ // client: gix init_bare trips wasi getcwd, so write HEAD/objects/refs directly. Idempotent.
1647
+ async function custodySkeleton(eng, gitdir) {
1648
+ const p = eng.fs.promises;
1649
+ for (const d of ["", "/objects", "/objects/pack", "/objects/info", "/refs", "/refs/heads"]) {
1650
+ try { await p.mkdir(`${gitdir}${d}`); } catch (e) { if (e && e.code !== "EEXIST") throw e; }
1651
+ }
1652
+ try { await p.writeFile(`${gitdir}/HEAD`, `ref: refs/heads/${BRANCH}\n`); } catch {}
1653
+ try { await p.writeFile(`${gitdir}/config`, "[core]\n\trepositoryformatversion = 0\n\tbare = true\n"); } catch {}
1654
+ }
1655
+ // ls-remote (upload-pack) → { fullRefName: oid }.
1656
+ const custodyLsRefs = (ledger, prefix = null) => lsRefs(ledger.remote, ledger.headers || {}, { service: "git-upload-pack", prefix });
1657
+ // Fetch `remoteRef`'s history into ws's repo (have=what we hold), apply_pack it, point `localRef` at the tip.
1658
+ // Returns the tip oid (null if the remote lacks the ref). `intoWs` lets a caller ingest into a different repo.
1659
+ async function custodyFetch(eng, ws, ledger, { remoteRef = "refs/heads/main", localRef = null, have = null } = {}) {
1660
+ const remoteRefFull = fullRef(remoteRef);
1661
+ const refs = await custodyLsRefs(ledger, remoteRefFull);
1662
+ const tip = refs[remoteRefFull] || null;
1663
+ if (!tip) return null;
1664
+ if (tip !== have) applyPackInto(eng, ws, await fetchPack(ledger.remote, ledger.headers || {}, { wants: [tip], haves: have ? [have] : [] }));
1665
+ kgit.writeRef(eng, gitdirOf(ws), fullRef(localRef || remoteRef), tip);
1666
+ return tip;
1667
+ }
1668
+ // Push a local ref to custody (advance / force / delete). The kernel builds the pack base(remoteOld)..new;
1669
+ // a delete or no-op sends the empty pack. Funnels through the per-repo push lane (serialised receive-packs).
1670
+ // `pointer`: the target oid is ALREADY in custody (a ref that points at an existing commit — shape/rename),
1671
+ // so send the EMPTY pack (a pure ref update, no objects). `newOid` overrides resolving a local ref.
1672
+ async function custodyPush(eng, ws, ledger, { localRef = null, remoteRef, del = false, pointer = false, newOid = null } = {}) {
1673
+ const headers = ledger.headers || {};
1674
+ const remoteRefFull = fullRef(remoteRef);
1675
+ const remote = await lsRefs(ledger.remote, headers, { service: "git-receive-pack", prefix: remoteRefFull });
1676
+ const oldOid = remote[remoteRefFull] || ZERO_OID;
1677
+ if (del) {
1678
+ if (oldOid === ZERO_OID) return; // already gone
1679
+ return pushVia(ledger, () => pushPack(ledger.remote, headers, { updates: [{ ref: remoteRefFull, oldOid, newOid: ZERO_OID }] }));
1680
+ }
1681
+ const tgt = newOid || kgit.resolveRef(eng, gitdirOf(ws), fullRef(localRef || remoteRef));
1682
+ if (!tgt) throw new Error(`custodyPush: local ref ${localRef || remoteRef} unresolved`);
1683
+ if (tgt === oldOid) return; // up to date
1684
+ // pointer ⇒ no objects (already in custody). Else: FF advance ⇒ delta base..new (minimal); non-ancestor
1685
+ // (force) ⇒ pack_delta returns null → full pack.
1686
+ let pack = null;
1687
+ if (!pointer) { pack = oldOid !== ZERO_OID ? packDelta(eng, ws, oldOid, tgt) : null; if (!pack) pack = packDelta(eng, ws, "", tgt); }
1688
+ return pushVia(ledger, () => pushPack(ledger.remote, headers, { updates: [{ ref: remoteRefFull, oldOid, newOid: tgt }], pack }));
1689
+ }
1690
+
1476
1691
  // DURABILITY-BEFORE-ACK: only acked once the write LANDED in the ledger; a failed push
1477
1692
  // unmounts (drops local state) so the next access re-restores from custody.
1478
1693
  export async function commitAndPush(eng, ws, ledger) {
1479
1694
  try {
1480
- await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir: gitdirOf(ws), url: ledger.remote, ref: "main", remoteRef: "main", force: false, headers: ledger.headers }));
1695
+ await custodyPush(eng, ws, ledger, { remoteRef: "refs/heads/main" });
1481
1696
  } catch (e) {
1482
1697
  unmount(eng, ws);
1483
1698
  throw new Error(`ledger push failed — write NOT durable, rolled back to ledger: ${e}`);
@@ -1498,15 +1713,15 @@ export async function pushShape(eng, ws, ledger) {
1498
1713
  const bytes = st && (st.sqliteBytes ?? st.bytes);
1499
1714
  if (bytes == null) return;
1500
1715
  const gitdir = gitdirOf(ws);
1501
- const head = await git.resolveRef({ fs: eng.fs, gitdir, ref: "refs/heads/main" }).catch(() => null);
1716
+ const head = kgit.resolveRef(eng, gitdir, "refs/heads/main");
1502
1717
  if (!head) return;
1503
1718
  const newRef = `refs/meta/shape/b${bytes}`;
1504
1719
  eng._lastShapeRef ??= new Map();
1505
1720
  if (eng._lastShapeRef.get(ws) === newRef) return;
1506
- await git.writeRef({ fs: eng.fs, gitdir, ref: newRef, value: head, force: true });
1507
- await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref: newRef, remoteRef: newRef, force: true, headers: ledger.headers }));
1721
+ kgit.writeRef(eng, gitdir, newRef, head);
1722
+ await custodyPush(eng, ws, ledger, { remoteRef: newRef, pointer: true }); // points at head (already in custody)
1508
1723
  const prev = eng._lastShapeRef.get(ws);
1509
- if (prev && prev !== newRef) { try { await pushVia(ledger, () => 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) */ } }
1724
+ if (prev && prev !== newRef) { try { await custodyPush(eng, ws, ledger, { remoteRef: prev, del: true }); } catch { /* leftover refs are harmless: the reader takes max(bytes) */ } }
1510
1725
  eng._lastShapeRef.set(ws, newRef);
1511
1726
  } catch { /* best-effort; placement self-heals */ }
1512
1727
  }
@@ -1515,30 +1730,33 @@ export async function pushShape(eng, ws, ledger) {
1515
1730
  // Returns {bytes} (the max, in case a restart left a stale ref) or null (no shape yet).
1516
1731
  export async function readShape(ledger) {
1517
1732
  try {
1518
- const refs = await git.listServerRefs({ http, url: ledger.remote, headers: { ...(ledger.headers || {}) }, prefix: "refs/meta/shape/", protocolVersion: 2 });
1733
+ const refs = await custodyLsRefs(ledger, "refs/meta/shape/");
1519
1734
  let best = null;
1520
- for (const r of refs || []) {
1521
- const m = /refs\/meta\/shape\/b(\d+)$/.exec(r.ref || "");
1735
+ for (const ref of Object.keys(refs)) {
1736
+ const m = /refs\/meta\/shape\/b(\d+)$/.exec(ref);
1522
1737
  if (m) { const bytes = +m[1]; if (!best || bytes > best.bytes) best = { bytes }; }
1523
1738
  }
1524
1739
  return best;
1525
1740
  } catch { return null; }
1526
1741
  }
1527
1742
 
1528
- // READ VISIBILITY (slice 5.1): the host STAMPS the presented `principal` onto the
1529
- // read RPC (an impure identity read — never a host DECISION); the githolon gates the
1743
+ // READ VISIBILITY (slice 5.1): the adapter STAMPS the presented `principal` onto the
1744
+ // read RPC (an impure identity read — never a adapter DECISION); the githolon gates the
1530
1745
  // read against its OWN folded role-bindings and returns `{ok:false, error:"read-
1531
1746
  // forbidden:..."}` for a private type the principal may not read. Absent/"" principal
1532
1747
  // is the pre-5.1 wire — public reads are byte-identical.
1533
1748
  // `keys` (slice 6) = the reader's injected scope keys [{scope, epoch, key(b64)}], resolved
1534
- // host-side (X25519 unwrap). Threaded into the read RPC; the wasm AEAD-opens envelope fields.
1535
- 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));
1536
- 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));
1749
+ // adapter-side (X25519 unwrap). Threaded into the read RPC; the wasm AEAD-opens envelope fields.
1750
+ // EVERY adapter read rides the opaque `query` verb (offer-kernel P2): the kernel's rpc_query_bytes dispatches
1751
+ // by `op` to the read handler + decides the gate; the adapter never calls a typed read RPC. (The typed arms
1752
+ // survive behind `legacy-adapter-api` until P8 deletes them; no caller reaches them after this.)
1753
+ export const qById = (eng, ws, id, principal = "", keys = []) => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "byId", aggregateId: id }), principal, keys, branch: BRANCH }, eng.STDERR));
1754
+ export const query = (eng, ws, queryId, paramsJson, principal = "", keys = []) => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ kind: "query", queryId, paramsJson }), principal, keys, branch: BRANCH }, eng.STDERR));
1537
1755
  // THE ZANZIBAR CHECK/EXPAND (governance authz spine): a pure, replay-deterministic authority probe
1538
1756
  // over the projected relations table. `check` → { ok, allowed:bool }; `expand` → { ok, subjects:[...] }.
1539
1757
  // No principal/keys: relation facts are public authority state (admin/creator/platformCreator/capability).
1540
- 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));
1541
- export const expand = (eng, ws, object, relation) => JSON.parse(call(eng.ex, "expand", { repoArg: repoArgOf(ws), workspace: ws, object, relation, branch: BRANCH }, eng.STDERR));
1758
+ export const check = (eng, ws, object, relation, subject) => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "check", object, relation, subject }), branch: BRANCH }, eng.STDERR));
1759
+ export const expand = (eng, ws, object, relation) => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "expand", object, relation }), branch: BRANCH }, eng.STDERR));
1542
1760
  // THE AUTHOR-SIDE SEAL (slice 6): encrypt a plaintext field value into the on-chain envelope
1543
1761
  // (XChaCha isn't in WebCrypto, so the client calls the wasm; the scope key is injected, never on chain).
1544
1762
  // The SLOT IDENTITY is the #58 STABLE SID (rename-safety): the caller resolves (type,field)→sid
@@ -1547,11 +1765,11 @@ export const expand = (eng, ws, object, relation) => JSON.parse(call(eng.ex, "ex
1547
1765
  export const cryptoSealField = (eng, { scopeKey, scope, epoch, aggSid, fieldSid, plaintext }) =>
1548
1766
  JSON.parse(call(eng.ex, "crypto_seal_field", { scopeKey, scope, epoch, aggSid, fieldSid, plaintext }, eng.STDERR)).envelope;
1549
1767
  // The client X25519 crypto (slice 6): keygen (device identity), wrap (committer → recipient pubkey),
1550
- // unwrap (reader's device secret → scope key). The secret stays client/host-side, never on chain.
1768
+ // unwrap (reader's device secret → scope key). The secret stays client/adapter-side, never on chain.
1551
1769
  export const cryptoKeygen = (eng) => JSON.parse(call(eng.ex, "crypto_keygen", {}, eng.STDERR));
1552
1770
 
1553
1771
  // AUTHOR DEVICE KEY (warrant-flip, identity_keys.md) — mint an ed25519 signing keypair IN the holon
1554
- // (`author_keygen`); the secret stays client-side (here: the test/host injects it back as `authorSecret`
1772
+ // (`author_keygen`); the secret stays client-side (here: the test/adapter injects it back as `authorSecret`
1555
1773
  // on the author call). Returns { ok, secret, public } (both base64). `author_sign` is the lower-level
1556
1774
  // standalone signer; the author DOOR signs internally from the injected secret, so callers use `author`.
1557
1775
  export const authorKeygen = (eng) => JSON.parse(call(eng.ex, "author_keygen", {}, eng.STDERR));
@@ -1564,39 +1782,95 @@ export const attestationSign = (eng, { secret, asserterWorkspace, object, relati
1564
1782
  export const cryptoWrapKey = (eng, { recipientPub, scopeKey }) => JSON.parse(call(eng.ex, "crypto_wrap_key", { recipientPub, scopeKey }, eng.STDERR));
1565
1783
  export const cryptoUnwrapKey = (eng, { secret, hpkeEpk, ct }) => JSON.parse(call(eng.ex, "crypto_unwrap_key", { secret, hpkeEpk, ct }, eng.STDERR)).scopeKey;
1566
1784
  // THE READ-POSTURE PROBE (slice 5.1): the githolon's own verdict on whether the
1567
- // workspace is private + whether `principal` may read ALL of it — the host relays it
1568
- // to gate the BULK reads (snapshot, git clone, count/sum) it cannot gate per-row-type.
1569
- export const readPosture = (eng, ws, principal = "") => JSON.parse(call(eng.ex, "read_posture", { repoArg: repoArgOf(ws), workspace: ws, principal, branch: BRANCH }, eng.STDERR));
1570
- export const count = (eng, ws, countId, groupKey) => JSON.parse(call(eng.ex, "count", { repoArg: repoArgOf(ws), workspace: ws, countId, groupKey, branch: BRANCH }, eng.STDERR));
1571
- export const sum = (eng, ws, sumId, groupKey) => JSON.parse(call(eng.ex, "sum", { repoArg: repoArgOf(ws), workspace: ws, sumId, groupKey, branch: BRANCH }, eng.STDERR));
1572
- /** The maintained O(1) min/max read (#47 — the slice-4 extremum hand-back made an RPC).
1573
- * `kind` = "min"|"max"; an absent group answers `value: null` (never a fabricated 0). */
1574
- export const extremum = (eng, ws, extremumId, kind, groupKey) => JSON.parse(call(eng.ex, "extremum", { repoArg: repoArgOf(ws), workspace: ws, extremumId, kind, groupKey: groupKey ?? "", branch: BRANCH }, eng.STDERR));
1785
+ // workspace is private + whether `principal` may read ALL of it — the adapter relays it
1786
+ // to gate the WHOLE-LEDGER reads (snapshot, git clone) it cannot serve partially.
1787
+ export const readPosture = (eng, ws, principal = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "readPosture" }), principal, branch: BRANCH }, eng.STDERR));
1788
+ // count/sum/extremum are GATED IN THE KERNEL (it knows each tally's `of` type + the read-visibility): the
1789
+ // adapter STAMPS the verified `principal` and relays the holon's verdict it runs NO gate of its own. They ride
1790
+ // the opaque `query` verb (P2): `op` selects the handler; extremum's own `kind` stays its min/max selector.
1791
+ export const count = (eng, ws, countId, groupKey, principal = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "count", countId, groupKey }), principal, branch: BRANCH }, eng.STDERR));
1792
+ export const sum = (eng, ws, sumId, groupKey, principal = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "sum", sumId, groupKey }), principal, branch: BRANCH }, eng.STDERR));
1793
+ /** The maintained O(1) min/max read (#47). `kind` = "min"|"max"; an absent group answers `value: null`
1794
+ * (never a fabricated 0). Kernel-gated; rides the `query` verb (`op:"extremum"`, `kind` = min/max). */
1795
+ export const extremum = (eng, ws, extremumId, kind, groupKey, principal = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "extremum", extremumId, kind, groupKey: groupKey ?? "" }), principal, branch: BRANCH }, eng.STDERR));
1796
+ // GIT OFF THE HOT PATH: the KERNEL builds the conformant delta packfile (gix-pack); the adapter is dumb
1797
+ // plumbing (frames pkt-lines + POSTs). Returns the pack bytes (Uint8Array), or null if the kernel
1798
+ // couldn't produce a delta (base not an ancestor / head==base) — the adapter then falls back to a standard
1799
+ // main-ref push (backupToCustody). NO isomorphic-git, NO negotiation: the sole writer knows its delta.
1800
+ export function packDelta(eng, ws, base, head) {
1801
+ try {
1802
+ const r = JSON.parse(call(eng.ex, "pack_delta", { repoArg: repoArgOf(ws), base, head }, eng.STDERR));
1803
+ if (!r || r.ok !== true || typeof r.packB64 !== "string") return null;
1804
+ return Uint8Array.from(atob(r.packB64), (c) => c.charCodeAt(0));
1805
+ } catch { return null; }
1806
+ }
1807
+
1808
+ // THE SERVING-LANE PACK (kernel-native clone/pull, replacing isomorphic-git fetch). `base` empty ⇒ a FULL
1809
+ // pack of all reachable from main (fresh clone); `base` = the client's known head ⇒ the incremental delta.
1810
+ // The kernel resolves main itself. Returns `{ packB64, bytes }` (base64 so it rides the container JSON op).
1811
+ export function servePack(eng, ws, base = "") {
1812
+ let r;
1813
+ try {
1814
+ r = JSON.parse(call(eng.ex, "pack_delta", { repoArg: repoArgOf(ws), workspace: ws, base, branch: BRANCH }, eng.STDERR));
1815
+ } catch (e) {
1816
+ // UP TO DATE: the client's `have` already IS main → nothing to send. A clean empty response, not an error.
1817
+ if (/nothing to pack|head == base/.test(String(e))) return { packB64: "", bytes: 0, upToDate: true };
1818
+ throw e;
1819
+ }
1820
+ if (!r || r.ok !== true || typeof r.packB64 !== "string") throw new Error("servePack: kernel produced no pack");
1821
+ return { packB64: r.packB64, bytes: r.bytes };
1822
+ }
1575
1823
  export const nomosActive = (eng, ws) => qById(eng, ws, `domain-installation:${eng.hashes.nomos}`).length > 0;
1576
1824
  // E2E crypto linkage probe (the encryption arc): run the wrap→unwrap + field seal→open
1577
1825
  // INSIDE the deployable wasm. Pure — no workspace, no chain. Returns {wrapHandoff, fieldAead}.
1578
- export const cryptoSelftest = (eng) => JSON.parse(call(eng.ex, "crypto_selftest", { repoArg: "/work/none" }, eng.STDERR));
1579
1826
 
1580
- /** Re-admit ONE carried intent (bytes) on local main the DLQ-retry primitive. */
1581
- export function applyIntentBytes(eng, ws, bytes) {
1582
- const name = `intent-in-${eng.seq++}.json`;
1827
+ // THE OFFER ADMISSION VERB (offer-kernel migration P1): admit a SEALED intent into `branch`
1828
+ // through the one gate and return the STRUCTURED VERDICT — `{ok:true, outcome:"admitted", head}`
1829
+ // or `{ok:true, outcome:"refused", offerId, verdict:{reason}}`. The adapter's admit lane migrates
1830
+ // onto this off `apply_intent` over the P1 slices; `defer` carries the batch-projection-defer.
1831
+ export function offerIntent(eng, ws, bytes, opts) {
1832
+ const name = `offer-in-${eng.seq++}.json`;
1583
1833
  writeWork(eng, name, bytes);
1584
- return JSON.parse(call(eng.ex, "apply_intent", { repoArg: repoArgOf(ws), workspace: ws, intentFile: `/work/${name}`, branch: BRANCH }, eng.STDERR));
1834
+ const deferProjection = !!(opts && opts.defer);
1835
+ const branch = (opts && opts.branch) || BRANCH;
1836
+ return JSON.parse(call(eng.ex, "offer", { repoArg: repoArgOf(ws), workspace: ws, intentFile: `/work/${name}`, branch, deferProjection }, eng.STDERR));
1837
+ }
1838
+
1839
+ /**
1840
+ * RE-ADMIT sealed bytes through the OFFER verb (offer-kernel P1.2 — the one effectful move), normalized to
1841
+ * the `{ok, head, error}` shape every adapter admit lane consumes. The adapter carries the bytes; the HOLON
1842
+ * adjudicates — `admitted` → the new head; `refused` → the gate's own verdict reason. This is how EVERY adapter
1843
+ * re-admission speaks `offer` (session admit, shard migrate, global replicate, DLQ retry); `apply_intent` is
1844
+ * retired from the adapter (the wasm arm survives behind `legacy-adapter-api` only until P8 deletes it).
1845
+ */
1846
+ export function offerAdmit(eng, ws, bytes, opts) {
1847
+ const v = offerIntent(eng, ws, bytes, opts);
1848
+ return v.outcome === "admitted"
1849
+ ? { ok: true, head: v.head }
1850
+ : { ok: false, error: v.verdict?.reason ?? v.error ?? "the offer was refused" };
1851
+ }
1852
+
1853
+ /** Re-admit ONE carried intent (bytes) on local main — through the ONE effectful verb `offer` (no
1854
+ * apply_intent back door). Normalized to the legacy {ok, head, error} shape every caller (bench, CLI
1855
+ * replay, examples, DLQ retry) consumes. `defer` rides through to offer's batch-projection-defer. */
1856
+ export function applyIntentBytes(eng, ws, bytes, opts) {
1857
+ return offerAdmit(eng, ws, bytes, opts);
1585
1858
  }
1586
1859
 
1587
1860
  // Main's recent intent ids + oids, cached by head (admission idempotence + walk boundary).
1588
1861
  async function mainIntentIds(eng, ws) {
1589
1862
  const gitdir = gitdirOf(ws);
1590
- const head = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null);
1863
+ const head = kgit.resolveRef(eng, gitdir, BRANCH);
1591
1864
  if (!head) return { head: null, ids: new Set(), oids: new Set() };
1592
1865
  const cached = eng.mainIntents.get(ws);
1593
1866
  if (cached && cached.head === head) return cached;
1594
1867
  const ids = new Set(), oids = new Set();
1595
- for (const c of await git.log({ fs: eng.fs, gitdir, ref: BRANCH, depth: 500 })) {
1868
+ // Main is a linear chain of intent commits (the container appends, never merges), so the recent intent
1869
+ // commits ARE the recent main commits — the bounded idempotence window + walk boundary (was git.log depth 500).
1870
+ for (const c of kgit.intentsAbove(eng, gitdir, head, "", 500)) {
1596
1871
  oids.add(c.oid);
1597
1872
  try {
1598
- const { blob } = await git.readBlob({ fs: eng.fs, gitdir, oid: c.oid, filepath: "intent.json" });
1599
- const id = JSON.parse(dec.decode(blob)).id;
1873
+ const id = JSON.parse(dec.decode(bytesFromB64(c.intentB64))).id;
1600
1874
  if (typeof id === "string" && id) ids.add(id);
1601
1875
  } catch {}
1602
1876
  }
@@ -1608,14 +1882,21 @@ async function mainIntentIds(eng, ws) {
1608
1882
  /**
1609
1883
  * EDGE ADMISSION (the one machinery): judge every session/<cid> ref. Structural lane
1610
1884
  * refusals (control-plane intents, undeployed domains) and domain rejections route per
1611
- * Jack's law: legitimate work DEAD-LETTERS (returned to the host to persist — the
1885
+ * Jack's law: legitimate work DEAD-LETTERS (returned to the adapter to persist — the
1612
1886
  * engine has no durable store), obvious attacks drop. Admitted intents merge to main,
1613
1887
  * durably push, the branch is consumed.
1614
1888
  */
1615
- export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFence = null, shardRouting = null, sessionRefs = null, defer = false } = {}) {
1889
+ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFence = null, shardRouting = null, defer = false } = {}) {
1616
1890
  const refuse = new Set(refuseDomains);
1617
1891
  const routes = shardRouting ? directiveRoutes(eng) : null;
1618
1892
  const gitdir = gitdirOf(ws);
1893
+ // FRESH HEADER COPY PER GIT OP. isomorphic-git's `discover` MUTATES the headers object it's given
1894
+ // (a v2 op stamps `Git-Protocol: version=2` in). Sharing one object across a v2 listServerRefs and a
1895
+ // v1 git.fetch made the fetch send `version=2`, the server answered v2, isomorphic-git parsed it as
1896
+ // v1 → undefined refs → `TypeError: ...reading 'size'` on EVERY session fetch. A fresh copy per call
1897
+ // makes that leak impossible no matter what mutated `ledger.headers` upstream. (Same pattern as the
1898
+ // checkpoint/shape scans' `freshHeaders()`.)
1899
+ const H = () => ({ ...(ledger.headers || {}) });
1619
1900
  // FLAME-GRAPH EDGE TIMINGS — coarse per-stage wall-clock the client stitches into the cross-tier trace.
1620
1901
  const _t0 = Date.now(); let _fetchMs = 0, _sealMs = 0, _intents = 0;
1621
1902
  const _spans = []; let _traceSeq = 0;
@@ -1628,29 +1909,16 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1628
1909
  };
1629
1910
  const sessionRefPrefix = "refs/heads/session/";
1630
1911
  const sessions = {};
1631
- const indexedRefs = sessionRefs && typeof sessionRefs === "object" ? sessionRefs : null;
1632
- const _scanDone = _span(indexedRefs ? "scan session index" : "scan session refs", "cscan", 1);
1633
- if (indexedRefs) {
1634
- const entries = Array.isArray(indexedRefs)
1635
- ? indexedRefs.map((r) => [String(r?.session || r?.cid || r?.client || ""), String(r?.head || r?.oid || "")])
1636
- : Object.entries(indexedRefs).map(([cid, head]) => [cid, String(head || "")]);
1637
- for (const [cid, head] of entries) {
1638
- if (/^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(cid) && /^[0-9a-f]{40}$/.test(head)) sessions[cid] = head;
1639
- }
1640
- } else {
1641
- const refs = await git.listServerRefs({
1642
- http,
1643
- url: ledger.remote,
1644
- headers: ledger.headers,
1645
- prefix: sessionRefPrefix,
1646
- protocolVersion: 2,
1647
- });
1648
- for (const r of refs || []) {
1649
- const ref = typeof r?.ref === "string" ? r.ref : "";
1650
- const oid = typeof r?.oid === "string" ? r.oid : "";
1651
- if (!ref.startsWith(sessionRefPrefix) || !oid) continue;
1652
- sessions[ref.slice(sessionRefPrefix.length)] = oid;
1653
- }
1912
+ const _scanDone = _span("scan session refs", "cscan", 1);
1913
+ // CUSTODY IS THE ONLY SOURCE. The adapter keeps NO session index there is no adapter-held head to
1914
+ // diverge from the ledger. Every admit lists the workspace's own session refs (listServerRefs)
1915
+ // and admits exactly what is durably there. (The earlier adapter "session-offer index" let a
1916
+ // client's CLAIMED head outlive the push that never durably landed; admit then chased an oid
1917
+ // custody never had NotFoundError on every tick → the client's work stranded forever. The
1918
+ // cure is not a better cache — it is no cache: the adapter holds no authority, the ledger does.)
1919
+ //
1920
+ for (const [ref, oid] of Object.entries(await custodyLsRefs(ledger, sessionRefPrefix))) {
1921
+ if (ref.startsWith(sessionRefPrefix) && oid) sessions[ref.slice(sessionRefPrefix.length)] = oid;
1654
1922
  }
1655
1923
  _scanDone({ sessions: Object.keys(sessions).length });
1656
1924
  const _scanMs = Date.now() - _t0;
@@ -1661,16 +1929,22 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1661
1929
  const consumed = []; let totalAdmitted = 0;
1662
1930
  // §5.4 (slice 4): on the COORDINATOR of a taxonomy that declares `.global(...)`
1663
1931
  // reference data, every admitted intent touching a global aggregate is surfaced
1664
- // to the HOST, which replicates the SAME sealed bytes into each shard chain.
1932
+ // to the ADAPTER, which replicates the SAME sealed bytes into each shard chain.
1665
1933
  const globalsDeclared = shardRouting && !shardRouting.selfLabel ? globalAggregates(eng) : null;
1666
1934
  // ── §5.2 SUMMARY CAPTURE (slice 3): on a SHARD (selfLabel) whose law declares
1667
1935
  // estate-scoped reads, derive each admitted intent's per-(read, group) deltas with
1668
1936
  // the shard's own projection as the oracle (tallies sandwiched around the fold —
1669
- // O(touched)); the HOST authors the coalesced `nomosPropagateSummary` Order into
1937
+ // O(touched)); the ADAPTER authors the coalesced `nomosPropagateSummary` Order into
1670
1938
  // the coordinator afterwards. The coordinator's gate re-verifies everything.
1671
1939
  const summaryCtx = shardRouting && shardRouting.selfLabel && scopedReads(eng).length > 0
1672
1940
  ? { reads: scopedReads(eng), prevs: new Map(), values: new Map(), events: [], rowsDelta: 0, rederived: new Set(), rowsByHome: new Map(), homeTags: new Map(), homeKeySet: new Set() }
1673
1941
  : null;
1942
+ // BATCH ADMIT (the flame fix): defer the per-intent projection catch-up + frontier write
1943
+ // to ONE flush after the whole cycle — UNLESS a per-intent projection read is required
1944
+ // mid-loop: §5.2 summary capture (reads pre/post tallies around each fold) or §5.4 global
1945
+ // surfacing (qById per admitted intent). Those keep the synchronous per-intent path. The
1946
+ // common case (no sharding) gets the batch: N×(SQLite catch-up + checkpoint write) → 1.
1947
+ const canDeferProjection = !summaryCtx && !(globalsDeclared && globalsDeclared.size > 0);
1674
1948
  if (summaryCtx) {
1675
1949
  // SLICE 8 — the per-home attribution table, off the shard's OWN receipts
1676
1950
  // (law-state: which homes this chain owns). A row id whose route-tag slot
@@ -1685,50 +1959,54 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1685
1959
  }
1686
1960
  } catch (e) { log("home-tag-table-error", { ws, error: String(e).slice(0, 200) }); }
1687
1961
  }
1688
- const headBefore = summaryCtx ? await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null) : null;
1962
+ const headBefore = summaryCtx ? kgit.resolveRef(eng, gitdir, BRANCH) : null;
1689
1963
  for (const [cid, head] of Object.entries(sessions)) {
1690
1964
  const ref = `refs/heads/session/${cid}`;
1691
1965
  const shortCid = cid.length > 14 ? `${cid.slice(0, 14)}...` : cid;
1692
1966
  const _sessionDone = _span(`session ${shortCid}`, "csession", 1, { client: cid });
1693
1967
  try {
1694
- await git.addRemote({ fs: eng.fs, gitdir, remote: "origin", url: ledger.remote, force: true });
1695
1968
  const _fetchDone = _span(`fetch ${shortCid}`, "cfetch", 2, { client: cid });
1696
1969
  const _f = Date.now();
1697
1970
  try {
1698
- // THIN-FETCH (the 40s→0.5s fix): a `singleBranch` fetch sets isomorphic-git's have-set to
1699
- // [the fetched ref] ONLY. The edge holds no local session ref yet ⇒ it offers ZERO haves the
1700
- // server re-ships the ENTIRE history (O(chain): measured ~10MB/40s for a 1-commit delta).
1701
- // Pre-seed the local session ref to OUR main head so the negotiation offers main as a have and the
1702
- // server sends only the delta above the common ancestor (~1KB/0.5s). main is append-only, so the
1703
- // client's fork point is always an ancestor of our head — the thin pack is always sound.
1704
- const localMain = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null);
1705
- if (localMain) { try { await git.writeRef({ fs: eng.fs, gitdir, ref, value: localMain, force: true }); } catch {} }
1706
- await git.fetch({ fs: eng.fs, http, gitdir, remote: "origin", ref, singleBranch: true, headers: ledger.headers });
1971
+ // THIN-FETCH (the 40s→0.5s fix): pass our main head as the `have` so the server sends ONLY the delta
1972
+ // above the common ancestor (the kernel's apply_pack ingests it). main is append-only, so the client's
1973
+ // fork point is always an ancestor of our head — the thin pack is sound. A failed thin fetch is an
1974
+ // optimization miss, never load-bearing: fall back to a clean full fetch so work is never stranded.
1975
+ const localMain = kgit.resolveRef(eng, gitdir, BRANCH);
1976
+ try {
1977
+ await custodyFetch(eng, ws, ledger, { remoteRef: ref, have: localMain });
1978
+ } catch (thinErr) {
1979
+ log("thin-fetch-fallback", { ws, cid, error: String(thinErr && thinErr.message || thinErr).slice(0, 160) });
1980
+ await custodyFetch(eng, ws, ledger, { remoteRef: ref, have: null });
1981
+ }
1707
1982
  } finally {
1708
1983
  _fetchMs += Date.now() - _f;
1709
1984
  _fetchDone();
1710
1985
  }
1711
1986
  const _walkDone = _span(`walk ${shortCid}`, "cwalk", 2, { client: cid });
1712
1987
  const main = await mainIntentIds(eng, ws);
1988
+ // The session's NEW intents: walk the session tip newest→oldest, stop at the first commit already on
1989
+ // main (the fork point), reverse to oldest→newest. intentsAbove returns {oid, intentB64} so the gate
1990
+ // loop reads the intent straight off the walk (no per-oid readBlob).
1991
+ const recent = kgit.intentsAbove(eng, gitdir, head, "", 100);
1713
1992
  const chain = [];
1714
- for (const c of await git.log({ fs: eng.fs, gitdir, ref: head, depth: 100 })) {
1715
- if (main.oids.has(c.oid)) break;
1716
- chain.push(c.oid);
1717
- }
1993
+ for (let i = recent.length - 1; i >= 0; i--) { if (main.oids.has(recent[i].oid)) break; chain.push(recent[i]); }
1718
1994
  chain.reverse(); // oldest → newest
1719
1995
  _walkDone({ intents: chain.length });
1720
1996
  const admitted = [], rejected = [], skipped = [];
1721
1997
  const _gateDone = _span(`gate+fold ${shortCid}`, "cgate", 2, { client: cid, intents: chain.length });
1722
- for (const oid of chain) {
1998
+ for (const c of chain) {
1999
+ const oid = c.oid;
1723
2000
  try {
1724
- const { blob } = await git.readBlob({ fs: eng.fs, gitdir, oid, filepath: "intent.json" });
2001
+ const blob = bytesFromB64(c.intentB64);
1725
2002
  let dom = null, intentId = null, dirId = null;
1726
2003
  try { const doc = JSON.parse(dec.decode(blob)); intentId = typeof doc.id === "string" ? doc.id : null; dom = doc && doc.payload && typeof doc.payload.domain === "string" ? doc.payload.domain : null; dirId = doc && doc.payload && typeof doc.payload.directiveId === "string" ? doc.payload.directiveId : null; } catch {}
1727
- // WRITE-AUTHORITY: law (control-plane) intents never ride the session lane
1728
- // nor do HOST-FENCED domains (e.g. `workspaces` on root: governance directives
1729
- // enter ONLY through the admin-gated lane, never open session pushes).
1730
- if (!dom || dom === "nomos" || dom === "bootstrap" || refuse.has(dom)) {
1731
- rejected.push({ oid: oid.slice(0, 10), error: `session lane refuses ${dom ? `'${dom}'` : "non-authored"} intents — law deploys go through POST /v1/workspaces/${ws}/domains with the workspace secret` });
2004
+ // THE GATE DECIDES (offer-kernel doctrine): no adapter domain-string filter for control-plane
2005
+ // an install/governance offer rides the SAME lane and the kernel gate refuses an author lacking
2006
+ // the declared relation (a TYPED gate refusal, not a adapter verdict). Only `!dom` (malformed) and
2007
+ // the explicit `refuse` fence (root's `workspaces` governance lane) stay structural, not authority.
2008
+ if (!dom || refuse.has(dom)) {
2009
+ rejected.push({ oid: oid.slice(0, 10), error: `${dom ? `'${dom}' is adapter-fenced on this workspace` : "non-authored intent"} — governance enters its own admin lane` });
1732
2010
  continue;
1733
2011
  }
1734
2012
  // THE RECEIPT FENCE (capability_marketplace.md slice 1.5 — the ratified
@@ -1752,10 +2030,10 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1752
2030
  if (routes && dom && dirId) {
1753
2031
  // THE SEAL PEN IS THE BAILIFF'S (slice 5): `nomosSealHome` (strikes the
1754
2032
  // migrated history out of a source's fold) and `nomosSealHandoff` (flips
1755
- // the map) are HOST-orchestrated legs of the §6 move — a session-lane
2033
+ // the map) are ADAPTER-orchestrated legs of the §6 move — a session-lane
1756
2034
  // offer of either is an attack on the move lane, refused and DROPPED.
1757
2035
  if (dirId === "nomosSealHome" || dirId === "nomosSealHandoff") {
1758
- rejected.push({ oid: oid.slice(0, 10), error: `session lane refuses '${dirId}' — the §6 handoff seals are authored by the host after migration verifies (begin a move with nomosSplitShard, or POST /v1/workspaces/<coordinator>/split)` });
2036
+ rejected.push({ oid: oid.slice(0, 10), error: `session lane refuses '${dirId}' — the §6 handoff seals are authored by the adapter after migration verifies (begin a move with nomosSplitShard, or POST /v1/workspaces/<coordinator>/split)` });
1759
2037
  continue;
1760
2038
  }
1761
2039
  const route = routes.get(`${dom} ${dirId}`);
@@ -1801,13 +2079,21 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1801
2079
  continue;
1802
2080
  }
1803
2081
  const cap = summaryCtx ? beginSummaryCapture(eng, ws, summaryCtx, blob) : null;
1804
- const res = applyIntentBytes(eng, ws, blob);
2082
+ // OFFER-KERNEL P1.2: edge re-admission runs through the `offer` verb (the doctrine's one
2083
+ // effectful move) over the SAME admit_core gate. The structured verdict is normalized
2084
+ // back to the {ok, head, error} the outcome handling below consumes; the refused/parked
2085
+ // ref-custody (replacing the JS DLQ) is the next sub-slice — for now refusals still
2086
+ // dead-letter exactly as before (the verdict reason IS the gate's refusal reason).
2087
+ const verdict = offerIntent(eng, ws, blob, { defer: canDeferProjection });
2088
+ const res = verdict.outcome === "admitted"
2089
+ ? { ok: true, head: verdict.head }
2090
+ : { ok: false, error: verdict.verdict?.reason ?? verdict.error ?? "the offer was refused" };
1805
2091
  if (res.ok) {
1806
2092
  main.ids.add(intentId); main.oids.add(res.head); main.head = res.head;
1807
2093
  admitted.push({ oid: oid.slice(0, 10), head: res.head });
1808
2094
  if (cap) finishSummaryCapture(eng, ws, summaryCtx, cap, res.head, intentId);
1809
2095
  // AN ADMITTED PLACEMENT (birth<Type> on the coordinator): surfaced to the
1810
- // HOST so it can author the assignment RECEIPT into the shard's own chain
2096
+ // ADAPTER so it can author the assignment RECEIPT into the shard's own chain
1811
2097
  // (sharding §3 — the receipt leg).
1812
2098
  if (shardRouting && dirId) {
1813
2099
  const placement = placementDirectives(eng).get(dirId);
@@ -1820,9 +2106,9 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1820
2106
  }
1821
2107
  }
1822
2108
  // AN ADMITTED SPLIT (nomosSplitShard on the coordinator, slice 5):
1823
- // surfaced to the HOST so it can run the §6 migration legs (re-admit
2109
+ // surfaced to the ADAPTER so it can run the §6 migration legs (re-admit
1824
2110
  // on the target, seal the source, close/open the subtotals, flip the
1825
- // map) — a session-authored split COMPLETES like a host-authored one.
2111
+ // map) — a session-authored split COMPLETES like a adapter-authored one.
1826
2112
  if (dirId === "nomosSplitShard" && !shardRouting.selfLabel) {
1827
2113
  let p = null;
1828
2114
  try { p = JSON.parse(dec.decode(blob)).payload?.payload ?? null; } catch {}
@@ -1832,7 +2118,7 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1832
2118
  }
1833
2119
  }
1834
2120
  // AN ADMITTED GLOBAL WRITE (§5.4, slice 4 — coordinator only): the sealed
1835
- // bytes are surfaced to the HOST, which re-admits them into every shard
2121
+ // bytes are surfaced to the ADAPTER, which re-admits them into every shard
1836
2122
  // chain (the reverse lane — replicated reference data). O(events) per
1837
2123
  // admitted intent, and only when the law declares `.global(...)`.
1838
2124
  if (globalsDeclared && globalsDeclared.size > 0) {
@@ -1880,13 +2166,26 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1880
2166
  _sessionDone();
1881
2167
  }
1882
2168
  }
2169
+ // THE DEFERRED-PROJECTION FLUSH (batch admit): the per-intent applies skipped the SQLite
2170
+ // catch-up + frontier write; flush ONCE here — refresh_projection folds every new commit in
2171
+ // a single incremental catch-up and walks the deferred heads to advance the verified frontier.
2172
+ if (canDeferProjection && totalAdmitted) {
2173
+ const _projDone = _span("projection flush (batch)", "cproject", 1, { admitted: totalAdmitted });
2174
+ try { refreshProjection(eng, ws); } catch (e) { log("admit-projection-flush-failed", { ws, error: String(e).slice(0, 200) }); }
2175
+ finally { _projDone(); }
2176
+ }
2177
+ // OFFER-KERNEL P1.2b: materialize this cycle's refusals as git-custody facts (refs/heads/refused/*),
2178
+ // pushed inline — BEFORE the main seal, which unmounts on failure (a refusal fact must not be lost to
2179
+ // an unrelated main-push failure). The holon's verdict recorded as a HOLON FACT that syncs + survives
2180
+ // eviction; the adapter's ctx.storage DLQ is retired in favour of these.
2181
+ if (deadLetters.length) { try { await materializeRefused(eng, ws, ledger, deadLetters); } catch (e) { log("refused-materialize-failed", { ws, error: String(e).slice(0, 200) }); } }
1883
2182
  // THE BATCH SEAL — one receive-pack for the whole admit cycle. A failed push unmounts (engine.unmount drops
1884
2183
  // the ws fs); we then SKIP the branch deletes so every offered intent re-admits next cycle (idempotent by
1885
2184
  // id). On success, delete the consumed session branches (best-effort cleanup; a stale branch is a harmless
1886
2185
  // re-fetch). Single-client workspaces were already one push/cycle; this collapses collaborative bursts.
1887
- // THE BAILIFF SEAM: when the HOST asked us to `defer` (the open session admit lane on a
2186
+ // THE BAILIFF SEAM: when the ADAPTER asked us to `defer` (the open session admit lane on a
1888
2187
  // non-coordinator workspace), we DO NOT push or delete consumed branches inline — the fold
1889
- // is already on resident main (truth), and the host backs it up to custody off the critical
2188
+ // is already on resident main (truth), and the adapter backs it up to custody off the critical
1890
2189
  // path (engine.backupToCustody, debounced + coalesced). This is sound because the SESSION
1891
2190
  // BRANCHES are themselves durable in custody (the durable inbox) and admit is idempotent by
1892
2191
  // intent id: a crash before backup just re-derives the SAME sealed main from the same offers.
@@ -1909,12 +2208,12 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1909
2208
  }
1910
2209
  if (_sealed && !deferDurability) {
1911
2210
  for (const { ref } of consumed) {
1912
- try { await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref: BRANCH, remoteRef: ref, delete: true, headers: ledger.headers })); } catch {}
2211
+ try { await custodyPush(eng, ws, ledger, { remoteRef: ref, del: true }); } catch {}
1913
2212
  }
1914
2213
  }
1915
- const newMain = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null);
2214
+ const newMain = kgit.resolveRef(eng, gitdir, BRANCH);
1916
2215
  log("admit", { ws, sessions: results.length, main: newMain });
1917
- // The coalesced §5.2 delta of this batch (null when no scoped read moved): the HOST
2216
+ // The coalesced §5.2 delta of this batch (null when no scoped read moved): the ADAPTER
1918
2217
  // closes it against the coordinator's recorded frontier and authors the Order.
1919
2218
  const summary = summaryCtx ? summaryOfCapture(summaryCtx, headBefore, newMain) : null;
1920
2219
  const _total = Date.now() - _t0;
@@ -1926,29 +2225,48 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1926
2225
  ...(placements.length ? { placements } : {}),
1927
2226
  ...(splits.length ? { splits } : {}),
1928
2227
  ...(globalsOut.length ? { globals: globalsOut } : {}),
1929
- // The host's backup lane consumes these: `deferred` ⇒ resident main carries totalAdmitted
2228
+ // The adapter's backup lane consumes these: `deferred` ⇒ resident main carries totalAdmitted
1930
2229
  // un-backed-up commits and `consumed` are the session branches to delete AFTER the push lands.
1931
2230
  ...(deferDurability && totalAdmitted ? { deferred: true, consumed, admitted: totalAdmitted } : {}),
1932
2231
  };
1933
2232
  }
1934
2233
 
2234
+ // WRITE-BEHIND CUSTODY MIRROR (git off the hot path). Push a CAPTURED head snapshot to custody main —
2235
+ // NOT the live `main` ref (re-resolving it would chase a moving target under concurrent folds, the bug
2236
+ // that stormed the old debounced backup). The container is the SOLE writer of main, so custody is a
2237
+ // pure FF mirror: no pull-back, no reconciliation. Read-only on eng.fs except a transient backup ref
2238
+ // (a different key from anything a fold touches; the head's objects are immutable). NEVER unmounts on
2239
+ // failure — the resident holon stays truth and the client can re-offer (worst case: the mirror lags).
2240
+ export async function backupHead(eng, ws, ledger, head) {
2241
+ if (!eng.mounted.has(ws)) return { ok: false, error: "not mounted" };
2242
+ if (!head) return { ok: false, error: "no head" };
2243
+ try {
2244
+ // Push EXACTLY the captured `head` (not live main, which moves under concurrent folds) to custody main —
2245
+ // the kernel builds the delta remoteOld..head; no local tmp ref needed (custodyPush names the oid).
2246
+ await custodyPush(eng, ws, ledger, { remoteRef: "refs/heads/main", newOid: head });
2247
+ return { ok: true, head };
2248
+ } catch (e) {
2249
+ return { ok: false, error: String((e && e.message) || e).slice(0, 200), head };
2250
+ }
2251
+ }
2252
+
1935
2253
  // THE BAILIFF'S BACKUP — push resident main to custody when quiet, NEVER on a write's critical
1936
2254
  // path and NEVER destructive. Unlike commitAndPush (durability-before-ack: unmounts on failure),
1937
- // a failed backup leaves the resident holon mounted and the session branches intact, so the host
2255
+ // a failed backup leaves the resident holon mounted and the session branches intact, so the adapter
1938
2256
  // just retries on the next quiet tick. Append-only main means an off-lock push of an older head
1939
2257
  // concurrent with a fresh fold is always a sound fast-forward subset (the newer head backs up next).
1940
2258
  export async function backupToCustody(eng, ws, ledger, { consumed = [] } = {}) {
1941
2259
  if (!eng.mounted.has(ws)) return { ok: false, error: "not mounted" };
1942
2260
  const gitdir = gitdirOf(ws);
1943
2261
  let head = null;
1944
- try { head = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }); } catch { return { ok: false, error: "no local main" }; }
2262
+ head = kgit.resolveRef(eng, gitdir, BRANCH); if (!head) return { ok: false, error: "no local main" };
1945
2263
  try {
1946
- await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref: BRANCH, remoteRef: BRANCH, force: false, headers: ledger.headers }));
2264
+ await custodyPush(eng, ws, ledger, { remoteRef: "refs/heads/main" });
1947
2265
  } catch (e) {
1948
2266
  return { ok: false, error: String(e).slice(0, 200), head };
1949
2267
  }
1950
2268
  for (const { ref } of consumed) {
1951
- try { await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref: BRANCH, remoteRef: ref, delete: true, headers: ledger.headers })); } catch {}
2269
+ try { await custodyPush(eng, ws, ledger, { remoteRef: ref, del: true }); } catch {}
1952
2270
  }
1953
2271
  return { ok: true, head };
1954
2272
  }
@@ -1960,7 +2278,7 @@ export async function backupToCustody(eng, ws, ledger, { consumed = [] } = {}) {
1960
2278
  * genesis (contiguity → per-intent re-admission with the engine re-run; byte-preserving,
1961
2279
  * read-only — see wasm_git_holon.rs `verify_chain`). Returns the wasm's verdict object:
1962
2280
  * `{valid:true, head, intents, plansRerun, controllerHash, installedDomains}` or
1963
- * `{valid:false, check, error, atIndex?, atCommit?}`. The HOST decides what a refusal
2281
+ * `{valid:false, check, error, atIndex?, atCommit?}`. The ADAPTER decides what a refusal
1964
2282
  * does (archive + refuse to serve); this op never mutates the ledger.
1965
2283
  */
1966
2284
  export async function verifyChain(eng, ws, ledger) {
@@ -1970,10 +2288,12 @@ export async function verifyChain(eng, ws, ledger) {
1970
2288
  return { valid: false, check: "mount", error: m.restoreError || "no refs/heads/main on the ledger — nothing to verify (push refs/heads/main to deliver a chain first)" };
1971
2289
  }
1972
2290
  try {
1973
- return JSON.parse(call(eng.ex, "verify_chain", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH }, eng.STDERR));
2291
+ // P4: chain verification rides the `query` verb (op:"verifyChain") a read-only verdict, not a
2292
+ // top-level adapter verb. The adapter still relays the verdict (archive-on-refuse, bless-on-valid) = custody.
2293
+ return JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "verifyChain" }), branch: BRANCH }, eng.STDERR));
1974
2294
  } finally {
1975
2295
  // The verified (or refused) chain must never linger as a servable mount: serving
1976
- // re-restores from custody AFTER the host blesses the verdict.
2296
+ // re-restores from custody AFTER the adapter blesses the verdict.
1977
2297
  unmount(eng, ws);
1978
2298
  }
1979
2299
  }
@@ -1986,7 +2306,8 @@ export async function verifyChain(eng, ws, ledger) {
1986
2306
  * the LEDGER's bytes, never a hot mount.)
1987
2307
  */
1988
2308
  export function verifyChainLocal(eng, ws) {
1989
- return JSON.parse(call(eng.ex, "verify_chain", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH }, eng.STDERR));
2309
+ // P4: rides the `query` verb (op:"verifyChain") a read-only verdict.
2310
+ return JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "verifyChain" }), branch: BRANCH }, eng.STDERR));
1990
2311
  }
1991
2312
 
1992
2313
  /**
@@ -2002,14 +2323,169 @@ export async function archiveRefusedMain(eng, ws, ledger, label) {
2002
2323
  if (!m.restored) { unmount(eng, ws); return { archived: false, error: "no main to archive" }; }
2003
2324
  const gitdir = gitdirOf(ws);
2004
2325
  try {
2005
- await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref: BRANCH, remoteRef: `refs/heads/refused/${label}`, force: true, headers: ledger.headers }));
2006
- await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref: BRANCH, remoteRef: `refs/heads/${BRANCH}`, delete: true, headers: ledger.headers }));
2326
+ // main's objects are already in custody a pure pointer (empty pack) to refused/<label>, then drop main.
2327
+ await custodyPush(eng, ws, ledger, { localRef: "refs/heads/main", remoteRef: `refs/heads/refused/${label}`, pointer: true });
2328
+ await custodyPush(eng, ws, ledger, { remoteRef: "refs/heads/main", del: true });
2007
2329
  return { archived: true, ref: `refs/heads/refused/${label}` };
2008
2330
  } finally {
2009
2331
  unmount(eng, ws);
2010
2332
  }
2011
2333
  }
2012
2334
 
2335
+ /** A refused-offer id sanitized to a git ref path component (the offerId is the intent id). */
2336
+ const refusedRefName = (offerId) => `refs/heads/refused/${String(offerId || "unknown").replace(/[^A-Za-z0-9._-]/g, "_")}`;
2337
+
2338
+ /**
2339
+ * OFFER-KERNEL P1.2b: materialize a REFUSED offer as a HOLON FACT in git custody — a commit on
2340
+ * `refs/heads/refused/<offerId>` carrying { intent.json, verdict.json } (the holon's verdict, NOT
2341
+ * adapter mail). A quarantine branch is OUTSIDE the verified ledger (commit-closure doesn't bind it),
2342
+ * so the adapter writes it as CUSTODY — the DECISION was the holon's (`offer` → `refused`), the adapter
2343
+ * only records it. The ref SYNCS (a client pulls refs/heads/refused/* to see its dead-lettered
2344
+ * work) and survives DO eviction. Deterministic (CKPT_AUTHOR timestamp 0) ⇒ idempotent: re-refusing
2345
+ * the same intent rewrites the same commit. `dl` is the admitAll dead-letter
2346
+ * `{ intentId, oid, error, blob, domain, directiveId }`.
2347
+ */
2348
+ export async function writeRefusedRef(eng, ws, dl) {
2349
+ const gitdir = gitdirOf(ws);
2350
+ const blob = dl.blob instanceof Uint8Array ? dl.blob : enc.encode(String(dl.blob || ""));
2351
+ const verdict = enc.encode(JSON.stringify({
2352
+ offerId: dl.intentId || null, oid: dl.oid || null,
2353
+ domain: dl.domain || null, directiveId: dl.directiveId || null,
2354
+ verdict: { reason: String(dl.error || "refused") },
2355
+ }));
2356
+ const ref = refusedRefName(dl.intentId || dl.oid);
2357
+ kgit.writeMetaCommit(eng, gitdir, {
2358
+ parents: [], files: [{ path: "intent.json", bytes: blob }, { path: "verdict.json", bytes: verdict }],
2359
+ message: `refused: ${dl.domain || "?"}/${dl.directiveId || "?"} — ${String(dl.error || "").slice(0, 120)}`,
2360
+ timeSecs: 0, ref,
2361
+ });
2362
+ return ref;
2363
+ }
2364
+
2365
+ /** Read the refused-offer facts back: refs/heads/refused/* → [{ ref, offerId, domain, directiveId,
2366
+ * verdict, intentB64 }]. The git-custody source for GET /dead-letters + the retry lane (P1.2b.2). */
2367
+ export async function listRefusedRefs(eng, ws) {
2368
+ const gitdir = gitdirOf(ws);
2369
+ const refs = kgit.listRefs(eng, gitdir, "refs/heads/refused/"); // [{ ref, oid }] — oid in hand, no resolveRef
2370
+ const out = [];
2371
+ for (const { ref, oid } of refs) {
2372
+ try {
2373
+ const vb = kgit.readBlob(eng, gitdir, oid, "verdict.json");
2374
+ const ib = kgit.readBlob(eng, gitdir, oid, "intent.json");
2375
+ const v = JSON.parse(dec.decode(vb));
2376
+ out.push({ ref, ...v, intentB64: b64Bytes(ib) });
2377
+ } catch { /* a partial/corrupt refused ref is skipped (the reader is best-effort) */ }
2378
+ }
2379
+ return out;
2380
+ }
2381
+
2382
+ /**
2383
+ * OFFER-KERNEL P1.2b: turn an admit cycle's refusals into git-custody facts. Each dead-letter is
2384
+ * written as `refs/heads/refused/<offerId>` ({intent.json, verdict.json}) and PUSHED to custody so
2385
+ * it SYNCS to clients + survives DO eviction — replacing the adapter-mail DLQ (`ctx.storage`). Called
2386
+ * inline from the admit lanes: refusals are EXCEPTIONAL (off the admit hot path), so the inline push
2387
+ * is cheap and makes the fact durable even on the deferred-seal lane (where main's push is debounced).
2388
+ * Best-effort per ref (a failed write/push is logged, never aborts the cycle — the session branch is
2389
+ * still durable, so the refusal re-materializes next cycle, idempotently). Returns the pushed refs.
2390
+ */
2391
+ export async function materializeRefused(eng, ws, ledger, deadLetters) {
2392
+ const gitdir = gitdirOf(ws);
2393
+ const refs = [];
2394
+ for (const dl of deadLetters || []) {
2395
+ try {
2396
+ const ref = await writeRefusedRef(eng, ws, dl);
2397
+ await custodyPush(eng, ws, ledger, { remoteRef: ref }); // new refused commit → ships its objects
2398
+ refs.push(ref);
2399
+ } catch (e) {
2400
+ log("refused-ref-materialize-failed", { ws, intentId: dl?.intentId, error: String(e).slice(0, 200) });
2401
+ }
2402
+ }
2403
+ return refs;
2404
+ }
2405
+
2406
+ /** Drop a refused-offer fact (retry succeeded, or an explicit discard): delete the local ref + push
2407
+ * the delete to custody so the quarantine branch is gone everywhere. Best-effort. */
2408
+ export async function deleteRefusedRef(eng, ws, ledger, offerId) {
2409
+ const gitdir = gitdirOf(ws);
2410
+ const ref = refusedRefName(offerId);
2411
+ try { kgit.deleteRef(eng, gitdir, ref); } catch {}
2412
+ try { await custodyPush(eng, ws, ledger, { remoteRef: ref, del: true }); }
2413
+ catch (e) { log("refused-ref-delete-push-failed", { ws, ref, error: String(e).slice(0, 200) }); }
2414
+ return ref;
2415
+ }
2416
+
2417
+ // ── THE OUTBOX (capability_offers_https.md — offer-kernel P6, slice 1) ─────────────────────
2418
+ // A capability is a holon at a URL; an obligation is offered to it as an HTTPS POST. The kernel
2419
+ // already surfaces the orders (reactorScan = obligations at status:'requested'); the capability's
2420
+ // endpoint rides the law (declaredCapabilities). The BAILIFF joins them and POSTs — pure transport,
2421
+ // it decides nothing (the holon decided by admitting the obligation; the law named the endpoint).
2422
+ // A delivered order is recorded as refs/heads/outbox/<requestId> (the delivery marker + custody;
2423
+ // idempotent — a delivered order is never re-POSTed). The receipt lane (slice 2) closes the
2424
+ // obligation; the credential lane (slice 3) forwards the caller's packet.
2425
+
2426
+ /** A requestId sanitized to a git ref path component. */
2427
+ const outboxRefName = (requestId) => `refs/heads/outbox/${String(requestId || "unknown").replace(/[^A-Za-z0-9._-]/g, "_")}`;
2428
+
2429
+ /** Has this order already been delivered? (the outbox ref is the delivered marker — idempotency). */
2430
+ export async function outboxRefExists(eng, ws, requestId) {
2431
+ return !!kgit.resolveRef(eng, gitdirOf(ws), outboxRefName(requestId));
2432
+ }
2433
+
2434
+ /** Record a delivered order as a git-custody fact: a commit on refs/heads/outbox/<requestId>
2435
+ * carrying { order.json, delivery.json }. Deterministic CKPT_AUTHOR ⇒ idempotent. */
2436
+ export async function writeOutboxRef(eng, ws, rec) {
2437
+ const gitdir = gitdirOf(ws);
2438
+ const orderBlob = enc.encode(JSON.stringify(rec.order ?? {}));
2439
+ const delivery = enc.encode(JSON.stringify({
2440
+ requestId: rec.requestId || null, capability: rec.capability || null, url: rec.url || null,
2441
+ status: rec.status || "delivered", httpStatus: rec.httpStatus ?? null,
2442
+ }));
2443
+ const ref = outboxRefName(rec.requestId);
2444
+ kgit.writeMetaCommit(eng, gitdir, {
2445
+ parents: [], files: [{ path: "order.json", bytes: orderBlob }, { path: "delivery.json", bytes: delivery }],
2446
+ message: `outbox: ${rec.capability || "?"} → ${String(rec.url || "?").slice(0, 80)}`,
2447
+ timeSecs: 0, ref,
2448
+ });
2449
+ return ref;
2450
+ }
2451
+
2452
+ /**
2453
+ * THE BAILIFF OUTBOX LOOP: scan the kernel's requested orders, join each to its declared
2454
+ * capability endpoint, and POST the obligation as an HTTPS offer. Best-effort + idempotent
2455
+ * (a delivered order has an outbox ref and is skipped). Runs on the engine plane (DO edge OR
2456
+ * heavy container — both have `fetch` + the ledger). Returns `{delivered:[requestId], skipped, errors}`.
2457
+ * The adapter decides nothing: the obligation (a holon admission) is the emit decision; the endpoint
2458
+ * is law. NO receipt handling yet (slice 2) — POSTing is delivery, the obligation stays 'requested'.
2459
+ */
2460
+ export async function emitOutbox(eng, ws, ledger) {
2461
+ const orders = (() => { try { return reactorScan(eng, ws); } catch { return []; } })();
2462
+ if (!Array.isArray(orders) || orders.length === 0) return { delivered: [], skipped: 0, errors: 0 };
2463
+ const byType = new Map();
2464
+ for (const c of declaredCapabilities(eng, ws)) if (c && c.aggregateId) byType.set(c.aggregateId, c);
2465
+ const gitdir = gitdirOf(ws);
2466
+ const delivered = []; let skipped = 0, errors = 0;
2467
+ for (const order of orders) {
2468
+ const cap = byType.get(order.taskType);
2469
+ if (!cap || !cap.endpoint) { skipped++; continue; } // no destination — rests at 'requested'
2470
+ if (await outboxRefExists(eng, ws, order.requestId)) { skipped++; continue; } // already delivered
2471
+ try {
2472
+ const res = await fetch(cap.endpoint, {
2473
+ method: "POST",
2474
+ headers: { "content-type": "application/json", "x-nomos-capability": cap.capability, "x-nomos-workspace": ws },
2475
+ body: JSON.stringify({ capability: cap.capability, requestId: order.requestId, taskType: order.taskType, params: order.params, scheduledAt: order.scheduledAt, deadlineAt: order.deadlineAt }),
2476
+ signal: AbortSignal.timeout(30000),
2477
+ });
2478
+ if (!res.ok) { errors++; log("outbox-deliver-non2xx", { ws, requestId: order.requestId, url: cap.endpoint, status: res.status }); continue; }
2479
+ const ref = await writeOutboxRef(eng, ws, { requestId: order.requestId, capability: cap.capability, url: cap.endpoint, order, status: "delivered", httpStatus: res.status });
2480
+ try { await custodyPush(eng, ws, ledger, { remoteRef: ref }); }
2481
+ catch (e) { log("outbox-ref-push-failed", { ws, ref, error: String(e).slice(0, 200) }); }
2482
+ delivered.push(order.requestId);
2483
+ } catch (e) { errors++; log("outbox-deliver-failed", { ws, requestId: order.requestId, url: cap.endpoint, error: String(e).slice(0, 200) }); }
2484
+ }
2485
+ if (delivered.length || errors) log("outbox", { ws, delivered: delivered.length, skipped, errors });
2486
+ return { delivered, skipped, errors };
2487
+ }
2488
+
2013
2489
  /** Genesis boot: bootstrap → the nomos controller; idempotent when already Active. */
2014
2490
  export async function boot(eng, ws, ledger) {
2015
2491
  const m = await mountWorkspace(eng, ws, ledger);
@@ -2021,17 +2497,25 @@ export async function boot(eng, ws, ledger) {
2021
2497
  return { status: "created", head: result.head, controllerHash: eng.hashes.nomos, rows: qById(eng, ws, `domain-installation:${eng.hashes.nomos}`), mount: m };
2022
2498
  }
2023
2499
 
2024
- /** Install a tenant package under the nomos controller (validation is the HOST's job). */
2500
+ /** Install a tenant package under the nomos controller (validation is the ADAPTER's job). */
2025
2501
  export async function deploy(eng, ws, ledger, usda, domainHash, dispositions) {
2026
2502
  // THE EVOLVE-GATE ANSWER LANE (#58): `dispositions` ({retired, retyped, rebinds,
2027
2503
  // ackBy}) ride the committed installDomain payload — IN-HISTORY facts the one gate
2028
2504
  // adjudicates the stable-id diff against. Absent for the common (non-destructive)
2029
2505
  // upgrade; the gate's typed refusal names the remedy when one is needed.
2030
2506
  const result = author(eng, ws, "nomos", "installDomain", installPayload(domainHash, usda, eng.installedBy, dispositions), eng.hashes.nomos);
2031
- // Shard identity used to be inferred from host-side taxonomy. That is retired:
2507
+ // ATOMICITY: VALIDATE the post-install projection BEFORE committing to custody. The install's
2508
+ // read manifest is re-derived on this read; if it cannot fold (e.g. a divergent read-projection
2509
+ // declaration the kernel refuses), this THROWS — and because we have NOT yet pushed, custody
2510
+ // stays clean (the durable source of truth never holds an unfoldable install; the in-memory mount
2511
+ // is evicted + re-mounted from clean custody on the error). A bad install must FAIL CLOSED, never
2512
+ // persist in the chain. (Was: commitAndPush THEN qById — a fold failure left the install pushed,
2513
+ // wedging every later fold. See read-manifest-evolution-gap.)
2514
+ const installation = qById(eng, ws, `domain-installation:${domainHash}`);
2515
+ // Shard identity used to be inferred from adapter-side taxonomy. That is retired:
2032
2516
  // if sharding needs declaration, the holon must derive and author it itself.
2033
2517
  await commitAndPush(eng, ws, ledger);
2034
- return { head: result.head, installation: qById(eng, ws, `domain-installation:${domainHash}`) };
2518
+ return { head: result.head, installation };
2035
2519
  }
2036
2520
 
2037
2521
  export function snapshot(eng, ws) {
@@ -2048,18 +2532,15 @@ export function stats(eng) {
2048
2532
 
2049
2533
  export async function ledgerStats(eng, ws, ledger = null) {
2050
2534
  const gitdir = gitdirOf(ws);
2051
- let head = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null);
2535
+ let head = kgit.resolveRef(eng, gitdir, BRANCH);
2052
2536
  if (!head && ledger?.remote) {
2053
2537
  try {
2054
- const info = await git.getRemoteInfo({ http, url: ledger.remote, headers: ledger.headers });
2055
- const remoteMain = (info.refs && info.refs.heads && info.refs.heads[BRANCH]) || null;
2538
+ // Cold: build the skeleton + pull main into the KERNEL's odb (custodyFetch apply_pack), then resolve.
2539
+ await custodySkeleton(eng, gitdir);
2540
+ const remoteMain = await custodyFetch(eng, ws, ledger, { remoteRef: "refs/heads/main" });
2056
2541
  if (!remoteMain) return { head: null, commits: 0 };
2057
- await git.init({ fs: eng.fs, gitdir, bare: true, defaultBranch: BRANCH }).catch(() => {});
2058
- await git.addRemote({ fs: eng.fs, gitdir, remote: "origin", url: ledger.remote, force: true });
2059
- await git.fetch({ fs: eng.fs, http, gitdir, remote: "origin", ref: BRANCH, singleBranch: true, headers: ledger.headers });
2060
- await git.writeRef({ fs: eng.fs, gitdir, ref: `refs/heads/${BRANCH}`, value: remoteMain, force: true });
2061
- await git.writeRef({ fs: eng.fs, gitdir, ref: "HEAD", value: `ref: refs/heads/${BRANCH}`, force: true, symbolic: true });
2062
- head = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null);
2542
+ kgit.writeRef(eng, gitdir, "HEAD", `ref: refs/heads/${BRANCH}`);
2543
+ head = kgit.resolveRef(eng, gitdir, BRANCH);
2063
2544
  const mounted = eng.mounted.get(ws);
2064
2545
  if (mounted) {
2065
2546
  mounted.restored = true;
@@ -2071,32 +2552,32 @@ export async function ledgerStats(eng, ws, ledger = null) {
2071
2552
  if (!head) return { head: null, commits: 0 };
2072
2553
  const mounted = eng.mounted.get(ws);
2073
2554
  if (mounted?.ledgerStats?.head === head) return mounted.ledgerStats;
2074
- const commits = (await git.log({ fs: eng.fs, gitdir, ref: head, depth: 10000000 })).length;
2555
+ // The chain is a linear sequence of intent commits, so intent-commit count == commit count.
2556
+ const commits = kgit.intentsAbove(eng, gitdir, head).length;
2075
2557
  const out = { head, commits };
2076
2558
  if (mounted) mounted.ledgerStats = out;
2077
2559
  return out;
2078
2560
  }
2079
2561
 
2080
- export async function countRemoteCommits(ledger) {
2081
- const info = await git.getRemoteInfo({ http, url: ledger.remote, headers: ledger.headers });
2082
- const head = (info.refs && info.refs.heads && info.refs.heads[BRANCH]) || null;
2562
+ export async function countRemoteCommits(eng, ledger) {
2563
+ const head = (await custodyLsRefs(ledger, "refs/heads/main"))["refs/heads/main"] || null;
2083
2564
  if (!head) return { head: null, commits: 0 };
2084
- const root = new Map();
2085
- root.set("count", new Directory(new Map()));
2086
- const preopen = new PreopenDirectory("/work", root);
2087
- const fs = makeGitFs(preopen, "/work", { File, Directory });
2088
- const gitdir = "/work/count/nomos.git";
2089
- await git.init({ fs, gitdir, bare: true, defaultBranch: BRANCH });
2090
- await git.addRemote({ fs, gitdir, remote: "origin", url: ledger.remote, force: true });
2091
- await git.fetch({ fs, http, gitdir, remote: "origin", ref: BRANCH, singleBranch: true, headers: ledger.headers });
2092
- await git.writeRef({ fs, gitdir, ref: `refs/heads/${BRANCH}`, value: head, force: true });
2093
- await git.writeRef({ fs, gitdir, ref: "HEAD", value: `ref: refs/heads/${BRANCH}`, force: true, symbolic: true });
2094
- const commits = (await git.log({ fs, gitdir, ref: head, depth: 10000000 })).length;
2095
- return { head, commits };
2565
+ // Count without mounting the workspace: pull main into a SCRATCH dir under the KERNEL's own odb (the adapter
2566
+ // has no repo — the kernel ingests the pack via apply_pack + walks it), then tear the scratch dir down.
2567
+ const scratch = "__ledgercount__";
2568
+ try {
2569
+ stageWorkspaceDir(eng, scratch);
2570
+ await custodySkeleton(eng, gitdirOf(scratch));
2571
+ await custodyFetch(eng, scratch, ledger, { remoteRef: "refs/heads/main" });
2572
+ const commits = kgit.intentsAbove(eng, gitdirOf(scratch), head).length;
2573
+ return { head, commits };
2574
+ } finally {
2575
+ try { eng.preopen.dir.contents.get("ws").contents.delete(scratch); } catch {}
2576
+ }
2096
2577
  }
2097
2578
 
2098
2579
  /** Per-branch projection observables (cursor, projectCalls, sqliteBytes — see #37). */
2099
- export const projectionStats = (eng, ws) => JSON.parse(call(eng.ex, "projection_stats", ws ? { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH } : {}, eng.STDERR));
2580
+ export const projectionStats = (eng, ws) => JSON.parse(call(eng.ex, "query", ws ? { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "projectionStats" }), branch: BRANCH } : { queryBytes: b64Json({ op: "projectionStats" }) }, eng.STDERR));
2100
2581
 
2101
2582
  /**
2102
2583
  * THE POST-ACK WARM LANE (#34): replenish the wasm engine's pristine-sandbox pool
@@ -2108,5 +2589,7 @@ export const projectionStats = (eng, ws) => JSON.parse(call(eng.ex, "projection_
2108
2589
  */
2109
2590
  export function warmEngine(eng) {
2110
2591
  flushDeferredProjections(eng); // the deferred-ack projection flush (#47) rides the same lane
2111
- return JSON.parse(call(eng.ex, "engine_warm", {}, eng.STDERR));
2592
+ // P3: the pristine-pool warm rides the `query` verb (op:"warm") — a non-effectful maintenance read,
2593
+ // not a top-level adapter verb. Still post-ack (the adapter calls warmEngine off the write critical path).
2594
+ return JSON.parse(call(eng.ex, "query", { repoArg: "/work", workspace: "root", queryBytes: b64Json({ op: "warm" }), branch: BRANCH }, eng.STDERR));
2112
2595
  }