Repository navigation
fix(#480): hard-delete the seed artefacts instead of parking them in the Trash - #484
Merged
Merged
Conversation
The seed gate soft-deleted the `admin` / `librarian` rows whose hash still matched the documented seed value. That made the wizard fire and stopped `admin/admin` from logging in, but left two residues: the seed `password_hash` stayed recoverable for the 30 days before auto-purge, one `UPDATE users SET deleted_at = NULL` away — which is exactly what the Trash panel's Restore button does — and the seeded `sessions` row, whose token is published in CLAUDE.md and the git history, was never touched at all. The gate now deletes both outright, in one transaction: sessions first (`fk_sessions_user` has no CASCADE), then the user rows. `admin_audit` and `api_keys` are ON DELETE SET NULL, so their rows survive detached. The predicate no longer filters on `deleted_at`, so an instance upgrading from v1.18.0 or earlier purges the rows its old gate had soft-deleted. The hash guard is unchanged: an operator who rotated the seeded password keeps their row. Refs #480 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D5cV9QRqQAFpiNKZwZfoNX
…e browser Integration coverage (`tests/seed_gate.rs`) moves from "both rows are soft-deleted" to "both rows are gone", and adds: the published session token is removed; a second session of a seeded user goes with the user (the FK that would otherwise abort the DELETE); the audit trail is detached rather than cascaded away; rows an older version soft-deleted are purged on the next boot. For the browser side, the `e2e-wizard` CI lane no longer wipes `users` and `sessions` with raw SQL. The wizard compose override unsets MYBIBLI_SEED_DEV_USERS, so the gate itself clears them at boot — which turns that lane into the end-to-end proof that a fresh production install starts clean. The wizard smoke spec then asserts what an operator would see: an empty Trash panel, and the published session cookie no longer authenticating. README, .env.example and CLAUDE.md say hard-delete where they said soft-delete. Refs #480 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D5cV9QRqQAFpiNKZwZfoNX
guycorbaz
marked this pull request as ready for review
September 10, 2026 19:25
guycorbaz
added a commit
that referenced
this pull request
Sep 11, 2026
Cut the v1.19.0 release documentation: version bump in Cargo.toml / Cargo.lock, and every version-bearing surface required by Foundation Rule 19 brought to 1.19.0. - Manual EN/FR: title-page version, install snippets, a "What's new in 1.19.0" section in chapter 8 (the Restore button that never worked, the seeded accounts leaving no trace, the bounded cover decode, and an explicit "nothing to do on upgrade"); the release-notes section now states that the PDFs are attached to the release rather than promising it once CI is wired. PDFs rebuilt. - README: status line, live-install label, image-size badge, current- release paragraph. - ROADMAP: current-stable header, a v1.19.0 shipped section, and the "merged but not released" block retired now that it has shipped. - docs/dockerhub-overview.md: tags list. - website/: index (nav badge, hero, JSON-LD softwareVersion, the hero paragraph rewritten around this release), about (nav badge), roadmap (meta descriptions, JSON-LD, nav badge, both narrative paragraphs), sitemap lastmod — stale since May, and the surface Rule 19 names as the easiest to forget. - sprint-status.yaml: last_updated header. No source change. The release carries #478, #480 and #479 (merged in #485, #484 and #486), the supply-chain CI work (#481), the network- posture documentation (#482), the community-health files (#477) and the documentation audit (#487). No migration. Claude-Session: https://claude.ai/code/session_01D5cV9QRqQAFpiNKZwZfoNX Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #480.
The residue
The #173 seed gate soft-deleted the
admin/librarianrows whosepassword_hashstill matched the documented seed value. That was enough to fire the wizard and to stopadmin/adminfrom logging in, but it left two things behind:UPDATE users SET deleted_at = NULLaway. The Trash panel's Restore button is thatUPDATE. Once [CR] The Trash panel's Restore button points at a route that does not exist #478 wires the missing route, an administrator who seesadminandlibrarianin the Trash and restores them — reasonably, believing a predecessor deleted them — gets back a live administrator whose password is published in this repository's ownSECURITY.md.sessionsrow was never touched. Its token is published inCLAUDE.md, in the E2E helpers and in the git history. Inert today on two counts, which is one count more than it should need.This is the change #480 says must land before #478.
The change
seed_gate::apply_withnow runs one transaction:user_idof any seeded row (fk_sessions_userhas no CASCADE, so this ordering is required, not cosmetic);admin_audit.user_idandapi_keys.created_byareON DELETE SET NULL(#69 / #70), so audit history survives detached — an integration test pins that.Two deliberate choices:
deleted_atfilter. An instance upgrading from v1.18.0 or earlier carries rows its old gate soft-deleted; the first boot on this version purges them too.One consequence worth stating: an operator who has been running with
MYBIBLI_SEED_DEV_USERS=1and then unsets it no longer gets the seeded admin back from the Trash. That is the point of the issue; the wizard fires and they create a real admin.Coverage
Rust —
tests/seed_gate.rsmoves from "both rows are soft-deleted" to "both rows are gone", plus: the published token is removed; a second session of a seeded user goes with the user; the audit row is detached, not cascaded; rows an older version soft-deleted are purged on the next boot; and the rotated-password and idempotence properties still hold.Browser — the
e2e-wizardCI lane no longer wipesusersandsessionswith raw SQL.docker-compose.wizard.ymlunsetsMYBIBLI_SEED_DEV_USERS, so the gate itself clears them during the boot the lane already waits for. Wiping by hand would have hidden a broken gate; letting the gate do it makes that lane the end-to-end proof that a fresh production install starts clean. The wizard smoke spec then asserts what an operator actually sees: an empty Trash panel, and the published session cookie no longer authenticating (/adminbounces to/login).Docs (
README.md,.env.example,CLAUDE.md) say hard-delete where they said soft-delete.Testing
Run locally against the wizard stack and the test MariaDB:
cargo clippy --all-targets -- -D warnings— clean.cargo test --lib— 1171 passed.cargo test --tests --no-fail-fast— 31 binaries, 204 tests, all green.removed_count=2, sessions_removed=1;sessionsempty, onlySYSTEMleft inusers.MYBIBLI_SETUP_E2E=1 npx playwright test specs/journeys/setup-wizard.spec.ts— passed against that stack.🤖 Generated with Claude Code
https://claude.ai/code/session_01D5cV9QRqQAFpiNKZwZfoNX