Commit graph

92 commits

Author SHA1 Message Date
Bryan Helmkamp
b96647314c
docs(deployment): split into local vs self-hosted, drop Render/Fly/DO
Reframe the deployment docs around the actual product story: Fabro
runs as a server, and the only deployment question is where that
server runs (laptop vs self-hosted Docker). Drop the Render, Fly.io,
and DigitalOcean guides and their config files; keep Railway as the
managed shortcut.

- New: administration/deployment.mdx (overview, two-mode framing)
- New: administration/self-host-docker.mdx (compose-first how-to)
- Move: administration/deploy-server.mdx -> reference/server-operations.mdx
  (it was operational reference, not deploy guidance)
- Delete: deploy-render.mdx, deploy-fly-io.mdx, deploy-digital-ocean.mdx
- Delete: render.yaml, fly.toml, railway.toml, Dockerfile.deploy
- docker-compose.yaml: load .env if present so users can drive the
  stack from a single env file end-to-end
- Update internal links and the docs-test that pinned the old path
2026-05-01 09:26:35 -04:00
Bryan Helmkamp
2abf50f0a5
refactor(server): project precise stage states 2026-04-30 06:26:45 -04:00
Bryan Helmkamp
a6a11cdd82
chore(run-files): fix pre-existing clippy lints
- classify_section now returns FileDiffChangeKind directly (unnecessary_wraps)
- collapse nested Some(...) or-pattern into single arm (unnested_or_patterns)
- replace .unwrap() with .expect() in append_completed_run_with_final_patch test helper (unwrap_used)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:16:40 -04:00
Bryan Helmkamp
6156f65799
fix(run-files): normalize degraded file diffs
Return degraded run files with the same FileDiff[] shape as live responses, using nullable contents and per-file unified patches so the web sidebar and deep links work consistently.
2026-04-29 13:00:55 -04:00
Bryan Helmkamp
9ad47990f4
fix(web): include server URL in auth quick start
Expose the configured server.web.url in system info so the empty runs quick start can show a runnable fabro auth login command instead of a placeholder.
2026-04-29 07:45:17 -04:00
Bryan Helmkamp
2460ffc37a
feat(install): add sandbox provider step to web install wizard
Operators choose Docker (default, zero-config) or Daytona (validated
via Daytona SDK) during browser install. Selection is captured in
settings.toml under [run.sandbox] -- explicitly even for Docker, so the
choice is locked in. Daytona keys land in the vault as DAYTONA_API_KEY
(Environment secret). Step always runs after object_store and before
the LLM step.

Server adds POST /install/sandbox/test (validates Daytona key via
client.list) and PUT /install/sandbox; both reuse the install-token
auth and InstallSecret redaction patterns established by object-store.
A resolve_install_sandbox_state helper preserves a saved Daytona key
when the operator revisits the step without re-entering it. The
in-memory api_key is dropped from PendingInstall after finish, matching
the manual_credentials cleanup for S3 access keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 14:59:17 -07:00
Bryan Helmkamp
f0d34f6e87
Make Rust tests independent of SPA assets 2026-04-27 08:17:37 -07:00
Bryan Helmkamp
54c5f30586
docs: move published docs under docs/public
Relocate the Mintlify tree to docs/public and consolidate internal docs under docs/internal. Update build scripts, tests, CI filters, README references, and local docs skills to follow the new layout.
2026-04-26 21:19:46 -04:00
Bryan Helmkamp
c28b040c6f
fix(install): reject wildcard public URLs
Normalize bind-address wildcards before presenting install URLs, reject wildcard public origins at CLI and server install boundaries, and surface recovery guidance in the installer and doctor output.
2026-04-25 18:58:00 -04:00
Bryan Helmkamp
f537222372
feat(auth): store dev tokens in auth store
Persist dev-token credentials in auth.json alongside OAuth entries so CLI targets resolve credentials consistently across TCP and Unix socket flows.

Move install-time token minting to runtime storage, add auth login --dev-token, and refresh the embedded SPA after updating the stale dev-token hint.
2026-04-24 16:02:36 -04:00
Bryan Helmkamp
64dbdf2500
fix(ci): resolve workspace test and lint regressions 2026-04-23 20:49:30 -04:00
Bryan Helmkamp
8f47bc9317
migrate server tests off raw settings layers 2026-04-23 18:31:33 -04:00
Bryan Helmkamp
2a60b6c3dc
add workflow settings builder toml entrypoint 2026-04-23 15:12:48 -04:00
Bryan Helmkamp
9220af6e80
migrate remaining run settings consumers to dense snapshots 2026-04-23 11:55:12 -04:00
Bryan Helmkamp
b2bcf0d5a8
refactor settings builders and dense run snapshots 2026-04-23 11:35:21 -04:00
Bryan Helmkamp
37cd8ff45f
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-server/src/lib.rs
#	lib/crates/fabro-server/src/serve.rs
#	lib/crates/fabro-server/src/server.rs
2026-04-23 08:34:44 -04:00
Bryan Helmkamp
e6e091fe8e
refactor(server): lock down server secrets 2026-04-23 07:15:27 -04:00
Bryan Helmkamp
fa62da5d9d
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-api/build.rs
#	lib/crates/fabro-server/src/install.rs
#	lib/crates/fabro-server/src/serve.rs
#	lib/packages/fabro-api-client/src/.openapi-generator/FILES
#	lib/packages/fabro-api-client/src/models/index.ts
2026-04-22 21:18:50 -04:00
Bryan Helmkamp
4bf0c40319
feat(install): add object-store step to web install 2026-04-22 19:31:09 -04:00
Bryan Helmkamp
ebb8bf7add
refactor settings API entrypoints 2026-04-22 18:58:47 -04:00
Bryan Helmkamp
911f957200
refactor(config): move Bind and ServerDaemon into fabro-config
`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>
2026-04-22 16:44:59 -04:00
Bryan Helmkamp
acc6dc49bf
Merge remote-tracking branch 'origin/main' 2026-04-22 16:25:53 -04:00
Bryan Helmkamp
7a58ab4e2b
refactor(server): unify daemon runtime metadata 2026-04-22 16:07:52 -04:00
Bryan Helmkamp
27cc1bb6c2
test(server): make global attach filter deterministic 2026-04-22 16:04:33 -04:00
Bryan Helmkamp
702c18cbb4
Merge remote-tracking branch 'origin/main' 2026-04-22 00:46:59 -04:00
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
3f6cc1f1ba
Merge remote-tracking branch 'origin/main' 2026-04-21 14:47:23 -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
a3e4e2bd3d
Merge remote-tracking branch 'origin/main' 2026-04-21 09:40:09 -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
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
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
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
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
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
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
2ec1fb2987
test(unwrap): clean server integration helpers 2026-04-19 21:06:36 -04:00
Bryan Helmkamp
cd9ff8fafe
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-server/src/server.rs
2026-04-19 19:54:30 -04:00
Bryan Helmkamp
64cf35cc7d
fix(lint): restore clippy cleanliness after async ripple from std::fs work
Two follow-ups the workspace lint now catches:

- fabro-server tests/it/api/install.rs: a newer install-router integration
  test was missing the `.await` after `build_install_router(...)` -- the
  fn became async when the devcontainer/install-mode resolver was
  converted to tokio::fs in commit 19939c5f0.
- fabro-cli main.rs: add #[expect(clippy::disallowed_methods)] to the
  #[cfg(test)] module whose write_test_settings helper uses sync
  std::fs::write to stage CLI settings fixtures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 19:41:47 -04:00
Bryan Helmkamp
a2b77b1a30
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-cli/src/commands/run/rewind.rs
#	lib/crates/fabro-cli/src/logging.rs
2026-04-19 19:33:40 -04:00
Bryan Helmkamp
1f6eb5b336
Merge origin/main into Run Files Changed feature branch
Integrates 39 commits from origin/main (archive/unarchive feature, UI
unification, theme/light-mode polish, Settings nav promotion, server
and CLI hardening).

Conflict resolutions:

- apps/fabro-web/app/routes/run-detail.tsx: origin removed the
  `broken` field from the tab config; local added the Files Changed
  tab. Kept the Files Changed tab, dropped the broken field per
  origin's shape.
- lib/crates/fabro-store/src/run_state.rs: both sides added tests
  in the same region. Kept local's two final_patch tests and all
  four of origin's archive/unarchive tests.
- lib/crates/fabro-spa/assets/: embedded SPA bundle rebuilt from
  the merged web source.
- lib/crates/fabro-workflow/src/operations/archive.rs: origin's new
  archive tests construct Event::WorkflowRunFailed{..}; added the
  final_patch: None field that local's lifecycle change introduced.

Workspace verification after merge: 4247 Rust tests + 95 web tests
all pass; clippy clean; fmt clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 19:30:36 -04:00
Bryan Helmkamp
83a21e5d37
test(server): Run Files HTTP integration suite (P2-9)
Adds lib/crates/fabro-server/tests/it/api/run_files.rs covering the
HTTP-level plumbing branches of GET /api/v1/runs/{id}/files:

- Invalid run_id path returns 400
- Unknown run returns 404 (IDOR-safe; same status as missing-run case)
- Malformed from_sha / to_sha query params return 400 before any work
- Non-default from_sha value returns 400 even when hex-well-formed
  (v1 reserves the parameter for a future version)
- Submitted run with no sandbox record returns empty envelope
- Demo mode (X-Fabro-Demo: 1) returns the 3-entry fixture without
  touching the run store, with at least one populated-content entry
- Response envelope shape matches PaginatedRunFileList contract:
  data: FileDiff[], meta: { truncated, total_changed, ... } with
  correct field types

Sandbox-path happy case (live diff) and degraded-fallback scenarios
stay covered by unit tests on stitch_file_diff, build_fallback_response,
and the sandbox_git helpers, since integration-level scheduler setup
for terminal-run tests is flaky without broader harness scaffolding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:40:14 -04:00
Bryan Helmkamp
19939c5f07
lint(clippy): disallow blocking std::fs on Tokio paths
Phase 2/3 of the std::fs lint initiative (Phase 1 refactors landed in
commit 9d1c0d98c).

clippy.toml additions (appended to disallowed-methods):
  std::fs::read, read_to_string, write, read_dir, copy, canonicalize
  std::fs::File::open, File::create, File::create_new
  std::fs::OpenOptions::open

File::options was deliberately excluded — it returns an OpenOptions
builder with no syscall. OpenOptions::open is where the block happens.
Non-blocking std::fs items (metadata, exists, create_dir_all, remove_*,
rename, and all std::fs types) remain legal.

Annotation policy (per updated plan):
  - Mixed async/sync production source: function- or statement-scoped
    #[expect(...)] so future accidental Tokio-path regressions in the
    same file still fire.
  - Fully-sync production source, test modules, integration tests,
    build.rs: file-level #![expect(...)].
  - Every #[expect] has a specific reason identifying the sync context.

Annotations added in ~90 files across the workspace. Notable narrow
placements: fabro-server server.rs current_server_target,
build_disk_usage_response, create_test_app_state_with_session_key;
fabro-server install.rs read_to_string rollback snapshot;
fabro-sandbox local.rs list_recursive; fabro-agent cli.rs FOLLOW-UP on
the JSON-stdout writer; fabro-llm providers/common.rs FOLLOW-UP for
load_file_as_base64 (7 translator call sites; revisit if file:// URL
usage grows).

build.rs blanket allows: fabro-api/build.rs, fabro-util/build.rs.

Pre-existing unrelated nightly-clippy warnings fixed under scope:
fabro-sandbox sandbox_spec.rs (unused_imports, unused_async),
reconnect.rs (unused_variables, unused_async).

Verified: cargo +nightly-2026-04-14 clippy --workspace --all-targets
-- -D warnings passes; fmt clean; 4129/4131 tests pass (two known
flakes under parallel nextest load, both pass individually and are
unrelated to this change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:22:21 -04:00
Bryan Helmkamp
9d1c0d98c7
fix: migrate blocking fs calls off Tokio paths
Phase 1 of the std::fs lint initiative. Refactors blocking std::fs entry
points that ran inside async contexts. Caller chains either converted to
async (using tokio::fs) or wrapped in tokio::task::spawn_blocking where
sync callers were already natural (Command builders, flock semantics).

HIGH (per-request async hot paths):
  - fabro-sandbox local.rs: wrap recursive std::fs::read_dir traversal in
    spawn_blocking. Fixes /api/runs/{id}/files stalling workers under
    concurrent or deep listings.
  - fabro-server static_files.rs: convert serve/serve_install/serve_with_mode
    and the static-asset load chain to async; use tokio::fs::read for the
    debug-only disk fallback. Cascades through install.rs build_install_router
    (now async) and ~17 test call sites.

LOW (async but not per-request):
  - fabro-workflow artifact.rs: sync_artifacts_to_env, offload_large_values
    → tokio::fs::read_to_string.
  - fabro-workflow artifact_snapshot.rs: compute_artifact_info → async +
    tokio::fs::read.
  - fabro-server ip_allowlist.rs: load_cache and store_cache → async +
    tokio::fs::{read,write,create_dir_all}.
  - fabro-server server.rs: wrap worker_command invocation in spawn_blocking
    at the async boundary in execute_run_subprocess; keep the sync
    worker_command + current_server_target signatures intact.
  - fabro-cli server/start.rs: wrap the OpenOptions::open call in
    acquire_lock in spawn_blocking; file-lock semantics require a real
    std::fs::File, and the flock polling loop stays async with time::sleep.

Deferred:
  - fabro-llm load_file_as_base64 (file:// attachment loader): 7 call sites
    across 4 providers, each inside sync translators. Left for Phase 3
    annotation with a FOLLOW-UP marker; file:// URLs are rare in practice.

Verified: workspace builds, 4131 tests pass, 182 skipped. The lint that
enforces this discipline lands in the next commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:00:56 -04:00
Bryan Helmkamp
54ddaa2cee
feat(install): redesign web wizard and scope dev token to PAT installs
Redesign the install wizard for clarity:
- swap the sidebar layout for a centered column and a horizontal stepper
- make completed/current stepper entries clickable links
- reorder steps so Server URL precedes LLMs
- use env-var placeholders (ANTHROPIC_API_KEY, etc.) with
  per-provider "Where do I get this?" disclosures
- replace the readonly "Validated username" input with a success pill
- drop the GitHub App name field (GitHub confirms the name anyway)
- re-label the GitHub App option and split review rows by strategy
- add a copy action to the Server URL on the review screen

Scope the dev token to PAT installs:
- only generate the dev token, write its files, and set FABRO_DEV_TOKEN
  inside the GithubInstallState::Token arm
- mark dev_token optional on InstallFinishResponse in the OpenAPI spec
- hide the Development token card on /install/finishing when absent
- add app_install_finish_omits_dev_token_and_does_not_write_it test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:11:02 -04:00
Bryan Helmkamp
acb6b3f9d6
refactor(install): tag GithubAppOwner with discriminated object shape
The install GitHub App manifest shape encoded owner as `"personal"` or
`"org:<slug>"` - a magic string parsed in install-app.tsx, built by
install-api.ts, and reparsed server-side. Replace with a tagged object
`{ kind: "personal" } | { kind: "org", slug }` in the OpenAPI spec, the
progenitor-generated Rust types, and the frontend.

Server-side, the internal `GitHubAppOwner` enum keeps its semantic
shape but gains a `TryFrom<GithubAppOwnerInput>` conversion and emits
the tagged JSON via `as_session_value`.

Frontend drops `buildGithubOwnerValue` in favor of
`buildInstallGithubAppOwner`, and the ready-screen renders the owner
through a small helper instead of string concatenation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:48:31 -04:00
Bryan Helmkamp
ad7fdc8d13
refactor(install): return spec-conformant ApiError shape
Install handlers returned `{"error": "..."}` while the OpenAPI paths
referenced the repo-wide `ErrorResponse` schema
(`{"errors":[{status,title,detail}]}`). Funnel the install helper through
`ApiError::into_response`, switch the invalid-token 401 and the
persistence-failure INTERNAL_SERVER_ERROR to the same shape, and update
the TS `readInstallError` helper + test fixtures to read
`body.errors[0].detail`.

The install-finish failure path still carries `leftover_env_keys`
alongside the error envelope so the rollback integration tests retain
their diagnostic field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:43:57 -04:00
Bryan Helmkamp
dd4e467bfc
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-server/tests/it/api/mod.rs
2026-04-19 13:36:54 -04:00