{
  "id": "rails-hotwire-postgresql",
  "name": "Rails + Hotwire + PostgreSQL",
  "use_cases": [
    "SaaS",
    "Business CRUD",
    "Collaboration and workflow products"
  ],
  "choose_when": [
    "An integrated monolith and server-owned UI fit the product.",
    "A small team benefits from stable conventions and limited integration work."
  ],
  "avoid_when": [
    "The core is an offline-first canvas or requires extensive browser-owned state.",
    "Hard CPU/latency constraints justify a specialist service."
  ],
  "tools": [
    {
      "job": "UI",
      "default": "ERB + Turbo + Stimulus",
      "alternatives": [
        "React client when browser-owned state dominates; retain Rails API"
      ],
      "reason": "Server-rendered HTML and selective JavaScript limit duplicated UI state.",
      "limits": "Do not force Hotwire onto rich offline editors.",
      "source_urls": [
        "https://hotwired.dev/"
      ]
    },
    {
      "job": "Database and migrations",
      "default": "PostgreSQL + Active Record migrations",
      "alternatives": [
        "SQLite for deliberately single-host deployments"
      ],
      "reason": "Relational constraints and conventional migrations fit business data.",
      "limits": "Use unique/foreign-key constraints; assess locks and use expand/contract changes for rolling deploys.",
      "source_urls": [
        "https://guides.rubyonrails.org/active_record_migrations.html"
      ]
    },
    {
      "job": "Authentication",
      "default": "Rails authentication generator",
      "alternatives": [
        "Devise when its additional authentication modules are needed"
      ],
      "reason": "Generated local sessions plus explicit per-resource authorization.",
      "limits": "Generator is a starting point; signup, authorization and account lifecycle require product decisions.",
      "source_urls": [
        "https://guides.rubyonrails.org/getting_started.html",
        "https://github.com/varvet/pundit",
        "https://github.com/heartcombo/devise"
      ]
    },
    {
      "job": "Authorization",
      "default": "Pundit policy classes",
      "alternatives": [
        "Existing resource-level policy implementation; keep authentication separate"
      ],
      "reason": "Generated local sessions plus explicit per-resource authorization.",
      "limits": "Generator is a starting point; signup, authorization and account lifecycle require product decisions.",
      "source_urls": [
        "https://guides.rubyonrails.org/getting_started.html",
        "https://github.com/varvet/pundit",
        "https://github.com/heartcombo/devise"
      ]
    },
    {
      "job": "Jobs",
      "default": "Active Job + Solid Queue",
      "alternatives": [
        "Another Active Job backend only for measured operational needs"
      ],
      "reason": "Database-backed jobs avoid a mandatory separate broker.",
      "limits": "Configure worker capacity, retry limits and idempotency; separate queue DB does not guarantee atomic app-write/enqueue.",
      "source_urls": [
        "https://guides.rubyonrails.org/active_job_basics.html"
      ]
    },
    {
      "job": "Tests",
      "default": "Minitest + Rails integration/system tests",
      "alternatives": [
        "Keep RSpec in an established RSpec repository"
      ],
      "reason": "Exercise authorization, database constraints and essential browser flows.",
      "limits": "Browser tests need their driver/browser; unit tests alone miss UI and transaction races.",
      "source_urls": [
        "https://guides.rubyonrails.org/testing.html"
      ]
    },
    {
      "job": "Lint and security",
      "default": "RuboCop + Brakeman + bundler-audit",
      "alternatives": [
        "Preserve existing compatible project tooling"
      ],
      "reason": "Style, application scanning and dependency advisories cover different risks.",
      "limits": "Scans do not establish security; test tenant isolation and unsafe uploads. bundler-audit has GPL-3.0 licensing.",
      "source_urls": [
        "https://guides.rubyonrails.org/getting_started.html",
        "https://guides.rubyonrails.org/security.html",
        "https://github.com/rubysec/bundler-audit"
      ]
    },
    {
      "job": "Deployment",
      "default": "Kamal + generated Dockerfile + PostgreSQL backups",
      "alternatives": [
        "Managed container hosting when server operations are undesirable"
      ],
      "reason": "Portable container deployment with application-owned configuration.",
      "limits": "Requires registry/server credentials and persistent data strategy; backups and restore drills remain operator work.",
      "source_urls": [
        "https://kamal-deploy.org/docs/installation/",
        "https://guides.rubyonrails.org/getting_started.html"
      ]
    },
    {
      "job": "Agent tooling",
      "default": "Generators + routes/schema/test feedback + version-matched Rails guides",
      "alternatives": [
        "Add only a reviewed MCP integration with a concrete need"
      ],
      "reason": "Conventions make edits easier to locate and verify.",
      "limits": "Agent productivity is an inference; no cross-framework coding-success benchmark established.",
      "source_urls": [
        "https://rubyonrails.org/ai"
      ]
    },
    {
      "job": "Licensing and hosting",
      "default": "Rails MIT; self-hostable",
      "alternatives": [
        "Paid infrastructure/support when operationally useful"
      ],
      "reason": "No mandatory proprietary Rails hosting service.",
      "limits": "Dependency licenses remain separate; do not infer all gems are MIT.",
      "source_urls": [
        "https://github.com/rails/rails/blob/main/MIT-LICENSE"
      ]
    },
    {
      "job": "Transactional email",
      "default": "Action Mailer",
      "alternatives": [
        "Preserve an existing compatible project choice"
      ],
      "reason": "Framework mailers integrate templates and queued delivery",
      "limits": "Configure provider transport and sender; use deliver_later when delivery must be queued; test content and failure behavior. Provider acceptance is not inbox delivery.",
      "source_urls": [
        "https://guides.rubyonrails.org/action_mailer_basics.html"
      ]
    },
    {
      "job": "Uploads and object storage",
      "default": "Active Storage",
      "alternatives": [
        "Preserve an existing compatible project choice"
      ],
      "reason": "Framework attachments support external storage and direct uploads",
      "limits": "Run the installed-version Active Storage generator/migrations; configure private storage, authorization, limits and cleanup. Public blob URLs and user-supplied files need an explicit access policy.",
      "source_urls": [
        "https://guides.rubyonrails.org/active_storage_overview.html"
      ]
    },
    {
      "job": "Realtime transport",
      "default": "Action Cable",
      "alternatives": [
        "Preserve an existing compatible project choice"
      ],
      "reason": "Rails channels integrate WebSocket messages with application code",
      "limits": "Configure chosen cable adapter and connection/channel authorization; test reconnect, revoked membership and host proxy behavior. Broadcast is not durable event history.",
      "source_urls": [
        "https://guides.rubyonrails.org/action_cable_overview.html"
      ]
    }
  ],
  "setup_steps": [
    "Start in a source directory in a local development workspace with supported Ruby/Rails, PostgreSQL and Git installed; choose and record compatible versions before generation.",
    "Run rails new product -d postgresql; enter product; configure development/test database access without committing credentials.",
    "Run bin/rails generate authentication; add Pundit via bundle add pundit and run bin/rails generate pundit:install; implement signup and resource policies deliberately.",
    "Run bin/rails db:prepare; start bin/rails server. Configure Solid Queue from the installed-version guide and run its worker with bin/jobs when enabled.",
    "Add bundler-audit to the development group if absent; preserve Gemfile.lock. Define authorization, duplicate-job and migration tests.",
    "Prepare config/deploy.yml, secrets, database persistence and backup/restore procedure. Only in an authorized deployment run bundle exec kamal setup initially or bundle exec kamal deploy thereafter."
  ],
  "verification_commands": [
    "ruby --version",
    "bundle exec rails --version",
    "bin/rails db:migrate:status",
    "bin/rails zeitwerk:check",
    "bin/rails test",
    "bin/rails test:system",
    "bin/rubocop",
    "bin/brakeman",
    "bundle exec bundler-audit check --update"
  ],
  "evidence_status": "documentation-reviewed",
  "open_questions": [
    "Which signup, SSO/MFA and resource authorization policies are required?",
    "Does the product fit server-owned screens or need substantial offline/client state?",
    "Which host, mail/storage services, worker throughput and restore requirements apply?"
  ],
  "sources": [
    {
      "url": "https://hotwired.dev/",
      "title": "Hotwire: HTML Over The Wire",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://guides.rubyonrails.org/active_record_migrations.html",
      "title": "Active Record Migrations",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://guides.rubyonrails.org/getting_started.html",
      "title": "Getting Started with Rails",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://github.com/varvet/pundit",
      "title": "Pundit README",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://github.com/heartcombo/devise",
      "title": "Devise README",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://guides.rubyonrails.org/active_job_basics.html",
      "title": "Active Job Basics",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://guides.rubyonrails.org/testing.html",
      "title": "Testing Applications",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://guides.rubyonrails.org/security.html",
      "title": "Securing Rails Applications",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://github.com/rubysec/bundler-audit",
      "title": "bundler-audit README and license",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://kamal-deploy.org/docs/installation/",
      "title": "Kamal Installation",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://rubyonrails.org/ai",
      "title": "Rails and AI",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://github.com/rails/rails/blob/main/MIT-LICENSE",
      "title": "Rails MIT License",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://guides.rubyonrails.org/action_mailer_basics.html",
      "title": "Rails Action Mailer",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://guides.rubyonrails.org/active_storage_overview.html",
      "title": "Rails Active Storage",
      "checked_date": "2026-10-10"
    },
    {
      "url": "https://guides.rubyonrails.org/action_cable_overview.html",
      "title": "Rails Action Cable",
      "checked_date": "2026-10-10"
    }
  ],
  "title": "Rails + Hotwire",
  "language": "Ruby",
  "group": "Web applications",
  "kind": [
    "web"
  ],
  "ui": "server",
  "host": "server",
  "summary": "Database-backed products with HTML, accounts and jobs close together.",
  "shape": "Rails · Turbo · Stimulus · PostgreSQL",
  "tradeoff": "Rich offline or canvas-heavy interfaces can need a separate client.",
  "research_file": "rails-laravel.json",
  "reviewed": "2026-10-10",
  "best_practices": [
    "Keep Turbo and Stimulus focused on interface behavior; put authorization in explicit policies.",
    "Run migrations and exercise the generated authentication flow before building product features around it.",
    "Treat Solid Queue workers as operated processes. Make retries safe and verify recovery after interruption.",
    "Use system tests for the critical browser journey and rehearse backups and restore before shipping."
  ],
  "research_freshness": {
    "core_guidance_checked_on": "2026-10-13",
    "sources": [
      "https://guides.rubyonrails.org/getting_started.html",
      "https://guides.rubyonrails.org/active_job_basics.html"
    ],
    "scope": "Core framework roles, current setup/agent documentation and cited-source availability rechecked; not a reimplementation or exhaustive semantic audit of every third-party dependency."
  },
  "guide_url": "/stacks/rails-hotwire-postgresql/",
  "markdown_url": "/stacks/rails-hotwire-postgresql/guide.md",
  "json_url": "/stacks/rails-hotwire-postgresql/record.json",
  "architecture_and_migration": {
    "id": "rails-hotwire-postgresql",
    "version_scope": "Rails guides showing Rails 8.1 examples; current Turbo handbook. These describe target capabilities, not the source application's installed versions. Detect Gemfile.lock, Ruby version and configured adapters before choosing a move.",
    "operating_model": "Rails supplies a conventional MVC application and Active Record persistence. Turbo Drive, Frames and Streams support a server-rendered HTML interface with incremental browser updates. Recommendation: keep domain writes on the server and introduce browser-owned state only for interactions that need it; a framework rewrite is not a prerequisite for fixing an inefficient query or incorrect queue contract.",
    "architecture": [
      "Recommendation: establish one authoritative domain writer and one schema migration runner per shared database during coexistence. Route a stable tenant or feature boundary, and keep the old application able to read new writes until rollback expires.",
      "Active Job is the application interface; Solid Queue is the Rails 8 default backend. Inspect the application's actual adapter before inferring a Redis dependency. Separate workers, recurring task configuration and domain writes are distinct responsibilities.",
      "Recommendation: treat Turbo stream DOM targets, Action Cable channel identifiers, job serialization and session cookies as separate contracts. Sharing a datastore does not prove any of their compatibility."
    ],
    "developer_workflow": [
      "In an existing checkout, read Gemfile.lock and environment configuration before adapting generators. In a disposable feature prototype, bin/rails generate model Product name:string and bin/rails db:migrate demonstrate the conventional model/migration path; review generated schema changes before applying them.",
      "bin/rails generate job guests_cleanup creates an Active Job class; configure its selected backend and worker process separately. Recipe only: use bin/jobs start for a configured Solid Queue application, then exercise enqueue, failure and recovery against its real queue database.",
      "bin/rails console is useful for inspecting application objects. Recommendation: capture authorized and unauthorized request fixtures, mixed-version writes and browser stream behavior as acceptance checks rather than treating successful boot as migration qualification.",
      "For a new local-account prototype, bin/rails generate authentication creates User and Session models plus login surfaces. Review account lifecycle and resource authorization separately; do not run it over an established authentication system without an intentional migration design."
    ],
    "avoid": [
      "Avoid calling a queue-adapter configuration change a backlog migration: the old backend retains work and Active Job can serialize GlobalID and custom types that need the original Ruby classes and database semantics.",
      "Avoid claiming atomic domain-write-and-enqueue from a separate Solid Queue database. After-commit enqueue prevents premature execution but is not a transaction across two stores. Recommendation: use a durable outbox when a crash between commit and delivery must be recoverable.",
      "Avoid the Action Cable async adapter for distributed production delivery; the guide restricts it to development/testing and a single process. Avoid replacing session or lock data with a disposable cache just because both use Redis."
    ],
    "transitions": [
      {
        "capability": "data",
        "existing_contract": "Inventory table names, primary keys, timestamps, enum values, constraints, callbacks and the application that currently owns migrations; Active Record migrations define schema evolution but do not describe all application side effects.",
        "coexistence": "Recommendation: keep the existing writer authoritative while the target reads representative data. Add backward-readable fields first; transfer one domain boundary explicitly. Keep a single migration runner rather than allowing two framework migration histories to modify the same tables independently.",
        "target": "Rails Active Record over the selected PostgreSQL schema, with reviewed database constraints and migrations. Recommendation: remove legacy fields only after old code and rollback readers have retired.",
        "benefits": [
          {
            "benefit": "Recommendation: conventional migrations and model access can concentrate schema work in one maintained application.",
            "prerequisites": "A named schema owner, compatible value semantics and staged writer transfer.",
            "tradeoffs": "Shared tables constrain both deployments; deleting or rewriting data may require reconciliation rather than migration reversal."
          }
        ],
        "checks": [
          "Create in the old application and read in Rails, then reverse the direction after writer transfer; compare IDs, timestamps and nullable values.",
          "Run the proposed migration against a disposable copy; verify both deployed versions can still read and update a fixture, including unique and foreign-key failures."
        ],
        "rollback": "Recommendation: route writes back only while the old reader understands new records. Preserve additive schema changes; rehearse reversible migrations separately and reconcile irreversible data changes.",
        "sources": [
          "https://guides.rubyonrails.org/active_record_migrations.html",
          "https://guides.rubyonrails.org/getting_started.html"
        ],
        "id": "data-1"
      },
      {
        "capability": "jobs",
        "existing_contract": "Record adapter, job class, argument serializers/GlobalIDs, queue names, retries, scheduled work, failed work and running leases. Rails exposes these through Active Job while each backend supplies execution behavior.",
        "coexistence": "Recommendation: retain old consumers for old payloads; route a new job category to a new queue/backend. Transfer its producers and recurring-task owner together. Drain or deliberately transform ready, scheduled, retry, failed and in-flight work; do not run both schedulers for the same business operation.",
        "target": "Active Job with configured Solid Queue or the retained qualified adapter. Same-database enqueue may join a domain transaction; a separate queue database needs an explicit delivery design. Recommendation: require business idempotency even when job uniqueness exists.",
        "benefits": [
          {
            "benefit": "SQL-backed job persistence may allow retirement of a dedicated broker.",
            "prerequisites": "All broker uses inventoried and retired; workers, queue schema, failure monitoring and backlog reconciliation in place.",
            "tradeoffs": "Database work and queue operations remain operational responsibilities; retaining Redis for another capability yields no service-count reduction."
          }
        ],
        "checks": [
          "Replay captured old serializer fixtures using the retained consumer; a deleted GlobalID target must follow the intended failure policy.",
          "Rollback a domain transaction and confirm no executable job escapes it; kill after an external effect before acknowledgement and verify a retry cannot repeat the business effect.",
          "Account for every backlog state and observe only one scheduled enqueue for the transferred category."
        ],
        "rollback": "Recommendation: stop new producers before restoring old routing; retain both consumers through the rollback window and reconcile completed external effects before re-enqueueing.",
        "sources": [
          "https://guides.rubyonrails.org/active_job_basics.html",
          "https://github.com/sidekiq/sidekiq/wiki/Best-Practices"
        ],
        "id": "jobs-1"
      },
      {
        "capability": "identity",
        "existing_contract": "Distinguish the application's generated Session model from Rails session storage. Capture cookie name, serializer, signing/encryption configuration, expiration, account IDs, logout/revocation and per-resource authorization.",
        "coexistence": "Recommendation: keep a shared identity authority or a documented session exchange endpoint. If cookies are shared, qualify the complete cookie format and rotation rules; route stable users and retain the old decoder for the agreed window.",
        "target": "An intentionally selected Rails authentication implementation with application authorization. CookieStore stores session data on the client; account/session lifecycle rules must be designed separately.",
        "benefits": [
          {
            "benefit": "Recommendation: coherent account and session ownership can preserve login continuity during feature migration.",
            "prerequisites": "Verified identity mapping, cookie/exchange fixtures and shared revocation behavior.",
            "tradeoffs": "Compatibility code and key rotation increase maintenance; planned reauthentication is an alternative when continuity is unnecessary."
          }
        ],
        "checks": [
          "Authenticate through the old path and visit the new route; then logout, expire or revoke the session and confirm both paths deny access.",
          "Replay a modified cookie, a stale cookie and a valid session from another tenant against a protected resource; each unauthorized operation must be rejected."
        ],
        "rollback": "Recommendation: preserve old account IDs and accepted session formats until routing stabilizes; coordinate revocation and key rotation so restoring traffic cannot revive invalidated sessions.",
        "sources": [
          "https://guides.rubyonrails.org/security.html",
          "https://guides.rubyonrails.org/getting_started.html"
        ],
        "id": "identity-1"
      },
      {
        "capability": "realtime",
        "existing_contract": "Capture channel names, connection authentication, subscription commands, DOM target IDs, Turbo stream actions and reconnect behavior. Turbo Streams can arrive over WebSockets, SSE or request responses; Action Cable owns its own client/server protocol.",
        "coexistence": "Recommendation: keep the current realtime endpoint for old clients; bridge versioned domain events to the selected target renderer. Assign one event publisher per committed change and route a room/user cohort including reconnects, rather than randomly splitting individual socket requests.",
        "target": "Turbo streams over the selected transport, with Action Cable using a configured shared adapter when multiple server processes must communicate. Solid Cable is database-backed; Redis remains a documented alternative.",
        "benefits": [
          {
            "benefit": "HTML updates can reuse server-rendered partials.",
            "prerequisites": "Stable DOM targets, authorized subscriptions and compatible event-to-view rendering.",
            "tradeoffs": "A new socket protocol still needs client changes or a bridge; reconnection recovery is an application acceptance requirement."
          }
        ],
        "checks": [
          "Place old and new clients on different server processes; commit one change and verify both authorized clients display it once.",
          "Disconnect during a write, reconnect and verify the UI converges to the persisted state; unauthorized origins and subscriptions must fail."
        ],
        "rollback": "Recommendation: preserve the old event renderer/endpoint and backward-readable stream payloads until the cohort can reconnect there; reconcile already-delivered state rather than blindly rebroadcasting.",
        "sources": [
          "https://guides.rubyonrails.org/action_cable_overview.html",
          "https://turbo.hotwired.dev/handbook/streams"
        ],
        "id": "realtime-1"
      },
      {
        "capability": "cache",
        "existing_contract": "List reconstructible values separately from sessions, counters and locks. Capture key namespaces, serialized values, TTLs and invalidation triggers; Rails.cache and request-local SQL query caching have different lifetimes.",
        "coexistence": "Recommendation: use a new cache namespace when representations differ, read through to the authoritative data and maintain invalidation for both deployed readers. Keep shared coordination uses on their qualified store while moving only disposable values.",
        "target": "A deliberately selected Rails cache store; Solid Cache is database-backed and new Rails 8 applications include it. MemoryStore and RedisCacheStore remain choices with different process/service boundaries.",
        "benefits": [
          {
            "benefit": "Database-backed caching can remove a dedicated cache service when it is the last use.",
            "prerequisites": "Reconstructible contents, tested expiration/invalidation, adequate database capacity and retirement of all other service uses.",
            "tradeoffs": "Cache traffic moves to the database; cache durability does not make it the authoritative domain store."
          }
        ],
        "checks": [
          "Warm both versions, update the source record and verify neither version serves the prior value after its specified invalidation boundary.",
          "Restart the application or empty only the dedicated cache namespace; requests must rebuild correctly without losing identity or coordination state."
        ],
        "rollback": "Recommendation: switch to the previous namespace/store and repopulate from domain data; retain the old backend while any coordination capability still depends on it.",
        "sources": [
          "https://guides.rubyonrails.org/caching_with_rails.html"
        ],
        "id": "cache-1"
      },
      {
        "capability": "operations",
        "existing_contract": "Record deployed web and Cable processes, configured workers/dispatchers, recurring.yml tasks and database connections. A web deployment alone does not establish that workers or broadcasts are running.",
        "coexistence": "Recommendation: give each process a named operational owner; keep existing backups and queue monitoring while deploying the new cohort. Transfer recurring schedule ownership once, after checking overlapping old and new schedules.",
        "target": "An explicit Rails process inventory with worker lifecycle, queue failure inspection and the chosen realtime adapter. Recommendation: document separate domain, queue and cache database restore ordering where applicable.",
        "benefits": [
          {
            "benefit": "Recommendation: visible process and schedule ownership makes failures and recovery boundaries reviewable.",
            "prerequisites": "A deployment recipe for the selected adapters and an exercised restore/restart procedure.",
            "tradeoffs": "Coexistence temporarily operates both systems; one healthy HTTP endpoint cannot certify all processes."
          }
        ],
        "checks": [
          "Stop a worker, enqueue a representative task and verify monitoring identifies its growing age; restart and observe the intended completion/failure policy.",
          "Restore a disposable backup and prove domain records, delayed tasks and session behavior are accounted for before promoting traffic."
        ],
        "rollback": "Recommendation: freeze producers and scheduler ownership transfers, restore the previous process configuration, then reconcile work accepted by the new deployment.",
        "sources": [
          "https://guides.rubyonrails.org/active_job_basics.html",
          "https://guides.rubyonrails.org/action_cable_overview.html"
        ],
        "id": "operations-1"
      }
    ],
    "sources": [
      {
        "url": "https://guides.rubyonrails.org/getting_started.html",
        "title": "Getting Started with Rails",
        "claim": "Rails 8.1 examples, MVC/Active Record, model and authentication generators, migration and console commands."
      },
      {
        "url": "https://guides.rubyonrails.org/active_record_migrations.html",
        "title": "Active Record Migrations",
        "claim": "Schema migration files, database constraints, reversal and rollback semantics."
      },
      {
        "url": "https://guides.rubyonrails.org/active_job_basics.html",
        "title": "Active Job Basics",
        "claim": "Active Job serialization, Solid Queue default and process roles, bin/jobs, separate database default, enqueue timing, recurring tasks and retry handling."
      },
      {
        "url": "https://guides.rubyonrails.org/security.html",
        "title": "Securing Rails Applications",
        "claim": "CookieStore, signing/encryption rotation, replay/fixation and session expiration considerations."
      },
      {
        "url": "https://guides.rubyonrails.org/action_cable_overview.html",
        "title": "Action Cable Overview",
        "claim": "Cable connections/channels, adapter configuration, async locality, Solid Cable, origins and server deployment."
      },
      {
        "url": "https://guides.rubyonrails.org/caching_with_rails.html",
        "title": "Caching with Rails",
        "claim": "Rails cache interfaces, SQL cache lifetime, Solid Cache and alternative stores."
      },
      {
        "url": "https://turbo.hotwired.dev/handbook/introduction",
        "title": "Turbo introduction",
        "claim": "Drive, Frames and Streams extend server-rendered HTML navigation and updates."
      },
      {
        "url": "https://turbo.hotwired.dev/handbook/streams",
        "title": "Turbo Streams",
        "claim": "DOM target operations and stream transport options."
      },
      {
        "url": "https://github.com/sidekiq/sidekiq/wiki/Best-Practices",
        "title": "Sidekiq Best Practices",
        "claim": "Jobs must be idempotent; execution is at least once rather than exactly once."
      }
    ],
    "reviewed_at": "2026-10-11",
    "evidence": "documentation-reviewed"
  },
  "rewrite_url": "/rewrite/guide.md",
  "migration_contracts_url": "/rewrite/contracts.json",
  "agent_workflow": [
    "Read README, dependency manifests, lockfiles, runtime pins and the maintained project workflow before changing anything. Preserve an existing stack unless reconsidering it is part of the request.",
    "Use this guide to select tools by requirement. Resolve compatible versions from official and installed-version documentation; record the actual lockfile and runtime versions.",
    "Create a plan for schema changes, identity, authorization, external effects and deployment. A library choice does not complete its integration.",
    "Run relevant checks after configuration. Exercise the real interface, failure paths and packaged artifact on the intended target. Report commands, versions, results and untested behavior.",
    "On failure, retain the command and diagnostic, fix the failing prerequisite and resume there. Do not regenerate over existing work or suppress failures to make a check green."
  ],
  "selection": {
    "native_ui": false,
    "static_hosting": false,
    "offline_client_candidate": false
  },
  "coverage_note": "Tools are curated starting points for this application role. Exact versions, integrations and offline behavior require project-specific qualification."
}
