`Bind` and `ServerDaemon` are serde-serialized descriptions of on-disk
server state (the `server.json` record). They belong with
`RuntimeDirectory` in fabro-config rather than in fabro-server's web
layer.
The practical payoff: fabro-test was hand-parsing `server.json` via
`serde_json::Value["pid"]` because fabro-server already depends on
fabro-test (cycle blocked the reverse edge). Moving these types into
fabro-config lets fabro-test call `ServerDaemon::{load_running, read,
remove}` directly, dropping ~20 lines of duplicated record parsing.
fabro-config gains `fabro-proc` and `tempfile` as deps to cover
`ServerDaemon::{is_running, write}`. All 16 `fabro_server::{bind,
daemon}` import sites in fabro-server and fabro-cli are rewritten to
`fabro_config::{bind, daemon}`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collapses the identical scopeguard + serve_command block in
`start.rs::execute_foreground` into a single `foreground::serve_with_daemon_record`
helper shared with `server::dispatch`.
Changes `prepare_foreground_server_log`, `acquire_lock`, and
`load_or_create_local_session_secret` to take `&RuntimeDirectory` instead
of `&Path storage_dir`, since each only consumed the path to immediately
rebuild a `RuntimeDirectory`. Callers that still need the raw storage
path for child processes keep it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unauthenticated commands surfaced only `error: Authentication required.`
with no remediation. Add a cyan-bold `hint:` line pointing at
`fabro auth login` in the top-level error printer, keyed off
`ExitClass::AuthRequired` so it covers every command that hits the
server (run, exec, ps, system info, etc.). Suppressed when `--json` is
set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The dev-token gating commit added ensure_home_server_auth_methods only to
run_cmd/create_cmd helpers, but many integration tests use context.command()
directly to invoke run/start/attach/etc. Patch the offenders rather than
hoisting auth-injection into command() itself, since command() is also used
by tests (e.g. uninstall) that explicitly want a stable settings file.
- attach, start, scenario lifecycle/recovery, json_global graph: call
context.ensure_home_server_auth_methods() up front
- validate(): hoist into the helper itself, since every validate test
needs it
- server_status, uninstall legacy-record tests: bake methods=["dev-token"]
into their hand-written settings.toml fixtures and pass FABRO_DEV_TOKEN
via env so the spawned server actually boots
- install: write_artifact_store_metadata_creates_marker test fixture also
needs explicit methods after the resolver became strict
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After removing the implicit [server.auth] dev-token default, several unit
tests still passed empty SettingsLayer values into paths that resolve
server settings, so they panicked with "server.auth.methods: field is
required". Restore them by injecting dev-token methods in test fixtures
(consistent with the existing fabro-config resolve_server test pattern),
and rescue create_test_app_state_with_session_key, which bypassed the
existing ensure_test_auth_methods helper.
Clippy clean-ups unblock `cargo clippy --workspace -- -D warnings`:
- fabro-config: bring SettingsLayer into scope, flatten single-arm match
- fabro-cli: gate storage_dir unit tests with allow(deprecated), drop
unnecessary borrow, scope effective_settings imports, drop needless
raw-string hashes
- fabro-server: replace Option<Option<String>> test helper with an
EnvOverride enum, widen test unwrap → expect
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`--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>
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>
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>
Implements the plan at
docs/plans/2026-04-20-003-refactor-unify-run-vocabulary-metadata-plan.md.
- Rename RunRecord to RunSpec and RunProjection.run to .spec everywhere
in Rust source, tests, helpers, test names, and error messages.
- Introduce SerializableProjection wrapper that trims bulky node text
fields (prompt, response, diff, stdout, stderr) for run.json snapshots.
- Collapse metadata-branch and CLI export to one RunDump::from_projection
builder emitting run.json + graph.fabro + stages/{stage_id}/... and
drop legacy top-level start/status/checkpoint/sandbox/retro/conclusion
split files.
- Replace MetadataStore::write_checkpoint with write_snapshot returning
the commit SHA; add read_run_projection/read_run_spec; demote
read_checkpoint/read_start_record to projection-field extractors.
- Switch fork, rewind, rebuild_meta, CLI rewind recovery, and retro
upload to read the unified projection layout.
- Add additive query methods on RunSpec and RunProjection.
Serde-level `alias = "spec"` shim dropped; `rename = "run"` retained to
keep the server API wire format stable per the plan's scope boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Expose apply_bearer_token_auth and ensure_refresh_target_transport
from fabro-client; drop the CLI's duplicate copies.
- Collapse AuthStore's two read paths into one NotFound-tolerant
reader and drop the pre-existence checks in get/remove/list.
- Avoid rewriting auth.json when remove found nothing.
- Inline the one-line user_config::build_public_http_client wrapper.
- Trim unused pub use fabro_api::types re-export and the narrating
doc comment in fabro-client/src/lib.rs.
- Clean up pre-existing unused imports in run/create.rs and
loopback.rs tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lift shared client DTOs into fabro-types, move auth/target/error/session
logic into fabro-client, and reduce fabro-cli to orchestration around the
builder-based client path.
This also lands the remaining plan cleanup for ApiError, ServerTarget
canonicalization, and the RunEventStream rename at the CLI boundary.
The `foreground_start_writes_tracing_to_storage_server_log` test
consistently took ~10.4 s. 10.3 s of that was spent inside `fabro
server stop`, which polls `process_running(pid)` every 100 ms until
the server exits. The test's server is spawned as a child of the test
process (`child.spawn()`), and the test only reaps it via
`child.wait_with_output()` after `fabro server stop` returns. After
Step A's revert, `process_running` is a plain `kill(pid, 0)`, which
returns true for a zombie — so the poll saw the dead-but-unreaped
server as alive and burned the full 10 s timeout.
Add `fabro_proc::process_running_strict(pid)` — the same
ps-shelling zombie-aware predicate commit 1ed8e6cbd introduced — and
use it only in `fabro-cli`'s server stop poll. The hot paths that
motivated Step A (test-harness marker scans, daemon-liveness probes)
continue to use the cheap `process_running`.
The ps cost (~2 ms per call) is paid at most once per 100 ms poll
interval and only while the server process still exists. In a normal
clean shutdown that's zero calls (process exits before the first
poll). In the zombie scenario the loop exits after ~1 poll instead
of running out the full timeout.
Verified on this branch:
cargo nextest run -p fabro-cli -E 'test(foreground_start_writes_tracing)'
before: 10.48s, 10.45s, 10.42s
after: 0.35s, 0.32s, 0.25s (30x faster)
The zombie regression test removed in commit da87f978c returns as
`process_running_strict_returns_false_for_unreaped_zombie_child`,
and also asserts that the cheap `process_running` keeps its
"zombie == alive" semantics so the harness hot paths stay honest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Delete the CliTargetTls settings, ClientTlsSettings struct, rustls-pemfile
dep, and the fabro-http wrapper methods (use_rustls_tls/identity/
add_root_certificate) that only the CLI client-auth path used. The server
no longer terminates TLS in-process and the CliAuthStrategy::Mtls variant
had no construction or match sites.
Also simplify ServerTarget::HttpUrl to a tuple variant (HttpUrl(String))
now that tls is gone, removing the struct-variant ceremony across 22
construction and destructure sites.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Finish the rename started with the Client alias: drop the alias and use
the Client name directly for the server-facing CLI client struct.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dedupe bearer-token extraction and the trailing from_bundle construction
in the managed Unix-socket connect path, return Bind from the
ensure_server_running_on_socket helper to match its sibling, and drop
"target" from its overlong name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ensure configured Unix socket targets still autostart on their requested
socket path during concurrent connection races, instead of falling back to
the storage-owned default bind.
Tighten CLI loopback target classification to use literal host checks,
update explicit local TCP auth coverage to match the remote-target
contract, and align server CSP assertions with the current external-script
SPA bundle. Also enable reqwest cookies in fabro-http so package-scoped
server tests compile without relying on workspace feature unification.
Consolidate three copies of `normalized_http_base_url` and
`build_public_http_client` into shared helpers in `user_config`,
add `Display for ServerTarget`, drop stale `#[allow(dead_code)]`
markers now that login/logout/JWT are wired, remove dead
`LOGIN_SUCCESSFUL` and `_error_description` field, and gate
test-only helpers behind `#[cfg(test)]`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop dead http_client() accessor and its allow(dead_code).
- Scope map_api_error to module-private; all call sites are in-file.
- Rename test helper test_api_client to test_client to match what it returns.
- Run system df's two independent server GETs concurrently with try_join!.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap the remaining CLI server API calls in server_client::Client,
remove the api/connect_api_client escape hatches, and migrate
model/install/tests to the new facade.
Split raw PID existence from actual process liveness in fabro-proc and
switch the server shutdown paths to the running-process predicate. This
avoids waiting out stop timeouts for unreaped zombie children while
keeping process-group behavior covered by measured regression tests.
Add the missing refresh transport guard, actionable auth-store lock errors
for unsupported filesystems, explicit OAuth state expiry, and the remaining
CLI auth regression coverage around replay revocation, HTML headers, and
secret-safe logging.
The CLI façade is the primary type callers reach for, so it deserves
the bare `Client` name (per `reqwest::Client`, `hyper::Client`
convention). The raw generated HTTP binding is secondary and is more
accurately named `ApiClient`. "Store" in `ServerStoreClient` was
leftover from the SlateDB-ownership refactor and no longer describes
the type.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>