Commit graph

863 commits

Author SHA1 Message Date
Bryan Helmkamp
0883cf77ee refactor(settings): stage 6.3b promote sandbox runtime types into fabro-sandbox
Moves the sandbox runtime types from `fabro-types/src/settings/sandbox.rs`
into a new `fabro-sandbox/src/config.rs` module:

- `SandboxSettings`, `LocalSandboxSettings`, `DaytonaSettings`,
  `DaytonaSnapshotSettings`, `DaytonaNetwork`, `DockerfileSource`,
  `WorktreeMode` (with the custom serde `DaytonaNetwork`
  serialize/deserialize impls intact).
- `bridge_sandbox` and `bridge_worktree_mode` (v2
  `RunSandboxLayer` → `SandboxSettings` converters) also move from
  `fabro-types/src/settings/v2/to_runtime.rs` into the new config
  module.

`fabro-sandbox/src/daytona/mod.rs` and `sandbox_spec.rs` update to
import from the crate-local `config` module instead of
`fabro_types::settings::sandbox`. The daytona module still re-exports
`DaytonaSettings as DaytonaConfig` etc., so no breaking changes for
callers of `fabro_sandbox::daytona::*`.

Consumer updates:

- `fabro-workflow/src/operations/start.rs` and `pipeline/types.rs`
  now import `WorktreeMode`, `SandboxSettings` (as `sandbox_config`
  alias), `bridge_sandbox`, and `bridge_worktree_mode` from
  `fabro_sandbox::config`.
- `fabro-server/src/run_manifest.rs` imports `bridge_sandbox` from
  `fabro_sandbox::config`.

`to_runtime.rs` in fabro-types shrinks to just the three remaining
helpers tied to the legacy `run.rs` module types
(`bridge_merge_strategy`, `bridge_pull_request`, `bridge_run_artifacts`).
Those move out in the next 6.3b pass when the `run.rs` module itself
moves.

Five of the seven legacy runtime type modules are now gone; two
remain (run, server). 3,758 workspace tests pass. `cargo fmt
--check --all` and `cargo clippy --workspace -- -D warnings` are
clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 18:17:06 -04:00
Bryan Helmkamp
5905befa8d refactor(settings): stage 6.3b promote mcp runtime types into fabro-mcp
Moves `McpServerEntry`, `McpServerSettings`, `McpTransport`, plus the
`default_startup_timeout_secs` / `default_tool_timeout_secs` helpers
from `fabro-types/src/settings/mcp.rs` into
`fabro-mcp/src/config.rs`. fabro-mcp was already the only crate that
re-exported them, so this deletes the `fabro-types` module entirely
and drops the `pub use mcp::*` re-export from `settings/mod.rs`.

`bridge_mcps` / `bridge_mcp_entry` (v2 `McpEntryLayer` → runtime
`McpServerEntry` converters) also move to `fabro-mcp/src/config.rs`.
`fabro-workflow::operations::start` and `fabro-cli::commands::exec`
now import `bridge_mcp_entry` from `fabro_mcp::config::bridge_mcp_entry`
instead of the v2 `to_runtime` module.

Four of the seven legacy runtime type modules are now gone; three
remain (run, sandbox, server). The `to_runtime.rs` module is down to
just sandbox, pull-request, merge-strategy, artifacts, and
worktree-mode helpers.

3,758 workspace tests pass. `cargo fmt --check --all` and
`cargo clippy --workspace -- -D warnings` are clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 18:12:00 -04:00
Bryan Helmkamp
581e063883 refactor(settings): stage 6.3b promote hook + project runtime types
Two more legacy runtime type modules deleted from `fabro-types`:

**project.rs** (19 LOC): `ProjectSettings` was a trivial one-field
struct with a `pub use` re-export in `fabro-config/src/project.rs`.
Nothing else referenced it. Deleted outright; `fabro-config/src/project.rs`
drops the re-export and fixes up a `v2::` import path.

**hook.rs** (230 LOC): `HookDefinition`, `HookEvent`, `HookSettings`,
`HookType`, `TlsMode` plus the `resolved_hook_type` / `is_blocking`
/ `timeout` / `runs_in_sandbox` / `effective_name` behavior methods
are **moved** (not just re-exported) into
`fabro-hooks/src/config.rs`. They're runtime shapes owned by the
hook executor, so they belong in the consumer crate.

`bridge_hook` (and its private `resolve_hook_type` /
`bridge_hook_event` helpers) also moved from
`fabro-types/src/settings/v2/to_runtime.rs` into
`fabro-hooks/src/config.rs`, because the target type is now local
to `fabro-hooks`. `fabro-workflow/src/operations/start.rs` now
imports `bridge_hook` from `fabro_hooks::config::bridge_hook`
instead of the v2 `to_runtime` module.

`fabro-hooks/src/types.rs` re-export of `HookEvent` switches from
the deleted `fabro_types::settings::hook` path to the new
crate-local `crate::config::HookEvent`.

`settings/mod.rs` drops `pub mod {hook, project}` and the
corresponding `pub use` re-exports. Three of the seven legacy
runtime type modules are now gone; four remain (mcp, run, sandbox,
server).

3,758 workspace tests pass. `cargo fmt --check --all` and
`cargo clippy --workspace -- -D warnings` are clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 18:07:04 -04:00
Bryan Helmkamp
a3808a98b3 refactor(settings): stage 6.3b promote user runtime types into consumers
First consumer migration pass. Deletes
`lib/crates/fabro-types/src/settings/user.rs` outright:

- `OutputFormat`, `PermissionLevel`: moved into `fabro-agent/src/cli.rs`
  where they are actually consumed as `AgentArgs` fields. They carry
  clap `ValueEnum` derives so `fabro-cli` keeps importing them via the
  `fabro_agent::cli::{OutputFormat, PermissionLevel}` public path.
- `ClientTlsSettings`: moved into `fabro-cli/src/user_config.rs` as a
  crate-private struct. Only `fabro-cli` references it (via
  `cli_target_from_v2` when building the HTTP client).
- `ExecSettings`, legacy `ServerSettings` (from `settings::user`):
  deleted outright — no callers remained.

Also removes the now-dead `From<&GitAuthorSettings> for GitAuthor`
impl in `fabro-checkpoint/src/author.rs`. The v2 `GitAuthorLayer`
conversion is the only path `fabro-workflow::git::git_author_from_settings`
uses. Drops the `fabro_types::settings::server::GitAuthorSettings`
import along with it.

`settings/mod.rs` drops the `pub mod user` declaration and the
`pub use user::*` re-export line. One of the seven legacy runtime
type modules is now gone; six remain.

3,758 workspace tests pass. `cargo fmt --check --all` and
`cargo clippy --workspace -- -D warnings` are clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:59:49 -04:00
Bryan Helmkamp
fb04e17329 refactor(settings): stage 6.3b delete legacy flat Settings struct
Deletes `fabro_types::Settings` — the ~65-field legacy flat view that
has been read-only since Stage 6.1 migrated all production read sites
to the v2 `SettingsFile`.

The last remaining readers all fall out of this commit:

- `fabro-server/src/demo/mod.rs` — the two demo settings fixtures
  (`runs::settings()` and `settings::server_settings()`) are rewritten
  as `serde_json::json!(...)` literals in the v2 `SettingsFile` shape.
  They produce the same wire bytes as the real handlers now return, so
  the demo page keeps rendering identically.
- `fabro-server/src/lib.rs::server_config` — drops the
  `pub use fabro_types::Settings` re-export. Only the inner
  `fabro_types::settings::server::*` module (still around until the
  full runtime-type cleanup) remains.
- `fabro-server/tests/it/openapi_conformance.rs` — drops the
  `server_settings_keys_match_openapi_spec` schema-drift test and all
  of its legacy type imports. The new freeform-object DTO in the spec
  (`type: object, additionalProperties: true`) has no `properties` to
  diff against, so the test was already a no-op. Leaves
  `all_spec_routes_are_routable` in place.
- `fabro-store/src/run_state.rs` — test fixture was building a
  `Settings::default()` JSON payload; switched to `SettingsFile::default()`.
- `fabro-types/src/run_event/mod.rs` — two `EventBody::RunCreated`
  round-trip tests were constructing `Settings::default()`; switched
  to `SettingsFile::default()`.
- `fabro-workflow/tests/it/integration.rs` — the two
  `hook_toml_*_parsing` tests decoded top-level `[[hooks]]` into a
  legacy `Settings`. That parse path was removed in Stage 6.1; the
  tests are deleted and replaced with a comment pointing at the v2
  `settings::v2::tree::tests` fixtures that cover the same ground.

The legacy flat struct's module-level doc comment in
`settings/mod.rs` is updated to explain the transitional runtime
shapes that still live under `hook`, `mcp`, `project`, `run`,
`sandbox`, `server`, and `user` — a follow-up pass will either
promote them into their consumer crates or inline them at the call
sites so the whole `settings/*.rs` file set can go away and 6.5b
flattening can happen.

3,758 workspace tests pass. `cargo fmt --check --all` and
`cargo clippy --workspace -- -D warnings` are clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:32:03 -04:00
Bryan Helmkamp
40c9aae29c feat(settings): stage 6.6 wire server + CLI to v2 SettingsFile DTO
Replaces the Stage 6.2 stopgap `strip_nulls(serde_json::to_value(full
SettingsFile))` path in `get_server_settings` with an explicit
redaction pass in the new `fabro_server::settings_view` module.

The redaction drops the narrow set of fields that leak operational
secrets or host filesystem layout:

- `server.listen.*` (bind + TLS material)
- `server.auth.api.jwt.{issuer, audience}` (auth topology)
- `server.auth.api.mtls.ca` (filesystem path)
- `server.auth.web.providers.github.client_secret`

Every other field is preserved. `InterpString` values that reference
`${env.NAME}` already serialize to their unresolved template form, so
no additional env-provenance walk is needed in this pass.

Implements the real `/api/v1/runs/:id/settings` handler — previously
wired to `not_implemented` — by opening the run reader, reading the
persisted `RunRecord.settings`, running it through the same
redaction, and serializing. The demo route still points at
`demo::get_run_settings`, unchanged.

Updates `fabro-cli` to deserialize the new wire shape as
`SettingsFile` directly:

- `server_client::retrieve_server_settings` now returns
  `SettingsFile` (no longer the legacy flat `Settings`) by decoding
  the progenitor `types::ServerSettings` newtype map into a
  `serde_json::Value` and then into `SettingsFile`.
- `commands/config/mod.rs::legacy_settings_to_v2` shim (TODO-1)
  **deleted**; `merged_config` passes the v2 file straight into
  `effective_settings::resolve_settings`.
- The `fabro-cli` integration tests rewrite their mock `/api/v1/settings`
  payloads as v2 TOML via `ConfigLayer::parse` instead of hand-rolling
  the legacy TOML shape.

All 3,761 workspace tests pass. `cargo fmt --check --all` and
`cargo clippy --workspace -- -D warnings` are clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:16:26 -04:00
Bryan Helmkamp
78c57d585c refactor(api): stage 6.6 collapse settings DTOs to freeform v2 shape
Replaces the legacy flat `ServerSettings` / `RunSettings` schemas in
`docs/api-reference/fabro-api.yaml` and 20+ supporting nested type
schemas (LlmSettings, SandboxSettings, HookDefinition, WebSettings,
ApiSettings, GitSettings, McpServerEntry, etc.) with two simple
`type: object, additionalProperties: true` schemas that declare the
wire shape as the v2 `SettingsFile` tree with secret-bearing subtrees
dropped before serialization.

Regenerates the Rust progenitor and TypeScript Axios clients against
the new spec. The progenitor generates `RunSettings` / `ServerSettings`
as `#[serde(transparent)]` newtypes over `serde_json::Map<String,
Value>`; the openapi-generator emits `{ [key: string]: any; }` inlined
into the API method signatures and no longer exports named model
types.

Updates fabro-web to define local `type ServerSettings =
Record<string, unknown>` / `type RunSettings = Record<string,
unknown>` aliases since the generated client no longer exports them.
The UI only `JSON.stringify`s these payloads into a CollapsibleFile,
so the opaque shape is fine.

All 3,756 workspace tests remain green. The OpenAPI conformance test
`server_settings_keys_match_openapi_spec` still passes because
`compare_schema` short-circuits on pure-map schemas (no `properties`);
it becomes a no-op that will be removed entirely when Stage 6.3b
deletes the legacy flat `Settings` struct it still builds.

Unblocks the server handler + CLI migration in the next commits of
Stage 6.6.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:04:33 -04:00
Bryan Helmkamp
ac5a60672f refactor(types): stage 6.5 promote v2 types to settings top level
Stage 6.5 can't flatten the `settings::v2::*` module tree onto
`settings::*` files wholesale because the v2 submodules
(`project.rs`, `run.rs`, `server.rs`) share filenames with the legacy
flat type modules that are still required by the OpenAPI legacy
`ServerSettings` response path (Stage 6.3 / 6.6 deletes them).

As the feasible piece of Stage 6.5 work:

- Re-export the v2 top-level type aliases from `fabro_types::settings`
  so consumers can write `fabro_types::settings::SettingsFile`,
  `fabro_types::settings::InterpString`, `fabro_types::settings::Duration`,
  etc. without the `::v2::` prefix.
- The re-export covers the whole public v2 surface:
  `{CURRENT_VERSION, CliLayer, Duration, FeaturesLayer, InterpString,
  ModelRef, ParseDurationError, ParseError, ParseModelRefError,
  ParseSizeError, ProjectLayer, Provenance, ResolveEnvError, Resolved,
  ResolvedModelRef, RunLayer, SchemaVersion, ServerLayer, SettingsFile,
  Size, SpliceArray, SpliceArrayError, VersionError, WorkflowLayer,
  parse_settings_file, validate_version}`.

The `v2` module itself stays in place to host the submodule tree
(accessors, to_runtime, run::*, cli::*, server::*, interp, etc.) until
Stage 6.3 finishes deleting the conflicting legacy files, at which
point the v2/ directory can be promoted to replace them.

Build, clippy, fmt, and 3756 / 3756 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:33:41 -04:00
Bryan Helmkamp
eb7f99310c refactor(config): stage 6.4 delete fabro-config re-export shims
fabro-config no longer carries the legacy pass-through shims that
forwarded type re-exports from `fabro_types::settings::{hook,mcp,sandbox,
server,user,run}`. Consumers now import the runtime types directly
from `fabro_types::settings::*`, which is the only definitional
location.

Deleted files:
- `fabro-config/src/hook.rs` (1 LOC glob re-export)
- `fabro-config/src/mcp.rs`   (1 LOC glob re-export)
- `fabro-config/src/sandbox.rs` (~8 LOC re-export list)
- `fabro-config/src/server.rs`  (re-exports + `resolve_storage_dir`;
  the `resolve_storage_dir` helper moved to `fabro_config`'s crate root
  and takes `&SettingsFile` directly)

Shrunk files:
- `fabro-config/src/run.rs` lost the `ArtifactsSettings` /
  `CheckpointSettings` / `GitHubSettings` / `LlmSettings` /
  `MergeStrategy` / `PullRequestSettings` / `SetupSettings` re-export
  block and the unused `resolve_env_refs` helper. What remains is just
  the workflow TOML loader helpers (`parse_run_config`, `load_run_config`,
  `resolve_graph_path`).
- `fabro-config/src/user.rs` lost the `ClientTlsSettings` /
  `ExecSettings` / `OutputFormat` / `PermissionLevel` /
  `ServerSettings` re-export block. The settings-path helpers and
  legacy-config warning logic stay. `fabro-cli/src/user_config.rs`
  now imports `ClientTlsSettings` directly from fabro_types.

Callers updated to use the canonical paths:
- `fabro-agent/src/cli.rs` imports `{OutputFormat, PermissionLevel}`
  from `fabro_types::settings::user`; added `fabro-types` dep.
- `fabro-hooks/src/{config,types}.rs` re-export from
  `fabro_types::settings::hook`.
- `fabro-mcp/src/config.rs` re-exports from `fabro_types::settings::mcp`.
- `fabro-sandbox/src/daytona/mod.rs` re-exports from
  `fabro_types::settings::sandbox`.
- `fabro-server/src/{lib,jwt_auth,tls,serve,demo}.rs` +
  `tests/it/openapi_conformance.rs` import server types from
  `fabro_types::settings::server` and call `fabro_config::resolve_storage_dir`
  from the crate root.
- `fabro-workflow/src/{operations/start,pipeline/types,pipeline/pull_request}.rs`
  import sandbox / pull_request types from `fabro_types::settings::*`.

Build, clippy, fmt, and 3756 / 3756 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:30:38 -04:00
Bryan Helmkamp
2c8b6c95aa refactor(settings): stage 6.3 delete dead Settings helpers + v2 install TOML
Stage 6.3 closes out the dead code that Stage 6.1 left behind:

fabro-types
- Delete the inherent helpers on the legacy flat `Settings` struct
  (`app_id`, `slug`, `client_id`, `git_author`, `sandbox_settings`,
  `setup_settings`, `setup_commands`, `setup_timeout_ms`,
  `preserve_sandbox_enabled`, `github_permissions`, `mcp_server_entries`,
  `verbose_enabled`, `prevent_idle_sleep_enabled`, `upgrade_check_enabled`,
  `dry_run_enabled`, `auto_approve_enabled`, `no_retro_enabled`,
  `storage_dir`, `slack_settings`). Nothing reads them anymore --
  consumers now use `SettingsFile` accessors (`github_app_id_str()`,
  `run_sandbox()`, `dry_run_enabled()`, `storage_dir()`, etc.). The
  `Settings` struct itself stays alive for the remaining legacy
  OpenAPI response path and a handful of demo-route payloads; Stage
  6.6 finishes the deletion alongside the OpenAPI spec rewrite.
- Delete the `#[cfg(test)] mod tests` block that only covered the
  deleted `storage_dir()` helper.

fabro-cli/commands/install.rs
- `merge_server_settings` now writes a v2 TOML file (with
  `[server.{api,listen.tls,web,auth.api.{jwt,mtls},auth.web}]` stanzas)
  instead of the legacy v1 top-level `[web]`/`[api]`/`[git]` shape.
  The generated file previously failed to parse as v2 on next startup;
  now it round-trips through `ConfigLayer::parse`.
- Tests rewritten to parse the generated TOML through
  `fabro_config::ConfigLayer::parse` and assert against the v2 tree
  (`server.auth.web.allowed_usernames`, `server.auth.api.{jwt,mtls}.enabled`,
  `server.listen.tls.{cert,key,ca}`). The `merge_server_settings_preserves_existing_*`
  tests collapsed into a single `preserves_existing_top_level_sections`
  test since the old tests were asserting v1 `[git]` / `[api]` keys
  that no longer make sense.

Build, clippy, fmt, and tests all green: 3756 / 3756 pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:20:26 -04:00
Bryan Helmkamp
884fa329ba feat(settings): stage 6.2 delete bridge_to_old seam
bridge.rs (818 LOC) is gone. Production consumers no longer produce a
full legacy `Settings` from v2 state; every read path walks the v2 tree
directly or uses one of the narrow v2->runtime helpers in the new
`settings::v2::to_runtime` module.

Core moves:

fabro-types
- Delete `settings::v2::bridge::bridge_to_old` and the whole bridge.rs
  file.
- Relocate the narrow v2->runtime helpers (`bridge_sandbox`,
  `bridge_mcp_entry`, `bridge_mcps`, `bridge_hook`, `bridge_worktree_mode`,
  `bridge_merge_strategy`, `bridge_pull_request`, `bridge_run_artifacts`)
  into a new `settings::v2::to_runtime` module. Each helper takes a
  single v2 subtree and produces the corresponding runtime shape;
  nothing assembles a full legacy `Settings` anymore.
- `settings/mod.rs` doc comment rewritten to describe `Settings` as a
  runtime shape, not a resolved parse target. Stage 6.3 deletes it.

fabro-config
- `ConfigLayer::resolve` is gone along with the `TryFrom<ConfigLayer>
  for Settings` impls. Consumers call `.into()` for a `SettingsFile`,
  or `.as_v2()` to borrow one.
- `fabro_config::server::resolve_storage_dir` now takes `&SettingsFile`.

fabro-server
- `api_server_settings` emits the v2 `SettingsFile` JSON shape
  directly instead of bridging to the legacy flat DTO. Stage 6.6
  replaces the shape again with an explicit allow-list DTO.
- `serve.rs`: `load_settings` returns `SettingsFile`;
  `apply_serve_overrides` / `apply_runtime_settings` mutate v2
  subtrees directly; `build_artifact_object_store` walks
  `server.artifacts`; `build_legacy_api_settings` projects the v2
  auth/listen/api subtrees down to the legacy `ApiSettings` shape for
  the (still-legacy) auth resolver.
- `diagnostics::check_crypto` walks `server.auth.api.{jwt,mtls}` and
  `server.listen.tls` directly.
- `web_auth.rs` oauth / register / setup-status / auth-me flows all
  read `server.web`, `server.integrations.github`, and
  `server.auth.web` directly via the v2 accessors. `merge_settings_keys`
  now writes v2 TOML (with `[server.web]`, `[server.integrations.github]`,
  etc.) instead of the legacy v1 top-level keys, and the register
  handler re-parses the freshly-written file back into the in-memory
  `SettingsFile` state.

fabro-cli
- `CommandContext::machine_settings` returns `&SettingsFile`.
- `user_config::load_settings` and friends return `SettingsFile`.
- `user_config::resolve_server_target` / `exec_server_target` /
  `configured_server_target` walk `cli.target.{http,unix}` directly.
  Tests rewritten against v2 TOML fixtures.
- `main.rs` logging init reads `cli.logging.level` / `server.logging.level`
  via v2 accessors.
- `commands/exec.rs` reads `cli.exec.{model,agent}` and builds mcps
  from `cli.exec.agent.mcps` (falling back to `run.agent.mcps`) via
  `to_runtime::bridge_mcp_entry`.
- `commands/pr/mod.rs` calls `github_app_id_str()`.
- `commands/run/create.rs` drops the legacy `.resolve()` call and uses
  `Into::<SettingsFile>::into(...)`.
- `commands/config/mod.rs::legacy_settings_to_v2` is now a real
  reverse-mapping helper that covers `storage`, `scheduler`,
  `integrations.{github,slack}`, `run.model`, `run.inputs`, and
  `cli.output.verbosity`. Stage 6.6 deletes it when the API client
  returns v2 natively.
- `tests/it/cmd/config.rs` tests now walk the v2 tree directly (via
  `cfg.run_model_name_str()`, `cfg.run_inputs()`, `cfg.run_sandbox()`,
  `cfg.run_hooks()`, `cfg.run_agent_mcps()`, `cfg.run_prepare_commands()`,
  `cfg.server_storage_root_str()`, etc.). The `bridge_to_old` test
  helper is gone.
- `tests/it/api/settings.rs` asserts against the v2 JSON shape.

Build, test, and quality gates all green:
- `cargo build --workspace --tests`
- `cargo clippy --workspace -- -D warnings`
- `cargo fmt --check --all`
- `cargo nextest run --workspace`: 3758 / 3758 passed, 182 skipped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:12:59 -04:00
Bryan Helmkamp
449d6abf00 test(settings): update fabro-cli test suite for v2 settings shape
Completes the fabro-cli test migration for Stage 6.1. Every test in
`cargo nextest run --workspace` now passes (3,764 passed / 0 failed).

Changes:

- cmd/support.rs: compact_inspect / compact_git_inspect now walk the
  v2 tree (/settings/run/goal, /settings/run/sandbox/provider,
  /settings/run/model/provider) and derive `dry_run` from the v2
  execution mode.
- cmd/attach.rs: the event-log filter strips _version and redacts
  settings.cli.target.path to [CLI_SOCKET] so randomized tempdir
  sockets don't pollute the snapshot. Insta snapshot accepted.
- cmd/run.rs: same cli.target redaction in the run event filter.
  dry_run_persists_event_history_in_store and
  json_run_implies_auto_approve_for_human_gates check for
  `settings.run.execution.approval == "auto"` instead of
  `settings.auto_approve == true`. Insta snapshot accepted.
- cmd/config.rs: parse_settings bridges the v2 YAML output back down
  to the legacy flat Settings shape so the existing helper assertions
  keep working. settings_fetches_server_settings_and_merges_with_local_config
  now asserts the v2 R22 behavior (run.inputs replaces wholesale, so
  server-side `server_only` is dropped in favor of project's vars).
  settings_uses_fabro_home_for_home_config_resolution walks the v2
  JSON paths (cli.output.verbosity, run.model.name).
  create_explicit_workflow_path_uses_project_config_relative_to_workflow
  asserts against the v2 run-record shape.
- fabro-cli/commands/config/mod.rs: legacy_settings_to_v2 is now a
  real (if narrow) reverse bridge covering storage, scheduler, github
  integration, slack integration, run.model, run.inputs, and cli
  verbosity. Stage 6.6 still replaces this when the API client returns
  v2 types natively, but for now the server-side defaults round-trip
  through the resolver with enough fidelity to keep the settings
  command integration tests honest.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 15:36:09 -04:00
Bryan Helmkamp
41ab919959 feat(settings): stage 6.1 consumer migration builds workspace-wide
Extends the stage 6.1 WIP into a compiling state across the workspace.
Most crates and their unit/integration tests now read run.* / cli.* /
server.* v2 layers directly or through targeted bridge helpers.

Key moves in this commit:

fabro-server
- AppState.settings: Arc<RwLock<SettingsFile>> -- all helpers,
  create_app_state_with_* factories, and tests updated.
- api_server_settings bridges SettingsFile -> legacy Settings via the
  transitional bridge so /api/v1/settings still emits the legacy DTO
  shape until Stage 6.6 replaces it with an allow-list DTO.
- get_system_info, get_system_df, get_github_repo, webhook startup, and
  other read sites use the v2 accessors (github_app_id_str,
  server_web, run_sandbox, run_model_*).
- web_auth.rs wraps each oauth / register / setup-status handler in a
  local `bridged` helper that produces a legacy Settings from the v2
  state, so the complex oauth mutation flow keeps working until its
  Stage 6.6 rewrite.
- diagnostics::check_github_app reads via github_*_str accessors;
  check_crypto bridges to the legacy shape inline.
- serve.rs: load_settings returns SettingsFile; apply_serve_overrides /
  apply_runtime_settings mutate v2 subtrees directly; the config poll
  loop and TLS/webhook startup use bridged() for legacy-shape reads.
- Tests in tests/it/{helpers,api/*,scenario/*} rewritten to construct
  SettingsFile via ConfigLayer::parse or v2 struct literals.

fabro-workflow
- Every test fixture in pipeline/{finalize,initialize,pull_request,retro,
  execute,persist}, operations/{create,rebuild_meta,start}, run_lookup,
  runtime_store, handler/manager_loop, and tests/it/{integration,
  daytona_integration}.rs now uses SettingsFile.
- start.rs hooks into the bridge helpers directly via use-imports.
- run_graph / run_graph_from_checkpoint / initialize / finalize /
  pull_request calls are Box::pin'd to stay under clippy's large-future
  threshold after the v2 tree brought RunOptions size up.
- resolve_run_settings writes resolved model/provider back into
  run.model as InterpStrings; tests assert via run_model_*_str().
- preprocess_and_validate pulls vars from run_inputs_as_strings().

fabro-cli
- manifest_builder uses ConfigLayer.combine(...).into() to get a v2
  SettingsFile for the manifest goal resolution path; file-based
  goal_file handling is deferred to 6.6 when the manifest schema catches
  up.
- runner::maybe_build_github_app_credentials and
  tests/it/cmd/{create,runner}.rs read from v2 accessors.
- commands/config/mod.rs::merged_config returns SettingsFile; the
  server-side retrieve_server_settings is bridged via a stopgap
  legacy_settings_to_v2 shim that Stage 6.6 replaces.
- commands/store/dump.rs sample_run_record constructs SettingsFile.

fabro-store, fabro-checkpoint
- Test fixtures constructing RunRecord values updated to SettingsFile.
- fabro-checkpoint/src/author.rs stays (v2 From impl landed in a
  previous additive commit).

fabro-config
- effective_settings.rs rewrite compiles and passes its unit tests.
- project::resolve_working_directory takes &SettingsFile.

Build status: `cargo build --workspace --tests`, `cargo clippy
--workspace -- -D warnings`, and `cargo fmt --check --all` all pass.
`cargo nextest run --workspace` passes 3,749 of 3,764 tests; the 15
remaining failures are fabro-cli integration tests whose snapshot +
TOML fixture shapes still need manual updates:

- cmd::config::* (seven tests): fixture TOML files still use v1
  top-level keys and the snapshot outputs expect the legacy flat JSON
  shape.
- cmd::inspect::* (four tests): run-record JSON snapshots embed the
  flat Settings shape.
- cmd::run::dry_run_persists_event_history_in_store and
  json_run_implies_auto_approve_for_human_gates: check `settings.dry_run
  == Some(true)` directly on the v2 file; should assert
  dry_run_enabled() instead.
- cmd::attach::attach_json_errors_without_prompting_for_human_input:
  unrelated insta snapshot drift caused by the new SettingsFile JSON
  shape leaking into an events-log snapshot.

Follow-up work for this stage also includes:
- Rewriting web_auth.rs register flow to emit v2 TOML directly and to
  re-parse the written file back into state.settings so in-memory
  state doesn't lag the on-disk file.
- Removing the legacy_settings_to_v2 shim in fabro-cli/config once
  the server-side settings endpoint returns v2 shapes (Stage 6.6).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 15:25:59 -04:00
Bryan Helmkamp
d0802fbfb1 wip(settings): stage 6.1 consumer migration (broken build)
Partial Stage 6.1 migration of consumers off the legacy flat Settings
shape to v2 SettingsFile. Commits the in-flight work so subsequent
sessions can resume from here. Workspace currently does NOT build --
fabro-server still has ~60 consumer sites that reference state.settings
as legacy Settings, and fabro-cli is entirely untouched.

Landed in this commit:

fabro-types
- RunRecord.settings: Settings -> SettingsFile
- RunCreatedProps.settings: Settings -> SettingsFile

fabro-config
- effective_settings: full rewrite. resolve_settings now returns
  SettingsFile; apply_server_defaults / apply_local_daemon_overrides
  are v2-native and use the v2 merge matrix for server-owned domains.
- project::resolve_working_directory takes &SettingsFile and reads
  run.working_dir as an InterpString.

fabro-workflow
- start.rs, create.rs, source.rs, validate.rs, run_options.rs, git.rs,
  initialize.rs, manager_loop.rs all migrated to &SettingsFile reads.
- resolve_sandbox_provider / resolve_worktree_mode / resolve_daytona_config
  / resolve_fallback_chain walk v2 trees using the bridge helper fns.
- LifecycleOptions built from run_prepare_commands() / run_prepare_timeout_ms().
- Hooks built via bridge_hook on v2 HookEntry.
- MCPs built via bridge_mcp_entry on v2 McpEntryLayer.
- resolve_run_settings writes resolved model/provider back into
  run.model (InterpString), not the flat llm struct.
- preprocess_and_validate pulls var expansion from run_inputs_as_strings.

fabro-server/run_manifest.rs
- PreparedManifest.settings -> SettingsFile.
- prepare_manifest_with_mode takes &SettingsFile.
- build_preflight_report / run_llm_check / resolve_model_provider /
  run_github_token_check / resolve_sandbox_provider / resolve_daytona_config
  all migrated.
- Tests rewritten to use v2 fixtures via ConfigLayer::parse.

fabro-server/server.rs
- AppState.settings type changed to Arc<RwLock<SettingsFile>>.
- github_app_credentials call site uses settings.github_app_id_str()
  accessor instead of the flat app_id().

Known remaining errors:
- fabro-server/server.rs: ~60 state.settings.read() sites still
  reference legacy Settings fields (llm, sandbox, setup, git, etc.).
- fabro-server/web_auth.rs: heavy git settings usage, tests.
- fabro-server/serve.rs: state mutation of flat llm/sandbox fields.
- fabro-server/diagnostics.rs: app_id / api auth strategies.
- fabro-cli: manifest_builder, commands, tests all untouched.
- Test fixtures across the workspace still construct Settings literals.
- insta snapshots will need bulk-accept after the runtime shape stabilizes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 12:52:03 -04:00
Bryan Helmkamp
d258ae8f24 feat(types): expose bridge helpers and expand v2 accessors
Stage 6.1 prep follow-ups that consumers need when walking v2 directly:

- `bridge::bridge_sandbox`, `bridge_mcp_entry`, `bridge_mcps`,
  `bridge_hook`, `bridge_exec`, `bridge_worktree_mode`,
  `bridge_merge_strategy` are now `pub`, so callers can lift the
  runtime shape they need out of the v2 tree without round-tripping
  through the full `bridge_to_old` legacy Settings builder.
- New `bridge::bridge_pull_request` and `bridge::bridge_run_artifacts`
  helpers extract their respective runtime shapes from v2 layers.
- `SettingsFile::run_prepare_commands()` / `run_prepare_timeout_ms()`
  flatten `run.prepare.steps` into the legacy script-string vector
  shape consumers pass to `LifecycleOptions::setup_commands`.
- `SettingsFile::run_inputs_as_strings()` stringifies `run.inputs`
  TOML values for var-expansion call sites.
- `fabro_checkpoint::GitAuthor` now has `From<&v2::run::GitAuthorLayer>`
  so consumers can construct a runtime author directly from the v2
  subtree without going through the legacy flat `GitAuthorSettings`.

All changes are additive. `bridge_to_old` still exists and nothing has
migrated off the flat `Settings` shape yet -- those moves land in
follow-up commits once each consumer crate is converted independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 12:44:44 -04:00
Bryan Helmkamp
2c3ac6f819 feat(types): add SettingsFile convenience accessors
Stage 6.1 prep: add flat-view accessor methods on SettingsFile that walk
the v2 parse tree. Consumers migrate off the legacy flat Settings shape
by calling these accessors instead of chaining .as_ref() through every
Option layer. Purely additive — no existing call sites change yet.

Accessors cover:
- run.* (goal, model, sandbox, prepare, checkpoint, hooks, pull_request,
  artifacts, execution, agent/mcps, inputs, metadata, git.author)
- execution-posture booleans (dry_run, auto_approve, no_retro,
  preserve_sandbox)
- cli.* (exec, output, verbosity, prevent_idle_sleep, upgrade_check)
- server.* (api, web, storage, artifacts, scheduler, logging,
  integrations.github, integrations.slack, max_concurrent_runs)
- storage_dir() with home-dir fallback and env interpolation
- all_labels() aggregation across project/workflow/run metadata

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 12:15:23 -04:00
Bryan Helmkamp
143ed5b417 fix(lint): clean up fabro-config test clippy warnings
- Drop the fabro_types::Combine re-export from fabro-config/lib.rs
  (unused externally after Stage 3 replaced the legacy Combine-based
  merge with the v2 merge matrix)
- Replace absolute `fabro_types::settings::v2::InterpString` paths in
  fabro-config/src/config.rs and merge.rs test blocks with a scoped
  `use` import, satisfying clippy::absolute_paths
- fabro-config/src/merge.rs tests: use `!contains_key`, drop redundant
  closures around InterpString::as_source, prefer indexing over
  get().unwrap() on the notifications HashMap
- fabro-config/src/project.rs tests: switch the run.execution.retros
  fixture off raw string literal hashes (only simple content inside)
  and use ToString::to_string in the error-chain join expression
2026-04-09 11:44:56 -04:00
Bryan Helmkamp
d8389025a8 docs(config): point new code at ConfigLayer::as_v2 rather than the bridge 2026-04-09 11:38:00 -04:00
Bryan Helmkamp
b0fff693e6 refactor(config): delete unused legacy shim modules, document transitional seam
Stage 6 initial cleanup. Removes two fabro-config shim modules that no
longer hold any code and adds a module-level comment to
fabro-types/src/settings/mod.rs documenting the transitional seam
between the flat legacy Settings shape and the authoritative v2
namespaced schema in fabro_types::settings::v2.

Deleted:
- fabro-config/src/combine.rs: was a one-line re-export of
  fabro_types::combine::Combine; nothing imports it anymore
- fabro-config/src/settings.rs: was reduced to a header comment
  after Stage 3 replaced TryFrom<ConfigLayer> for Settings with
  ConfigLayer::resolve via the v2 bridge

Stage 6 full deletion (legacy flat Settings type, the bridge, the
old settings/{hook,mcp,project,run,sandbox,server,user}.rs modules,
plus the Combine trait derive) is scheduled for a follow-up PR that
migrates every consumer call site from the flat settings.llm /
.vars / .sandbox / .setup / .hooks / .mcp_servers / .goal / .work_dir
/ .github / .git / .pull_request / .checkpoint / .artifacts fields to
the v2 SettingsFile tree. That touches ~128 call sites across ~15
files and is a mechanical but large follow-up; the current bridge is
the safe intermediate state.
2026-04-09 11:35:44 -04:00
Bryan Helmkamp
3eabc013d1 test(migration): land final Stage 4 fixes — 100% workspace tests green
Close out consumer migration with targeted behavior fixes and the
remaining integration-test fixture rewrites. The full workspace
nextest run now reports 3,760 passed / 0 failed / 182 skipped.

Runtime fixes:
- effective_settings::apply_server_defaults now propagates the full
  server-side Settings shape (llm, sandbox, setup, checkpoint,
  pull_request, artifacts, hooks, mcp_servers, github, slack, fabro)
  into the resolved CLI settings, matching the pre-Stage-3 'merge
  everything server' behavior for RemoteServer/LocalDaemon modes
- fabro-cli commands/run/overrides: route --verbose through
  cli.output.verbosity = verbose instead of a run.metadata stash,
  so it resolves to settings.verbose via the bridge
- fabro-server run_manifest manifest_args_layer: same — emit a
  CliLayer with cli.output.verbosity rather than stuffing the flag
  into run.metadata
- fabro-test settings_storage_dir: detect the managed marker and
  return None instead of parsing the injected server.storage.root,
  so isolated_server correctly spins up a new storage dir
- fabro-server run_manifest_local_daemon test now passes with full
  server-side settings snapshot propagation

Test fixture + assertion updates:
- cmd::config::settings_local_explicit_workflow_path_uses_workflow_project_layers:
  assertion updated for v2 R30 whole-list replacement of
  run.prepare.steps across layers (only workflow-setup survives)
- cmd::config::create_explicit_workflow_path_uses_project_config_relative_to_workflow:
  same correction for the persisted run.settings.setup.commands
- cmd::attach::attach_json_errors_without_prompting_for_human_input
  and cmd::run::json_run_implies_auto_approve_for_human_gates: strip
  the bridge-emitted settings.server and settings.version fields from
  the JSON snapshot so the randomised unix-socket path does not flap
  the insta snapshot
- cmd::server_start::concurrent_autostart_converges_on_one_shared_daemon_and_cleans_up:
  rewrite the injected settings.toml to v2 shape with
  [server.storage] root and [cli.target] type = unix path
- scenario::smoke::attach_smoke_covers_arg_validation_and_remote_server_behaviors:
  two [server] target fixtures rewritten to [cli.target]
  type = http url

Accepted insta snapshots for attach and run JSON outputs. Workspace
build + clippy both clean under -D warnings.
2026-04-09 11:07:18 -04:00
Bryan Helmkamp
16204acf0d fix(effective_settings): keep cli/server stanzas from user settings.toml
LocalDaemon and RemoteServer modes were stripping owner-specific
domains (cli, server) from the user layer as well as from fabro.toml
and workflow.toml. Per the plan's trust boundary rule, owner-specific
domains should only be consumed from ~/.fabro/settings.toml, so the
user layer is the one place they MUST survive. Strip only the
workflow and project layers.
2026-04-09 10:19:18 -04:00
Bryan Helmkamp
d951d0bf82 fix(lint): clippy cleanup for Stage 3/4 consumer migration
- effective_settings server_defaults_layer: drop Result wrapper since
  the body never fails after the v2 switch
- merge.rs: allow needless_pass_by_value module-wide since every
  merge helper consumes both sides by design
- fabro-cli overrides.rs: replace &Option<String> sigs with
  Option<&str>, collapse Default-plus-assignment into struct literal
  (avoid clippy::field_reassign_with_default), and box the metadata
  HashMap inline
- fabro-cli manifest_builder.rs: pull DaytonaDockerfileLayer into
  scope so the pattern match stays absolute-path-clean
- fabro-cli main.rs + commands/config/mod.rs: Box::pin the settings
  subcommand future so clippy::large_futures stays happy
2026-04-09 10:16:47 -04:00
Bryan Helmkamp
783e544308 test(cli): migrate remaining config/exec/create fixtures to v2
Update remaining legacy-shape TOML fixtures in fabro-cli integration
tests to the v2 schema and adjust assertions for v2 merge semantics:

- settings_legacy_cli_config_warns_and_ignores_it: verbose → cli.output.verbosity
- settings_user_config_wins_over_legacy_cli_config: [llm]/[vars] → [run.model]/[run.inputs]
- settings_uses_fabro_home_for_home_config_resolution: same
- settings_fetches_server_settings_and_merges_with_local_config: [server] target → [cli.target]
- settings_cli_server_target_overrides_configured_server_target: same
- exec fixtures across exec.rs: [exec] → [cli.exec.*] + [cli.output], [server] → [cli.target]
- server target fixtures across model/ps/create/rm/run: [server] target → [cli.target]
- settings_local_workflow_name_applies_run_overlay_and_deep_merges:
  assertions updated for v2 R22 (run.inputs replaces), hooks replaced
  by id, checkpoint.exclude_globs replaces, sandbox.env and
  daytona.labels stay sticky merge-by-key per R71
2026-04-09 10:11:28 -04:00
Bryan Helmkamp
5dab0f4c0b fix(bridge): use hook command shorthand to avoid duplicate serde key
The old HookDefinition struct has HookType flattened via
#[serde(flatten)], so emitting hook_type = Some(HookType::Command {...})
produces an inner 'command' key at the same level as the outer
HookDefinition.command shorthand field. Round-tripping through YAML
then fails with 'duplicate field command'.

Bridge script/command hooks via the HookDefinition.command shorthand
instead, leaving hook_type = None. Also: sandbox FABRO_CONFIG in the
manifest_builder unit test so it doesn't pick up the developer's real
~/.fabro/settings.toml, and update settings_local_merges_cli_and_project_defaults
to reflect v2 R22 semantics: run.inputs replaces wholesale across
layers rather than merging by key, while daytona.labels stays a sticky
merge-by-key map per R71.
2026-04-09 10:06:16 -04:00
Bryan Helmkamp
4a48305fd2 feat(tests): migrate fabro-cli fixtures and repo fabro.toml to v2
Stage 4 consumer migration: rewrite test fixtures across fabro-cli
integration tests and the repo's own fabro.toml + workflow.toml files
to use the v2 namespaced schema.

Fixtures migrated:
- repo fabro.toml: [fabro] root → [project] directory, [pull_request]
  → [run.pull_request], [sandbox] → [run.sandbox], daytona labels
  and snapshot moved under [run.sandbox.daytona], [[hooks]] →
  [[run.hooks]] with id, integer memory/disk → '8GB'/'20GB' Size
  values
- fabro/workflows/{implement-issue,implement-plan,gh-triage,smoke}/
  workflow.toml: version → _version, [github] →
  [server.integrations.github]
- fabro-cli integration tests: config.rs (settings/external fixtures),
  repo.rs, repo_init.rs, runner.rs, run.rs, store_dump.rs,
  workflow.rs, workflow_create.rs, support.rs
- fabro-server run_manifest.rs: prepare_manifest test constructs v2
  manifest configs (run.prepare.steps + server.integrations.github)
  and updated assertion to reflect v2 whole-list replacement of
  run.prepare.steps across layers

Validate command tests all green; ~10 tests remain that need targeted
fixes for specific behaviors that shifted between schemas.
2026-04-09 09:57:30 -04:00
Bryan Helmkamp
eb077ed3a2 feat(config): switch parser and layering to v2 schema
Stage 3 of the settings TOML redesign. Switches the core parse/merge/
resolve path to the v2 namespaced schema while keeping the legacy flat
Settings shape accessible via the bridge for not-yet-migrated consumers.

Parser and layering:
- ConfigLayer is now a newtype around v2 SettingsFile. Loading via
  ConfigLayer::parse/load/settings/for_workflow/project now hard-fails
  on legacy top-level keys (version, llm, vars, sandbox, etc.) with
  targeted rename hints emitted by fabro_types::settings::v2::tree
- new fabro_config::merge module encodes the merge matrix directly:
  replace-by-default maps, sticky merge for run.sandbox.env and
  provider-native labels, splice-aware string arrays for
  run.model.fallbacks and notification route events, whole-list
  replacement for run.prepare.steps, field-merge keyed objects for
  notifications/MCPs/web-auth providers, and ordered hook id-aware
  replacement
- ConfigLayer::resolve delegates to fabro_types::settings::v2::bridge
  so consumers keep reading through the legacy Settings shape until
  Stage 4 migrates them off it
- effective_settings::resolve_settings now treats project/workflow/
  run/features as shared layered domains and strips cli/server from
  non-local layers before merging, fulfilling the owner-first trust
  boundary rule

Consumer migration (Stage 4 preview, kept to the files that block
the workspace build):
- fabro-server run_manifest builds v2 RunLayer from ManifestArgs and
  resolves manifest dockerfile references through the v2 sandbox
  daytona snapshot tree
- fabro-cli manifest_builder consults run.goal via v2; user_config
  writes the v2 server.storage.root field under the CLI storage-dir
  override; run/overrides constructs a v2 RunLayer from RunArgs
- fabro-cli scaffolds (repo init, workflow create) emit _version = 1
  with project.directory/workflow.graph/run.sandbox etc.

fabro-config / fabro-types legacy parse-time types (ProjectConfig,
LlmConfig, SandboxConfig, PullRequestConfig, ExecConfig, SettingsFile
try_into, etc.) are deleted from the parse path; the resolved type
re-exports (LlmSettings, SandboxSettings, etc.) remain as shims so
unmigrated consumers keep compiling.

fabro-test helper: settings.toml fixtures now use _version = 1 plus
[server.storage] root and [cli.target] type = "unix" path. Legacy
flat storage_dir/server.target handling removed from the sync path.

Known Stage 4/5 follow-ups:
- fabro-cli integration test fixtures still use legacy-shape TOML
  (version = 1, [llm], [sandbox], [vars], [exec], [fabro], etc.);
  tests currently fail to parse against the v2 schema as intended.
  Migrating them is the bulk of Stage 4 and lands in subsequent
  commits.
- OpenAPI ServerSettings schema, generated clients, apps/fabro-web
  workflowData fallback, and docs/reference examples are unchanged
  and land in Stage 5.
2026-04-09 09:49:23 -04:00
Bryan Helmkamp
91b36e7006 feat(types): flesh out v2 subtrees and add legacy bridge
Stage 2 of the settings TOML redesign. Completes the v2 resolved
settings tree and adds a temporary internal bridge so callers can
migrate incrementally during stages 3 and 4.

- run subtree: model (with splice-aware fallbacks), git author,
  prepare steps (script xor command), execution (mode, approval,
  retros as positive-form), checkpoint, sandbox (with local/daytona
  provider leaves and sticky env), notifications (keyed routes with
  slack/discord/teams subtables), interviews (provider + subtables),
  agent (permissions + mcps map), hooks (id-aware ordered list), scm
  (with github leaf), pull_request, artifacts
- cli subtree: target (http/unix), auth (strategy), exec (model,
  agent, prevent_idle_sleep), output (format, verbosity), updates,
  logging
- server subtree: listen (tcp/unix with tls), api, web, auth (api
  jwt/mtls, web providers), storage, artifacts (local/s3 provider
  leaves), slatedb (local/s3 provider leaves), scheduler, logging,
  integrations (github/slack/discord/teams)
- closed ObjectStoreProvider enum so unknown providers hard-fail
  schema validation
- provider-specific subtables use enumerated known providers rather
  than flatten+HashMap so strict deny_unknown_fields still holds
- bridge module (settings::v2::bridge) with bridge_to_old() mapping
  the v2 resolved tree back to the legacy flat Settings shape for
  fields that current consumers read. Env interpolation emits raw
  source form; resolution is a Stage 3 concern
- representative_full_tree_parses integration test exercises the
  canonical example from the brainstorm document end-to-end
- 140 tests passing; workspace clippy-clean under -D warnings
2026-04-09 09:17:37 -04:00
Bryan Helmkamp
ab9a2d9418 feat(types): add settings v2 parse tree scaffolding
Stage 1 of the settings TOML redesign. Introduces the namespaced v2
schema module alongside the existing flat Settings shape so the
workspace still builds while the new parser architecture comes online.

- value-language helpers with full unit-test coverage:
  - Duration: single-unit suffixes (ms, s, m, h, d); rejects composed
    values like '1h30m'; canonical renderer picks the largest unit
  - Size: decimal (KB, MB, GB, TB) and binary (KiB, MiB, GiB, TiB)
    units; bare integers default to GB; canonical renderer picks the
    largest decimal unit
  - ModelRef: bare vs qualified forms with a ModelRegistry trait for
    later ambiguity resolution
  - InterpString: ${env.NAME} tokens with whole-value, substring, and
    multi-token support; provenance tagging for outward-facing redaction
  - SpliceArray: '...' marker with append, prepend, and single-marker
    enforcement
- SchemaVersion pre-validation: missing defaults to 1, legacy 'version'
  key hard-fails with a rename hint, unsupported higher versions
  hard-fail with an upgrade hint
- SettingsFile top-level sparse parse tree with strict unknown-key
  rejection and targeted rename hints for every legacy top-level
  section (llm, vars, exec, fabro, setup, sandbox, etc.)
- Skeleton ProjectLayer/WorkflowLayer/RunLayer/CliLayer/ServerLayer/
  FeaturesLayer with deny_unknown_fields; full subtree fleshed out in
  Stage 2

65 new unit tests all passing. fabro-types is clippy-clean under
-D warnings.
2026-04-09 09:02:45 -04:00
Bryan Helmkamp
fa3507d8c7 feat(artifacts): remove scratch artifact cache staging
Stage captured artifacts in per-attempt tempdirs and persist them through an
explicit artifact sink instead of writing into run scratch cache.

Server-managed and test-owned runs now write directly to ArtifactStore, while
CLI worker runs keep the staged upload path. The local run summary now prints
durable artifact identifiers and copy hints rather than scratch-cache paths,
and the run-directory docs and integration coverage were updated to match.
2026-04-08 17:42:55 -04:00
Bryan Helmkamp
2fd5411f8f fix(test): remove dist/ dependency from source_maps_are_not_served test
Use a synthetic .map path instead of scanning apps/fabro-web/dist at
runtime, which requires a prior bun build and breaks on fresh checkouts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:51:23 -04:00
Bryan Helmkamp
56228ea07e test(cli): add integration tests for fabro uninstall
7 IT tests in cmd/uninstall.rs covering:
- help snapshot
- not-installed detection (plain + JSON)
- dry-run preview without deleting
- --yes removes ~/.fabro/
- --json inventory output (dry-run + execute)

Also fixes the "not installed" check to use marker files
(settings.toml, certs/, storage/) instead of directory existence,
since the CLI's logging startup may auto-create the directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:47:48 -04:00
Bryan Helmkamp
ed70aa6dda fix(test): remove workspace test build warnings
Drop the unused test-only import in fabro-server and the unused test
binding in fabro-workflow so workspace test builds stay warning-free.
2026-04-08 16:47:23 -04:00
Bryan Helmkamp
ad4c5f0c98 fix(cli): restore fabro-cli verification after merge
Resolve new clippy failures introduced by the merge and update the root
help snapshot to include the uninstall command so fabro-cli lint and
test verification return to green.
2026-04-08 16:41:13 -04:00
Bryan Helmkamp
f0d57dacc9 Merge remote-tracking branch 'origin/main' 2026-04-08 16:32:44 -04:00
Bryan Helmkamp
166b60e3b1 feat(cli): add fabro uninstall command
Adds a top-level `fabro uninstall` command that reverses `fabro install`
and `install.sh`. Defaults to dry-run (preview) mode, requiring `--yes`
to execute.

Features:
- Inventory and dry-run preview with sizes and `--json` support
- Server shutdown (guarded — only when server is running)
- Safety guardrails (refuses to delete /, $HOME, or dirs without markers)
- Shell config cleanup (exact `# fabro` sentinel match, PATH validation,
  atomic write via temp+rename)
- Binary status reporting with tailored brew/cargo/manual hints
- Exit code: 0 on success, 1 on critical failure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:31:32 -04:00
Bryan Helmkamp
30e63b270f Merge remote-tracking branch 'origin/main' 2026-04-08 16:30:26 -04:00
Bryan Helmkamp
367fd9302b refactor(cli): centralize command settings and server access
Add CommandContext to load machine settings once per invocation, cache
server access, and route migrated commands through the shared
ServerStoreClient path instead of reloading settings and reconnecting ad
hoc.
2026-04-08 16:27:25 -04:00
Bryan Helmkamp
e098d8d4c0 refactor: remove negative scratch assertions and rename child dir to stages/
Remove test assertions that verified legacy files (final.patch,
workflow_bundle.json, manifest.json, cache/artifacts/values/) do not
exist in scratch directories — these are a test smell since the code
that wrote them is long gone.

Also rename child workflow scratch path from nodes/{id}_{visit}/child
to stages/{id}@{visit}/child to align with stage_id convention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:23:02 -04:00
Bryan Helmkamp
45675fc42d feat(server): make web ui optional
Add a server-side web.enabled toggle and CLI overrides so Fabro can run
with API and health only while disabling the embedded SPA, browser auth
routes, and web-only helper endpoints.
2026-04-08 16:22:41 -04:00
Bryan Helmkamp
150443123c Merge remote-tracking branch 'origin/main' into production-web-ui 2026-04-08 16:07:38 -04:00
Bryan Helmkamp
15d1c67be9 fix: resolve clippy warnings across workspace
- fabro-types: remove redundant "freeform" match arm (match_same_arms)
- fabro-server: use let...else and remove needless return
- fabro-cli/runner: use while-let instead of match loop, unwrap Option
  from build_artifact_uploader return type
- fabro-cli/attach: introduce AttachOptions struct to reduce bool
  parameter count (fn_params_excessive_bools)
- fabro-test: fix unused variable and needless continue in session lock

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:56:51 -04:00
Bryan Helmkamp
6b0419bc10 fix(test): eliminate session lock race and openssl /dev/stdin flake
Two flake sources identified across 100+ full-suite runs:

1. Session lock EINVAL race: cleanup_session_root's remove_dir_all
   could delete the session root between with_session_lock's
   create_dir_all and File::create, causing EINVAL. Fix: retry the
   create-dir + create-file sequence as a unit.

2. mTLS cert generation: openssl req -key /dev/stdin failed under fd
   pressure with "Bad file descriptor". Fix: read from the already-
   written server.key file path instead of piping through /dev/stdin.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:48:51 -04:00
Bryan Helmkamp
abd87909ae feat(server): support host-only tcp binds
Accept `--bind <ip>` as a TCP bind request while keeping the default
Unix socket behavior unchanged. Resolve host-only TCP binds inside the
serving process so startup output, server metadata, and status always
reflect the concrete host:port, preferring 32276 and falling back to a
random port with a warning when needed.
2026-04-08 15:43:09 -04:00
Bryan Helmkamp
1a1c998dc9 fix(workflow): cancel devcontainer lifecycle commands on shutdown
Pass the run-scoped cancellation flag into devcontainer lifecycle
commands so startup shutdown interrupts those commands promptly and
preserves the cancelled workflow result. Add workflow regression tests
for cancelled setup and devcontainer startup paths.
2026-04-08 15:06:11 -04:00
Bryan Helmkamp
bbd04111ad fix(workflow): cancel setup commands during startup shutdown
Reuse the existing sandbox cancellation bridge for workflow setup
commands so server-side startup cancellation interrupts setup work
promptly and preserves the cancelled terminal state under nextest.
2026-04-08 14:58:32 -04:00
Bryan Helmkamp
f7d86d72e2 Merge remote-tracking branch 'origin/main' into production-web-ui 2026-04-08 14:44:35 -04:00
Bryan Helmkamp
8726065fd1 feat: embed fabro spa and align interruption semantics
Move the built web bundle into an embedded fabro-spa crate so Cargo and
release builds no longer depend on Bun at build time, and preserve the
local dev override path for fast UI iteration.

At the same time, rename interview and agent-level aborted flows to
interrupted, keep cancelled for run-level shutdown, and stop reporting
skipped answers as interruptions in the run event stream.
2026-04-08 14:43:44 -04:00
Bryan Helmkamp
a0e4c648c7 fix(test): reduce test server stop timeout from 8s to 500ms
The test harness waited 8s for the server to shut down gracefully,
accommodating the server's 5s WORKER_CANCEL_GRACE. But in tests,
the CLI returns before workers exit (terminal SSE event → CLI exits →
TestContext drops → SIGTERM while workers still cleaning up), so the
last test in every session paid a ~5s penalty. No real work needs
preserving in tests, so SIGKILL after 500ms instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 14:29:00 -04:00
Bryan Helmkamp
2138e0cade fix(test): use no_proxy client in detach signal test to prevent macOS timeout flakes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:51:34 -04:00
Bryan Helmkamp
31d80373d4 fix: session cookie decryption and add HTTP endpoint logging
Cookie auth was broken because parse_cookie_header used Cookie::parse
which does not percent-decode values. The cookie crate's private jar
percent-encodes on Set-Cookie but Cookie::parse leaves %2F/%3D intact,
making base64 decryption fail silently. Switch to Cookie::parse_encoded.

Also:
- Add tower-http TraceLayer for request/response logging (DEBUG for
  requests, INFO for responses with status and latency)
- Add structured tracing to all web_auth handlers per logging strategy
- Replace eprintln debug calls with tracing::warn
- Update GitHub App manifest homepage URL to https://fabro.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:46:30 -04:00