`group_vars/matrix_servers` selects postgres whenever postgres is enabled, which
is the default, so postgres is what essentially every deployment runs. The
scenarios were testing sqlite - a path almost nobody is on.
How little that path is used is not a guess: the mautrix-meta bridges could not
start at all under sqlite, and nobody reported it. Testing the engine users are
actually on is worth more than keeping coverage of the one they are not, so no
scenario is left behind on sqlite.
Four of the eight scenarios have a database and are converted; the other four
have none and are untouched.
molecule-shared/tasks/postgres.yml stands Postgres up on the scenario's network,
with the data directory on a tmpfs since it is thrown away with the container.
The image is pinned at the major the postgres role deploys to new installations
and left to Renovate: when a new major lands, the PR bumping that pin runs every
scenario against it, which is the earliest warning we get that a component does
not cope.
Each scenario gives its database and user names that differ from the role's
defaults, so the component reaching the database proves the role built its
connection string out of them. The assertions moved from "a file appeared at the
path we configured" to "these tables exist", which is strictly stronger: tables
can only appear once the component has resolved the hostname, authenticated with
the credentials the role rendered, and run its migrations to completion.
Costs about 10 seconds per affected scenario (115s to 125s locally for
mautrix-whatsapp), on jobs that run in parallel.
Gotcha worth recording: since Postgres 18 the image puts PGDATA in a versioned
subdirectory and refuses to start if it finds a mount at the old
/var/lib/postgresql/data, so the tmpfs is mounted at /var/lib/postgresql.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEH3vxYSQ5SV4N5z61eyGT
They were hard-wrapped at 80 characters, broke mid-parenthesis, and spent lines
restating what the code below them does.
Rewrapped at natural boundaries instead, with the narration dropped and only the
reasons, gotchas and surprises kept. Section dividers stay - they delineate long
plays rather than narrate them.
Comments only; no scenario behaviour changes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEH3vxYSQ5SV4N5z61eyGT
`matrix_bridge_mautrix_discord_bridge_double_puppet_server_map_default` was
built from `matrix_bridge_beeper_linkedin_homeserver_domain` and
`..._homeserver_address` - a different role's variables, copy-pasted along with
the expression.
No user is affected today: group_vars/matrix_servers points both roles'
`homeserver_address` at `matrix_addons_homeserver_client_api_url` and both
domains at `matrix_domain`, so the rendered value is identical either way. It
only diverges for someone who overrides beeper-linkedin's homeserver settings,
where mautrix-discord would silently follow them.
It does not survive mautrix-discord being used without beeper-linkedin's
defaults in scope, which is how the Molecule scenario found it: with only the
one role loaded, the template fails on the undefined name. The scenario's
neutralising override is dropped in the same commit, so the expression is now
exercised rather than bypassed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEH3vxYSQ5SV4N5z61eyGT
Scenarios install their Galaxy dependencies with `force: true`, so two roles
running at once re-extract the same collections and roles into ~/.ansible and
pull them out from under each other mid-play. It surfaces as a collection that
was working moments earlier going missing:
the connection plugin 'community.docker.docker' was not found
Found while running five scenarios in parallel, where it cost a run.
ANSIBLE_HOME relocates both `collections/` and `roles/`, so one variable covers
both halves; the scenarios' ANSIBLE_ROLES_PATH workaround now follows it rather
than hardcoding ~/.ansible/roles. Left alone if already set, and unset in CI,
where each role runs in its own job and has nothing to collide with.
Verified by removing var/molecule-ansible-home entirely and running
matrix-alertmanager-receiver from cold: green through idempotence, with the
collections and roles landing under the per-role directory - which also shows
nothing was quietly relying on the shared ~/.ansible being populated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEH3vxYSQ5SV4N5z61eyGT
Proves the bridge starts on the configuration this role rendered: the
systemd unit is active and has not been restarting, the appservice port is
open, config.yaml and registration.yaml (parsed, not grepped) carry the
scenario's homeserver address, bot username, tokens, command prefix,
avatar-proxy key, public address and log level, the sqlite database was
created under the role's data path as the role's uid, and the running
container is the version defaults/main.yml pins.
It does not bridge anything. Discord is behind an account, which is where a
scenario stops being a test of this repository.
Two things are specific to this role rather than copied from the
mautrix-whatsapp scenario:
- mautrix-discord is the only bridge here whose validate_config.yml requires
a public address. It is composed from hostname + scheme + path prefix, and
those same three feed the Traefik avatar-proxy labels, so the scenario
picks a non-`/` path prefix and a non-default scheme and asserts the
composed router rule and strip-prefix middleware - both in the label file
and, read back off the running container, as labels Docker accepted.
Traefik labels are left enabled here for that reason, unlike in the
mautrix-whatsapp scenario which asserts their absence.
- matrix_bridge_mautrix_discord_bridge_double_puppet_server_map_default in
the role's defaults references matrix_bridge_beeper_linkedin_homeserver_domain
and matrix_bridge_beeper_linkedin_homeserver_address - variables belonging
to a different role, evidently copy-pasted from
matrix-bridge-beeper-linkedin, and unique to this role among the bridges.
A playbook run has every role's defaults in scope so it resolves silently;
a role scenario has only this role loaded and the template fails on the
undefined name. The scenario neutralises it in its own group_vars rather
than touching the role.
Falsified the crash-loop assertion: pointing the in-container sqlite path at
a directory that does not exist makes the bridge exit on startup, and the run
then fails at "Assert the service is active and has not been restarting" with
"activating after 5 automatic restart(s)". Reverted, and green again since -
including idempotence.