rollback ran a migration's down section when it had one and then deleted
its tracking row either way. A migration that changed the schema without a
`-- +down` section was therefore recorded as rolled back while its change
stayed in the database, and the next migrate replayed statements such as
ALTER TABLE ADD COLUMN that cannot run twice, leaving the database stuck
between the two states with no way forward or back.
It now refuses that case with WRN-DB-MIGRATION-IRREVERSIBLE and leaves the
tracking row alone, so the database stays consistent and the operator
chooses: add a down section, or write a new migration that reverses the
change. The check runs before the lock is taken, so a dry run reports the
same refusal rather than promising a rollback that would fail.
A migration whose up is empty still rolls back by dropping its tracking
row, since there is nothing to undo. That keeps placeholder migrations
working, and `wrnexus db rollback` prints the refusal as a message rather
than a stack trace.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The generator embeds each query's SQL as a string literal, taking whatever line
endings the checkout happened to have. On a CRLF checkout every regenerated
query differed from the committed one by `\n` -> `\r\n`, so `wrnexus build`
dirtied the working tree and that churn buried real changes in the same file --
which is how a hand-applied edit ends up preferable to running the generator.
Line endings carry no meaning in SQL, so normalise on parse and let generated
output be stable across platforms.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The same generate command emitted ? one run and $1 the next, which looked
like non-determinism. It is not: postgres uses $1 placeholders where
sqlite and mysql use ?, and the driver comes from the active profile, so
building under a different profile rewrites this committed file.
The header now records the dialect it was generated for, making the flip
visible in the diff and explaining check:generated-types failures instead
of leaving them looking like random churn.
Worth deciding separately: a committed artifact whose contents depend on
the active profile will keep drifting. Either generate per dialect, or
stop committing it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>