Fix migration adding the cycle end date to memberships #560

Merged
simon merged 3 commits from issue/mitgliederverwaltung-559 into main 2026-07-31 10:23:39 +02:00
Owner

closes #559

Description of the implemented changes

The changes were:

  • Bugfixing
  • New Feature
  • Breaking Change
  • Refactoring

Upgrading an existing installation aborted during migrations: the migration that adds the cycle_end column to membership fee cycles failed with an error naming a fee interval (for example "yearly"), so the container never started. The interval is now resolved without depending on other parts of the application already being loaded, and an interval the migration cannot interpret stops it explicitly instead of storing a guessed cycle end.

What has been changed?

  • Resolve fee intervals without a loaded module
  • Require release-safe application code in migrations
  • Note the fixed membership fee cycle upgrade migration

Definition of Done

Code Quality

  • No new technical depths
  • Linting passed
  • Documentation is added were needed

Accessibility

  • New elements are properly defined with html-tags
  • Colour contrast follows WCAG criteria
  • Aria labels are added when needed
  • Everything is accessible by keyboard
  • Tab-Order is comprehensible
  • All interactive elements have a visible focus

Testing

  • Tests for new code are written
  • All tests pass
  • axe-core dev tools show no critical or major issues

Additional Notes

  • F1 is an oversized commit on purpose: 4 files, ~339 insertions. It carries the one-function fix plus its whole test surface (example tests, a property test and a new test-support module), because two source commits span both test files and a rebase todo cannot split a commit.
  • The regression is pinned structurally, by asserting that the compiled backfill module carries the four interval atoms in its own atom table. A behavioural test cannot catch this class: atoms are VM-global and the test suite itself contains the interval literals, so a runtime conversion succeeds under ExUnit no matter which modules are loaded.
  • Verified end to end against a real production release, not only in tests: in a bare release VM :erlang.binary_to_existing_atom("yearly", :utf8) raises, the old code path reproduces the reported error against real data, and bin/migrate on a database with existing cycles now completes with correct values including the leap-year case (2024-02-01 → 2024-02-29), no NULL rows, and cycle_end back under NOT NULL.
  • Not covered, deliberately: no CI stage exercises a release migrate against a database with existing rows, so this failure class stays invisible to CI. Worth its own issue.
  • The claim that fresh installations were unaffected is derived from the code (without existing rows no interval conversion happens) and is pinned by a test, but it was not confirmed on a running instance.
closes #559 ## Description of the implemented changes The changes were: - [x] Bugfixing - [ ] New Feature - [ ] Breaking Change - [ ] Refactoring Upgrading an existing installation aborted during migrations: the migration that adds the `cycle_end` column to membership fee cycles failed with an error naming a fee interval (for example `"yearly"`), so the container never started. The interval is now resolved without depending on other parts of the application already being loaded, and an interval the migration cannot interpret stops it explicitly instead of storing a guessed cycle end. ## What has been changed? - Resolve fee intervals without a loaded module - Require release-safe application code in migrations - Note the fixed membership fee cycle upgrade migration ## Definition of Done ### Code Quality - [ ] No new technical depths - [x] Linting passed - [x] Documentation is added were needed ### Accessibility - [ ] New elements are properly defined with html-tags - [ ] Colour contrast follows WCAG criteria - [ ] Aria labels are added when needed - [ ] Everything is accessible by keyboard - [ ] Tab-Order is comprehensible - [ ] All interactive elements have a visible focus ### Testing - [x] Tests for new code are written - [x] All tests pass - [ ] axe-core dev tools show no critical or major issues ## Additional Notes - F1 is an oversized commit on purpose: 4 files, ~339 insertions. It carries the one-function fix plus its whole test surface (example tests, a property test and a new test-support module), because two source commits span both test files and a rebase todo cannot split a commit. - The regression is pinned structurally, by asserting that the compiled backfill module carries the four interval atoms in its own atom table. A behavioural test cannot catch this class: atoms are VM-global and the test suite itself contains the interval literals, so a runtime conversion succeeds under ExUnit no matter which modules are loaded. - Verified end to end against a real production release, not only in tests: in a bare release VM `:erlang.binary_to_existing_atom("yearly", :utf8)` raises, the old code path reproduces the reported error against real data, and `bin/migrate` on a database with existing cycles now completes with correct values including the leap-year case (2024-02-01 → 2024-02-29), no NULL rows, and `cycle_end` back under `NOT NULL`. - Not covered, deliberately: no CI stage exercises a release migrate against a database with existing rows, so this failure class stays invisible to CI. Worth its own issue. - The claim that fresh installations were unaffected is derived from the code (without existing rows no interval conversion happens) and is pinned by a test, but it was not confirmed on a running instance.
Atoms only come into existence when a module is loaded. The production
migrate path loads the application without starting it, so no module
carrying the fee-interval atoms was loaded when the backfill converted the
interval column, and a perfectly valid value raised ArgumentError — the
container never started.

The mapping now lives in the backfill itself, which the migration calls, so
loading it is enough. An interval the mapping does not know aborts instead
of storing a guessed cycle_end behind a NOT NULL column, where it would no
longer be detectable.
docs(changelog): note the fixed membership fee cycle upgrade migration
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
9e93f02fb0
simon merged commit 07ffeacded into main 2026-07-31 10:23:39 +02:00
simon deleted branch issue/mitgliederverwaltung-559 2026-07-31 10:23:40 +02:00
Sign in to join this conversation.
No description provided.