GNOME-only autostart entry installed to /etc/xdg/autostart/. Uses
X-GNOME-Autostart-Phase=Initialization so it runs before user
autostarts but after GDM hands the session off. NoDisplay=true keeps
it out of the apps menu — autostart-only.
Plan deviation: project uses debian/rules for installation (no
debian/authforge-gui.install file exists), so the install line goes
there instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the prep-lane stub with the real Flow C: a fullscreen,
undecorated adw::ApplicationWindow that queries the daemon's pending
flag for the current user, presents an AdwStatusPage "Get started"
button when a flag is set, runs the Phase 8 enrollment dialog, calls
ClearPendingFlag on success, and exits. A 60s idle watchdog (5s tick)
falls back to gnome-session-quit --logout. EventControllerKey and
GestureClick poke the WatchdogState on activity; missing pending flag
short-circuits to immediate exit so the autostart cost is one D-Bus
call.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Convert gui/src/firstrun.rs to a module dir with watchdog.rs, the
pure-logic WatchdogState helper that the GTK widget code in Task 2
will poll. Three unit tests cover fresh/expired/poke transitions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
clear_pending_flag is added alongside get_pending_status because Phase 10
needs both — its first-login modal checks status on startup and clears
the flag on successful enrollment. Bundling here keeps the Phase 10 lane
free of bus.rs edits.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the #[allow(dead_code)] on AppState::get_pending_status — it's now
called from the D-Bus dispatch. Two new p2p tests cover the absent and
the round-trip cases.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
has_pending stays #[cfg(test)] because production GUI reads via the new
get_pending_status (one round-trip carries both presence and flag);
production PAM module reads the file directly. Method gated with
#[allow(dead_code)] until Task 5 wires D-Bus.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PendingStatus carries an explicit 'present' bool next to a PendingFlag
because zvariant doesn't support Option<T>. Two new serde tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
argv is read manually before adw::Application sees it (GTK's option parser
rejects unknown long flags), then the filtered list is passed to
app.run_with_args. Stub exits cleanly with a stderr breadcrumb so a
developer who passes --first-run before Phase 10 lands doesn't get a
stuck process.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KeysPage::new now takes a single AppContext that owns parent_window,
toast_overlay, and the shared daemon Rc<RefCell<Option<Daemon>>>. Phases
9, 10, 11, and 12 GUI tab will all consume this struct.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ships four documents:
* 2026-04-27-parallel-fanout-9-10-11.md — coordinator. File-conflict map,
prep-lane scope rationale, post-prep parallel-safety guarantee, merge
order rule, and risks.
* 2026-04-27-prep-shell-and-pending.md — small ~1hr prep lane: extract
AppContext, add --first-run flag scaffold (with stub firstrun::run),
add daemon GetPendingStatus D-Bus method + GUI client wrapper, new
PendingStatus wire type. 6 tasks, single agent.
* 2026-04-27-phase-10-firstrun.md — first-login flow. Fullscreen modal,
60s idle watchdog (TDD'd as pure WatchdogState), enrollment via Phase
8 enroll_dialog, ClearPendingFlag on success, gnome-session-quit on
idle, autostart .desktop entry, debian install. 6 tasks.
* 2026-04-27-phase-11-totp.md — TOTP support behind default-on Cargo
feature. Daemon-side: 160-bit secret + base32 + otpauth URI + atomic
0600 writes in pam_google_authenticator format. PAM profile renderer
extension. D-Bus surface. CLI subcommand. GUI tab with QR modal.
Deviation flagged: TOTP recovery codes reuse the Phase 12 recovery
flow rather than introducing a parallel hashed-recovery file format.
9 tasks.
Execution model: prep lane first (single agent ~1hr), then dispatch three
subagents in parallel worktrees for the three follow-on lanes. Per the
conflict map, the lanes are file-disjoint after prep merges; merge order
is any-order.
If all four lanes land cleanly, the roadmap jumps from 13/19 to 17/19
phases code-complete in a single session, leaving Phase 13 (deb finalization),
14 (PPA + smoke), 17 (integration tests), 18 (user docs), and the v1.0
release tag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Phase 9/10/11 status rows: dependencies satisfied, mark "open now" with
pointers to step-level plans where they exist.
* Phase 12 row: GUI tab is folded into the Phase 9 lane plan (not its own).
* ASCII dependency diagram: collapse the just-landed Phase-8/Phase-12-backend
layer; show the three open lanes (9+12-GUI, 11 TOTP, 10 first-login)
fanning into Phase 13.
* "Parallel-safe right now" heading bumped to post-Phase-8+12-backend.
Table swaps the now-stale Recovery and post-Phase-8 GUI rows for the
three actually-open lanes, with file footprints + plan links.
* Sequential tail paragraph names the six Code-complete phases (1, 3, 4,
6, 8, 12) whose deferred gates Phase 14 must clear.
* Phase 12 closeout intro names the merge commit and corrects the now-stale
"waiting on Phase 8" wording.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flips Phase 12 status to ✅ Code complete; bumps progress to 12/19 (63%).
Closeout block summarizes the 8 implementation tasks, lists all new
artifacts, calls out the 80-test count (was 60), and flags the Phase 14
deferred verifications (libargon2-dev compile + pamtester smoke).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* RecoveryCmd::List no longer takes a user — lists all active codes
across users, matching the daemon's ListRecoveryCodes signature.
* RecoveryCmd::Revoke <user> calls RevokeRecoveryCode; exits 1 with
a stderr message when the user has no active code.
* bus.rs gains list_recovery_codes() / revoke_recovery_code(user).
* Two new clap-parser tests cover the new subcommand shapes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
C-side:
* New mode=recovery argv branch in pam_sm_authenticate. Reads the two-line
/var/lib/authforge/recovery/<user> file, argon2_verify against PAM_AUTHTOK,
on match unlinks the file (one-shot) and writes pending(re_enroll=true).
Always returns PAM_IGNORE on failure paths so a missing/wrong code never
blocks normal auth.
* Makefile links -largon2 alongside -lpam.
Daemon-side:
* policy_apply::render_profile renders the recovery line first in the auth
stack with [success=done default=ignore] — successful recovery short-
circuits the rest, missing/wrong code falls through.
* New policy_apply test asserts the recovery line precedes the default
backstop.
Doc:
* pam/TESTING.md adds libargon2-dev to the build prereqs and a new
Smoke test 4 walking through the manual recovery-code flow.
C compile gate (make -C pam) requires libargon2-dev — flagged as a
deferred verification step until a host with the dev package is available.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the random-number stub at dbus.rs:132 with state.issue_recovery,
add ListRecoveryCodes (returns Vec<RecoveryCodeSummary>) and RevokeRecoveryCode.
New polkit actions list-recovery / revoke-recovery (auth_admin_keep). Adds
4 D-Bus integration tests; the previously-stub generate-code test now
exercises the real Argon2id-backed write path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
StorageConfig gains a recovery_dir field; from_env_or_defaults reads
AUTHFORGE_RECOVERY_DIR (default /var/lib/authforge/recovery). AppState
exposes issue/list/revoke methods that Task 6 wires through D-Bus. Test
fixtures in state.rs and dbus.rs updated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two-line file format (expires_unix\nargon2id-PHC) keeps the C PAM module
parser trivial — no json-c link needed. Atomic temp+rename means a
concurrent reader never sees a half-written file.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 8: GUI Keys tab. Lists current user's enrolled FIDO2 credentials,
enrolls a new key with a touch-prompt modal driven by the daemon's
EnrollmentFailed signal (success comes from the call return), removes
credentials per row, degrades gracefully to a Retry banner when the
daemon is unreachable, and surfaces transient errors via adw::Toast.
8 tasks, 4 unit tests (error classifier).
Pure-logic module: generate 8-digit codes, Argon2id PHC hash with OS RNG salt,
verify via constant-time PasswordVerifier. Dead-code allow until Task 4 wires
the store on top.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds enroll_dialog::present which opens an adw::AlertDialog with a spinner
and races the EnrollOwn call against the EnrollmentFailed signal stream:
- Call returns Ok(_cred) -> close dialog, fire on_success refresh callback.
- Call returns Err -> swap dialog body to the user-message form.
- Signal arrives first -> swap to the signal payload (faster than waiting
for the call's typed Err to traverse the bus).
KeysPage::start_enroll wires the activated row to present(), passing the
parent window for modal anchoring and a refresh closure as on_success.
Plan deviation: AlertDialog is libadwaita v1_5-gated, so gui/Cargo.toml
enables that feature. Targets Ubuntu 24.04+ (libadwaita 1.5).
Extract the inline path-traversal check from PendingStore into a shared
storage::safe_user::join_user_segment helper. RecoveryStore (next) reuses it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KeysPage::new spawns the connect-and-render flow on glib::MainContext::
spawn_local. On success it shows a placeholder PreferencesPage (Task 5
fills in the list); on failure it shows StatusPage with a Retry button
that re-runs the connect attempt.
main.rs installs the multi-thread tokio runtime guard before app.run()
so zbus's tokio futures execute correctly when polled by glib.
Layout deviation from plan: ToolbarView is libadwaita v1_4-gated; the
project sticks with gtk::Box vertical for portability (commit c6a5e94
established this pattern).
Phase 12 backend lane is unblocked by Phase 6+7 and runs independently of
Phase 8. Phase 9 + Phase 12 GUI lane gates on Phase 8 landing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>