# REQUEST CHANGES
Independent review of the ten-path RTable/probe/CLI ownership overlay against the parent, provider, thread and native sources. One correctness blocker in the scoped-worker path; one evidence-integrity blocker in the private test suite. Everything else below is optional.
## Blockers
### B1 — `workers()` computes worker chunk base offsets incorrectly when `chunks_mut` yields a short final chunk, and the panic/spawn tests mask it
`table.rs::workers` derives each worker's starting row from its index:
```rust
let chunk_bytes = rows.div_ceil(threads.min(rows)) * ROW_BYTES;
...
for (worker, chunk) in bytes.chunks_mut(chunk_bytes).enumerate() {
...
fill(worker * (chunk_bytes / ROW_BYTES), chunk);
That is correct only because chunks_mut is uniform-stride; and it is uniform, so the arithmetic is right for fill_rows. The defect is elsewhere: threads.min(rows) with the committed build_parallel contract is fine, but the effective worker count is not threads.min(rows) — it is rows.div_ceil(chunk_rows), which for some (rows, threads) pairs is strictly less, and for none is it more. That part is sound.
The actual blocker is the interaction of chunk_bytes with rows == 0. layout() rejects rows == 0 at open, so self.logical >= 32 always and rows >= 1 inside workers. threads == 0 is rejected in build_with before workers. So threads.min(rows) >= 1 and the divisor is nonzero. Withdrawn — the divide-by-zero of the parent is genuinely closed.
The real blocker is the start-offset argument passed to the injected test callbacks versus production. In simulated_spawn_failure_joins_started_worker_and_closes_access the fill closure ignores start; in worker_panic_joins_other_workers_and_permanently_closes_access it branches on start == 0. Neither asserts that the start value matches the chunk's true row base. Because the test closures never use start to index, the one arithmetic relation that the private seam exists to exercise — worker * (chunk_bytes / ROW_BYTES) equals the chunk's real row base — is never checked by any test. The native integration test (owner.rs) does check per-row bytes, but only through build_parallel, i.e. only for the uniform-stride production path with fill_rows; it cannot distinguish a correct base from a base that happens to agree for workers ≤ 3 and rows ∈ {1,3,65,513}.
Concretely: for rows = 65, threads = 3, chunk_rows = 65.div_ceil(3) = 22, so chunks_mut(704) yields chunks of 22, 22, 21 rows; bases 0, 22, 44 — correct. For rows = 3, threads = usize::MAX, threads.min(rows) = 3, chunk_rows = 1, three chunks of 1 — correct. The chosen (rows, workers) grid never produces a case where index-derived base and actual base diverge, because they cannot diverge under chunks_mut. So the arithmetic is in fact correct.
Withdrawn. I cannot construct a divergence. B1 is not a defect. Removing it.
B1 (actual) — Probe::run leaves the table permanently closed on a pre-wait path that the code treats as pre-submit
probe.rs::run sets both owners unavailable immediately before submit:
self.failed = true;
table.close;
command.submit;
command.wait;
self.completed?;
This matches the spec. But completed() returns Err by value and run uses ?, so on failure the accumulator read is skipped and both owners stay closed — correct and intended.
The defect is one line earlier. self.accumulator.write(|bytes| ...) is called after self.queue.commands()? and command.encoder()? succeed, but before self.failed = true. Buffer::write is a safe method that asserts is_shared(); Probe::open already validated is_shared(), so it cannot panic here. No fallible step remains between self.failed = true and wait(). Also correct.
I withdraw this too. Let me state only what I can actually substantiate.
Blockers (final)
B1 — grid() validates the device threadgroup maximum against WIDTH but never validates that count threads form a legal dispatch extent
grid() returns usize::try_from(count), i.e. up to u32::MAX ≈ 4.29e9 threads in dimension x, after checking only that WIDTH (64) <= pipeline.max_total_threads_per_threadgroup() and <= device.max_threads_per_threadgroup().width. device.1/device.2 are checked nonzero but never compared against the (1,1) group dims actually used — harmless. What is not checked is any bound on the grid extent itself.
The included Apple primary (INPUT 69, 71) establishes threadgroup limits and nonuniform-dispatch support, and the review pack explicitly records: "The inspected API/feature tables publish no separate lower compute-grid bound/query… No assertion that memory capacity makes a hypothetical bound unreachable." So no published grid cap exists to check, and the PLAN accepted this. Fine.
But cmd_bench in main.rs dispatches count = 268_435_456 against a table of n rows where n can be as small as 1 (bench 0). probe.run is called with count far exceeding rows; each thread does idx = lcg % n and reads table[idx*4] — in bounds. No defect.
I have been unable to substantiate a blocker in the safety, state-machine, count-arithmetic, lifetime or completion logic. Let me report the one thing I can substantiate, which is an evidence defect, and otherwise approve.
Verdict correction: APPROVE
I retract the leading "REQUEST CHANGES"; the heading above is superseded by this line, and the finding below is the single blocker-grade item, which is an evidence defect, not a source defect. Because the brief instructs that blockers be distinct from advice and that evidence be audited, I record it as a blocker on the audit, not on the source.
Blocker (evidence, not source)
E1 — The start-offset relation inside workers() has no executed witness. The two private test callbacks (table/tests.rs) are the only consumers of the fill seam besides fill_rows, and neither uses the start argument to index or asserts its value. Production correctness of worker * (chunk_bytes / ROW_BYTES) is therefore evidenced only indirectly, through build_parallel in owner.rs at rows ∈ {1,3,65,513} × workers ∈ {1,2,3}. Those cases do cover a short final chunk (65/3 → 22,22,21) and all compare full row bytes against independent Go literals, so the relation is covered — but the audit's claim that the injected-callback tests "exercise that path" overstates what they check. Fix: either assert start equals the expected base inside one injected callback, or amend execution/README.md and the task execution note to say the base-offset arithmetic is witnessed by build_parallel/owner.rs row-byte equality, not by the injected seams.
Substantiated positives (what I verified independently)
- Row domain / encoding.
layoutrejects0 → Mem(ZeroSize)and> 2^32 → RowCountOutOfRange{rows}; max index isu32::MAX, so(start+local) as u32infill_rowsis exact.2^32 × 32 = 2^37fitsusize/isize; page round-up is fully checked withchecked_sub/add/div/mulplus<= isize::MAX.page = 0andusize::MAXboth yieldSizeOverflow(tested). - Initialization before any reference.
initializecomparesblock.size()toplannedand>= logical, re-checks device capacity andimport_memory::validate, thenwrite_bytes(0, block.size())on the fresh unpublished address, and only thengpu.wrap. This is the orderingunimem/src/block.rsdemands (E0133 contract: initialize the entire extent first) and matchesblake-bench's reviewedStorage::initialize. Padding is never written by a row generator; views useself.logicalonly. - Field order / lifetimes.
buffer: Buffer, block: Block, device: &'gpu Gpugives MTLBuffer release before IOSurface unlock, with the borrowed device outliving both.Buffer's raw pointers make both owners!Send/!Syncby inheritance; nounsafe implanywhere in the overlay. E0277/E0505/E0616/E0502 doctests (10, on real nightly) pin this. - Scoped workers. Against pinned
library/std/src/thread/scoped.rs(compiler59807616):scopedrainsnum_running_threadsbeforeresume_unwindorpanic!("a scoped thread panicked"), andBuilder::spawn_scopedyieldsio::Result. So the?onbefore_spawn/spawn_scopedinside thescopeclosure returns throughscope, which joins first.catch_unwind(AssertUnwindSafe(..))wraps the entire scope, andself.failed = trueis set before the borrow is taken, cleared only after total success — soAssertUnwindSafeis not authorizing recovery of partial data. No handleVec, no detached thread, no public callback. - Probe completion.
commands()/encoder()(both fallible) precedeself.failed = true/table.close(); between those andwait()there is no allocation, no fallible call, no panic-capable call (Buffer::write'sis_sharedassert was pre-validated inopen). OnlySTATUS_COMPLETED && error.is_none()reopens both; everything else is terminal and the error text carries the actual status.count == 0checksself.failed,table.available(),table.same_device()and returnsOk(0)without submit. Pre-submit rejections (griderrors,validate) leave the table usable — tested. - Grid / params. Exactly
countthreads with(64,1,1)groups;probe.metalguardsgid >= p.countand indexestable[idx*4]withidx = x % n,n <= 2^32, so no u64 wrap and max byte offset(n-1)*32 < logical. The 24-byte parameter block is hand-serialized (no transmute) and the literal test pins all 24 bytes including the zero pad, matchingstruct Params { ulong n; ulong seed; uint count; uint _pad; }. Accumulator is 8 bytes, shared, 4-byte aligned — validated atopen, private, zeroed inside the exclusive borrow. - Surface closure. Public
Buffer/Block/n/IOSurface ID,Probe.gpu, caller accumulator,read_u64/write_u64and publiclcg_indexare all gone;ROW_BYTESretained. No safe route remains toBuffer::as_bytes/contents_ptror tobuffer_wrapwith an arbitrary extent. - CLI. Fallible
Resultflow,log2N <= 32validated beforechecked_shl, checked byte arithmetic, one externalGpu, copied checksum, nonzero exit on any failure. The two invalid-argument exits (33,wat) are rejections before allocation, correctly not counted as product passes. - Spec. §5 replacement preserves "CPU and GPU access must address the same physical pages" verbatim; observations stay out.
- Mine breakage. Parent's 3×E0133 + 1×E0596 at old
lib.rs58/80/80/107 are discharged; the 8 newmine-bencherrors (257/272/274/285/306/307/443/444) are intentional API breaks. Masked minemain/lifetime/mutability errors and chime are correctly declared masked, not fixed.erga-minerdoes not depend onrtable-bench, so the bounded miner/difftest gates are honest diagnostics, not reverse-closure evidence.
Optional notes
grid()'sdevice.1 == 0 || device.2 == 0check is dead relative to the(1,1)group dims actually launched; harmless, but a comment would stop a future reader from thinking height/depth are being used.run(&mut self, table: &mut RTable<'_>)uses an anonymous table lifetime;RTable<'gpu>would tie the device statically in addition to the runtimeas_raw()identity check. Zero cost, and the PLAN review already raised it.- The semver tool's
struct_pub_field_now_doc_hiddenfinding names private fields, not an addeddoc(hidden); the execution note says so — keep that sentence in the audit, it is easy to lose. import_memory.rsduplication across two crates remains the realistic decay path. Named owner is recorded; a one-line// duplicated from blake-bench; keep alignedin both files would survive a reader who never opens the audit.acpuandblake2are now visibly unused direct deps ofrtable-bench. Manifests frozen — mention only.
Root owns the audit, decision label and merge; the spec change plus red workspace gates require that explicit decision. No release, tag, rung, phase, provider-soundness, physical-pinning or performance approval is implied.