fix(cli): six qa/bench/probar/qualify defects — a gate that could not fail, a manifest of files that did not exist, and three flags with no effect - #2426
Conversation
… fail, a manifest of files that did not exist, and three flags with no effect Found by dogfooding apr 0.63.0 installed from crates.io. Six of the seven findings in #2380; the seventh (--assert-tps) is PR #2372. apr qualify --tier standard FAILED for every user who was not sitting in the aprender source checkout. The Contract Audit gate shelled out to `pv audit contracts/aprender/tensor-layout-v1.yaml` — a path resolved against the CALLER's cwd. From /tmp it reported "error: Failed to read contract file: No such file or directory (os error 2)" and exited 5; from the repo root the same model and tier PASSED. The contract ships with the source tree, not with the binary, so a missing file is a missing input, not a failed audit: qualify.rs now resolves it via APR_CONTRACTS_DIR or by walking up from cwd, and SKIPs with an actionable message when it is nowhere to be found. apr probar tensor printed a "Generated files:" manifest of .png paths and then wrote Netpbm .pgm bytes — every listed path was a file that did not exist, so a CI step copying them into probar fixtures failed with ENOENT. probar.rs:416 said "For now, write as .pgm" and :445 did `let _ = png_path;` to silence the unused variable. Rather than downgrade the promise, this adds a dependency-free 8-bit grayscale PNG encoder (png_encode.rs: IHDR + zlib-stored IDAT + IEND, CRC-32 and Adler-32 in ~110 lines) and the export writes real PNGs — `file` now reports "PNG image data, 256 x 100, 8-bit grayscale" and Pillow decodes it. The printed manifest and the export are now derived from one function, so they cannot drift again. Separately, `--format bogus` was swallowed by `.unwrap_or(ExportFormat::Both)` in dispatch_analysis.rs:85 and silently exported something else at exit 0; the FromStr error it already produced is now surfaced. apr qa passed two GPU gates vacuously on a CPU-only build. Capability Match asserted "Architecture 'qwen2': all 5 required ops supported by GPU" — a claim about kernels a non-cuda build has no path to — and PTX Parity reported "0/0 kernel pairs passed PTX parity", because without the cuda feature validate_all_kernel_pairs returns an empty report whose all_passed() (failed == 0) is vacuously true. Zero comparisons is not a pass. Both now SKIP, matching GPU Speedup and GPU State Isolation in the same report. apr bench --percentiles documents "Values must be in (0, 100]" and then accepted 0 and 101, emitting `"latency_p101_ms": null` into the CRUX-E-07 JSON report — a consumer saw a plausible metric key instead of an argument error. Non-numeric input was already rejected by the value parser, so the range check belongs there too; bench::run also checks, for non-clap callers. apr eval --device accepted arbitrary values and had no effect in perplexity mode: --device cpu, cuda and bogus produced byte-identical perplexity with no mention of a device anywhere in the output. The only readers of `device` are the humaneval/mbpp paths. --device is now restricted to cpu|cuda by clap, the effective device is reported in the header and the JSON, and asking for cuda prints why it was not honoured. apr showcase --step bogus answered "No step specified. Use --auto-verify or --step <step>" — the unknown-value branch fell through to the not-specified branch because both mapped to None. It now names the offending value and lists the available steps. Mutation check: reverting all six fixes and keeping the tests turns 14 tests RED, including test_locate_contract_returns_none_off_tree ... panicked at 'must not invent a path that does not exist' test_every_listed_generated_file_actually_exists ... panicked at 'Png: listed /tmp/.tmpxBAi54/layer_000_block_0.png but it was never written' test_dispatch_analysis_probar_rejects_unknown_format ... panicked at 'error must name the rejected value, got: File not found: ...' ptx_gate_skips_when_no_kernel_pairs_were_compared ... panicked at '0/0 comparisons must be reported as SKIP' capability_gate_makes_no_gpu_claim_without_cuda ... panicked at 'a CPU-only build must not assert GPU support, got: Architecture 'qwen2': all 5 required ops supported by GPU' parse_percentile_rejects_out_of_range_points ... panicked at 'point outside (0, 100] must be rejected: 0.0' perplexity_device_notice_fires_... ... panicked at 'cuda must be called out' test_showcase_unknown_step_names_the_offending_value ... panicked at 'error must quote what the user typed, got: Validation failed: No step specified. Use --auto-verify or --step <step>' The capability-gate test was itself vacuous on the first pass — its GGUF fixture had no metadata, so the gate short circuited on "missing architecture metadata" and never reached the GPU claim. It now writes a general.architecture KV and asserts the SKIP message, which is what proves the fixture engaged. test_export_by_format_png_creates_pgm_only asserted the defect (that a .pgm existed) and was rewritten to assert the .png the command advertises. 6651 apr-cli lib tests pass; fmt and clippy -D warnings clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pull request was converted to draft
|
Parking to let the merge queue drain — branch untouched, this will be reopened. Six PRs are in the merge queue and their The queue is the only path by which anything actually merges, so it gets the runners until it is empty. Reopening immediately afterwards. |
|
Superseded by #2449 — this branch is merged verbatim into that batch. The binding constraint was one ~50-minute Closing rather than leaving open so this PR cannot move #2449's base and force it to re-run. The branch is untouched and this is reopenable if the batch does not land. |
Found by dogfooding
apr0.63.0 installed from crates.io. Six of the seven findings in #2380 — the seventh (--assert-tpsdivided by ten) is PR #2372 and is not touched here.Every repro below was run first against the crates.io 0.63.0 binary, then against a release build of this branch.
apr qualify --tier standardfailed for everyone outside the source checkoutThe Contract Audit gate shelled out to
pv audit contracts/aprender/tensor-layout-v1.yaml— a path resolved against the caller's working directory (qualify.rs:441-446). The gate's verdict was therefore a function of where you stood.The contract ships with the source tree, not with the binary, so a missing file is a missing input, not a failed audit.
locate_contractnow resolves it fromAPR_CONTRACTS_DIRor by walking up from cwd, and the gate SKIPs with an actionable message when it is nowhere to be found.apr probar tensorlisted files it had not writtenIt printed a
Generated files:manifest of.pngpaths (probar.rs:249) and then wrote Netpbm.pgmbytes (probar.rs:417, withlet _ = png_path;at :445 silencing the unused variable). A CI step copying the listed paths into probar fixtures fails with ENOENT.Rather than downgrade the promise, this adds a dependency-free 8-bit grayscale PNG encoder (
png_encode.rs: IHDR + zlib-stored IDAT + IEND, CRC-32 and Adler-32, ~110 lines) and the export writes real PNGs. The printed manifest and the export are now derived from one function, so they cannot drift apart again.Separately,
--format boguswas swallowed by.unwrap_or(ExportFormat::Both)(dispatch_analysis.rs:85) and silently exported something the user never asked for, at exit 0. TheFromStrerror it already produced is now surfaced:apr qapassed two GPU gates vacuously on a CPU-only buildCapability Match asserted GPU kernel support from a build with no GPU backend compiled in. PTX Parity reported a pass over zero comparisons: without the
cudafeaturevalidate_all_kernel_pairsreturns an empty report, andall_passed()isfailed == 0, which is vacuously true. Zero comparisons is not a pass — that gate could not fail by construction. Both now SKIP, matching their two neighbours:apr bench --percentilesemitted a null metric instead of an argument errorThe help text says "Values must be in (0, 100]"; 0 and 101 were accepted and reached
bench.rs:264-267, where the non-OkPercentileOutcomeserialised asnullunder a plausible key.Non-numeric input was already rejected by the value parser, so the range check belongs there;
bench::runalso checks, for non-clap callers.apr eval --deviceaccepted anything and did nothing--device cpu,cudaandbogusproduced byte-identical perplexity with no mention of a device anywhere in the output — a typo like--device cuderan on CPU while the operator believed otherwise. The only readers ofdeviceare the humaneval/mbpp paths (inference.rs:121,:1531); the perplexity path never consults it.apr showcase --step <unknown>contradicted the command lineThe unknown-value branch fell through to the not-specified branch because both mapped to
None.Mutation check
Reverting all six fixes while keeping the tests turns 14 tests RED:
with, verbatim:
The 14th,
capability_gate_makes_no_gpu_claim_without_cuda, was itself vacuous on the first pass — its GGUF fixture carried no metadata, so the gate short circuited on "missing architecture metadata" and never reached the GPU claim. It now writes ageneral.architectureKV and additionally asserts the SKIP message, which is what proves the fixture engaged. Re-mutated afterwards:One pre-existing test encoded a defect and was rewritten:
test_export_by_format_png_creates_pgm_onlyasserted that a.pgmexisted — exactly the file the command claimed it had not written. It is nowtest_export_by_format_png_creates_png_onlyand asserts the.pngthe command advertises, plus that no stray Netpbm file is left behind.Gates
cargo fmt --all -- --checkclean,cargo clippy -p apr-cli --lib -- -D warningsclean, 6651 apr-cli lib tests pass. End-to-end verification ran against/mnt/nvme-raid0/targets/aprender/release/apr(apr 0.63.0 (5643db51d)) built from this branch — never a bareapr.Audit epic: #2373
Refs #2380 (partial) — fixes findings 2, 3, 4, 5, 6 and 7. Finding 1 (
apr qa --assert-tpsdivided by ten for GGUF and discarded for APR/SafeTensors) is not touched here; it is PR #2372 and remains the only open item in the cluster once this lands.🤖 Generated with Claude Code