Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1092,10 +1092,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
the `delete_many` bulk path included — with no repository attribute required.
This model form ships for `destroy`, `delete_all`, `nullify`, and `restrict`
(grandchild recursion and the `delete_many` bulk path included), driving the
same runtime cascade as the repository attribute; only both-sites conflict
diagnostics are a deferred follow-up — when both a repository-side
`dependent(...)` and a model-side `dependent`/`on_delete` are declared the
repository attribute silently wins — and a `dependent`/`on_delete` on a
same runtime cascade as the repository attribute; when both a repository
`dependent(...)` and a model-side `#[has_many(..., dependent = ...)]` are
declared for the same delete the repository attribute still wins, but a
debug-only `tracing::warn!` now surfaces the silently-inert model-side
declaration (issue #1788), and a `dependent`/`on_delete` on a
`through = <join_table>` association is a directed compile error rather than a
mis-targeted cascade (issue #1738). See `docs/guide/repositories.md`.
- **audit:** version/audit writes are auto-attributed to the current actor. A new
Expand Down Expand Up @@ -1381,6 +1382,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
doctor` gained an `alert_transports` check that (in production) flags a
whitespace-mangled routing key, a non-absolute `pagerduty_url`, or a
non-absolute-`https` Slack/Discord URL (issue #1630).
- **server:** config-driven in-process TLS termination (part of #1603). A new `[server.tls]` section (`cert_path`, `key_path`, optional `reload_interval_secs` (default 60) and `handshake_timeout_secs` (default 10)) makes the app terminate HTTPS itself on the same host:port instead of needing a sidecar proxy. Off by default and gated behind the off-by-default `tls` cargo feature; startup fails fast on a missing/unreadable file, unparseable or empty PEM, a key that does not match the leaf, or an expired/not-yet-valid leaf or intermediate. Certificates hot-reload by polling the cert/key mtimes, so an ACME/`certbot` renewal is picked up without a restart. `autumn doctor` gains a `tls` check that fails on a missing/invalid/expired certificate and warns when the leaf expires within 30 days.
- **server:** automatic ACME (Let's Encrypt) certificate provisioning and renewal (part of #1608). With the off-by-default `acme` cargo feature, a `[server.tls.acme]` section (`domains`, `contact_email`, optional `directory` [Let's Encrypt staging by default], `cache_dir` [default `config/acme`], `http_challenge_port` [default 80], `renew_before_days` [default 30]) obtains and auto-renews the server's TLS certificate over the HTTP-01 challenge — no static cert on disk and no reverse proxy. Static `cert_path`/`key_path` and ACME are mutually exclusive (exactly one must be configured); the issued cert hot-swaps into the same reloadable resolver the `[server.tls]` listener serves, a `:80` listener answers the challenge and redirects HTTP to HTTPS, and a coordinator-leader-elected loop renews hourly before expiry. Single-host slice for now; wildcards/DNS-01 are out of scope (#1620).
- **security:** one-time submit tokens give forms a server-side, at-most-once guarantee with no client JavaScript (issue #1360). A per-render random token is exposed via the `SubmitToken` extractor (plus `SubmitFormField`) and embedded as a hidden `_submit_token` field; on a mutating POST `SubmitTokenLayer` consumes it against the shared idempotency store — first use runs and caches the handler's 2xx/3xx response, a replayed token short-circuits and returns that first response, and a concurrent duplicate that loses the in-flight lock race gets a `409`. If the response stream errors after the handler already committed a 2xx/3xx, the in-flight lock is held (surfacing a `500`, not recording the token) so a retry still gets a `409`. Enabled by default under `[security.submit_token]` and applied inner to the CSRF layer; the consumed-token store inherits `[idempotency].backend`, where an inherited in-memory backend in production only warns while an explicit `backend = "memory"` in production fails fast.
- **security:** build-time route auth-coverage manifest (part of #1604). Every route now carries an exposure classification — `gated`, `public`, `framework`, or `unclassified` — and a new `autumn routes audit` subcommand lists them and emits a stable-ordered (by path then method) JSON security manifest. It is a CI gate: it exits non-zero when any route is `unclassified` (or omitted from the dump), naming each offender. A new `#[public]` marker attribute mirrors `#[secured]` to declare a handler deliberately unauthenticated.
- **web:** new `autumn_web::a11y` module of typed accessible UI primitives that make the accessible name a compile-time obligation (part of #1706). `Img::new(src, alt)` requires alt text (decorative images opt in via `Img::decorative`); `Button::new(name)`/`Button::icon`, `Link::new(href, text)`/`Link::icon`, and `MenuItem::new(name)` all require an accessible name with no nameless constructor (icon-only forms route the name to `aria-label`); `Link::new_tab()` adds `target="_blank"` with `rel="noopener noreferrer"`. `TextField::new` returns a `TextField<NoLabel>` typestate that does not implement `Render` until `.label()`/`.aria_label()`/`.labelled_by()` transitions it to `TextField<Labeled>`, so an unlabeled field is unrepresentable. Inaccessible forms fail to compile (proven by trybuild fixtures); all primitives are re-exported from the prelude under the `maud` feature.
- **cli:** new `autumn a11y verify [PATH] --format <text|json> --strict` command that statically scans project `.rs` files for raw `maud::html!` markup bypassing the typed `a11y` primitives, emitting WCAG-keyed findings (image-alt 1.1.1; label 1.3.1/3.3.2/4.1.2; button-name 4.1.2; link-name 2.4.4/4.1.2) and exiting non-zero to fail CI (part of #1706). The scan is advisory and best-effort — a token-level heuristic over `html!` bodies, not a parser: it skips anything it cannot statically resolve, so the typed primitives remain the compile-time proof of accessibility and this pass only covers the raw-`html!` escape hatch.
- **web:** allowlisted sort and filter for list views (closes #1126). A new `Infallible` `ListQuery` extractor (with `SortDir`) parses `?sort=<col>`, `?dir=asc|desc`, and `?filter[<col>]=<val>` — it never rejects a request, falling back to the model's default order for an empty/unknown `sort` and to ascending for an invalid `dir`. `#[model]` emits typed per-column allowlist helpers and `#[repository]` emits a tenant- and soft-delete/shard-aware `list()` that routes requested keys through Diesel's typed DSL via `.into_boxed()`, so only real columns reach SQL and an attacker-supplied `?sort=id;DROP TABLE users` falls through to the default ordering. Scaffolded non-live, non-owner-scoped index views wire their `data_table` to this automatically.
- **generate:** `autumn generate scaffold` now emits a default-deny record-level `Policy`/`Scope` for every non-`--api` resource (opt out with `--no-policy`); when an owner column is detected (`user_id` → `author_id` → `owner_id` → first `*_id` referencing `users`) it also authorizes the create/edit/update/delete handlers and scopes the index to the current user's rows. A new standalone `autumn generate policy <Model>` command scaffolds `src/policies/<snake>.rs` and wires `.policy(…)`/`.scope(…)` into `main.rs` for an existing model (issue #1125).
- **generate:** `autumn generate scaffold --searchable title,body` makes the named text fields full-text searchable — it adds `#[searchable]` attributes to the model, `searchable` to the `#[repository]`, a migration adding a `search_vector tsvector GENERATED ALWAYS AS (…) STORED` column plus a GIN index, and a search box on the index wired to `GET /<plural>/search` (an empty `?q` falls back to the plain paginated list). Omitting `--searchable` leaves scaffold output byte-identical; the `--live`/`--live-validation` index variants gate the search box off; naming a non-text/unknown field, a uuid-PK model, or an owner-scoped model fails generation before any files are written (issue #1319).
- **generate:** `autumn generate scaffold post avatar:Attachment` now produces working no-JS file uploads end-to-end — the create/update handlers take an `autumn_web::extract::Multipart` extractor, stream each uploaded file to the `BlobStore` via `field.save_to_blob_store(&*store, key)`, and bind the returned `Blob` from a plain `multipart/form-data` submit. `autumn destroy` also no longer strips the `multipart`/`storage` features when a hand-written route still uses them (part of #1236).
- **cli:** `autumn new <name> --api` scaffolds a JSON-first project — handlers return `Json<…>`, `autumn-web` is pinned `default-features = false` to a lean API feature set (`db`, `cache-moka`, `http-client`, `reporting`, `flash`) that drops the maud/htmx/tailwind view stack, and no `static/` tree, `input.css`, `tailwind.config.js`, vendored assets, or Tailwind CI/README notes are generated (the first `cargo run` serves JSON). `--api` cannot be combined with `--daemon` or `--bundled-pg` but composes with `--with-i18n` and `--with-seed`.

### Fixed

Expand Down Expand Up @@ -1562,6 +1574,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
child of a `<div>` — a bare `<tr>`/`<td>`/`<tbody>`/`<option>`/`<col>` is
foster-parented out and vanishes; use the element-replacing path with the
id on the row for table-row swaps (issue #1688).
- **web:** static-first (SSG/ISR) responses now derive their `Content-Type` from the route's file extension instead of hard-coding `text/html`, so the outer compression layer gzips/brotli-encodes compressible pages (with `Vary`) while binary manifest assets keep their real MIME and are left uncompressed; WOFF/WOFF2 fonts are excluded from compression (fixes #752).
- **repository:** hardened `destroy`-cascade codegen so no `before_delete` hook fires until every reachable `restrict` probe has passed — a grandchild `restrict` pre-scan now runs read-only over all selected child ids before the mutating loop, and the parent `restrict` probe precedes the parent `before_delete` across both the model-declared `has_many` and repository-attribute branches. A mixed soft/hard-delete diamond no longer leaves a dangling FK: a new physically-deleted set (distinct from the all-handled set) keys the revisit-skip, so a soft-deleted row no longer suppresses a later hard-delete of the same row (follow-ups to #1789; issue #1800).
- **generate:** `autumn generate auth User --passkeys` again passes `cargo check` against webauthn-rs 0.5.5 (which dropped `CredentialID`'s `Display`/`ToString`) — the generated code encodes credential IDs via a shared `encode_cred_id` helper (base64url, no padding), adds a `base64 = "0.22"` dependency, uses concrete `axum::response::Redirect` return types, reorders the `WebauthnCredential` fields, and warns when a project pins an older `base64` (issue #1822).
- **generate:** `autumn destroy scaffold` on a non-live scaffold no longer strands files when the project's shared `pub fn layout` was lost or renamed — the generate-only shared-layout preflight is now skipped on the revert path, so destroy recomputes its plan and removes the generated files cleanly instead of hard-failing before deleting anything (fixes #1834).
- **cli:** scaffolded container images now report real git provenance on `/actuator/info` instead of null `git.commit`/`git.branch`/`git.dirty` — the generated `build.rs` prefers `AUTUMN_BUILD_*` env vars (threaded from `docker build --build-arg` through Dockerfile `ARG`/`ENV`) over shelling to git, which failed in-container because `.dockerignore` excludes `/.git`, and treats dirty as three-state so an unknown state is omitted rather than reported as `false` (fixes #1676).

### Changed

Expand Down Expand Up @@ -1648,6 +1665,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
re-mint throttle). Both are unsigned, so a negative value in `autumn.toml`
now fails deserialization rather than silently minting expired tokens or
defeating the email-bomb throttle (issue #1737).
- **generate:** `autumn generate auth`'s browser auth pages now render through the shared 4-arg `crate::layout(title, current_path, flash, content)` — the same helper `autumn new`/scaffold emit — instead of a private bare-DOCTYPE stub; the generator preflights for a shared `pub fn layout` in `src/main.rs` and fails with an actionable message pointing at `autumn new` when it is missing (issue #1353).
- **generate:** scaffolded resources now route every URL through a generated `autumn_web::paths![index, show, new, create, edit, update, delete]` module (plus `events` under `--live` and one `validate_{field}` per validated field under `--live-validation`) instead of hand-written path strings — all view hrefs, form actions, redirects, the SSE `sse-connect`, and inline-validation `hx-post` attributes call `paths::*`, giving each resource's URLs a single source of truth (issue #1133).
- **reliability:** the request-path module set (~25 modules including `form`, `extract`, `idempotency`, session/scheduler/storage/sync, and the middleware stack) is now gated by clippy restriction lints that deny `unwrap`/`expect`/`panic`/`unreachable`/`todo`/`unimplemented`/`indexing_slicing` on non-test builds, and poison-prone locks recover via `unwrap_or_else(PoisonError::into_inner)` rather than panicking — so a poisoned lock or stray unwrap can no longer panic-abort a worker on the request path. A `scripts/check-panic-gate.sh` manifest, run in CI's lint job, keeps the gate from being silently dropped (part of #1611).

### Fixed

Expand Down
18 changes: 14 additions & 4 deletions skills/autumn-web/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ when their details matter:
- `references/examples.md` - official 0.5.0 example patterns for minimal apps,
CRUD, production-ish jobs, Redis channels, S3 storage plugins, and signed
webhooks. Use this before generating full app code.
- `docs/guide/accessibility.md` - accessible-by-construction UI. Prefer the
typed `autumn_web::a11y` primitives (`Img` / `Button` / `Link` / `MenuItem` /
`TextField`) over raw `<img>` / `<button>` / `<input>` in `html!` — the accessible
name (alt text, button label, field label) is a required constructor argument,
so a missing one is a compile error, not a runtime audit miss. Treat `autumn
a11y verify` as an advisory/best-effort CI net, not a guarantee — the typed
primitives are the compile-time proof (trunk-dev, #1706).

## Prefer framework idioms over raw Diesel/Axum

Expand Down Expand Up @@ -395,10 +402,13 @@ the model instead of the repository **(unreleased — trunk-dev, issues #1738,
a generated `Model::dependents()`) when no repository-side `dependent(...)` is
present; the repository attribute wins when both are declared. Ships for
`destroy`, `delete_all`, `nullify`, and `restrict`, grandchild recursion and
the `delete_many` bulk path included; only both-sites conflict diagnostics are
a deferred follow-up (when both sites declare, the repository attribute
silently wins), and `dependent`/`on_delete` on a `through = <join_table>`
association is a compile error (issue #1738).
the `delete_many` bulk path included. When both a repository `dependent(...)`
and a model-side `#[has_many(..., dependent=...)]` are declared the repository
attribute still wins, but a debug-only `tracing::warn!` (emitted in
`debug_assertions` builds) now surfaces the silently-inert model-side
declaration instead of dropping it without a trace (issue #1788).
`dependent`/`on_delete` on a `through = <join_table>` association is a compile
error (issue #1738).

See `docs/guide/repositories.md`.

Expand Down
Loading
Loading