Commit graph

29 commits

Author SHA1 Message Date
Bryan Helmkamp
9837a08929
refactor(install): share persistence pipeline (#332)
## Summary

Unifies installer persistence so CLI and web install paths share the
same file/env/vault primitives, while preserving the CLI's server-API
secret persistence and auth bootstrap behavior.

## What Changed

- Added shared `fabro-install` config writers for installer-owned tagged
enum tables, replacing `server.listen` and `cli.target` atomically so
stale variant fields cannot survive.
- Added `InstallPersistencePlan` for disk-backed settings, server env,
and vault writes/removals with the existing rollback semantics for
settings and vault failures.
- Refactored `fabro install`, `fabro install github`, and
`/install/finish` to use the shared persistence plan where their disk
behavior overlaps.
- Preserved full-install ordering: settings/env first, workflow-visible
secrets through the server API second, and CLI `auth.json` only after
API secret persistence succeeds.
- Preserved web installer failure response fields for leftover and
removed env keys, plus the post-success finish hook/shutdown behavior.

## Test Plan

- `cargo nextest run -p fabro-install`
- `cargo nextest run -p fabro-cli commands::install::tests`
- `cargo nextest run -p fabro-cli --test it cmd::install`
- `cargo nextest run -p fabro-server --features test-support --test it
api::install`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `git diff --check`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
2026-05-21 08:04:04 -04:00
Bryan Helmkamp
cd74013d06
refactor(auth): split credential sources and vault schemas (#306)
## Summary

Compared with `origin/main`, this PR splits credential storage and
credential references into explicit types. Vault secrets now distinguish
`token`, `oauth`, and `file` payloads, while runtime/model configuration
points to credentials through explicit `env:<NAME>` and `vault:<NAME>`
source refs.

## Changes

- Replaces the old `environment`/`credential` secret schema vocabulary
with `token`/`oauth`/`file` across OpenAPI, Rust API tests, generated
TypeScript models, CLI/docs references, and the changelog.
- Updates auth resolution, refresh, provider strategies, workflow LLM
handling, server diagnostics, install flows, run manifests, and secret
handlers to consume typed vault entries and explicit credential sources.
- Updates provider catalog TOMLs and config parsing so provider auth and
extra headers use `vault` refs instead of ambiguous `credential` refs.
- Updates CLI install/login/run/secret paths and integration tests to
write and read the new credential shapes.
- Removes the temporary legacy vault migration and empty-vault fallback,
then centralizes provider vault secret-name lookup and Codex API
credential shaping.

## Verification

- `cargo +nightly-2026-04-14 fmt --all`
- `cargo +nightly-2026-04-14 clippy -p fabro-auth -p fabro-model -p
fabro-config -p fabro-vault -p fabro-server -p fabro-cli --all-targets
-- -D warnings`
- `ulimit -n 4096 && cargo nextest run -p fabro-auth -p fabro-model -p
fabro-config -p fabro-vault -p fabro-server -p fabro-cli` (`1938`
passed, `35` skipped)
2026-05-18 11:07:42 -04:00
Bryan Helmkamp
302e2445b4
refactor(model): move provider facts into catalog (#298)
## Summary

Moves provider-specific facts out of `AdapterKind` metadata and into
provider catalog data, leaving adapters responsible for runtime protocol
behavior. This makes providers that share an adapter mostly TOML-driven
while still surfacing adapter construction failures during readiness
checks.

## What Changed

- Provider TOML now owns auth mode, API-key/header policy, billing
policy, agent profile, base URLs/env overrides, extra headers, and probe
markers.
- Auth, install, config, diagnostics, and server flows resolve provider
credentials from catalog auth config, including API-key, header-only,
and no-auth providers.
- LLM client registration now reports adapter construction failures,
validates final adapter requests before HTTP dispatch, and preserves
custom primary auth headers.
- Billing and docs now use provider-owned billing policy instead of
adapter metadata, and the old adapter metadata surface is removed.

## Reviewer Notes

OpenAI-compatible `base_url` validation now happens during
adapter/client registration rather than catalog build. That keeps
catalog parsing adapter-agnostic while still letting readiness and model
listing reflect providers that cannot register.

## Verification

- `cargo check -p fabro-model -p fabro-auth -p fabro-llm -p fabro-server
-p fabro-cli`
- `cargo nextest run -p fabro-llm -- adapter_registry`
- `cargo nextest run -p fabro-model -- catalog`
- `cargo nextest run -p fabro-auth -- api_key`
- `cargo nextest run -p fabro-server -- install`
- `cargo +nightly-2026-04-14 fmt --check --all`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:59:08 -04:00
Bryan Helmkamp
87950295bd
refactor(llm): split provider identity from adapters (#280)
## Summary

This PR separates provider identity from adapter behavior across the LLM
stack. Provider IDs now represent catalog rows and provider metadata,
while adapter/profile routing owns protocol behavior for Anthropic,
OpenAI, Gemini, and OpenAI-compatible providers.

## Changes

- Replace the shared `fabro_model::Provider` enum with open-ended
`ProviderId` catalog identity and typed `AdapterKind` metadata.
- Route auth, CLI, ACP, workflow, memory selection, profile
construction, and LLM client registration through catalog provider rows
instead of provider-ID fallbacks.
- Move API-key URL/header/env metadata into provider catalog/auth flows
and require configured provider rows for credential-backed clients.
- Simplify billing to `algorithm`-tagged OpenAI, Anthropic, and Gemini
shapes; OpenAI-compatible adapters bill through the OpenAI algorithm.
- Remove greenfield compatibility paths for old provider aliases, legacy
provider-tagged billing JSON, and the `openai_compatible`
pseudo-provider env fallback.
- Update fixtures and tests to exercise catalog-driven
Kimi/Zai/Minimax/Inception/custom OpenAI-compatible routing.

## Validation

- `cargo test --no-run -p fabro-model -p fabro-auth -p fabro-agent -p
fabro-workflow -p fabro-server -p fabro-llm -p fabro-api -p fabro-cli -p
fabro-store -p fabro-static`
- `cargo nextest run -p fabro-model -p fabro-auth -p fabro-agent -p
fabro-workflow -p fabro-server --no-fail-fast`
- `cargo nextest run -p fabro-llm -p fabro-api -p fabro-cli -p
fabro-store -p fabro-static --no-fail-fast`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `git diff --check`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
2026-05-16 13:13:41 -04:00
Bryan Helmkamp
32f100cbe7
feat(install): make LLM setup optional in web installer and CLI (#265)
Some checks failed
Rust / Clippy (push) Waiting to run
Rust / Format (push) Waiting to run
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Has been cancelled
TypeScript / Test (push) Has been cancelled
TypeScript / Build (push) Has been cancelled
## Summary

Makes LLM setup explicitly skippable in both the web installer and
`fabro install`, without making omission accidental. A skipped LLM step
lets install complete with zero LLM credentials; later LLM-dependent
workflows keep using the existing provider-not-configured behavior.
`fabro doctor` is intentionally unchanged.

Plan: `docs/superpowers/plans/2026-05-14-optional-llm-install.md`

## Key changes

**Server + API**
- `PUT /install/llm` now accepts `{"providers":[]}` as "LLM step
completed, skipped" — the empty-list rejection is removed; per-provider
validation for non-empty lists is retained.
- OpenAPI: dropped `minItems: 1` from
`InstallLlmProvidersInput.providers`, updated schema descriptions so
empty = skipped and `llm: null` = incomplete. TypeScript client
regenerated.
- `/install/finish` still requires the LLM step to be present, but
tolerates zero credentials — it writes settings, runtime auth secrets,
and GitHub secrets normally and writes no LLM vault entries.

**Web installer**
- New "Skip LLM setup" secondary action on the LLM step (via a
`secondaryAction` prop on `StepPanel`) that records an empty provider
list and advances to GitHub.
- Review screen shows `LLM providers: Skipped` (step completed, empty)
vs `Not configured` (step never completed), via a new
`describeLlmSummary` helper.
- Continue with no API keys still shows the existing validation error —
skipping is only reachable through the explicit skip action.

**CLI**
- Interactive `fabro install` asks "Configure LLM providers now?"
(default yes) before provider selection; declining returns an empty
selection and continues to GitHub.
- Hidden non-interactive `--skip-llm` flag, mutually exclusive with
`--llm-provider` / `--llm-api-key-stdin` / `--llm-api-key-env` via clap
`conflicts_with_all`. Missing LLM flags are still validation errors
unless `--skip-llm` is present. Non-interactive usage text updated with
a skip example.

## Code review

Ran a 12-reviewer `ce:review` pass (correctness, testing,
maintainability, project-standards, agent-native, learnings, security,
api-contract, reliability, adversarial, cli-readiness,
kieran-typescript). No P0/P1 findings; agent-native parity PASS. Applied
fixes in `40a29c591`:
- Re-entrancy guard on `runStepSubmit` so a fast double-click on "Skip
LLM setup" can't fire two requests.
- `validate()` only suggests `--skip-llm` in the missing-provider error
when no credential flag is set (it conflicts with those flags).
- Added tests: all three `--skip-llm` conflict arms, the review screen's
"Not configured" branch, and the skip-button failure path.

One advisory finding left as report-only: an empty `PUT /install/llm`
overwrites previously-saved credentials if a user navigates Back and
clicks Skip — judged acceptable since the button is explicitly labeled
and clicking it is deliberate.

## Testing

- `cargo nextest run -p fabro-server -p fabro-cli -p fabro-install` —
1521 passed
- `cargo build -p fabro-api`, `cargo fmt --check`, `cargo clippy`
(changed crates) — clean
- `bun test` (install-app) — 14 passed; `bun run typecheck` — clean
- New coverage: server accepts empty providers + session shows `llm`
complete with `providers:[]`; finish with skipped LLM persists no LLM
vault credentials but keeps GitHub secrets; web skip button PUTs
`providers:[]` and navigates to GitHub; review renders Skipped / Not
configured; CLI `--skip-llm` requires `--non-interactive`, conflicts
with all credential flags, `validate()` succeeds with `--skip-llm`,
usage text documents `--skip-llm`.

Not added (out of plan scope): an automated test for the interactive
`InstallInputSource` skip branch — `InteractiveInstallInputSource` is
TTY-coupled and has no existing tests; the non-interactive `--skip-llm`
path is fully covered.

## Post-Deploy Monitoring & Validation

This change is install-time only; there is no continuous runtime impact.
Validate during the next install/release smoke:

- **Web installer:** run a fresh browser install, click "Skip LLM setup"
on the LLM step, confirm it advances to GitHub and the review screen
reads `LLM providers: Skipped`. Finish the install and confirm the
server restarts into normal mode with no LLM credentials in the vault
(`secrets.json` has no credential entries) and
GitHub/server/object-store/sandbox settings written normally.
- **CLI:** run `fabro install --non-interactive --skip-llm
--github-strategy token --github-username <user>` and confirm it
completes; run interactive `fabro install` and confirm declining
"Configure LLM providers now?" continues to GitHub.
- **Healthy signals:** install completes (web `/install/finish` → 202;
CLI exits 0), server boots in normal mode, `fabro doctor` runs and
reports no LLM providers configured (expected, unchanged behavior).
- **Failure signals / rollback trigger:** install fails to finish,
server fails to boot after a skipped install, or `/install/finish`
rejects a completed-but-empty LLM step. Rollback = revert this PR;
install behavior returns to requiring at least one LLM provider.
- **Validation window/owner:** next install smoke / release
verification, owned by whoever runs the release.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:34:39 -04:00
Bryan Helmkamp
2d7c79de1e
feat(server): validate Daytona API key scopes
Probe the Daytona API at install, `fabro secret set DAYTONA_API_KEY`,
and `fabro doctor` time to confirm the configured key carries the
snapshot/sandbox scopes Fabro needs. Operators now see a precise scope
error against the control plane instead of a generic sandbox-create
failure at first run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:09:01 -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
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
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
e6e091fe8e
refactor(server): lock down server secrets 2026-04-23 07:15:27 -04:00
Bryan Helmkamp
4bf0c40319
feat(install): add object-store step to web install 2026-04-22 19:31:09 -04:00
Bryan Helmkamp
7a58ab4e2b
refactor(server): unify daemon runtime metadata 2026-04-22 16:07:52 -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
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
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
2ec1fb2987
test(unwrap): clean server integration helpers 2026-04-19 21:06:36 -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
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
3f21644d80
fix(install): cover follow-up edge cases
Harden the remaining install flow regressions and add the missing
coverage for startup dispatch, finish-time shutdown behavior, and
partial-state persistence after vault failures.
2026-04-19 13:32:46 -04:00
Bryan Helmkamp
75f8ed845b
fix(install): harden web wizard against review findings
Tighten the browser-based install flow after correctness and adversarial
review, without changing the external wizard shape.

- Persist the actual bind in server.listen, not the canonical URL
- Reject concurrent /install/finish and rapid GitHub App retries
- Keep the prior GitHub Token strategy until App callback succeeds
- Recover from poisoned install locks instead of propagating panics
- Rollback both settings and vault on failed persistence
- Redirect GitHub callback errors back into the wizard UI
- Validate LLM keys via /models probe instead of a billed generate()
- Reject canonical URLs with trailing slash, path, query, or fragment
- Accept any valid install-token source, not just the first present one
- Redact the install token in structured logs
- Assert install-mode SPA marker injection at startup
- Warn on suspected concurrent operators via UA + X-Forwarded-For
- Add component-level test for the GitHub callback error banner

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:43:00 -04:00
Bryan Helmkamp
ecdfdd82d8
feat(install): add browser-based setup flow
Implement the web-first install experience across the server, CLI, API spec,
web app, and packaged SPA assets.

This also removes test-side process env mutation by pushing env-dependent
decision points behind explicit helpers and test wiring.
2026-04-19 11:20:58 -04:00