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>
zbus 4.4 does not expose a glib feature; my earlier draft was wrong about
that. Switch the plan to use the workspace tokio config and install a
multi-thread tokio runtime in main.rs, with the runtime guard kept alive for
the lifetime of app.run(). glib::MainContext::spawn_local still drives the
widget-touching closures; tokio's reactor wakes them.
Also: hoist futures-util into the Task 1 dep bundle so Task 6 doesn't need
a separate Cargo.toml edit.
Single-lane, sequential plan to land the authforge GUI Keys tab — empty-state
banner with Retry, list of enrolled credentials with per-row remove, modal
TouchDialog driving EnrollOwn against the Phase 3 EnrollmentSucceeded /
EnrollmentFailed signals. zbus uses the glib runtime feature (not the
workspace tokio config), so all async runs on the GTK main thread via
glib::MainContext::spawn_local — no tokio dep in the GUI process.
Eight tasks, ~1 day of work. TDD applies to error.rs (pure logic, 4 tests);
widget code ships with the manual smoke recipe at gui/TESTING.md as the
acceptance gate.
Cleans up scattered status markers into a single legend (Done / Code complete
/ Spec'd / Bundled), backfills v0.1.0-phase1 tag reference for Phase 1, marks
the table consistently for what's actually shipping vs. what needs the Phase
14 VM smoke. Updates the lane diagram to show what's done vs. in flight, and
adds a 'lessons learned from this session's parallelism' section capturing
when subagents-in-worktrees actually work and when they slip on sandbox-blocked
verification commands.
Adds closeout notes for Phases 3, 4+5, 6, 7, 15, 16 alongside the existing
Phase 1 and Phase 2 sections — what shipped, plan deviations, and any wire-
breaking signature changes (SetPolicy gained a force flag in Phase 4+5).