Skip to content

ripwire --doctor should detect a binary whose translation units disagree on struct layout #162

Description

@joyful-ii-V-I

What is missing

If src/model.h changes while a build is running, some object files end up compiled against the old
Symbol layout and some against the new one. Their mtimes are newer than the header, so make
concludes everything is up to date and never recompiles them — the build reports success, exit 0, no
warnings, forever. Nothing in CMake can repair this, and test/g1freshcheck.sh cannot see it,
because the binary is newer than its sources and only its contents are stale. The ask is for
ripwire --doctor to say it in one line: each translation unit records the struct layouts it was
compiled with, and the doctor reports when they disagree.

The evidence

CLAUDE.md records three incidents from this failure mode, all from building across or editing
under a branch switch:

  • A sanitizer build finishing after a checkout that changed Symbol produced a genuine
    AddressSanitizer heap-buffer-overflow report for a bug that did not exist. The overflowing region
    was 1344 bytes — exactly 14 × 96, a multiple of the previous struct size.
  • A header's mirrored constant kept emitting its old value through repeated successful rebuilds; a
    gate failed with parserVer=41, expected 12/42 and looked like a missed mirror update.
  • Ten identical uncaught std::length_error aborts in one morning of worktree churn, thrown from a
    resize( symbols.size() ), with zero repro in 38 runs on clean rebuilds of the same commit.

Facts read from the tree that the kit rests on:

  • The ripwire target is src/main.cpp plus src/ingest.cpp, src/pagerank.cpp and
    src/infra/diagnostics.cpp; IngestResult carries vectors of Symbol, Reference and eight
    other structs across that boundary.
  • Symbol is pinned by static_assert( sizeof( Symbol ) == 64 + 2 * sizeof( std::string ) ) — which
    every translation unit checks against its own view of the header, so it passes in both halves of
    a mixed binary. Reference has no size pin at all.
  • --doctor (src/verbs_doctor.h) emits one <c n= ok=> row per check and exits 1 when any fails;
    test/doctorcheck.sh asserts a named row set.
  • --layout and --stray-content --abi model struct layout from source text ("a MODEL, NOT THE
    ABI") and cannot see what a compiler actually produced.

Size

Good first issue. One recording header, one registry, one doctor row, one gate, and a short
audit of the cache-format size pins. No external corpus, no network, no API keys — a C++23
toolchain and CMake able to produce both the plain dev build and a Release build, because the
records must survive NDEBUG and LTO.

Where to start

prompts/help-wanted/struct-layout-doctor.md is a self-contained prompt for a coding agent. It
carries the hazard and its three recorded incidents, the translation units involved, why a header
static_assert cannot catch it, how to reproduce it safely in a scratch tree, the recording and
comparison design space (internal linkage, a per-unit identity, surviving Release and LTO, comparing
for agreement rather than pinning numbers), the doctor row's three states, the gate arms, the
acceptance criteria and the traps. Like every prompt in prompts/, it ends by writing a plan and
stopping
— a maintainer agrees the plan before any code is written.

Build the gate first: a two-translation-unit fixture whose struct definitions differ must make the
comparator report a disagreement before the real row exists.

Comment here to claim it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions