Commit graph

1680 commits

Author SHA1 Message Date
Bryan Helmkamp
537a5125cb
feat(auth): tighten server auth surface with single origin
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>
2026-04-21 23:38:31 -04:00
Bryan Helmkamp
5b1c40764d
refactor(cli): enforce CLI/server settings boundary
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>
2026-04-21 23:37:15 -04:00
Bryan Helmkamp
105559bc8a
feat(cli): exit with code 4 on authentication-required failures
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>
2026-04-21 23:34:21 -04:00
Bryan Helmkamp
720fbb210a
fix(install): skip dev-token material when dev-token auth is not enabled
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Waiting to run
TypeScript / Test (push) Waiting to run
TypeScript / Build (push) Waiting to run
`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>
2026-04-21 16:42:22 -04:00
Bryan Helmkamp
a8599a924f
feat(install): pivot happy path to fabro server start web wizard
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>
2026-04-21 16:02:49 -04:00
Bryan Helmkamp
710f9869f0
fix(auth): stop demo mode from overriding authentication
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>
2026-04-21 16:02:49 -04:00
Bryan Helmkamp
64e4239534
fix(install): don't enable dev-token auth when GitHub App is selected
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>
2026-04-21 16:02:49 -04:00
Bryan Helmkamp
cdba970891
refactor(oauth): brand CLI callback pages to match the Fabro auth theme
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>
2026-04-21 16:02:49 -04:00
Bryan Helmkamp
964d531a6e
refactor(auth): restyle /auth/cli/resume to match Fabro SPA theme
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>
2026-04-21 16:02:49 -04:00
Bryan Helmkamp
3f54a006f1
Regenerate fabro-spa 2026-04-21 14:49:51 -04:00
Bryan Helmkamp
3f6cc1f1ba
Merge remote-tracking branch 'origin/main' 2026-04-21 14:47:23 -04:00
Bryan Helmkamp
11ec4a2208
feat(runs): prepend fabro auth login to blank-slate quick-start on github-auth servers
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>
2026-04-21 14:44:14 -04:00
Bryan Helmkamp
7ed57ba863
fix(install): show loading until session resolves, not just during fetch
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>
2026-04-21 14:40:21 -04:00
Bryan Helmkamp
328bddea33
fix(install): pass GitHub App manifest state as form field, not URL query
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>
2026-04-21 14:21:33 -04:00
Bryan Helmkamp
e121eadb04
fix(proc): expect disallowed_methods on /proc walk
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>
2026-04-21 12:26:41 -04:00
Bryan Helmkamp
e9cd2bd5b8
fix(proc): treat zombie-only process groups as dead on Linux
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>
2026-04-21 12:21:11 -04:00
Bryan Helmkamp
3d831ef273
fix(auth): restrict CLI start session reuse to GitHub auth
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>
2026-04-21 12:13:07 -04:00
Bryan Helmkamp
17020ee445
Merge remote-tracking branch 'origin/main' 2026-04-21 10:58:30 -04:00
Bryan Helmkamp
6eee5a5a72
Merge remote-tracking branch 'origin/main' 2026-04-21 09:44:10 -04:00
Bryan Helmkamp
1bbfd00d4d
refactor: simplify API type replacements and status round-trip tests
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>
2026-04-21 09:43:13 -04:00
Bryan Helmkamp
a3e4e2bd3d
Merge remote-tracking branch 'origin/main' 2026-04-21 09:40:09 -04:00
Bryan Helmkamp
7b28b12f49
refactor(auth): simplify translation middleware and jwt_auth
- 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>
2026-04-21 09:39:54 -04:00
Bryan Helmkamp
ab6c7f3e8c
test(auth): cover refresh-token pass-through and session+demo mint
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>
2026-04-21 09:13:39 -04:00
Bryan Helmkamp
413112249c
fix(workflow): keep event replay alive on unknown stage status
Fall back to StageStatus::Fail with a warning when historical event data
contains an unrecognized stage status string instead of panicking during replay.
2026-04-21 09:12:30 -04:00
Bryan Helmkamp
1176bf6d60
fix(agent): shell-quote detached MCP launch commands
Quote each argv element and the wrapped sh -c payload so detached MCP
server startup cannot be broken by embedded quotes or shell metacharacters.
2026-04-21 09:12:26 -04:00
Bryan Helmkamp
e92a2ba9db
refactor(server): drop duplicate run status adapters
Use the unified status family directly in server.rs and remove the manual
conversion helpers that only existed to bridge duplicate API/domain types.
2026-04-21 09:10:57 -04:00
Bryan Helmkamp
eb7e65b6b4
refactor(api): reuse domain status types in fabro-api
Replace the generated status family with fabro-types replacements and add
parity tests that lock type identity and JSON shape to the OpenAPI spec.
2026-04-21 09:10:53 -04:00
Bryan Helmkamp
ebf8f499af
refactor(types): add StatusReason parsing helpers
Add Display and FromStr support for StatusReason so the domain type can
replace the generated API enum without losing string conversion behavior.
2026-04-21 09:10:47 -04:00
Bryan Helmkamp
7a70af1e2b
refactor(auth): translate non-jwt auth into bearer tokens
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.
2026-04-21 09:05:20 -04:00
Bryan Helmkamp
4865efa499
refactor(store): simplify Repository and blob wiring
- 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>
2026-04-21 09:00:00 -04:00
Bryan Helmkamp
b862762b29
docs(store): document record repository pattern
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.
2026-04-21 08:39:44 -04:00
Bryan Helmkamp
0d2be41e57
Merge remote-tracking branch 'origin/main' 2026-04-21 08:38:37 -04:00
Bryan Helmkamp
bf1aa6eed5
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-store/src/lib.rs
#	lib/crates/fabro-store/src/slate/mod.rs
#	lib/crates/fabro-store/src/slate/run_store.rs
2026-04-21 08:35:07 -04:00
Bryan Helmkamp
3525be358b
fix(cli): box run command futures for clippy 2026-04-21 08:33:25 -04:00
Bryan Helmkamp
f75e5c2ef6
refactor(store): extract Record/Repository abstractions
Replaces hand-written K/V stores in fabro-store with a shared Record trait
plus Repository<R> typed K/V layer. Adds KeyedMutex for per-key serialization
and transaction() for all-or-nothing WriteBatch commits. Renames
SlateAuthCodeStore/SlateAuthTokenStore to AuthCodeStore/RefreshTokenStore and
adds BlobStore and RunCatalogIndex wrappers on top of Repository. Deletes
catalog.rs in favor of RunCatalogIndex. Database gains blobs() and
catalog_index() accessors; auth_tokens() is renamed refresh_tokens().

Plan: docs/plans/2026-04-20-003-refactor-fabro-store-record-abstractions-plan.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 08:29:34 -04:00
Bryan Helmkamp
2a3f550e2e
refactor: simplify metadata snapshot + retro upload paths
- MetadataStore: drop redundant write_files (identical to write_snapshot)
  and the brittle has_projection_data OR-chain in read_run_projection.
- operations: introduce find_run_id_by_prefix_opt using
  META_BRANCH_PREFIX; delete the duplicate find_run_id_by_prefix_in_refs
  helper in rebuild_meta and have it call the shared function.
- rebuild_meta: stop cloning latest_init_snapshot once the init snapshot
  has been written; take() the stored snapshot instead of cloning again.
- retro::upload_data_files: collapse the ten eager *_path variables into
  inline base.join(...) args by making upload_file take &Path; replace
  Vec<String>.join("\n") + "\n" with a streaming String loop.
- Migrate retro_agent test std::fs::read_to_string to tokio::fs to
  satisfy disallowed_methods clippy lint under tokio tests.
- Minor: fork.rs drop misnamed `now` var; metadata.rs doc comment
  describes the unified RunProjection snapshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 08:27:59 -04:00
Bryan Helmkamp
705dccdea7
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-store/src/lib.rs
#	lib/crates/fabro-store/src/run_state.rs
2026-04-21 07:53:10 -04:00
Bryan Helmkamp
5e482486e7
test: finish metadata cleanup sweep
Rename the last stale workflow test helpers and assertions that still used
pre-refactor checkpoint/retro file terminology, and update the retro docs
to describe the exported layout that now exists.
2026-04-21 07:44:15 -04:00
Bryan Helmkamp
125a73aae3
fix(client): reject all obfuscated IPv4 host forms at parse time
Replace the narrow decimal/hex obfuscation check with a general
comparison: if the parsed host is an IPv4 literal and the raw input
host differs from the canonical dotted-quad form, the user supplied
an obfuscated variant (octal, short-form, mixed radix, leading
zeros, decimal integer, hex integer) that url::Url has already
normalized to 127.0.0.1. All such variants are rejected. Test now
covers decimal, hex, octal, two-/three-part short, mixed hex/
decimal, and leading-zero octets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:18:44 -04:00
Bryan Helmkamp
7dd058cc40
refactor: unify run vocabulary and metadata snapshot layout
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>
2026-04-20 22:17:23 -04:00
Bryan Helmkamp
1214bebd14
fix(client): reject obfuscated IPv4 literals at target parse time
url::Url normalizes decimal (http://2130706433) and hex
(http://0x7f000001) IPv4 host forms into 127.0.0.1, so after
canonical_http_url rewrites the target the loopback classifier
cannot tell them apart from a legitimate http://127.0.0.1 and
lets a refresh token ride plaintext HTTP. Detect these forms on
the raw input string and bail out before the url crate can hide
them, and split the loopback test to cover the parse-time
rejection path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:49:09 -04:00
Bryan Helmkamp
eb8ea317ec
refactor(client): dedupe helpers and fix TOCTOU in auth store
- 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>
2026-04-20 21:44:09 -04:00
Bryan Helmkamp
f0f04abf44
refactor(client): extract fabro-client crate
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.
2026-04-20 20:42:20 -04:00
Bryan Helmkamp
2b86ad231d
Merge remote-tracking branch 'origin/main'
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Waiting to run
TypeScript / Test (push) Waiting to run
TypeScript / Build (push) Waiting to run
2026-04-20 17:40:09 -04:00
Bryan Helmkamp
4c4d4efcda
fix(cli): detect zombies in server stop poll loop
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>
2026-04-20 17:39:52 -04:00
Bryan Helmkamp
acd89fb235
refactor: remove CLI client mTLS
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>
2026-04-20 17:37:24 -04:00
Bryan Helmkamp
224ce9e21e
test(harness): collapse live_marker_count match into matches!
Clippy (match_same_arms) on the Step C rewrite: Ok(false) and Err(_)
both mean "treat as alive", so expressing it as `if matches!(..., Ok(true))`
reads cleaner and satisfies the lint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:38:28 -04:00
Bryan Helmkamp
39744068c9
Revert "test(harness): add FABRO_TEST_PROBE_LOG timing probes"
The probe helper served its purpose in narrowing the recent per-test
setup regression to `reap_stale_session_roots`. Remove it now that the
underlying cause (process_running shelling out to `ps`) is fixed and
the reap is amortized to once per process. The plan was to carry it
through verification so Step B could quote reap_nextest numbers, then
drop it — this commit is that drop.

This reverts commit 24e7e5af8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:36:31 -04:00
Bryan Helmkamp
2348a1e483
test(harness): use advisory locks for peer presence
Replace PID-based liveness probing in `live_marker_count` with flock
advisory-lock presence detection. Each test process opens
`<session_root>/clients/<pid>` once, holds LOCK_SH for the lifetime
of any live TestContext in the process, and releases it explicitly
when `cleanup_session_root` fires at refcount zero. Reapers probe with
LOCK_EX | LOCK_NB: success means the previous owner is gone (normal
exit, panic, SIGKILL, or zombie — the kernel releases advisory locks
at process exit in every case) and the stale marker is removed.

Compared to the PID check this was replacing:
  - Handles PID recycling correctly (the new holder does not inherit
    the previous owner's advisory lock).
  - Handles zombies correctly without shelling out to `ps`.
  - Costs one open + one flock per peer, ~50 us on macOS.

The marker handle is stored in a process-scoped
`Mutex<Option<(PathBuf, File)>>` so it can be released and
reacquired across the drop-to-zero / rise-from-zero cycles that
`session_refs` already implements. Storing the path alongside the
handle enables a debug assertion that the process never drifts
between session roots.

`ClientMarker` and its serde plumbing are removed; the marker file is
now empty, its existence and lock state carrying the signal.

Full workspace wall-clock after A+B+C: 13.3–13.6 s, down from 20–25 s
on HEAD before the fix and comparable to the 14 s Friday baseline
despite the intervening +85 tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:33:53 -04:00
Bryan Helmkamp
3885b67515
test(harness): amortize stale-session reap across tests
reap_stale_session_roots cleans up session roots left over by prior
nextest runs that crashed. TestContext::new called it twice per test
(once per SessionMode). Under a 721-test fabro-cli suite that was
~1400 reap calls where one would do, accounting for several seconds of
per-suite overhead even after the process_running regression was
reverted.

Gate each call behind a per-process OnceLock so at most one reap runs
per SessionMode per test binary. The reap itself (and its internal
per-root session lock, which iterates candidate roots) is unchanged;
we just stop re-entering it for every TestContext::new.

Measured on fabro-cli after this change:
  reap_nextest probes: 356 calls, 302 under 1 ms (OnceLock fast path),
  sum 1.4 s (down from Friday's 3.1 s and HEAD's 88.6 s before the
  process_running revert).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:27:17 -04:00