ONCE Campfire's own Minitest suite, run against a Roundhouse emit of the same commit. A failure here mostly names a Rails construct not yet modeled; some are answers that came out wrong with nothing raised, and some have not been triaged at all. That makes their tests the worklist generator — so the ranked causes below, not the pass count, are what this page is for.
scripts/campfire-suite and runs on Ruby 3.4.10.
The input is pinned at 94a48aacb65a, which is the whole reason this lane
runs in CI on every commit: with campfire held still, every movement in the number belongs to a
Roundhouse change and nothing else. Bumping the pin moves the number deliberately, and the commit
that bumps it owns the delta. (The lobsters conformance lane makes
the opposite call — it tracks upstream HEAD and pins nothing, so it cannot attribute per commit and
runs on a schedule instead.)
The file list is theirs. Every test file campfire's own suite declares, read from the emitted Makefile's SPINEL_TESTS rather than restated here — a `find test -name '*_test.rb'` would also sweep in the scaffold's own tests, which are not campfire's.
Each run stands alone. There is no history here and no comparison to previous runs: a chart of a number that moves for a dozen reasons is not the point of a worklist.
Occurrences churn; causes are the unit of work, so each carries a stable slug you can still refer to forty commits later. Owner says who can act: most of this is Roundhouse's own modeling debt.
Counts are how many tests are currently stopped by each cause, not how many will pass when it lands: a test is attributed to the first thing that stopped it, so clearing one cause routinely reveals another behind it. Each unpassed test carries its own message; a file that never ran contributes its whole count against its load error.
| Blocked | Cause | Owner | |
|---|---|---|---|
| 8 | The emit ran and answered differentlysilent-divergence | untriaged | |
NOT a root cause — the bucket of tests that still need one. Nothing raised: the emitted code ran to completion and produced a different answer, so each of these has to be followed down its own chain. This is the most valuable cluster on the page and the most expensive: a missing method names itself, a wrong answer does not.
5 of 8 tests
| |||
| 6 | An emitted method's arity does not match its callerarity-divergence | roundhouse | |
The receiver exists and the name resolves; the shape does not. Usually a lowering that dropped or added a parameter — kwargs folded into a positional, a block-form helper emitted in its non-block shape. Each needs the emitted definition read against its call site.
5 of 6 tests
| |||
| 4 | Active Storage — the attachment scopes and the bytesactive-storage-attachments | roundhouse | |
The metadata half answers (205c3031). What is left is the macro-generated preload scopes (`with_attached_*`, `with_rich_text_*`) and everything variant-shaped: `ActiveStorage::VariantWithRecord`, `representation` and `preview` on an `Attached`, and the bytes. Avatars and logos reach it now that the fixture-file helpers in front of them answer — clearing a wall ahead of this cause moves tests INTO it, which is what just happened.
4 tests
| |||
| 2 | `ActionText::Attachment.from_node` — building an attachment from a parsed nodeactiontext-attachment-from-node | roundhouse | |
Named in docs/pipeline/runtime.md's "What is left" for attachments, alongside `ActionText::Attachables::MissingAttachable`. The PARSE already works — `#attachments` returns every node with every attribute it carried — so this is the constructor that turns one of those nodes back into an attachment object, plus the missing-attachable case a stale sgid should land in.
2 tests
| |||
| 2 | A gem the emit does not carryout-of-scope-gems | gem | |
web push, concurrent-ruby, and sentry-ruby. The RQRCode arm of this rule matches nothing now that qr_code_controller_test is green. Each is a decision about gem fate rather than modeling debt — shim the surface the app calls, or declare it out of scope and ledger it. None of them is on the path to more coverage than the tests naming it.
2 tests
| |||
| 1 | A fixture accessor the emitted loader never definedfixture-accessors | roundhouse | |
`users(:david)`-style accessors are generated per fixture file. Where one is missing the test cannot name its own data, so nothing behind it has been seen. The name comes from the app's own fixtures rather than from Rails, which is what separates this from the emitted test base class's own surface.
1 test
| |||
| 1 | A params hash interpolated into the request PATHparams-hash-in-path | roundhouse | |
The request went out as `POST /users/{params: {push_subscription: {…}}}` — the params object rendered into the path instead of being sent as the body. Bracket-nested query parameters are the shape behind it: the test writes `params: { push_subscription: { endpoint: …, keys: { … } } }` and nothing splits the nesting into a body. Reads as a routing error, which is why it sat untriaged.
1 test
| |||
| 1 | `allow_browser` — the modern-browser gateallow-browser | roundhouse | |
campfire's own concern calls `allow_browser versions: VERSIONS, block: -> { render template: "sessions/incompatible_browser" }`, so the version FLOOR is the app's (`{ safari: 17.2, chrome: 120, … }`) and what is missing is ours: the macro, its `block:` fallback, and parsing a User-Agent into a browser plus version. Rails reads that last part from the `useragent` gem's rule table — PORT the table, do not derive it. The file is 7/8 on this one test.
1 test
| |||
| 6 | Not yet triagedunclassified | untriaged | |
No rule in bench/campfire/suite-causes.json matches these. Triage, then add a rule.
6 tests
| |||
A rule at zero is kept, not deleted: it is the tripwire that catches the fix coming undone. What it means depends on why it is at zero.
| Rule | Why it reads zero |
|---|---|
id-zero-sentinel | zero is the intended reading. This is a deliberate divergence rather than a gap, and the rule exists to catch the day it becomes visible again. |
pagination | landed in 0ef8fb79; the rule stays as a regression tripwire. |
conditional-get | `fresh_when` / `stale?` landed in 7ea262e2; the rule stays as a regression tripwire. |
turbo-streams-channel | the constant landed in fd1a3259; the rule stays as a regression tripwire. |
sti-becomes | `becomes!` landed in f2d42854; the rule stays as a regression tripwire. |
test-harness-surface | the family landed over several commits, `fixture_file_upload` last in f86ac0b6; the rule stays as a regression tripwire. |
Held at zero by the harness, not by the compiler — the stub ledger below covers what would otherwise fail here: belongs-to-autosave. Deleting the stub is what makes these counts real.
No explanation recorded, so these are the open question: either the cause is cleared and nobody wrote that down, or the failure's message changed and it is now being counted as untriaged above. route-helper-takes-a-record, view-helper-in-helper-module, broadcast-assertions, rails-module-facade, globalid-sgid, relation-vs-array, params-object-representation.
scripts/campfire-walk-stubs.rb is spliced into the emitted app before the count above is taken. Each of its 5 entries stands for something the compiler cannot produce yet — a gem parked by design, or a gap ledgered elsewhere — and buys the suite a few more steps rather than fixing anything. A gap closed in the compiler is an entry deleted there, so the length of this list is itself a progress report, and the pass rate above should be read as "with these in place".
| Stub | Why it is there |
|---|---|
| belongs_to AUTOSAVE — `room.creator = User.new(...)` then `room.save!` saves the unsaved target first in Rails | ×2, both COMPILER gaps already on the ledger (milestone walk finding #6 and #3), patched together because they sit in one method. We read `value.id` (0) and the save fails `Creator must exist`, which is the first wall of the whole walk. * a `has_many … do … end` EXTENSION BLOCK is dropped with no diagnostic, so `room.memberships.grant_to` does not exist anywhere in the emit. Rewritten here the way Rails would sequence it. This is app logic, not a library stand-in — the loudest kind of ledger entry, and the first two to delete. |
| Active Storage | `has_one_attached :logo` on Account and `has_one_attached :avatar` on User are not modeled, and the reader is absent — so `Current.account.logo.attached?` takes down the LAYOUT and rooms/show's nav, i.e. every page, not just upload flows. Milestone walk finding #8, which argues that a facade answering `attached? == false` is worth separating from variants and uploads. This is that facade, in the crudest form: nothing is ever attached. |
| `belongs_to :creator, class_name: "User", default: -> { Current.user }` — the DEFAULT LAMBDA is dropped, so… | `belongs_to :creator, class_name: "User", default: -> { Current.user }` — the DEFAULT LAMBDA is dropped, so nothing sets `creator_id` and every message fails `Creator must exist`. Milestone walk finding #6. The association scope itself works (`@room.messages.create_with_attachment!` carries `room_id` through `where_scope` / `scope_attributes`), so this lambda is the whole of what is missing. Rails evaluates the lambda at INITIALIZATION; this stands in for it at validation time, which is close enough to measure what lies behind. |
| A VIEW HELPER THAT READS A CONTROLLER IVAR | campfire's `link_to_edit_room(room)` ignores its own argument and uses `@room` — legal in Rails, where a helper runs in the view's context and shares the controller's ivars. Our helpers lower to module functions with no ivar context at all, so `@room` is nil and the room page dies in its nav. The emit even passes `room` in; nothing connects the two. |
| `pluck` on a folded association (`room.memberships.pluck(:user_id)`) | Same family as `find_by!` above and `index_by` further up — an ActiveRecord /ActiveSupport collection method the folded Array does not answer. |
A file that never ran is not the same as a file whose tests failed: it died at load, so its whole count is charged to one error and nothing behind that error has been seen yet.
| File | Tests | Pass | Fail | First error |
|---|---|---|---|---|
test/controllers/messages_by_bots_controller_test | 23 | 17 | 6 | FAIL Messages::ByBotsControllerTest#test_create_file: undefined method 'strip' for an instance of ActionDispatch::Http::UploadedFile |
test/models/push_subscription_test | 17 | 12 | 5 | FAIL Push::SubscriptionTest#test_endpoint_resolution_is_deferred_from_the_enqueue_path_to_the_delivery_worker: wrong number of arguments (given 1, expected 0) |
test/models/room_push_test | 5 | 0 | 5 | FAIL Room::PushTest#test_deliver_new_message_to_other_room_users_with_push_subscriptions: uninitialized constant Net::HTTP::Persistent |
test/models/action_text_attachment_test | 3 | 0 | 3 | FAIL ActionTextAttachmentTest#test_lookup_user_attachable_with_invalid_sgid: undefined method 'from_node' for class ActionText::Attachment |
test/models/message_attachment_test | 3 | 0 | 3 | FAIL Message::AttachmentTest#test_creating_a_message_creates_image_thumbnail: undefined method 'representation' for an instance of ActiveStorage::Attached |
test/controllers/users_push_subscriptions_controller_test | 6 | 4 | 2 | FAIL Users::PushSubscriptionsControllerTest#test_touch_existing_subscription: No route matches POST /users/{params: {push_subscription: {"endpoint" => "https:// |
test/models/account_test | 4 | 2 | 2 | FAIL AccountTest#test_settings: assert_equal failed |
test/controllers/first_runs_controller_test | 4 | 3 | 1 | FAIL FirstRunsControllerTest#test_create_is_not_vulnerable_to_race_conditions: uninitialized constant Concurrent::CyclicBarrier |
test/controllers/sessions_controller_test | 8 | 7 | 1 | FAIL SessionsControllerTest#test_new_denied_with_incompatible_browser: expected "h1" in response body |
test/controllers/users_avatars_controller_test | 4 | 3 | 1 | FAIL Users::AvatarsControllerTest#test_show_image: assert_equal failed |
test/models/user_avatar_test | 3 | 2 | 1 | FAIL User::AvatarTest#test_avatar_variant_is_a_resized_variant_of_a_variable_avatar: uninitialized constant ActiveStorage::VariantWithRecord |
test/models/webhook_test | 6 | 5 | 1 | FAIL WebhookTest#test_delivery_with_ok_attachment_reply: wrong number of arguments (given 1, expected 3) |
test/controllers/accounts_bots_controller_test | 5 | 5 | 0 | all green |
test/controllers/accounts_bots_keys_controller_test | 1 | 1 | 0 | all green |
test/controllers/accounts_controller_test | 4 | 4 | 0 | all green |
test/controllers/accounts_custom_styles_controller_test | 3 | 3 | 0 | all green |
test/controllers/accounts_join_codes_controller_test | 2 | 2 | 0 | all green |
test/controllers/accounts_logos_controller_test | 6 | 6 | 0 | all green |
test/controllers/accounts_users_controller_test | 3 | 3 | 0 | all green |
test/controllers/autocompletable_users_controller_test | 4 | 4 | 0 | all green |
test/controllers/messages_boosts_by_bots_controller_test | 12 | 12 | 0 | all green |
test/controllers/messages_boosts_controller_test | 2 | 2 | 0 | all green |
test/controllers/messages_controller_test | 15 | 15 | 0 | all green |
test/controllers/qr_code_controller_test | 1 | 1 | 0 | all green |
test/controllers/rooms_closeds_controller_test | 9 | 9 | 0 | all green |
test/controllers/rooms_controller_test | 5 | 5 | 0 | all green |
test/controllers/rooms_directs_controller_test | 6 | 6 | 0 | all green |
test/controllers/rooms_involvements_controller_test | 5 | 5 | 0 | all green |
test/controllers/rooms_opens_controller_test | 9 | 9 | 0 | all green |
test/controllers/rooms_refreshes_controller_test | 1 | 1 | 0 | all green |
test/controllers/searches_controller_test | 5 | 5 | 0 | all green |
test/controllers/sessions_transfers_controller_test | 2 | 2 | 0 | all green |
test/controllers/unfurl_links_controller_test | 5 | 5 | 0 | all green |
test/controllers/users_bans_controller_test | 7 | 7 | 0 | all green |
test/controllers/users_controller_test | 6 | 6 | 0 | all green |
test/controllers/users_profiles_controller_test | 3 | 3 | 0 | all green |
test/controllers/users_sidebars_controller_test | 3 | 3 | 0 | all green |
test/controllers/welcome_controller_test | 2 | 2 | 0 | all green |
test/models/account_joinable_test | 2 | 2 | 0 | all green |
test/models/block_banned_requests_test | 3 | 3 | 0 | all green |
test/models/first_run_test | 3 | 3 | 0 | all green |
test/models/membership_test | 9 | 9 | 0 | all green |
test/models/message_searchable_test | 3 | 3 | 0 | all green |
test/models/message_test | 3 | 3 | 0 | all green |
test/models/opengraph_document_test | 3 | 3 | 0 | all green |
test/models/opengraph_fetch_test | 11 | 11 | 0 | all green |
test/models/opengraph_location_test | 7 | 7 | 0 | all green |
test/models/opengraph_metadata_test | 10 | 10 | 0 | all green |
test/models/room_test | 6 | 6 | 0 | all green |
test/models/rooms_direct_test | 3 | 3 | 0 | all green |
test/models/rooms_open_test | 2 | 2 | 0 | all green |
test/models/user_bot_test | 4 | 4 | 0 | all green |
test/models/user_role_test | 3 | 3 | 0 | all green |
test/models/user_test | 4 | 4 | 0 | all green |
From the transpile that produced the tree under test: 1,098 files, 0 errors, 1,031 warnings. Warnings are a modeling-debt ledger rather than a failure — the Ruby target emits through its gaps — so the COUNTS are not comparable to the test failures above.
They are not independent of them either. send_dispatch_failed in particular names a call the analyzer could not resolve, and three of the fixes on this lane came out of those lines before the suite reached them at all — the analyzer had already written the diagnosis. Read the emit log before the fail log.
| Diagnostic | Count |
|---|---|
gradual_untyped | 372 |
unresolved_type | 281 |
lower_residue | 213 |
unsupported | 128 |
blank_unlowered | 36 |
missing_preload | 1 |