Superseded by Principle 13.
Wait rows in unlock and shared mount helpers
Principle: 13. Announce long-running work
Context
The single-passphrase invariant
(Principle 4) requires braid unlock to verify the supplied credential against every reachable LUKS
member before opening any mapper. On a 3-disk pool that is three
sequential Argon2 derivations – visible to the user as three
back-to-back [wait] passphrase: checking against ... rows.
Two later phases of the same command stayed silent:
- Per-disk
cryptsetup luksOpen. cryptsetup re-derives Argon2 insideluksOpeneven after--test-passphrasealready verified. The user saw three[ok] disk X: unlockedrows arrive one by one with no leading announcement. - Mount phase.
scan_and_mountrunsbtrfs device scan+mkdir+mountin sequence and emits a single[ok] pool: mounted ...row at the end. None of the three steps are announced individually.
The result: between the last verify row and the mount row, the user stared at an inactive terminal for several seconds with no signal that anything was happening.
Options considered
- TTY spinner / progress bar. Rejected: requires a TTY, fights log
capture, and looks broken inside
braid-auto-unlock.servicejournals. - Best-effort ad-hoc waits. Add
[wait]rows whenever a gap is noticed. Rejected: gaps recur whenever a new slow path is added. - Codify “
[wait]before every long-running step” as a project principle now. Rejected: principles are authoritative (docs/design/principles.md); a principle the codebase doesn’t satisfy on the day it lands is a documentation bug.add,replace,remove,remove-missing,recover’s own (non-shared) slow paths,enroll, andlockkeep silent gaps today. - Scope the rule to
braid unlockand the shared mount helpers today; promote to a principle once the other commands comply. Accepted.
Decision
braid unlock – and braid recover’s mount tail, which routes
through the same shared helpers – emit a [wait] row before every
long-running step:
- per-disk
cryptsetup luksOpen(passphrase and keyfile arms), worded[wait] disk {name}: unlocking...; - the mount phase (
btrfs device scan+mkdir+mount), worded[wait] pool: mounting {mount_point}.... The single row covers all three steps because emitting one row per step would be noisy without buying the user any actionable information.
Each [wait] row uses
status_tag::status_line(StatusTag::Wait, ...) and is closed by the
existing per-step success row.
Other interactive commands keep their current behavior until they are individually updated.
Tradeoffs accepted
- Slightly more verbose stderr.
- Enforcement for the in-scope helpers is by VM-test assertion in
tests/cli/braid-unlock.py,tests/cli/braid-unlock-key-file.py, andtests/cli/braid-recover.py. Project-wide enforcement is deferred until promotion to a principle. braid recoverinherits the new rows automatically becauseexecute_mount_onlyandexecute_unlock_and_mountare shared. This is desirable:recover’s mount tail is exactly the same long-running work asunlock’s.
Promotion outcome
Promoted to Principle 13 once add, replace, remove,
remove-missing, recover’s replay tail and self-mount remount
cycle, lock, and enroll were brought into compliance.
See
cli/src/mount.rs–open_disks_with_credentialandscan_and_mounthost the new rows.cli/src/status_tag.rs– the canonicalStatusTag::Waitandstatus_linehelpers.cli/src/unlock.rs:93-96– the already-mounted short-circuit that returns before any helper runs (so already-mounted unlocks emit no new rows).