One Attempt to Score Everything: The noexcept Ledger for C++ Patch Review

Score noexcept contracts and runtime behavior together in one attempt to block stitched green builds.
This article proposes a ledger mechanism for C++ patch review. The core problem: matching printed output cannot verify the `noexcept` contract callers compiled against — these are two separate contracts. A patch can pass all happy-path tests while quietly removing `noexcept` from the header, or throw inside a `noexcept`-marked function body (triggering `std::terminate` on bad input). The design isolates the golden header in a model-unwritable directory, gives each sample its own attempt folder, and requires six scoring columns — compile, `static_assert` contract check, happy link/run, malformed link/run — to all pass on the same attempt before promotion, structurally blocking stitched false positives. The mechanism explicitly makes no promises about undefined behavior, data races, or memory leaks inside function bodies.
A Tuesday code review. A small C++ frame-parser patch. The candidate patch printed payload lengths that matched the golden file exactly, and the happy-path binary exited with status zero. The reviewer merged it. Then a downstream caller — already compiled against the old noexcept promise — hit std::terminate when it encountered a short frame.
The problem: the printed output never mentioned exceptions, and the green test run never fed the parser an empty buffer. That gap is exactly what the "ledger" mechanism described here is designed to catch.

What Matching Output Hides
A happy-path fixture checks observable text output. It does not check the exception specification that callers have already compiled against. These are two separate contracts, and a patch can satisfy one while breaking the other.
Removing noexcept from a header leaves the happy output unchanged. Conversely, keeping noexcept on the declaration while throwing inside the function body also leaves output looking fine — until a bad input triggers std::terminate. Both oversights are silent against a golden text file.
The workflow described here scores both oversights on the same attempt id. But it makes no claim that those scores prove the absence of undefined behavior, data races, or memory leaks — its boundaries are explicit.
Exception specification is the part of a C++ function declaration that tells the compiler and callers whether the function might throw. noexcept is the most common form: appending the noexcept keyword to a declaration is a promise that the function will not throw. Callers can rely on this promise for compile-time optimizations — for example, standard library containers choose move over copy when moving elements only if the move constructor is marked noexcept. This contract is compiled into the caller's binary: once downstream code has been compiled under the assumption that "this function won't throw," quietly removing noexcept from the header later doesn't automatically invalidate the compiled caller, but the implicit assumption between them is now misaligned. More dangerous is the reverse: the declaration keeps noexcept, but the implementation throws internally — the C++ standard requires the runtime to call std::terminate and abort immediately, with no opportunity to catch any propagating exception. This is the root cause of the "short frame triggers abort" scenario at the start of the article.
Separating Contract from Fixture
The core design principle is placing the golden file outside any directory the model is permitted to overwrite. Each sample gets a fresh attempt folder; object files left behind by a previous sample never leak into the next link step.
golden/frame.hpp is the last reviewed declaration. The attempt's include tree starts from a copy of that header and accepts only the edits the prompt permits. A comment-only diff is not a failure — a consumer translation unit decides whether the declaration is still non-throwing.
Specifically: tests/contract_tu.cpp includes the candidate header; tests/happy.cpp and tests/malformed.cpp link against the candidate object file and each print one line. The malformed program is the runtime half of the contract — an exit code of 0 plus the sentinel output 0 followed by a newline means the short frame was reported correctly without triggering an abort.
Note that this is a proposal with one unexecuted example. It assumes a POSIX g++ environment accepting -std=c++20. On Windows, numeric abort codes differ, but any non-zero status still fails that column.
Steps That Reject "Stitched" Green Builds
The most insidious bad habit in code review is stitching: a retried sample appears to make progress, and the tool prints only the latest green record while discarding the attempt that actually held the contract. The ledger mechanism is designed specifically to block this.
The execution steps are roughly as follows:
- Create
attempts/001/include,src, andbuilddirectories, and copygolden/frame.hppinto the attempt's include tree before applying the candidate patch. - Place that sample's
payload.cppunderattempts/001/src. If a previous sample has already failed, leave its directory untouched. - Compile the candidate into
payload.o. A non-zero status fails thepatch_compilecolumn and blocks only this attempt from promotion. - Compile
tests/contract_tu.cppagainst the candidate header and new object file. A failingstatic_assertfails thenoexcept_contractcolumn. - Link and run
happy.cpp, comparing stdout byte-for-byte (including the trailing newline) againstgolden/happy.out. - Link and run
malformed.cppwith a short timeout, requiring exit code 0 and a match againstgolden/malformed.out. A signal, escaped exception, or timeout failsmalformed_run. - Append a JSON object to
ledger.jsonlcontaining the attempt id, the hash ofpayload.cpp, and every column. Promotion only happens when the same object marks all required columns as ok. - Retain failing rows. A later sample must pass on its own columns — "attempt 1 stdout passed" plus "attempt 2 contract passed" does not constitute evidence.
Translation unit is the basic compilation unit in C++, corresponding to a .cpp source file together with all the headers it #includes, expanded as a whole. Each translation unit is compiled independently into an object file (.o), which the linker then combines into the final executable or library. The significance of contract_tu.cpp as a standalone translation unit is that it parses the candidate header from its own perspective, simulating the compile environment of a real downstream caller. If a patch changes a header declaration, this translation unit will detect the change at compile time and trigger a static_assert failure — without waiting for the link or run phase. Object file isolation (each attempt has its own build directory) prevents another form of contamination: a .o file compiled in an old attempt being reused by a later link step, causing a "new header + old implementation" combination to silently pass linking while behaving inconsistently.
Declaration, Fixture, and Scorer
In the reviewed contract golden/frame.hpp, the payload_size function is declared noexcept, with a comment explicitly requiring "short frame returns 0, no throw."
The consumer check tests/contract_tu.cpp uses static_assert(noexcept(payload_size(FrameView{}))) and std::is_nothrow_invocable_v. The key point: the noexcept operator checks the declaration the compiler can see — it does not execute the function body. This is why it can catch the case where noexcept has been quietly removed from the header at compile time.
A subtle implementation detail: if a definition is marked noexcept but still throws, the program compiles normally and then calls std::terminate at runtime. Python's subprocess module reports POSIX signals as negative return codes — typically -6 for SIGABRT. The scorer treats every non-zero code as a failing column, so it doesn't special-case 134 or -6. And if the candidate removes noexcept from both the header and the definition, the contract translation unit fails to compile even if both text fixtures still match.
The scoring script grade_attempt.py records all six required columns — patch_compile, noexcept_contract, happy_link, happy_stdout, malformed_link, malformed_run — uniformly. It uses all(...) to determine promotable, and writes the sha256 of payload.cpp into the ledger so reviewers can confirm that the file on disk is the one that was scored.
The noexcept operator and the noexcept specifier are two distinct language mechanisms that are easy to conflate. The specifier appears on a function declaration, telling the compiler "this function won't throw"; the operator is used in an expression and returns a boolean at compile time indicating whether the given expression is declared non-throwing. In noexcept(payload_size(FrameView{})), the compiler only looks at payload_size's declaration — it does not execute the function body. This is a purely static check. std::is_nothrow_invocable_v<F, Args...> is a standard library type trait that relies on the noexcept operator internally and is semantically equivalent but better suited to generic contexts. Placing either of these inside a static_assert means: the moment noexcept is removed from the header declaration, the translation unit produces a hard error at compile time rather than exposing the problem at runtime. This is the critical layering between "compile-time contract" and "runtime behavior."
Reading a Ledger Row
Each ledger row corresponds to an actionable judgment:
- All six columns ok: the same sample held both the declaration and both runs. This is a "review candidate," not an automatic merge.
- happy stdout passes, noexcept contract fails: the header dropped
noexceptwhile text still matched — not promoted. - happy stdout passes, malformed run non-zero: the short frame triggered an abort or escaped exception — not promoted.
- Attempt A passed stdout, attempt B passed contract: stitched evidence — not promoted.
- malformed run timed out: possibly hung, or the timeout on this machine was set too tight — not promoted; requires manual inspection and a re-run of the same attempt.
- payload.cpp missing: incomplete sample — not promoted.
"Promotion" here only means the ledger process exits with status zero; it does not mean maintainers can skip the diff. A failing noexcept_contract row typically ends with a compiler static-assertion message; a failing patch_compile often points to a mismatched exception specification, meaning the header and definition have diverged; a malformed_run failure with a negative return code is the terminate path. Read the sequence before asking the model for the next sample — swapping in a new sample cannot fix a ledger that scored against the wrong header copy.
Who Should Not Use This Gate
The author honestly marks the boundaries of this mechanism — which is exactly why it's trustworthy.
The declaration check is blind to the function body; the malformed process covers one input, not the entire input space; leaks and races need their own independent gates; a five-second timeout conflates a slow disk with a deadlock before you even read that row.
More importantly, there's the question of applicability: codebases that intentionally allow certain functions to throw should not apply static_assert(noexcept(...)) to those functions — every honest edit would fail. Teams needing formal proofs or a complete undefined-behavior audit need tools considerably heavier than "six process columns." Additionally, a change whose explicit purpose is to relax a public contract should not be run against the old golden header — the correct approach is to update golden/frame.hpp and the expected output files in a single reviewed commit, then sample against the new contract. Blocking an approved API modification is itself a process error.
(Disclosure: This article was prepared as part of MonkeyCode product outreach. MonkeyCode's free model access can supply candidate payload.cpp files and replacement headers where permitted, and its free server option can host attempts/ and ledger.jsonl; but the product itself does not score patches — the script does. The script does not call the model or log into the server. Keeping the network steps outside the scorer means an API timeout cannot be recorded as a contract pass.)
Related articles

Why Does the Tesla Roadster Need to Fly? A Deep Dive into Musk's Wild Demo
Tesla's Roadster launch showed wall-climbing, upside-down driving, and flying stunts—but The Vergecast notes the demo car may lack a battery, the Rocket version is invite-only and not street-legal, and the website terms don't guarantee delivery. After nine years of delays, why does this car still need to exist?

AI Is Not a Replacement: Human-AI Collaboration Through the Lens of Factory Root Cause Analysis
Starting from factory root cause analysis, this article explains how AI accelerates evidence chain construction rather than replacing human experts, exploring the pragmatic implementation logic and business value of 'AI supporting people rather than replacing them.'

AI Agent Decision Auditing: Building an Evidence Chain in Regulated Workflows
Starting from a Reddit discussion, this article analyzes three core issues in AI agent decision auditing within regulated workflows: what fields audit records should contain, how to handle feedback latency for erroneous decisions, and how to continuously calibrate LLM judge consistency with human judgment.