# Using Rust off Rails, day to day

> You say what the app should do. The agent writes all the code, and the compiler and the test suite decide when it's done. Framework version 0.7.3. HTML version: https://rustoffrails.org/guide

## 1. You write intent. The agent writes all the code.

That's the founding rule, doctrine §0 ([philosophy](https://rustoffrails.org/philosophy.md)). Every line of Rust, SQL, HTML, CSS, TypeScript, every migration, test and Dockerfile is written by the agent.

**You:** specs in `product/`, in plain English; answers in the interview; feedback in conversation; decisions only you can make (who can see what, what happens on delete, what it costs).

**The agent:** every line of code, config and scripts; the spec first, then the code; tests for every acceptance criterion, on both databases; a trail in `docs/decisions.md`.

### The files you touch

- `product/README.md`: what the app is, who uses it, the rules that must always hold. The interview's answers land in its Decisions section.
- `product/features/*.md`: one file per feature. The agent fills the gaps, gives each acceptance criterion a stable ID, writes the threat model, and marks what it assumed.
- `AGENTS.md` (the header): the app's modes and choices, set by the interview.

```md
# Feature: Reviews

## Why
Readers want to know what others thought of a book.

## Who
Any logged-in user can review a book once. Only the author edits or deletes it.

## Behavior
- A review has 1–5 stars and a body (max 2,000 characters).
- The book's owner gets an email for each new review.
- Deleting a book deletes its reviews.

## Acceptance criteria
- [ ] AC-reviews-1: A logged-in user can review a book with 1–5 stars and a body
- [ ] AC-reviews-2: A second review of the same book shows "You already reviewed this book"
- [ ] AC-reviews-3: The book's owner gets an email when a review is posted

## Security (agent-written threat model)
| STRIDE    | Threat                              | Control             | Test                              |
|-----------|-------------------------------------|---------------------|-----------------------------------|
| Spoofing  | An anonymous visitor posts a review | RequireUser         | anonymous_cannot_create_a_review  |
| Tampering | A user edits someone else's review  | policy: author only | other_user_gets_404_on_update     |
```

```text
APP:            bookshelf
FRONTEND_MODE:  htmx
UI_LIBRARY:     none
ISLANDS:        no
API:            none
PROD_DATABASE:  postgres
DEPLOY_TARGET:  later
ACCOUNTS:       magic-link  ROLES: none  TENANCY: per-user
```

## 2. The loop

Every change goes through the same loop, one vertical slice at a time:

1. **Ask**: only what a human can answer, each with a recommended default.
2. **Spec**: write or update `product/features/*.md`. The spec is the record; chat is not.
3. **Migrate**: migration and entity together, rollback proven.
4. **Model**: params, validation, queries: typed, never strings.
5. **Failing tests**: one per acceptance criterion, citing its ID.
6. **Build**: controllers, routes, views, jobs, mailers.
7. **Check**: `cargo check` until the compiler agrees.
8. **Verify in a browser**: run the app and click through the change.
9. **Record**: tick the spec, update routes and decisions.

### The Definition of Done

- Format and lint: `cargo fmt`, `cargo clippy` with warnings as errors.
- Both databases: the whole suite on SQLite and Postgres.
- Traceability: every `AC-…` ID in `product/` is cited by a test.
- Coverage: at least 80% of lines, 90% for models and policies.
- Security gates: dependency audit and a secret scan; a threat model and attack tests for security-relevant changes.
- Browser smoke: UI changes exercised in a real browser.

## 3. Skills

Nine slash commands in Claude Code. They also trigger on plain requests.

- `/offrails:new`: you start a new app in an empty directory.

  ```claude
  /offrails:new bookshelf: people track books they've read
  ```

- `/offrails:feature`: you want something added or changed, or you edited `product/` (no argument: build what changed).

  ```claude
  /offrails:feature let people review books; email the owner
  ```

- `/offrails:scaffold`: a full resource (migration, model, policy, controller, routes, views, tests).

  ```claude
  /offrails:scaffold Book title:string author:string published_on:date? rating:integer owner:references
  ```

- `/offrails:generate`: one piece: a model, resource, migration, job, mailer, channel, authentication or task.

  ```claude
  /offrails:generate job weekly_digest
  ```

- `/offrails:check`: run the Definition of Done and fix what fails. `--deep` adds mutation testing.

  ```claude
  /offrails:check --deep
  ```

- `/offrails:doctor`: audit against every convention, especially after hand edits. `--fix` repairs what it finds.

  ```claude
  /offrails:doctor --fix
  ```

- `/offrails:security`: before shipping, or after auth, payments, uploads, realtime or API changes.

  ```claude
  /offrails:security uploads
  ```

- `/offrails:deploy`: Fly.io, Kamal, Railway or Render; confirms before anything outward-facing.

  ```claude
  /offrails:deploy fly
  ```

- `/offrails:upgrade`: bring the app's `.offrails/` up to date and migrate the conventions.

  ```claude
  /offrails:upgrade
  ```

## 4. Guardrails

The plugin's hooks act only inside an app with a `.offrails/` directory.

- **Blocked before an edit:** `.offrails/` is read-only; `web/src/types/` is generated by ts-rs; committed migrations are append-only.
- **Flagged after an edit:** raw SQL outside `src/kernel/`; a raw `.begin()` transaction (use `kernel::db::begin`); banned crates and JS packages; `/:id` routes (axum 0.8 uses `/{id}`); `sleep` in tests.
- **Security tripwires:** Askama `|safe`, inline `<script>` and `on*` handlers, `dangerouslySetInnerHTML`, Svelte `{@html}`, `unsafe-inline`/`unsafe-eval` in a CSP, permissive CORS, SQL built with `format!`, cookies with Secure or HttpOnly off. Each needs a logged decision and your explicit OK.
- **Around the session:** no `pkill` or `killall`; the stop gate (the agent can't end its turn while `cargo fmt --check` or `cargo check` fails); drift detection at session start.

## 5. Choosing modes

- `FRONTEND_MODE`: `htmx` (default; server-rendered, no Node; most apps), `inertia` (React or Svelte pages with server routing; when you want a JS UI), `spa` (a separate single-page app + JSON API; only when the UI must be standalone), `api` (no UI; mobile backends, partners, scripts; in progress).
- `UI_LIBRARY` (only with a JS UI): `react` (default; the largest ecosystem), `svelte` (smaller bundles, less code).
- `API`: `none` (default; only your UI talks to the server), `rest` (REST under `/api/v1`, OpenAPI, bearer tokens), `graphql` (one `/graphql` endpoint), `both`.

## 6. Upgrading apps

Each app vendors a pinned copy of the framework in `.offrails/`, so nothing changes until you upgrade, whichever agent built the app.

1. **Commit, then get the latest release** (installs it into `~/.offrails/current`; or re-run the one-liner):

   ```sh
   offrails update
   ```

2. **Check whether the app is behind** (framework 0.4.0+; compares content fingerprints, not just version numbers):

   ```sh
   cd my-app && offrails upgrade --check
   ```

3. **Upgrade** (framework 0.4.0+). It launches your agent with the recipe: Claude Code runs `/offrails:upgrade`; opencode and Codex get it automatically; any other agent: paste the printed prompt.

   ```sh
   offrails upgrade
   ```

   In Claude Code directly:

   ```claude
   /offrails:upgrade
   ```

   On a release before 0.4.0, after `offrails update`, give your agent this prompt in the app:

   ```prompt
Upgrade this Rust off Rails app to the framework in ~/.offrails/current, following https://github.com/rust-off-rails/rustoffrails/blob/main/generators/upgrade.md step by step. Reconcile, don't re-create; never edit applied migrations. Start from a clean git tree, show me the plan before changing anything, and finish with the Definition of Done on SQLite and Postgres.
   ```

### What the agent does

It follows the framework's [upgrade recipe](https://rustoffrails.org/docs/generators/upgrade.md), the same steps in every agent:

- Reads the [CHANGELOG](https://rustoffrails.org/changelog.md)'s App migration steps from your version up, and diffs the old docs against the new.
- Reconcile, don't re-create: each change is new, present and conforming, present but divergent (reshaped in place, with a new migration that keeps your data), or conflicting with a product decision (kept and recorded).
- Never edits an applied migration.
- Shows you the plan first and waits for your yes.
- Finishes with the Definition of Done on SQLite and Postgres, and logs the upgrade in `docs/decisions.md`.

### Rules of thumb

- Commit first.
- Several releases behind? Upgrade one release at a time.
- Drift is detected by content, not version: a fingerprint of the app's `.offrails/` is compared with the installed framework's.

## FAQ

- **Can I edit the code myself?** Yes. The agent keeps your change working, records its intent in the spec or `docs/decisions.md`, and owns that code again from then on.
- **Which agents work?** Claude Code with the plugin (skills + hooks) is recommended. Codex, Cursor and any agent that reads `AGENTS.md` work from the same Markdown.
- **Can my agent read the docs without installing anything?** Yes: connect it to the hosted MCP server, [https://rustoffrails.org/mcp](https://rustoffrails.org/mcp) (read-only, no account). To build an app you still install the framework.
- **Is it production-ready?** It's young. It is dogfooded on real apps, its guides are verified against current crate versions, and every app carries its own tests on SQLite and Postgres, a security checklist and a Definition of Done. Run `/offrails:security` before shipping anything sensitive.
- **Why Rust?** The compiler is the best reviewer an agent can have: typed queries, compile-checked templates, generated front-end types. What types can't prove, the tests prove on both databases. One binary, one Docker image.