shared/checkout.js apply materialises a missing object as empty
checkout.apply's tree walk fetches each leaf's object and, on an UNRESOLVED object (getObject → null), falls back to bytes = new Uint8Array(0) — then writes a zero-byte file AND records a new/upd row anyway. The live verbs/get/get.js:334 leaf does the right thing (if (!obj) return; — skip the leaf, no row). Currently latent because apply is DEAD: it is exported but neither get.js nor patch.js calls it (they use leafUnchanged/materialise/ scanWt). It becomes a real data-integrity bug — silent zero-byte truncation — the moment apply is wired in. Method: Issues.
shared/checkout.js:142-143 `const obj = keeper.getObject(leaf.sha); constbytes = obj ? obj.bytes : new Uint8Array(0);` — null object → empty bytes.
:147-148 `materialise(wtRoot, rel, leaf, bytes); rows.push({ verb: existed? "upd" : "new", path: rel });` — writes the empty file + records a row regardless of whether the object resolved.
verbs/get/get.js:333-334 the live leaf: `const obj = g.k.getObject(newSha);if (!obj) return;` — the correct early-return (skip, no row).
shared/checkout.js:125 function apply(...) is exported (:168) but has
NO caller in verbs/get/get.js or verbs/patch/patch.js (both use checkout.leafUnchanged/materialise/scanWt, never apply) — dead code.
apply never writes empty bytes for an unresolved object: skip the leaf
(or throw), matching get.js:334. The well-known empty-blob sha (a real 0-byte file) is the ONLY case that legitimately writes nothing.
apply cleanup: remove it, or fix-and-keep if it is theintended checkout entry point.
shared/checkout.js only; keep materialise/scanWt/leafUnchanged(live callers) byte-identical. Pairs with the dead-apply cleanup decision.
apply: `const obj = keeper.getObject(leaf.sha); if (!obj)
return; before materialise — mirror get.js`. Distinguish the empty-blob sha (write 0 bytes, with a row) from a missing object (skip).
apply + its export; document that get.js/patch.js ownmaterialisation. Confirm no out-of-tree caller first.
apply (or its fixed twin) with a leaf whose sha isabsent from the store; assert NO zero-byte file is written and NO row.
apply, or remove the dead apply + export.apply is now LIVE: shared/submount.js:273,320
call checkout.apply(k, pin, subWt, …) for submodule checkout (was dead in 2026-06-25). The empty-bytes bug persists at shared/checkout.js:179-180 (const obj=keeper.getObject(leaf.sha); const bytes = obj ? obj.bytes : new Uint8Array(0);), so a missing sub object now silently writes a 0-byte file + a row. RESCOPE: skip-on-missing (the removal arm is off the table), and this is effectively no longer LOW. The dead-apply removal in JS-090 is also now moot.