Remove dead code from bun_jsc, bun_core, the bindgenv2 codegen, 15 error types, and the C++ bindings - #40915
Remove dead code from bun_jsc, bun_core, the bindgenv2 codegen, 15 error types, and the C++ bindings#40915robobun wants to merge 9 commits into
Conversation
…ters, unused error impls, dead C++ bindings
A relink of the debug build with --gc-sections --print-gc-sections lists
every function nothing in the final binary references. This removes the
ones that are dead on every platform:
- src/jsc/bindgen.rs: the Zig-port Bindgen adapters (BindgenStrongAny,
BindgenNull, BindgenOptional, BindgenString, BindgenArray, ExternTaggedUnion2,
ExternUnion2, ExternArrayList) and their only users Strong::adopt,
bun_alloc::realloc_raw, and the WTFString alias with its
ExternalSharedDescriptor impl.
- src/codegen/bindgenv2: the Zig type emitters (bindgenType, zigType,
optionalZigType, CodeStyle, toZigNamespace). The generator only emits C++.
- Hand-written ErrName impls on 15 crate error types that nothing calls
through the trait, the unused Display impls on bun_md::ParserError,
bun_jsc::SystemError and HTMLImportManifest, Appender::append_lower_case
and its impls, MaxHeapScope Deref impls, BitSet::set_intersection,
Builtins::{from_executable,len,is_empty,modules}, AbortSignal::detach.
- jest.classes.ts: call: true on the seven noConstructor Expect classes only
emitted an unused thunk and extern declaration.
- C++: the unused node:crypto key helpers keyFromString,
passphraseFromBufferSource and the 4/6-argument parseKeyFormat/parseKeyType
overloads, the WebCore::JSErrorHandler class, rejectPromiseWithGetterTypeError,
toJSNewlyCreated(Ref<Blob>), two validateInteger instantiations, and the
uWS run()/listen(port, cb) entry points with their uws_app_run/uws_app_listen
C wrappers.
|
Updated 11:49 PM PT - Aug 29th, 2026
❌ @robobun, your commit 1379860 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 40915That installs a local version of the PR into your bun-40915 --bun |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review. WalkthroughThe pull request removes obsolete public APIs and binding helpers across Rust, TypeScript, and C++ code. It narrows string appender contracts, removes legacy error-name and bindgen surfaces, updates JSC binding behavior, and marks selected Jest asymmetric matchers as non-callable. ChangesAPI cleanup
Merge Risk: ⚪ Minimal · up to This cleanup removes unused code without introducing an actionable merge-blocking risk; it is merge-ready after normal checks and review. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
| construct: false, | ||
| noConstructor: true, | ||
| call: true, | ||
| call: false, |
There was a problem hiding this comment.
In isolation this change looks unrelated and dangerous, and is actually a behavioural change and not removing dead code? What is this change?
There was a problem hiding this comment.
Hi. Full trace is in #40915 (comment). Short version: for a noConstructor: true class, call: true changes no behaviour. The only consumer of <Name>Class__call in generate-classes.ts is the JS<Name>Constructor class (Base(vm, structure, <Name>Class__call, construct)), and generateConstructorImpl is skipped when noConstructor is set (line 1623). So for these seven classes the flag emitted a Rust thunk and a C++ extern declaration that nothing referenced, which is how the linker flagged them.
Regenerating with both flag values and diffing shows only those 7 thunks, 7 declarations and 7 type-only .d.ts interfaces differ. expect.closeTo(...) still calls ExpectCloseTo::call directly (expect.rs:1300), the matcher objects were never callable, and new expect.any(...) threw before and after. Happy to instead teach the generator to ignore call under noConstructor and keep the flags as they were, if you prefer that shape.
There was a problem hiding this comment.
Follow-up in 80b894e: define() in src/codegen/class-definitions.ts now throws on call: true with noConstructor: true, so the generator rejects the inert combination instead of silently emitting an unused thunk. The old jest.classes.ts fails that check; the current one passes and generates byte-identical output.
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline finding, I grepped for remaining callers/externs of the other deleted C++ symbols — JSErrorHandler, keyFromString/passphraseFromBufferSource, toJSNewlyCreated(Ref<Blob>), rejectPromiseWithGetterTypeError, and the two validateInteger JSValue-name instantiations — and found none; the uWS pair is the only removal that leaves a dangling Rust extern "C" declaration.
Extended reasoning...
The confirmed finding flags uws_app_run/uws_app_listen being deleted from libuwsockets.cpp while src/uws_sys/App.rs still declares and calls them. Since this is a gc-sections-driven sweep, the obvious question is whether the same dangling-reference class applies to any of the other C++ deletions. I checked each removed C++ symbol against the full src/ tree and none of JSErrorHandler, the CryptoUtil helpers, the Blob toJSNewlyCreated overloads, rejectPromiseWithGetterTypeError/CastedThisErrorBehavior::RejectPromise, or the dropped validateInteger explicit instantiations have surviving references, so the uWS case appears to be the only instance of that bug class in the diff. Also confirmed Bun__setSyntheticAllocationLimitForTesting is still reachable via dispatch_js2native.rs after the #[host_fn(export = ...)] attribute was dropped.
|
Good question, and I should have spelled this out in the body. It is not a behaviour change. Here is what
Effect 3 is the only consumer of the symbol, and it lives in I regenerated the classes with both flag values and diffed the output. The only differences are the seven The Rust Runtime check, same file against the release binary and the debug build with this change: expect(1.005).toEqual(expect.closeTo(1, 2));
expect({ a: 1 }).toEqual({ a: expect.any(Number) });
const m = expect.closeTo(1, 2);
expect(typeof m).toBe("object");
expect(() => (m as any)()).toThrow();
expect(() => new (expect.any as any)(Number)).toThrow();Both pass identically. The asymmetric matcher instances were never callable and If you would rather keep the flags and have the generator skip the |
src/uws_sys/App.rs still declares uws_app_run and uws_app_listen. Removing only the C++ definitions leaves dangling extern declarations.
|
4a96393 restores the uWS |
…, and ten inert host_fn shims The remaining Bindgen trait and BindgenTrivial adapter had no users either. The ErrName impls on the bun_brotli, bun_clap, bun_css, bun_router and bun_uws_sys error types are never called through the trait. A bare #[bun_jsc::host_fn] on a function that dispatch_js2native.rs calls directly only emits an unused __jsc_host_* shim. define() now rejects call: true together with noConstructor: true, since the constructor class is the only consumer of the call target.
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🟡
src/jsc/generated.rs— nit: this doc comment still namesBindgenOptional(BindgenTrivial<T>).ExternType, but commit 80b894e deletes the wholesrc/jsc/bindgen.rsmodule and scrubbed the three other such references in this file (lines 161, 181-182, 447); this one was missed and now points at types that no longer exist. Fix: reword to describe the extern layout directly, matching the updated comment onExternArrayLista few lines below.Extended reasoning...
The final commit in this PR removes
src/jsc/bindgen.rsentirely (the module declaration is dropped fromsrc/jsc/lib.rs:461) and, in the same filesrc/jsc/generated.rs, rewrites three comments that previously referencedbindgen.ExternArrayList,BindgenOptional(BindgenString).ExternType, andBindgenNullso they no longer name deleted types. Line 132's/// BindgenOptional(BindgenTrivial<T>).ExternType — ...was left untouched. On the base branch those types exist and the reference is accurate; after merging, a reader following the doc comment finds noBindgenOptionalorBindgenTrivialanywhere in the tree. No behavioural impact — documentation only — but it is the one instance the sweep in this PR missed while explicitly fixing its siblings.Verification: nit — Commit 80b894e deletes
src/jsc/bindgen.rsoutright (module declaration removed fromsrc/jsc/lib.rs:461;git show HEAD:src/jsc/bindgen.rs→ "does not exist"). In the same commit,src/jsc/generated.rsrewrites the three other bindgen-type references: line 161 (bindgen.ExternArrayList(T)→ "Extern layout of a C++ vector handed to Rust"), lines 181-182… | nit — The stale…
| if (!thisObject) [[unlikely]] { | ||
| if constexpr (shouldThrow == CastedThisErrorBehavior::Throw) | ||
| return JSC::throwVMDOMAttributeGetterTypeError(&lexicalGlobalObject, throwScope, JSClass::info(), attributeName); | ||
| else if constexpr (shouldThrow == CastedThisErrorBehavior::RejectPromise) |
There was a problem hiding this comment.
can you replace this with a static_assert false or similar? if we ever add something that uses this it would silently now owrk.
There was a problem hiding this comment.
Done in eeba22a: get() now opens with static_assert(shouldThrow != CastedThisErrorBehavior::RejectPromise, ...), so any getter instantiated with that behavior fails the build instead of falling through to the ReturnEarly path. Builds clean with bun bd; the abort, message-event and url tests pass.
The RejectPromise branch is gone, so a getter instantiated with that behavior would fall through to the ReturnEarly path and return undefined. The static_assert turns that into a build error.
The previous commit re-added them by accident.
…llections, and three JS builtins (#42069) ### Problem - 25 dead-code PRs are already open, so a name grep finds little that is unclaimed. This run used the compiler instead: demote each `pub` item to `pub(crate)` and let rustc's `dead_code` lint say what nothing uses. - What survived that check on all nine CI target triples, and is not already in an open PR, is the list below. ### Fix - `bun_runtime::Error`: remove 14 variants that nothing constructs (`FmtError`, `ERR_TLS_CERT_ALTNAME_INVALID`, `ConnectionClosed`, `MissingCredentials`, `InvalidMethod`, `InvalidEndpoint`, `InvalidSessionToken`, `SignError`, `MissingPackageJSON`, `MissingEntryPoint`, `lcovCoverageError`, `CompilationFailed`, `JSErrorObject`, `Unsupported`), their `name()` arms, the two match arms that tested for them (`cli/mod.rs`, `jsc_hooks.rs`), and a `cfg(not(macos))` block inside a `cfg(macos)` function in `webview/HostProcess.rs`. - `bun_jsc`: remove `CrateError::JSErrorObject` (its only producer was the arm above) and the three never-called `HotReloadTaskView` methods. The trait stays as the type-erasure marker that `HotReloaderCtx::reload` takes. - `bun_collections`: `StringHashMap::values_mut` has no caller in any crate. - JS builtins: a 23-line commented-out `fileURLToPath` in `node/url.ts` (unchanged since 2025-01), the unused `format` / `formatWithOptions` getters in `internal/repl/node-inspect.js`, and the unused `reportUncaughtException` export in `internal/shared.ts`. - Verified: `cargo check --workspace` on all nine triples from `rust:check-all`, `bun bd`, then `repl.test.ts`, `readline.node.test.ts`, `hot.test.ts`, `watch.test.ts`, and the `node/url` tests. ### Background - The workspace denies `dead_code` and `unreachable_pub`, so rustc already rejects unused private items. The blind spot is a `pub` item that is reachable from its crate root but that no other crate imports. Demoting it to `pub(crate)` puts it back under the lint. - A demoted inherent method can silently lose to a trait method of the same name in other crates (`Blob::finalize` against the blanket `JsFinalize`, `__IsFreeze::IS_FREEZE` against `__NotFreeze`). rustc then reports the inherent item as dead while behavior changed. Every method hit was checked for a same-named path in other crates and those were left alone. <details><summary>Notes</summary> - Scope: 87 Rust crates (everything under `src/` except the proc-macro crates), `src/js`, the C++ `extern "C"` definitions in `src/jsc/bindings`, `Cargo.toml` dependencies, and orphan `.rs` files (via cargo dep-info). No unused dependencies and no orphan files were found. - Found dead but already removed by an open PR, so not repeated here: the `bindgen.rs` marker structs and `ExportRenamer` (#40915, #40557), the `uws_sys`/`mimalloc_sys`/`lsquic_sys` externs (#40172), `has_termination_request`, `clear_exception`, `from_typed_array`, `INTERNAL_MODULE_REGISTRY_FLAG`, and the 20 orphaned C++ `extern "C"` functions (#40232). - Probably dead, left out of the diff: 119 never-constructed `Feature` variants in `src/css/prefixes.rs` (the file is generated by `build-prefixes.js`), the never-constructed `bake::Mode::ProductionDynamic`, and several struct fields rustc reports as never read that exist to own an allocation (`JSTranspiler::arena`, `CurrentBundle::{bv2, heap, ast_alloc_state}`). - rustc's lint misreports inherent associated types (`ThreadPool::Worker`, `EntryPoint::Kind`) as unused. They are used and were kept. - `bun_core::strings::split_once` (the multi-byte variant) has no caller, but `clippy.toml` names it as the replacement for `str::split_once` and `bstr`'s `split_once_str`, so it stays. </details>
Problem
dead_codeandunreachable_pub, and the open dead-code PRs hold the cross-cratepubfindings. What remains is invisible to both: trait impls nothing calls, helpers whose only callers are dead, and C++ functions that only their own declarations reference.--gc-sections --print-gc-sectionslists every function the binary does not reference. Each candidate was then checked against every platform,constuse, tests, derives and the open PRs. 73 files, +88 / -1317 (the insertions are the new test and thedefine()guard).Fix
src/jsc/bindgen.rsmodule with its only users, 20ErrNameimpls nothing calls through the trait, threeDisplayimpls,Appender::append_lower_case, ten inert#[host_fn]shims, and small unused helpers (Notes).src/codegen/bindgenv2still carried the Zig type emitters. The script only writes C++, and its output is byte-identical before and after.WebCore::JSErrorHandlerclass,rejectPromiseWithGetterTypeError,toJSNewlyCreated(Ref<Blob>), and twovalidateIntegerinstantiations.bun bdbuilds. Newtest/internal/class-definitions.test.tscovers thedefine()guard and the jest class flags (fails on the basesrc/codegen).bun bd teston 12 files in the touched areas (1,061 pass; the 6 failures reproduce with a binary linked from the unchanged objects).bun run rust:check-allpasses on every target. CI on 1379860: 181 of 182 jobs pass; the red one isurl.test.tson darwin x64, which fails on main too (ICU 76 table). Self-reviewed: 11 concerns raised, 9 addressed (Notes).Background
dead_codelint runs per crate and treatspubitems and trait impls as roots, so apub fnthat only a deadpub fncalls passes the lint.-O0with one section per function, so a discarded section has no caller at all. A Linux link cannot see platform-gated callers, so every textual reference was traced by hand.ErrName(bun_core::output) names an error kind for the root error printer.JSErrorHandleris WebKit's five-argumentonerrorlistener; Bun'sonerrorsetters all useJSEventListener.Notes
Removed, Rust:
src/jsc/bindgen.rs(file deleted,pub mod bindgendropped fromsrc/jsc/lib.rs): theBindgentrait and every adapter (BindgenTrivial,BindgenStrongAny,BindgenNull,BindgenOptional,BindgenString,BindgenArray,ExternTaggedUnion2,ExternUnion2,ExternArrayList). Nothing outside the file names the module; the generated code insrc/jsc/generated.rsdefines its ownExternArrayList. Three comments ingenerated.rsthat described layouts by these names now describe the layout. Remove dead code from bun_core, bun_jsc, bun_css, react_compiler, and two C++ bindings #40367 removesBindgenTrivialfrom this file and rewords one moregenerated.rscomment; whichever PR lands second drops that hunk on rebase.bun_jsc::Strong::adopt(onlyBindgenStrongAnyused it),bun_alloc::realloc_raw(onlyBindgenArray),bun_core::WTFStringwithimpl ExternalSharedDescriptor for WTFStringImplStructand the re-exports inbun_core::lib,bun_core::wtf,bun_core::string::wtf,bun_ptr(onlyBindgenString).impl ErrName for Errorinbun_ast,bun_js_parser,bun_js_parser_jsc,bun_js_printer,bun_exe_format,bun_dotenv,bun_shell_parser,bun_io(plus its inherentname()),bun_libarchive(plus inherentname()),bun_options_types,bun_spawn_sys,bun_sourcemap,bun_standalone_graph,bun_crash_handler,bun_brotli,bun_clap,bun_css,bun_router(plus inherentname()),bun_uws_sys(plus inherentname()), andForManifestErrorinbun_install.ErrNamehas three generic consumers (Output::err,JSGlobalObject::throw_error,handle_root_error); none is instantiated with these types on any target (rust:check-allwould fail otherwise). Every escape point of each type was traced: callers use?into a#[from]variant,.is_err(),let _ =,matchon variants, or the inherentname().impl Display for bun_md::ParserErrorwith its emptyimpl Error(consumers match on variants),impl Display for bun_jsc::SystemError(consumers callto_error_instance; the liveDisplayis onbun_sys::SystemError),impl Display for HTMLImportManifest(onlyEscapedJsonis formatted).Appender::append_lower_case(trait method never called through the trait) with the impls inbun_install::FilenameStoreAppender,bun_resolver::FilenameStoreAppender,pm_diff_command::BumpAppender, and the wholeimpl Appender for &FilenameStoreinbun_resolver(every call site passes aFilenameStoreAppenderorBumpAppender).MaxHeapScopeDeref/DerefMut(the onescope()caller keeps the guard forDroponly),ArrayBitSet::set_intersectionandAutoBitSet::set_intersection,Builtins::{from_executable, len, is_empty, modules}(consumers useparse,module,find,dependencies),AbortSignal::detach.#[crate::host_fn(export = "Bun__setSyntheticAllocationLimitForTesting")]and the bare#[bun_jsc::host_fn]on ten functions thatdispatch_js2native.rscalls directly (get_active_tasks,js_escape_reg_exp,js_escape_reg_exp_for_package_name_matching,to_utf16_alloc_sentinel,patch_make_diff,patch_apply,patch_parse,translate_nt_status_to_e,translate_uv_error_to_e,sigaction_layout). The attribute only emits a__jsc_host_*shim that nothing references; the functions stay.jest.classes.ts:call: trueon the sevennoConstructor: trueExpect classes. Not a behaviour change:generate-classes.tsonly consumes<Name>Class__callin theJS<Name>Constructorclass, whichnoConstructorsuppresses, so the flag emitted a thunk and a declaration nothing referenced. The matchers keep their inherentcall(), whichexpect.any(...)et al. invoke directly. The classes:ExpectAny,ExpectAnything,ExpectArrayContaining,ExpectCloseTo,ExpectObjectContaining,ExpectStringContaining,ExpectStringMatching. Re-runninggenerate-classes.tsbefore and after shows exactly those 7 thunks, 7 declarations and 7 type-only.d.tsinterfaces gone.ExpectandExpectTypeOfkeepcall: trueand still wire their__call.Added:
define()insrc/codegen/class-definitions.tsnow rejectscall: truetogether withnoConstructor: true, so the inert combination cannot come back.Removed, codegen (
src/codegen/bindgenv2):Type.bindgenType,Type.zigType,Type.optionalZigType,CodeStyle, every subclass implementation ininternal/*.ts, thebindgenOptional()helper, andtoZigNamespacewith the second duplicate-name check it fed (the first check ontype.namestays). The 14 generated files from the 3.bindv2.tssources are byte-identical;test/internal/build-codegen-declared-outputs.test.tspasses.Removed, C++:
src/jsc/bindings/node/crypto/CryptoUtil.cpp/.h:keyFromString,passphraseFromBufferSource(onlykeyFromStringcalled it), the 4-argumentparseKeyFormatand 6-argumentparseKeyTypeoverloads (live code uses theThrowScope&overloads).src/jsc/bindings/webcore/JSErrorHandler.cpp/.h(files deleted), the includes inEventEmitter.cppandEventTarget.cpp, and thesetAttributeEventListener<JSErrorHandler>instantiation.JSErrorHandler::createhad no caller.rejectPromiseWithGetterTypeError(JSDOMExceptionHandling.cpp/.h) and theCastedThisErrorBehavior::RejectPromisebranch inIDLAttribute::getthat was its only reference (no attribute instantiates it).get()nowstatic_asserts that it is never instantiated withRejectPromise, so a future user gets a build error instead of theReturnEarlyfallthrough.toJSNewlyCreated(..., Ref<Blob>&&)and the inlineRefPtr<Blob>forwarder (blob.cpp/.h); Blob wrappers go throughtoJS(..., Blob&).NodeValidator.cpp: thevalidateInteger<size_t>andvalidateInteger<uint32_t>JSValue-name explicit instantiations (the only JSValue-name caller usesssize_t).Tests run:
test/js/bun/test/expect.test.js,expect-extend.test.js,test/js/node/crypto/crypto.key-objects.test.ts,crypto-sign-regression.test.ts,test/js/web/workers/worker.test.ts,message-event.test.ts,test/js/web/abort/abort.test.ts,test/js/web/fetch/blob-cow.test.ts,test/js/web/html/blob-array-fast-path.test.ts,test/js/node/events/event-emitter.test.ts,test/js/bun/http/serve.test.ts,test/cli/install/bun-add.test.ts. The 2 worker failures (message flood timing, preload import timeout) and 4 serve failures (IPv6, root port range, #6583, /bun:info loopback) reproduce with the pre-change relinked binary in this container.Overlap with open PRs: no deletion here repeats one in #39929, #40122, #40172, #40232, #40294, #40367, #40492, #40525, #40557, #40690, #40762, #40824 or #40881. Files shared with them, at other hunks:
src/js_parser/error.rs(#40525),src/bun_core/string/immutable.rs(#40525, #40824),src/bundler/HTMLImportManifest.rs(#40762),src/jsc/AbortSignal.rs(#40232, #40824),src/jsc/virtual_machine_exports.rs(#40557),src/jsc/bindgen.rs(#40367),src/jsc/bindings/node/crypto/CryptoUtil.cpp/.h(#40232).JSErrorHandler.cppis deleted here while #39929 edits one line of it.Left alone on purpose (linker-dead on Linux, live elsewhere or by design):
Bun__Process__hasTitle,getMainThreadScriptExecutionContext,uws_get_loop_with_native,WStr,fmt_path_u16,write_windows_env_block,Dir::copy_file,File::kind,WaitGroup(Windows);Blob__fromMmapWithTypeand theinit_mmapchain,bun_sysconf__SC_CLK_TCK,posix_spawnattr_reset_signals,add_pre_exit_callback,FilePoll/FlagsFormatterDisplay,Bun__noOrphans_*(macOS/BSD);bun_analytics::*,inflate_embedded*(release only);ScopedLogger::new,hash_const,RawRwLock::new,Semaphore::new, thecomptime_string_map!andenumsethelpers (constcontexts); everythiserror/strum/bitflagsderive;btjs/dumpBtjsTrace(lldb helpers);impl Drop for Once<T>andGarbageCollectionController(never dropped today, but correct); the V8 and NAPI API (exported for native modules);PerformanceResourceTimingand friends (web-visible globals).Self-review: 11 concerns, 9 addressed in 80b894e (delete
bindgen.rsoutright, the five extraErrNameimpls, the ten inerthost_fnshims, thedefine()guard) and 4a96393 (uWS). Not taken: re-slicing theJSErrorHandler, uWS andbindgen.rshunks into #39929, #40294 and #40367 (those PRs are not mine to edit; theJSErrorHandler.cppmodify/delete against #39929 resolves as "deleted" on rebase), and waiting for #39929 to land first.Follow-ups that become possible once open PRs land:
NameMinifier::default_number_to_minified_name(#40557 removes its only caller), the uWSrun()/listen(port, cb)entry points and theiruws_app_run/uws_app_listenC wrappers (#40172 removes the RustApp::run/App::listenthat declare them; the linker discards all of them today),Stat<BIG>::get_constructor'sBun__JSBigIntStatsObjectConstructorbranch (never monomorphized).Method:
clang++ @inputs -Wl,--gc-sections -Wl,--print-gc-sectionsover the existing debug objects, without-rdynamic, thenllvm-cxxfilton the removed.text.*sections and a diff againstllvm-nmof the kept symbols.[review] gate passed · iteration 4 · 73 files touched
fails on main (without fix)
passes on PR (with fix)
diff hotspot
gate history · 3 passed · 1 rejected · iteration 4
evidence per changed file