Back to blog
2026-09-28 Thijs Creemers

Release: v1.0.0-rc-4 — closed by default, and prod refuses what only works in dev


A large candidate, and not the one rc-3 promised. Its post said the breaking list was five. It is twenty-five: this release adds twenty. Most come from two passes over the framework before 1.0.0 — one asking what an anonymous caller can reach, one asking what a production boot accepts that only makes sense on a laptop. The answer to both was "too much", and closing it changes behaviour you may rely on. Better you meet that in a candidate than in 1.0.0.

Every break below is under # Breaking in the changelog, with what to do about it. The upgrade list at the end puts them in the order to work through.

Breaking: nothing answers an anonymous caller unless you say so

  • Routes require login unless they carry :public true. A webhook or a public page needs the flag now.

  • The workflow API answers 401 without a session or bearer token. It was open. The auth middleware’s own 401 now has a JSON body.

  • Scaffolded APIs and list pages require a signed-in user. Pass --public-api to open them. An older module keeps its open routes until you regenerate shell/*http.clj or add the guards by hand.

Breaking: a prod boot refuses what only works in dev

  • :wagoe/payment-provider has no default, and :mock boots only in dev and test. The mock accepts any webhook as paid, and wagoe add payments used to write it into prod. If your prod config.edn has {:provider :mock}, remove it and name :stripe or :mollie.

  • Redis with no host fails the boot outside dev and test. It quietly connected to localhost. Set REDIS_HOST for events, cache, jobs and realtime, even when Redis is on the same machine.

  • :wagoe/tenant refuses SQLite, and H2 without :allow-h2? true. Schema-per-tenant needs PostgreSQL; a test profile on H2 adds the flag.

  • bb setup --prod true changes prod only, merging into what is there, and refuses --payment mock and --ai-provider. Run it without --prod for dev.

Breaking: the database holds the rules the code assumed

  • One workflow instance per workflow and entity. A second start returns the first. migrate up refuses a table that already has duplicates and names them: keep one of each, then migrate.

  • Tenant slugs, schema names, memberships and invite tokens are unique. Boot stops, naming the table, if rows already duplicate one.

  • Workflow’s tables ship as a migration, with TIMESTAMP WITH TIME ZONE. Stop every replica and run migrate up before starting rc-4; seed timestamps as #inst.

  • Admin hard-deletes by default, and refuses to delete a parent with has-many rows unless the relation says :on-delete :cascade. Set :soft-delete true where you relied on deleted_at.

  • An unknown or misplaced admin entity-config key stops startup, naming the path. It used to be ignored, which is how a typo’d :hide-fields went unnoticed.

  • A scaffolder date field is a DATE. Use datetime for a timestamp.

Breaking: the rest

  • sortable-th and pagination swap outerHTML, so a table refresh stops nesting a second container inside the first. A handler that returns only the target’s contents passes :hx-swap "innerHTML".

  • The workflow API speaks kebab-case JSON — send workflow-id, entity-type, entity-id; read current-state — and a refused transition answers {"error": {…}} like the scaffolded APIs. Drop checks on success.

  • :wagoe/logging :level sets Logback’s root and wagoe loggers, over logback.xml.

  • register-user throws :validation-error for a password-policy refusal; :violations is unchanged.

  • Scaffolder field specs refuse an unknown modifier, and required together with optional. indexed now writes an index and optional is honoured.

  • bb setup exits 1 on a closed stdin instead of accepting every default — a script passes flags, bb setup --database sqlite — and in an existing project it creates no missing config file.

Fixed: what leaked

  • Password hashes, MFA secrets and session tokens were written to the application log. Nothing to rotate, but purge any logs you retain.

  • Open redirect after login via ?return-to=/\evil.com. Backslashes, control characters and encoded slashes are refused, in admin’s return_to too.

  • An admin entity’s :hide-fields could un-hide password hashes and API keys. Detected secret columns stay out of list, search and edit.

  • A new tenant could take a deleted tenant’s slug, and with it that tenant’s schema and data. A slug is never reused now. Upgrade if you delete tenants.

  • User, MFA, storage and workflow pages and APIs showed exception messages, and /health/ready showed database and cache errors. They log the cause and answer something generic.

Fixed: a scaffolded module works the first time

  • A scaffolded GET returns the children POST created and the workflow instance; the list takes ?include=.

  • bb scaffold field --required gives the generated tests' rows a value instead of failing them.

  • Scaffolded migrations kept their indexes on SQLite and ran on PostgreSQL.

  • A scaffolded API’s 400 names the field, and a reference to a missing row is a 400 on that field rather than a 500.

  • Creating an entity with an enum field no longer answers 500, and repository tests round-trip a row instead of asserting nothing.

  • bb scaffold ai --dry-run writes nothing, and one description names its module the same way every run.

  • migrate up migrates only the modules in :active, and works on a database that never booted.

  • bb create-admin takes one piped password instead of looping on it.

Fixed: the CLI

Every wagoe command answers --help. Config keys that bb setup, bb scaffold integrate and wagoe add append follow the file’s indentation. bb setup changes only what you answer, lists each change first, and no longer tells you to copy .env.example over a .env that holds your JWT_SECRET. The (go) box shows the dev dashboard’s URL at the port it actually bound.

Added: a status that is a workflow

bb scaffold generate --module-name invoice --entity Invoice \
  --field number:string:required --workflow 'status:entered>delivered>paid'

Quote the spec — unquoted, the shell reads each > as a redirect. The status becomes a workflow that API and admin creates both start, moved by POST /invoices/:id/transition. bb db:seed starts a seeded row’s instance in the state the row names.

Around it:

  • bb scaffold entity --belongs-to invoice --min 1 — the invoice API creates its line items in the same request and transaction, and refuses fewer. The admin gets one form for the parent and its first children.

  • bb scaffold subscriber --module-name m --event :admin/entity-created writes an event subscriber, its handler and its test.

  • GET /api/v1/workflow/instances?entity-type=invoice&entity-id=<id> finds an entity’s workflow instances.

  • Workflow guards see the instance, and can load its entity through an :entity-loader.

  • With the admin on, generate and entity write each entity’s admin config, with secret columns hidden.

Version alignment

All 31 artifacts bumped to v1.0.0-rc-4 to maintain lockstep versioning.

Upgrade

Re-run the installer to pick up the latest release:

curl -fsSL https://get.wagoe.org | bash

Coming from rc-3, in this order:

  1. Purge retained application logs if you keep them — they may hold password hashes and session tokens.

  2. Stop every replica and run migrate up before starting rc-4. If it refuses on duplicate workflow instances or tenant rows, it names them; remove the extras and run it again.

  3. Fix prod config: remove {:provider :mock} from payments, set REDIS_HOST, and move tenancy off SQLite.

  4. Mark public routes with :public true, pass --public-api for scaffolded APIs that must stay open, and give workflow API clients a session or token.

  5. Workflow API clients send and read kebab-case keys, and stop checking success.

  6. Admin config: set :soft-delete true or :on-delete :cascade where you relied on the old delete, and fix whatever key startup names.

  7. Scripts that drive bb setup pass flags; scripts that pass an unknown field modifier drop it.

Coming from a beta, work through the rc-1 upgrade list first.