Skip to content

test(bench): integrity-check acceptance benchmark — NRN-83 closed on evidence#107

Merged
dbtlr merged 5 commits into
mainfrom
nrn-83-integrity-benchmark
Jul 8, 2026
Merged

test(bench): integrity-check acceptance benchmark — NRN-83 closed on evidence#107
dbtlr merged 5 commits into
mainfrom
nrn-83-integrity-benchmark

Conversation

@dbtlr

@dbtlr dbtlr commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

The daemon initiative's founding bug (NRN-83: Cache::open pays PRAGMA integrity_check per process, O(db-size), dwarfing queries at 50k docs) is closed on structural evidence, not timing. This PR adds the committed, re-runnable acceptance harness that proves the ADR-0005 resolution end-to-end.

Evidence (this machine, 50k synthetic docs, 58MB cache.db):

shape         direct median   routed(warm) med     served
count          409.8ms            146.9ms              5
find           440.6ms            176.5ms              5
get            410.7ms            146.6ms              5
integrity_check markers: direct = 15 (one per read) · daemon = 1 (verify-once)

15 routed reads across three shapes share exactly one integrity check; 15 direct reads pay 15 (trust preserved on the no-daemon path). The assertion is marker-counting cross-checked against served markers — it cannot pass vacuously, and it demonstrably fails under the cold hypothesis.

What's committed

  • tests/bench_util/ — deterministic synthetic-vault generator: content is a pure function of (index, seed) via SplitMix64, no wall-clock or thread RNG; determinism self-tests run in the default suite.
  • tests/integrity_benchmark.rs#[ignore]-gated acceptance harness (explicit run: cargo test --release --test integrity_benchmark -- --ignored --nocapture), size/seed/iters env-overridable, isolated cache home + own daemon, prints the evidence table. Timing is evidence only; the structural marker gates are the assertions.
  • src/cache/open.rs — opt-in NORN_TRACE_INTEGRITY_CHECK stderr marker (off by default; contract documented: enabling it intentionally reveals where the check runs, so stderr byte-identity does not hold under trace — deliberately release-available, unlike the debug-gated timing knob, because it changes no behavior and the release benchmark needs it).
  • Cache-dir resolution exposed as an explicit-home pure function (resolve_cache_dir_in) — the harness mutates no process environment.

Verification

Quartet clean: check --locked / 2329 tests, 0 failed / clippy -D warnings / fmt; Cargo.lock untouched. Benchmark run green at 50k and 2k scales with identical structural results.

Adversarial review

Two rounds: 7 findings — 5 fixed, 2 dismissed (suppression-scan prose false positive; the benchmark's #[ignore] is the harness design). Notable: round 1 demoted a self-contradicting timing assert to evidence and took the documented position on the trace hook's release availability; round 2 caught the harness reintroducing the set_var UB class NRN-215 eliminated — fixed by pure-function decomposition.

Adversarial-Review: run engine=code-review tier=high findings=7 fixed=5 dismissed=2 deferred=0

dbtlr added 5 commits July 8, 2026 14:23
Emit a stable `norn trace: integrity_check` stderr marker per PRAGMA
integrity_check execution when NORN_TRACE_INTEGRITY_CHECK is set. Gated
on the env var so normal output and the byte-identical routing proofs
are untouched. Gives an out-of-process harness a deterministic,
cross-process count of how many times a code path pays the O(db-size)
check: a warm daemon opens-once/verifies-once (one marker across N
routed reads) versus N markers for N direct reopens.
Committed, re-runnable acceptance benchmark proving the daemon
initiative's founding-bug fix (ADR 0005) end-to-end at 50k-doc scale:
routed reads pay zero per-invocation integrity_check while direct
no-daemon reads still verify every call.

- tests/bench_util/: deterministic synthetic-vault generator (SplitMix64,
  pure function of (N, seed) — no wall-clock/thread RNG), with
  determinism self-tests that run in the default pass.
- tests/integrity_benchmark.rs: #[ignore]-gated harness. Structural
  acceptance assertion (not timing): exactly ONE integrity_check marker
  in the daemon's stderr across all routed reads vs one-per-call direct,
  cross-checked against served markers so it cannot pass vacuously.
  Scale/seed/iters env-overridable; prints an evidence table.

50k run: direct read median ~410ms vs routed warm ~147ms; direct=15
integrity_check markers, daemon=1.
- src/cache/open.rs: make the trace hook's contract explicit — opt-in
  diagnostic off by default; enabling it intentionally diverges routed
  vs direct stderr; never enable where norn's stderr is asserted on.
  Documents why it stays release-available (pure observability) in
  contrast to the debug-gated NORN_CACHE_LOCK_TIMEOUT_MS (which changes
  timing behavior). CHANGELOG gains the same clarification.
- integrity_benchmark: demote the routed-faster-than-direct timing
  check from a hard assert to a WARN println — the structural marker
  assertions are the only acceptance gates; timing is evidence.
- integrity_benchmark: locate cache.db via the production identity
  mapping instead of a recursive walk, failing loudly if absent. Adds a
  #[doc(hidden)] resolve_cache_dir seam (cache.rs, re-exported in
  lib.rs) that stringifies the error so the crate-private CacheError
  does not leak into public visibility; same posture as Cache::conn.
- bench_util: drop the blanket #![allow(dead_code)] (everything
  compiles into the #[ignore] test binary; no per-item allows needed).
The harness set XDG_CACHE_HOME via std::env::set_var so the in-process
resolver would match the children's explicit .env() — reintroducing the
process-global env-mutation class NRN-215 removed; under
--include-ignored the benchmark runs concurrently with the bench_util
unit tests, where set_var races libtest's own getenv on any variable.

Make the resolution explicit instead: identity::cache_dir_for now
delegates to a new cache_dir_in(cache_home, vault_root) — the same
mapping as a pure function of its arguments — and the #[doc(hidden)]
seam becomes resolve_cache_dir_in(cache_home, vault_root). The harness
computes the path from the same private home value it already passes to
children; zero env mutation anywhere in the test binary.
Two review rounds (engine=code-review, agents pinned to Opus):
7 findings — 5 fixed (trace-hook contract documented with the
release-availability position, timing assert demoted to evidence,
cache-dir resolution via explicit-home pure function replacing both
the recursive walk and a set_var UB reintroduction, blanket
dead_code allow removed), 2 dismissed (suppression-scan prose false
positive; the benchmark's #[ignore] is the harness design —
explicitly-run acceptance gate, not suite erosion).

Adversarial-Review: run engine=code-review tier=high findings=7 fixed=5 dismissed=2 deferred=0
@dbtlr dbtlr merged commit 7ab44d6 into main Jul 8, 2026
7 checks passed
@dbtlr dbtlr deleted the nrn-83-integrity-benchmark branch July 8, 2026 18:59
@dbtlr dbtlr mentioned this pull request Jul 8, 2026
dbtlr added a commit that referenced this pull request Jul 8, 2026
Cuts v0.46.0 — the daemon-reads-complete release (Phase 1 exit).
Version 0.45.1 -> 0.46.0; CHANGELOG promoted with theme paragraph.
Shipped: #101 #102 #103 #104 #105 #106 #107.

Adversarial-Review: run engine=inline tier=low findings=0 fixed=0 dismissed=0 deferred=0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant