- Rails engine, install generator, migration, and CLI
- Explicit actor registry, references, JSON state, and state migrations
- Fluent direct synchronous RPC, configured
sync, and durableasync - Durable message history plus ready/claimed membership tables
- Concurrent sequence allocation and actor creation
- Activation leases, renewal, unique activation tokens, generations, and fenced commits
- Bounded activation passes, idle cache, hot-actor yield, and process records
- At-least-once retries, terminal domain rejection, strict poison ordering, dead letters, and tail retry
- Transactional effects with success/failure actor messages carrying the originally staged arguments for callback correlation
- Actor-to-actor asynchronous outbox delivery. Effects and broadcasts use portable status rows with polling indexes and database check constraints on status, which works on all three adapters; a future version may add narrow ready/claimed membership tables for very large outboxes, as messages already have
- One-shot and recurring reminders with
:latestor:allcatch-up, exercised through a realsolid_objects startworker as well as in process. They were listed here while broken in that worker: the scheduler reached a constant the caller path happened to load, so reminders never fired in production and every in-process test still passed - Durable invalidation-only observable broadcasts by default, explicit
broadcast: :valuescalar Turbo replacement, keyed ERB components, signed component locals, and authorized replace or morph refresh. Default observables retain component change detection while storing and broadcasting no projected value - Batched component refreshes: components sharing a signed
batch:collapse to one browser request per revision, served as HTML frames in a JSON envelope - Personalized state payload broadcasts computed per subscriber under that
subscriber's authorization context, fenced by actor revision, resolved through
payload_authorization_contextso the block andauthorize_querysee the same subject a controller render passes, and confined so one failing payload cannot reject the subscription or stop its siblings - Reconciliation read APIs
- Installation doctor, authorization reference, fit guide, and legacy-state migration cookbook
- Database server verification: each adapter reports its version against a tested minimum, MySQL confirms Solid Objects tables use InnoDB, and the doctor warns rather than refusing to run on an untested server
- Handler Active Record write isolation, same-database commit actions, ambient transaction rejection, adapter lock/query deadlines, bounded SQLite lock retries outside those deadlines, structured sync timeout diagnostics, and result recovery
- Bounded message/process pruning, actor-type opt-in instance expiration, graceful caller shutdown, committed state snapshots, and an opt-in Minitest helper that clears every actor-owned table itself rather than relying on the database cascade, which a host application may not enforce, and runs due reminders against an explicit test time without moving the database clock
- Supervisor role replacement: a role whose thread dies is restarted until shutdown is requested, and dead process records plus expired message and process history are pruned on their own intervals without an application scheduling its own job
- SQLite, PostgreSQL, and MySQL integration suites, with MySQL run against both
the
mysql2andtrilogyclients because an adapter name, a connection collation, and an error code name all differ between them - Opt-in cross-process wake-up on PostgreSQL through
WakeUpAdapters.for, with a listening connection per waiting thread and release on supervisor shutdown - Opt-in cross-process wake-up on Redis, the option for MySQL applications,
measured at 103.8 ms to 5.7 ms at p50; the
redisgem stays outside this gem's dependencies - Inline RBS generation/validation, Steep, Standard Ruby, Solid Queue's exact RuboCop policy, and a warning-free Brakeman scan
- Compatibility CI across the supported span: Ruby 3.3, 3.4, and 4.0 against
Rails 7.1, 7.2, 8.0, and 8.1, pinned through
RAILS_VERSIONso the advertised range is verified rather than assumed. The compatibility job runs SQLite only; the PostgreSQL and MySQL jobs run on the newest Rails, so adapter behavior on Rails 7.1 and 7.2 is unmeasured against those servers. Rails 7.0 is out of range because its SQLite adapter requiressqlite3 ~> 1.4, and this gem needs the busy-handler control that arrived insqlite32.x - The transmit family, both sides.
SolidObjects::Transmission.receiveis the ingest: envelope validation, actor type resolution with a per-callresolve_actor_type:escape hatch, and an internal idempotent enqueue keyedtransmit:<effectId>.Actor#transmitandSolidObjects.register_transmitare the staging side: a transactionalsolid-objects.transmiteffect and a drain that delivers every undelivered sibling for the actor up to the claimed effect's mailbox sequence, oldest first, so per-actor order survives a failed delivery. The wire contract is pinned by golden fixtures incompatibility/transmit-envelopes.json. The engine mountsPOST /solid_objects/transmitbehind a deny-by-defaultauthorize_transmissionpolicy with a configurable actor type resolver. Bidirectional replication as a declared surface, with echo suppression, is not implemented - A JavaScript suite covering every browser module, run in CI with Node's test runner and jsdom, plus a browser suite running the same modules against real Chromium and a real Turbo build, with every GitHub Actions reference pinned to a commit SHA. The browser suite covers the reconnect burst: convergence of batched and unbatched components, an inert replay of an applied revision, cancellation of the request left in flight by the drop, incarnation ordering after a destroy and recreate, and payload delivery exactly once per revision
- Wake-up strategy: in-process signaling, durable polling, injection, and
cross-process adapters for PostgreSQL and Redis are implemented and tested.
What is not done is making any of them automatic. In-process signaling cannot
cross process boundaries, so by default a commit in a web process does not
wake a broadcast executor in a worker process and that delivery waits up to
the current adaptive polling interval, up to the one-second
idle_polling_intervaldefault. The runtime warns once when it observes this topology without an adapter. An adapter removes that floor, measured before adaptive polling at 103.7 ms to 2.9 ms at p50 on PostgreSQL and 103.8 ms to 5.7 ms on Redis, but each stays opt-in for a reason: the PostgreSQL adapter opens a connection per waiting thread outside the pool andLISTENdoes not survive a transaction-pooling proxy such as PgBouncer, and Redis is not a dependency of this gem.WakeUpAdapters.forselects notifications on PostgreSQL and the in-process default elsewhere; it never selects Redis. An application that configures nothing keeps polling, and MySQL applications keep polling unless they configure Redis explicitly. - Realtime: scalar and dependency-driven keyed ERB component replacement or
morphing, personalized refresh authorization, revision fencing, coalescing,
reconnect convergence, batched refreshes, and personalized state payloads are
implemented; application-directed Turbo append intents are not. Batch
coalescing happens in the browser rather than the broadcast executor, so one
commit still sends one Action Cable message per changed observable even
though it costs one browser request. Reconnect convergence previously
bypassed batching entirely, issuing one request per stale component at the
moment a restart reconnects every client at once; it now shares the batching
the live invalidation path uses. Payload delivery over Action Cable was
untested end to end, which is how a raising payload block came to reject the
subscription; it is now covered and confined, and the payload authorization
context is resolved through
payload_authorization_contextrather than handing the block a raw Cable connection. - Backpressure: mailbox/payload/state/result caps and fair yields exist; distributed per-actor rate limits and global admission control do not.
- Administration:
SolidObjects::Webis a mountable Rack dashboard covering instances, mailbox, reminders, effects, broadcasts, dead letters, and processes, with actor-type and actor-id filtering, status filters, paging, a polled stats endpoint, Chart.js charts, and extension registration. The chart library is fetched from a CDN with a subresource integrity hash, which a deployment without outbound network access must replace with a vendored copy or turn off. Every route declares its own administration policy and a route declared without one raises at load time, so the deny-by-default posture is enforced by construction rather than by remembering to add a check. It changes only two things: an idempotent dead letter retry and instance pause/resume. What does not exist is audit records of who pressed what, and bulk-safe tools: retry is one dead letter at a time, becauseDeadLetterManagerexposes no bulk operation. Pause is an operator brake and not a stop, since a pass already in flight finishes its turn and a synchronous caller waiting on a paused instance times out. Retry also only exists for message dead letters: a dead effect or broadcast has no retry API, which matters for transmit effects because a dead one is a lost replay until an operator returns its row to pending. The page cost was reasoned about rather than measured: the summary bar issues a fixed set of indexed aggregate queries per page, which is whyHEAD /exists for uptime monitors, but no dashboard latency has been benchmarked against a large table.
- Add result lookup by request ID and broader deadlock retry classification.
- Add Turbo append intents.
- Add distributed rate limits, global admission hooks, and cache-capacity eviction.
- Expand security scanning beyond the Brakeman scan, such as dependency auditing and secret scanning.
- Benchmark all workloads under documented hardware/database settings and publish adapter-specific adoption measurements. Throughput, synchronous latency, query counts, and the three reactive delivery paths are measured on SQLite; adapter-specific and end-to-end browser measurements are not.
No production-ready claim should be made until these hardening milestones have operational soak evidence.