ONCE Campfire's own Minitest suite, run against a Roundhouse emit of the same commit. Most failures name a Rails construct not yet modeled; some are answers that came out wrong, and a few are this harness's own gaps. 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 2aa414107714, 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 | |
|---|---|---|---|
| 15 | The emitted test base class is missing Rails' test surfacetest-harness-surface | roundhouse | |
Not the app's code failing — the scaffolding around it. `file_fixture` and `fixture_file_upload` read from test/fixtures/files, `response` is the integration-test response object, and the bare `assert` family comes from Minitest itself. Every one of these is a method the emitted TestBase would have to answer before the assertion behind it is even reached, so this cluster hides an unknown number of further walls.
5 of 15 tests
| |||
| 7 | 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 7 tests
| |||
| 5 | A params object read as a Hashparams-object-representation | roundhouse | |
The emit gives each permitted set its own type (`RoomParams`), and the app then indexes it, deletes from it, or hands it somewhere expecting a Hash. Both directions are the same modeling question — what a params object IS — and it is the one the indifferent-access design addresses.
5 tests
| |||
| 5 | A relation answering as an Array, or an Array asked for relation methodsrelation-vs-array | roundhouse | |
Both directions of one gap: a query lowered to an Array is then asked for `find_by`/`new`/a dynamic finder, or a Relation is asked for an Enumerable method the shim does not carry. `Model.where(...)` is a relation, not an array of records (eb57882f) — this is the residue of that same distinction.
5 tests
| |||
| 5 | Global IDs — minting and dereferencing an sgidglobalid-sgid | roundhouse | |
`Message#mentionees` dereferences an sgid out of rich text (`body.body.attachables.grep(User)`), so the ledger line in docs/pipeline/runtime.md that says nothing here dereferences one is now false. Needs mint plus resolve plus a name-keyed class dispatch — a body of work of its own, not a method.
5 tests
| |||
| 5 | `Rails.configuration` and the routes objectrails-module-facade | roundhouse | |
The emitted `Rails` module answers a small surface; the app reaches past it for configuration and for the application's route set (to build URLs outside a request). Each addition here is a decision about what part of Rails' global state the port models at all, rather than a missing method.
5 tests
| |||
| 3 | Broadcast assertions over a queue that is a logbroadcast-assertions | roundhouse | |
The emit records broadcasts into `Broadcasts.log` rather than delivering them through a cable adapter, so the app's own `assert_broadcasts` counts a different thing than it was written against. Ties into the cable work — the dispatch half, not the streaming half.
3 tests
| |||
| 3 | A gem the emit does not carryout-of-scope-gems | gem | |
QR codes, web push, concurrent-ruby. 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.
3 tests
| |||
| 2 | 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.
2 tests
| |||
| 2 | Active Storage — the attachment scopes and the bytesactive-storage-attachments | roundhouse | |
The metadata half answers (205c3031); the macro-generated preload scopes and the bytes do not. Avatars and logos reach it through the fixture-file helpers above, so clearing those will move tests INTO this cause rather than out of it.
2 tests
| |||
| 2 | Pagy's controller surfacepagination | gem | |
campfire paginates with pagy, whose concern injects this into the controller. A shim of the portion-extraction surface is small; whether the port carries pagy or lowers pagination itself is the question underneath 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. Distinct from the test-harness surface above only in that the name comes from the app's fixtures rather than from Rails.
1 test
| |||
| 1 | A view helper called from a helper MODULEview-helper-in-helper-module | roundhouse | |
Inside a template these expand at transpile time. Inside a helper module the expansion has no template to splice into, so the call survives to runtime and lands on a bare module. Already the shape behind several closed findings (`button_to` with a block, `link_to` with a block); the remaining ones are the same wall a method away from the template.
1 test
| |||
| 1 | An unsaved `id` is 0, not nil — and a lookup used itid-zero-sentinel | roundhouse | |
A deliberate divergence, ledgered in docs/pipeline/runtime.md: `ActiveRecord::Base#initialize` seeds `@id = 0` so ids stay plain machine integers across every target, where Rails answers nil. This is where that choice becomes visible — a code path that would have skipped a nil lookup instead performs one for id 0.
1 test
| |||
| 22 | Not yet triagedunclassified | untriaged | |
No rule in bench/campfire/suite-causes.json matches these. Triage, then add a rule.
12 of 22 tests
| |||
Rules matching nothing this run — either cleared, or the failure's message changed and it is now counted as untriaged: route-helper-takes-a-record, conditional-get, belongs-to-autosave, turbo-streams-channel, sti-becomes.
scripts/campfire-walk-stubs.rb is spliced into the emitted app before the count above is taken. Each of its 14 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 |
|---|---|
| the `platform_agent` gem (Basecamp's User-Agent parser) | : the `platform_agent` gem (Basecamp's User-Agent parser). campfire's ApplicationPlatform subclasses it. Out of scope by design — a gem we do not intend to transpile — so this answers the surface the subclass actually calls: `match?`, and a `user_agent` facade carrying `browser` / `platform`. |
| `ActiveModel::Model`, which `ActionText::Attachment ::OpengraphEmbed` includes | : `ActiveModel::Model`, which `ActionText::Attachment ::OpengraphEmbed` includes. Rails' module gives a keyword-ish `initialize(attributes = {})` plus validations; only the constructor is reached here. |
| `ActionView::Helpers::SanitizeHelper` as an INCLUDABLE module | : `ActionView::Helpers::SanitizeHelper` as an INCLUDABLE module. Opengraph::Metadata includes it to call `sanitize(strip_tags(title))` on a model. Both helpers are known to analyze as view-side functions; what is missing is the module a model can mix in. Deliberately crude here — the walk only needs the calls to answer, and a real implementation is Rails' sanitizer, not a regex. |
| a superclass EXPRESSION is dropped, silently — `class Image < Struct.new(:asset_path, :width, :height)` emits… | : a superclass EXPRESSION is dropped, silently — `class Image < Struct.new(:asset_path, :width, :height)` emits as bare `class Image`, so its `super(...)` lands on BasicObject and the app dies at LOAD time. COMPILER BUG, already on the ledger (milestone walk finding #6); this is not a fix, it just declares the class first so the emitted body REOPENS it and keeps the Struct it should have had. Delete this the day the emitter carries the expression. |
| `Enumerable#index_by`, an ActiveSupport core extension our shared runtime does not carry | : `Enumerable#index_by`, an ActiveSupport core extension our shared runtime does not carry. campfire builds `Sound::INDEX` with it in a class body, so it fires at LOAD time. Small and genuinely ours to implement in runtime/ruby — this entry should be short-lived. |
| 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: * belongs_to AUTOSAVE — `room.creator = User.new(...)` then `room.save!` saves the unsaved target first in Rails. 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. |
| association-loading scopes GENERATED by macros we do not model — `has_rich_text :body` generates… | : association-loading scopes GENERATED by macros we do not model — `has_rich_text :body` generates `with_rich_text_body_and_embeds`, `has_one_attached :attachment` generates `with_attached_attachment`. The emitted `Message.with_attachment_details` calls both, so the room page dies before it renders a single message. Dropping them costs QUERIES, not rows (they are preloads), which is the same argument `scope_is_row_preserving` already makes about `includes`. |
| Active Storage | : 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, and now the only thing standing between the walk and a posted message: the association scope itself works (`@room.messages.create_with_attachment!` carries `room_id` through `where_scope` / `scope_attributes`). 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 | , and a NEW one this walk found: 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. |
| `scope defaults: { user_id: "me" }` is not modeled | , NEW: `scope defaults: { user_id: "me" }` is not modeled. Rails' route DEFAULTS supply the segment, so campfire's views call `user_push_subscriptions_path` with no arguments at all and get `/users/me/push_subscriptions`. We emit the helper with a REQUIRED `user_id`, so the bare call raises ArgumentError from the room page's nav. The default belongs in the emitted signature. |
| `pluck` on a folded association (`room.memberships.pluck(:user_id)`) | : `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. |
| `app/channels/` is ingested now, so `UnreadRoomsChannel.stream_name_for` — which a MODEL calls on the… | , NEW and arriving WITH a gain: `app/channels/` is ingested now, so `UnreadRoomsChannel.stream_name_for` — which a MODEL calls on the broadcast path — is real emitted code instead of a stub. The cost is that RoomMessagesChannel `include`s turbo-rails' stream-name module at LOAD time, and that module is not modeled. Its two methods are the SIGNING half of Turbo's stream names, and implementing them honestly means deciding what a signed stream name is for us: `turbo_stream_from` emits `<base64>--unsigned` today and Cable's `decode_stream` reads it back the same way. Wiring a real HMAC through both ends is its own stone, so this stands in until then — `verified_stream_name_from_params` raises rather than returning something plausible, since a stream name nobody verified is exactly the thing RoomMessagesChannel exists to prevent. |
| the `sentry-ruby` gem | : the `sentry-ruby` gem. campfire's message helpers rescue every Exception and report it before falling back to an "unrenderable" partial, so the constant is reached the moment ANY message render raises. Out of scope by design — a gem we do not intend to transpile. |
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_controller_test | 15 | 5 | 10 | FAIL MessagesControllerTest#test_index_returns_a_page_before_the_specified_message: expected "#message_4" in response body |
test/controllers/accounts_logos_controller_test | 6 | 0 | 6 | FAIL Accounts::LogosControllerTest#test_show_stock: undefined method 'expires_in' for an instance of Accounts::LogosController |
test/models/messages_by_bots_controlle_test | 7 | 2 | 5 | FAIL Messages::ByBotsControlleTest#test_create: undefined method 'to_attrs' for an instance of Hash |
test/models/room_push_test | 5 | 0 | 5 | FAIL Room::PushTest#test_deliver_new_message_to_other_room_users_with_push_subscriptions: undefined method 'configuration' for module Rails |
test/controllers/autocompletable_users_controller_test | 4 | 0 | 4 | FAIL Autocompletable::UsersControllerTest#test_search_returns_matching_users: undefined method 'active' for an instance of Array |
test/controllers/sessions_controller_test | 8 | 4 | 4 | FAIL SessionsControllerTest#test_new_denied_with_incompatible_browser: expected "h1" in response body |
test/controllers/users_avatars_controller_test | 4 | 0 | 4 | FAIL Users::AvatarsControllerTest#test_show_initials: undefined method 'expires_in' for an instance of Users::AvatarsController |
test/controllers/accounts_bots_controller_test | 5 | 2 | 3 | FAIL Accounts::BotsControllerTest#test_create: undefined method 'new' for an instance of ActiveRecord::Relation |
test/controllers/accounts_controller_test | 4 | 1 | 3 | FAIL AccountsControllerTest#test_edit: undefined method 'set_page_and_extract_portion_from' for an instance of AccountsController |
test/controllers/rooms_controller_test | 5 | 2 | 3 | FAIL RoomsControllerTest#test_show: undefined local variable or method 'with_attached_attachment' for class Message |
test/controllers/unfurl_links_controller_test | 5 | 2 | 3 | FAIL UnfurlLinksControllerTest#test_create: undefined method 'response' for an instance of UnfurlLinksControllerTest |
test/models/account_test | 4 | 1 | 3 | FAIL AccountTest#test_settings: assert_equal failed |
test/models/action_text_attachment_test | 3 | 0 | 3 | FAIL ActionTextAttachmentTest#test_lookup_user_attachable_with_invalid_sgid: undefined method 'attachable_sgid' for an instance of User |
test/models/message_attachment_test | 3 | 0 | 3 | never ran — test/models/message_attachment_test.rb:25:in '<class:AttachmentTest>': uninitialized constant ActionDispatch::TestProcess (NameError) |
test/models/opengraph_location_test | 7 | 4 | 3 | FAIL Opengraph::LocationTest#test_private_network_urls: no implicit conversion of Symbol into Integer |
test/controllers/messages_boosts_controller_test | 2 | 0 | 2 | FAIL Messages::BoostsControllerTest#test_create: undefined method 'broadcast_append_to' for an instance of Boost |
test/controllers/sessions_transfers_controller_test | 2 | 0 | 2 | FAIL Sessions::TransfersControllerTest#test_show_renders_when_not_signed_in: undefined method 'form_with' for module FormsHelper |
test/controllers/users_push_subscriptions_controller_test | 3 | 1 | 2 | FAIL Users::PushSubscriptionsControllerTest#test_create_new_push_subscription: assert_equal failed |
test/models/message_test | 3 | 1 | 2 | FAIL MessageTest#test_creating_a_message_enqueues_to_push_later: assert_enqueued_jobs failed: expected 1 job(s) of Room::PushMessageJob, got 0 |
test/models/opengraph_fetch_test | 11 | 9 | 2 | FAIL Opengraph::FetchTest#test_fetch_document_resolves_hostnames_once_to_avoid_dns_rebinding: undefined method 'assert_throws' for an instance of Opengraph::Fet |
test/models/user_avatar_test | 3 | 1 | 2 | FAIL User::AvatarTest#test_avatar_variant_is_a_resized_variant_of_a_variable_avatar: undefined method 'file_fixture' for an instance of User::AvatarTest |
test/models/webhook_test | 6 | 4 | 2 | FAIL WebhookTest#test_payload: uninitialized constant Rails::Application::Routes |
test/controllers/first_runs_controller_test | 4 | 3 | 1 | FAIL FirstRunsControllerTest#test_create_is_not_vulnerable_to_race_conditions: uninitialized constant FirstRunsControllerTest::Concurrent |
test/controllers/qr_code_controller_test | 1 | 0 | 1 | FAIL QrCodeControllerTest#test_show_renders_a_qr_code_as_a_cacheable_svg_image: uninitialized constant QrCodeController::RQRCode |
test/controllers/rooms_involvements_controller_test | 5 | 4 | 1 | FAIL Rooms::InvolvementsControllerTest#test_update_involvement_sends_turbo_update_when_becoming_visible_and_when_going_invisible: assert_turbo_stream_broadcasts |
test/controllers/accounts_bots_keys_controller_test | 1 | 1 | 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_users_controller_test | 3 | 3 | 0 | all green |
test/controllers/rooms_closeds_controller_test | 9 | 9 | 0 | all green |
test/controllers/rooms_directs_controller_test | 6 | 6 | 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/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/opengraph_document_test | 3 | 3 | 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: 826 files, 0 errors, 1,483 warnings. Warnings are a modeling-debt ledger rather than a failure — the Ruby target emits through its gaps — so they are not comparable to the test failures above.
| Diagnostic | Count |
|---|---|
gradual_untyped | 785 |
unresolved_type | 304 |
lower_residue | 211 |
unsupported | 122 |
send_dispatch_failed | 40 |
ivar_unresolved | 15 |
blank_unlowered | 5 |
incompatible_binop | 1 |