{
  "generated_at": "2026-10-31T10:23:38Z",
  "lobsters": {
    "path": "/home/rubys/lobsters",
    "sha": "cb2b28371dbf5549b6fad293a6c2dfc8ec7a0320",
    "committed_at": "2026-10-30T23:34:31-05:00",
    "dirty": false
  },
  "roundhouse": {
    "sha": "544377934c32b5623ce820c8bd86f4d47aa89cf7",
    "dirty": false
  },
  "ruby": {
    "pinned": "4.0.9",
    "via_mise": true
  },
  "canary": {
    "entered": true,
    "loaded": true,
    "rails_booted": false,
    "valid_source": "/home/rubys/roundhouse/build/lobsters-specs/emit/runtime/active_record/connection.rb",
    "valid_from_emit": true,
    "ok": true
  },
  "emit": {
    "reused": false,
    "errors": 0,
    "warnings": 2009,
    "by_kind": {
      "gradual_untyped": 1043,
      "unresolved_type": 457,
      "lower_residue": 380,
      "unconsumed_initializer": 52,
      "unsupported": 32,
      "incompatible_binop": 15,
      "missing_preload": 14,
      "send_dispatch_failed": 9,
      "ivar_unresolved": 7
    },
    "files": 1085,
    "path": "/home/rubys/roundhouse/build/lobsters-specs/emit"
  },
  "scope": {
    "suites": [
      "spec/models"
    ],
    "spec_files_run": 26,
    "spec_files_in_suite": 111,
    "excluded": "Request, feature, mailbox and routing specs are not run. Model specs exercise the compiler surface most densely; the request tier is next, and the capybara/mailbox tier is ledgered rather than attempted."
  },
  "examples": {
    "total": 369,
    "passed": 226,
    "failed": 143,
    "pending": 0,
    "errors_outside_examples": 1,
    "duration_s": 90.2
  },
  "errors_outside_examples": [
    "\nAn error occurred in an `after(:context)` hook.\nFailure/Error: @stories.each(&:destroy!)\n\nActiveRecord::StatementInvalid:\n  no such column: links.story_id:\n  SELECT \"links\".\"id\" AS id, \"links\".\"from_comment_id\" AS from_comment_id, \"links\".\"from_story_id\" AS from_story_id, \"links\".\"normalized_url\" AS normalized_url, \"links\".\"title\" AS title, \"links\".\"to_comment_id\" AS to_comment_id, \"links\".\"to_story_id\" AS to_story_id, \"links\".\"url\" AS url FROM links WHERE (links.\"story_id\" = ?)\n                                                                                                                                                                                                                                                                                                                  ^\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/db.rb:1278:in 'Db.raise_statement_error'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/db.rb:1347:in 'Db.prepare'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/app/models/link.rb:527:in 'Link._hydrate_all'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/active_record/relation.rb:1535:in 'ActiveRecord::Relation#load_records'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/active_record/relation.rb:1694:in 'ActiveRecord::Relation#loaded_records'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/active_record/relation.rb:1701:in 'ActiveRecord::Relation#each'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/app/models/story.rb:2759:in 'Story#before_destroy'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/active_record/connection.rb:1899:in 'block (2 levels) in ActiveRecord::Base#destroy'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/active_record/connection.rb:1918:in 'block in ActiveRecord::Base#_halted_by_abort'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/active_record/connection.rb:1917:in 'Kernel#catch'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/active_record/connection.rb:1917:in 'ActiveRecord::Base#_halted_by_abort'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/active_record/connection.rb:1899:in 'block in ActiveRecord::Base#destroy'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/active_record/connection.rb:936:in 'ActiveRecord::Base.transaction'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/active_record/connection.rb:1898:in 'ActiveRecord::Base#destroy'\n# /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/active_record/connection.rb:1911:in 'ActiveRecord::Base#destroy!'\n# /home/rubys/lobsters/spec/models/search_spec.rb:62:in 'Array#each'\n# /home/rubys/lobsters/spec/models/search_spec.rb:62:in 'block (2 levels) in <top (required)>'\n# ------------------\n# --- Caused by: ---\n# SQLite3::SQLException:\n#   no such column: links.story_id:\n#   SELECT \"links\".\"id\" AS id, \"links\".\"from_comment_id\" AS from_comment_id, \"links\".\"from_story_id\" AS from_story_id, \"links\".\"normalized_url\" AS normalized_url, \"links\".\"title\" AS title, \"links\".\"to_comment_id\" AS to_comment_id, \"links\".\"to_story_id\" AS to_story_id, \"links\".\"url\" AS url FROM links WHERE (links.\"story_id\" = ?)\n#                                                                                                                                                                                                                                                                                                                   ^\n#   /home/rubys/roundhouse/build/lobsters-specs/emit/runtime/db.rb:1387:in 'Db.prepare_plain'\n"
  ],
  "harness_compensations": [
    {
      "what": "require \"net/http\" in the shim",
      "kind": "gap",
      "why": "app/models/sponge.rb references Net:: constants but the emit never requires net/http."
    },
    {
      "what": "Numeric duration builders in the shim",
      "kind": "by_design",
      "why": "Specs and factories are untranspiled plain Ruby; the emit path rewrites n.days at transpile time, so the shared runtime never reopens Numeric."
    }
  ],
  "causes": [
    {
      "slug": "expectation-mismatch",
      "title": "Ran, answered wrong",
      "owner": "compiler",
      "failures": 35,
      "share": 0.2448,
      "note": "No exception — the emitted code executed and produced a different answer than Rails does. Each needs individual diagnosis; they cluster by message no better than by coin flip, so this bucket is a count, not a cause. Keep it visible: it is where correctness bugs hide once the crash-shaped failures clear.",
      "stale_rule": false,
      "examples": [
        "Comment extracts links from markdown",
        "Invitation has a valid factory",
        "ModNote validates the length of markeddown_note",
        "ModNote validates the presence of markeddown_note",
        "Search can search titles for stories"
      ]
    },
    {
      "slug": "symbol-into-integer",
      "title": "Symbol used where an Integer index is expected",
      "owner": "compiler",
      "failures": 32,
      "share": 0.2238,
      "note": "Undiagnosed. Shape suggests a Hash emitted as an Array (or a symbol-keyed read lowered to an index) — needs a reduction before it can be ranked honestly.",
      "stale_rule": false,
      "examples": [
        "Comment hat can't be worn if user doesn't have that hat",
        "Comment hat can be one of the user's hats",
        "InvitationRequest has a limit on the email field",
        "InvitationRequest has a limit on the memo field",
        "InvitationRequest has a limit on the name field"
      ]
    },
    {
      "slug": "errors-object-api",
      "title": "errors is an Array, not an ActiveModel::Errors",
      "owner": "compiler",
      "failures": 5,
      "share": 0.035,
      "note": "The runtime errors collection is Array[String]; attribute identity is lost at push, so errors[:attr] and errors.messages have nothing to answer with.",
      "stale_rule": false,
      "examples": [
        "ModNote validates the length of note",
        "ModNote validates the presence of note",
        "Moderation validates the length of action",
        "Moderation validates the length of reason",
        "Moderation validates a moderation linked to exactly one object"
      ]
    },
    {
      "slug": "harness-time-helpers",
      "title": "travel_to unavailable to specs",
      "owner": "harness",
      "failures": 1,
      "share": 0.007,
      "note": "ActiveSupport::Testing::TimeHelpers is a spec-side helper stock Rails mixes into example groups. Ours to provide in the shim, not compiler debt.",
      "stale_rule": false,
      "examples": [
        "User allows totp codes from the surrounding time steps"
      ]
    },
    {
      "slug": "nested-constant-missing",
      "title": "Nested constant not emitted",
      "owner": "compiler",
      "failures": 1,
      "share": 0.007,
      "note": "Constants nested inside a model class (Hat::Mail, Notification::Notifiable) are dropped rather than emitted alongside the class.",
      "stale_rule": false,
      "examples": [
        "Hat santizes email addresses"
      ]
    },
    {
      "slug": "relation-pick",
      "title": "Relation#pick unimplemented",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "pick returns a single value rather than an Array; a previous fix covered the call sites that lower to SQL, not the runtime Relation.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "model-class-first",
      "title": "Model.first unimplemented as a class method",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "Relation#first exists; the class-level shorthand does not forward to it. Related to the scalar-class-method forwarding gap.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "update-attribute",
      "title": "update_attribute unimplemented",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "Single-attribute write that skips validation.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "apppath-concat",
      "title": "Rails::AppPath#+ unimplemented",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "Rails.root is modeled as an AppPath value class that supports join but not +.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "kernel-bigdecimal",
      "title": "Kernel#BigDecimal not available on models",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "Kernel-level conversion function called from model bodies; the runtime exposes no BigDecimal.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "nil-arith-coercion",
      "title": "nil reaching integer arithmetic",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "A nullable read flows into arithmetic without narrowing. Sibling of the nullable-column arc that closed across all targets on 07-25 — this is the arithmetic seam that arc did not cover.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "dirty-saved-change-alias",
      "title": "saved_change_to_* read surface incomplete",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "This regex spans TWO shapes, which is why its count did not move when only one was fixed: the per-column saved_change_to_<col>? (synthesized by the lowering, since a static runtime cannot answer a name it learns at emit time) and the argument-taking saved_change_to_attribute?(:col) (a runtime Base method). The generic form was the dominant one — 133 of the 149 examples.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "has-attribute-predicate",
      "title": "has_attribute? unimplemented on the model surface",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "Arrived with lobsters c7dce360 (#2164), which rewrote the Token concern's after_initialize to guard on has_attribute?(:token). The concern is included widely, so it fires on nearly every model factory — it briefly accounted for 140 of 354 examples as the first exception, sitting in front of the dirty-alias cause behind it.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "scope-not-on-relation",
      "title": "a model scope does not chain off a Relation",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "Scopes lower to class methods on the model, so Model.scope resolves but relation.scope does not — lobsters reaches them through associations (user.comments.active, where :active is scope :active on Comment). Matches the whole family rather than one scope name, since which scope surfaces first is an accident of call order. Sibling of the residual noted in the Relation[T] work: scalar class methods deliberately do not forward.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "binary-column-text-escape",
      "title": "binary columns inline-escape as text — NUL bytes truncate the SQL",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "comments.confidence_order is t.binary holding pack()ed bytes; Db.escape_string doubles quotes but a NUL byte ends the statement at the C level, so the INSERT dies at that column. Needs a blob ValueType through the column mapping (X'hex' literals + column_blob reads) across the adapter shims. Sits under the comment factory's save, so it gates most of the comment/story cluster.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "searchparser-parslet-dsl",
      "title": "SearchParser emits an empty class — parslet's class-body DSL is dropped",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "parslet `rule(...)` calls in a class body are silently dropped though main.rb requires the gem, so SearchParser emits as a 2-line empty class. Needs verbatim class-body passthrough for non-AR classes on the ruby family, and a warning either way.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "keystore-upsert",
      "title": "Keystore.upsert unimplemented",
      "owner": "compiler",
      "failures": 0,
      "share": 0.0,
      "note": "Needs Rails upsert with on_duplicate:/returning: and `self.primary_key = \"key\"` honored. Keystore.increment_value_for sits under Story#mark_submitter, so it fires on nearly every model that saves a story.",
      "stale_rule": true,
      "examples": []
    },
    {
      "slug": "unclassified",
      "title": "Not yet triaged",
      "owner": "unknown",
      "failures": 69,
      "share": 0.4825,
      "note": "No rule in bench/lobsters/spec-causes.json matches these. Triage, then add a rule.",
      "stale_rule": false,
      "examples": [
        {
          "example": "Category logs modification in moderation log logs on update",
          "error": "NoMethodError: undefined method 'update' for nil"
        },
        {
          "example": "CommentRedirectCache caches the destination url, no newline",
          "error": "NoMethodError: undefined method 'comment_redirect_cache_dir=' for an instance of Rails::FrameworkConfig"
        },
        {
          "example": "CommentRedirectCache .delete is idempotent",
          "error": "NoMethodError: undefined method 'comment_redirect_cache_dir=' for an instance of Rails::FrameworkConfig"
        },
        {
          "example": "Comment speed limit limits within 2 minutes",
          "error": "ActiveRecord::StatementInvalid: no such column: comments.id:"
        },
        {
          "example": "Comment speed limit limits longer with flags",
          "error": "ActiveRecord::RecordInvalid: Validation failed: Story must exist"
        },
        {
          "example": "Comment speed limit has an extra message if author flagged a parent",
          "error": "ActiveRecord::RecordInvalid: Validation failed: Story must exist"
        },
        {
          "example": "Comment speed limit doesn't limit slow responses",
          "error": "ActiveRecord::RecordInvalid: Validation failed: Story must exist"
        },
        {
          "example": "Domain origin takes a selector and replacement to generate an origin identifier",
          "error": "NoMethodError: undefined method 'starts_with?' for an instance of String"
        },
        {
          "example": "Domain origin creates a bare-domain origin for bare and trailing slash URLs",
          "error": "NoMethodError: undefined method 'starts_with?' for an instance of String"
        },
        {
          "example": "Domain origin inserts start-and-end-of-line anchors to ",
          "error": "NoMethodError: undefined method 'starts_with?' for an instance of String"
        }
      ]
    }
  ],
  "by_file": [
    {
      "file": "spec/models/story_spec.rb",
      "examples": 81,
      "passed": 47,
      "failed": 34
    },
    {
      "file": "spec/models/search_spec.rb",
      "examples": 33,
      "passed": 9,
      "failed": 24
    },
    {
      "file": "spec/models/domain_spec.rb",
      "examples": 26,
      "passed": 6,
      "failed": 20
    },
    {
      "file": "spec/models/link_spec.rb",
      "examples": 14,
      "passed": 0,
      "failed": 14
    },
    {
      "file": "spec/models/user_spec.rb",
      "examples": 22,
      "passed": 8,
      "failed": 14
    },
    {
      "file": "spec/models/comment_spec.rb",
      "examples": 25,
      "passed": 18,
      "failed": 7
    },
    {
      "file": "spec/models/invitation_request_spec.rb",
      "examples": 7,
      "passed": 3,
      "failed": 4
    },
    {
      "file": "spec/models/mod_note_spec.rb",
      "examples": 4,
      "passed": 0,
      "failed": 4
    },
    {
      "file": "spec/models/invitation_spec.rb",
      "examples": 4,
      "passed": 1,
      "failed": 3
    },
    {
      "file": "spec/models/moderation_spec.rb",
      "examples": 3,
      "passed": 0,
      "failed": 3
    },
    {
      "file": "spec/models/notification_spec.rb",
      "examples": 16,
      "passed": 13,
      "failed": 3
    },
    {
      "file": "spec/models/tag_spec.rb",
      "examples": 10,
      "passed": 7,
      "failed": 3
    },
    {
      "file": "spec/models/comment_redirect_cache_spec.rb",
      "examples": 2,
      "passed": 0,
      "failed": 2
    },
    {
      "file": "spec/models/message_spec.rb",
      "examples": 4,
      "passed": 2,
      "failed": 2
    },
    {
      "file": "spec/models/vote_spec.rb",
      "examples": 13,
      "passed": 11,
      "failed": 2
    },
    {
      "file": "spec/models/category_spec.rb",
      "examples": 6,
      "passed": 5,
      "failed": 1
    },
    {
      "file": "spec/models/hat_spec.rb",
      "examples": 5,
      "passed": 4,
      "failed": 1
    },
    {
      "file": "spec/models/mod_mail_spec.rb",
      "examples": 1,
      "passed": 0,
      "failed": 1
    },
    {
      "file": "spec/models/story_image_spec.rb",
      "examples": 2,
      "passed": 1,
      "failed": 1
    },
    {
      "file": "spec/models/comment_stat_spec.rb",
      "examples": 1,
      "passed": 1,
      "failed": 0
    },
    {
      "file": "spec/models/hat_request_spec.rb",
      "examples": 3,
      "passed": 3,
      "failed": 0
    },
    {
      "file": "spec/models/keystore_spec.rb",
      "examples": 4,
      "passed": 4,
      "failed": 0
    },
    {
      "file": "spec/models/mastodon_app_spec.rb",
      "examples": 3,
      "passed": 3,
      "failed": 0
    },
    {
      "file": "spec/models/search_parser_spec.rb",
      "examples": 78,
      "passed": 78,
      "failed": 0
    },
    {
      "file": "spec/models/story_text_spec.rb",
      "examples": 1,
      "passed": 1,
      "failed": 0
    },
    {
      "file": "spec/models/username_spec.rb",
      "examples": 1,
      "passed": 1,
      "failed": 0
    }
  ]
}