Skip to content

The 2026-10 adversarial audit and its response: S0 to S5, family version 0.6.0 - #72

Merged
tap merged 16 commits into
mainfrom
ccr-baf78007-zqpei0
Oct 9, 2026
Merged

tap merged 16 commits into
mainfrom
ccr-baf78007-zqpei0

Conversation

@tap

@tap tap commented Oct 9, 2026

Copy link
Copy Markdown
Owner

What this changes

The multi-agent adversarial audit of the tree after the rational engine and the C ABI reshaping (docs/AUDIT_2026-10.md: 18 confirmed findings, 4 major, 14 minor, 16 nits, mutation testing) and its response plan (docs/AUDIT_2026-10_PLAN.md), executed in six steps, one commit each:

  • S0 bumps DspTap to tap/DspTap's chain.h fix (the stages reset at construction and read-only through the chain; the accounting contract stated as an inequality) and pins the audit's overflow scenario here.
  • S1 rational correctness and the test gaps: the schedule's advance widened to uint32_t so every charter ratio runs (ratio<3, 1024> once ran silently wrong); the fixed-point rounding direction pinned at the stage by a constructed-tie test, in bridge too; decimators in the reset tests; a fourth compile-fail case; the relaxed Q15 designs measured and their numbers stated; the bare-metal floor; clang-format guards in the vector generator; the promised example; the stale numbers.
  • S2 the three C ABIs: the exported symbol set is a test per engine; TAP_SR_<ENGINE>_API replaces the visibility pragmas and the MSVC leg builds the C ABI; async refuses a bad profile; thread contracts in the siblings' headers.
  • S3 the async vocabulary pass under 0.6.0: preset retired for profile, economy() to program(), the async C ABI in the siblings' argument order, test_asrc_* renamed, the umbrella header and README in the sibling shape, the old engine name out of the present tense.
  • S4 the docs sweep: three engines everywhere the prose said two, the root README's profile table, the plan status lines, Doxyfile, provenance on the Q31 figure.
  • S5 the gates: a retired-words checker over every tracked text file (notebook source cells included) as a style job; the READMEs' quick starts compile as tests; the one-meaning-per-profile-name rule in CLAUDE.md.

Why

The maintainer's question was whether quality suffered when the new engine came in. The audit's answer: the datapaths did not slip (bit-identity, allocation-freedom, sanitizers, scipy legs, A/B streams all clean), the discipline around the code did (two latent bugs in new stage and substrate code, a contract equation that was false, a gate the book claimed that did not exist, and prose lagging the code at every seam between rounds of work), and the oldest engine still spoke the dialect it was written in. The plan fixes the defects, closes the test gaps, brings async to the family's vocabulary without touching its numbers, and adds the gates that keep the seams from reopening.

Verification

Built and ran on x86-64 (gcc 13, Release, every engine's WERROR on, C ABI on) at every step: 290 → 305 ctest entries, all green. All 60 output hashes and every [ measured ] line are identical to the pre-change run at every step (the async hash labels read program where they read economy), so no datapath's output moved. Changed sources are clean under clang++ -Werror with the warning set and under clang-tidy; the C ABI headers compile as C11 consumers; the exported-symbol test was shown red against a library built without the hidden preset (883 symbols). Mutation testing after S1: 18 of 19 mutants killed, the nineteenth an equivalent mutant (dead code for its format), the live rounding points of both fixed-point formats mutated in the substrate and killed by the new tie test (the report's follow-up section). The book's include anchors resolve.

Not run here, so CI is the first real gate for them: the Windows MSVC leg with the C ABI on (new), the Cortex-M33 / M55 / Hexagon legs (the new Stage.WideRatiosOfTheCharterRunTheSchedule designs two wide stages at a 40 dB custom spec, under a second on the host), the instruction-count ratchet (the widened field is off the hot path; if a count moves, re-record with the reason), the book and print builds, and the new retired-words job. The notebooks were not re-executed: no measured behaviour changed, but the async notebooks' ctypes cells now use the new argument order, so a re-execution is due when the environment is next at hand.

Notes for the reviewer

  • Contract change. Family version 0.6.0 (D13). filter_spec::economy() → program(); preset → profile in every identifier; tap_sr_async_create(hz, target_latency_frames, profile, channels) and create_format(hz, target_latency_frames, profile, format, channels), profile and format before channels and unsigned channels, as the siblings take them. No alias (D7). Any C caller of the async ABI outside this tree needs the new order; none was named. async's tiers keep their own names on purpose: they are image rejection through the interpolated bank, not Nyquist stopbands, and a name means one thing across the family.
  • Submodule pin moved. submodules/dsptap to tap/DspTap 20a06ac on branch ccr-baf78007-zqpei0 (tap/DspTap's PR for the chain fix). After that PR merges by rebase/squash, repoint the pin at the identical tree on main before merging this one.
  • Notebooks not re-executed: behaviour did not change (see above).
  • scripts/book_figures.py keeps the rendered titles that say "presets" until the figures are next regenerated; the retired-words checker allows those two lines.
  • F25 (the STYLE.md banner template) is a taphouse change, referred.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV


Generated by Claude Code

claude added 16 commits October 9, 2026 02:38
… reshaping)

Nine independent finders over the rational engine's datapath, fixed-point
profiles, design layer and generators, the three C ABIs, the test battery
(including mutation testing), the family rules, build and CI, the docs'
stated numbers, the bridge/async regression surface, and real-time safety
under sanitizers; triage; then three-lens adversarial verification of every
finding above nit level. 18 confirmed (4 major, 14 minor), 1 disputed,
16 nits, plus the record of what was checked and found sound and what the
host-only audit could not cover.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
Seventeen mutants of the rational headers against the host battery:
fourteen killed, three survived, all of them the tie direction of the
fixed-point rounding point (the measured form of F13).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
Twelve proposed decisions and six pull requests in order: DspTap's
chain.h first, then rational's correctness and test gaps, the three
C ABIs, the async vocabulary pass under 0.6.0, the docs sweep, and the
gates against recurrence; every audit finding and cohesion item mapped
to its step.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
…he inequality it is

DspTap 20a06ac: tap::dsp::chain resets the stages it is given and exposes
them read-only, so a stage pulled and then chained can no longer write
past the scratch sized for process-reached states (audit F01); its
contract block states outputs_for(frames_needed(k)) >= k, not == k, and
names no chain pull (F03). Here: rational/PLAN.md 2.5 says the same,
stage.h's contract block carries the bound the chain relies on, and
test_chain.cpp pins the audit's scenario (an up-by-8 stage pulled one
frame, then moved into a chain, equals a chain of fresh stages bit for
bit; the test fails against the previous chain) and that no chain hands
out a mutable stage.

Gate: 291 ctest entries green; every engine's printed hashes and measured
lines identical before and after the bump (60 hashes).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
- stage.h: schedule_entry::advance widens to uint32_t; a mixed ratio of
  the charter with M >= 256 L (ratio<3, 1024>, ratio<2, 729>) once
  truncated its advances to a byte and ran silently wrong (F02).
  Stage.WideRatiosOfTheCharterRunTheSchedule pins the schedule sum, the
  chunking invariance and the accounting for both at a 40 dB custom spec.
- The tie direction of the single rounding point is pinned at the stage
  (F13, the audit's three surviving mutants): the impulse test's oracle is
  now the test's own round-half-up of the one product, and
  ImpulseTiesRoundHalfUp drives amplitudes whose products are exact ties
  (Q15 8192 / 16384 / -32768 by table gain, Q31 2^29) through the unity,
  power-of-two-divided and Q31 paths; bridge gets the same test.
- Reset tests exercise a decimator and a decimator chain mid-stream (F12).
- compile_fail isolates L != M with ratio<1, 1>; four cases (F07).
- design.h: the search-bound comment states 2.8x the longest pin and the
  exceptions name the bound; validate() names the relaxed passband;
  stage_taps_per_phase() validates and throws like design_stage (F08, F22).
- FixedPoint.RelaxedQ15TablesStopbandsAreStated measures every relaxed
  Q15 design; the economy half-band at 147/160 attains -67.6 dB, stated
  in PLAN.md section 6, CLAUDE.md and the README (F05).
- bare_metal_main.cpp: the selected-test floor is 80 against 99 (F11).
- make_reference_vectors.py emits clang-format guards; the committed
  header regenerates byte-identical, token-identical to before (F23).
- examples/multirate_chain.cpp, the example the layout promised; the
  cross-validation row struck with its reason (F18); rational.h's status,
  the plan's swapped deviations, the stale pre-lever numbers (F20, F28,
  F34); the wrap above SIZE_MAX / L documented (F35).

Gate: 298 ctest entries green; every pre-change measured line and all 60
output hashes identical; clang -Werror and clang-tidy clean on the changed
tests; the mutation harness re-run against this tree is recorded in the
audit report's follow-up (M1, M2, M4a, M4b, M5a killed as before; M3 is an
equivalent mutant, finalize_output's unity branch being unreachable for
Q15, whose live rounding point the tie test now pins).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
…e error shape

- <engine>.Family.ExportedSymbolsArePinned: nm on each built library
  against tests/family/symbols_<engine>.txt (14 / 17 / 23 entry points),
  anything else in the dynamic table a failure; shown red against a build
  without the hidden preset (883 symbols). The book's sentence and
  CLAUDE.md now name the test (F04).
- TAP_SR_<ENGINE>_API on every declaration (dllexport while building,
  dllimport for a consumer, default visibility elsewhere); the shims'
  visibility pragmas go; the MSVC leg builds the C ABI and runs the CApi
  suites, the DLL emitted beside the test executable (F10).
- async: a preset outside 0..2 returns NULL as the siblings' profile does,
  pinned (F09); bridge's and rational's headers state the thread contract
  (F24); async's PLAN counts fourteen functions in all (F33).

Gate: 301 ctest entries green; hashes and measured lines unchanged; the
headers compile as C11 consumers; the shims clean under clang -Werror.
The MSVC leg is not reproducible here and is the one piece CI must show.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
async is brought to the family's vocabulary without touching its datapath
or its ladder's numbers (the cohesion assessment's C1 to C4; A4, A5, A9,
A11 of the audit response plan):

- preset retires for profile in every identifier, comment and document;
  filter_spec::economy() is filter_spec::program() (one meaning per
  profile name across the family: async's tiers are image rejection
  through the interpolated bank, not Nyquist stopbands, so they keep
  their own names and the one collision goes). No alias (D7).
- The async C ABI takes the siblings' shape: tap_sr_async_create(hz,
  target_latency_frames, profile, channels) and create_format(hz,
  target_latency_frames, profile, format, channels), profile and format
  before channels, unsigned channels; the tests, the notebooks' ctypes
  declarations and the header's contract follow.
- test_asrc_* become test_lock / test_program / test_quality /
  test_quality_16k, bench_asrc becomes bench_converter; every reference,
  the book's include anchors among them, follows (the notebook filenames
  stay, the book links them).
- async.h's umbrella comment is rewritten as the third of three engines
  (the charter, the boundaries as identity, the composition, the
  profiles); sample_traits.h and polyphase_filter.h name the siblings.
- async/README.md takes the sibling shape: Quick start, The boundaries
  are identity, not policy, Position in the Tap family (three engines),
  Build, then its own measured sections; version 0.6.0 (F15). rational's
  README gains the family section; bridge's diagram shows three engines.
- The old name leaves the present tense: bluetooth_bridge's label and
  comments, bridge's CMake options, ratio.h, the cross-validation and
  skeleton tests, the reference generator, the notebooks' sources, the
  book's Part 5; the composition's latency is 1.93 ms everywhere (F29).
  "formerly RatioTap" stays where it is history.
- Version 0.6.0 in the three umbrella headers and the root project(),
  version_macros.cpp re-pinned, CApi.VersionIsBitPacked expects 0x000600.

Gate: 301 ctest entries green; all 60 output hash values and every
measured line identical to the pre-change run (the async hash labels
read program where they read economy); async's shim and tests clean
under clang -Werror; every changed source clang-format clean; every
book include anchor resolves. The rendered titles in
scripts/book_figures.py keep the old word until the figures are next
regenerated (no matplotlib here); noted in the plan.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
- Root README: the profile table (one meaning per name across the family,
  the three ladders with their numbers), three engines in the build block,
  the test naming, the ratchet, the layout and the dependency rule (F17).
- Root PLAN: the status line records the executed migration, the rational
  plan and the audit; D13 carries 0.5.0 and 0.6.0; the "Next" paragraph
  says the Q15 levers shipped and states the current numbers (F19, F30).
- Doxyfile indexes the rational engine and describes the family (F14).
- bridge's README selects its tests by the bridge label and lists the
  ratchet flags once; rational's README names macs_per_output_exact()
  (F16, F31); the composition's latency reads 1.93 ms there too (F29).
- The Q31 figure carries its provenance in stage.h and the README (F06).
- The book's C ABI chapter describes create_format's body (F32); the CI
  workflow comments say every engine (F26; F27 landed with S1).
- F25 (the STYLE.md banner template) is a taphouse change, referred.

Gate: the book's include anchors resolve; no "both engines" wording
remains outside the history records; docs only, nothing built changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
- scripts/retired_words.txt and scripts/check_retired_words.py: every
  tracked text file, notebook source cells included and outputs excluded,
  must carry no retired word (the bridge engine's pre-merge name in the
  present tense, the old async test and bench names, preset, async's
  retired economy(), the retired ratio label, a stale rational status
  line); the allowed senses and the history records are listed in the
  checker. A job in style.yml runs it. Shown red on its first run: it
  found two leftovers of S3 (a header comment and a notebook cell), fixed
  here.
- <name>.Family.ReadmeQuickStartCompiles for the root README and each
  engine's: the first ```cpp block, extracted at configure time
  (tests/family/readme_snippet.cmake) with a prelude declaring the
  caller's buffers, built by its test under the engine's label.
- Root CLAUDE.md: the one-meaning-per-profile-name rule (A4) and the rule
  that a change to an engine's shared vocabulary updates the root README's
  table and the siblings' READMEs in the same PR; the gates named under
  Style. The last two-engine phrases in cmake/retired_options.cmake,
  bridge/CLAUDE.md and async/docs/PERFORMANCE.md.

Gate: 305 ctest entries green (the four quick-start tests among them);
the vocabulary check clean over the tree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
Eighteen of nineteen killed; the nineteenth is an equivalent mutant
(finalize_output's unity branch is unreachable for Q15), and the live
rounding points of both formats, mutated in the substrate, are killed by
the tie test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
scripts/book_figures.py's two rendered strings say profiles; the figure
is regenerated with the pinned matplotlib (requirements.lock, 3.11.2),
which is why the SVG's path data moves too. The vocabulary checker's
allowance for those two lines goes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
…0.6.0

The three notebooks whose code cells changed in S3 (the async C ABI's
argument order, the bridge demo's engine name) re-executed through the
pinned environment (requirements.lock) against the shipping C++ on this
branch; no measured behaviour changed, and the outputs now match their
cells (the bridge demo prints bridge 0.6.0). The comparison and RBJ
notebooks changed in prose only and keep their outputs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
The clang-format job on #72 wrapped the one overlong line S1 added.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
The identical tree, now reachable from main after the squash merge
(d7ebe4b for 20a06ac), per the release flow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
…2 bits for the wide ratios

The M55 ratchet on #72 measured the price of S1's uint32_t advance: the
8-byte schedule entry cost the Q15 interpolators 8.7 % (up2 and up3), the
2/3 mixed stage 3.7 % and the float up-by-2 3.5 %, every checksum
unchanged. The entry is now templated on the ratio: a byte wherever every
advance fits one (ceil(M / L) <= 255, the whole vocabulary: the 4-byte
entry the baselines measured) and 32 bits for the wide ratios of the
charter (ratio<3, 1024>: 342), so no charter ratio is truncated (F02) and
no pinned count moves. sizeof pinned by a probe: 4 for up_2, ratio_3_8
and ratio<3, 512>, 8 for ratio<3, 1024>. Host: 123 rational entries green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
@tap
tap merged commit 391145d into main Oct 9, 2026
29 checks passed
tap pushed a commit that referenced this pull request Oct 9, 2026
The clang-format job on #72 wrapped the one overlong line S1 added.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg2PWk3CFfguD2qywBVnDV
@tap
tap deleted the ccr-baf78007-zqpei0 branch October 9, 2026 16:09
tap pushed a commit that referenced this pull request Oct 9, 2026
tap pushed a commit that referenced this pull request Oct 11, 2026
The thirteen streaming rows of rational/bench/baselines.json were the
2026-10-02 record (v0.10); three commits since moved them inside the ±3 %
gate without re-recording, so the gate measured from a tree other than
the one it guards. Re-recorded on all three targets from main at c120bc9
(arm-none-eabi-gcc 13.2.1, CodeLinaro 19.1.5, qemu 8.2.2 with the tree's
plugin: the toolchains that reproduce the committed rows to the
instruction, and bridge's twelve and async's eight rows --exact on M33,
M55 and Hexagon, so only rational moves). The construct row, recorded
2026-10-10, is unchanged.

The reason, traced by rebuilding the M55 workloads at every commit from
4eb1180 to e71d0b1: ab8fea3 (v0.11, the power-of-two row gain for the
Q15 mixed ratios going down) is the one visible move, ratio23_q15_eco
+355,117 on the M55 (+1.93 %, stated as +1.9 % in its commit), +7,346 on
the M33 and -224 on Hexagon; d4fff5c (v0.13, the mirror-exact designer,
the stage.h snap removed) added a construction-side constant per
workload (M55 +76 by-2, +112 by-3 and 2/3, +196 transparent, +128 the
chain; M33 +192 / +158 / +545 / +1,102; Hexagon +52 / +69 / +132 /
+2,201); the DspTap bump to tap/DspTap#57 (82cd646) added +798 to the
chain's construction on the M55. Every other commit re-measures
identically; the sweep also shows #72's S1 sitting +3.5 ... +8.8 % until
391145d took it back, as that commit records. rational/PLAN.md carries
the record (v0.15) beside the hand-kept table; the README table is
regenerated.

bridge/CLAUDE.md's ratchet sentence now says "beyond tolerance", the
family rule in the root CLAUDE.md: a count that moves inside the gate is
re-recorded when the record is corrected, as here, not on every change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GKzq1VSdcMYCRMhddiMaf
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.

2 participants