@githolon/testing 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,31 +1,38 @@
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 cloud/cloudflare-shell/src/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, manifest overlays, 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";
27
24
  export const repoArgOf = (ws) => `/work/ws/${ws}`;
28
25
  export const gitdirOf = (ws) => `/work/ws/${ws}/nomos.git`;
26
+ function b64Bytes(bytes) {
27
+ let s = "";
28
+ for (let i = 0; i < bytes.length; i += 0x8000) {
29
+ s += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
30
+ }
31
+ return btoa(s);
32
+ }
33
+ function b64Json(o) {
34
+ return b64Bytes(enc.encode(JSON.stringify(o)));
35
+ }
29
36
 
30
37
  const unpack = (p) => { const v = typeof p === "bigint" ? p : BigInt(p); return { ptr: Number(v >> 32n), len: Number(v & 0xffffffffn) }; };
31
38
  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,26 +62,66 @@ function call(ex, mode, fields, STDERR) {
55
62
  } finally { ex.git_holon_dealloc(reqPtr, req.length); }
56
63
  }
57
64
 
58
- function seedManifests(m, strings) {
59
- m.set("domain_manifests.json", new File(enc.encode(strings.read)));
60
- m.set("identity-manifests.json", new File(enc.encode(strings.identity)));
65
+ // ── KERNEL GIT PLUMBING (kgit) — the cloud shell speaks NO git; the kernel (gix) owns the odb. ──
66
+ // These wrap the kernel's local-object RPCs (the adapter is a byte pipe, never touches objects/refs).
67
+ // They mirror the isomorphic-git call shapes they replace so the migration is a 1:1 substitution.
68
+ // The repo dir the wasm opens is `${repoArg}/nomos.git`; the adapter names it as `gitdir` everywhere, so
69
+ // derive repoArg back from it (gitdir === gitdirOf(ws) === `${repoArgOf(ws)}/nomos.git`).
70
+ const repoArgOfGitdir = (gitdir) => gitdir.replace(/\/nomos\.git$/, "");
71
+ const bytesFromB64 = (b64) => Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
72
+ const kgit = {
73
+ // resolveRef → tip oid hex, or null if the ref is absent (replaces git.resolveRef(...).catch(()=>null)).
74
+ resolveRef: (eng, gitdir, ref) =>
75
+ JSON.parse(call(eng.ex, "resolve_ref", { repoArg: repoArgOfGitdir(gitdir), ref }, eng.STDERR)).oid,
76
+ // readBlob → the blob bytes at `path` inside commit `oid`, or null if not found (no throw).
77
+ readBlob: (eng, gitdir, oid, path) => {
78
+ const r = JSON.parse(call(eng.ex, "read_blob", { repoArg: repoArgOfGitdir(gitdir), oid, path }, eng.STDERR));
79
+ return r.ok ? bytesFromB64(r.blobB64) : null;
80
+ },
81
+ // intentsAbove → [{ oid, intentB64 }] oldest→newest over (base, head] (replaces log + readBlob intent.json).
82
+ // `limit` (0 = all) caps to the most-recent N intent commits (replaces git.log depth).
83
+ intentsAbove: (eng, gitdir, head, base = "", limit = 0) =>
84
+ JSON.parse(call(eng.ex, "intents_above", { repoArg: repoArgOfGitdir(gitdir), head, base, limit }, eng.STDERR)).intents,
85
+ // listRefs → [{ ref, oid }] under prefix (replaces git.listRefs for LOCAL refs).
86
+ listRefs: (eng, gitdir, prefix) =>
87
+ JSON.parse(call(eng.ex, "list_refs", { repoArg: repoArgOfGitdir(gitdir), prefix }, eng.STDERR)).refs,
88
+ // writeRef → force-point a ref (plain oid or `ref:`-symbolic) (replaces git.writeRef).
89
+ writeRef: (eng, gitdir, ref, value) =>
90
+ JSON.parse(call(eng.ex, "write_ref", { repoArg: repoArgOfGitdir(gitdir), ref, value }, eng.STDERR)),
91
+ // deleteRef → remove a local ref, idempotent (replaces git.deleteRef).
92
+ deleteRef: (eng, gitdir, ref) =>
93
+ JSON.parse(call(eng.ex, "delete_ref", { repoArg: repoArgOfGitdir(gitdir), ref }, eng.STDERR)),
94
+ // writeMetaCommit → blobs→tree→commit→ref for a CUSTODY-META fact (checkpoint/refused/outbox/shape).
95
+ // `files`: [{ path, bytes:Uint8Array }]; returns the commit oid (replaces writeBlob+writeTree+writeCommit[+writeRef]).
96
+ writeMetaCommit: (eng, gitdir, { parents = [], files, message = "nomos custody meta", timeSecs = 0, ref = "" }) =>
97
+ JSON.parse(call(eng.ex, "write_meta_commit", {
98
+ repoArg: repoArgOfGitdir(gitdir), parents,
99
+ files: files.map((f) => ({ path: f.path, blobB64: b64Bytes(f.bytes) })),
100
+ message, timeSecs, ref,
101
+ }, eng.STDERR)).oid,
102
+ };
103
+
104
+ function seedManifests(_m, _strings) {
105
+ // Runtime manifests are committed inside USDA packages and derived by the holon
106
+ // from active installed law. Hosts must not seed admission/projection sidecars.
61
107
  }
62
108
 
63
- // (#39 BAILIFF: no host-chosen `authorityScope` literal. It was a dead, hardcoded "workspace/root" stamped
109
+ // (#39 BAILIFF: no adapter-chosen `authorityScope` literal. It was a dead, hardcoded "workspace/root" stamped
64
110
  // 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
111
+ // (domain_lifecycle.rs folds it to an Option<String> and never branches on it). The adapter carries the
66
112
  // package bytes; it does not author an authority-scoping fact the law never declared. Absent ⇒ folded None.)
67
113
  export const installPayload = (hash, usda, installedBy, dispositions) => ({ domainHash: hash, packageUsda: usda, installedBy, dependencies: [], finalizers: [], ...(dispositions ? { dispositions } : {}) });
68
114
 
69
115
  /**
70
116
  * Boot the resident wasm + /work tree. `wasmModule` is a PRECOMPILED WebAssembly.Module
71
- * (the host compiles its way); `manifestStrings` = {read, identity} (baked overlays —
72
- * the HOST owns overlay storage and recomputes); `replica` = the host's unique 63-bit id.
117
+ * (the adapter compiles its way); `replica` = the adapter's unique 63-bit id.
118
+ *
119
+ * Runtime read/admission metadata is sovereign holon state: committed USDA packages
120
+ * are folded by the githolon, and the adapter never stages manifest sidecars.
73
121
  */
74
- export async function createEngine({ wasmModule, bootstrapPkg, nomosPkg, manifestStrings, replica, installedBy = "nomos-cloud" }) {
122
+ export async function createEngine({ wasmModule, bootstrapPkg, nomosPkg, replica, installedBy = "nomos-cloud" }) {
75
123
  const hashes = { bootstrap: await sha256hex(bootstrapPkg), nomos: await sha256hex(nomosPkg) };
76
124
  const root = new Map();
77
- seedManifests(root, manifestStrings);
78
125
  root.set("domain.package.usda", new File(enc.encode(bootstrapPkg)));
79
126
  root.set("ws", new Directory(new Map()));
80
127
  const preopen = new PreopenDirectory("/work", root);
@@ -87,39 +134,19 @@ export async function createEngine({ wasmModule, bootstrapPkg, nomosPkg, manifes
87
134
  for (const n of ["memory", "git_holon_alloc", "git_holon_dealloc", "git_holon_call"]) if (!inst.exports[n]) throw new Error(`missing export ${n}`);
88
135
  const eng = {
89
136
  ex: inst.exports, preopen, STDERR, seq: 0, replica, hashes,
90
- bootstrapPkg, nomosPkg, manifestStrings, installedBy,
137
+ bootstrapPkg, nomosPkg, installedBy,
91
138
  fs: makeGitFs(preopen, "/work", { File, Directory }),
92
139
  mounted: new Map(), // ws -> { restored, remoteMain, restoreError }
93
140
  mainIntents: new Map(), // ws -> { head, ids:Set, oids:Set } (admission idempotence)
94
- knownDomainsCache: null,
95
141
  };
96
142
  return eng;
97
143
  }
98
144
 
99
- /** Swap the manifest strings (a deploy staged a new overlay) + restage every mount. */
100
- export function setManifests(eng, strings) {
101
- eng.manifestStrings = strings;
102
- eng.knownDomainsCache = null;
103
- eng.directiveRoutesCache = null;
104
- eng.scopedReadsCache = null;
105
- eng.placementDirectivesCache = null;
106
- eng.tenantTypesCache = null;
107
- eng.globalAggregatesCache = null;
108
- const root = eng.preopen.dir.contents;
109
- seedManifests(root, strings);
110
- const wsRoot = root.get("ws");
111
- for (const ws of eng.mounted.keys()) {
112
- const wsDir = wsRoot.contents.get(ws);
113
- if (wsDir) seedManifests(wsDir.contents, strings);
114
- }
115
- }
116
-
117
- /** The domains DEPLOYED to this workspace = the effective identity map's keys. */
118
- export function knownDomains(eng) {
119
- if (eng.knownDomainsCache) return eng.knownDomainsCache;
120
- eng.knownDomainsCache = new Set(Object.keys(JSON.parse(eng.manifestStrings.identity)));
121
- return eng.knownDomainsCache;
122
- }
145
+ // There is NO adapter→holon manifest channel. The wasm derives every read/identity
146
+ // manifest from the FOLDED ledger (installed_read_manifest_bytes_from_state) and
147
+ // refuses a package lacking nomosReadProjection. A adapter cannot provide a manifest:
148
+ // the op does not exist (Read Confinement — the kernel's transition function, not a
149
+ // adapter convention). `setManifests` is gone, not no-op'd, so the lie is unrepresentable.
123
150
 
124
151
  // ── TRANSPARENT SHARDING: the WRONG-HOME GATE (the edge bailiff — sharding slice 2) ──
125
152
  //
@@ -135,18 +162,10 @@ export function knownDomains(eng) {
135
162
 
136
163
  /** directive routes off the effective identity manifests: `${domain} ${directiveId}` → route. */
137
164
  export function directiveRoutes(eng) {
138
- if (eng.directiveRoutesCache) return eng.directiveRoutesCache;
139
- const routes = new Map();
140
- let identity = {};
141
- try { identity = JSON.parse(eng.manifestStrings.identity); } catch {}
142
- for (const [domain, manifestJson] of Object.entries(identity)) {
143
- try {
144
- const m = typeof manifestJson === "string" ? JSON.parse(manifestJson) : manifestJson;
145
- for (const r of (m && m.routes) || []) routes.set(`${domain} ${r.directive}`, r);
146
- } catch {}
147
- }
148
- eng.directiveRoutesCache = routes;
149
- return routes;
165
+ void eng;
166
+ // Adapter-side manifest taxonomy is retired. Shard routing needs a holon-owned
167
+ // taxonomy API before it can be re-enabled without leaking law into the adapter.
168
+ return new Map();
150
169
  }
151
170
 
152
171
  /** The 48-bit route tag of a home key (must agree byte-for-byte with the client mint). */
@@ -160,7 +179,7 @@ function routeTagHexOfMintedId(id) {
160
179
  }
161
180
 
162
181
  /**
163
- * Judge ONE routed intent against the shard map. `shardRouting` is HOST-INJECTED:
182
+ * Judge ONE routed intent against the shard map. `shardRouting` is ADAPTER-INJECTED:
164
183
  * `{ selfLabel, coordinatorWs, assignments() }` — `selfLabel` is this workspace's shard
165
184
  * label (`s<k>`) or null when it IS the coordinator; `assignments()` resolves the
166
185
  * ACTIVE `NomosShardAssignment` rows from the coordinator's law-state. Returns null to
@@ -214,7 +233,7 @@ export async function wrongHomeVerdict(shardRouting, route, payload) {
214
233
 
215
234
  // ── §5.2 — THE DELTA LANE (sharding slice 3): the shard-side EMITTER machinery ──
216
235
  //
217
- // After a shard's edge admission folds a batch, the HOST authors one coalesced
236
+ // After a shard's edge admission folds a batch, the ADAPTER authors one coalesced
218
237
  // `nomosPropagateSummary` Order into the COORDINATOR's chain: per touched
219
238
  // (scoped read, group) the ABSOLUTE subtotal + the covered range's per-intent
220
239
  // event deltas as content-addressed evidence. The coordinator's ONE gate
@@ -236,7 +255,7 @@ export const NOMOS_SHARD_ROWS_READ = "nomosShardRows";
236
255
  const FRAMEWORK_AGG_TYPES = new Set([
237
256
  "NomosShardAssignment", "NomosShardIdentity", "NomosHomeReceipt", "NomosShardRegistry",
238
257
  "NomosShardPolicy", "NomosSummarySubtotal", "NomosSummaryFrontier", "NomosDeepVerify",
239
- "NomosCheckpointSeal",
258
+ "NomosCheckpointSeal", "NomosMoveObligation", // P6 — the move-leg obligation (coordinator law-state)
240
259
  // CONTROL-PLANE law-state (every chain carries them via installDomain) — never tenant data:
241
260
  "PolicyBundle", "DomainInstallation",
242
261
  ]);
@@ -246,77 +265,30 @@ const FRAMEWORK_AGG_TYPES = new Set([
246
265
  * the suffix re-derivation, deep-verify's recount): control-plane rows (PolicyBundle,
247
266
  * DomainInstallation) and framework law-state never skew the capacity metric. */
248
267
  function tenantTypes(eng) {
249
- if (eng.tenantTypesCache) return eng.tenantTypesCache;
250
- let readManifest = {};
251
- try { readManifest = JSON.parse(eng.manifestStrings.read); } catch {}
252
- // §5.4 (slice 4): GLOBAL reference data is replicated custody on every shard —
253
- // its rows arrive by the reverse lane (replicateIntents — a host leg the §5.2
254
- // capture never sees), so counting them as tenant load would let the three
255
- // row-count lanes drift (capture vs suffix vs deep-verify recount). Excluded
256
- // from the capacity metric on EVERY lane, by the one shared definition here.
257
- const globals = globalAggregates(eng);
258
- const out = new Set(Object.keys(readManifest.aggregateFieldKinds || {}).filter((t) => !FRAMEWORK_AGG_TYPES.has(t) && !globals.has(t)));
259
- eng.tenantTypesCache = out;
260
- return out;
268
+ void eng;
269
+ return new Set();
261
270
  }
262
271
 
263
272
  /** The derived PLACEMENT directives off the effective identity manifests:
264
273
  * packed type `site` ⇒ `birthSite` keyed by payload field `siteId` (the law's naming). */
265
274
  export function placementDirectives(eng) {
266
- if (eng.placementDirectivesCache) return eng.placementDirectivesCache;
267
- const out = new Map(); // directiveId -> { axis, keyField }
268
- const pascal = (s) => s.replace(/[_\s-]+(\w)/g, (_m, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase());
269
- let identity = {};
270
- try { identity = JSON.parse(eng.manifestStrings.identity); } catch {}
271
- for (const manifestJson of Object.values(identity)) {
272
- let m;
273
- try { m = typeof manifestJson === "string" ? JSON.parse(manifestJson) : manifestJson; } catch { continue; }
274
- for (const t of (m && m.workspaceTypes) || []) {
275
- if (!t || t.mode !== "packed") continue;
276
- const p = pascal(String(t.id));
277
- out.set(`birth${p}`, { axis: t.id, keyField: `${p.length ? p[0].toLowerCase() + p.slice(1) : p}Id` });
278
- }
279
- }
280
- eng.placementDirectivesCache = out;
281
- return out;
275
+ void eng;
276
+ return new Map();
282
277
  }
283
278
 
284
279
  /** The ESTATE-SCOPED maintained reads off the effective identity manifests.
285
280
  * SLICE 4: `where` (the canonical predicate, hash-bearing in the manifest) rides
286
281
  * along — the LIVE capture's oracle is the projection's maintained tally (already
287
- * predicate-aware in the kernel); the SUFFIX re-derivation evaluates it host-side
282
+ * predicate-aware in the kernel); the SUFFIX re-derivation evaluates it adapter-side
288
283
  * ({@link evalCanonicalPred}) so both lanes agree byte-for-byte. */
289
284
  export function scopedReads(eng) {
290
- if (eng.scopedReadsCache) return eng.scopedReadsCache;
291
- const out = [];
292
- let identity = {};
293
- try { identity = JSON.parse(eng.manifestStrings.identity); } catch {}
294
- for (const manifestJson of Object.values(identity)) {
295
- let m;
296
- try { m = typeof manifestJson === "string" ? JSON.parse(manifestJson) : manifestJson; } catch { continue; }
297
- for (const c of (m && m.counts) || []) {
298
- if (c && typeof c.scope === "string") out.push({ id: c.id, kind: "count", of: c.of, by: c.by ?? null, ...(c.where ? { where: c.where } : {}) });
299
- }
300
- for (const s of (m && m.sums) || []) {
301
- if (s && typeof s.scope === "string") out.push({ id: s.id, kind: "sum", of: s.of, by: s.by ?? null, field: s.sumField, ...(s.where ? { where: s.where } : {}) });
302
- }
303
- // SLICE 8 — scoped EXTREMUMS: per-shard min/max ride the same delta lane
304
- // (events carry post-intent ABSOLUTES; the gate extremizes-on-monotone and
305
- // requires declared re-derivation on retractions). The oracle is the
306
- // projection's long-maintained extrema lane (#47's `extremum` RPC).
307
- for (const [key, kind] of [["mins", "min"], ["maxes", "max"]]) {
308
- for (const e of (m && m[key]) || []) {
309
- if (e && typeof e.scope === "string") out.push({ id: e.id, kind, of: e.of, by: e.by ?? null, field: e.valueField, ...(e.where ? { where: e.where } : {}) });
310
- }
311
- }
312
- }
313
- eng.scopedReadsCache = out;
314
- return out;
285
+ void eng;
286
+ return [];
315
287
  }
316
288
 
317
289
  /**
318
290
  * Evaluate a canonical count/sum predicate (`predicate.ts` CanonicalPred — eq/ne
319
- * clauses + and/or trees) over a host-folded field map. EXACTLY the kernel's
291
+ * clauses + and/or trees) over a adapter-folded field map. EXACTLY the kernel's
320
292
  * declared semantics: values canonicalize to strings (int fields compare their
321
293
  * decimal form); `eq` over an ABSENT field is FALSE, `ne` over an absent field is
322
294
  * TRUE (unset ≠ any literal — the §7-D symmetry rule). Used ONLY by the suffix
@@ -340,19 +312,8 @@ export function evalCanonicalPred(pred, get) {
340
312
  * DOWNWARD into every shard chain by re-admission (the reverse delta lane).
341
313
  */
342
314
  export function globalAggregates(eng) {
343
- if (eng.globalAggregatesCache) return eng.globalAggregatesCache;
344
- const out = new Set();
345
- let identity = {};
346
- try { identity = JSON.parse(eng.manifestStrings.identity); } catch {}
347
- for (const manifestJson of Object.values(identity)) {
348
- let m;
349
- try { m = typeof manifestJson === "string" ? JSON.parse(manifestJson) : manifestJson; } catch { continue; }
350
- for (const t of (m && m.workspaceTypes) || []) {
351
- for (const g of (t && t.globals) || []) if (typeof g === "string") out.add(g);
352
- }
353
- }
354
- eng.globalAggregatesCache = out;
355
- return out;
315
+ void eng;
316
+ return new Set();
356
317
  }
357
318
 
358
319
  /** Parse an intent blob's sealed events: [{aggregate, ops: Map(field -> {Str|Int})}]. */
@@ -450,7 +411,7 @@ function finishSummaryCapture(eng, ws, ctx, cap, oid, intentId) {
450
411
  ctx.values.set(key, { read, group, value: after });
451
412
  }
452
413
  // The synthetic row-count read: existence flips, with the projection as oracle.
453
- // The ABSOLUTE rides relative to the coordinator-held value (the HOST closes it
414
+ // The ABSOLUTE rides relative to the coordinator-held value (the ADAPTER closes it
454
415
  // at emit time — a shard never enumerates its own whole projection per batch).
455
416
  // SLICE 8 — EXACT PER-HOME SUBTOTALS: each flip is also attributed to its HOME
456
417
  // (the row id's route tag, or the row id itself for an axis root) so the split
@@ -481,7 +442,7 @@ function finishSummaryCapture(eng, ws, ctx, cap, oid, intentId) {
481
442
 
482
443
  /**
483
444
  * The coalesced delta of one admission batch (null when nothing summarizable moved).
484
- * `rowsDelta` rides separately: the HOST closes the `nomosShardRows` absolute against
445
+ * `rowsDelta` rides separately: the ADAPTER closes the `nomosShardRows` absolute against
485
446
  * the coordinator-held value at emit time (held + Σ deltas — the gate re-checks both
486
447
  * halves). The other subtotals' `prev` is the batch-start maintained tally, which
487
448
  * equals the coordinator-held value exactly when the frontier is contiguous — and the
@@ -516,7 +477,7 @@ function summaryOfCapture(ctx, fromOid, toOid) {
516
477
  subtotals: subtotals.sort((a, b) => (summaryKey(a.readId, a.group) < summaryKey(b.readId, b.group) ? -1 : 1)),
517
478
  events: ctx.events,
518
479
  rowsDelta: ctx.rowsDelta,
519
- // SLICE 8: the exact per-home row deltas — the host closes each against the
480
+ // SLICE 8: the exact per-home row deltas — the adapter closes each against the
520
481
  // coordinator-held per-home subtotal exactly like the "" grand total.
521
482
  rowsByHome: Object.fromEntries([...ctx.rowsByHome.entries()].sort((a, b) => (a[0] < b[0] ? -1 : 1))),
522
483
  };
@@ -524,7 +485,7 @@ function summaryOfCapture(ctx, fromOid, toOid) {
524
485
 
525
486
  /**
526
487
  * THE SUFFIX RE-DERIVATION (the frontier-gap self-heal + /summaries/resync): derive
527
- * the delta payload for (fromOid → main head] from THE CHAIN ITSELF — a host-side
488
+ * the delta payload for (fromOid → main head] from THE CHAIN ITSELF — a adapter-side
528
489
  * pure fold of the scoped reads' group/sum fields over every commit since genesis
529
490
  * (Lww in chain order; scoped reads are predicate-free by the compile gate). Used
530
491
  * when the coordinator's recorded frontier does not match a live capture (a lost
@@ -533,10 +494,9 @@ function summaryOfCapture(ctx, fromOid, toOid) {
533
494
  export async function deriveSummarySuffix(eng, ws, fromOid) {
534
495
  const reads = scopedReads(eng);
535
496
  const gitdir = gitdirOf(ws);
536
- const head = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null);
497
+ const head = kgit.resolveRef(eng, gitdir, BRANCH);
537
498
  if (!head) return { ok: false, error: "no local main to derive from" };
538
- const commits = await git.log({ fs: eng.fs, gitdir, ref: BRANCH });
539
- commits.reverse(); // genesis → head
499
+ const commits = kgit.intentsAbove(eng, gitdir, head); // [{oid, intentB64}] genesis → head
540
500
  const state = new Map(); // aggregateId -> { type, fields: Map }
541
501
  const running = new Map(); // summaryKey -> value (ADDITIVE kinds)
542
502
  const bump = (key, d) => running.set(key, (running.get(key) ?? 0) + d);
@@ -566,7 +526,7 @@ export async function deriveSummarySuffix(eng, ws, fromOid) {
566
526
  let sawFrom = inSuffix;
567
527
  for (const c of commits) {
568
528
  let blob = null;
569
- try { blob = (await git.readBlob({ fs: eng.fs, gitdir, oid: c.oid, filepath: "intent.json" })).blob; } catch {}
529
+ try { blob = bytesFromB64(c.intentB64); } catch {}
570
530
  const deltas = [];
571
531
  let intentId = "";
572
532
  if (blob) {
@@ -713,7 +673,7 @@ export async function deriveSummarySuffix(eng, ws, fromOid) {
713
673
  };
714
674
  }
715
675
 
716
- /** One read's contribution shape over a host-folded aggregate (suffix derivation only).
676
+ /** One read's contribution shape over a adapter-folded aggregate (suffix derivation only).
717
677
  * SLICE 4: predicate-aware — a `where`-bearing scoped read contributes only while the
718
678
  * folded fields satisfy the canonical predicate (same semantics as the kernel's
719
679
  * maintained tally, so the suffix lane and the live capture agree byte-for-byte). */
@@ -743,7 +703,7 @@ function contributionDiff(read, before, after) {
743
703
 
744
704
  // ── §6 — THE SHARD LIFECYCLE (slice 5): migration = re-admission, never trust ──
745
705
  //
746
- // The host legs of a split: the TARGET pulls the moving homes' intents off the
706
+ // The adapter legs of a split: the TARGET pulls the moving homes' intents off the
747
707
  // SOURCE chain and re-admits EACH through its own gate (`apply_intent` — the same
748
708
  // primitive the upload birth and edge admission run: plan re-run, byte-compared,
749
709
  // invariants judged); the SOURCE then seals the move with ONE law intent that
@@ -795,14 +755,14 @@ export async function collectHomedIntents(eng, ws, homeKeys) {
795
755
  for (const hk of homeKeys) tagOf.set(await routeTagHexOfHomeKey(hk), hk);
796
756
  const keySet = new Set(homeKeys);
797
757
  const gitdir = gitdirOf(ws);
798
- const commits = await git.log({ fs: eng.fs, gitdir, ref: BRANCH });
799
- commits.reverse(); // genesis → head
758
+ const head = kgit.resolveRef(eng, gitdir, BRANCH);
759
+ const commits = head ? kgit.intentsAbove(eng, gitdir, head) : []; // [{oid, intentB64}] genesis → head
800
760
  const docs = [];
801
761
  const strikeParity = new Map(); // intentId -> strike count
802
762
  for (const c of commits) {
803
763
  let doc = null;
804
764
  try {
805
- const { blob } = await git.readBlob({ fs: eng.fs, gitdir, oid: c.oid, filepath: "intent.json" });
765
+ const blob = bytesFromB64(c.intentB64);
806
766
  doc = JSON.parse(dec.decode(blob));
807
767
  docs.push({ oid: c.oid, blob, doc });
808
768
  } catch { continue; }
@@ -846,7 +806,7 @@ export async function migrateHomes(eng, ws, ledger, sourceWs, homeKeys, { onlyId
846
806
  for (const e of homed) {
847
807
  if (wanted && !wanted.has(e.id)) continue;
848
808
  if (main.ids.has(e.id)) { skipped.push(e.id); continue; }
849
- const res = applyIntentBytes(eng, ws, e.blob);
809
+ const res = offerAdmit(eng, ws, e.blob);
850
810
  if (res.ok) {
851
811
  main.ids.add(e.id); main.oids.add(res.head); main.head = res.head;
852
812
  migrated.push(e.id);
@@ -896,7 +856,7 @@ export async function sealHomes(eng, ws, ledger, { domain, lawHash, homeKeys, ta
896
856
  * chain head carrying the diffs (deep-verify recomputes from the same oracle).
897
857
  */
898
858
  export async function summaryCloseAgainstHeld(eng, ws, heldRows, heldFrontier) {
899
- const head = await git.resolveRef({ fs: eng.fs, gitdir: gitdirOf(ws), ref: BRANCH }).catch(() => null);
859
+ const head = kgit.resolveRef(eng, gitdirOf(ws), BRANCH);
900
860
  if (!head) return { ok: false, error: "no local main to close against" };
901
861
  if (head === heldFrontier) return { ok: true, upToDate: true, head };
902
862
  const subtotals = [];
@@ -965,15 +925,15 @@ export async function collectGlobalIntents(eng, ws) {
965
925
  const globals = globalAggregates(eng);
966
926
  if (globals.size === 0) return [];
967
927
  const gitdir = gitdirOf(ws);
968
- const commits = await git.log({ fs: eng.fs, gitdir, ref: BRANCH });
969
- commits.reverse(); // genesis → head
928
+ const head = kgit.resolveRef(eng, gitdir, BRANCH);
929
+ const commits = head ? kgit.intentsAbove(eng, gitdir, head) : []; // [{oid, intentB64}] genesis → head
970
930
  const typeOf = new Map(); // aggregateId -> __type (set once at creation — types never change)
971
931
  const strikeParity = new Map();
972
932
  const docs = [];
973
933
  for (const c of commits) {
974
934
  let blob = null, doc = null;
975
935
  try {
976
- blob = (await git.readBlob({ fs: eng.fs, gitdir, oid: c.oid, filepath: "intent.json" })).blob;
936
+ blob = bytesFromB64(c.intentB64);
977
937
  doc = JSON.parse(dec.decode(blob));
978
938
  } catch { continue; }
979
939
  docs.push({ oid: c.oid, blob, doc });
@@ -1009,7 +969,7 @@ export async function replicateIntents(eng, ws, ledger, intents, { push = true }
1009
969
  const replicated = [], skipped = [], refused = [];
1010
970
  for (const e of intents) {
1011
971
  if (e.id && main.ids.has(e.id)) { skipped.push(e.id); continue; }
1012
- const res = applyIntentBytes(eng, ws, e.blob);
972
+ const res = offerAdmit(eng, ws, e.blob);
1013
973
  if (res.ok) {
1014
974
  if (e.id) main.ids.add(e.id);
1015
975
  main.oids.add(res.head); main.head = res.head;
@@ -1019,7 +979,7 @@ export async function replicateIntents(eng, ws, ledger, intents, { push = true }
1019
979
  }
1020
980
  }
1021
981
  // push=false is the OFFLINE proof lane only (bench harness — no ledger remote);
1022
- // every host leg keeps durability-before-ack.
982
+ // every adapter leg keeps durability-before-ack.
1023
983
  if (replicated.length && push) await commitAndPush(eng, ws, ledger);
1024
984
  log("replicate-globals", { ws, replicated: replicated.length, skipped: skipped.length, refused: refused.length });
1025
985
  return { replicated, skipped, refused };
@@ -1027,18 +987,37 @@ export async function replicateIntents(eng, ws, ledger, intents, { push = true }
1027
987
 
1028
988
  /**
1029
989
  * Open browser/session lane without a client-side Artifacts receive-pack: the
1030
- * client offers the exact intent bytes it authored locally; the host applies the
990
+ * client offers the exact intent bytes it authored locally; the adapter applies the
1031
991
  * same structural fences as session admission, folds accepted bytes, and seals
1032
992
  * main once. This is still untrusted work: owner-only/control-plane/receipt
1033
993
  * writes are refused or dead-lettered exactly like session refs.
1034
994
  */
1035
- export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains = [], receiptFence = null } = {}) {
995
+ export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains = [], receiptFence = null, shardRouting = null, clientBase = null, defer = false } = {}) {
1036
996
  const refuse = new Set(refuseDomains);
1037
997
  const main = await mainIntentIds(eng, ws);
1038
998
  const sessions = new Map();
1039
999
  const deadLetters = [];
1040
1000
  let admittedTotal = 0;
1041
1001
  const timings = { spans: [], intents: 0, sessions: 0 };
1002
+ // THE §5.2 SUMMARY CAPTURE on the OFFER LANE (offers are the only source of intents — this lane is the one
1003
+ // admit, routed workspaces included). A shard (selfLabel set) with scoped reads captures the per-batch
1004
+ // subtotal delta the same way the session-branch lane does; the adapter then closes it onto the coordinator
1005
+ // (`_postAdmissionHooks` → `_emitSummary`). Null on an unsharded/coordinator/read-free batch (zero cost).
1006
+ const summaryCtx = shardRouting && shardRouting.selfLabel && scopedReads(eng).length > 0
1007
+ ? { reads: scopedReads(eng), prevs: new Map(), values: new Map(), events: [], rowsDelta: 0, rederived: new Set(), rowsByHome: new Map(), homeTags: new Map(), homeKeySet: new Set() }
1008
+ : null;
1009
+ if (summaryCtx) {
1010
+ try {
1011
+ for (const r of query(eng, ws, "aggregatesByType", JSON.stringify({ __type: "NomosHomeReceipt" }))) {
1012
+ const hk = r.data && typeof r.data.homeKey === "string" ? r.data.homeKey : null;
1013
+ if (!hk) continue;
1014
+ summaryCtx.homeKeySet.add(hk);
1015
+ summaryCtx.homeTags.set(await routeTagHexOfHomeKey(hk), hk);
1016
+ }
1017
+ } catch (e) { log("home-tag-table-error", { ws, error: String(e).slice(0, 200) }); }
1018
+ }
1019
+ const gitdir0 = gitdirOf(ws);
1020
+ const headBefore = summaryCtx ? kgit.resolveRef(eng, gitdir0, BRANCH) : null;
1042
1021
  const t0 = Date.now(); let seq = 0;
1043
1022
  const span = (name, kind, depth = 1, extra = {}) => {
1044
1023
  const s0 = Date.now();
@@ -1062,14 +1041,15 @@ export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains
1062
1041
  const dom = typeof doc?.payload?.domain === "string" ? doc.payload.domain : null;
1063
1042
  const dirId = typeof doc?.payload?.directiveId === "string" ? doc.payload.directiveId : null;
1064
1043
  const oid = String(offer?.oid || intentId || "").slice(0, 10);
1065
- if (!dom || dom === "nomos" || dom === "bootstrap" || refuse.has(dom)) {
1066
- 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` });
1067
- continue;
1068
- }
1069
- if (!knownDomains(eng).has(dom)) {
1070
- const error = `domain '${dom}' is not deployed to this workspace deploy its law (POST /v1/workspaces/${ws}/domains with the workspace secret), then POST /v1/workspaces/${ws}/dead-letters/retry`;
1071
- rec.rejected.push({ oid, error, deadLettered: true });
1072
- deadLetters.push({ intentId, oid, session: cid, error, blob, domain: dom, directiveId: dirId });
1044
+ // THE GATE DECIDES (offer-kernel doctrine OFFER_KERNEL_ARCHITECTURE.dot): the adapter does NOT
1045
+ // pre-filter control-plane (`nomos`/`bootstrap`) intents by domain string that was the adapter
1046
+ // "choosing admission", which the doctrine forbids. An install/governance offer rides the SAME lane;
1047
+ // the kernel gate refuses an author who lacks the declared relation (installDomain.requiresRelationGate
1048
+ // = {relation:"installDomain", object:"workspace:self"}), so an unauthorized control-plane write is a
1049
+ // TYPED GATE refusal, not a adapter verdict. Only `!dom` (malformed) and the explicit per-workspace
1050
+ // `refuse` fence (root's `workspaces` governance lane) stay — those are structural, not authority.
1051
+ if (!dom || refuse.has(dom)) {
1052
+ rec.rejected.push({ oid, error: `${dom ? `'${dom}' is adapter-fenced on this workspace` : "non-authored intent"} — governance enters its own admin lane` });
1073
1053
  continue;
1074
1054
  }
1075
1055
  if (receiptFence && dirId && Array.isArray(receiptFence[dom]) && receiptFence[dom].includes(dirId)) {
@@ -1082,12 +1062,22 @@ export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains
1082
1062
  rec.skipped.push({ oid, intent: intentId.slice(0, 12), alreadyOnMain: true });
1083
1063
  continue;
1084
1064
  }
1085
- const res = applyIntentBytes(eng, ws, blob);
1065
+ const cap = summaryCtx ? beginSummaryCapture(eng, ws, summaryCtx, blob) : null;
1066
+ // DEFERRED-PROJECTION ACK on the OFFER lane (#47). The per-intent read-projection refresh
1067
+ // (~220ms on a fat ledger) is the warm-sync hot-path waste: the fold IS the correctness step,
1068
+ // the projection only feeds READS, and every read op self-heals to head. So we ACK on the fold
1069
+ // and let the post-ack warm lane (`flushDeferredProjections`, marked below) catch the projection
1070
+ // up — exactly as the session lane (admitAll) already does. A summaryCtx (§5.2) batch keeps the
1071
+ // synchronous refresh: its scoped-read capture re-derives off committed projection state.
1072
+ const _tOffer = Date.now();
1073
+ const res = offerAdmit(eng, ws, blob, { defer: !summaryCtx });
1074
+ log("offer-admit-ms", { ws, ms: Date.now() - _tOffer, deferred: !summaryCtx, ok: res.ok });
1086
1075
  if (res.ok) {
1087
1076
  if (intentId) main.ids.add(intentId);
1088
1077
  main.oids.add(res.head); main.head = res.head;
1089
1078
  rec.admitted.push({ oid, head: res.head });
1090
1079
  admittedTotal += 1;
1080
+ if (cap) finishSummaryCapture(eng, ws, summaryCtx, cap, res.head, intentId);
1091
1081
  } else {
1092
1082
  const error = `${String(res.error || "").slice(0, 300)} — dead-lettered; fix the law (or the payload), then POST /v1/workspaces/${ws}/dead-letters/retry`;
1093
1083
  rec.rejected.push({ oid, error, deadLettered: true });
@@ -1101,22 +1091,41 @@ export async function admitIntentOffers(eng, ws, ledger, offers, { refuseDomains
1101
1091
  }
1102
1092
  timings.sessions = sessions.size;
1103
1093
  timings.intents = admittedTotal;
1104
- if (admittedTotal) {
1094
+ // The deferred projection (above) is flushed by the post-ack warm lane — register this ws so
1095
+ // `flushDeferredProjections` catches it up after we've ACKed. (summaryCtx never deferred.)
1096
+ if (admittedTotal && !summaryCtx) (eng.pendingProjection ??= new Set()).add(ws);
1097
+ // OFFER-KERNEL P1.2b: refusals on the offer lane become git-custody facts too (pushed inline,
1098
+ // before the seal that unmounts on failure). Runs even when nothing admitted — a cycle can be
1099
+ // pure refusals.
1100
+ if (deadLetters.length) { try { await materializeRefused(eng, ws, ledger, deadLetters); } catch (e) { log("refused-materialize-failed", { ws, error: String(e).slice(0, 200) }); } }
1101
+ // GIT OFF THE HOT PATH. The fold above is the whole correctness step — the client said "btw I did
1102
+ // this", we re-executed it, resident main is now truth. We ACK on that. Custody (Artifacts) is a
1103
+ // write-behind MIRROR, never on the hot path: when `defer` is set the adapter backs the captured head
1104
+ // up off-lane (server.mjs custodyWriteBehind → backupHead), coalesced to one push in flight. The
1105
+ // CONTAINER IS THE SOLE WRITER of main, so the mirror needs no pull-back — it just FF-pushes the
1106
+ // snapshot. A summaryCtx (§5.2) batch still seals durable-before-ack (its follow-ups depend on it).
1107
+ const deferDurability = defer && !summaryCtx;
1108
+ if (admittedTotal && !deferDurability) {
1105
1109
  const sealDone = span("seal offered intents", "cseal", 1, { admitted: admittedTotal });
1106
1110
  await commitAndPush(eng, ws, ledger);
1107
1111
  sealDone();
1108
1112
  }
1109
1113
  const gitdir = gitdirOf(ws);
1110
- const head = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => main.head);
1114
+ const head = kgit.resolveRef(eng, gitdir, BRANCH) ?? main.head;
1115
+ // The coalesced §5.2 delta of this batch (null when no scoped read moved) — the adapter closes it onto the
1116
+ // coordinator (`_postAdmissionHooks` → `_emitSummary`), exactly as the session-branch lane does.
1117
+ const summary = summaryCtx ? summaryOfCapture(summaryCtx, headBefore, head) : null;
1118
+ // (The legacy inline `mainDelta` perf hack is GONE: the client pulls the delta itself via GET /pack +
1119
+ // the kernel's gix apply_pack — one byte-pipe lane, no adapter-side loose-object walk.)
1111
1120
  log("admit-intent-offers", { ws, sessions: sessions.size, admitted: admittedTotal, rejected: [...sessions.values()].reduce((n, s) => n + s.rejected.length, 0) });
1112
- return { sessions: [...sessions.values()].map((s) => ({ ...s, ...(s.skipped.length ? {} : { skipped: undefined }) })), main: head, deadLetters, timings };
1121
+ return { sessions: [...sessions.values()].map((s) => ({ ...s, ...(s.skipped.length ? {} : { skipped: undefined }) })), main: head, deadLetters, timings, ...(summary ? { summary } : {}), ...(deferDurability && admittedTotal && head ? { deferred: true, head } : {}) };
1113
1122
  }
1114
1123
 
1115
1124
  /**
1116
1125
  * §5.5 (slice 4) — the CONTENT HASH of the coordinator's committed subtotal state:
1117
1126
  * sha256 over the canonical (row-id-sorted) projection of every
1118
1127
  * `NomosSummarySubtotal` row. The ONE definition every checkpoint party shares —
1119
- * the sealing host claims it, an auditor (deep-verify anchoring, the e2e, any
1128
+ * the sealing adapter claims it, an auditor (deep-verify anchoring, the e2e, any
1120
1129
  * peer that mounts the chain) recomputes it from law-state and byte-compares.
1121
1130
  */
1122
1131
  export async function checkpointStateHash(eng, ws) {
@@ -1155,7 +1164,7 @@ export async function authorOn(eng, ws, ledger, domain, directiveId, payload, la
1155
1164
  * the SHARD's whole chain from genesis (the same wasm `verify_chain` the upload
1156
1165
  * birth runs), then recompute every coordinator-held subtotal for that shard from
1157
1166
  * the shard's OWN maintained tallies and byte-compare. `coordinatorRows` is the
1158
- * coordinator's law-state (`NomosSummarySubtotal` rows for this shard) — the HOST
1167
+ * coordinator's law-state (`NomosSummarySubtotal` rows for this shard) — the ADAPTER
1159
1168
  * resolves and passes them (the engine has no cross-workspace reach). Auditors pay
1160
1169
  * for verification; ordinary reads stay O(1).
1161
1170
  */
@@ -1202,7 +1211,7 @@ export async function deepVerifyShard(eng, shardWs, shardLedger, coordinatorRows
1202
1211
  if (!match) allMatch = false;
1203
1212
  checks.push({ readId: d.readId, group, coordinator: d.value, shard: recomputed, match });
1204
1213
  }
1205
- const head = await git.resolveRef({ fs: eng.fs, gitdir: gitdirOf(shardWs), ref: BRANCH }).catch(() => null);
1214
+ const head = kgit.resolveRef(eng, gitdirOf(shardWs), BRANCH);
1206
1215
  return {
1207
1216
  verdict: allMatch ? "verified" : "mismatch",
1208
1217
  head,
@@ -1214,7 +1223,6 @@ export async function deepVerifyShard(eng, shardWs, shardLedger, coordinatorRows
1214
1223
 
1215
1224
  function stageWorkspaceDir(eng, ws) {
1216
1225
  const wsContents = new Map();
1217
- seedManifests(wsContents, eng.manifestStrings);
1218
1226
  eng.preopen.dir.contents.get("ws").contents.set(ws, new Directory(wsContents));
1219
1227
  }
1220
1228
 
@@ -1223,34 +1231,22 @@ export async function mountWorkspace(eng, ws, ledger) {
1223
1231
  if (eng.mounted.has(ws)) return eng.mounted.get(ws);
1224
1232
  stageWorkspaceDir(eng, ws);
1225
1233
  const gitdir = gitdirOf(ws);
1226
- let restored = false, remoteMain = null, restoreError = null, checkpoint = null;
1234
+ let restored = false, remoteMain = null, restoreError = null;
1227
1235
  try {
1228
- const info = await git.getRemoteInfo({ http, url: ledger.remote, headers: ledger.headers });
1229
- remoteMain = (info.refs && info.refs.heads && info.refs.heads.main) || null;
1236
+ // COLD MOUNT, kernel-native: write the bare skeleton (plain fs), then GET /pack the whole `main` history
1237
+ // via raw smart-HTTP and let the kernel's gix apply_pack ingest it (kept PACKED) zero isomorphic-git.
1238
+ // NO CHECKPOINT. A derived projection snapshot is NEVER trusted — it can corrupt and wedge the holon (it
1239
+ // did), which violates the law: truth is the replayable ledger, nothing derived is load-bearing. The
1240
+ // resident holon folds `main` ONCE on its first read and stays WARM in RAM thereafter — no cold-fold in
1241
+ // the hot path because the bailiff keeps it resident.
1242
+ await custodySkeleton(eng, gitdir);
1243
+ remoteMain = await custodyFetch(eng, ws, ledger, { remoteRef: "refs/heads/main" });
1230
1244
  if (remoteMain) {
1231
- await git.init({ fs: eng.fs, gitdir, bare: true, defaultBranch: "main" });
1232
- await git.addRemote({ fs: eng.fs, gitdir, remote: "origin", url: ledger.remote, force: true });
1233
- const ckpt = await discoverCheckpointFromCustody(ledger).catch(() => null);
1234
- let shallowOk = false;
1235
- if (ckpt?.cursor && ckpt.frontier) {
1236
- const steps = [32, 96, 256, 1024];
1237
- for (let i = 0; i < steps.length; i++) {
1238
- const depth = steps[i], relative = i > 0;
1239
- await git.fetch({ fs: eng.fs, http, gitdir, remote: "origin", ref: "main", singleBranch: true, depth, relative, headers: ledger.headers });
1240
- if (await git.readCommit({ fs: eng.fs, gitdir, oid: ckpt.cursor }).then(() => true, () => false)) { shallowOk = true; break; }
1241
- }
1242
- }
1243
- if (!shallowOk) await git.fetch({ fs: eng.fs, http, gitdir, remote: "origin", ref: "main", singleBranch: true, headers: ledger.headers });
1244
- await git.writeRef({ fs: eng.fs, gitdir, ref: "refs/heads/main", value: remoteMain, force: true });
1245
- await git.writeRef({ fs: eng.fs, gitdir, ref: "HEAD", value: "ref: refs/heads/main", force: true, symbolic: true });
1246
- if (shallowOk && ckpt?.blob) {
1247
- const r = importCheckpoint(eng, ws, ckpt.blob, ckpt.frontier);
1248
- checkpoint = { restored: !!(r && r.restored), frontierSeeded: !!(r && r.frontierSeeded), cursor: r && r.cursor ? r.cursor : ckpt.cursor, ref: ckpt.ref };
1249
- }
1245
+ kgit.writeRef(eng, gitdir, "HEAD", "ref: refs/heads/main");
1250
1246
  restored = true;
1251
1247
  }
1252
1248
  } catch (e) { restoreError = String((e && e.stack) || e).split("\n").slice(0, 5).join(" | "); }
1253
- const m = { restored, remoteMain, restoreError, checkpoint };
1249
+ const m = { restored, remoteMain, restoreError };
1254
1250
  eng.mounted.set(ws, m);
1255
1251
  return m;
1256
1252
  }
@@ -1261,9 +1257,9 @@ function assertAuthored(res, what) {
1261
1257
  }
1262
1258
 
1263
1259
  /**
1264
- * Mount a host-ephemeral workspace. It is still a normal WASI GitHolon under
1265
- * `/work/ws/<name>`; the host behavior is what differs: no custody restore, no
1266
- * Artifacts push, and the whole chain dies with the resident host.
1260
+ * Mount a adapter-ephemeral workspace. It is still a normal WASI GitHolon under
1261
+ * `/work/ws/<name>`; the adapter behavior is what differs: no custody restore, no
1262
+ * Artifacts push, and the whole chain dies with the resident adapter.
1267
1263
  */
1268
1264
  export async function mountEphemeralWorkspace(eng, ws, { domainHash = "", packageUsda = "" } = {}) {
1269
1265
  let m = eng.mounted.get(ws);
@@ -1287,13 +1283,13 @@ export async function mountEphemeralWorkspace(eng, ws, { domainHash = "", packag
1287
1283
  return m;
1288
1284
  }
1289
1285
 
1290
- /** Author into a host-ephemeral workspace and leave the resulting head in memory only. */
1286
+ /** Author into a adapter-ephemeral workspace and leave the resulting head in memory only. */
1291
1287
  export async function authorEphemeral(eng, ws, mountOpts, domain, directiveId, payload, lawHash, opts = {}) {
1292
1288
  await mountEphemeralWorkspace(eng, ws, mountOpts);
1293
1289
  return author(eng, ws, domain, directiveId, payload, lawHash, { deferProjection: false, ...opts });
1294
1290
  }
1295
1291
 
1296
- /** Query a host-ephemeral workspace, mounting/installing its package first if needed. */
1292
+ /** Query a adapter-ephemeral workspace, mounting/installing its package first if needed. */
1297
1293
  export async function queryEphemeral(eng, ws, mountOpts, queryId, paramsJson, principal = "", keys = []) {
1298
1294
  await mountEphemeralWorkspace(eng, ws, mountOpts);
1299
1295
  return query(eng, ws, queryId, paramsJson, principal, keys);
@@ -1312,18 +1308,44 @@ function writeWork(eng, name, bytes) { eng.preopen.dir.contents.set(name, new Fi
1312
1308
  /** THE CURRENT LAW PER DOMAIN KEY — the holon's OWN resolution (`refresh_key_map`,
1313
1309
  * the map the verify walk and every dispatch use: latest Active install per key
1314
1310
  * by install stamp). Returns `{ "<domainKey>": "<currentHash>", … }`. status uses
1315
- * THIS instead of re-deriving recency host-side from deploy order. */
1311
+ * THIS instead of re-deriving recency adapter-side from deploy order. */
1316
1312
  export function currentLaw(eng, ws) {
1317
- const out = JSON.parse(call(eng.ex, "current_law", { repoArg: repoArgOf(ws), branch: BRANCH }, eng.STDERR));
1313
+ const out = JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "currentLaw" }), branch: BRANCH }, eng.STDERR));
1318
1314
  return out.keyMap || {};
1319
1315
  }
1320
1316
 
1317
+ /** THE CAPABILITY DECLARATION READ (Full-A — capability_marketplace.md §4): the holon
1318
+ * surfaces every CURRENTLY-INSTALLED domain's `nomosCapabilities` law key (the impure-
1319
+ * capability quartets sealed into the law). The deploy lane relays these as a "declared —
1320
+ * bind it or obligations wait" notice WITHOUT reaching into root law-state. Returns
1321
+ * `[{capability, domain, aggregateId, obligation, complete, fail, block, deadLetter, tasksQueryId}]`. */
1322
+ export function declaredCapabilities(eng, ws) {
1323
+ const out = JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "declaredCapabilities" }), branch: BRANCH }, eng.STDERR));
1324
+ return out.capabilities || [];
1325
+ }
1326
+
1327
+ /** THE REACTOR SCAN (the kernel's existing "orders to emit"): every impure-capability
1328
+ * obligation at status:'requested', as `[{taskType, requestId, scheduledAt, deadlineAt, params}]`.
1329
+ * The bailiff outbox loop reads this + joins each taskType to its declared endpoint. */
1330
+ export function reactorScan(eng, ws) {
1331
+ // THE OUTBOX VERB (one of the seven): the holon's obligations-to-emit. The adapter reads its emit-queue
1332
+ // through `outbox`, never the pre-doctrine `reactor_scan` (which survives behind legacy-adapter-api for tools).
1333
+ return JSON.parse(call(eng.ex, "outbox", { repoArg: repoArgOf(ws), branch: BRANCH }, eng.STDERR));
1334
+ }
1335
+
1321
1336
  /** THE EVOLVE DRY-RUN (managed_rollout.md slice 1 — "would this upgrade admit?"):
1322
1337
  * the evolve gate's pure judgment over the mounted workspace's folded state and a
1323
1338
  * CANDIDATE package, without installing. Returns {admits} or {admits:false,
1324
1339
  * domain, detail} — byte-identical to the refusal the real install would meet. */
1325
1340
  export function evolveDryRun(eng, ws, usda, domainHash, dispositions = null) {
1326
- return JSON.parse(call(eng.ex, "evolve_dryrun", { repoArg: repoArgOf(ws), branch: BRANCH, bundle: usda, hash: domainHash, ...(dispositions ? { dispositions } : {}) }, eng.STDERR));
1341
+ // "WOULD THIS UPGRADE ADMIT?" is `offer` with the commit withheld (the offer-kernel dry-run): the SAME
1342
+ // `author(nomos/installDomain, <candidate package + dispositions>)` a real deploy runs, with dryRun:true.
1343
+ // The candidate rides the payload (packageUsda) exactly as a live install carries it — no inline-queryBytes
1344
+ // bundle, no separate verb. The full admission gate runs (incl. the #58 evolve gate, which reads the bundle
1345
+ // from the intent's own events); the commit never happens. The verdict normalizes to the {admits} shape the
1346
+ // evolve-dryrun callers consume — `Ok` ⇒ admits; a refusal carries the gate's own reason (the evolve detail).
1347
+ const r = author(eng, ws, "nomos", "installDomain", installPayload(domainHash, usda, eng.installedBy, dispositions), eng.hashes.nomos, { dryRun: true, deferProjection: false });
1348
+ return r.ok ? { admits: true } : { admits: false, detail: r.error };
1327
1349
  }
1328
1350
 
1329
1351
  /** THE FRONT-DOOR MINT (engine plane): reserve a kernel-minted typed id
@@ -1347,26 +1369,39 @@ export function author(eng, ws, domain, directiveId, payload, controllerHash, op
1347
1369
  // THE DEFERRED-PROJECTION ACK (#47, sharding slice 7): the read-projection
1348
1370
  // catch-up (incl. the derive/combine materialize) moves OFF the author ack path
1349
1371
  // by default — every read op self-heals to head, and the post-ack warm lane
1350
- // (`warmEngine`, which every host already calls) flushes it eagerly. Pass
1372
+ // (`warmEngine`, which every adapter already calls) flushes it eagerly. Pass
1351
1373
  // `opts.deferProjection: false` to keep the pre-#47 synchronous refresh.
1352
1374
  const defer = opts.deferProjection !== false;
1353
1375
  // authorSecret (warrant-flip): the author's client-held ed25519 device secret (b64), injected for THIS
1354
1376
  // local author ONLY so the wasm author door mints the signed-author attestation + self-verifies. Absent
1355
1377
  // on a legacy/unwarranted author (the door's legacy fast path); a warranted workspace REQUIRES it.
1356
- 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));
1378
+ // THE ONE EFFECTFUL VERB: authoring is `offer` of a PAYLOAD intent (a `directiveId` present the holon
1379
+ // plans it, drawing fresh captured ports from the envelope). The structured verdict normalizes back to the
1380
+ // {ok, head, intentOut} shape every caller consumes — `author` is no longer a adapter verb (it dissolved into
1381
+ // offer; the wasm `author` arm survives for the bench/CLI behind legacy-adapter-api until P8).
1382
+ // DRY-RUN (offer-kernel): `offer` with the commit withheld — run the plan + full gate, get the verdict,
1383
+ // commit nothing. This is the home of the old `evolve_dryrun` verb (see `evolveDryRun`). Never defers
1384
+ // projection (no commit to flush).
1385
+ 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));
1386
+ const res = v.outcome === "admitted" ? { ok: true, head: v.head, intentOut: v.intentOut }
1387
+ : v.outcome === "refused" ? { ok: false, error: v.verdict?.reason ?? v.error }
1388
+ : v; // defensive: a non-verdict shape passes through unchanged
1357
1389
  if (defer && res.ok) (eng.pendingProjection ??= new Set()).add(ws);
1358
1390
  return res;
1359
1391
  }
1360
1392
 
1361
- /** Catch one workspace's read projection up to head NOW (the deferred-ack flush). */
1393
+ /** Catch one workspace's read projection up to head NOW (the deferred-ack flush). P3: rides the
1394
+ * `query` verb (op:"refresh") — a non-effectful maintenance read, not a top-level adapter verb. */
1362
1395
  export function refreshProjection(eng, ws) {
1363
- return JSON.parse(call(eng.ex, "refresh_projection", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH }, eng.STDERR));
1396
+ return JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "refresh" }), branch: BRANCH }, eng.STDERR));
1364
1397
  }
1365
1398
 
1366
1399
  /** Flush every deferred projection refresh (rides the post-ack warm lane). */
1367
1400
  export function flushDeferredProjections(eng) {
1368
1401
  for (const ws of eng.pendingProjection ?? []) {
1402
+ const _t = Date.now();
1369
1403
  try { refreshProjection(eng, ws); } catch {}
1404
+ log("flush-projection-ms", { ws, ms: Date.now() - _t });
1370
1405
  }
1371
1406
  if (eng.pendingProjection) eng.pendingProjection.clear();
1372
1407
  }
@@ -1377,24 +1412,24 @@ export function flushDeferredProjections(eng) {
1377
1412
  // the DO under load). The checkpoint persists the folded projection (cursor + every table) so a
1378
1413
  // cold mount RESTORES it and the first read folds only tip−cursor (O(delta)).
1379
1414
 
1380
- /** Export `ws`'s projection-at-head to a base64 blob the HOST persists. Returns
1415
+ /** Export `ws`'s projection-at-head to a base64 blob the ADAPTER persists. Returns
1381
1416
  * `{cursor, blob, bytes}` (or `{empty:true}` for an unborn branch). */
1382
1417
  export function exportCheckpoint(eng, ws) {
1383
1418
  return JSON.parse(call(eng.ex, "checkpoint_export", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH }, eng.STDERR));
1384
1419
  }
1385
1420
 
1386
1421
  /** Restore a checkpoint blob into the pool BEFORE the first read on a cold mount (no-op if the pool
1387
- * is already warm). The HOST restores ONLY a blob whose stored lawHash matches the workspace's
1422
+ * is already warm). The ADAPTER restores ONLY a blob whose stored lawHash matches the workspace's
1388
1423
  * CURRENT law (schema parity); a mismatched/absent blob costs at most a cold fold, never a wrong read. */
1389
1424
  export function importCheckpoint(eng, ws, blobB64, frontierB64) {
1390
1425
  return JSON.parse(call(eng.ex, "checkpoint_import", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH, blob: blobB64, ...(frontierB64 ? { frontier: frontierB64 } : {}) }, eng.STDERR));
1391
1426
  }
1392
1427
 
1393
- // ── CHECKPOINT IN CUSTODY (the host-agnostic cold-mount; compute placement is beneath the contract) ──
1428
+ // ── CHECKPOINT IN CUSTODY (the adapter-agnostic cold-mount; compute placement is beneath the contract) ──
1394
1429
  // The folded projection is a fact ABOUT the chain — so carry it IN custody (a git ref), not in some
1395
- // host's private storage the NEXT host can't reach. The ephemeral heavy container has no ctx.storage;
1430
+ // adapter's private storage the NEXT adapter can't reach. The ephemeral heavy container has no ctx.storage;
1396
1431
  // a DO checkpoint is invisible to it; so root (heavy) re-folds O(chain) on every cold start. Put the
1397
- // checkpoint where every peer already looks — the ledger — and ANY host restores it O(delta): the git
1432
+ // checkpoint where every peer already looks — the ledger — and ANY adapter restores it O(delta): the git
1398
1433
  // carries its own folded state. Keyed by the CURRENT law hash (schema parity): a law upgrade orphans
1399
1434
  // the old checkpoint (the next reader cold-folds once, re-publishes under the new hash). Best-effort &
1400
1435
  // post-ack — a missing/stale checkpoint costs at most one cold fold, never a wrong read; main is truth.
@@ -1402,7 +1437,7 @@ const CKPT_REF = (lawHash) => `refs/meta/checkpoint/${lawHash}`;
1402
1437
  const CKPT_AUTHOR = { name: "nomos", email: "nomos@holon", timestamp: 0, timezoneOffset: 0 };
1403
1438
 
1404
1439
  // A serialized SQLite projection is mostly FREE PAGES (the fold DELETEs+reinserts per commit) — it
1405
- // gzips ~30×. We compress the b64 export host-side before it enters custody: the ledger meters pushed
1440
+ // gzips ~30×. We compress the b64 export adapter-side before it enters custody: the ledger meters pushed
1406
1441
  // bytes against the workspace quota, and a cold peer fetches less. Runtime-neutral (CompressionStream
1407
1442
  // is in Node 18+ and workerd). The `meta` blob records the codec so a future format stays readable.
1408
1443
  async function streamAll(stream) {
@@ -1421,36 +1456,6 @@ async function gunzip(bytes) {
1421
1456
  return streamAll(new Response(bytes).body.pipeThrough(ds));
1422
1457
  }
1423
1458
 
1424
- async function discoverCheckpointFromCustody(ledger) {
1425
- const freshHeaders = () => ({ ...(ledger.headers || {}) });
1426
- const refs = ((await git.listServerRefs({ http, url: ledger.remote, headers: freshHeaders(), prefix: "refs/meta/checkpoint/", protocolVersion: 2 })) || [])
1427
- .filter((r) => r.ref && r.ref.startsWith("refs/meta/checkpoint/") && r.oid);
1428
- if (!refs.length) return null;
1429
- const root = new Map();
1430
- root.set("ckpt", new Directory(new Map()));
1431
- const preopen = new PreopenDirectory("/work", root);
1432
- const fs = makeGitFs(preopen, "/work", { File, Directory });
1433
- const gitdir = "/work/ckpt/nomos.git";
1434
- await git.init({ fs, gitdir, bare: true, defaultBranch: BRANCH });
1435
- await git.addRemote({ fs, gitdir, remote: "origin", url: ledger.remote, force: true });
1436
- const readFile = async (oid, filepath) => { try { return (await git.readBlob({ fs, gitdir, oid, filepath })).blob; } catch { return null; } };
1437
- for (let i = refs.length - 1; i >= 0 && i >= refs.length - 3; i--) {
1438
- const { ref, oid } = refs[i];
1439
- try {
1440
- await git.fetch({ fs, http, gitdir, remote: "origin", ref, singleBranch: true, depth: 1, headers: freshHeaders() });
1441
- const gz = await readFile(oid, "checkpoint");
1442
- if (!gz) continue;
1443
- let meta = {}; try { const mb = await readFile(oid, "meta"); if (mb) meta = JSON.parse(dec.decode(mb)); } catch {}
1444
- const codec = meta.codec ?? "gzip";
1445
- const blob = dec.decode(codec === "gzip" ? await gunzip(gz) : gz);
1446
- const fgz = await readFile(oid, "frontier");
1447
- const frontier = fgz ? dec.decode(codec === "gzip" ? await gunzip(fgz) : fgz) : null;
1448
- return { ref, oid, cursor: meta.cursor || null, blob, frontier };
1449
- } catch { /* try an older checkpoint */ }
1450
- }
1451
- return null;
1452
- }
1453
-
1454
1459
  /** Wrap an exported checkpoint into git objects under `refs/meta/checkpoint/<lawHash>` (LOCAL only —
1455
1460
  * no network). The ref points at a parentless commit whose tree carries `checkpoint` (the b64 blob)
1456
1461
  * and `meta` ({cursor,bytes,lawHash}). Returns {ref, commitOid}. Separated from the push so the
@@ -1458,26 +1463,21 @@ async function discoverCheckpointFromCustody(ledger) {
1458
1463
  export async function writeCheckpointObjects(eng, ws, ex, lawHash) {
1459
1464
  const gitdir = gitdirOf(ws);
1460
1465
  const gz = await gzip(enc.encode(ex.blob));
1461
- const blobOid = await git.writeBlob({ fs: eng.fs, gitdir, blob: gz });
1462
1466
  // THE FOLD FRONTIER (the gate's Workspace state @cursor) rides ALONGSIDE the projection so a cold mount
1463
1467
  // can AUTHOR on a shallow clone (the projection alone is a read-collapsed view the gate can't fold from).
1464
1468
  // Era-safe: pre-this-era exports carry no `ex.frontier`, so the `frontier` file is simply absent and a
1465
1469
  // reader falls back to a full clone (writes need full history). Gzipped like the projection blob.
1466
- const tree = [
1467
- { mode: "100644", path: "checkpoint", oid: blobOid, type: "blob" },
1468
- ];
1470
+ const files = [{ path: "checkpoint", bytes: gz }];
1469
1471
  let fgzLen = 0;
1470
1472
  if (ex.frontier) {
1471
1473
  const fgz = await gzip(enc.encode(ex.frontier));
1472
1474
  fgzLen = fgz.length;
1473
- tree.push({ mode: "100644", path: "frontier", oid: await git.writeBlob({ fs: eng.fs, gitdir, blob: fgz }), type: "blob" });
1475
+ files.push({ path: "frontier", bytes: fgz });
1474
1476
  }
1475
- 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 })) });
1476
- tree.push({ mode: "100644", path: "meta", oid: metaOid, type: "blob" });
1477
- const tree_ = await git.writeTree({ fs: eng.fs, gitdir, tree });
1478
- 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)}` } });
1477
+ 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 })) });
1479
1478
  const ref = CKPT_REF(lawHash);
1480
- await git.writeRef({ fs: eng.fs, gitdir, ref, value: commitOid, force: true });
1479
+ // ONE kernel call: blobs→tree→parentless commit→force ref. Fixed timeSecs deterministic (idempotent).
1480
+ const commitOid = kgit.writeMetaCommit(eng, gitdir, { parents: [], files, message: `checkpoint ${ws} @${String(ex.cursor).slice(0, 16)}`, timeSecs: 0, ref });
1481
1481
  return { ref, commitOid };
1482
1482
  }
1483
1483
 
@@ -1487,17 +1487,17 @@ export async function writeCheckpointObjects(eng, ws, ex, lawHash) {
1487
1487
  * pre-this-era checkpoint (no `frontier` file). */
1488
1488
  export async function readCheckpointBlob(eng, ws, commitOid) {
1489
1489
  const gitdir = gitdirOf(ws);
1490
- const { blob } = await git.readBlob({ fs: eng.fs, gitdir, oid: commitOid, filepath: "checkpoint" });
1490
+ const blob = kgit.readBlob(eng, gitdir, commitOid, "checkpoint");
1491
1491
  let codec = "gzip";
1492
- try { codec = JSON.parse(dec.decode((await git.readBlob({ fs: eng.fs, gitdir, oid: commitOid, filepath: "meta" })).blob)).codec ?? "gzip"; } catch {}
1492
+ try { codec = JSON.parse(dec.decode(kgit.readBlob(eng, gitdir, commitOid, "meta"))).codec ?? "gzip"; } catch {}
1493
1493
  const dezip = async (b) => dec.decode(codec === "gzip" ? await gunzip(b) : b);
1494
1494
  let frontier = null;
1495
- try { frontier = await dezip((await git.readBlob({ fs: eng.fs, gitdir, oid: commitOid, filepath: "frontier" })).blob); } catch { /* pre-this-era: no frontier */ }
1495
+ try { const f = kgit.readBlob(eng, gitdir, commitOid, "frontier"); if (f) frontier = await dezip(f); } catch { /* pre-this-era: no frontier */ }
1496
1496
  return { blob: await dezip(blob), frontier };
1497
1497
  }
1498
1498
 
1499
1499
  /** The checkpoint's schema-parity key: a hash of the workspace's CURRENT law map (every installed
1500
- * domain). The HOST passes nothing — the engine derives it from the chain (bailiff). A law upgrade
1500
+ * domain). The ADAPTER passes nothing — the engine derives it from the chain (bailiff). A law upgrade
1501
1501
  * changes the map → a new key → the old checkpoint is orphaned (the next reader cold-folds once and
1502
1502
  * re-publishes). An explicit `lawHash` (the DO's existing govHash callers, tests) overrides. */
1503
1503
  export async function lawParity(eng, ws, lawHash) {
@@ -1515,7 +1515,7 @@ export async function pushCheckpoint(eng, ws, ledger, lawHash) {
1515
1515
  lawHash = await lawParity(eng, ws, lawHash);
1516
1516
  if (!lawHash) return { pushed: false, reason: "no-law" };
1517
1517
  const gitdir = gitdirOf(ws);
1518
- const head = await git.resolveRef({ fs: eng.fs, gitdir, ref: "refs/heads/main" }).catch(() => null);
1518
+ const head = kgit.resolveRef(eng, gitdir, "refs/heads/main");
1519
1519
  eng._ckptPushed ??= new Map();
1520
1520
  if (head && eng._ckptPushed.get(ws) === `${lawHash}:${head}`) {
1521
1521
  return { pushed: false, reason: "unchanged" };
@@ -1525,12 +1525,66 @@ export async function pushCheckpoint(eng, ws, ledger, lawHash) {
1525
1525
  const tag = `${lawHash}:${ex.cursor}`;
1526
1526
  if (eng._ckptPushed.get(ws) === tag) return { pushed: false, reason: "unchanged" };
1527
1527
  const { ref } = await writeCheckpointObjects(eng, ws, ex, lawHash);
1528
- await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref, remoteRef: ref, force: true, headers: ledger.headers }));
1528
+ await custodyPush(eng, ws, ledger, { remoteRef: ref }); // new checkpoint commit → ships its objects
1529
1529
  eng._ckptPushed.set(ws, tag);
1530
1530
  return { pushed: true, cursor: ex.cursor, bytes: ex.bytes ?? 0 };
1531
1531
  } catch (e) { return { pushed: false, reason: String((e && e.message) || e).slice(0, 160) }; }
1532
1532
  }
1533
1533
 
1534
+ // ── WRITE-BEHIND CHECKPOINT (the cold-fold-on-remount fix) ─────────────────────────────────────────
1535
+ // THE BUG this kills: with write-behind custody on, we mirrored only the LEDGER (the commits), never the
1536
+ // CHECKPOINT (the folded projection). So a re-mounted/evicted holon found no checkpoint and RE-FOLDED the
1537
+ // whole chain on its first admit — O(chain), ~5.7ms/intent, ~9s on a 1635-intent ledger. "Warm" was a lie:
1538
+ // mount restored the git bytes but not the folded truth. These two halves let the write-behind lane persist
1539
+ // the checkpoint the SAME way it persists the ledger — export under the wasm lock (brief, O(delta) on a warm
1540
+ // holon), push off the lock — so a cold mount restores O(1) and admits one intent in O(delta). Cold ≈ warm.
1541
+
1542
+ /** STAGE a checkpoint LOCALLY (wasm export + git object write). The caller MUST hold the wasm serialize
1543
+ * lock (exportCheckpoint re-enters the engine). Returns {ref, tag, cursor, bytes} ready to push, or null
1544
+ * when there is nothing to persist (no law / empty / unchanged since the last push). */
1545
+ export async function stageCheckpoint(eng, ws, lawHash) {
1546
+ try {
1547
+ lawHash = await lawParity(eng, ws, lawHash);
1548
+ if (!lawHash) return null;
1549
+ eng._ckptPushed ??= new Map();
1550
+ const ex = exportCheckpoint(eng, ws);
1551
+ if (!ex || ex.empty || !ex.ok || !ex.blob) return null;
1552
+ const tag = `${lawHash}:${ex.cursor}`;
1553
+ if (eng._ckptPushed.get(ws) === tag) return null; // already mirrored this cursor
1554
+ const { ref } = await writeCheckpointObjects(eng, ws, ex, lawHash);
1555
+ return { ref, tag, cursor: ex.cursor, bytes: ex.bytes ?? 0 };
1556
+ } catch { return null; }
1557
+ }
1558
+
1559
+ /** PUSH a staged checkpoint to custody. Network only — the caller must NOT hold the wasm lock (the staged
1560
+ * git objects are immutable once written, so the push is race-free against concurrent admits). */
1561
+ export async function pushStagedCheckpoint(eng, ws, ledger, staged) {
1562
+ if (!staged || !staged.ref) return { pushed: false };
1563
+ await custodyPush(eng, ws, ledger, { remoteRef: staged.ref });
1564
+ (eng._ckptPushed ??= new Map()).set(ws, staged.tag);
1565
+ return { pushed: true, cursor: staged.cursor, bytes: staged.bytes };
1566
+ }
1567
+
1568
+ /** RECOVERY: delete EVERY `refs/meta/checkpoint/*` on the ledger. A corrupt checkpoint wedges cold
1569
+ * mount (the restore traps before the holon can serve); deleting it makes the next mount re-fold
1570
+ * from `main` (content-addressed, intact) and write a fresh checkpoint on the next write. Pushes the
1571
+ * ref deletion to the RAW Artifacts remote with the ledger's own write token (no per-ref gate there
1572
+ * — the gate is the worker's git PROXY, not custody). Returns the deleted ref names. */
1573
+ export async function clearCheckpointRefs(ledger) {
1574
+ const headers = ledger.headers || {};
1575
+ // A ref deletion ships NO objects + needs NO local repo: the receive-pack advertisement gives each ref's
1576
+ // current oid (the deletion command's old-oid), and pushPack sends the empty pack — pure custody transport.
1577
+ const refs = await lsRefs(ledger.remote, headers, { service: "git-receive-pack", prefix: "refs/meta/checkpoint/" });
1578
+ const deleted = [];
1579
+ for (const ref of Object.keys(refs)) {
1580
+ try {
1581
+ await pushVia(ledger, () => pushPack(ledger.remote, headers, { updates: [{ ref, oldOid: refs[ref], newOid: ZERO_OID }] }));
1582
+ deleted.push(ref);
1583
+ } catch { /* best-effort cleanup */ }
1584
+ }
1585
+ return deleted;
1586
+ }
1587
+
1534
1588
  /** Restore ws's projection from custody BEFORE the first read on a cold mount: ls-remote the law's
1535
1589
  * checkpoint ref, shallow-fetch ONLY that commit (O(checkpoint), never O(chain)), import it. No-op if
1536
1590
  * absent or the law drifted. Returns {restored, cursor?, reason?}. */
@@ -1543,13 +1597,12 @@ export async function restoreCheckpointFromCustody(eng, ws, ledger, lawHash) {
1543
1597
  lawHash = await lawParity(eng, ws, lawHash);
1544
1598
  if (!lawHash) return { restored: false, reason: "no-law" };
1545
1599
  const ref = CKPT_REF(lawHash);
1546
- const freshHeaders = () => ({ ...(ledger.headers || {}) });
1547
- const serverRefs = await git.listServerRefs({ http, url: ledger.remote, headers: freshHeaders(), prefix: ref, protocolVersion: 2 });
1548
- const found = (serverRefs || []).find((r) => r.ref === ref);
1549
- if (!found || !found.oid) return { restored: false, reason: "absent" };
1550
- const gitdir = gitdirOf(ws);
1551
- await git.fetch({ fs: eng.fs, http, gitdir, url: ledger.remote, ref, singleBranch: true, depth: 1, headers: freshHeaders() });
1552
- const { blob, frontier } = await readCheckpointBlob(eng, ws, found.oid);
1600
+ const serverRefs = await custodyLsRefs(ledger, ref);
1601
+ const foundOid = serverRefs[ref];
1602
+ if (!foundOid) return { restored: false, reason: "absent" };
1603
+ // Fetch ONLY the checkpoint commit (parentless O(checkpoint), never O(chain)) into ws's repo + apply_pack.
1604
+ await custodyFetch(eng, ws, ledger, { remoteRef: ref });
1605
+ const { blob, frontier } = await readCheckpointBlob(eng, ws, foundOid);
1553
1606
  const r = importCheckpoint(eng, ws, blob, frontier);
1554
1607
  if (r && r.restored === true && r.cursor) {
1555
1608
  eng._ckptPushed ??= new Map();
@@ -1559,17 +1612,72 @@ export async function restoreCheckpointFromCustody(eng, ws, ledger, lawHash) {
1559
1612
  } catch (e) { return { restored: false, reason: String((e && e.message) || e).slice(0, 160) }; }
1560
1613
  }
1561
1614
 
1562
- // Serialize a push through the host's per-repo lane when present (the DO threads `ledger.pushLane`); the
1615
+ // Serialize a push through the adapter's per-repo lane when present (the DO threads `ledger.pushLane`); the
1563
1616
  // container has none and serializes on its own chain. The point: ONE receive-pack to a given Artifacts repo
1564
1617
  // at a time — a detached checkpoint/shape push can never race the admit's main push (concurrent receive-packs
1565
1618
  // were corrupting/500ing the custody store). All same-repo pushes below funnel through here.
1566
1619
  const pushVia = (ledger, fn) => (ledger && ledger.pushLane ? ledger.pushLane(fn) : fn());
1567
1620
 
1621
+ // ── DUMB-PIPE CUSTODY (raw smart-HTTP via custody-transport; the KERNEL builds/ingests every pack). ──
1622
+ // These replace isomorphic-git's getRemoteInfo/listServerRefs/init/addRemote/fetch/push. The adapter only
1623
+ // shuttles bytes: ls-refs to learn a tip, fetchPack→apply_pack to ingest, packDelta→pushPack to emit.
1624
+ const fullRef = (r) => (r === "HEAD" || r.startsWith("refs/") ? r : `refs/heads/${r}`);
1625
+ const applyPackInto = (eng, ws, pack) => {
1626
+ if (!pack || !pack.length) return;
1627
+ const r = JSON.parse(call(eng.ex, "apply_pack", { repoArg: repoArgOf(ws), packB64: b64Bytes(pack) }, eng.STDERR));
1628
+ if (!r || r.ok !== true) throw new Error(`apply_pack: ${r && r.error}`);
1629
+ };
1630
+ // Build the bare-repo skeleton (plain fs) so gix can open the repo BEFORE any objects land — mirrors the
1631
+ // client: gix init_bare trips wasi getcwd, so write HEAD/objects/refs directly. Idempotent.
1632
+ async function custodySkeleton(eng, gitdir) {
1633
+ const p = eng.fs.promises;
1634
+ for (const d of ["", "/objects", "/objects/pack", "/objects/info", "/refs", "/refs/heads"]) {
1635
+ try { await p.mkdir(`${gitdir}${d}`); } catch (e) { if (e && e.code !== "EEXIST") throw e; }
1636
+ }
1637
+ try { await p.writeFile(`${gitdir}/HEAD`, `ref: refs/heads/${BRANCH}\n`); } catch {}
1638
+ try { await p.writeFile(`${gitdir}/config`, "[core]\n\trepositoryformatversion = 0\n\tbare = true\n"); } catch {}
1639
+ }
1640
+ // ls-remote (upload-pack) → { fullRefName: oid }.
1641
+ const custodyLsRefs = (ledger, prefix = null) => lsRefs(ledger.remote, ledger.headers || {}, { service: "git-upload-pack", prefix });
1642
+ // Fetch `remoteRef`'s history into ws's repo (have=what we hold), apply_pack it, point `localRef` at the tip.
1643
+ // Returns the tip oid (null if the remote lacks the ref). `intoWs` lets a caller ingest into a different repo.
1644
+ async function custodyFetch(eng, ws, ledger, { remoteRef = "refs/heads/main", localRef = null, have = null } = {}) {
1645
+ const remoteRefFull = fullRef(remoteRef);
1646
+ const refs = await custodyLsRefs(ledger, remoteRefFull);
1647
+ const tip = refs[remoteRefFull] || null;
1648
+ if (!tip) return null;
1649
+ if (tip !== have) applyPackInto(eng, ws, await fetchPack(ledger.remote, ledger.headers || {}, { wants: [tip], haves: have ? [have] : [] }));
1650
+ kgit.writeRef(eng, gitdirOf(ws), fullRef(localRef || remoteRef), tip);
1651
+ return tip;
1652
+ }
1653
+ // Push a local ref to custody (advance / force / delete). The kernel builds the pack base(remoteOld)..new;
1654
+ // a delete or no-op sends the empty pack. Funnels through the per-repo push lane (serialised receive-packs).
1655
+ // `pointer`: the target oid is ALREADY in custody (a ref that points at an existing commit — shape/rename),
1656
+ // so send the EMPTY pack (a pure ref update, no objects). `newOid` overrides resolving a local ref.
1657
+ async function custodyPush(eng, ws, ledger, { localRef = null, remoteRef, del = false, pointer = false, newOid = null } = {}) {
1658
+ const headers = ledger.headers || {};
1659
+ const remoteRefFull = fullRef(remoteRef);
1660
+ const remote = await lsRefs(ledger.remote, headers, { service: "git-receive-pack", prefix: remoteRefFull });
1661
+ const oldOid = remote[remoteRefFull] || ZERO_OID;
1662
+ if (del) {
1663
+ if (oldOid === ZERO_OID) return; // already gone
1664
+ return pushVia(ledger, () => pushPack(ledger.remote, headers, { updates: [{ ref: remoteRefFull, oldOid, newOid: ZERO_OID }] }));
1665
+ }
1666
+ const tgt = newOid || kgit.resolveRef(eng, gitdirOf(ws), fullRef(localRef || remoteRef));
1667
+ if (!tgt) throw new Error(`custodyPush: local ref ${localRef || remoteRef} unresolved`);
1668
+ if (tgt === oldOid) return; // up to date
1669
+ // pointer ⇒ no objects (already in custody). Else: FF advance ⇒ delta base..new (minimal); non-ancestor
1670
+ // (force) ⇒ pack_delta returns null → full pack.
1671
+ let pack = null;
1672
+ if (!pointer) { pack = oldOid !== ZERO_OID ? packDelta(eng, ws, oldOid, tgt) : null; if (!pack) pack = packDelta(eng, ws, "", tgt); }
1673
+ return pushVia(ledger, () => pushPack(ledger.remote, headers, { updates: [{ ref: remoteRefFull, oldOid, newOid: tgt }], pack }));
1674
+ }
1675
+
1568
1676
  // DURABILITY-BEFORE-ACK: only acked once the write LANDED in the ledger; a failed push
1569
1677
  // unmounts (drops local state) so the next access re-restores from custody.
1570
1678
  export async function commitAndPush(eng, ws, ledger) {
1571
1679
  try {
1572
- await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir: gitdirOf(ws), url: ledger.remote, ref: "main", remoteRef: "main", force: false, headers: ledger.headers }));
1680
+ await custodyPush(eng, ws, ledger, { remoteRef: "refs/heads/main" });
1573
1681
  } catch (e) {
1574
1682
  unmount(eng, ws);
1575
1683
  throw new Error(`ledger push failed — write NOT durable, rolled back to ledger: ${e}`);
@@ -1590,15 +1698,15 @@ export async function pushShape(eng, ws, ledger) {
1590
1698
  const bytes = st && (st.sqliteBytes ?? st.bytes);
1591
1699
  if (bytes == null) return;
1592
1700
  const gitdir = gitdirOf(ws);
1593
- const head = await git.resolveRef({ fs: eng.fs, gitdir, ref: "refs/heads/main" }).catch(() => null);
1701
+ const head = kgit.resolveRef(eng, gitdir, "refs/heads/main");
1594
1702
  if (!head) return;
1595
1703
  const newRef = `refs/meta/shape/b${bytes}`;
1596
1704
  eng._lastShapeRef ??= new Map();
1597
1705
  if (eng._lastShapeRef.get(ws) === newRef) return;
1598
- await git.writeRef({ fs: eng.fs, gitdir, ref: newRef, value: head, force: true });
1599
- await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref: newRef, remoteRef: newRef, force: true, headers: ledger.headers }));
1706
+ kgit.writeRef(eng, gitdir, newRef, head);
1707
+ await custodyPush(eng, ws, ledger, { remoteRef: newRef, pointer: true }); // points at head (already in custody)
1600
1708
  const prev = eng._lastShapeRef.get(ws);
1601
- 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) */ } }
1709
+ if (prev && prev !== newRef) { try { await custodyPush(eng, ws, ledger, { remoteRef: prev, del: true }); } catch { /* leftover refs are harmless: the reader takes max(bytes) */ } }
1602
1710
  eng._lastShapeRef.set(ws, newRef);
1603
1711
  } catch { /* best-effort; placement self-heals */ }
1604
1712
  }
@@ -1607,30 +1715,33 @@ export async function pushShape(eng, ws, ledger) {
1607
1715
  // Returns {bytes} (the max, in case a restart left a stale ref) or null (no shape yet).
1608
1716
  export async function readShape(ledger) {
1609
1717
  try {
1610
- const refs = await git.listServerRefs({ http, url: ledger.remote, headers: { ...(ledger.headers || {}) }, prefix: "refs/meta/shape/", protocolVersion: 2 });
1718
+ const refs = await custodyLsRefs(ledger, "refs/meta/shape/");
1611
1719
  let best = null;
1612
- for (const r of refs || []) {
1613
- const m = /refs\/meta\/shape\/b(\d+)$/.exec(r.ref || "");
1720
+ for (const ref of Object.keys(refs)) {
1721
+ const m = /refs\/meta\/shape\/b(\d+)$/.exec(ref);
1614
1722
  if (m) { const bytes = +m[1]; if (!best || bytes > best.bytes) best = { bytes }; }
1615
1723
  }
1616
1724
  return best;
1617
1725
  } catch { return null; }
1618
1726
  }
1619
1727
 
1620
- // READ VISIBILITY (slice 5.1): the host STAMPS the presented `principal` onto the
1621
- // read RPC (an impure identity read — never a host DECISION); the githolon gates the
1728
+ // READ VISIBILITY (slice 5.1): the adapter STAMPS the presented `principal` onto the
1729
+ // read RPC (an impure identity read — never a adapter DECISION); the githolon gates the
1622
1730
  // read against its OWN folded role-bindings and returns `{ok:false, error:"read-
1623
1731
  // forbidden:..."}` for a private type the principal may not read. Absent/"" principal
1624
1732
  // is the pre-5.1 wire — public reads are byte-identical.
1625
1733
  // `keys` (slice 6) = the reader's injected scope keys [{scope, epoch, key(b64)}], resolved
1626
- // host-side (X25519 unwrap). Threaded into the read RPC; the wasm AEAD-opens envelope fields.
1627
- 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));
1628
- 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));
1734
+ // adapter-side (X25519 unwrap). Threaded into the read RPC; the wasm AEAD-opens envelope fields.
1735
+ // EVERY adapter read rides the opaque `query` verb (offer-kernel P2): the kernel's rpc_query_bytes dispatches
1736
+ // by `op` to the read handler + decides the gate; the adapter never calls a typed read RPC. (The typed arms
1737
+ // survive behind `legacy-adapter-api` until P8 deletes them; no caller reaches them after this.)
1738
+ 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));
1739
+ 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));
1629
1740
  // THE ZANZIBAR CHECK/EXPAND (governance authz spine): a pure, replay-deterministic authority probe
1630
1741
  // over the projected relations table. `check` → { ok, allowed:bool }; `expand` → { ok, subjects:[...] }.
1631
1742
  // No principal/keys: relation facts are public authority state (admin/creator/platformCreator/capability).
1632
- 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));
1633
- export const expand = (eng, ws, object, relation) => JSON.parse(call(eng.ex, "expand", { repoArg: repoArgOf(ws), workspace: ws, object, relation, branch: BRANCH }, eng.STDERR));
1743
+ 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));
1744
+ 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));
1634
1745
  // THE AUTHOR-SIDE SEAL (slice 6): encrypt a plaintext field value into the on-chain envelope
1635
1746
  // (XChaCha isn't in WebCrypto, so the client calls the wasm; the scope key is injected, never on chain).
1636
1747
  // The SLOT IDENTITY is the #58 STABLE SID (rename-safety): the caller resolves (type,field)→sid
@@ -1639,11 +1750,11 @@ export const expand = (eng, ws, object, relation) => JSON.parse(call(eng.ex, "ex
1639
1750
  export const cryptoSealField = (eng, { scopeKey, scope, epoch, aggSid, fieldSid, plaintext }) =>
1640
1751
  JSON.parse(call(eng.ex, "crypto_seal_field", { scopeKey, scope, epoch, aggSid, fieldSid, plaintext }, eng.STDERR)).envelope;
1641
1752
  // The client X25519 crypto (slice 6): keygen (device identity), wrap (committer → recipient pubkey),
1642
- // unwrap (reader's device secret → scope key). The secret stays client/host-side, never on chain.
1753
+ // unwrap (reader's device secret → scope key). The secret stays client/adapter-side, never on chain.
1643
1754
  export const cryptoKeygen = (eng) => JSON.parse(call(eng.ex, "crypto_keygen", {}, eng.STDERR));
1644
1755
 
1645
1756
  // AUTHOR DEVICE KEY (warrant-flip, identity_keys.md) — mint an ed25519 signing keypair IN the holon
1646
- // (`author_keygen`); the secret stays client-side (here: the test/host injects it back as `authorSecret`
1757
+ // (`author_keygen`); the secret stays client-side (here: the test/adapter injects it back as `authorSecret`
1647
1758
  // on the author call). Returns { ok, secret, public } (both base64). `author_sign` is the lower-level
1648
1759
  // standalone signer; the author DOOR signs internally from the injected secret, so callers use `author`.
1649
1760
  export const authorKeygen = (eng) => JSON.parse(call(eng.ex, "author_keygen", {}, eng.STDERR));
@@ -1656,39 +1767,95 @@ export const attestationSign = (eng, { secret, asserterWorkspace, object, relati
1656
1767
  export const cryptoWrapKey = (eng, { recipientPub, scopeKey }) => JSON.parse(call(eng.ex, "crypto_wrap_key", { recipientPub, scopeKey }, eng.STDERR));
1657
1768
  export const cryptoUnwrapKey = (eng, { secret, hpkeEpk, ct }) => JSON.parse(call(eng.ex, "crypto_unwrap_key", { secret, hpkeEpk, ct }, eng.STDERR)).scopeKey;
1658
1769
  // THE READ-POSTURE PROBE (slice 5.1): the githolon's own verdict on whether the
1659
- // workspace is private + whether `principal` may read ALL of it — the host relays it
1660
- // to gate the BULK reads (snapshot, git clone, count/sum) it cannot gate per-row-type.
1661
- export const readPosture = (eng, ws, principal = "") => JSON.parse(call(eng.ex, "read_posture", { repoArg: repoArgOf(ws), workspace: ws, principal, branch: BRANCH }, eng.STDERR));
1662
- export const count = (eng, ws, countId, groupKey) => JSON.parse(call(eng.ex, "count", { repoArg: repoArgOf(ws), workspace: ws, countId, groupKey, branch: BRANCH }, eng.STDERR));
1663
- export const sum = (eng, ws, sumId, groupKey) => JSON.parse(call(eng.ex, "sum", { repoArg: repoArgOf(ws), workspace: ws, sumId, groupKey, branch: BRANCH }, eng.STDERR));
1664
- /** The maintained O(1) min/max read (#47 — the slice-4 extremum hand-back made an RPC).
1665
- * `kind` = "min"|"max"; an absent group answers `value: null` (never a fabricated 0). */
1666
- 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));
1770
+ // workspace is private + whether `principal` may read ALL of it — the adapter relays it
1771
+ // to gate the WHOLE-LEDGER reads (snapshot, git clone) it cannot serve partially.
1772
+ 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));
1773
+ // count/sum/extremum are GATED IN THE KERNEL (it knows each tally's `of` type + the read-visibility): the
1774
+ // adapter STAMPS the verified `principal` and relays the holon's verdict it runs NO gate of its own. They ride
1775
+ // the opaque `query` verb (P2): `op` selects the handler; extremum's own `kind` stays its min/max selector.
1776
+ 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));
1777
+ 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));
1778
+ /** The maintained O(1) min/max read (#47). `kind` = "min"|"max"; an absent group answers `value: null`
1779
+ * (never a fabricated 0). Kernel-gated; rides the `query` verb (`op:"extremum"`, `kind` = min/max). */
1780
+ 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));
1781
+ // GIT OFF THE HOT PATH: the KERNEL builds the conformant delta packfile (gix-pack); the adapter is dumb
1782
+ // plumbing (frames pkt-lines + POSTs). Returns the pack bytes (Uint8Array), or null if the kernel
1783
+ // couldn't produce a delta (base not an ancestor / head==base) — the adapter then falls back to a standard
1784
+ // main-ref push (backupToCustody). NO isomorphic-git, NO negotiation: the sole writer knows its delta.
1785
+ export function packDelta(eng, ws, base, head) {
1786
+ try {
1787
+ const r = JSON.parse(call(eng.ex, "pack_delta", { repoArg: repoArgOf(ws), base, head }, eng.STDERR));
1788
+ if (!r || r.ok !== true || typeof r.packB64 !== "string") return null;
1789
+ return Uint8Array.from(atob(r.packB64), (c) => c.charCodeAt(0));
1790
+ } catch { return null; }
1791
+ }
1792
+
1793
+ // THE SERVING-LANE PACK (kernel-native clone/pull, replacing isomorphic-git fetch). `base` empty ⇒ a FULL
1794
+ // pack of all reachable from main (fresh clone); `base` = the client's known head ⇒ the incremental delta.
1795
+ // The kernel resolves main itself. Returns `{ packB64, bytes }` (base64 so it rides the container JSON op).
1796
+ export function servePack(eng, ws, base = "") {
1797
+ let r;
1798
+ try {
1799
+ r = JSON.parse(call(eng.ex, "pack_delta", { repoArg: repoArgOf(ws), workspace: ws, base, branch: BRANCH }, eng.STDERR));
1800
+ } catch (e) {
1801
+ // UP TO DATE: the client's `have` already IS main → nothing to send. A clean empty response, not an error.
1802
+ if (/nothing to pack|head == base/.test(String(e))) return { packB64: "", bytes: 0, upToDate: true };
1803
+ throw e;
1804
+ }
1805
+ if (!r || r.ok !== true || typeof r.packB64 !== "string") throw new Error("servePack: kernel produced no pack");
1806
+ return { packB64: r.packB64, bytes: r.bytes };
1807
+ }
1667
1808
  export const nomosActive = (eng, ws) => qById(eng, ws, `domain-installation:${eng.hashes.nomos}`).length > 0;
1668
1809
  // E2E crypto linkage probe (the encryption arc): run the wrap→unwrap + field seal→open
1669
1810
  // INSIDE the deployable wasm. Pure — no workspace, no chain. Returns {wrapHandoff, fieldAead}.
1670
- export const cryptoSelftest = (eng) => JSON.parse(call(eng.ex, "crypto_selftest", { repoArg: "/work/none" }, eng.STDERR));
1671
1811
 
1672
- /** Re-admit ONE carried intent (bytes) on local main the DLQ-retry primitive. */
1673
- export function applyIntentBytes(eng, ws, bytes) {
1674
- const name = `intent-in-${eng.seq++}.json`;
1812
+ // THE OFFER ADMISSION VERB (offer-kernel migration P1): admit a SEALED intent into `branch`
1813
+ // through the one gate and return the STRUCTURED VERDICT — `{ok:true, outcome:"admitted", head}`
1814
+ // or `{ok:true, outcome:"refused", offerId, verdict:{reason}}`. The adapter's admit lane migrates
1815
+ // onto this off `apply_intent` over the P1 slices; `defer` carries the batch-projection-defer.
1816
+ export function offerIntent(eng, ws, bytes, opts) {
1817
+ const name = `offer-in-${eng.seq++}.json`;
1675
1818
  writeWork(eng, name, bytes);
1676
- return JSON.parse(call(eng.ex, "apply_intent", { repoArg: repoArgOf(ws), workspace: ws, intentFile: `/work/${name}`, branch: BRANCH }, eng.STDERR));
1819
+ const deferProjection = !!(opts && opts.defer);
1820
+ const branch = (opts && opts.branch) || BRANCH;
1821
+ return JSON.parse(call(eng.ex, "offer", { repoArg: repoArgOf(ws), workspace: ws, intentFile: `/work/${name}`, branch, deferProjection }, eng.STDERR));
1822
+ }
1823
+
1824
+ /**
1825
+ * RE-ADMIT sealed bytes through the OFFER verb (offer-kernel P1.2 — the one effectful move), normalized to
1826
+ * the `{ok, head, error}` shape every adapter admit lane consumes. The adapter carries the bytes; the HOLON
1827
+ * adjudicates — `admitted` → the new head; `refused` → the gate's own verdict reason. This is how EVERY adapter
1828
+ * re-admission speaks `offer` (session admit, shard migrate, global replicate, DLQ retry); `apply_intent` is
1829
+ * retired from the adapter (the wasm arm survives behind `legacy-adapter-api` only until P8 deletes it).
1830
+ */
1831
+ export function offerAdmit(eng, ws, bytes, opts) {
1832
+ const v = offerIntent(eng, ws, bytes, opts);
1833
+ return v.outcome === "admitted"
1834
+ ? { ok: true, head: v.head }
1835
+ : { ok: false, error: v.verdict?.reason ?? v.error ?? "the offer was refused" };
1836
+ }
1837
+
1838
+ /** Re-admit ONE carried intent (bytes) on local main — through the ONE effectful verb `offer` (no
1839
+ * apply_intent back door). Normalized to the legacy {ok, head, error} shape every caller (bench, CLI
1840
+ * replay, examples, DLQ retry) consumes. `defer` rides through to offer's batch-projection-defer. */
1841
+ export function applyIntentBytes(eng, ws, bytes, opts) {
1842
+ return offerAdmit(eng, ws, bytes, opts);
1677
1843
  }
1678
1844
 
1679
1845
  // Main's recent intent ids + oids, cached by head (admission idempotence + walk boundary).
1680
1846
  async function mainIntentIds(eng, ws) {
1681
1847
  const gitdir = gitdirOf(ws);
1682
- const head = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null);
1848
+ const head = kgit.resolveRef(eng, gitdir, BRANCH);
1683
1849
  if (!head) return { head: null, ids: new Set(), oids: new Set() };
1684
1850
  const cached = eng.mainIntents.get(ws);
1685
1851
  if (cached && cached.head === head) return cached;
1686
1852
  const ids = new Set(), oids = new Set();
1687
- for (const c of await git.log({ fs: eng.fs, gitdir, ref: BRANCH, depth: 500 })) {
1853
+ // Main is a linear chain of intent commits (the container appends, never merges), so the recent intent
1854
+ // commits ARE the recent main commits — the bounded idempotence window + walk boundary (was git.log depth 500).
1855
+ for (const c of kgit.intentsAbove(eng, gitdir, head, "", 500)) {
1688
1856
  oids.add(c.oid);
1689
1857
  try {
1690
- const { blob } = await git.readBlob({ fs: eng.fs, gitdir, oid: c.oid, filepath: "intent.json" });
1691
- const id = JSON.parse(dec.decode(blob)).id;
1858
+ const id = JSON.parse(dec.decode(bytesFromB64(c.intentB64))).id;
1692
1859
  if (typeof id === "string" && id) ids.add(id);
1693
1860
  } catch {}
1694
1861
  }
@@ -1700,14 +1867,21 @@ async function mainIntentIds(eng, ws) {
1700
1867
  /**
1701
1868
  * EDGE ADMISSION (the one machinery): judge every session/<cid> ref. Structural lane
1702
1869
  * refusals (control-plane intents, undeployed domains) and domain rejections route per
1703
- * Jack's law: legitimate work DEAD-LETTERS (returned to the host to persist — the
1870
+ * Jack's law: legitimate work DEAD-LETTERS (returned to the adapter to persist — the
1704
1871
  * engine has no durable store), obvious attacks drop. Admitted intents merge to main,
1705
1872
  * durably push, the branch is consumed.
1706
1873
  */
1707
- export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFence = null, shardRouting = null, sessionRefs = null } = {}) {
1874
+ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFence = null, shardRouting = null, defer = false } = {}) {
1708
1875
  const refuse = new Set(refuseDomains);
1709
1876
  const routes = shardRouting ? directiveRoutes(eng) : null;
1710
1877
  const gitdir = gitdirOf(ws);
1878
+ // FRESH HEADER COPY PER GIT OP. isomorphic-git's `discover` MUTATES the headers object it's given
1879
+ // (a v2 op stamps `Git-Protocol: version=2` in). Sharing one object across a v2 listServerRefs and a
1880
+ // v1 git.fetch made the fetch send `version=2`, the server answered v2, isomorphic-git parsed it as
1881
+ // v1 → undefined refs → `TypeError: ...reading 'size'` on EVERY session fetch. A fresh copy per call
1882
+ // makes that leak impossible no matter what mutated `ledger.headers` upstream. (Same pattern as the
1883
+ // checkpoint/shape scans' `freshHeaders()`.)
1884
+ const H = () => ({ ...(ledger.headers || {}) });
1711
1885
  // FLAME-GRAPH EDGE TIMINGS — coarse per-stage wall-clock the client stitches into the cross-tier trace.
1712
1886
  const _t0 = Date.now(); let _fetchMs = 0, _sealMs = 0, _intents = 0;
1713
1887
  const _spans = []; let _traceSeq = 0;
@@ -1720,29 +1894,16 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1720
1894
  };
1721
1895
  const sessionRefPrefix = "refs/heads/session/";
1722
1896
  const sessions = {};
1723
- const indexedRefs = sessionRefs && typeof sessionRefs === "object" ? sessionRefs : null;
1724
- const _scanDone = _span(indexedRefs ? "scan session index" : "scan session refs", "cscan", 1);
1725
- if (indexedRefs) {
1726
- const entries = Array.isArray(indexedRefs)
1727
- ? indexedRefs.map((r) => [String(r?.session || r?.cid || r?.client || ""), String(r?.head || r?.oid || "")])
1728
- : Object.entries(indexedRefs).map(([cid, head]) => [cid, String(head || "")]);
1729
- for (const [cid, head] of entries) {
1730
- if (/^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(cid) && /^[0-9a-f]{40}$/.test(head)) sessions[cid] = head;
1731
- }
1732
- } else {
1733
- const refs = await git.listServerRefs({
1734
- http,
1735
- url: ledger.remote,
1736
- headers: ledger.headers,
1737
- prefix: sessionRefPrefix,
1738
- protocolVersion: 2,
1739
- });
1740
- for (const r of refs || []) {
1741
- const ref = typeof r?.ref === "string" ? r.ref : "";
1742
- const oid = typeof r?.oid === "string" ? r.oid : "";
1743
- if (!ref.startsWith(sessionRefPrefix) || !oid) continue;
1744
- sessions[ref.slice(sessionRefPrefix.length)] = oid;
1745
- }
1897
+ const _scanDone = _span("scan session refs", "cscan", 1);
1898
+ // CUSTODY IS THE ONLY SOURCE. The adapter keeps NO session index there is no adapter-held head to
1899
+ // diverge from the ledger. Every admit lists the workspace's own session refs (listServerRefs)
1900
+ // and admits exactly what is durably there. (The earlier adapter "session-offer index" let a
1901
+ // client's CLAIMED head outlive the push that never durably landed; admit then chased an oid
1902
+ // custody never had NotFoundError on every tick → the client's work stranded forever. The
1903
+ // cure is not a better cache — it is no cache: the adapter holds no authority, the ledger does.)
1904
+ //
1905
+ for (const [ref, oid] of Object.entries(await custodyLsRefs(ledger, sessionRefPrefix))) {
1906
+ if (ref.startsWith(sessionRefPrefix) && oid) sessions[ref.slice(sessionRefPrefix.length)] = oid;
1746
1907
  }
1747
1908
  _scanDone({ sessions: Object.keys(sessions).length });
1748
1909
  const _scanMs = Date.now() - _t0;
@@ -1753,16 +1914,22 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1753
1914
  const consumed = []; let totalAdmitted = 0;
1754
1915
  // §5.4 (slice 4): on the COORDINATOR of a taxonomy that declares `.global(...)`
1755
1916
  // reference data, every admitted intent touching a global aggregate is surfaced
1756
- // to the HOST, which replicates the SAME sealed bytes into each shard chain.
1917
+ // to the ADAPTER, which replicates the SAME sealed bytes into each shard chain.
1757
1918
  const globalsDeclared = shardRouting && !shardRouting.selfLabel ? globalAggregates(eng) : null;
1758
1919
  // ── §5.2 SUMMARY CAPTURE (slice 3): on a SHARD (selfLabel) whose law declares
1759
1920
  // estate-scoped reads, derive each admitted intent's per-(read, group) deltas with
1760
1921
  // the shard's own projection as the oracle (tallies sandwiched around the fold —
1761
- // O(touched)); the HOST authors the coalesced `nomosPropagateSummary` Order into
1922
+ // O(touched)); the ADAPTER authors the coalesced `nomosPropagateSummary` Order into
1762
1923
  // the coordinator afterwards. The coordinator's gate re-verifies everything.
1763
1924
  const summaryCtx = shardRouting && shardRouting.selfLabel && scopedReads(eng).length > 0
1764
1925
  ? { reads: scopedReads(eng), prevs: new Map(), values: new Map(), events: [], rowsDelta: 0, rederived: new Set(), rowsByHome: new Map(), homeTags: new Map(), homeKeySet: new Set() }
1765
1926
  : null;
1927
+ // BATCH ADMIT (the flame fix): defer the per-intent projection catch-up + frontier write
1928
+ // to ONE flush after the whole cycle — UNLESS a per-intent projection read is required
1929
+ // mid-loop: §5.2 summary capture (reads pre/post tallies around each fold) or §5.4 global
1930
+ // surfacing (qById per admitted intent). Those keep the synchronous per-intent path. The
1931
+ // common case (no sharding) gets the batch: N×(SQLite catch-up + checkpoint write) → 1.
1932
+ const canDeferProjection = !summaryCtx && !(globalsDeclared && globalsDeclared.size > 0);
1766
1933
  if (summaryCtx) {
1767
1934
  // SLICE 8 — the per-home attribution table, off the shard's OWN receipts
1768
1935
  // (law-state: which homes this chain owns). A row id whose route-tag slot
@@ -1777,58 +1944,54 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1777
1944
  }
1778
1945
  } catch (e) { log("home-tag-table-error", { ws, error: String(e).slice(0, 200) }); }
1779
1946
  }
1780
- const headBefore = summaryCtx ? await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null) : null;
1947
+ const headBefore = summaryCtx ? kgit.resolveRef(eng, gitdir, BRANCH) : null;
1781
1948
  for (const [cid, head] of Object.entries(sessions)) {
1782
1949
  const ref = `refs/heads/session/${cid}`;
1783
1950
  const shortCid = cid.length > 14 ? `${cid.slice(0, 14)}...` : cid;
1784
1951
  const _sessionDone = _span(`session ${shortCid}`, "csession", 1, { client: cid });
1785
1952
  try {
1786
- await git.addRemote({ fs: eng.fs, gitdir, remote: "origin", url: ledger.remote, force: true });
1787
1953
  const _fetchDone = _span(`fetch ${shortCid}`, "cfetch", 2, { client: cid });
1788
1954
  const _f = Date.now();
1789
1955
  try {
1790
- // THIN-FETCH (the 40s→0.5s fix): a `singleBranch` fetch sets isomorphic-git's have-set to
1791
- // [the fetched ref] ONLY. The edge holds no local session ref yet ⇒ it offers ZERO haves the
1792
- // server re-ships the ENTIRE history (O(chain): measured ~10MB/40s for a 1-commit delta).
1793
- // Pre-seed the local session ref to OUR main head so the negotiation offers main as a have and the
1794
- // server sends only the delta above the common ancestor (~1KB/0.5s). main is append-only, so the
1795
- // client's fork point is always an ancestor of our head — the thin pack is always sound.
1796
- const localMain = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null);
1797
- if (localMain) { try { await git.writeRef({ fs: eng.fs, gitdir, ref, value: localMain, force: true }); } catch {} }
1798
- await git.fetch({ fs: eng.fs, http, gitdir, remote: "origin", ref, singleBranch: true, headers: ledger.headers });
1956
+ // THIN-FETCH (the 40s→0.5s fix): pass our main head as the `have` so the server sends ONLY the delta
1957
+ // above the common ancestor (the kernel's apply_pack ingests it). main is append-only, so the client's
1958
+ // fork point is always an ancestor of our head — the thin pack is sound. A failed thin fetch is an
1959
+ // optimization miss, never load-bearing: fall back to a clean full fetch so work is never stranded.
1960
+ const localMain = kgit.resolveRef(eng, gitdir, BRANCH);
1961
+ try {
1962
+ await custodyFetch(eng, ws, ledger, { remoteRef: ref, have: localMain });
1963
+ } catch (thinErr) {
1964
+ log("thin-fetch-fallback", { ws, cid, error: String(thinErr && thinErr.message || thinErr).slice(0, 160) });
1965
+ await custodyFetch(eng, ws, ledger, { remoteRef: ref, have: null });
1966
+ }
1799
1967
  } finally {
1800
1968
  _fetchMs += Date.now() - _f;
1801
1969
  _fetchDone();
1802
1970
  }
1803
1971
  const _walkDone = _span(`walk ${shortCid}`, "cwalk", 2, { client: cid });
1804
1972
  const main = await mainIntentIds(eng, ws);
1973
+ // The session's NEW intents: walk the session tip newest→oldest, stop at the first commit already on
1974
+ // main (the fork point), reverse to oldest→newest. intentsAbove returns {oid, intentB64} so the gate
1975
+ // loop reads the intent straight off the walk (no per-oid readBlob).
1976
+ const recent = kgit.intentsAbove(eng, gitdir, head, "", 100);
1805
1977
  const chain = [];
1806
- for (const c of await git.log({ fs: eng.fs, gitdir, ref: head, depth: 100 })) {
1807
- if (main.oids.has(c.oid)) break;
1808
- chain.push(c.oid);
1809
- }
1978
+ for (let i = recent.length - 1; i >= 0; i--) { if (main.oids.has(recent[i].oid)) break; chain.push(recent[i]); }
1810
1979
  chain.reverse(); // oldest → newest
1811
1980
  _walkDone({ intents: chain.length });
1812
1981
  const admitted = [], rejected = [], skipped = [];
1813
1982
  const _gateDone = _span(`gate+fold ${shortCid}`, "cgate", 2, { client: cid, intents: chain.length });
1814
- for (const oid of chain) {
1983
+ for (const c of chain) {
1984
+ const oid = c.oid;
1815
1985
  try {
1816
- const { blob } = await git.readBlob({ fs: eng.fs, gitdir, oid, filepath: "intent.json" });
1986
+ const blob = bytesFromB64(c.intentB64);
1817
1987
  let dom = null, intentId = null, dirId = null;
1818
1988
  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 {}
1819
- // WRITE-AUTHORITY: law (control-plane) intents never ride the session lane
1820
- // nor do HOST-FENCED domains (e.g. `workspaces` on root: governance directives
1821
- // enter ONLY through the admin-gated lane, never open session pushes).
1822
- if (!dom || dom === "nomos" || dom === "bootstrap" || refuse.has(dom)) {
1823
- 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` });
1824
- continue;
1825
- }
1826
- // LAW-FENCE part 2: the domain must be DEPLOYED to this workspace. An
1827
- // undeployed domain's write is the canonical LEGITIMATE JAM — dead-letter.
1828
- if (!knownDomains(eng).has(dom)) {
1829
- const error = `domain '${dom}' is not deployed to this workspace — deploy its law (POST /v1/workspaces/${ws}/domains with the workspace secret), then POST /v1/workspaces/${ws}/dead-letters/retry`;
1830
- rejected.push({ oid: oid.slice(0, 10), error, deadLettered: true });
1831
- deadLetters.push({ intentId, oid: oid.slice(0, 10), session: cid, error, blob, domain: dom, directiveId: dirId });
1989
+ // THE GATE DECIDES (offer-kernel doctrine): no adapter domain-string filter for control-plane
1990
+ // an install/governance offer rides the SAME lane and the kernel gate refuses an author lacking
1991
+ // the declared relation (a TYPED gate refusal, not a adapter verdict). Only `!dom` (malformed) and
1992
+ // the explicit `refuse` fence (root's `workspaces` governance lane) stay structural, not authority.
1993
+ if (!dom || refuse.has(dom)) {
1994
+ 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` });
1832
1995
  continue;
1833
1996
  }
1834
1997
  // THE RECEIPT FENCE (capability_marketplace.md slice 1.5 — the ratified
@@ -1852,10 +2015,10 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1852
2015
  if (routes && dom && dirId) {
1853
2016
  // THE SEAL PEN IS THE BAILIFF'S (slice 5): `nomosSealHome` (strikes the
1854
2017
  // migrated history out of a source's fold) and `nomosSealHandoff` (flips
1855
- // the map) are HOST-orchestrated legs of the §6 move — a session-lane
2018
+ // the map) are ADAPTER-orchestrated legs of the §6 move — a session-lane
1856
2019
  // offer of either is an attack on the move lane, refused and DROPPED.
1857
2020
  if (dirId === "nomosSealHome" || dirId === "nomosSealHandoff") {
1858
- 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)` });
2021
+ 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)` });
1859
2022
  continue;
1860
2023
  }
1861
2024
  const route = routes.get(`${dom} ${dirId}`);
@@ -1901,13 +2064,21 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1901
2064
  continue;
1902
2065
  }
1903
2066
  const cap = summaryCtx ? beginSummaryCapture(eng, ws, summaryCtx, blob) : null;
1904
- const res = applyIntentBytes(eng, ws, blob);
2067
+ // OFFER-KERNEL P1.2: edge re-admission runs through the `offer` verb (the doctrine's one
2068
+ // effectful move) over the SAME admit_core gate. The structured verdict is normalized
2069
+ // back to the {ok, head, error} the outcome handling below consumes; the refused/parked
2070
+ // ref-custody (replacing the JS DLQ) is the next sub-slice — for now refusals still
2071
+ // dead-letter exactly as before (the verdict reason IS the gate's refusal reason).
2072
+ const verdict = offerIntent(eng, ws, blob, { defer: canDeferProjection });
2073
+ const res = verdict.outcome === "admitted"
2074
+ ? { ok: true, head: verdict.head }
2075
+ : { ok: false, error: verdict.verdict?.reason ?? verdict.error ?? "the offer was refused" };
1905
2076
  if (res.ok) {
1906
2077
  main.ids.add(intentId); main.oids.add(res.head); main.head = res.head;
1907
2078
  admitted.push({ oid: oid.slice(0, 10), head: res.head });
1908
2079
  if (cap) finishSummaryCapture(eng, ws, summaryCtx, cap, res.head, intentId);
1909
2080
  // AN ADMITTED PLACEMENT (birth<Type> on the coordinator): surfaced to the
1910
- // HOST so it can author the assignment RECEIPT into the shard's own chain
2081
+ // ADAPTER so it can author the assignment RECEIPT into the shard's own chain
1911
2082
  // (sharding §3 — the receipt leg).
1912
2083
  if (shardRouting && dirId) {
1913
2084
  const placement = placementDirectives(eng).get(dirId);
@@ -1920,9 +2091,9 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1920
2091
  }
1921
2092
  }
1922
2093
  // AN ADMITTED SPLIT (nomosSplitShard on the coordinator, slice 5):
1923
- // surfaced to the HOST so it can run the §6 migration legs (re-admit
2094
+ // surfaced to the ADAPTER so it can run the §6 migration legs (re-admit
1924
2095
  // on the target, seal the source, close/open the subtotals, flip the
1925
- // map) — a session-authored split COMPLETES like a host-authored one.
2096
+ // map) — a session-authored split COMPLETES like a adapter-authored one.
1926
2097
  if (dirId === "nomosSplitShard" && !shardRouting.selfLabel) {
1927
2098
  let p = null;
1928
2099
  try { p = JSON.parse(dec.decode(blob)).payload?.payload ?? null; } catch {}
@@ -1932,7 +2103,7 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1932
2103
  }
1933
2104
  }
1934
2105
  // AN ADMITTED GLOBAL WRITE (§5.4, slice 4 — coordinator only): the sealed
1935
- // bytes are surfaced to the HOST, which re-admits them into every shard
2106
+ // bytes are surfaced to the ADAPTER, which re-admits them into every shard
1936
2107
  // chain (the reverse lane — replicated reference data). O(events) per
1937
2108
  // admitted intent, and only when the law declares `.global(...)`.
1938
2109
  if (globalsDeclared && globalsDeclared.size > 0) {
@@ -1980,12 +2151,34 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1980
2151
  _sessionDone();
1981
2152
  }
1982
2153
  }
2154
+ // THE DEFERRED-PROJECTION FLUSH (batch admit): the per-intent applies skipped the SQLite
2155
+ // catch-up + frontier write; flush ONCE here — refresh_projection folds every new commit in
2156
+ // a single incremental catch-up and walks the deferred heads to advance the verified frontier.
2157
+ if (canDeferProjection && totalAdmitted) {
2158
+ const _projDone = _span("projection flush (batch)", "cproject", 1, { admitted: totalAdmitted });
2159
+ try { refreshProjection(eng, ws); } catch (e) { log("admit-projection-flush-failed", { ws, error: String(e).slice(0, 200) }); }
2160
+ finally { _projDone(); }
2161
+ }
2162
+ // OFFER-KERNEL P1.2b: materialize this cycle's refusals as git-custody facts (refs/heads/refused/*),
2163
+ // pushed inline — BEFORE the main seal, which unmounts on failure (a refusal fact must not be lost to
2164
+ // an unrelated main-push failure). The holon's verdict recorded as a HOLON FACT that syncs + survives
2165
+ // eviction; the adapter's ctx.storage DLQ is retired in favour of these.
2166
+ if (deadLetters.length) { try { await materializeRefused(eng, ws, ledger, deadLetters); } catch (e) { log("refused-materialize-failed", { ws, error: String(e).slice(0, 200) }); } }
1983
2167
  // THE BATCH SEAL — one receive-pack for the whole admit cycle. A failed push unmounts (engine.unmount drops
1984
2168
  // the ws fs); we then SKIP the branch deletes so every offered intent re-admits next cycle (idempotent by
1985
2169
  // id). On success, delete the consumed session branches (best-effort cleanup; a stale branch is a harmless
1986
2170
  // re-fetch). Single-client workspaces were already one push/cycle; this collapses collaborative bursts.
2171
+ // THE BAILIFF SEAM: when the ADAPTER asked us to `defer` (the open session admit lane on a
2172
+ // non-coordinator workspace), we DO NOT push or delete consumed branches inline — the fold
2173
+ // is already on resident main (truth), and the adapter backs it up to custody off the critical
2174
+ // path (engine.backupToCustody, debounced + coalesced). This is sound because the SESSION
2175
+ // BRANCHES are themselves durable in custody (the durable inbox) and admit is idempotent by
2176
+ // intent id: a crash before backup just re-derives the SAME sealed main from the same offers.
2177
+ // Coordinator/sharded admits (any §5.2/§5.4/placement/split follow-up) stay durable-before-ack.
2178
+ const hasCoordination = !!(summaryCtx || placements.length || splits.length || globalsOut.length);
2179
+ const deferDurability = defer && !hasCoordination;
1987
2180
  let _sealed = true;
1988
- if (totalAdmitted) {
2181
+ if (totalAdmitted && !deferDurability) {
1989
2182
  const _sealDone = _span(`seal (batch)`, "cseal", 1, { sessions: consumed.length, admitted: totalAdmitted });
1990
2183
  const _s = Date.now();
1991
2184
  try {
@@ -1998,14 +2191,14 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
1998
2191
  _sealDone();
1999
2192
  }
2000
2193
  }
2001
- if (_sealed) {
2194
+ if (_sealed && !deferDurability) {
2002
2195
  for (const { ref } of consumed) {
2003
- try { await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref: BRANCH, remoteRef: ref, delete: true, headers: ledger.headers })); } catch {}
2196
+ try { await custodyPush(eng, ws, ledger, { remoteRef: ref, del: true }); } catch {}
2004
2197
  }
2005
2198
  }
2006
- const newMain = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null);
2199
+ const newMain = kgit.resolveRef(eng, gitdir, BRANCH);
2007
2200
  log("admit", { ws, sessions: results.length, main: newMain });
2008
- // The coalesced §5.2 delta of this batch (null when no scoped read moved): the HOST
2201
+ // The coalesced §5.2 delta of this batch (null when no scoped read moved): the ADAPTER
2009
2202
  // closes it against the coordinator's recorded frontier and authors the Order.
2010
2203
  const summary = summaryCtx ? summaryOfCapture(summaryCtx, headBefore, newMain) : null;
2011
2204
  const _total = Date.now() - _t0;
@@ -2017,9 +2210,52 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
2017
2210
  ...(placements.length ? { placements } : {}),
2018
2211
  ...(splits.length ? { splits } : {}),
2019
2212
  ...(globalsOut.length ? { globals: globalsOut } : {}),
2213
+ // The adapter's backup lane consumes these: `deferred` ⇒ resident main carries totalAdmitted
2214
+ // un-backed-up commits and `consumed` are the session branches to delete AFTER the push lands.
2215
+ ...(deferDurability && totalAdmitted ? { deferred: true, consumed, admitted: totalAdmitted } : {}),
2020
2216
  };
2021
2217
  }
2022
2218
 
2219
+ // WRITE-BEHIND CUSTODY MIRROR (git off the hot path). Push a CAPTURED head snapshot to custody main —
2220
+ // NOT the live `main` ref (re-resolving it would chase a moving target under concurrent folds, the bug
2221
+ // that stormed the old debounced backup). The container is the SOLE writer of main, so custody is a
2222
+ // pure FF mirror: no pull-back, no reconciliation. Read-only on eng.fs except a transient backup ref
2223
+ // (a different key from anything a fold touches; the head's objects are immutable). NEVER unmounts on
2224
+ // failure — the resident holon stays truth and the client can re-offer (worst case: the mirror lags).
2225
+ export async function backupHead(eng, ws, ledger, head) {
2226
+ if (!eng.mounted.has(ws)) return { ok: false, error: "not mounted" };
2227
+ if (!head) return { ok: false, error: "no head" };
2228
+ try {
2229
+ // Push EXACTLY the captured `head` (not live main, which moves under concurrent folds) to custody main —
2230
+ // the kernel builds the delta remoteOld..head; no local tmp ref needed (custodyPush names the oid).
2231
+ await custodyPush(eng, ws, ledger, { remoteRef: "refs/heads/main", newOid: head });
2232
+ return { ok: true, head };
2233
+ } catch (e) {
2234
+ return { ok: false, error: String((e && e.message) || e).slice(0, 200), head };
2235
+ }
2236
+ }
2237
+
2238
+ // THE BAILIFF'S BACKUP — push resident main to custody when quiet, NEVER on a write's critical
2239
+ // path and NEVER destructive. Unlike commitAndPush (durability-before-ack: unmounts on failure),
2240
+ // a failed backup leaves the resident holon mounted and the session branches intact, so the adapter
2241
+ // just retries on the next quiet tick. Append-only main means an off-lock push of an older head
2242
+ // concurrent with a fresh fold is always a sound fast-forward subset (the newer head backs up next).
2243
+ export async function backupToCustody(eng, ws, ledger, { consumed = [] } = {}) {
2244
+ if (!eng.mounted.has(ws)) return { ok: false, error: "not mounted" };
2245
+ const gitdir = gitdirOf(ws);
2246
+ let head = null;
2247
+ head = kgit.resolveRef(eng, gitdir, BRANCH); if (!head) return { ok: false, error: "no local main" };
2248
+ try {
2249
+ await custodyPush(eng, ws, ledger, { remoteRef: "refs/heads/main" });
2250
+ } catch (e) {
2251
+ return { ok: false, error: String(e).slice(0, 200), head };
2252
+ }
2253
+ for (const { ref } of consumed) {
2254
+ try { await custodyPush(eng, ws, ledger, { remoteRef: ref, del: true }); } catch {}
2255
+ }
2256
+ return { ok: true, head };
2257
+ }
2258
+
2023
2259
  /**
2024
2260
  * VERIFY AN UPLOADED LEDGER — "the githolon executes and thus validates it". Fresh-mounts
2025
2261
  * the workspace from custody (never a cached mount — the bytes under judgment are the
@@ -2027,7 +2263,7 @@ export async function admitAll(eng, ws, ledger, { refuseDomains = [], receiptFen
2027
2263
  * genesis (contiguity → per-intent re-admission with the engine re-run; byte-preserving,
2028
2264
  * read-only — see wasm_git_holon.rs `verify_chain`). Returns the wasm's verdict object:
2029
2265
  * `{valid:true, head, intents, plansRerun, controllerHash, installedDomains}` or
2030
- * `{valid:false, check, error, atIndex?, atCommit?}`. The HOST decides what a refusal
2266
+ * `{valid:false, check, error, atIndex?, atCommit?}`. The ADAPTER decides what a refusal
2031
2267
  * does (archive + refuse to serve); this op never mutates the ledger.
2032
2268
  */
2033
2269
  export async function verifyChain(eng, ws, ledger) {
@@ -2037,10 +2273,12 @@ export async function verifyChain(eng, ws, ledger) {
2037
2273
  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)" };
2038
2274
  }
2039
2275
  try {
2040
- return JSON.parse(call(eng.ex, "verify_chain", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH }, eng.STDERR));
2276
+ // P4: chain verification rides the `query` verb (op:"verifyChain") a read-only verdict, not a
2277
+ // top-level adapter verb. The adapter still relays the verdict (archive-on-refuse, bless-on-valid) = custody.
2278
+ return JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "verifyChain" }), branch: BRANCH }, eng.STDERR));
2041
2279
  } finally {
2042
2280
  // The verified (or refused) chain must never linger as a servable mount: serving
2043
- // re-restores from custody AFTER the host blesses the verdict.
2281
+ // re-restores from custody AFTER the adapter blesses the verdict.
2044
2282
  unmount(eng, ws);
2045
2283
  }
2046
2284
  }
@@ -2053,7 +2291,8 @@ export async function verifyChain(eng, ws, ledger) {
2053
2291
  * the LEDGER's bytes, never a hot mount.)
2054
2292
  */
2055
2293
  export function verifyChainLocal(eng, ws) {
2056
- return JSON.parse(call(eng.ex, "verify_chain", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH }, eng.STDERR));
2294
+ // P4: rides the `query` verb (op:"verifyChain") a read-only verdict.
2295
+ return JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "verifyChain" }), branch: BRANCH }, eng.STDERR));
2057
2296
  }
2058
2297
 
2059
2298
  /**
@@ -2069,14 +2308,169 @@ export async function archiveRefusedMain(eng, ws, ledger, label) {
2069
2308
  if (!m.restored) { unmount(eng, ws); return { archived: false, error: "no main to archive" }; }
2070
2309
  const gitdir = gitdirOf(ws);
2071
2310
  try {
2072
- 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 }));
2073
- await pushVia(ledger, () => git.push({ fs: eng.fs, http, gitdir, url: ledger.remote, ref: BRANCH, remoteRef: `refs/heads/${BRANCH}`, delete: true, headers: ledger.headers }));
2311
+ // main's objects are already in custody a pure pointer (empty pack) to refused/<label>, then drop main.
2312
+ await custodyPush(eng, ws, ledger, { localRef: "refs/heads/main", remoteRef: `refs/heads/refused/${label}`, pointer: true });
2313
+ await custodyPush(eng, ws, ledger, { remoteRef: "refs/heads/main", del: true });
2074
2314
  return { archived: true, ref: `refs/heads/refused/${label}` };
2075
2315
  } finally {
2076
2316
  unmount(eng, ws);
2077
2317
  }
2078
2318
  }
2079
2319
 
2320
+ /** A refused-offer id sanitized to a git ref path component (the offerId is the intent id). */
2321
+ const refusedRefName = (offerId) => `refs/heads/refused/${String(offerId || "unknown").replace(/[^A-Za-z0-9._-]/g, "_")}`;
2322
+
2323
+ /**
2324
+ * OFFER-KERNEL P1.2b: materialize a REFUSED offer as a HOLON FACT in git custody — a commit on
2325
+ * `refs/heads/refused/<offerId>` carrying { intent.json, verdict.json } (the holon's verdict, NOT
2326
+ * adapter mail). A quarantine branch is OUTSIDE the verified ledger (commit-closure doesn't bind it),
2327
+ * so the adapter writes it as CUSTODY — the DECISION was the holon's (`offer` → `refused`), the adapter
2328
+ * only records it. The ref SYNCS (a client pulls refs/heads/refused/* to see its dead-lettered
2329
+ * work) and survives DO eviction. Deterministic (CKPT_AUTHOR timestamp 0) ⇒ idempotent: re-refusing
2330
+ * the same intent rewrites the same commit. `dl` is the admitAll dead-letter
2331
+ * `{ intentId, oid, error, blob, domain, directiveId }`.
2332
+ */
2333
+ export async function writeRefusedRef(eng, ws, dl) {
2334
+ const gitdir = gitdirOf(ws);
2335
+ const blob = dl.blob instanceof Uint8Array ? dl.blob : enc.encode(String(dl.blob || ""));
2336
+ const verdict = enc.encode(JSON.stringify({
2337
+ offerId: dl.intentId || null, oid: dl.oid || null,
2338
+ domain: dl.domain || null, directiveId: dl.directiveId || null,
2339
+ verdict: { reason: String(dl.error || "refused") },
2340
+ }));
2341
+ const ref = refusedRefName(dl.intentId || dl.oid);
2342
+ kgit.writeMetaCommit(eng, gitdir, {
2343
+ parents: [], files: [{ path: "intent.json", bytes: blob }, { path: "verdict.json", bytes: verdict }],
2344
+ message: `refused: ${dl.domain || "?"}/${dl.directiveId || "?"} — ${String(dl.error || "").slice(0, 120)}`,
2345
+ timeSecs: 0, ref,
2346
+ });
2347
+ return ref;
2348
+ }
2349
+
2350
+ /** Read the refused-offer facts back: refs/heads/refused/* → [{ ref, offerId, domain, directiveId,
2351
+ * verdict, intentB64 }]. The git-custody source for GET /dead-letters + the retry lane (P1.2b.2). */
2352
+ export async function listRefusedRefs(eng, ws) {
2353
+ const gitdir = gitdirOf(ws);
2354
+ const refs = kgit.listRefs(eng, gitdir, "refs/heads/refused/"); // [{ ref, oid }] — oid in hand, no resolveRef
2355
+ const out = [];
2356
+ for (const { ref, oid } of refs) {
2357
+ try {
2358
+ const vb = kgit.readBlob(eng, gitdir, oid, "verdict.json");
2359
+ const ib = kgit.readBlob(eng, gitdir, oid, "intent.json");
2360
+ const v = JSON.parse(dec.decode(vb));
2361
+ out.push({ ref, ...v, intentB64: b64Bytes(ib) });
2362
+ } catch { /* a partial/corrupt refused ref is skipped (the reader is best-effort) */ }
2363
+ }
2364
+ return out;
2365
+ }
2366
+
2367
+ /**
2368
+ * OFFER-KERNEL P1.2b: turn an admit cycle's refusals into git-custody facts. Each dead-letter is
2369
+ * written as `refs/heads/refused/<offerId>` ({intent.json, verdict.json}) and PUSHED to custody so
2370
+ * it SYNCS to clients + survives DO eviction — replacing the adapter-mail DLQ (`ctx.storage`). Called
2371
+ * inline from the admit lanes: refusals are EXCEPTIONAL (off the admit hot path), so the inline push
2372
+ * is cheap and makes the fact durable even on the deferred-seal lane (where main's push is debounced).
2373
+ * Best-effort per ref (a failed write/push is logged, never aborts the cycle — the session branch is
2374
+ * still durable, so the refusal re-materializes next cycle, idempotently). Returns the pushed refs.
2375
+ */
2376
+ export async function materializeRefused(eng, ws, ledger, deadLetters) {
2377
+ const gitdir = gitdirOf(ws);
2378
+ const refs = [];
2379
+ for (const dl of deadLetters || []) {
2380
+ try {
2381
+ const ref = await writeRefusedRef(eng, ws, dl);
2382
+ await custodyPush(eng, ws, ledger, { remoteRef: ref }); // new refused commit → ships its objects
2383
+ refs.push(ref);
2384
+ } catch (e) {
2385
+ log("refused-ref-materialize-failed", { ws, intentId: dl?.intentId, error: String(e).slice(0, 200) });
2386
+ }
2387
+ }
2388
+ return refs;
2389
+ }
2390
+
2391
+ /** Drop a refused-offer fact (retry succeeded, or an explicit discard): delete the local ref + push
2392
+ * the delete to custody so the quarantine branch is gone everywhere. Best-effort. */
2393
+ export async function deleteRefusedRef(eng, ws, ledger, offerId) {
2394
+ const gitdir = gitdirOf(ws);
2395
+ const ref = refusedRefName(offerId);
2396
+ try { kgit.deleteRef(eng, gitdir, ref); } catch {}
2397
+ try { await custodyPush(eng, ws, ledger, { remoteRef: ref, del: true }); }
2398
+ catch (e) { log("refused-ref-delete-push-failed", { ws, ref, error: String(e).slice(0, 200) }); }
2399
+ return ref;
2400
+ }
2401
+
2402
+ // ── THE OUTBOX (capability_offers_https.md — offer-kernel P6, slice 1) ─────────────────────
2403
+ // A capability is a holon at a URL; an obligation is offered to it as an HTTPS POST. The kernel
2404
+ // already surfaces the orders (reactorScan = obligations at status:'requested'); the capability's
2405
+ // endpoint rides the law (declaredCapabilities). The BAILIFF joins them and POSTs — pure transport,
2406
+ // it decides nothing (the holon decided by admitting the obligation; the law named the endpoint).
2407
+ // A delivered order is recorded as refs/heads/outbox/<requestId> (the delivery marker + custody;
2408
+ // idempotent — a delivered order is never re-POSTed). The receipt lane (slice 2) closes the
2409
+ // obligation; the credential lane (slice 3) forwards the caller's packet.
2410
+
2411
+ /** A requestId sanitized to a git ref path component. */
2412
+ const outboxRefName = (requestId) => `refs/heads/outbox/${String(requestId || "unknown").replace(/[^A-Za-z0-9._-]/g, "_")}`;
2413
+
2414
+ /** Has this order already been delivered? (the outbox ref is the delivered marker — idempotency). */
2415
+ export async function outboxRefExists(eng, ws, requestId) {
2416
+ return !!kgit.resolveRef(eng, gitdirOf(ws), outboxRefName(requestId));
2417
+ }
2418
+
2419
+ /** Record a delivered order as a git-custody fact: a commit on refs/heads/outbox/<requestId>
2420
+ * carrying { order.json, delivery.json }. Deterministic CKPT_AUTHOR ⇒ idempotent. */
2421
+ export async function writeOutboxRef(eng, ws, rec) {
2422
+ const gitdir = gitdirOf(ws);
2423
+ const orderBlob = enc.encode(JSON.stringify(rec.order ?? {}));
2424
+ const delivery = enc.encode(JSON.stringify({
2425
+ requestId: rec.requestId || null, capability: rec.capability || null, url: rec.url || null,
2426
+ status: rec.status || "delivered", httpStatus: rec.httpStatus ?? null,
2427
+ }));
2428
+ const ref = outboxRefName(rec.requestId);
2429
+ kgit.writeMetaCommit(eng, gitdir, {
2430
+ parents: [], files: [{ path: "order.json", bytes: orderBlob }, { path: "delivery.json", bytes: delivery }],
2431
+ message: `outbox: ${rec.capability || "?"} → ${String(rec.url || "?").slice(0, 80)}`,
2432
+ timeSecs: 0, ref,
2433
+ });
2434
+ return ref;
2435
+ }
2436
+
2437
+ /**
2438
+ * THE BAILIFF OUTBOX LOOP: scan the kernel's requested orders, join each to its declared
2439
+ * capability endpoint, and POST the obligation as an HTTPS offer. Best-effort + idempotent
2440
+ * (a delivered order has an outbox ref and is skipped). Runs on the engine plane (DO edge OR
2441
+ * heavy container — both have `fetch` + the ledger). Returns `{delivered:[requestId], skipped, errors}`.
2442
+ * The adapter decides nothing: the obligation (a holon admission) is the emit decision; the endpoint
2443
+ * is law. NO receipt handling yet (slice 2) — POSTing is delivery, the obligation stays 'requested'.
2444
+ */
2445
+ export async function emitOutbox(eng, ws, ledger) {
2446
+ const orders = (() => { try { return reactorScan(eng, ws); } catch { return []; } })();
2447
+ if (!Array.isArray(orders) || orders.length === 0) return { delivered: [], skipped: 0, errors: 0 };
2448
+ const byType = new Map();
2449
+ for (const c of declaredCapabilities(eng, ws)) if (c && c.aggregateId) byType.set(c.aggregateId, c);
2450
+ const gitdir = gitdirOf(ws);
2451
+ const delivered = []; let skipped = 0, errors = 0;
2452
+ for (const order of orders) {
2453
+ const cap = byType.get(order.taskType);
2454
+ if (!cap || !cap.endpoint) { skipped++; continue; } // no destination — rests at 'requested'
2455
+ if (await outboxRefExists(eng, ws, order.requestId)) { skipped++; continue; } // already delivered
2456
+ try {
2457
+ const res = await fetch(cap.endpoint, {
2458
+ method: "POST",
2459
+ headers: { "content-type": "application/json", "x-nomos-capability": cap.capability, "x-nomos-workspace": ws },
2460
+ body: JSON.stringify({ capability: cap.capability, requestId: order.requestId, taskType: order.taskType, params: order.params, scheduledAt: order.scheduledAt, deadlineAt: order.deadlineAt }),
2461
+ signal: AbortSignal.timeout(30000),
2462
+ });
2463
+ if (!res.ok) { errors++; log("outbox-deliver-non2xx", { ws, requestId: order.requestId, url: cap.endpoint, status: res.status }); continue; }
2464
+ const ref = await writeOutboxRef(eng, ws, { requestId: order.requestId, capability: cap.capability, url: cap.endpoint, order, status: "delivered", httpStatus: res.status });
2465
+ try { await custodyPush(eng, ws, ledger, { remoteRef: ref }); }
2466
+ catch (e) { log("outbox-ref-push-failed", { ws, ref, error: String(e).slice(0, 200) }); }
2467
+ delivered.push(order.requestId);
2468
+ } catch (e) { errors++; log("outbox-deliver-failed", { ws, requestId: order.requestId, url: cap.endpoint, error: String(e).slice(0, 200) }); }
2469
+ }
2470
+ if (delivered.length || errors) log("outbox", { ws, delivered: delivered.length, skipped, errors });
2471
+ return { delivered, skipped, errors };
2472
+ }
2473
+
2080
2474
  /** Genesis boot: bootstrap → the nomos controller; idempotent when already Active. */
2081
2475
  export async function boot(eng, ws, ledger) {
2082
2476
  const m = await mountWorkspace(eng, ws, ledger);
@@ -2088,39 +2482,15 @@ export async function boot(eng, ws, ledger) {
2088
2482
  return { status: "created", head: result.head, controllerHash: eng.hashes.nomos, rows: qById(eng, ws, `domain-installation:${eng.hashes.nomos}`), mount: m };
2089
2483
  }
2090
2484
 
2091
- /** Install a tenant package under the nomos controller (validation is the HOST's job). */
2485
+ /** Install a tenant package under the nomos controller (validation is the ADAPTER's job). */
2092
2486
  export async function deploy(eng, ws, ledger, usda, domainHash, dispositions) {
2093
2487
  // THE EVOLVE-GATE ANSWER LANE (#58): `dispositions` ({retired, retyped, rebinds,
2094
2488
  // ackBy}) ride the committed installDomain payload — IN-HISTORY facts the one gate
2095
2489
  // adjudicates the stable-id diff against. Absent for the common (non-destructive)
2096
2490
  // upgrade; the gate's typed refusal names the remedy when one is needed.
2097
2491
  const result = author(eng, ws, "nomos", "installDomain", installPayload(domainHash, usda, eng.installedBy, dispositions), eng.hashes.nomos);
2098
- // THE SHARD-IDENTITY ENSURE (#41 slice 3a): a taxonomy-bearing law carries the
2099
- // homing invariant, which judges against the workspace's OWN declared identity
2100
- // (`NomosShardIdentity`, law-state in the shard's own chain). The worker is the
2101
- // bailiff: it records the CUSTODY fact (this workspace's name ⇒ its shard label)
2102
- // through the chain's own gate right after the law lands — attributed, replayable,
2103
- // idempotent (an Ensure; re-deploys re-assert the same state). Coordinator ⇒ label ""
2104
- // (no homing claim — the invariant holds vacuously there).
2105
- try {
2106
- eng.directiveRoutesCache = null; // the just-staged overlay may add routes
2107
- const routed = new Set([...directiveRoutes(eng).keys()].map((k) => k.split(" ")[0]));
2108
- if (routed.size) {
2109
- const m = ws.match(/^(.*)--(s\d+)$/);
2110
- const label = m ? m[2] : "";
2111
- const coordinator = m ? m[1] : ws;
2112
- const existing = qById(eng, ws, "nomos-shard-identity:self");
2113
- const current = existing.length ? existing[0].data : null;
2114
- if (!current || current.label !== label || current.coordinator !== coordinator) {
2115
- for (const dom of routed) {
2116
- try {
2117
- const declared = author(eng, ws, dom, "nomosDeclareShardIdentity", { label, coordinator, declaredAt: new Date().toISOString() }, domainHash);
2118
- if (declared && declared.ok !== false) break; // one declaration IS the workspace's identity
2119
- } catch {} // a routed domain owned by an earlier law: its own deploy declared it
2120
- }
2121
- }
2122
- }
2123
- } catch {}
2492
+ // Shard identity used to be inferred from adapter-side taxonomy. That is retired:
2493
+ // if sharding needs declaration, the holon must derive and author it itself.
2124
2494
  await commitAndPush(eng, ws, ledger);
2125
2495
  return { head: result.head, installation: qById(eng, ws, `domain-installation:${domainHash}`) };
2126
2496
  }
@@ -2139,18 +2509,15 @@ export function stats(eng) {
2139
2509
 
2140
2510
  export async function ledgerStats(eng, ws, ledger = null) {
2141
2511
  const gitdir = gitdirOf(ws);
2142
- let head = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null);
2512
+ let head = kgit.resolveRef(eng, gitdir, BRANCH);
2143
2513
  if (!head && ledger?.remote) {
2144
2514
  try {
2145
- const info = await git.getRemoteInfo({ http, url: ledger.remote, headers: ledger.headers });
2146
- const remoteMain = (info.refs && info.refs.heads && info.refs.heads[BRANCH]) || null;
2515
+ // Cold: build the skeleton + pull main into the KERNEL's odb (custodyFetch apply_pack), then resolve.
2516
+ await custodySkeleton(eng, gitdir);
2517
+ const remoteMain = await custodyFetch(eng, ws, ledger, { remoteRef: "refs/heads/main" });
2147
2518
  if (!remoteMain) return { head: null, commits: 0 };
2148
- await git.init({ fs: eng.fs, gitdir, bare: true, defaultBranch: BRANCH }).catch(() => {});
2149
- await git.addRemote({ fs: eng.fs, gitdir, remote: "origin", url: ledger.remote, force: true });
2150
- await git.fetch({ fs: eng.fs, http, gitdir, remote: "origin", ref: BRANCH, singleBranch: true, headers: ledger.headers });
2151
- await git.writeRef({ fs: eng.fs, gitdir, ref: `refs/heads/${BRANCH}`, value: remoteMain, force: true });
2152
- await git.writeRef({ fs: eng.fs, gitdir, ref: "HEAD", value: `ref: refs/heads/${BRANCH}`, force: true, symbolic: true });
2153
- head = await git.resolveRef({ fs: eng.fs, gitdir, ref: BRANCH }).catch(() => null);
2519
+ kgit.writeRef(eng, gitdir, "HEAD", `ref: refs/heads/${BRANCH}`);
2520
+ head = kgit.resolveRef(eng, gitdir, BRANCH);
2154
2521
  const mounted = eng.mounted.get(ws);
2155
2522
  if (mounted) {
2156
2523
  mounted.restored = true;
@@ -2162,32 +2529,32 @@ export async function ledgerStats(eng, ws, ledger = null) {
2162
2529
  if (!head) return { head: null, commits: 0 };
2163
2530
  const mounted = eng.mounted.get(ws);
2164
2531
  if (mounted?.ledgerStats?.head === head) return mounted.ledgerStats;
2165
- const commits = (await git.log({ fs: eng.fs, gitdir, ref: head, depth: 10000000 })).length;
2532
+ // The chain is a linear sequence of intent commits, so intent-commit count == commit count.
2533
+ const commits = kgit.intentsAbove(eng, gitdir, head).length;
2166
2534
  const out = { head, commits };
2167
2535
  if (mounted) mounted.ledgerStats = out;
2168
2536
  return out;
2169
2537
  }
2170
2538
 
2171
- export async function countRemoteCommits(ledger) {
2172
- const info = await git.getRemoteInfo({ http, url: ledger.remote, headers: ledger.headers });
2173
- const head = (info.refs && info.refs.heads && info.refs.heads[BRANCH]) || null;
2539
+ export async function countRemoteCommits(eng, ledger) {
2540
+ const head = (await custodyLsRefs(ledger, "refs/heads/main"))["refs/heads/main"] || null;
2174
2541
  if (!head) return { head: null, commits: 0 };
2175
- const root = new Map();
2176
- root.set("count", new Directory(new Map()));
2177
- const preopen = new PreopenDirectory("/work", root);
2178
- const fs = makeGitFs(preopen, "/work", { File, Directory });
2179
- const gitdir = "/work/count/nomos.git";
2180
- await git.init({ fs, gitdir, bare: true, defaultBranch: BRANCH });
2181
- await git.addRemote({ fs, gitdir, remote: "origin", url: ledger.remote, force: true });
2182
- await git.fetch({ fs, http, gitdir, remote: "origin", ref: BRANCH, singleBranch: true, headers: ledger.headers });
2183
- await git.writeRef({ fs, gitdir, ref: `refs/heads/${BRANCH}`, value: head, force: true });
2184
- await git.writeRef({ fs, gitdir, ref: "HEAD", value: `ref: refs/heads/${BRANCH}`, force: true, symbolic: true });
2185
- const commits = (await git.log({ fs, gitdir, ref: head, depth: 10000000 })).length;
2186
- return { head, commits };
2542
+ // Count without mounting the workspace: pull main into a SCRATCH dir under the KERNEL's own odb (the adapter
2543
+ // has no repo — the kernel ingests the pack via apply_pack + walks it), then tear the scratch dir down.
2544
+ const scratch = "__ledgercount__";
2545
+ try {
2546
+ stageWorkspaceDir(eng, scratch);
2547
+ await custodySkeleton(eng, gitdirOf(scratch));
2548
+ await custodyFetch(eng, scratch, ledger, { remoteRef: "refs/heads/main" });
2549
+ const commits = kgit.intentsAbove(eng, gitdirOf(scratch), head).length;
2550
+ return { head, commits };
2551
+ } finally {
2552
+ try { eng.preopen.dir.contents.get("ws").contents.delete(scratch); } catch {}
2553
+ }
2187
2554
  }
2188
2555
 
2189
2556
  /** Per-branch projection observables (cursor, projectCalls, sqliteBytes — see #37). */
2190
- export const projectionStats = (eng, ws) => JSON.parse(call(eng.ex, "projection_stats", ws ? { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH } : {}, eng.STDERR));
2557
+ 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));
2191
2558
 
2192
2559
  /**
2193
2560
  * THE POST-ACK WARM LANE (#34): replenish the wasm engine's pristine-sandbox pool
@@ -2199,5 +2566,7 @@ export const projectionStats = (eng, ws) => JSON.parse(call(eng.ex, "projection_
2199
2566
  */
2200
2567
  export function warmEngine(eng) {
2201
2568
  flushDeferredProjections(eng); // the deferred-ack projection flush (#47) rides the same lane
2202
- return JSON.parse(call(eng.ex, "engine_warm", {}, eng.STDERR));
2569
+ // P3: the pristine-pool warm rides the `query` verb (op:"warm") — a non-effectful maintenance read,
2570
+ // not a top-level adapter verb. Still post-ack (the adapter calls warmEngine off the write critical path).
2571
+ return JSON.parse(call(eng.ex, "query", { repoArg: "/work", workspace: "root", queryBytes: b64Json({ op: "warm" }), branch: BRANCH }, eng.STDERR));
2203
2572
  }