{
  "generated_at": "2026-08-21T20:36:07Z",
  "campfire": {
    "repo": "basecamp/once-campfire",
    "pinned": true,
    "sha": "2aa4141077141335e79cb0eac5a0eec1a57b2772"
  },
  "roundhouse": {
    "sha": "c55f358871b98beeba1b48eefae921b28771cac2",
    "dirty": false
  },
  "ruby": {
    "version": "3.4.10"
  },
  "emit": {
    "errors": 0,
    "warnings": 1483,
    "by_kind": {
      "gradual_untyped": 785,
      "unresolved_type": 304,
      "lower_residue": 211,
      "unsupported": 122,
      "send_dispatch_failed": 40,
      "ivar_unresolved": 15,
      "blank_unlowered": 5,
      "incompatible_binop": 1
    },
    "files": 826
  },
  "stubs": {
    "spliced": true,
    "file": "scripts/campfire-walk-stubs.rb",
    "entries": [
      {
        "what": "the `platform_agent` gem (Basecamp's User-Agent parser)",
        "why": ": 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`."
      },
      {
        "what": "`ActiveModel::Model`, which `ActionText::Attachment ::OpengraphEmbed` includes",
        "why": ": `ActiveModel::Model`, which `ActionText::Attachment ::OpengraphEmbed` includes. Rails' module gives a keyword-ish `initialize(attributes = {})` plus validations; only the constructor is reached here."
      },
      {
        "what": "`ActionView::Helpers::SanitizeHelper` as an INCLUDABLE module",
        "why": ": `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."
      },
      {
        "what": "a superclass EXPRESSION is dropped, silently — `class Image < Struct.new(:asset_path, :width, :height)` emits…",
        "why": ": 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."
      },
      {
        "what": "`Enumerable#index_by`, an ActiveSupport core extension our shared runtime does not carry",
        "why": ": `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."
      },
      {
        "what": "belongs_to AUTOSAVE — `room.creator = User.new(...)` then `room.save!` saves the unsaved target first in Rails",
        "why": "×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."
      },
      {
        "what": "association-loading scopes GENERATED by macros we do not model — `has_rich_text :body` generates…",
        "why": ": 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`."
      },
      {
        "what": "Active Storage",
        "why": ": 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."
      },
      {
        "what": "`belongs_to :creator, class_name: \"User\", default: -> { Current.user }` — the DEFAULT LAMBDA is dropped, so…",
        "why": ": `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."
      },
      {
        "what": "A VIEW HELPER THAT READS A CONTROLLER IVAR",
        "why": ", 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."
      },
      {
        "what": "`scope defaults: { user_id: \"me\" }` is not modeled",
        "why": ", 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."
      },
      {
        "what": "`pluck` on a folded association (`room.memberships.pluck(:user_id)`)",
        "why": ": `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."
      },
      {
        "what": "`app/channels/` is ingested now, so `UnreadRoomsChannel.stream_name_for` — which a MODEL calls on the…",
        "why": ", 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."
      },
      {
        "what": "the `sentry-ruby` gem",
        "why": ": 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."
      }
    ],
    "count": 14
  },
  "attribution": {
    "per_test": true,
    "note": "Each unpassed test carries its own message; a file that never ran contributes its whole count against its load error."
  },
  "tests": {
    "total": 240,
    "passed": 167,
    "failed": 73
  },
  "files": {
    "total": 52,
    "green": 28,
    "aborted": 1
  },
  "scope": {
    "note": "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."
  },
  "causes": [
    {
      "slug": "test-harness-surface",
      "title": "The emitted test base class is missing Rails' test surface",
      "owner": "compiler",
      "failures": 15,
      "share": 0.2055,
      "note": "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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/accounts_logos_controller_test · Accounts::LogosControllerTest#test_show_custom",
          "error": "undefined method 'fixture_file_upload' for an instance of Accounts::LogosControllerTest"
        },
        {
          "example": "test/controllers/accounts_logos_controller_test · Accounts::LogosControllerTest#test_show_custom_small_size",
          "error": "undefined method 'fixture_file_upload' for an instance of Accounts::LogosControllerTest"
        },
        {
          "example": "test/controllers/accounts_logos_controller_test · Accounts::LogosControllerTest#test_show_stock_when_custom_logo_cannot_be_resized",
          "error": "undefined method 'fixture_file_upload' for an instance of Accounts::LogosControllerTest"
        },
        {
          "example": "test/controllers/accounts_logos_controller_test · Accounts::LogosControllerTest#test_destroy",
          "error": "undefined method 'fixture_file_upload' for an instance of Accounts::LogosControllerTest"
        },
        {
          "example": "test/controllers/unfurl_links_controller_test · UnfurlLinksControllerTest#test_create",
          "error": "undefined method 'response' for an instance of UnfurlLinksControllerTest"
        }
      ]
    },
    {
      "slug": "silent-divergence",
      "title": "The emit ran and answered differently",
      "owner": "unknown",
      "failures": 7,
      "share": 0.0959,
      "note": "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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/messages_controller_test · MessagesControllerTest#test_index_returns_a_page_before_the_specified_message",
          "error": "expected \"#message_4\" in response body"
        },
        {
          "example": "test/controllers/rooms_controller_test · RoomsControllerTest#test_destroy_only_allowed_for_creators_or_those_who_can_administer",
          "error": "assert_difference failed: expected -1, got 0"
        },
        {
          "example": "test/controllers/sessions_controller_test · SessionsControllerTest#test_new_denied_with_incompatible_browser",
          "error": "expected \"h1\" in response body"
        },
        {
          "example": "test/controllers/sessions_controller_test · SessionsControllerTest#test_new_allowed_with_compatible_browser",
          "error": "expected \"h1\" in response body"
        },
        {
          "example": "test/controllers/users_push_subscriptions_controller_test · Users::PushSubscriptionsControllerTest#test_create_new_push_subscription",
          "error": "assert_equal failed"
        }
      ]
    },
    {
      "slug": "params-object-representation",
      "title": "A params object read as a Hash",
      "owner": "compiler",
      "failures": 5,
      "share": 0.0685,
      "note": "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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/accounts_bots_controller_test · Accounts::BotsControllerTest#test_remove_webhook",
          "error": "undefined method 'delete' for an instance of AccountsBotsUserParams"
        },
        {
          "example": "test/controllers/accounts_controller_test · AccountsControllerTest#test_update",
          "error": "private method 'require' called for an instance of Hash"
        },
        {
          "example": "test/models/messages_by_bots_controlle_test · Messages::ByBotsControlleTest#test_create",
          "error": "undefined method 'to_attrs' for an instance of Hash"
        },
        {
          "example": "test/models/messages_by_bots_controlle_test · Messages::ByBotsControlleTest#test_create_with_utf_8_content",
          "error": "undefined method 'to_attrs' for an instance of Hash"
        },
        {
          "example": "test/models/messages_by_bots_controlle_test · Messages::ByBotsControlleTest#test_create_does_not_trigger_a_webhook_to_the_sending_bot_in_a_direct_room",
          "error": "undefined method 'to_attrs' for an instance of Hash"
        }
      ]
    },
    {
      "slug": "relation-vs-array",
      "title": "A relation answering as an Array, or an Array asked for relation methods",
      "owner": "compiler",
      "failures": 5,
      "share": 0.0685,
      "note": "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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/accounts_bots_controller_test · Accounts::BotsControllerTest#test_create",
          "error": "undefined method 'new' for an instance of ActiveRecord::Relation"
        },
        {
          "example": "test/controllers/autocompletable_users_controller_test · Autocompletable::UsersControllerTest#test_search_returns_matching_users",
          "error": "undefined method 'active' for an instance of Array"
        },
        {
          "example": "test/controllers/autocompletable_users_controller_test · Autocompletable::UsersControllerTest#test_search_results_escape_html_in_names",
          "error": "undefined method 'active' for an instance of Array"
        },
        {
          "example": "test/controllers/autocompletable_users_controller_test · Autocompletable::UsersControllerTest#test_room_search_returns_matching_users",
          "error": "undefined method 'with_attached_avatar' for an instance of ActiveRecord::Relation"
        },
        {
          "example": "test/controllers/sessions_transfers_controller_test · Sessions::TransfersControllerTest#test_update_establishes_a_session_when_the_code_is_valid",
          "error": "undefined method 'find_by_transfer_id' for an instance of ActiveRecord::Relation"
        }
      ]
    },
    {
      "slug": "globalid-sgid",
      "title": "Global IDs — minting and dereferencing an sgid",
      "owner": "compiler",
      "failures": 5,
      "share": 0.0685,
      "note": "`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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/messages_controller_test · MessagesControllerTest#test_creating_a_message_broadcasts_the_message_to_the_room",
          "error": "undefined method 'to_gid_param' for an instance of Rooms::Closed"
        },
        {
          "example": "test/controllers/messages_controller_test · MessagesControllerTest#test_mentioning_a_bot_triggers_a_webhook",
          "error": "undefined method 'attachable_sgid' for an instance of User"
        },
        {
          "example": "test/models/action_text_attachment_test · ActionTextAttachmentTest#test_lookup_user_attachable_with_invalid_sgid",
          "error": "undefined method 'attachable_sgid' for an instance of User"
        },
        {
          "example": "test/models/message_test · MessageTest#test_mentionees",
          "error": "undefined method 'attachable_sgid' for an instance of User"
        },
        {
          "example": "test/models/messages_by_bots_controlle_test · Messages::ByBotsControlleTest#test_create_does_not_trigger_a_webhook_to_the_sending_bot_if_it_mentions_itself",
          "error": "undefined method 'attachable_sgid' for an instance of User"
        }
      ]
    },
    {
      "slug": "rails-module-facade",
      "title": "`Rails.configuration` and the routes object",
      "owner": "compiler",
      "failures": 5,
      "share": 0.0685,
      "note": "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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/models/room_push_test · Room::PushTest#test_deliver_new_message_to_other_room_users_with_push_subscriptions",
          "error": "undefined method 'configuration' for module Rails"
        },
        {
          "example": "test/models/room_push_test · Room::PushTest#test_notifies_subscribed_users",
          "error": "undefined method 'configuration' for module Rails"
        },
        {
          "example": "test/models/room_push_test · Room::PushTest#test_does_not_notify_for_connected_rooms",
          "error": "undefined method 'configuration' for module Rails"
        },
        {
          "example": "test/models/room_push_test · Room::PushTest#test_does_not_notify_for_invisible_rooms",
          "error": "undefined method 'configuration' for module Rails"
        },
        {
          "example": "test/models/webhook_test · WebhookTest#test_payload",
          "error": "uninitialized constant Rails::Application::Routes"
        }
      ]
    },
    {
      "slug": "broadcast-assertions",
      "title": "Broadcast assertions over a queue that is a log",
      "owner": "compiler",
      "failures": 3,
      "share": 0.0411,
      "note": "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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/messages_boosts_controller_test · Messages::BoostsControllerTest#test_create",
          "error": "undefined method 'broadcast_append_to' for an instance of Boost"
        },
        {
          "example": "test/controllers/messages_controller_test · MessagesControllerTest#test_creating_a_message_broadcasts_unread_room_to_each_member",
          "error": "assert_broadcasts failed: expected 1 broadcast(s) to \"user_1_unreads\", got 0"
        },
        {
          "example": "test/controllers/rooms_involvements_controller_test · Rooms::InvolvementsControllerTest#test_update_involvement_sends_turbo_update_when_becoming_visible_and_when_going_invisible",
          "error": "assert_turbo_stream_broadcasts failed: expected 2 broadcast(s) to \"use"
        }
      ]
    },
    {
      "slug": "out-of-scope-gems",
      "title": "A gem the emit does not carry",
      "owner": "gem",
      "failures": 3,
      "share": 0.0411,
      "note": "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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/first_runs_controller_test · FirstRunsControllerTest#test_create_is_not_vulnerable_to_race_conditions",
          "error": "uninitialized constant FirstRunsControllerTest::Concurrent"
        },
        {
          "example": "test/controllers/qr_code_controller_test · QrCodeControllerTest#test_show_renders_a_qr_code_as_a_cacheable_svg_image",
          "error": "uninitialized constant QrCodeController::RQRCode"
        },
        {
          "example": "test/models/room_push_test · Room::PushTest#test_destroys_invalid_subscriptions",
          "error": "uninitialized constant WebPush::ExpiredSubscription"
        }
      ]
    },
    {
      "slug": "arity-divergence",
      "title": "An emitted method's arity does not match its caller",
      "owner": "compiler",
      "failures": 2,
      "share": 0.0274,
      "note": "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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/accounts_bots_controller_test · Accounts::BotsControllerTest#test_update",
          "error": "wrong number of arguments (given 0, expected 1..4)"
        },
        {
          "example": "test/controllers/users_push_subscriptions_controller_test · Users::PushSubscriptionsControllerTest#test_destroy_a_push_subscription_via_dev_mode",
          "error": "wrong number of arguments (given 1, expected 2)"
        }
      ]
    },
    {
      "slug": "active-storage-attachments",
      "title": "Active Storage — the attachment scopes and the bytes",
      "owner": "compiler",
      "failures": 2,
      "share": 0.0274,
      "note": "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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/rooms_controller_test · RoomsControllerTest#test_show",
          "error": "undefined local variable or method 'with_attached_attachment' for class Message"
        },
        {
          "example": "test/controllers/rooms_controller_test · RoomsControllerTest#test_shows_records_the_last_room_visited_in_a_cookie",
          "error": "undefined local variable or method 'with_attached_attachment' for class Message"
        }
      ]
    },
    {
      "slug": "pagination",
      "title": "Pagy's controller surface",
      "owner": "gem",
      "failures": 2,
      "share": 0.0274,
      "note": "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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/accounts_controller_test · AccountsControllerTest#test_edit",
          "error": "undefined method 'set_page_and_extract_portion_from' for an instance of AccountsController"
        },
        {
          "example": "test/controllers/accounts_controller_test · AccountsControllerTest#test_edit_groups_administrators_separately_from_members_with_a_divider",
          "error": "undefined method 'set_page_and_extract_portion_from' for an instance of AccountsController"
        }
      ]
    },
    {
      "slug": "fixture-accessors",
      "title": "A fixture accessor the emitted loader never defined",
      "owner": "compiler",
      "failures": 1,
      "share": 0.0137,
      "note": "`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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/models/action_text_attachment_test · ActionTextAttachmentTest#test_lookup_invalid_sgid_for_an_attachable_requiring_a_valid_sgid",
          "error": "undefined method 'rooms' for an instance of ActionTextAttachmentTest"
        }
      ]
    },
    {
      "slug": "view-helper-in-helper-module",
      "title": "A view helper called from a helper MODULE",
      "owner": "compiler",
      "failures": 1,
      "share": 0.0137,
      "note": "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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/sessions_transfers_controller_test · Sessions::TransfersControllerTest#test_show_renders_when_not_signed_in",
          "error": "undefined method 'form_with' for module FormsHelper"
        }
      ]
    },
    {
      "slug": "id-zero-sentinel",
      "title": "An unsaved `id` is 0, not nil — and a lookup used it",
      "owner": "compiler",
      "failures": 1,
      "share": 0.0137,
      "note": "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.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/users_avatars_controller_test · Users::AvatarsControllerTest#test_show_image_with_invalid_token_responds_404",
          "error": "Couldn't find User with id=0"
        }
      ]
    },
    {
      "slug": "route-helper-takes-a-record",
      "title": "A route helper interpolated a record instead of its id",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "`room_path(room)` rendered `#<Room:0x…>` into the path. Route helpers project to `.id` only where the SEGMENT holds one (8b4258e5); these are the sites that projection does not reach yet. Visible only as a wrong string, which is why they surface as redirect assertions rather than exceptions.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "conditional-get",
      "title": "`fresh_when` / `stale?` — conditional GET",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "ETag/Last-Modified handling on the controller. Small in surface and shared by every target once the response object carries the headers; the tests behind it assert 304s, so they are only reachable after it lands.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "belongs-to-autosave",
      "title": "`belongs_to` autosave — an unsaved target saved with its owner",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "Assigning an unsaved record and saving the owner saves the target first in Rails. The emit reads `value.id` (0) and the presence validation fails. Carries a stub entry in scripts/campfire-walk-stubs.rb, so the count here is what the stub does NOT cover.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "turbo-streams-channel",
      "title": "`Turbo::StreamsChannel` as a constant the app can name",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "Broadcasts lower to `Broadcasts.record`, which is the portable shape; the app also names the channel class directly to broadcast or to assert. The constant has no emitted counterpart, so those tests stop before reaching the broadcast at all.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "sti-becomes",
      "title": "`becomes!` — recasting a record to an STI sibling",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "campfire opens and closes rooms by recasting: `room.becomes!(Rooms::Open)` rewrites the type column and returns the record as the sibling class. The STI read and write halves both landed (7aad7ba7, 7245435a); the recast is the half that did not.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "unclassified",
      "title": "Not yet triaged",
      "owner": "unknown",
      "failures": 16,
      "share": 0.2192,
      "note": "No rule in bench/campfire/suite-causes.json matches these. Triage, then add a rule.",
      "stale_rule": false,
      "examples": [
        {
          "example": "test/controllers/accounts_logos_controller_test · Accounts::LogosControllerTest#test_show_stock",
          "error": "undefined method 'expires_in' for an instance of Accounts::LogosController"
        },
        {
          "example": "test/controllers/accounts_logos_controller_test · Accounts::LogosControllerTest#test_show_stock_small_size",
          "error": "undefined method 'expires_in' for an instance of Accounts::LogosController"
        },
        {
          "example": "test/controllers/autocompletable_users_controller_test · Autocompletable::UsersControllerTest#test_room_search_is_scoped_by_membership",
          "error": "undefined method 'users' for nil"
        },
        {
          "example": "test/controllers/messages_boosts_controller_test · Messages::BoostsControllerTest#test_destroy",
          "error": "destroy"
        },
        {
          "example": "test/controllers/messages_controller_test · MessagesControllerTest#test_index_returns_a_page_after_the_specified_message",
          "error": "undefined method 'persisted?' for an instance of ActiveSupport::Duration"
        },
        {
          "example": "test/controllers/messages_controller_test · MessagesControllerTest#test_update_updates_a_message_belonging_to_the_user",
          "error": "undefined method 'broadcast_replace_to' for an instance of Message"
        },
        {
          "example": "test/controllers/messages_controller_test · MessagesControllerTest#test_admin_updates_a_message_belonging_to_another_user",
          "error": "undefined method 'broadcast_replace_to' for an instance of Message"
        },
        {
          "example": "test/controllers/sessions_controller_test · SessionsControllerTest#test_create_with_invalid_credentials",
          "error": "assert_nil failed"
        },
        {
          "example": "test/controllers/sessions_controller_test · SessionsControllerTest#test_destroy_removes_the_push_subscription_for_the_device",
          "error": "undefined method 'destroy_by' for class Push::Subscription"
        },
        {
          "example": "test/controllers/unfurl_links_controller_test · UnfurlLinksControllerTest#test_create_for_twitter_com",
          "error": "URI should be a String, Regexp, Addressable::Template, a callable object, or respond to #to_str. Got: Hash"
        },
        {
          "example": "test/controllers/unfurl_links_controller_test · UnfurlLinksControllerTest#test_create_for_x_com",
          "error": "URI should be a String, Regexp, Addressable::Template, a callable object, or respond to #to_str. Got: Hash"
        },
        {
          "example": "test/controllers/users_avatars_controller_test · Users::AvatarsControllerTest#test_show_initials",
          "error": "undefined method 'expires_in' for an instance of Users::AvatarsController"
        }
      ]
    }
  ],
  "by_file": [
    {
      "file": "test/controllers/messages_controller_test",
      "status": "fail",
      "tests": 15,
      "passed": 8,
      "failed": 7,
      "first_error": "FAIL MessagesControllerTest#test_index_returns_a_page_before_the_specified_message: expected \"#message_4\" in response body"
    },
    {
      "file": "test/controllers/accounts_logos_controller_test",
      "status": "fail",
      "tests": 6,
      "passed": 0,
      "failed": 6,
      "first_error": "FAIL Accounts::LogosControllerTest#test_show_stock: undefined method 'expires_in' for an instance of Accounts::LogosController"
    },
    {
      "file": "test/models/messages_by_bots_controlle_test",
      "status": "fail",
      "tests": 7,
      "passed": 2,
      "failed": 5,
      "first_error": "FAIL Messages::ByBotsControlleTest#test_create: undefined method 'to_attrs' for an instance of Hash"
    },
    {
      "file": "test/models/room_push_test",
      "status": "fail",
      "tests": 5,
      "passed": 0,
      "failed": 5,
      "first_error": "FAIL Room::PushTest#test_deliver_new_message_to_other_room_users_with_push_subscriptions: undefined method 'configuration' for module Rails"
    },
    {
      "file": "test/controllers/autocompletable_users_controller_test",
      "status": "fail",
      "tests": 4,
      "passed": 0,
      "failed": 4,
      "first_error": "FAIL Autocompletable::UsersControllerTest#test_search_returns_matching_users: undefined method 'active' for an instance of Array"
    },
    {
      "file": "test/controllers/sessions_controller_test",
      "status": "fail",
      "tests": 8,
      "passed": 4,
      "failed": 4,
      "first_error": "FAIL SessionsControllerTest#test_new_denied_with_incompatible_browser: expected \"h1\" in response body"
    },
    {
      "file": "test/controllers/users_avatars_controller_test",
      "status": "fail",
      "tests": 4,
      "passed": 0,
      "failed": 4,
      "first_error": "FAIL Users::AvatarsControllerTest#test_show_initials: undefined method 'expires_in' for an instance of Users::AvatarsController"
    },
    {
      "file": "test/controllers/accounts_bots_controller_test",
      "status": "fail",
      "tests": 5,
      "passed": 2,
      "failed": 3,
      "first_error": "FAIL Accounts::BotsControllerTest#test_create: undefined method 'new' for an instance of ActiveRecord::Relation"
    },
    {
      "file": "test/controllers/accounts_controller_test",
      "status": "fail",
      "tests": 4,
      "passed": 1,
      "failed": 3,
      "first_error": "FAIL AccountsControllerTest#test_edit: undefined method 'set_page_and_extract_portion_from' for an instance of AccountsController"
    },
    {
      "file": "test/controllers/rooms_controller_test",
      "status": "fail",
      "tests": 5,
      "passed": 2,
      "failed": 3,
      "first_error": "FAIL RoomsControllerTest#test_show: undefined local variable or method 'with_attached_attachment' for class Message"
    },
    {
      "file": "test/controllers/unfurl_links_controller_test",
      "status": "fail",
      "tests": 5,
      "passed": 2,
      "failed": 3,
      "first_error": "FAIL UnfurlLinksControllerTest#test_create: undefined method 'response' for an instance of UnfurlLinksControllerTest"
    },
    {
      "file": "test/models/account_test",
      "status": "fail",
      "tests": 4,
      "passed": 1,
      "failed": 3,
      "first_error": "FAIL AccountTest#test_settings: assert_equal failed"
    },
    {
      "file": "test/models/action_text_attachment_test",
      "status": "fail",
      "tests": 3,
      "passed": 0,
      "failed": 3,
      "first_error": "FAIL ActionTextAttachmentTest#test_lookup_user_attachable_with_invalid_sgid: undefined method 'attachable_sgid' for an instance of User"
    },
    {
      "file": "test/models/message_attachment_test",
      "status": "fail",
      "tests": 3,
      "passed": 0,
      "failed": 3,
      "first_error": "test/models/message_attachment_test.rb:25:in '<class:AttachmentTest>': uninitialized constant ActionDispatch::TestProcess (NameError)",
      "aborted": true
    },
    {
      "file": "test/controllers/messages_boosts_controller_test",
      "status": "fail",
      "tests": 2,
      "passed": 0,
      "failed": 2,
      "first_error": "FAIL Messages::BoostsControllerTest#test_create: undefined method 'broadcast_append_to' for an instance of Boost"
    },
    {
      "file": "test/controllers/sessions_transfers_controller_test",
      "status": "fail",
      "tests": 2,
      "passed": 0,
      "failed": 2,
      "first_error": "FAIL Sessions::TransfersControllerTest#test_show_renders_when_not_signed_in: undefined method 'form_with' for module FormsHelper"
    },
    {
      "file": "test/controllers/users_push_subscriptions_controller_test",
      "status": "fail",
      "tests": 3,
      "passed": 1,
      "failed": 2,
      "first_error": "FAIL Users::PushSubscriptionsControllerTest#test_create_new_push_subscription: assert_equal failed"
    },
    {
      "file": "test/models/message_test",
      "status": "fail",
      "tests": 3,
      "passed": 1,
      "failed": 2,
      "first_error": "FAIL MessageTest#test_creating_a_message_enqueues_to_push_later: assert_enqueued_jobs failed: expected 1 job(s) of Room::PushMessageJob, got 0"
    },
    {
      "file": "test/models/opengraph_fetch_test",
      "status": "fail",
      "tests": 11,
      "passed": 9,
      "failed": 2,
      "first_error": "FAIL Opengraph::FetchTest#test_fetch_document_resolves_hostnames_once_to_avoid_dns_rebinding: undefined method 'assert_throws' for an instance of Opengraph::Fet"
    },
    {
      "file": "test/models/user_avatar_test",
      "status": "fail",
      "tests": 3,
      "passed": 1,
      "failed": 2,
      "first_error": "FAIL User::AvatarTest#test_avatar_variant_is_a_resized_variant_of_a_variable_avatar: undefined method 'file_fixture' for an instance of User::AvatarTest"
    },
    {
      "file": "test/models/webhook_test",
      "status": "fail",
      "tests": 6,
      "passed": 4,
      "failed": 2,
      "first_error": "FAIL WebhookTest#test_payload: uninitialized constant Rails::Application::Routes"
    },
    {
      "file": "test/controllers/first_runs_controller_test",
      "status": "fail",
      "tests": 4,
      "passed": 3,
      "failed": 1,
      "first_error": "FAIL FirstRunsControllerTest#test_create_is_not_vulnerable_to_race_conditions: uninitialized constant FirstRunsControllerTest::Concurrent"
    },
    {
      "file": "test/controllers/qr_code_controller_test",
      "status": "fail",
      "tests": 1,
      "passed": 0,
      "failed": 1,
      "first_error": "FAIL QrCodeControllerTest#test_show_renders_a_qr_code_as_a_cacheable_svg_image: uninitialized constant QrCodeController::RQRCode"
    },
    {
      "file": "test/controllers/rooms_involvements_controller_test",
      "status": "fail",
      "tests": 5,
      "passed": 4,
      "failed": 1,
      "first_error": "FAIL Rooms::InvolvementsControllerTest#test_update_involvement_sends_turbo_update_when_becoming_visible_and_when_going_invisible: assert_turbo_stream_broadcasts"
    },
    {
      "file": "test/controllers/accounts_bots_keys_controller_test",
      "status": "pass",
      "tests": 1,
      "passed": 1,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/accounts_custom_styles_controller_test",
      "status": "pass",
      "tests": 3,
      "passed": 3,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/accounts_join_codes_controller_test",
      "status": "pass",
      "tests": 2,
      "passed": 2,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/accounts_users_controller_test",
      "status": "pass",
      "tests": 3,
      "passed": 3,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/rooms_closeds_controller_test",
      "status": "pass",
      "tests": 9,
      "passed": 9,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/rooms_directs_controller_test",
      "status": "pass",
      "tests": 6,
      "passed": 6,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/rooms_opens_controller_test",
      "status": "pass",
      "tests": 9,
      "passed": 9,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/rooms_refreshes_controller_test",
      "status": "pass",
      "tests": 1,
      "passed": 1,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/searches_controller_test",
      "status": "pass",
      "tests": 5,
      "passed": 5,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/users_bans_controller_test",
      "status": "pass",
      "tests": 7,
      "passed": 7,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/users_controller_test",
      "status": "pass",
      "tests": 6,
      "passed": 6,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/users_profiles_controller_test",
      "status": "pass",
      "tests": 3,
      "passed": 3,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/users_sidebars_controller_test",
      "status": "pass",
      "tests": 3,
      "passed": 3,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/controllers/welcome_controller_test",
      "status": "pass",
      "tests": 2,
      "passed": 2,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/account_joinable_test",
      "status": "pass",
      "tests": 2,
      "passed": 2,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/block_banned_requests_test",
      "status": "pass",
      "tests": 3,
      "passed": 3,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/first_run_test",
      "status": "pass",
      "tests": 3,
      "passed": 3,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/membership_test",
      "status": "pass",
      "tests": 9,
      "passed": 9,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/message_searchable_test",
      "status": "pass",
      "tests": 3,
      "passed": 3,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/opengraph_document_test",
      "status": "pass",
      "tests": 3,
      "passed": 3,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/opengraph_location_test",
      "status": "pass",
      "tests": 7,
      "passed": 7,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/opengraph_metadata_test",
      "status": "pass",
      "tests": 10,
      "passed": 10,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/room_test",
      "status": "pass",
      "tests": 6,
      "passed": 6,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/rooms_direct_test",
      "status": "pass",
      "tests": 3,
      "passed": 3,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/rooms_open_test",
      "status": "pass",
      "tests": 2,
      "passed": 2,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/user_bot_test",
      "status": "pass",
      "tests": 4,
      "passed": 4,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/user_role_test",
      "status": "pass",
      "tests": 3,
      "passed": 3,
      "failed": 0,
      "first_error": ""
    },
    {
      "file": "test/models/user_test",
      "status": "pass",
      "tests": 4,
      "passed": 4,
      "failed": 0,
      "first_error": ""
    }
  ]
}