Campfire conformance

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.

Unpassed tests by who can act: 15 roundhouse · 14 untriaged · 2 gem.

campfire 94a48aacb65a · roundhouse c06a9a267a0a · 2026-09-11T04:17:18Z
257/288 tests pass42 of 54 files green5 spliced stubsemit 1,098 files, 0 errors
257 passing · 31 not passing
What this measures. Not whether Roundhouse compiles campfire — the Ruby target is gap-tolerant and answers that with a zero whether or not the output behaves. Their suite asserts behavior test by test, so a failure here names a construct that was emitted but is wrong or missing. The emit is produced fresh by 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.

Todos, by tests blocked

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.

BlockedCauseOwner
8
The emit ran and answered differently
silent-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
  • test/controllers/messages_by_bots_controller_test · Messages::ByBotsControllerTest#test_index_includes_message_details
    assert_equal failed
  • test/controllers/messages_by_bots_controller_test · Messages::ByBotsControllerTest#test_index_pages_through_older_messages_with_the_link_header
    assert_equal failed
  • test/controllers/messages_by_bots_controller_test · Messages::ByBotsControllerTest#test_index_in_a_room_with_no_messages
    assert_equal failed
  • test/controllers/messages_by_bots_controller_test · Messages::ByBotsControllerTest#test_update
    expected response :ok, got status=302 body=""
  • test/controllers/messages_by_bots_controller_test · Messages::ByBotsControllerTest#test_update_with_utf_8_content
    expected response :ok, got status=302 body=""
6
An emitted method's arity does not match its caller
arity-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
  • test/models/push_subscription_test · 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/push_subscription_test · Push::SubscriptionTest#test_delivery_is_skipped_when_the_endpoint_no_longer_resolves_to_a_public_ip
    wrong number of arguments (given 1, expected 0)
  • test/models/push_subscription_test · Push::SubscriptionTest#test_delivery_is_skipped_for_a_non_permitted_host_even_when_it_resolves_publicly
    wrong number of arguments (given 1, expected 0)
  • test/models/push_subscription_test · Push::SubscriptionTest#test_delivery_is_skipped_for_a_permitted_host_on_a_non_default_port
    wrong number of arguments (given 1, expected 0)
  • test/models/push_subscription_test · Push::SubscriptionTest#test_delivery_sends_with_the_pinned_endpoint_ip
    wrong number of arguments (given 1, expected 0)
4
Active Storage — the attachment scopes and the bytes
active-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
  • test/models/account_test · AccountTest#test_logo_variant_is_a_resized_variant_of_a_variable_logo
    uninitialized constant ActiveStorage::VariantWithRecord
  • test/models/message_attachment_test · Message::AttachmentTest#test_creating_a_message_creates_image_thumbnail
    undefined method 'representation' for an instance of ActiveStorage::Attached
  • test/models/message_attachment_test · Message::AttachmentTest#test_creating_a_message_creates_video_preview
    undefined method 'preview' for an instance of ActiveStorage::Attached
  • test/models/user_avatar_test · User::AvatarTest#test_avatar_variant_is_a_resized_variant_of_a_variable_avatar
    uninitialized constant ActiveStorage::VariantWithRecord
2
`ActionText::Attachment.from_node` — building an attachment from a parsed node
actiontext-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
  • test/models/action_text_attachment_test · ActionTextAttachmentTest#test_lookup_user_attachable_with_invalid_sgid
    undefined method 'from_node' for class ActionText::Attachment
  • test/models/action_text_attachment_test · ActionTextAttachmentTest#test_lookup_attachable_with_nil_sgid
    undefined method 'from_node' for class ActionText::Attachment
2
A gem the emit does not carry
out-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
  • test/controllers/first_runs_controller_test · FirstRunsControllerTest#test_create_is_not_vulnerable_to_race_conditions
    uninitialized constant Concurrent::CyclicBarrier
  • test/models/room_push_test · Room::PushTest#test_destroys_invalid_subscriptions
    uninitialized constant WebPush::ExpiredSubscription
1
A fixture accessor the emitted loader never defined
fixture-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
  • test/models/action_text_attachment_test · ActionTextAttachmentTest#test_lookup_invalid_sgid_for_an_attachable_requiring_a_valid_sgid
    undefined method 'rooms' for an instance of ActionTextAttachmentTest
1
A params hash interpolated into the request PATH
params-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
  • test/controllers/users_push_subscriptions_controller_test · Users::PushSubscriptionsControllerTest#test_touch_existing_subscription
    No route matches POST /users/{params: {push_subscription: {"endpoint" => "https://fcm.googleapis.com/fcm/send/123", "p256d
1
`allow_browser` — the modern-browser gate
allow-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
  • test/controllers/sessions_controller_test · SessionsControllerTest#test_new_denied_with_incompatible_browser
    expected "h1" in response body
6
Not yet triaged
unclassified
untriaged
No rule in bench/campfire/suite-causes.json matches these. Triage, then add a rule.
6 tests
  • test/controllers/messages_by_bots_controller_test · Messages::ByBotsControllerTest#test_create_file
    undefined method 'strip' for an instance of ActionDispatch::Http::UploadedFile
  • test/controllers/users_push_subscriptions_controller_test · Users::PushSubscriptionsControllerTest#test_re_registering_a_legacy_invalid_subscription_is_rejected_with_422
    uninitialized constant Users::PushSubscriptionsControllerTest::PushSubscription
  • test/models/room_push_test · Room::PushTest#test_deliver_new_message_to_other_room_users_with_push_subscriptions
    uninitialized constant Net::HTTP::Persistent
  • test/models/room_push_test · Room::PushTest#test_notifies_subscribed_users
    uninitialized constant Net::HTTP::Persistent
  • test/models/room_push_test · Room::PushTest#test_does_not_notify_for_connected_rooms
    uninitialized constant Net::HTTP::Persistent
  • test/models/room_push_test · Room::PushTest#test_does_not_notify_for_invisible_rooms
    uninitialized constant Net::HTTP::Persistent

Rules that matched nothing

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.

RuleWhy it reads zero
id-zero-sentinelzero 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.
paginationlanded 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-channelthe 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-surfacethe 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.

What the harness compensates for

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".

StubWhy 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 IVARcampfire'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.

By test file

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.

FileTestsPassFailFirst error
test/controllers/messages_by_bots_controller_test23176FAIL Messages::ByBotsControllerTest#test_create_file: undefined method 'strip' for an instance of ActionDispatch::Http::UploadedFile
test/models/push_subscription_test17125FAIL 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_test505FAIL Room::PushTest#test_deliver_new_message_to_other_room_users_with_push_subscriptions: uninitialized constant Net::HTTP::Persistent
test/models/action_text_attachment_test303FAIL ActionTextAttachmentTest#test_lookup_user_attachable_with_invalid_sgid: undefined method 'from_node' for class ActionText::Attachment
test/models/message_attachment_test303FAIL Message::AttachmentTest#test_creating_a_message_creates_image_thumbnail: undefined method 'representation' for an instance of ActiveStorage::Attached
test/controllers/users_push_subscriptions_controller_test642FAIL Users::PushSubscriptionsControllerTest#test_touch_existing_subscription: No route matches POST /users/{params: {push_subscription: {"endpoint" => "https://
test/models/account_test422FAIL AccountTest#test_settings: assert_equal failed
test/controllers/first_runs_controller_test431FAIL FirstRunsControllerTest#test_create_is_not_vulnerable_to_race_conditions: uninitialized constant Concurrent::CyclicBarrier
test/controllers/sessions_controller_test871FAIL SessionsControllerTest#test_new_denied_with_incompatible_browser: expected "h1" in response body
test/controllers/users_avatars_controller_test431FAIL Users::AvatarsControllerTest#test_show_image: assert_equal failed
test/models/user_avatar_test321FAIL User::AvatarTest#test_avatar_variant_is_a_resized_variant_of_a_variable_avatar: uninitialized constant ActiveStorage::VariantWithRecord
test/models/webhook_test651FAIL WebhookTest#test_delivery_with_ok_attachment_reply: wrong number of arguments (given 1, expected 3)
test/controllers/accounts_bots_controller_test550all green
test/controllers/accounts_bots_keys_controller_test110all green
test/controllers/accounts_controller_test440all green
test/controllers/accounts_custom_styles_controller_test330all green
test/controllers/accounts_join_codes_controller_test220all green
test/controllers/accounts_logos_controller_test660all green
test/controllers/accounts_users_controller_test330all green
test/controllers/autocompletable_users_controller_test440all green
test/controllers/messages_boosts_by_bots_controller_test12120all green
test/controllers/messages_boosts_controller_test220all green
test/controllers/messages_controller_test15150all green
test/controllers/qr_code_controller_test110all green
test/controllers/rooms_closeds_controller_test990all green
test/controllers/rooms_controller_test550all green
test/controllers/rooms_directs_controller_test660all green
test/controllers/rooms_involvements_controller_test550all green
test/controllers/rooms_opens_controller_test990all green
test/controllers/rooms_refreshes_controller_test110all green
test/controllers/searches_controller_test550all green
test/controllers/sessions_transfers_controller_test220all green
test/controllers/unfurl_links_controller_test550all green
test/controllers/users_bans_controller_test770all green
test/controllers/users_controller_test660all green
test/controllers/users_profiles_controller_test330all green
test/controllers/users_sidebars_controller_test330all green
test/controllers/welcome_controller_test220all green
test/models/account_joinable_test220all green
test/models/block_banned_requests_test330all green
test/models/first_run_test330all green
test/models/membership_test990all green
test/models/message_searchable_test330all green
test/models/message_test330all green
test/models/opengraph_document_test330all green
test/models/opengraph_fetch_test11110all green
test/models/opengraph_location_test770all green
test/models/opengraph_metadata_test10100all green
test/models/room_test660all green
test/models/rooms_direct_test330all green
test/models/rooms_open_test220all green
test/models/user_bot_test440all green
test/models/user_role_test330all green
test/models/user_test440all green

Emit diagnostics

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.

DiagnosticCount
gradual_untyped372
unresolved_type281
lower_residue213
unsupported128
blank_unlowered36
missing_preload1
Artifact: summary.json — every number on this page comes from it. Source: basecamp/once-campfire at 94a48aacb65a.