You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Native extensions. audition . in a gem checkout, audition <gem> on an installed gem, app targets, and bundle sweeps now
report every compiled extension (.bundle/.so) that never
declares Ractor safety, as a warning: every method such an
extension defines raises Ractor::UnsafeError on the first
call from a non-main Ractor, C, Rust, and Zig alike (verified
on Ruby 4.0.6). The static check (native-extension) is a
byte scan for the rb_ext_ractor_safe import, so it covers
precompiled platform gems that ship no sources; an unbuilt
checkout is scanned at the source level instead (ext/** in
C, Rust, or Zig), anchored at the declaration or at Init_*,
where it belongs, and harness trees (fuzz, benches, tests)
are ignored. The require and Rails probes extend it to
dependencies (runtime-native-extension): the harness records
every compiled file the load pulled in and byte-scans those
too, attributing them to the dependency; Ruby's own archdir
extensions are left to Ruby, and files the static check
already reported are not repeated. Declared extensions get an
info note, since the declaration is the maintainer's
assertion, not a proof. Cargo and Zig build output and .dSYM copies are skipped.
The require probe finds squashed entry files. audition activesupport used to fail its dynamic probe with "cannot load
such file": the gem's entry is active_support, and no rule
inverts that spelling. When the target ships exactly one
top-level file under lib/, the probe now requires it after
the name and its slashed form fail, and reports the last error
seen rather than the first.
Mutable constants learn three shapes, each verified on Ruby
4.0.6: a bare Object.new sentinel (safe .freeze autofix,
withheld when the file gives the object singleton methods; BasicObject.new, which has no #freeze, gets an Object.new.freeze replacement in the unsafe tier), Set.new([...]), Set[...], and [...].to_set as containers,
and Concurrent::Map, which cannot be frozen at all, beside
the sync primitives. A constant frozen by a bare NAME.freeze
statement later in the same class body now counts as
build-then-freeze, so only provably mutable elements are
reported. The container autofix writes a plain .freeze when
every element is provably shareable and keeps the deep Ractor.make_shareable wrap otherwise; bracket-less X = :a, :b gains its brackets. On the 43 Rails files studied
below the new rules flag all fourteen sites the PRs fixed and
none after; on current rails/rails they find sixteen more that
no PR has touched.
Advice text stands on Ruby semantics: the class-variable macro
advice names the singleton-ivar-plus-delegate conversion next
to class_attribute, the runtime-require advice names the
class-level macro as the boot-time scope for an optional
dependency, and no advice cites Rails as the reason for a
recipe or carries a commit id.
Fix knowledge base: fourth pass, the 28 merged "Ractor
Support" PRs that the commit-message and pickaxe selection of
the first two passes never saw (freezes, memo deletions, and
eager requires mention neither Ractor nor ractor). All 45
commits and their review threads read in full and distilled
into nine new patterns in docs/rails_core_best_practices.md:
sentinel and Set constants, benchmark-gated memo deletion,
constants versus module ivars for registries, settings as
singleton ivars plus delegate, capture-free boot procs and the
boot-in-raise-mode gate, share-a-copy and freeze-in-the-setter,
main-or-local singletons with per-Ractor rebuilds, subsystem
make_shareable! hooks, and boot-time loading hygiene. audition
was run on both sides of the 43 files the PRs touch.
Fix knowledge base: third pass, the gem dialect. i18n PR 741
(the first full gem conversion out of the Rails ractorization
effort) read in full and distilled into three new patterns in
docs/rails_core_best_practices.md: config class variables
moving to singleton-class ivars behind delegators, the opt-in <gem>/ractorize entry point, and frozen caches degrading to
recompute-per-call. audition was run on both sides of the PR
to verify its checks against the conversion; it confirmed the
fixes, caught a .freeze lost in a rebase and a class
variable read that still raises from workers (reproduced on
Ruby 4.0.6), and the class-variable semantics claim in the
checks' advice was re-verified: reads raise even for
shareable values.
CI-ready. --exit-zero (alias for the new --fail-on never,
also accepted in .audition.yml) reports every finding but
always exits 0, the adoption mode other linters ship under the
same name. --format github now also appends a verdict and
counts markdown table to the job summary page when GitHub
Actions provides one, strips ./ prefixes so annotations
anchor to the PR diff, and works for bundle sweeps: one
annotation per failing gem plus the readiness line, instead of
a terminal table.
Git-hook-ready. Several .rb file arguments now audit as one
static target, the shape hook managers pass staged files in
(lefthook's {staged_files}, pre-commit's filename
arguments); config, pragmas, and the baseline resolve against
the working directory. Previously everything after the first
argument was silently ignored. README gained a CI and git
hooks section with copy-paste lefthook, pre-commit, and
GitHub Actions configs.
Dogfooding: this repository now runs audition on itself, on
every commit through lefthook (staged files, static) and on
every push through a non-blocking CI self-audit with PR
annotations and a job summary.
Dynamic dependency attribution now matches the static
scanner's exclusion rule: constants whose source lives in a
vendored or dot directory under the target root (Bundler's
deployment mode and Actions' bundler-cache put every gem in vendor/bundle) count as dependencies, not as the target's
own code. Found by the very first CI self-audit, which
attributed the vendored gems to audition itself and flipped
the verdict from blocked to not_ready.
Report rendering split into one class per format
(Report::Text, Report::Json, Report::Github); the Report class keeps only the data, verdict, and counts. The Report#to_text/to_json/to_github methods are gone, an API
change for anyone driving audition programmatically.