`--local` is supposed to render the settings that apply to the local CLI
/ client side, so it has no business resolving server settings. Drop the
server section and the warning path, return only project/workflow/run/
cli/features. Removes the boundary violation that was about to break the
CI boundary check, and restores the legacy_*_silently_ignored tests to
their original silent assertion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add FABRO_SUPPRESS_OPEN_BROWSER env knob via fabro_util::browser::try_open.
apply_test_isolation now sets it, so install-mode and auth-login tests that
spawn a real fabro binary no longer pop real browser windows. All six
open::that call sites route through the helper; consolidates the direct
open crate dep into fabro-util.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Conflict resolved in fabro-client tests: union both import sets so the
new auth-required classification tests (httpmock-based) and our positive
plain-HTTP refresh test (raw TCP responder) coexist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Route install/uninstall through local_server::storage_dir instead of hand-
rolled copies, drop dead connect_api_client and run_dir plumbing, eliminate
double-resolve in prepare_server_bootstrap, and tighten the boundary
allowlist now that uninstall no longer needs the exemption.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inline the validate_canonical_origin wrapper, move reload-failure logging
to the single caller with accurate wording, drop a hand-rolled tracing
capture layer from tests, and migrate web_auth OAuth handlers to
state.canonical_origin() so the is_empty/resolve guards fall out.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Simplifies three spots surfaced by a code-reuse pass: use
console::strip_ansi_codes in fatal_error_line, use provider_kind()
instead of re-pattern-matching the LLM error shape in
classify_server_agent_auth, and drop an unused const on Classified::class.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements plan: single origin, drop CLI preflight, gate demo toggle.
Removes loopback client target and CLI auth config preflight endpoint;
adds canonical_origin module on the server; regenerates SPA and TS API
client.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move server-only settings reads out of user-facing CLI commands into a
dedicated local_server module, the install/uninstall exceptions, and the
worker subcommand. Adds bin/dev/check-boundary.sh to prevent regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Returns exit code 4 whenever the CLI fails because the user needs to run
fabro auth login, so scripts and the install wizard can distinguish
re-auth from generic failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`run_install_inner` unconditionally created `~/.fabro/dev-token`, wrote
`<storage>/server-state/dev-token`, and emitted `FABRO_DEV_TOKEN=...` into
`<storage>/server.env` — even when the user chose GitHub App auth and
the final `server.auth.methods` did not include `dev-token`. Commit
64e423953 removed `dev-token` from `server.auth.methods` but left the
token-material generation untouched. The server-side install handler
already gated these side-effects correctly; the CLI path had diverged.
Now `run_install_inner` parses the final `settings.toml` and only
generates/writes the dev-token when `server.auth.methods` actually
contains `dev-token`, mirroring `fabro-server`'s install handler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The primary install flow is now to start the server, complete setup in a
browser-based wizard, and restart. fabro install is retained as the
headless CLI-only alternative.
- Auto-open the install URL in the user's browser when fabro server start
enters install mode; print a manual-open fallback when open::that fails
- Rewrite install.md so agents drive the full start → wait → restart loop
- Retarget install.sh Y/n prompt from fabro install to fabro server start
- Update README, quick-start, deploy-server, cli reference, and marketing
captions to point at fabro server start as the next step after download
- Add troubleshooting entries for "wizard didn't open" and "server exited
after wizard"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The demo router hardcoded AuthMode::Disabled, which caused /auth/config
and /auth/me to lie and let demo endpoints be reached without a session
whenever the fabro-demo=1 cookie was set. With the cookie set on a
GitHub-configured server, /login rendered "Paste your dev token" with
no input and no GitHub button because /auth/config returned empty
methods.
Have the demo router inherit the real AuthMode so demo mode is purely a
data-source toggle: authentication is identical regardless of the
cookie. Update the translate test that locked in the old bypass, add a
companion test for the authed happy path, and add a regression test
that /auth/config returns real methods under the demo cookie.
As defense in depth, the login page now renders an explicit "no
authentication method is configured" state when methods is empty
instead of the misleading dev-token prompt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the install wizard (or `fabro install github --strategy app`)
writes GitHub App settings, it now removes "dev-token" from
`server.auth.methods`, mirroring how `write_token_settings` removes
"github" in the opposite direction. Users who want both auth methods
can still configure that explicitly by editing `settings.toml`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The ephemeral loopback server that finishes `fabro auth login` returned
four raw HTML fragments (one literally `<p>Logged in. You can close
this tab.</p>`, and two that weren't even wrapped in a document).
Replace them with a self-contained dark-theme shell that mirrors the
redesigned /auth/cli/resume page: inline Fabro logo SVG, dark panel
over the atmosphere gradient, mint status dot for success, coral for
failure, consistent typography. Shell is fully offline — this process
doesn't have /logo.svg or the SPA CSS available, so everything is
inlined. Also HTML-escape the oauth error_description before
interpolation, and add `white-space: nowrap` to inline <code> in the
resume shell so `fabro auth login` never wraps mid-command.
Copy alignment: success eyebrow "Signed in" + headline "You're signed
in to Fabro"; error eyebrow "Sign-in failed" + headline "CLI sign-in
could not continue", with remediation pointing at the exact command.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CLI login confirmation and error pages rendered as a light-theme
white panel with a navy pill button, jarring against the dark SPA the
user arrives from. Rebuild the inline shell against the app's semantic
tokens — navy page with the same two-radial atmosphere gradient as
app.css, translucent panel, mint status-dot eyebrow, teal-500 primary
button on navy-950 text, Fabro logo at the top — and tighten the
identity card to use a real metadata line instead of a nested
paragraph. Error variant reuses the same shell with a coral eyebrow
and names `fabro auth login` explicitly in the remediation copy. Button
now reads `Continue as @login`, matching the identity row and making
it read as a GitHub handle rather than a bare string.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On a GitHub-auth-only server, `fabro repo init` fails for a fresh CLI
because no credential is present yet, so the onboarding hint was wrong
for those installs. Fetch /auth/config alongside the board query and,
when `methods` contains "github", prefix the quick-start with
`fabro auth login`. The copy-to-clipboard target is derived from the
same list so it stays in sync. Falls open to the prior two-line hint if
the config call fails — the blank slate must not gate on that request.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GithubAppDoneScreen was firing `<Navigate to="/install/github">` on the
first render after GitHub's manifest callback because the render path
used `sessionState.status === "loading"` as its loading gate. Between
initial mount (sessionState defaults to "idle") and the session-fetch
useEffect flipping it to "loading", the main layout rendered once with
`session === null`. Done screen saw `github === undefined`, treated the
session as misconfigured, and bounced the user back to the already-done
"Connect GitHub" form — a redirect loop after a successful GitHub App
install. Broaden the gate to `!session` so every transient state with a
token-but-no-session shows the loading screen, not a half-rendered step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitHub's App Manifest endpoint rejects `redirect_url` values that carry a
query string with "invalid redirect_uri", leaving the web wizard stuck:
the 10-minute pending-setup guard then blocked every retry for ten
minutes. Move the CSRF state out of `redirect_url` and into a hidden
`state` form field on the auto-submit — GitHub preserves it on the
callback, matching the CLI's working Manifest flow. Drop the retry
conflict so a fresh POST to /install/github/app/manifest always replaces
the pending entry and mints a new state token; stale callbacks are
already rejected by the existing state-match check on the redirect
handler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Workspace clippy.toml bans std::fs::read_dir / read_to_string without
an explicit expect annotation. The new Linux zombie-group probe uses
both and only compiles on Linux, so the lint wasn't hit locally on
macOS. Annotate the helper with the reason it needs sync I/O.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Linux's kill(-pgid, 0) succeeds even when every group member is a zombie
waiting to be reaped; macOS returns ESRCH in the same situation. Callers
polling on process_group_alive (fabro-server's SIGTERM grace loop, plus
the zombie-only regression test in fabro-proc) therefore saw divergent
behavior: CI on Linux had been failing for days on the asserting test.
After the cheap kill(2) probe, walk /proc and confirm at least one
non-zombie process still reports the given pgid. Non-Linux unix targets
keep the fast path. Falls back to "alive" on /proc read failure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dev-token sessions now carry a non-empty IdpIdentity, so filtering by
identity presence alone let the CLI start flow auto-resume under a
dev-token session. Tighten eligibility to GitHub-authenticated sessions
and update the auth_harness test helper to pass auth_mode by reference
to match the current build_router_with_options signature.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collapse the five near-identical settings.with_replacement calls in
fabro-api/build.rs into a single data-driven table loop, flatten the
status round-trip test loops into per-variant assertions (dropping
redundant duplicate assertions against both API and domain variants
since TypeId already proves they're the same type), and tighten the
unknown-stage-status tracing message so it describes the event rather
than narrating the fallback rationale.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document the preference for reusing canonical Rust types across the API
boundary, aligning near-miss types instead of tolerating drift, and backing
any build.rs replacements with parity tests.
- drop per-request AuthMode clone in auth_translation_middleware
- remove dead CredentialSource enum and VerifiedAuth field
- collapse cookie_key_error + jwt_key_error into session_secret_key_error
- use header::AUTHORIZATION constant in bearer_token
- remove narrating doc comments on extractor structs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two middleware gaps from the translation-refactor plan: a
fabro_refresh_* bearer should flow through unchanged, and a session
cookie should still mint a JWT when x-fabro-demo is also set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fall back to StageStatus::Fail with a warning when historical event data
contains an unrecognized stage status string instead of panicking during replay.
Move session cookie and dev credential handling into middleware so the
real router only sees Bearer JWTs. This also carries profile claims
through /auth/me and requires session signing material whenever auth is
enabled.
- Use hex crate for [u8; 32] RecordId instead of hand-rolled loops
- Drop dead prefix_segments cache field; key assembly consumes
R::PREFIX.split('/') directly, removing an intermediate Vec<&str>
- Cache BlobStore on RunDatabaseInner (built once in open_writer/
open_reader via a new build() helper) instead of per-blob construction
- Trim the replay_revocations doc comment to drop a stale plan reference
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a short record-layer overview plus a concrete example for defining a
new record type and wrapping Repository<R> in a domain store, so the
internal SlateDB abstraction is easier to discover and reuse.