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.

campfire 90b330024dec · roundhouse bd57107193b8 · 2026-10-01T14:09:34Z
405/405 tests pass69 of 69 files green5 spliced stubsemit 1,196 files, 0 errors
405 passing · 0 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.11.

The input is pinned at 90b330024dec, 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.

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
turbo-signed-stream-nameserved 2026-09-22 (room_messages_channel_test 6/6 + the stock-channel pair 2/2); the rule stays as a regression tripwire.
web-push-pinned-deliveryserved 2026-09-22 on both lanes (web_push_persistent_request_test 2/2 ruby and spinel, through campfire's own PersistentRequest over the real gem / the port); the rule stays as a regression tripwire.
cable-test-caseserved 2026-09-22 (test/channels 20/20 on the ruby lane); the rule stays as a regression tripwire for a harness method going missing.
view-test-caseserved 2026-09-22 (messages_helper_test 3/3); the rule stays as the tripwire for a `view` call no helper defines, which is ActionView::Base's own surface and still unmodeled.
uri-parse-conformancethe space case served upstream 2026-09-22 (matz/spinel#4806 + fd49be64); the rule stays as the tripwire for the colon-form divergences still open in the same parser, and for the guard itself being loosened again.
rails-test-support-classesserved 2026-09-22 (opengraph-embed 8/8, log_scrubbing 6/6); the rule stays as the tripwire for the next Rails test stand-in the corpus reaches for, and names the three it has needed so far.
kwargs-splat-forwardingserved 2026-09-21 (opengraph-embed 7/8, the eighth is `ActionDispatch::TestRequest`); the rule stays as a regression tripwire.
actiontext-fragment-mutationserved 2026-09-21 (Node writes, `Fragment#update`/`at_css`/`css`, and Rails' attachable-driven plain text; content_filters 12/14); the rule stays as a regression tripwire.
activesupport-string-core-ext`squish` served 2026-09-22; the rule stays as the tripwire, and names the three String core exts (`squish!`, `truncate_words`, `remove`) the corpus has not asked for yet.
ipaddr-octets-on-stdlibserved 2026-09-21 (guard 24/24 on the ruby lane); the rule stays as the tripwire for a runtime IPAddr method the stdlib facade does not mirror.
tempfile-stdlibserved 2026-09-22; the rule stays as the tripwire for the next caller that reaches for a temp file, and for the port's one real difference from Ruby's — it opens by name where Ruby opens O_EXCL and retries.
vips-policy-not-applied-in-testsserved 2026-09-22 (16/17, the last one is `skip`); the rule stays as the tripwire for the policy going unapplied again — and it is worth keeping loud, because the failure it catches is a security control that is configured, reported as on, and off.
test-harness-surfaceserved 2026-09-22; the rule stays as the tripwire for the next harness method the corpus reaches for — `file_fixture`, `fixture_file_upload`, `response`, `parsed_body` and the bare `assert` family are all named in the match because each hides an unknown number of assertions behind it.
attachable-partials-not-renderedboth halves landed: sgid attachables through the generated `Content.render_attachment` (fe2ce576), content-type attachables through their own `from_node` with the partial's local named `model_name.element` and `caption` delegated back through the node, plus the shared `auto_link` running Rails' body pass so the outer node is stripped around the partial on every lane; ruby 294/300, spinel 292/300. The rule stays as the tripwire for an attachment node reaching the page bare again.
test-helper-kwarg-defaultsthe skipped-slot fill landed 2026-09-19 (unfurl 7/7 both lanes); the rule stays as a regression tripwire.
direct-uploads-not-servedserved 2026-09-20 (4/4 both lanes; with the generated filter reopens deleted the anonymous POST creates a blob and the anonymous PUT is not a 401, so the guard is what the tests see); the rule stays as a regression tripwire.
mocha-stub-raisesserved 2026-09-19 (sessions 9/10 spinel, 10/10 ruby); the rule stays as a regression tripwire.
mocha-bare-mockserved 2026-09-20 (sessions 10/10 both lanes; a no-op `disconnect` fails the test with `expected 1 time(s), got 0`); the rule stays as a regression tripwire.
proc-call-lostspinel#4529 closed by PR #4530 (b93306b6, same day): the fast path's guard is arity-aware; spinel lane 286 -> 290/300 on the merge. The rule stays as the tripwire for the two guards disagreeing again.
sti-becomes`becomes!` landed in f2d42854; the rule stays as a regression tripwire.
actiontext-attachment-from-node`from_node`, `#attachable` and `MissingAttachable` landed with the generated `ActionText::Attachable.locate` (the includer set is the registry); the rule stays as a regression tripwire.
sgid-wire-formatthe wire format is Rails' and the tolerant list is read off the reopen; the rule stays as a regression tripwire for either end drifting (a salt, digest, padding or `?expires_in` change fails the pinned literal first).
socket-interceptionserved 2026-09-17 (sequential Resolv answers + the TcpSocketStub seam); the rule stays as a regression tripwire.
object-extendcampfire ed0f9a5d (2026-09-20) mints the sgid with `to_sgid(for:)` in action_text_attachment_test, and 91d294f4 (#280, 2026-09-24) does the same for action_text_attachables_test, both of which `lower::to_sgid` grounds; the construct itself is still outside the subset, so the rule stays as the tripwire for the next test that reaches for a per-object `extend` — owner `subset`, not a todo.
globalid-sgidminting served 2026-09-22; the rule stays as the tripwire for a mint whose model the pass cannot name, which is reported rather than guessed.
turbo-streams-channelthe constant landed in fd1a3259; the rule stays as a regression tripwire.
conditional-get`fresh_when` / `stale?` landed in 7ea262e2; the rule stays as a regression tripwire.
paginationlanded in 0ef8fb79; the rule stays as a regression tripwire.
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.
shared-cache-table-lockthe harness opens a per-test file database since 2026-09-17; the rule stays as the tripwire — a reappearance means a shim opened a shared-cache database again.
render-json-encoderthe monomorphized writer landed for the declared-readers case (campfire's only site); the rule stays as a regression tripwire, and names the shapes that would still reach the encoder.

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. vips-image-construction, fixture-accessors, params-object-representation, relation-vs-array, rails-module-facade, broadcast-assertions, view-helper-in-helper-module, active-storage-attachments, route-helper-takes-a-record, out-of-scope-gems, arity-divergence, allow-browser, params-hash-in-path, silent-divergence.

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/channels/application_cable_connection_test330all green
test/channels/presence_channel_test660all green
test/channels/room_messages_channel_test660all green
test/channels/room_messages_via_stock_turbo_channel_test220all green
test/channels/unread_rooms_channel_test330all green
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/first_runs_controller_test440all green
test/controllers/messages_boosts_by_bots_controller_test12120all green
test/controllers/messages_boosts_controller_test220all green
test/controllers/messages_by_bots_controller_test23230all 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_test880all 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_controller_test10100all green
test/controllers/sessions_transfers_controller_test220all green
test/controllers/unfurl_links_controller_test770all green
test/controllers/users_avatars_controller_test440all green
test/controllers/users_bans_controller_test770all green
test/controllers/users_controller_test660all green
test/controllers/users_profiles_controller_test330all green
test/controllers/users_push_subscriptions_controller_test660all green
test/controllers/users_sidebars_controller_test330all green
test/controllers/welcome_controller_test220all green
test/helpers/content_filters_test19190all green
test/helpers/messages_helper_test330all green
test/helpers/rich_text_helper_test440all green
test/models/account_joinable_test220all green
test/models/account_test440all green
test/models/action_text__attachment_test330all green
test/models/action_text_attachment_opengraph_embed_test990all green
test/models/action_text_attachment_test330all green
test/models/active_storage_authentication_test440all green
test/models/block_banned_requests_test330all green
test/models/first_run_test330all green
test/models/log_scrubbing_formatter_test660all green
test/models/membership_test990all green
test/models/message_attachment_test330all 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_test11110all green
test/models/push_subscription_test17170all green
test/models/restricted_http_private_network_guard_test24240all green
test/models/room_push_test550all green
test/models/room_test660all green
test/models/rooms_direct_test330all green
test/models/rooms_open_test220all green
test/models/user_avatar_test330all green
test/models/user_bot_test440all green
test/models/user_role_test330all green
test/models/user_test440all green
test/models/vips_loader_policy_test15150all green
test/models/web_push_persistent_request_test220all green
test/models/webhook_test660all green

Emit diagnostics

From the transpile that produced the tree under test: 1,196 files, 0 errors, 600 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_untyped261
unresolved_type193
lower_residue142
blank_unlowered2
unsupported1
missing_preload1
Artifact: summary.json — every number on this page comes from it. Source: basecamp/once-campfire at 90b330024dec.