Commit graph

46 commits

Author SHA1 Message Date
Calen Pennington
596b105b06
fix(skills): Ensure the local server can access project skills (#383)
The local server wasn't setting git_root, and wasn't defaulting to the
workspace working directory, so it didn't find project-specific skills.

---------

Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
2026-05-24 13:22:00 -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
fabro-sh-0530[bot]
54bc67017e
feat: Replace duration/elapsed fields with wall_time_ms and StageTiming (#343)
## Summary

Replaces the ambiguous `runtime_secs`, `elapsed_secs`, and `duration_ms`
timing fields on run/stage public API surfaces with explicit
`wall_time_ms` (elapsed clock time) and a `StageTiming` value object
that also carries `inference_time_ms`, `tool_time_ms`, and
`active_time_ms`.

This is a greenfield breaking change — no compatibility shims are
preserved.

### What changed

**API shape**
- `RunBillingStage.runtime_secs` → `RunBillingStage.timing: StageTiming`
- `RunBillingTotals.runtime_secs` → `RunBillingTotals.timing:
StageTiming`
- `RunSummary.timestamps.duration_ms` / `elapsed_secs` removed; a
top-level `timing: StageTiming | null` field added
- Stage list item `duration_secs` → `wall_time_ms`

**Web app (`apps/fabro-web`)**
- `run-billing.tsx`: `liveRuntimeSecs` → `liveWallTimeMs`; live ticking
now returns milliseconds and the footer total sums `wallTimeMs` across
rows
- `stage-sidebar.ts`: `duration_secs` → `wall_time_ms` for the per-stage
duration display
- `runs.ts`: `elapsed_secs` lookup replaced with `timing.wall_time_ms`
- `formatElapsedSecs` / `formatDurationSecs` call sites replaced with
`formatDurationMs`

**Lockfile / tooling**
- `@openapitools/openapi-generator-cli@2.20.2` added as a dev dependency
to `@qltysh/fabro-api-client` to support regenerating the TypeScript
client after schema edits; several transitive deps pulled in alongside
it.

### Design notes

- **Units are now consistent**: every timing value on run/stage surfaces
is in milliseconds; the old API mixed seconds (`runtime_secs`,
`elapsed_secs`) with milliseconds (`duration_ms`).
- **Live ticking** still works correctly: the in-flight billing row
computes `now - startedAt` in ms and sums across rows for the footer,
avoiding a server round-trip during a running stage.
- **`StageTiming.active_time_ms = inference_time_ms + tool_time_ms`** —
parallel work is summed, so run active time can exceed wall time.
- Subsystem-internal `duration_ms` fields (sandbox setup, devcontainer
lifecycle, hooks) are intentionally left unchanged; only public
run/stage timing surfaces are affected.


### Fabro Details

<details>
<summary>Ran 9 stages in 115m 53s for $108.50</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 6s | – | 0 |
| preflight_lint | 2m 18s | – | 0 |
| implement | 80m 53s | $101.97 | 0 |
| simplify_opus | 21m 35s | $4.09 | 0 |
| simplify_gpt | 5m 1s | $2.44 | 0 |
| verify | 3m 11s | – | 0 |
| fmt | 3s | – | 0 |
| **Total** | **115m 53s** | **$108.50** | **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."]
    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: Bryan Helmkamp <bryan@brynary.com>
2026-05-21 21:06:40 -04:00
Bryan Helmkamp
66519ee12a
feat(errors): add structured failure diagnostics (#277)
## Summary
- Make `FailureDetail` the canonical rich diagnostic shape for stage and
terminal failures, with terminal `RunFailure` carrying `{ reason, detail
}`.
- Preserve cause chains and move process stdout/stderr diagnostics into
sanitized `exec_output_tail` instead of embedding them in messages or
causes.
- Update ACP error plumbing, CLI/server/store rendering, OpenAPI, and
the generated TypeScript API client for the nested failure contract.

Closes #273

## Test Plan
- `cargo nextest run -p fabro-types -p fabro-core -p fabro-acp -p
fabro-api -p fabro-store -p fabro-server -p fabro-workflow -p fabro-cli
--no-fail-fast -E 'not test(/returns_svg/)' --status-level fail
--final-status-level fail`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `bun run typecheck` in `lib/packages/fabro-api-client`
- `bun run typecheck` in `apps/fabro-web`
2026-05-16 13:25:07 -04:00
Bryan Helmkamp
6297b200f7
refactor(run): add rich failure contract (#256)
## Summary

Terminal run failures now use a first-class `RunFailure` contract so
downstream consumers receive structured diagnostics instead of flat
`error` / `causes` / `reason` fields. The wire shape keeps concise
public messages, source-chain causes, classification, optional
actor/signature data, and redacted exec output tail in one nested value.

Refs fabro-sh/fabro#198

## What Changed

- Added `fabro_types::RunFailure` and changed `run.failed` to emit
`properties.failure` with `final_git_commit_sha` for failed-run commit
state.
- Replaced `Conclusion.failure_reason` with `Conclusion.failure` while
leaving stage-level `StageCompletion.failure_reason` untouched.
- Updated workflow internals to preserve owned error source chains until
terminal event projection, then convert them into `RunFailure.causes`.
- Updated store, server, CLI, OpenAPI, and generated TypeScript client
consumers to use the nested failure object.
- Added serialization, OpenAPI replacement, projection, and lifecycle
coverage for the new contract.

## Validation

- `cargo nextest run -p fabro-api -p fabro-types -p fabro-workflow -p
fabro-store -p fabro-server -p fabro-cli`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cd apps/fabro-web && bun run typecheck`
- `cd apps/fabro-web && bun test`
- `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-13 12:32:28 -04:00
Bryan Helmkamp
cccb557281
feat(api): unify public run shape
Return canonical Run payloads across run list, board, create, and lifecycle endpoints. Move archive state out of RunStatus and into lifecycle metadata, split sandbox runtime from planned sandbox data, and separate static pull request records from live pull request details.

Regenerate the TypeScript API client and migrate web, CLI, server, store, workflow, and API tests to the new contract.
2026-05-10 20:48:55 -04:00
Bryan Helmkamp
81af03edcd
fix(api): keep archive mutation test on typed answers 2026-05-08 10:28:09 -07:00
Bryan Helmkamp
b0dfd6b3b4
fix(api): type interview answer submissions
Replace the loose interview answer request payload with a discriminated OpenAPI union so generated clients enforce the wire contract. Surface structured HTTP error details in the web client and update browser, CLI, and server answer submission paths to use the typed variants.
2026-05-08 09:18:35 -07:00
Bryan Helmkamp
befb2e00ec
feat(runs): merge command output streams
Route command stderr into stdout at execution time and expose a single output log across events, projections, API clients, and the web UI. Keep replay compatibility for older command.completed events that still contain split stdout/stderr fields.
2026-05-07 22:07:13 -07:00
Bryan Helmkamp
36c686b828
test(server): avoid float equality in usage scenario
Use a tolerance-based runtime assertion so workspace clippy can run with float_cmp denied.
2026-05-04 11:12:40 -04:00
Bryan Helmkamp
253af11508
refactor(billing): simplify run-billing post-review cleanups
Use BilledTokenCounts::default() for the non-LLM branch, hoist the
by-model stage count and hasLlmStages predicate out of JSX, and drop
the in-test for-loop in favor of iterator-based assertions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:01:04 -04:00
Bryan Helmkamp
95eb13750a
fix(billing): render non-LLM run stages
Include completed stages without LLM usage in run billing responses so command-only runs still show runtime rows. Keep token and model aggregates scoped to billed LLM usage, and render placeholder values in the web billing table.
2026-05-03 22:49:33 -04:00
Bryan Helmkamp
886173f5ad
refactor(server): gate test app state helpers
Move test-only app state constructors into the gated test_support module
so release builds do not expose or link fixture helpers and secrets.
2026-05-02 13:02:41 -04:00
Bryan Helmkamp
43f03087e6
Merge remote-tracking branch 'origin/main' 2026-05-02 10:06:25 -04:00
Bryan Helmkamp
8d7b9a804a
Unify run event principals 2026-05-01 21:56:47 -04:00
Bryan Helmkamp
95dae5afac
feat(run): separate stage state and artifact retries 2026-05-01 20:10:47 -04:00
Bryan Helmkamp
8f47bc9317
migrate server tests off raw settings layers 2026-04-23 18:31:33 -04:00
Bryan Helmkamp
6c3211e3ce
refactor(run-status): unify tagged lifecycle states 2026-04-22 17:15:13 -04:00
Bryan Helmkamp
c6cafceae0
test: de-flake pr_list and full_http_lifecycle_cancel
Two CLI/server tests racing against peer state on the shared fabro server
session, surfaced by running the default nextest profile 20 times.

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:04:08 -04:00
Bryan Helmkamp
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
48eb5efed6
refactor(archive): consolidate helpers after review
Three cleanups from `/simplify` review:

- Promote `archived_rejection_message` and `ensure_not_archived` to `pub`
  via operations/mod.rs and reuse them from `resume`, the CLI rewind
  caller, and the server's `reject_if_archived` guard so the canonical
  error string lives in exactly one place.
- Tighten `RewindInput.current_status` from `Option<RunStatus>` to
  `RunStatus`. The runtime check for None was enforcing a compile-time
  invariant. CLI callers already load the projection and now surface a
  clean error up-front if it's missing. Drop the None-branch test that
  existed only to cover the removed runtime check.
- Collapse `archive_run` / `unarchive_run` HTTP handlers into a shared
  `run_archive_action` body with an `ArchiveAction` enum, mirroring the
  CLI pattern. Removes ~20 lines of copy-paste and unifies error-mapping.

Also drop narrative comments that referenced plan unit numbers in the
scenario tests, and clean up the convoluted `ps_runs` helper pattern
that built an empty-slot arg vec before filling it in.

No behavior change. Full workspace: 4185 tests pass, clippy clean.
2026-04-19 18:14:16 -04:00
Bryan Helmkamp
828a09636e
test(server): cover submit_answer, artifact, sandbox, blob guards
Extends `archived_runs_reject_mutations_with_actionable_body` to assert
the archive guard fires on the four write surfaces the Unit 4 audit
guarded but the scenario skipped: POST /questions/{qid}/answer, POST
/stages/{stage_id}/artifacts, PUT /sandbox/file, POST /blobs. Synthetic
stage/question/filename values are fine — `reject_if_archived` runs
before each endpoint's state-specific lookups.
2026-04-19 17:59:32 -04:00
Bryan Helmkamp
3ae46b37c1
test(server): verify R14 contract and tighten rewind guard
Scenario coverage for the plan's R14 read-only-on-archived contract over
HTTP:

- archived_runs_reject_mutations_with_actionable_body drives a run to
  succeeded, archives it, then asserts 409 on /cancel, /pause, /unpause,
  /start, and /events with the actionable 'fabro unarchive' body.
- appending_run_archived_event_directly_is_rejected covers the widened
  denylist on append_run_event.
- archive_returns_404_for_unknown_run proves the RunNotFound mapping.
- list_runs_respects_include_archived_flag exercises Unit 5's listing
  filter.

Also adds inline server.rs tests that pin the spec/router behavior at
the unit layer and documents that rewind.rs now requires callers to
pass current_status (already threaded through from the CLI and scenario
tests).
2026-04-19 17:42:50 -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
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
6a87f0a071 fmt: apply nightly rustfmt after merge
Restore a clean nightly rustfmt baseline on the merged main branch so
cargo +nightly fmt --check --all passes again after bringing in
origin/main.
2026-04-11 13:43:30 -04:00
Bryan Helmkamp
007cfed240 refactor: remove backwards-compat error type aliases
No production deployments exist, so there's no need for migration shims.
Remove all six backwards-compat type aliases (AgentError, SdkError,
CoreError, GraphvizError, StoreError, FabroError) and migrate ~880
callsites to use the canonical Error name directly within each crate,
or qualified imports (e.g., `use fabro_llm::Error as LlmError`) for
cross-crate references. Also fix a pre-existing absolute-path clippy
lint in fabro-server error.rs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 12:51:42 -04:00
Bryan Helmkamp
5eeacd7864 fmt 2026-04-11 11:27:46 -04:00
Bryan Helmkamp
8014f61054 chore(lint): fix all clippy warnings including --tests
Resolve every clippy warning across the workspace when running with
--tests enabled. Previously only library code was lint-clean; test
code had accumulated issues that were invisible without --tests.

Fixes:
- redundant_closure_for_method_calls: |s| s.as_source() -> InterpString::as_source
  (effective_settings, resolve_cli/root/server/features, run_event/record_serde,
  materialize_run) — add InterpString imports where needed
- absolute_paths: inline fabro_types::settings::* paths -> use imports;
  add #![allow(clippy::absolute_paths)] to fabro-cli and fabro-server
  IT test harnesses (matching the existing pattern in integration.rs)
- bool_assert_comparison: assert_eq!(x, true) -> assert!(x)
- needless_raw_string_hashes: r#"..."# -> r"..." where no inner quotes
- field_reassign_with_default: mut + field assign -> struct literal with ..Default
- match_same_arms: merge Timeout | Disconnected arms in attach.rs
- needless_pass_by_value: signal_rx by ref in attach.rs
- unreadable_literal: 9999999999 -> 9_999_999_999
- default_trait_access: Default::default() -> BTreeMap::default()
- items_after_statements: move use to function top
- large_futures: allow in integration.rs test module (test-only, not prod)
- filter_map_bool_then: .filter_map(bool::then) -> .filter().map()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:16:24 -04:00
Bryan Helmkamp
58f400c70c feat(api): flatten EventEnvelope wire JSON (schema v2)
Wire EventEnvelope now inlines the RunEvent payload fields alongside
seq at the top level of the JSON object. The internal Rust
EventEnvelope { seq, payload } stays structurally unchanged; only the
API/SSE serialization layer flattens for clients.

- OpenAPI spec: add stage_id, parallel_group_id, parallel_branch_id,
  tool_call_id, actor to RunEvent; model EventEnvelope as allOf(seq,
  RunEvent); introduce ActorRef/ActorKind schemas.
- fabro-server: rewrite api_event_envelope_from_store to merge seq
  into the payload JSON value before returning the generated flat
  type; remove the now-unused nested ApiRunEvent conversion helper.
- fabro-cli server_client: add wire_event_envelope_into_store helper
  that turns flat wire JSON back into fabro_store::EventEnvelope
  { seq, payload } for internal consumers.
- Regenerate progenitor Rust types and typescript-axios client.
- Update demo stubs, SSE tests, CLI test helpers, and insta
  snapshots to expect the flattened shape and the new stage_id field.

Incidental: the typescript regeneration also picked up prior-merged
spec fields (ApiQuestion stage/timeout/context, upload manifest
batches, web-settings) that were stale in the TS client.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 10:40:21 -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
c33deedb34 fix(test): eliminate recovery, cancel, and label flakes
Stabilize the recovery scenario around rebuilt metadata timing and node
ordinals, make in-process run cancellation converge on a cancelled
reason, and keep the label assertion unit test out of the shared
TestContext session lifecycle.
2026-04-08 10:13:17 -04:00
Bryan Helmkamp
acc92e5358 refactor(interview): simplify pending question handling
Collapse the live answer rendezvous into ControlInterviewer, move pending
question storage onto a shared typed record, and route HTTP and Slack answer
submission through one server-side flow.
2026-04-07 19:44:32 -04:00
Bryan Helmkamp
87bc42be70 refactor(interview): move run answers onto control channels
Persist pending interviews in run state, deliver accepted answers to workers
through the server-owned control path, and remove the old scratch-file and
WebInterviewer transports.

This also moves Slack onto the canonical server answer flow, adds richer
question metadata to the API and run events, and covers the subprocess
question lifecycle with end-to-end tests.
2026-04-07 19:23:36 -04:00
Bryan Helmkamp
d65a4c5f1a Merge remote-tracking branch 'origin/main'
# Conflicts:
#	apps/fabro-web/app/routes/run-overview.tsx
#	apps/fabro-web/app/routes/workflow-detail.tsx
#	apps/fabro-web/app/routes/workflows.tsx
#	lib/crates/fabro-workflow/src/lifecycle/artifact.rs
#	lib/crates/fabro-workflow/src/pipeline/finalize.rs
2026-04-07 15:24:07 -04:00
Bryan Helmkamp
6ca2833e77 refactor(billing): unify the LLM billing domain
Replace the overlapping usage and cost model with canonical billing
primitives centered on ModelRef, ModelHandle, TokenCounts, and
BilledModelUsage. This also renames the public API and web surface from
usage to billing, removes compatibility aliases, and normalizes provider
usage adapters onto the shared billing vocabulary.
2026-04-07 14:33:35 -04:00
Bryan Helmkamp
0eca7d94d6 fix(run): make attach stream terminal-authoritative
Replay persisted run events for attach requests, keep the SSE stream live
only while the run is active, and close on terminal run events instead of
returning 410 for completed runs.

The CLI now treats premature attach EOF as an error, and the affected
integration tests were stabilized around store-backed event ordering and
recovered rewind timelines.
2026-04-07 14:32:23 -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
0421705af0 refactor(server): submit runs and preflight via manifests 2026-04-05 20:07:10 -04:00
Bryan Helmkamp
b6dad44c70 fix(test): satisfy clippy import style in lifecycle tests 2026-04-05 17:55:30 -04:00
Bryan Helmkamp
40783c0e9b Merge remote-tracking branch 'origin/main'
Resolve the fabro-server merge conflicts by keeping the settings-aware test
app-state helper from origin/main while preserving the secret-store-aware
AppState construction added locally.
2026-04-05 17:45:57 -04:00
Bryan Helmkamp
388f110ac0 fix(test): disable worktrees in fabro-server integration tests
Server scenario tests were inheriting the default local sandbox
worktree mode, which meant they created git worktrees and branches
before stage execution. Under suite load that setup intermittently
stalled the run long enough for the scenario polling windows to fail.

Disable worktrees in the shared server test settings and let lifecycle
scenarios use the same test-only settings through a settings-aware
registry factory helper.
2026-04-05 15:33:16 -04:00
Bryan Helmkamp
cdddcdeadf refactor(cli): remove global execution mode 2026-04-05 15:20:00 -04:00
Bryan Helmkamp
b25613271f refactor(test): restructure fabro-server it/ into api/ and scenario/ subdirs
Move integration tests from monolithic api.rs into api/ (single-endpoint
contract tests) and scenario/ (multi-API-step flows), mirroring the CLI's
cmd/ vs scenario/ pattern. Move 3 scheduler-dependent unit tests from
server.rs into it/scenario/ where they get the correct nextest timeout
(kind=test override). Deduplicate shared helpers into helpers.rs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:58:24 -04:00