Every behavior change starts with a focused failing Minitest, followed by the smallest implementation that makes it pass and a refactor while green. The suite follows Solid Queue's organization:
test/unitfor actor DSL and pure value objectstest/modelsfor records, constraints, and query objectstest/integrationfor end-to-end runtime and process behaviortest/test_helpersfor deterministic barriers, process control, and cross-connection observationtest/dummyfor a real host Rails application and engine integration
Core coordination tests use real PostgreSQL, MySQL, and SQLite connections. Tests that require independent sessions do not use transactional wrapping and clean their records explicitly. Race tests use barriers, queues, condition variables, or child-process pipes rather than timing-based sleeps as their primary synchronization.
solid_objects.gemspecGemfileRakefilelib/solid_objects.rblib/solid_objects/version.rblib/solid_objects/engine.rbtest/dummy/**test/test_helper.rb.github/workflows/ci.yml.standard.ymlSteepfilesig/**
SolidObjects.configure and SolidObjects::VERSION. All owned Ruby files use # rbs_inline: enabled, instance-variable declarations, and method signatures following cardmagic/classifier.
None.
- Engine boots in the dummy application.
- PostgreSQL, MySQL InnoDB, and SQLite adapters and minimum server versions are validated.
- Zeitwerk eager loading succeeds.
- RBS::Inline generation, RBS validation, and Steep checking succeed.
- Rails or Ruby outside the supported range.
- Missing
pgadapter. - Missing
mysql2orsqlite3adapter in its matrix job. - Test database unavailable.
Bundle installs, each dummy database boots, and the empty Minitest suite, Standard Ruby, generated RBS validation, and Steep pass.
lib/solid_objects/actor.rblib/solid_objects/actor_registry.rblib/solid_objects/actor_definition.rblib/solid_objects/state.rblib/solid_objects/serialization.rblib/solid_objects/context.rblib/solid_objects/errors.rbtest/unit/actor_registry_test.rbtest/unit/actor_test.rbtest/unit/state_test.rbtest/unit/serialization_test.rb
SolidObjects::Actoractor_typeattributewith actor instance readers and writers- Public instance methods as messages
messagefor explicit dynamic definitionsquery,observablestate_version,migrate_stateon_activate,on_deactivateActorClass.ref(actor_id)
None.
- Registration and duplicate rejection
- Public, private, query, and observable method lookup
- Defaults are not shared
- JSON normalization and unsafe value rejection
- Complete state migration chains and newer-state rejection
- Anonymous or duplicate actor types
- Undefined message names
- Mutable defaults shared across actors
- Unsafe serialization
- Missing migration step
Actor definitions can be instantiated and executed in memory without persistence or constantization.
db/migrate/001_create_solid_objects_tables.rbapp/models/solid_objects/record.rbapp/models/solid_objects/instance.rbapp/models/solid_objects/message.rbapp/models/solid_objects/ready_message.rbapp/models/solid_objects/claimed_message.rbapp/models/solid_objects/reminder.rbapp/models/solid_objects/effect.rbapp/models/solid_objects/broadcast.rbapp/models/solid_objects/dead_letter.rbapp/models/solid_objects/process.rblib/solid_objects/database_adapter.rblib/solid_objects/database_adapters/postgresql.rblib/solid_objects/database_adapters/mysql.rblib/solid_objects/database_adapters/sqlite.rbdocs/database-schema.mdtest/models/schema_constraints_test.rbtest/models/instance_test.rbtest/models/message_test.rb
No new application API. Internal records are deliberately namespaced.
Create the seven domain tables plus ready- and claimed-message membership tables. Add foreign keys, unique constraints, positive sequence/version checks, ordinary composite polling indexes, and cleanup indexes. Use JSONB on PostgreSQL, JSON on MySQL, and Rails JSON-compatible columns on SQLite. Do not use partial indexes or a message status column.
- Actor identity uniqueness
- Concurrent actor creation
- Counter and membership constraints
- Foreign-key cleanup behavior
- Ready and claimed hot-table index definitions
- Configured table prefix
- MySQL tables use InnoDB
- SQLite write transactions and busy retry
- Unsupported database or server version
- Prefix changed after migration
- A message accidentally represented in both ready and claimed tables
- Cascades deleting required diagnostic data
The schema installs in PostgreSQL, MySQL, and SQLite dummy databases and database constraints reject invalid states independently of Rails validations.
lib/solid_objects/reference.rblib/solid_objects/client.rblib/solid_objects/mailbox.rblib/solid_objects/message_reference.rblib/solid_objects/wake_up.rbtest/integration/enqueue_test.rbtest/integration/enqueue_test.rbtest/integration/sync_test.rbtest/integration/synchronous_invocation_test.rb
Reference#asyncReference#sync- Synchronous method-style message, query, and read-only attribute dispatch
MessageReference#id,#status,#result- Authorization context and hooks
No new tables. Use instance sequence and message request/idempotency columns.
- Per-actor sequence allocation under concurrent connections
- Independent sequences for different actors
- Idempotency key deduplication
- Async return value
- Synchronous success, rejection, failure, and timeout
- Caller-assisted processing behind earlier asynchronous work
- Same-actor serialization and different-actor concurrency
- Mailbox and payload limits
- Message/query authorization failure
- Concurrent first enqueue
- Lock timeout or deadlock
- Duplicate idempotency key with different payload
- Synchronous caller timeout
- Oversized payload or mailbox
Messages and ready membership enqueue durably in strict per-actor sequence.
Direct methods and sync claim and execute the actor locally when possible,
while async returns immediately for worker execution. Every path uses the
same mailbox, lease, fencing, and durable result.
lib/solid_objects/activation.rblib/solid_objects/lease.rblib/solid_objects/executor.rblib/solid_objects/worker.rblib/solid_objects/dispatcher.rblib/solid_objects/process_registry.rbexamples/shopping_cart_actor.rbtest/integration/vertical_slice_test.rbtest/integration/sequential_processing_test.rbtest/integration/retry_test.rbtest/integration/lease_test.rbtest/integration/fencing_test.rbtest/integration/crash_recovery_test.rb
Runnable SolidObjects::Worker; current message context inside actors. A worker cannot process actor state without a registered process, renewable activation lease, and fencing generation.
No new tables.
- Shopping cart synchronous and asynchronous invocation
- One actor processes messages sequentially
- Different actors can execute concurrently
- Lease acquire, renew, expire, and release
- Two workers cannot hold the same actor lease
- Deterministic stale-writer rejection
- Crash recovery and at-least-once redelivery
- State and completion are atomic
- Basic retry and strict head-of-mailbox blocking
- Handler-level duplicate-delivery guards
- Actor-to-actor asynchronous delivery
- Actor exception
- Serialization failure after actor code
- Query mutates state
- Worker shutdown during a turn
- Process pause beyond lease expiry
- Lease renewal race
The example actor runs end to end against all three databases and persists/reactivates state. Real multi-connection tests prove that generation A cannot write after generation B acquires and commits. The runnable worker always enforces leases and fencing; no unsafe single-worker mode exists.
lib/solid_objects/supervisor.rblib/solid_objects/activation_manager.rblib/solid_objects/configuration.rbtest/integration/process_lifecycle_test.rbtest/integration/fairness_test.rb
Process configuration and lifecycle hooks.
Use process and activation columns already created. Add a migration only if query-plan evidence requires a new lease index.
- Heartbeats and stale process cleanup
- Graceful shutdown
- Max message and duration budgets
- Hot actor fairness
- Process pause rather than death
- Heartbeat task failure
- Database outage during release
- Child process boot or shutdown timeout
Real process tests on PostgreSQL, MySQL, and SQLite demonstrate heartbeat cleanup, bounded fairness, and graceful shutdown. PostgreSQL and MySQL additionally prove SKIP LOCKED; SQLite proves serialized BEGIN IMMEDIATE claims and busy retry.
lib/solid_objects/effect_registry.rblib/solid_objects/effect_executor.rblib/solid_objects/outbox_dispatcher.rbtest/integration/effects_test.rbtest/integration/actor_communication_test.rb
emitSolidObjects.register_effectsend_to
Use the effects table. Add delivery-token or outcome columns only through a migration.
- Effect insert is atomic with state/message completion
- Rollback leaves no effect
- Delivery retry and dead effect
- Stable idempotency context
- Success/failure outcome messages
- Transactional actor-to-actor delivery
- Direct and
syncactor-to-actor calls rejected in actor context
- External success before local acknowledgement
- Handler missing after deploy
- Outcome payload too large
- Target actor message renamed
Effects and actor messages are never delivered for a rolled-back actor turn and can be retried without losing their stable IDs.
lib/solid_objects/reminder_scheduler.rbtest/integration/reminders_test.rb
schedule- Reminder cancellation and inspection API
Use reminders plus mailbox idempotency. Add a unique occurrence index if not in the initial schema.
- One-shot reminder
- Recurring occurrence uniqueness with two schedulers
- Reminder reactivates idle actor
- Scheduler crash recovery
- Missed-occurrence policies
- Cancellation race
- Clock jumps
- Duplicate scheduler claims
- Long outage creates excessive catch-up
- Reminder callback removed in code
Due reminders become ordinary mailbox messages exactly once per occurrence record while their eventual message execution remains at least once.
app/channels/solid_objects/actor_channel.rbapp/controllers/solid_objects/actor_states_controller.rbapp/helpers/solid_objects/actors_helper.rblib/solid_objects/stream_name.rblib/solid_objects/broadcast_executor.rbconfig/routes.rbdocs/realtime.mdtest/channels/solid_objects/actor_channel_test.rbtest/helpers/solid_objects/actors_helper_test.rbtest/integration/broadcasts_test.rb
solid_object- Scope
valueandcomponent - Subscription and state-read authorization
Use broadcast outbox rows and their retry fields.
- Initial server render
- Stable DOM IDs
- One actor subscription for multiple targets
- Signed token verification and authorization
- Changed observable detection
- Broadcast inserted with commit, never rollback
- Broadcast retry
- Reconnect refresh current state
- Action Cable or Turbo absent
- Disconnected client
- Duplicate replacement
- Authorization changes while connected
- Component renderer missing
An authorized scope renders current values and converges after reconnect; observable broadcasts are durable and post-commit.
lib/solid_objects/cli.rbexe/solid_objectslib/solid_objects/log_subscriber.rblib/solid_objects/instrumentation.rblib/tasks/solid_objects_tasks.rakeapp/controllers/solid_objects/admin/**app/views/solid_objects/admin/**docs/operations.mddocs/correctness.mddocs/security.mdtest/unit/cli_test.rbtest/integration/instrumentation_test.rbtest/integration/dead_letters_test.rbtest/models/instance_reconciliation_test.rb
CLI start, check, status, dead-letter list/retry, and prune commands. Optional read-only admin engine. Batchable read-only actor relations: .active, .without_pending_work, and .orphaned.
No expected changes.
- Required notification events and redacted payloads
- Structured log fields
- Dead-letter inspection and retry
- Admin authorization
- Pruning retention and bounded batches
- CLI exit statuses
- Lost-alarm and orphan discovery without direct state mutation
- Sensitive data in logs
- Unbounded admin queries
- Retrying wrong dead letter
- Cleanup racing with a synchronous waiter
- Reconciliation code mutating actor state outside
async - Reconciliation stampedes without delayed
available_at
Operators can inspect health and failures without direct SQL, locate lost alarms and orphaned actors, and observe every required transition without raw arguments. Documentation requires reconciliation repairs to use delayed async delivery rather than direct instance updates.
test/dummy/app/actors/shopping_cart_actor.rbtest/dummy/app/actors/chat_room_actor.rbtest/dummy/app/controllers/**test/dummy/app/views/**benchmark/enqueue.rbbenchmark/claim.rbbenchmark/processing.rbbenchmark/workloads.rbREADME.mddocs/development.mddocs/state-migrations.mddocs/roadmap.md
Final documented v0.x API.
Only evidence-driven index changes, each with query-plan tests and migration notes.
- Shopping cart and chat room end-to-end flows
- Per-backend query counts
- Rolling-version compatibility fixtures
- Full suite on supported Rails versions
- Standard Ruby and security audit
- Gem build and install smoke test
- Example-specific API design
- Benchmark environment mistaken for capacity guarantee
- Version matrix regressions
- Packaging omits engine files or migrations
The gem builds, installs into the dummy app, passes all available database suites plus formatting, inline RBS, type, and security checks, and documents implemented, partial, and future behavior without a production-ready claim unsupported by evidence.