Commit graph

27 commits

Author SHA1 Message Date
Scott Werner
2bd04c7935
Demote control-plane config to plain String; native FABRO_WEB_URL read (#510)
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (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
Third reducing PR of the interpolation unification (**D11 resolution
(c)**): the control plane never interpolates. `InterpString` is now
strictly the user-facing workflow config language; server identity,
storage, listen, object-store, and GitHub App identifiers are plain
`String`, consumed where needed with no resolution point.

## Demoted to `String` (was `InterpString`)

Both the layer and resolved types:

- `server.listen.unix.path`, `server.api.url`, `server.web.url`
- `server.storage.root`, `server.artifacts.prefix`,
`server.slatedb.prefix`
- object store: `Local.root`, S3 `bucket` / `region` / `endpoint`
(shared by artifacts + slatedb)
- `github.app_id` / `client_id` / `slug`

**Kept `InterpString`:** `slack.default_channel` (run-time consumption —
the one server-defined survivor). `server.listen.tcp.address` stays the
`SocketAddr` `parsed_value` special case.

## Native `FABRO_WEB_URL` read

Deployment-time late binding now goes through a native env read instead
of a `{{ env.* }}` token: `FABRO_WEB_URL` overrides `server.web.url`
(**env override > settings literal > default**), applied in
`canonical_origin` and reused by the JWT issuer, cookie-secure check,
and system-info. `docker/split-web` no longer ferries the value through
a settings token (compose still sets the env var). `canonical_origin`'s
error message now advertises a knob that is actually true for everyone.

## Behavior change (release notes)

- `{{ env.* }}` / `{{ vars.* }}` tokens in the demoted server fields are
now **literal text**, not interpolated. The resolve layer emits
`warn_if_demoted_template` for every demoted field, so operators with
tokens still in server config **fail loud** rather than silently
treating the token as a literal.
- Operators who relied on env-based storage location should use the
existing native `FABRO_STORAGE_DIR` (`--storage-dir`) override.
`FABRO_STORAGE_ROOT` promotion is intentionally deferred (not a proven
need).

## Cleanup

`fabro-server`'s `crate::interp` shrinks to just the process-env lookup
facade; `resolve_interp` / `_path` / `_with` and the
`AppState::resolve_interp` seam are deleted (nothing resolves
server-scope `InterpString` anymore).

## Verification

- `cargo build --workspace` 
- `cargo +nightly clippy --workspace --all-targets -- -D warnings` 
(incl. the `as_source` gate)
- `cargo +nightly fmt --check --all` 
- `cargo nextest run --workspace`: 6305 passed; added two tests covering
the `FABRO_WEB_URL` override precedence (env-wins and settings-literal
fallback).

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 10:03:14 -04:00
Scott Werner
ce404cddef
Interpolation foundation (InterpString v2) (#472)
# Interpolation foundation (InterpString v2)

First step of unifying config-string interpolation across Fabro. This PR
is the
**behavior-neutral foundation** only — it introduces the type machinery
and a
clippy gate, but changes no field's interpolation behavior. The actual
field
work follows as separate stacked PRs, sequenced **reduce-first**:
narrowing
changes (demote fields that shouldn't interpolate, de-template DOT
attrs) land
before capability additions (resolve env in MCP / prepare / hooks).

## Why

Config strings interpolate `{{ ... }}` inconsistently today — some
fields
resolve `{{ env.X }}`, others are typed as if they do but silently pass
the
literal template text downstream. We're converging on three field types
(`String`, `InterpString`, and later an importable template for
prompts/goals)
with four namespaces (`env`, `vars`, `secrets`, `inputs`). This PR lays
the
`InterpString` foundation; it does not migrate any field.

## What's in it

- Segments generalize to `Token { namespace, name }` with a `Namespace`
enum
(`env`/`vars`/`secrets`/`inputs`). `secrets`/`inputs` are **reserved** —
  parsed as tokens ahead of their resolvers.
- `ResolveCtx` with per-namespace lookups. `resolve_with()` fails loudly
  (`Unavailable`) for a token whose namespace isn't provided in context;
`substitute_with()` substitutes provided namespaces and preserves the
rest.
`resolve()` / `substitute_variables()` are thin wrappers over one core
path.
- `ResolveEnvError` → `ResolveError { namespace, name, kind: Missing |
Unavailable }`
(message text unchanged for env/vars; the kind no longer bakes the
namespace
  in, so it scales to four namespaces without an enum explosion).
- `Provenance` tracks secret-sourced names alongside env-sourced, for
uniform
  redaction later.
- **`as_source()` is clippy-gated** (`disallowed-methods`). It keeps its
name;
  every call site carries an `#[expect(..., reason)]` classifying it
(serialization, error display, known-leak-pending-fix, demotion-pending,
test). The lint turns the leak surface into a greppable, reasoned
work-list
  and the method stays for its permanent uses (serde round-trip of the
  unresolved template + diagnostics).
- fabro-server: five duplicate `process_env_var` facades and two
duplicate
  `resolve_interp` helpers consolidated into one `crate::interp` module.

## Behavior changes (honest list)

- **`{{ secrets.* }}` / `{{ inputs.* }}` are now reserved.** On main
they
  weren't recognized as tokens → silent literal passthrough. Now, at
`resolve()` consumers they **fail loud** (`Unavailable`) instead of
passing
the literal string through (nobody wants the literal characters as a
value —
  strictly better, but technically a change). At `as_source` sites they
  round-trip unchanged. Actual resolution lands in later enhancing PRs.
- Some fabro-server resolution errors gain a `"failed to resolve
<source>"`
  context line.

Otherwise behavior-neutral: every field resolves exactly as it did on
main.

## What's deferred to follow-up PRs (reduce-first order)

- **Reducing / cleanup (next):** demote leak fields to `String`
  (`run.model.*`, `cli.exec.model.*`, `run.git.author.*`,
  `run.scm.owner/repository`); de-template `condition`/`label`/`model`/
  `provider`/`speed` and `output_schema`.
- **Enhancing (after):** resolve `{{ env.* }}` in MCP transports,
prepare
  steps, and hooks; wire `secrets`/`inputs`.

## Verification

- `cargo build --workspace`
- `cargo nextest run --workspace` → 6449 passed, 181 skipped
- `cargo +nightly fmt --check --all`
- `cargo +nightly clippy --workspace --all-targets -- -D warnings` →
clean

## Reviewer notes

- The reserved-namespace `Unavailable` error for `secrets`/`inputs` is
  **intentional**, not a missing case — they're parsed ahead of their
  resolvers so misuse fails loud instead of leaking.
- `as_source` is clippy-gated but keeps its name deliberately — the gate
is
  the enforcement; renaming was avoided as unnecessary churn.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 12:51:08 -04:00
Bryan Helmkamp
e952bb4c7f
fix(config): disable Slack unless configured
Require an explicit server.integrations.slack table before Slack reports enabled or starts from vault tokens.
2026-06-02 18:41:36 -04:00
Bryan Helmkamp
ab0f94fd82
feat(system): report runtime integration status (#416)
## Summary

Settings > Integrations now reflects the server's actual integration
readiness instead of only static `settings.toml` booleans. This adds
`/api/v1/system/integrations` as the runtime source of truth, covering
server config, vault credential presence, and Slack Socket Mode
connection state.

## What Changed

- Added shared `fabro-types` integration status models and reused them
from `fabro-api` to avoid duplicate API/domain types.
- Added `GET /api/v1/system/integrations` to the OpenAPI spec, Rust
server routes, demo routes, and generated TypeScript client.
- Reports GitHub and Slack status as `disabled`, `missing_credentials`,
`configured`, `connecting`, `connected`, or `error`, with non-secret
metadata and missing credential names.
- Tracks Slack Socket Mode runtime state from the Slack connection loop
and respects explicit `server.integrations.slack.enabled = false` even
when vault tokens exist.
- Updated the Integrations settings page to read the new runtime
endpoint, so a vault-configured Slack setup no longer appears simply as
disabled.

## Verification

- `cargo build -p fabro-api`
- `cargo nextest run -p fabro-api system_integrations`
- `cargo nextest run -p fabro-config
resolved_server_integrations_are_slack_only_for_chat`
- `cargo nextest run -p fabro-slack
run_event_loop_notifies_connected_status`
- `cargo nextest run -p fabro-server --features test-support --test it
get_system_integrations`
- `cargo nextest run -p fabro-server`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cd apps/fabro-web && bun test
app/routes/settings-integrations.test.tsx app/lib/query-keys.test.ts`
- `cd apps/fabro-web && bun run typecheck`
- `cd apps/fabro-web && bun run build`

---

[![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-26 19:27:32 -04:00
Bryan Helmkamp
02a87cb650
fix(settings): show scheduler slot usage (#404)
## Summary

Fixes the Settings Resources concurrency meter so it reports scheduler
capacity usage instead of all non-terminal runs. `/api/v1/system/info`
now exposes `runs.scheduler_slots_used`, computed from the same status
predicate the scheduler uses, while `runs.active` remains unchanged for
existing lifecycle semantics.

The settings page uses only the new slot count, so pending approval runs
and runnable queued runs no longer make the concurrency meter look full.

## Verification

- `cargo build -p fabro-api`
- `cargo nextest run -p fabro-server --features test-support
worker_started_child_run_requires_approval_before_becoming_runnable`
- `cargo nextest run -p fabro-server --features test-support
scheduler_capacity_counts_only_runs_occupying_slots`
- `cargo nextest run -p fabro-server --features test-support
get_system_info_returns_runtime_fields`
- `cargo nextest run -p fabro-server --features test-support
test_app_state_with_options_respects_max_concurrent_runs`
- `cargo nextest run -p fabro-server --features test-support
openapi_conformance`
- `bun test app/routes/settings-monitoring.test.tsx`
- `bun run typecheck`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 (context unknown, reasoning unknown) via
[Codex](https://openai.com/codex)
2026-05-25 18:28:18 -04:00
fabro-sh-0530[bot]
f73f2a53f3
Replace queued with pending/runnable and add approval flow (web + API s… (#371)
## Summary

Replaces the single `queued` pre-execution state with explicit `pending`
and `runnable` states, and wires approve/deny actions for
parent-generated child runs that require human approval before they can
execute. This diff covers the web UI and OpenAPI spec layers of that
change.

## What changed

**Run status model**
- `queued` is removed from all TypeScript types, display maps, column
routing, and tests.
- `pending` (awaiting approval) and `runnable` (eligible for the
scheduler) replace it as distinct board columns and `RunStatus` variants
with their own labels and colors (`runnable` gets cyan; `pending` stays
muted).

**Approval actions**
- New `approveRun` / `denyRun` API calls in `run-actions.ts` invoke the
new `POST /runs/{id}/approve` and `POST /runs/{id}/deny` endpoints.
- `canApprove` predicate requires both `status.kind === "pending"` and
`lifecycle.approval?.state === "pending"` — a run whose status is
pending but has no approval record does not expose the action.
- `useApproveRun` / `useDenyRun` mutations in `mutations.ts` follow the
same pattern as `useCancelRun`.
- `ActionsMenu` in `run-detail.tsx` gains Approve (lifecycle group) and
Deny (destructive group) menu items.

**Board and event plumbing**
- `columnForStatus` now routes `pending → pending column` and `runnable
→ runnable column`; `submitted` stays in the pending column.
- `BOARD_STATUS_EVENTS` and `RUN_SUMMARY_EVENTS` replace `run.queued`
with `run.start_requested`, `run.pending`, `run.approved`, `run.denied`,
and `run.runnable`.
- The `pending` column is hidden when empty (same behaviour the old
`queued` column had).

**Waterfall phases (`run-phases.ts`)**
- `queued` phase is removed; `pending` and `runnable` phases are added
in order.
- The submitted phase closes at `run.start_requested` rather than
`run.queued`.
- Each phase derives its timestamps from its own event rather than a
single `firstTs` lookup, making multi-phase pre-execution timelines
accurate.

**OpenAPI spec**
- `POST /api/v1/runs/{id}/approve` and `POST /api/v1/runs/{id}/deny`
endpoints added with 200/404/409 responses.
- `startRun` description updated to describe the pending/runnable
branching behaviour.
- `cancelRun` description updated to reference `pending`/`runnable`
instead of `queued`.

### Plan Summary

- **Task 3** (OpenAPI schema additions for approve/deny endpoints) —
complete in this diff.
- **Task 6** (Web UI surfaces: board columns, run-detail actions,
waterfall phases, event subscriptions) — complete in this diff.
- **Task 7** (doc cleanup: references to `queued` replaced in plans,
brainstorms, and QA docs) — complete in this diff.


### Fabro Details

<details>
<summary>Ran 9 stages in 127m 37s for $104.98</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 2s | – | 0 |
| preflight_compile | 2m 15s | – | 0 |
| preflight_lint | 2m 29s | – | 0 |
| implement | 92m 10s | $91.53 | 0 |
| simplify_opus | 18m 35s | $10.65 | 0 |
| simplify_gpt | 7m 36s | $2.81 | 0 |
| verify | 3m 42s | – | 0 |
| fmt | 3s | – | 0 |
| **Total** | **127m 37s** | **$104.98** | **0** |

</details>

<details>
<summary>Ran <code>ImplementPlan.fabro</code> (12 nodes and 15
edges)</summary>

```dot
digraph ImplementPlan {
    graph [
        goal="Implement and simplify",
        model_stylesheet="
            * { model: claude-opus-4-7; }
        "
    ]
    rankdir=LR

    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    toolchain         [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
    preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", max_retries=0]
    fix_lints         [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
    implement         [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD.", model="gpt-55", reasoning_effort="xhigh"]
    simplify_opus     [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
    simplify_gpt      [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"]
    verify            [label="Verify", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1 && cargo dev docs refresh 2>&1 && cargo dev docs check 2>&1", goal_gate=true, retry_target="fixup"]
    fixup             [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings, test failures, and generated docs errors.", max_visits=3]
    fmt               [label="Format", shape=parallelogram, script="cargo +nightly-2026-04-14 fmt --all 2>&1", max_retries=0]

    start -> toolchain
    toolchain -> preflight_compile [condition="outcome=succeeded"]
    toolchain -> exit
    preflight_compile -> preflight_lint [condition="outcome=succeeded"]
    preflight_compile -> exit
    preflight_lint -> implement [condition="outcome=succeeded"]
    preflight_lint -> fix_lints
    fix_lints -> preflight_lint
    implement -> simplify_opus -> simplify_gpt -> verify
    verify -> fmt   [condition="outcome=succeeded"]
    verify -> fixup
    fixup -> verify
    fmt -> exit
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: fabro <fabro@anthropic.com>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
2026-05-23 15:34:33 -04:00
Bryan Helmkamp
4190e13a20
Collapsible run stage sidebar (#352)
## What

Makes the run-detail stage sidebar (shown on the Overview and Stages
tabs) collapsible with a slide animation.

- A toggle button slides the panel between full width (`w-56`) and an
icon-only rail (`w-12`), animating `width` over 300ms with the same
easing as the Ask Fabro panel.
- When collapsed, **stage status icons stay visible** — green check /
red X / spinning teal for running — so run progress is still scannable
at a glance. Workflow links (Graph Source, Run Logs, etc.) collapse to
icons too so they remain reachable.
- Labels and durations become `sr-only` with `title` tooltips for hover.
- The open/closed choice persists to `localStorage`
(`fabro:stage-sidebar-collapsed`), carrying across the Overview and
Stages tabs and reloads.

## Layout

- The collapse toggle is inline with the `STAGES` heading row (or
`WORKFLOW` when a run has no stages yet), so it doesn't push the stage
list down.
- The stage sidebar's top padding on the Stages tab was reduced (`pt-6`
→ `pt-3`) so the heading aligns with the adjacent content column and
sits closer to the tab nav.

## Notes

Self-contained in `StageSidebar` — `run-overview.tsx` and
`run-stages.tsx` render it inside flex layouts that already track its
width, so the slide works in both with no parent changes (aside from the
padding tweak).

Verified: `tsc` typecheck passes; `stage-sidebar` lib tests pass
(10/10).

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fabro-sh-0530[bot] <281434857+fabro-sh-0530[bot]@users.noreply.github.com>
Co-authored-by: Fabro <noreply@fabro.sh>
2026-05-22 12:20:29 -04:00
Bryan Helmkamp
37d6b3dbcd
fix(server): count whole storage tree in Fabro-managed bytes
build_disk_usage_response only summed scratch/ run dirs and logs/*.log,
omitting objects/ (SlateDB + artifacts), sessions/, and vaults/ — a ~30x
undercount of "Fabro managed" storage on the resources page.

Measure the whole storage_dir tree for total_size_bytes so it can't drift
as new subdirectories are added. Reclaimable stays a curated estimate that
matches what `fabro system prune` actually frees. A residual "other"
summary row keeps `fabro system df` totals consistent and surfaces as a
"Database & artifacts" table row.

Also add a KiB tier to formatBytesAsMemory so small storage values render
human-readably instead of raw byte counts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 11:28:39 -04:00
Bryan Helmkamp
40ed64c1c2
Add system resources settings page (#328)
## Summary

Adds server-visible resource reporting and a compact Resources settings
tab for CPU, memory, and the filesystem that contains Fabro storage.

## Changes

- Adds `GET /api/v1/system/resources` backed by `sysinfo`, including CPU
sampling, cgroup-aware memory reporting, storage filesystem matching,
and Fabro-managed disk byte totals.
- Extends the OpenAPI contract and regenerates the Rust and TypeScript
API clients.
- Adds a deterministic demo-mode resources route.
- Adds `/settings/resources` with 5 second polling and panels for
overview, CPU, memory, disk, and notes.
- Adds server integration/unit coverage and web route/render coverage.

## Screenshot

![Resources settings
page](https://raw.githubusercontent.com/fabro-sh/fabro/feature/system-resources-settings/docs/public/images/web/settings-resources.jpg)

## Verification

- `cargo build -p fabro-api`
- `cd lib/packages/fabro-api-client && bun run generate`
- `cargo nextest run -p fabro-server --features test-support --test it
api::system`
- `cargo test -p fabro-server resource_sampler::tests`
- `cd apps/fabro-web && bun test`
- `cd apps/fabro-web && bun run typecheck`
- `cd apps/fabro-web && bun run build`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound%20Engineering-Codex-6f42c1)](https://github.com/compound-engineering)

🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 17:22:53 -04:00
Bryan Helmkamp
5fc9157017
refactor(workflow): remove retro stage (#230)
## Summary

Removes Fabro's automatic retro generation stage so workflow runs go
directly from execution to finalization and optional PR creation. This
drops the retro-specific crate, events, projection fields, config/API
knobs, and user-facing docs in favor of the existing durable run
observability surfaces.

## What Changed

- Deleted the `fabro-retro` crate and the workflow `retro` pipeline
phase, with finalization now consuming `Executed` state directly.
- Removed retro configuration and API surface area, including
`--no-retro`, `[run.execution].retros`, manifest `no_retro`,
`features.retros`, and run projection `retro*` fields.
- Retired typed `retro.*` events while keeping historical event logs
readable by deserializing retired retro event names as `Unknown`.
- Stopped appending retro sections to generated PR bodies and updated
docs, marketing copy, screenshots, and navigation to point users toward
observability/event-stream inspection.

## Testing

Not run during PR creation; this branch already contained the
implementation commit.

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 (unknown context, reasoning unspecified) via
[Codex](https://openai.com/codex)
2026-05-09 10:18:20 -04:00
Bryan Helmkamp
8d7b9a804a
Unify run event principals 2026-05-01 21:56:47 -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
8f47bc9317
migrate server tests off raw settings layers 2026-04-23 18:31:33 -04:00
Bryan Helmkamp
27cc1bb6c2
test(server): make global attach filter deterministic 2026-04-22 16:04: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
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
84f3c80566
refactor(api): move features flags from /auth/me to /system/info
Features like session_sandboxes and retros are server-level capability
flags, not user settings. Expose them on GET /system/info where they
belong alongside other server metadata.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 08:51:22 -04:00
Bryan Helmkamp
378073c13a
fix(tests): satisfy clippy in system api test 2026-04-14 23:36:05 -04:00
Bryan Helmkamp
bdbfcd9d81
refactor(server): simplify AppState construction
Replace the internal positional AppState builder with an AppStateConfig
and route both production and test setup through the new config-backed
path. Preserve the in-process test helper behavior while fixing the
ignored max_concurrent_runs argument with a regression test.
2026-04-14 21:35:55 -04:00
Bryan Helmkamp
05c7fedd31 refactor(server): remove implicit dry-run fallback
Remove the server startup path that inferred dry-run from provider
availability and let run.execution.mode inherit normally from
settings.

Model tests now return skip for unconfigured providers at request
time, completions use the real error path, and the CLI/docs/tests are
updated for the removed server --dry-run flag.
2026-04-14 12:28:55 -04:00
Bryan Helmkamp
5eeacd7864 fmt 2026-04-11 11:27:46 -04:00
Bryan Helmkamp
c5c81d2985 refactor(settings): rename settings layer and move parsing 2026-04-10 08:10:06 -04:00
Bryan Helmkamp
4e7839c202 refactor(settings): stage 6.5b sweep ::v2:: prefix out of consumers
Final mechanical pass: replaces every remaining
`fabro_types::settings::v2::*` import path with
`fabro_types::settings::*` (or the appropriate submodule) across 53
files in 10 crates, then deletes the transitional
`pub mod v2 { pub use super::*; }` alias from
`fabro-types/src/settings/mod.rs`.

No functional changes — all touches are `sed s|settings::v2::|settings::|g`
on import statements and fully-qualified type paths. The v2
namespace is now fully gone; the authoritative module path is
`fabro_types::settings::{accessors, cli, duration, features, interp,
model_ref, project, run, server, size, splice_array, tree, version,
workflow}`.

All 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:42:05 -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
ba02af2f88 feat(run): harden server-supervised worker lifecycle
Move subprocess workers fully behind the server-owned run store by
switching worker/server coordination to HTTP-backed run events and
control state. Reconcile stale in-flight runs on boot, terminate live
workers during shutdown, and update process titles to reflect server and
worker lifecycle phases.
2026-04-07 07:59:35 -04:00
Bryan Helmkamp
38a2306e2e feat(system): add server-backed system commands 2026-04-06 16:10:06 -04:00