Skip worktree checkpoint setup when a local sandbox is not backed by a git repository, and keep the API contract aligned with RunSpec serialization for omitted labels.
Move server-only settings reads out of user-facing CLI commands into a
dedicated local_server module, the install/uninstall exceptions, and the
worker subcommand. Adds bin/dev/check-boundary.sh to prevent regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the plan at
docs/plans/2026-04-20-003-refactor-unify-run-vocabulary-metadata-plan.md.
- Rename RunRecord to RunSpec and RunProjection.run to .spec everywhere
in Rust source, tests, helpers, test names, and error messages.
- Introduce SerializableProjection wrapper that trims bulky node text
fields (prompt, response, diff, stdout, stderr) for run.json snapshots.
- Collapse metadata-branch and CLI export to one RunDump::from_projection
builder emitting run.json + graph.fabro + stages/{stage_id}/... and
drop legacy top-level start/status/checkpoint/sandbox/retro/conclusion
split files.
- Replace MetadataStore::write_checkpoint with write_snapshot returning
the commit SHA; add read_run_projection/read_run_spec; demote
read_checkpoint/read_start_record to projection-field extractors.
- Switch fork, rewind, rebuild_meta, CLI rewind recovery, and retro
upload to read the unified projection layout.
- Add additive query methods on RunSpec and RunProjection.
Serde-level `alias = "spec"` shim dropped; `rename = "run"` retained to
keep the server API wire format stable per the plan's scope boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lift shared client DTOs into fabro-types, move auth/target/error/session
logic into fabro-client, and reduce fabro-cli to orchestration around the
builder-based client path.
This also lands the remaining plan cleanup for ApiError, ServerTarget
canonicalization, and the RunEventStream rename at the CLI boundary.
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.
Enable clippy::allow_attributes_without_reason at the workspace level.
Add concise, callsite-specific reasons to existing allow attributes, including generated code 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>
- rust.yml: move clippy to nightly-2026-04-14 (was stable); also pin
fmt to the same nightly date for consistency. Both jobs now use the
dated nightly and the run-step uses `cargo +nightly-2026-04-14 ...`.
- AGENTS.md: update developer commands to match CI.
- Duration constructors: replace `Duration::from_secs(N * 60)` /
`Duration::from_millis(N * 1000)` with `from_mins` / `from_secs` /
`from_hours` across the workspace to satisfy clippy's new
`duration_suboptimal_units` lint. std::time::Duration only — custom
`settings::duration::Duration` sites kept on `from_secs`.
- map/unwrap_or cleanup: `.map(f).unwrap_or(v)` → `.map_or(v, f)`,
`.map(f).unwrap_or(false)` on Result → `.is_ok_and(f)`, per
`clippy::map_unwrap_or`.
- Misc lints: collapse nested `if` into match guard in
handler/llm/api.rs and run_state.rs; replace `columns.len() > 0`
with `!columns.is_empty()`; switch a pair of `sort_by` calls to
`sort_by_key`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Update 5 more dry_run_examples snapshots to use [GRAPH_PATH] filter
- Skip LLM preflight check when graph has no LLM nodes (fixes
preflight_allows_pull_request_enabled_without_github_credentials)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config discovery walks from the workflow file's parent directory, so
tests using fixtures at their repo path (test/simple.fabro) would find
the repo's .fabro/project.toml. This caused settings like preserve=true
to leak into tests and break sandbox cleanup event assertions.
Add TestContext::install_fixture() which copies fixtures into the test's
temp dir. Update all CLI run/attach/start tests to use it. Remove the
now-unused example_fixture() function.
Restore preserve=true in .fabro/project.toml — tests are now isolated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove EnvGuard and env-mutating test from fabro-auth (shared mutable state)
- Revert project.toml preserve=true that broke sandbox cleanup event tests
- Fix clippy: use is_some_and, scoped imports for StageStatus and render
- Update snapshot tests for new Run: ULID line and model_test output
- Fix preflight test assertion (name said "allows", asserted failure)
- Update cancel_queued_run test: cancelled runs now appear on board
- Add graph direction query param support to get_graph endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Conflicts resolved:
- install.rs: kept simplified auth (port 32276, no TLS, no username
in merge_server_settings), adapted to origin's input_source API by
removing username from ServerConfigSelection::Write
- serve.rs: kept ProviderCredentials import from origin, dropped
ClientAuth (removed with mTLS)
- server.rs: kept both imports (ServerAuthMethod + Provider)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Propagate the local dev token through worker subprocesses, share the
same authenticated local-server helper across CLI integration tests,
and clean up the async token wait path so fmt, clippy, and full tests
pass again after the dev-token auth rollout.
Add the shared fabro-http transport crate and route hand-written HTTP client construction through it.
Use FABRO_HTTP_PROXY_POLICY for test no-proxy defaults, remove direct reqwest deps from ordinary crates, and add clippy bans for raw reqwest entrypoints.
Mostly consolidation of code added in the recent schema v2 work:
- Share a single ActorRef::user() constructor between server control
actions and workflow provenance conversions.
- Share StageScope::from_context() between current_stage_scope and
StageScope::for_handler so the 4-field construction lives in one place.
- Collapse RunEvent::to_value's if-let chain into an insert_opt helper.
- Use Value::String(id.to_string()) instead of serde_json::to_value for
StageId/ParallelBranchId when seeding the parallel branch context.
- Share parse_event_envelopes via tests/it/support/mod.rs instead of
duplicating the parsing block in two CLI run_events helpers.
Also fix parallel-branch git.commit to emit via emit_scoped with a
branch-specific StageScope so it carries stage_id / parallel_group_id /
parallel_branch_id alongside the other stage-scoped events.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the hand-written to_wire_value / from_wire_value helpers
and the wire_event_envelope_from_generated bridge with
#[serde(flatten)] on EventEnvelope.payload. Derived serde now
produces and accepts the wire shape natively:
{ "seq": 42, "id": "...", "event": "...", ... }
instead of the nested { "seq": 42, "payload": { ... } } the
derive would otherwise emit. #[serde(flatten)] composes fine with
the #[serde(transparent)] EventPayload(Value) wrapper, so the
inner payload object is merged into the outer map on both sides.
- fabro-store/src/types.rs: add #[serde(flatten)]; delete the two
wire helpers (33 lines of Value-map poking); update the
round-trip test to assert the shape is actually flat.
- fabro-server/src/server.rs: sse_event_from_store serializes
the envelope directly; api_event_envelope_from_store pipelines
to_value into from_value.
- fabro-cli/src/server_client.rs: buffer_sse_events parses
straight into EventEnvelope via serde_json::from_str;
list_run_events uses the existing convert_type helper in place
of the deleted wire_event_envelope_from_generated bridge.
- fabro-cli tests: helpers that called from_wire_value now call
serde_json::from_value.
Drops the shape check that from_wire_value used to perform on
parse (id/ts/run_id/event must exist as strings): that check
extracted run_id from the payload and then validated it against
itself, so it only guaranteed presence, not correctness.
EventPayload::new(value, expected_run_id) still runs the same
check where a caller has a real external run_id to cross-match.
Generated code and the OpenAPI allOf(seq, RunEvent) schema are
untouched; the wire JSON is byte-identical before and after.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Centralize flattened EventEnvelope conversion in fabro-store so the CLI,
server, and test helpers reuse one wire-shape path. Also thread parallel
group and branch ids through nested stage and agent events so the new
envelope fields stay populated inside parallel branches.
Drop scratch-only compatibility paths and legacy test scaffolding now that
SlateDB-backed state is authoritative. This removes scratch file fallbacks,
updates docs and UI labels, and moves tests onto durable store-backed helpers.
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.
Default test daemons now opt into an in-memory object store and test
helpers carry explicit run ids instead of rediscovering runs from
shared state.
This also disables the disk-backed store dump integration tests until
store dump is routed through the server's live store handles.
Keep local server targeting based on explicit server targets instead of
implicitly deriving a socket from storage_dir. This makes ~/.fabro/fabro.sock
the default local socket again, keeps storage under ~/.fabro/storage, threads
FABRO_CONFIG through server autostart paths, and updates the CLI test harness
for the new split.
- centralize FABRO_HOME and storage path resolution in fabro-config
- rename store types, extract ArtifactStore, and simplify run key layout
- switch run scratch to scratch/, remove RuntimeState, and refresh docs/clients
Consolidate CLI and server machine defaults under settings.toml,
including loader renames, writer preservation fixes, same-machine
manifest handling, and docs/test updates for the new config model.
Move durable run access and execution control onto the server-backed client,
canonicalize run APIs under /api/v1/runs, and switch CLI integration tests
to a shared test daemon/storage model with shared-state-safe assertions.
Add a shared StoredEvent schema in fabro-types and switch workflow,
store, CLI, and server event handling to use it directly.
This removes the writer/reader mismatch around flattened failure data,
updates affected projections and progress rendering, and refreshes the
fixture/snapshot coverage around the canonical event shape.
Merged origin/main incorporating:
- db_prefix threading in SlateRunStore for run isolation
- matches_run validation in active run cache
- NodeVisitRef type in fabro-store types
- ListRunsQuery parameter for list_runs API
- HashSet dedup in catalog listing
- Updated snapshot tests for new run directory format
Preserved from feature branch:
- NodeAsset struct and exports
- StageId-based node references in run state
- make_run_dir as pub for cross-crate access
- Thread-spawn approach in handler test_default for tokio safety
- parse_run_id handles YYYYMMDD-ULID directory format
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove API/CLI request and log artifact writes plus panic.txt now that
run state and events are store-backed. Update the direct tests to assert
returned behavior instead of on-disk debug files.
Replace in-process workflow E2E hook tests with fabro-cli workflow
integration tests that run fabro as a subprocess and pass OpenAI twin
env only to the child process. Remove the unsafe env-var mutation helper
from fabro-workflow integration tests.
- Move real_cli_claude/codex/gemini tests from fabro-workflow to fabro-cli,
which has a 20s nextest timeout (vs 6s default), and add poll_interval(10ms)
- Reduce DockerSandbox stop_container grace period from 5s to 1s
- Reduce timeout_handling test sleep from 60s to 2s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests were flaky because command() inherited the real repo as the
working directory. When the repo was clean, detached runs attempted
git worktree creation against it, sometimes failing and injecting
extra warning lines into snapshots.
Now command() defaults to the non-git temp_dir, eliminating this
class of flakiness. Tests needing a specific directory override with
.current_dir(). Also canonicalizes fixture paths and adds a
[FIXTURES] snapshot filter via test_context!() macro.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>