Commit graph

2582 commits

Author SHA1 Message Date
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
Bryan Helmkamp
da87f978cd
fix(proc): revert process_running to cheap kill(0) probe
Commit 1ed8e6cbd changed process_running(pid) to shell out to `ps` on
every call to distinguish running processes from zombies. That cost
~2 ms per invocation on macOS (fork + exec + wait), and the test
harness calls process_running O(tests × markers) times under session
flock contention. Across a `cargo nextest run -p fabro-cli` that added
up to ~90 s of suite time, and the zombie-aware semantics turned out
to have no production caller on Unix (the server's worker-termination
loop uses process_group_alive; the CLI stop/status paths don't need
zombie detection for a daemon that reparents to init).

Restore the pre-1ed8e6cbd body: process_running is now a straight
kill(pid, 0) via process_exists on Unix, true on non-unix. Delete
unix_process_state (the `ps` helper) and its zombie regression test,
since they describe behavior we're rolling back. process_group_alive
and its tests are unchanged.

Measured on this branch against baseline db953c838:
  reap_nextest p50:   172 ms -> 0.3 ms
  TestContext:🆕  316 ms mean -> 15 ms mean

If a future caller genuinely needs zombie-aware semantics, add it back
alongside that caller with a benchmark in context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:24:42 -04:00
Bryan Helmkamp
24e7e5af83
test(harness): add FABRO_TEST_PROBE_LOG timing probes
Gated test-harness diagnostic. Writes one tab-separated line per phase
of TestContext::new to the path named by FABRO_TEST_PROBE_LOG, using an
O_APPEND+single-write-per-line pattern so concurrent test processes do
not interleave. Disabled when the env var is unset.

Used to isolate the source of a recent test-suite slowdown; removed
again at the end of the same change set once verification is done.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:24:26 -04:00
Bryan Helmkamp
c6cafceae0
test: de-flake pr_list and full_http_lifecycle_cancel
Two CLI/server tests racing against peer state on the shared fabro server
session, surfaced by running the default nextest profile 20 times.

pr_list_missing_github_credentials_errors depended on an empty shared
store; if pr_view_reads_pull_request_from_store_without_pull_request_json
ran first it left a PR record behind and this test hit the
credentials-required branch instead of "No pull requests found." The
snapshot captured the empty path, but the test name promises the error
path. Seed a PullRequestCreated event against the test's own run so the
store is guaranteed non-empty and the credentials-required error fires
deterministically.

full_http_lifecycle_cancel asserted that the cancel response body's
pending_control == "cancel", but that field is re-read from the store
projection after the worker has been signaled. The worker is sitting at
a human gate; on hot CI it can emit a clearing event before the handler
re-reads the projection, yielding a legitimate null. Relax the
assertion to accept "cancel" or null; durable convergence to
failed/cancelled is still asserted below.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:04:08 -04:00
Bryan Helmkamp
3e881d073b
refactor(cli): rename ServerStoreClient to Client
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>
2026-04-20 14:54:33 -04:00
Bryan Helmkamp
9db635422b
refactor(cli): tidy socket autostart helper
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>
2026-04-20 14:51:59 -04:00
Bryan Helmkamp
8f037b68b0
fix(cli): restore configured socket autostart
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.
2026-04-20 14:36:54 -04:00
Bryan Helmkamp
42aeffeb6b
Merge remote-tracking branch 'origin/main' into merge-origin-main-20260420b
# Conflicts:
#	lib/crates/fabro-cli/src/commands/doctor.rs
#	lib/crates/fabro-cli/src/commands/model.rs
#	lib/crates/fabro-cli/src/commands/provider/login.rs
#	lib/crates/fabro-cli/src/commands/repo/init.rs
#	lib/crates/fabro-cli/src/commands/secret/list.rs
#	lib/crates/fabro-cli/src/commands/secret/rm.rs
#	lib/crates/fabro-cli/src/commands/secret/set.rs
#	lib/crates/fabro-cli/src/commands/system/df.rs
#	lib/crates/fabro-cli/src/commands/system/events.rs
#	lib/crates/fabro-cli/src/commands/system/info.rs
#	lib/crates/fabro-cli/src/commands/system/prune.rs
#	lib/crates/fabro-cli/src/commands/version.rs
#	lib/crates/fabro-cli/src/server_client.rs
#	lib/crates/fabro-server/src/csp.rs
2026-04-20 14:25:20 -04:00
Bryan Helmkamp
41135dd9d1
fix(cli): remove explicit remote same-host auth fallbacks 2026-04-20 14:17:01 -04:00
Bryan Helmkamp
ed6fecfc5a
fix(auth): harden loopback checks and align CSP tests
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.
2026-04-20 13:30:45 -04:00
Bryan Helmkamp
323c797e0f
refactor(auth): simplify CLI auth plumbing after code review
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>
2026-04-20 11:13:37 -04:00
Bryan Helmkamp
4c35c4b69f
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-server/src/jwt_auth.rs
#	lib/crates/fabro-server/src/serve.rs
#	lib/crates/fabro-server/src/server.rs
#	lib/crates/fabro-server/src/web_auth.rs
2026-04-20 09:47:47 -04:00
Bryan Helmkamp
21c5b7c21b
test(server): drop stale inline-script-hash CSP guards
After the dark-only refactor the embedded SPA no longer contains an
inline theme-bootstrap script, so the two tests that asserted "embedded
index has >= 1 inline <script>" and "CSP header contains 'sha256-'" now
fail. The guards existed to catch accidental loss of the bootstrap
script; that loss was intentional. The rest of the CSP machinery (hash
extraction, policy assembly, external-script handling) is still
exercised by the remaining unit tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 09:44:47 -04:00
Bryan Helmkamp
5e09aa8176
Merge remote-tracking branch 'origin/main' 2026-04-20 09:40:17 -04:00
Bryan Helmkamp
9a4b812e2d
refactor(cli): tighten Client facade and parallelize system df
- 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>
2026-04-20 09:40:11 -04:00
Bryan Helmkamp
153fcf20b5
fix(auth): require browser confirmation for CLI login
Harden the CLI browser auth flow by moving auth-code issuance behind
an explicit same-origin confirmation step, and update the real-browser
test harness to submit the confirmation page.
2026-04-20 09:40:05 -04:00
Bryan Helmkamp
afd6994b30
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-spa/assets/assets/entry-evtq2sja.js
#	lib/crates/fabro-spa/assets/index.html
2026-04-20 09:33:08 -04:00
Bryan Helmkamp
837406f27c
refactor(web): drop vestigial graphTheme aliases and dead code
Removes unused annotateRunningNodes from run-graph and inlines the
const gt = graphTheme / const theme = graphTheme shims left over from
the dark-mode-only refactor. Template strings reference graphTheme
directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 09:32:45 -04:00
Bryan Helmkamp
c9ffc96b5f
refactor(web): dedupe cancel toast through unified lifecycle helper
Extends handleLifecycleToastResult to cover the cancel intent and
switches cancel's effect onto the shared helper. lastProcessed is now
keyed per intent so the three effects don't clobber each other's dedup
state, and cancel picks up the same replay guard that archive and
unarchive already had.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 09:30:30 -04:00
Bryan Helmkamp
e12c3632e2
Merge remote-tracking branch 'origin/main' 2026-04-20 09:29:03 -04:00
Bryan Helmkamp
63cd749dcb
refactor(cli): hide ApiClient behind Client facade
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.
2026-04-20 09:29:02 -04:00
Bryan Helmkamp
c769b2a30c
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	apps/fabro-web/app/routes/run-files.tsx
#	lib/crates/fabro-spa/assets/assets/app.css
#	lib/crates/fabro-spa/assets/index.html
2026-04-20 09:28:27 -04:00
Bryan Helmkamp
f33d8df98a
fix(proc): silence clippy in signal.rs test module
Adds expect(disallowed_types) at the tests module for the intentional
sync BufReader usage in the zombie-process-group helper, and drops the
absolute-path call site by bringing pre_exec_setpgid into scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 09:27:55 -04:00
Bryan Helmkamp
5370a64f96
refactor(web): drop light mode, go dark-only
Removes the light/dark toggle infrastructure in favor of a single dark
theme. Deletes the theme context, boot script, light-mode CSS overrides,
logotype-light asset, and the pierre-light diff theme. Collapses
graph-theme into a single constant. Adds scheme-only-dark on <html> so
native controls and the server-injected Graphviz @media query render
dark.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 09:27:08 -04:00
Bryan Helmkamp
c5de844d66
refactor(web): unify archive/unarchive toast handler
Merges handleArchiveToastResult and handleUnarchiveToastResult into a
single helper. Replaces the content-hash dedup key with object identity
on fetcher.data and collapses the two "last key" fields into one
lastProcessed. Tests now import the exported helper directly instead of
casting through Record<string, unknown>.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 09:21:33 -04:00
Bryan Helmkamp
e895913d10
Merge remote-tracking branch 'origin/main' 2026-04-20 09:12:41 -04:00
Bryan Helmkamp
a0098bd961
fix(web): dedupe archive lifecycle toasts 2026-04-20 09:12:37 -04:00
Bryan Helmkamp
1ed8e6cbd5
fix(server): treat zombie processes as stopped
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.
2026-04-20 09:05:29 -04:00
Bryan Helmkamp
6ac8bf6d81
fix(auth): harden CLI auth review gaps
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.
2026-04-20 09:03:22 -04:00
Bryan Helmkamp
aa8cdd6986
refactor(cli): rename ServerStoreClient -> Client, fabro_api::Client -> ApiClient
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>
2026-04-20 08:56:09 -04:00
Bryan Helmkamp
2b0e3a743d
Merge remote-tracking branch 'origin/main' 2026-04-20 08:45:24 -04:00
Bryan Helmkamp
8259b7445e
feat(web): add run lifecycle actions
Expose cancel, archive, and unarchive from the run detail view,
surface blocked-question context, and route run-detail and run-files
notifications through a single shared toast provider.

This also refreshes the embedded SPA bundle and marks the lifecycle
actions plan complete.
2026-04-20 08:43:30 -04:00
Bryan Helmkamp
277fe8ca5f
refactor(server): relocate github helpers and use macros for test context
- Move the GitHub App webhook config update to fabro-github as
  update_app_webhook_config, matching the crate's existing HttpClient +
  Result<_, String> conventions. Server-side callers go through the new
  symbol.
- Add Bind::tcp_port() on the enum itself and drop the free function.
- Collapse the six near-identical "webhook strategy configured but ...;
  skipping webhook startup" warn branches into resolve_webhook_preconditions
  returning a Ready/Skip enum, with one warn! at the call site.
- Replace the per-file test-helper wrappers (assert_status, checked_response,
  response_json, response_bytes) with local macro_rules! macros so
  file!()/line!() expand at the caller. Panic context now identifies the
  failing assertion's source line instead of the wrapper's definition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 08:39:12 -04:00
Bryan Helmkamp
86bb88cdca
refactor(server): tidy webhook wiring and test-helper context
- Distinguish the two GitHub webhook auth-failure warn messages (missing
  signature header vs. HMAC mismatch) so logs can tell them apart.
- Route update_github_app_webhook through fabro_github::github_api_base_url()
  so GITHUB_BASE_URL overrides the webhook config endpoint too.
- Drop a narrative shutdown comment that restated the next two lines.
- Replace concat!(file!(), ":", line!()) inside local test-helper wrappers;
  those macros expand at the wrapper definition site, so every panic
  reported the same phantom location. Pass the wrapper name instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 08:27:50 -04:00
Bryan Helmkamp
bd0a03701a
test(auth): extract shared CLI auth harness
Move the real CLI auth integration harness into shared test support so
scenario/auth.rs keeps only the scenario cases and mock-browser helpers.
This makes the real-server auth setup reusable for future CLI integration
tests without duplicating the bootstrap code.
2026-04-20 08:25:29 -04:00
Bryan Helmkamp
6829935649
test(http): satisfy clippy in response assert helpers
Align the shared HTTP assertion helper with repo lint rules so the
full workspace verification stays green after the new diagnostics
coverage landed.
2026-04-20 08:18:12 -04:00
Bryan Helmkamp
730c752f4f
test(auth): cover detached runs in CLI auth flow 2026-04-20 08:13:53 -04:00
Bryan Helmkamp
8e3f1524f8
Merge remote-tracking branch 'origin/main' 2026-04-20 08:07:03 -04:00
Bryan Helmkamp
9c3c66c59a
test(http): improve HTTP test failure diagnostics
Add shared axum/reqwest response assertion helpers in fabro-test,
migrate the Rust HTTP test surface to use them, and document the
new rule in the testing strategy.
2026-04-20 08:06:14 -04:00
Bryan Helmkamp
f2a512185e
test(auth): add remaining CLI auth integration coverage 2026-04-20 08:05:22 -04:00
Bryan Helmkamp
773307eec0
chore(spa): refresh embedded bundle to match bun 1.3.13
CI's TypeScript Build job upgraded bun to 1.3.13 (via setup-bun@v2.2.0
pulling the latest release), which produces a different content-hashed
entry CSS than the bundle committed under bun 1.3.10. The drift was
caught by the widened path filter in c8b807f30 and failed the
git diff --exit-code check on lib/crates/fabro-spa/assets.

Rebuilds with bun 1.3.13 so the embedded SPA matches the build CI
reproduces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 07:48:33 -04:00
Bryan Helmkamp
61bce5450c
feat(auth): add CLI token refresh and auth scenarios
Retry CLI API calls once after a 401 access_token_expired response by
refreshing the stored OAuth session and rebuilding the generated API
client. Clear local auth when the refresh chain is expired or revoked,
and add end-to-end CLI scenarios covering login, authenticated use,
refresh, and logout.
2026-04-20 07:39:02 -04:00
Bryan Helmkamp
1ef10b46ce
refactor(server): simplify github webhook wiring
- Capture webhook secret at route mount time via Arc<[u8]> router
  state so the handler drops its per-request server_secret lookup
  and the dead NOT_FOUND fallback.
- Extract WEBHOOK_ROUTE and WEBHOOK_SECRET_ENV constants; apply
  across serve.rs, server.rs, and TailscaleFunnelManager so the
  mounted route and the URLs pushed to GitHub cannot drift.
- Flatten the seven-level nested webhook startup match in serve.rs
  into a single start_webhook_strategy helper with early returns,
  short-circuiting when the secret is absent and replacing the
  server.api.url .expect with a propagated error.
- Share compute_signature and a new read_repo_file helper across
  tests; delete the duplicated webhook_signature, TestHmacSha256,
  and read_doc/repo_root copies.
- Replace the nested for-loops in the new webhook auth tests with
  five flat #[tokio::test] cases per CLAUDE.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 07:32:38 -04:00
Bryan Helmkamp
0686b82bce
fix(api): correct GitHub webhook OpenAPI contract
Model the GitHub webhook request body as JSON so the generated TypeScript
client exposes a coherent request shape, and add explicit conformance
coverage for the secret-gated webhook route.
2026-04-20 07:13:49 -04:00
Bryan Helmkamp
9c68c57bcc
feat(auth): add CLI GitHub login and logout flow
Add the server-side CLI OAuth endpoints and token persistence needed to
mint JWT access tokens and rotating refresh tokens from the existing
GitHub web auth flow.

Add CLI auth storage plus `fabro auth login`, `logout`, and `status`, and
prefer stored OAuth access tokens when building target clients.
2026-04-20 07:12:52 -04:00
Bryan Helmkamp
c8cc9f20b0
feat(server): add explicit GitHub webhook strategies
Move GitHub webhook intake onto the main API router, add explicit
server_url and tailscale_funnel strategies, and validate strategy
requirements at config resolution. This also updates the API contract,
generated client, and operator docs to match the new webhook model.
2026-04-19 22:53:22 -04:00
Bryan Helmkamp
aa3903f8a3
test(unwrap): clean cli fixture helpers 2026-04-19 21:09:28 -04:00
Bryan Helmkamp
2ec1fb2987
test(unwrap): clean server integration helpers 2026-04-19 21:06:36 -04:00
Bryan Helmkamp
0b91c71514
test(unwrap): clean shared support and workflow fixtures 2026-04-19 21:01:25 -04:00