The blob-derived rounds of index/lindex.js scan (LINK, SYM and BEE-071's TRI) run in one process: git's 4807 files take 31 s, linux about 9 minutes, all of it CPU in the per-blob loop. Ruled 2026-09-17 (gritzko): ONE utility file in bee stripes the changed blobs over N fork() CHILDREN by PATH HASH, each child mints its stripe into a lane of its own, and the parent merges every child's runs into the main lanes at the end — the spot --fork N / CAPOMergeWorkers shape of the C tool (beagle/spot/CAPO.exe.c:745, CAPO.c:396), which libdog does not carry. A forked child INHERITS the parent's memory (gritzko, 2026-09-17): the ODB handle, the open lanes, the tree cache and the changed list are already there, so there is no re-exec, no second tree diff and no worker verb — the child filters the list it holds and mints. The runtime leaf, io.fork() + io.exit(), is QJAB-014. The unit is step 3 of scan WHOLE: all three rounds ride one blob read, so one striping serves them all; the commit walk stays sequential. Main-tree work permitted for this ticket as for BEE-071; no commits, no push.
index/lindex.js:220:PS scan: step 2 derives changed by the pruning tree diff of the tip against the mark commits, step 3 loops it — blobBytes, the gates, hk.parse, linkRows, symRowsOf, tri.mint — writing through one idxWriter on the lite3 lane and one tix on the tri lane, step 4 writes the three marks LAST. linkRows/symRowsOf check idempotence through valsOn(ix, …), a READ of the main lane.io.reap(pid) exists (index/hook.js:79:bn reaps git); io.fork() -> pid (0 in the child) and io.exit(code) (an _exit, no parent-side teardown) are QJAB-014; there is NO pipe, a child talks back through files and its exit code; there is no core count in io. After fork() the child shares the parent's mmaps copy-on-write and its fds: it must NEVER write through the inherited rw handles — a seal in the child would race the parent's memtable — and only ever opens lanes of its own.abc.index handle: run(i) is a live typed view of run i, feed(view) bulk-puts a contiguous entries view, abc._merge_<lane>(runs[]) is the k-way deduplicated union of sorted runs (quickjab/hit.c), for u32 and wh128 alike. Runs are <seqno><ext> files, a writer takes max + 1 in ITS dir and the memtable is the one <ext> file per dir — so two writers on one dir collide, and a worker needs a dir of its own.index/index.js:466:Wt pathHl(path) — the 40-bit path hash every family keys on, the stripe key here.index/fork.js, the ONE utility: fork.run(ctx, N, stripeFn, foldFn) or the equivalent shape — decide N (--fork <n> on bee index, else $BEE_FORK, else the count of /sys/devices/system/cpu/cpu[0-9]* entries, else 4; 1 means inline), fork the children, reap them, fold, clean up. Nothing in it knows what a LINK, SYM or TRI row is.i of N owns the changed blobs whose Number(pathHl(path) & 0xffffn) % N === i, filtered out of the changed list it inherited — nothing crosses a process boundary on the way in. Below 1000 changed blobs per child the parent runs inline, as CAPO does — the fork costs more than it saves.io.fork() returns 0 it reads the main lite3 lane through the INHERITED handle for valsOn only (reads are safe; the runs are immutable and the memtable it inherited is a snapshot), writes its rows into <gitdir>/be/w<i>/ — both exts, the bulk memtable, durable: false — through the very linkRows/symRowsOf/tri.mint the inline path uses, commits once, writes its counts, and leaves through io.exit(0); any throw is io.exit(1) after one stderr line. It never calls commit, close or put on a handle it did not open.run(i) view, _merge_<lane> them into one array per lane, feeds it into the main lane, commits, then removes w<i>/. Zero-copy adopt is a later QJAB leaf; the merge-and-feed is the v1.w<i>/) has its stripe run inline by the parent; the three marks are written only after every stripe is folded, so a crash anywhere costs time, never a wrong mark.rec sums the children's counts (each writes its { files, links, syms, tris, rows } as JSON into its dir, the one thing that crosses back), so bee index's summary line reads as before.test/fork/run.sh + rows.js: the FOLDED rows of a forked run equal the rows of a serial run on the same fixture (dump both lanes, compare sets); --fork 1 is the inline path byte for byte; a stripe is a pure function of the path (the same file lands on the same worker across two runs); a worker killed mid-way (a fixture path that makes it throw) is rerun inline and the marks still land last; below the threshold no w<i>/ dir ever appears; a rerun over the unmoved tip writes nothing.bee //git index after rm -rf of the lane: 31 s serial today; under 8 s with 8 workers. Record both in the Outcome.index/hook.js; the way IN is the fork's copy-on-write memory and nothing else.scan before spawning; it is derived state under <gitdir>/be like every other file there, and the sweep of index/index.js must not unlink it mid-run (its exts are the live ones).adopt for v1: no Pup change, one copy of the postings, measured before an adopt leaf is asked for.fork() over spawn (gritzko, 2026-09-17): the child starts with everything open and the list in hand; a spawned worker would re-open the repo, re-diff the tree and need a verb of its own.io.fork() / io.exit()w<i>/bee index --fork <n>, $BEE_FORK, README line, INDEXES.mkd note under the suspects contract