The persisted bool described user intent (\"the user opted into the
in-place execution mode\"), not a literal consequence -- SlateDB and
event-sourced checkpoints flow regardless of the flag, only git
checkpoints are skipped. Renaming aligns the name with intent and
decouples it from any future implementation that allows git
checkpoints in-place.
The fork validator still consults this bool to bail out with a clear
error before searching for git checkpoints that won't exist.
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.
Integrates origin's worker-JWT-auth work (commits 8a6f83bb0..c847a828d)
with the config-boundary refactor that landed locally. Conflicts
resolved:
- commands/dump.rs: take origin's removal of the 500-line in-process
test block (replaced by real-server integration coverage).
- commands/run/runner.rs: keep local's dense WorkflowSettings import,
drop dead SettingsLayer import, pull in origin's ActorRef.
- manifest_builder.rs: adopt origin's lifted working_directory
resolution (fixes#159 - manifest git detection in nested repos),
but via local's resolve_working_directory_from_run API that takes
the dense RunNamespace. Update the regression test's
ManifestBuildInput literal to local's run_overrides/cli_overrides
field shape.
- server.rs: keep origin's jwt_auth_mode/jwt_auth_state/
test_user_subject/issue_test_user_jwt/issue_test_worker_token/
create_run_with_bearer/bearer_request test helpers, adapt
jwt_auth_state to local's create_test_app_state_with_session_key
signature (ServerSettings + RunLayer), keep local's dense
canonical_origin_settings that returns ServerSettings via
server_settings_from_toml. Rewrite
build_app_state_requires_session_secret_for_worker_tokens against
the dense AppStateConfig (resolved_settings +
resolved_runtime_settings_for_tests).
Post-merge verification: workspace builds clean, cargo +nightly
fmt --check all clean, cargo +nightly clippy --workspace
--all-targets -- -D warnings clean, cargo nextest run --workspace
4560 tests passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The dev-token gating commit added ensure_home_server_auth_methods only to
run_cmd/create_cmd helpers, but many integration tests use context.command()
directly to invoke run/start/attach/etc. Patch the offenders rather than
hoisting auth-injection into command() itself, since command() is also used
by tests (e.g. uninstall) that explicitly want a stable settings file.
- attach, start, scenario lifecycle/recovery, json_global graph: call
context.ensure_home_server_auth_methods() up front
- validate(): hoist into the helper itself, since every validate test
needs it
- server_status, uninstall legacy-record tests: bake methods=["dev-token"]
into their hand-written settings.toml fixtures and pass FABRO_DEV_TOKEN
via env so the spawned server actually boots
- install: write_artifact_store_metadata_creates_marker test fixture also
needs explicit methods after the resolver became strict
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.
Extends the workspace clippy.toml — which already bans std:🧵:sleep,
std:🧵:spawn, and std::process::Command::new on Tokio paths — with:
- disallowed-types: std::io::{Read, Write, BufRead, BufReader, BufWriter}
and std::net::{TcpStream, TcpListener, UdpSocket}
- disallowed-methods: std::io::{stdin, stdout, stderr}
Non-blocking std::io items (Error, ErrorKind, Result, IsTerminal, Cursor)
remain allowed. std::fs is intentionally deferred.
Annotates ~24 pre-existing sync call sites with #[expect(..., reason = "...")]
matching the established pattern. All annotations describe why blocking I/O
is intentional in that context (sync CLI command, test helper, pre-fork
flush, etc.), so a future conversion to async will surface as an unfulfilled
lint expectation instead of silently drifting.
Fixes one real Tokio-path issue surfaced by the new lint:
fabro-cli's server-start daemon-health poller (try_connect) was a sync fn
called from async execute_daemon; std::net::TcpStream::connect_timeout
blocked a Tokio worker for up to 100ms per poll iteration. Converted to
tokio::net::{TcpStream, UnixStream} with tokio::time::timeout.
One follow-up flagged in-code: fabro-agent/src/cli.rs's JSON event writer
uses std::io::stdout() inside tokio::spawn. Annotated with a FOLLOW-UP
reason pointing at tokio::io::stdout; left unchanged since volume is low
and scope exceeded this pass.
Verified: clippy clean, cargo +nightly fmt --check clean, full nextest
workspace run (4131 passed, 182 skipped).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Stacked cleanup of the `canonicalize blocked run status` work (local
commit `d13cdf374`) plus reconciliation with origin's `canonicalize
paginated run list responses` (origin commit `8ab689da7`). Both efforts
ran in parallel and diverged on the column name (`blocked` vs `waiting`)
and on how the board response is shaped — this PR converges them,
keeping `blocked` as the canonical column id while adopting origin's
`column` field on `RunListItem` and `StoreRunSummary` shape.
Also fixes a production-worker regression introduced by the
canonicalization: the worker's start-precondition only accepted
`Submitted | Starting`, so once runs started transitioning through
`Queued` on the way to `Starting`, every subprocess-worker run failed
with `Precondition failed: cannot start run: status is Queued`. That
cascaded into ~90 failing CLI/server integration tests locally.
## Commits
1. `f65843168` refactor(runs): simplify blocked status follow-ups
2. `1492d956c` chore: resolve clippy warnings
3. `676fd9f44` first merge of origin/main
4. `23fc92a2f` **fix(runs): allow Queued status in start precondition**
← the cascade-fix
5. `36b507a83` refactor: simplify pause/unpause + dedupe web status
tables
6. `8d8d27748` refactor(workflow): encapsulate BlockedStateTracker
inside HumanHandler
7. `1c17fda35` second merge of origin/main — resolves waiting vs blocked
8. `4cd3ef7b1` refactor(workflow): Mutex<usize> → AtomicUsize
9. `2e5a58e8a` fix(demo): align run-4 lifecycle status with Blocked
board column
## Test plan
- [x] fmt, clippy, build, doctests all clean
- [x] `cargo nextest run --workspace` — **4092/4092 pass**
- [x] `bun test` — **26/26 pass**, typecheck + production build clean
- [x] Manual CLI repro of the Queued-precondition fix
- [x] Browser smoke test: all 5 columns render with correct
labels/colors, demo run-4 appears in Blocked lane with question text
intact
## Known follow-up (not blocking)
A "paused-while-blocked" run (status `Paused` + `blocked_reason: Some`)
lands in the `running` column because the visible status chooses
`Paused` over `Blocked`. The pending question is not prominent on the
board. Addressing it would require `board_column()` to branch on
`(status, blocked_reason)` rather than just `status` — worth a separate
ticket.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLI integration tests spawned the real fabro binary while letting the
parent process's env pass through. The pr_view "no credentials" snapshot
failed in CI because the Nightly workflow's minted GITHUB_TOKEN was
inherited by the child and turned the expected "credentials required"
error into a real GitHub API call (404 / 401). On developer laptops the
same leak occurs whenever gh auth login is active.
Introduce apply_test_isolation(cmd, home) in fabro-test: env_clear() +
re-populate PATH, HOME, NO_COLOR, and the FABRO_* test overrides. Route
TestContext::command(), the internal server bootstrap, and the four
ad-hoc spawners in tests/it/cmd/{attach,render_graph,runner,server_start}
through the same helper so the isolation is systemic instead of
per-callsite. Tests that deliberately need a credential (OPENAI_API_KEY,
GITHUB_APP_PRIVATE_KEY, etc.) continue to set it explicitly on the
returned Command; those survive the clear.
Add a regression test that sets sentinel GITHUB_TOKEN and
ANTHROPIC_API_KEY in the parent, spawns /usr/bin/env through the helper,
and asserts the child sees neither credential while still seeing PATH
and the harness's FABRO_NO_UPGRADE_CHECK override.
Verified: the full workspace (4022 tests) passes with GITHUB_TOKEN and
ANTHROPIC_API_KEY set in the parent, which previously broke the
pr_view_reads_pull_request_from_store_without_pull_request_json
snapshot. cargo fmt and nightly clippy are clean.
- Update VERSION filter regex to handle prerelease suffixes (e.g., 0.204.0-beta.1)
- Add VERSION filter to JSON snapshots in fabro_json_snapshot macro
- Fix attach test to use [VERSION] placeholder instead of hardcoded version
- Update upgrade help snapshot to include new --prerelease flag
- Change fake version in upgrade test from v0.176.3 to v999.0.0 to avoid collision
- Strip --watch-web from server start help (debug-only flag, varies by build)
- Gate test_panic module with #[cfg(debug_assertions)] (debug-only command)
Co-Authored-By: Claude Opus 4.7 (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>
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.
Embed defaults.toml as a base settings layer and apply it when
materializing effective settings and resolving typed settings.
This also fixes partial CLI table merging so builtin fields survive
higher-precedence overrides, and updates the affected CLI tests and
snapshots.
Add a Clippy disallowed-methods guardrail for std::thread sleep/spawn
and convert the CLI polling paths to tokio::time::sleep so they no
longer block Tokio workers. Keep the intentional OS-thread sites with
narrow #[expect(...)] annotations that explain why std::thread is
required there.
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.
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>
Brings in the events schema v2 work (RunEvent envelope fields, ActorRef,
parallel branch ids, flattened EventEnvelope wire JSON) on top of the
local Stage 6 settings TOML redesign.
Conflict resolutions:
- fabro-types/src/lib.rs: keep new ParallelBranchId re-export from
origin; drop the legacy Settings/ArtifactStorage* re-exports (the
flat Settings struct was deleted in Stage 6.3b).
- fabro-server/src/server.rs: keep new ActorRef import from origin;
drop the unused legacy Settings import that came along with it.
- fabro-api-client/src/models/web-settings.ts: keep our deletion. The
remote modification was an incidental TS-client regeneration that
Stage 6.6 already invalidated by collapsing settings DTOs to a
freeform v2 shape.
- fabro-workflow/src/event.rs: rewrite the run_created actor test to
use SettingsFile::default() instead of the deleted Settings type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Populate stage_id / parallel_group_id / parallel_branch_id on every
event tied to a concrete stage execution, per the spec at
docs-internal/fabro-event-schema-v2-concrete-shape.md:223-279.
Before this commit, stored_event_fields() only set stage_id for the
four Event::Stage* variants and Event::Agent -- the only variants
that carried visit/parallel_group_id/parallel_branch_id in their
payload. Every other stage-scoped event (Checkpoint*, PromptCompleted,
Command*, AgentCli*, Prompt, Interview*, Failover, StallWatchdog,
GitCommit, ArtifactCaptured) fell through to node_stored_fields()
and left stage_id as None.
New approach: scope is carried alongside the event, not on the
variant.
- fabro-workflow/src/event.rs: new StageScope type
{ node_id, visit, parallel_group_id, parallel_branch_id }. New
Emitter::emit_scoped(&event, &scope) for stage-level emission.
to_run_event_at and stored_event_fields take an
Option<&StageScope> that merges into the returned envelope
fields. StageScope::for_handler(context, node_id) is the
canonical handler-side constructor -- prefers
context.current_stage_scope() set by the fidelity lifecycle,
falls back to a scope synthesized from the node_id + context
visit count for tests that don't go through the full lifecycle.
- fabro-workflow/src/context.rs: new
WorkflowContext::current_stage_scope() method reads CURRENT_NODE,
internal.node_visit_count, internal.parallel_group_id,
internal.parallel_branch_id from the context.
- Remove the now-redundant visit/parallel_group_id/parallel_branch_id
fields from Event::Stage{Started,Completed,Failed,Retrying} and
the parallel_* fields from Event::Agent. These existed only to
feed stored_event_fields() and are obsolete once scope is
threaded through the emitter.
Emission site migration (all stage-scoped handlers now use
emit_scoped):
- lifecycle/event.rs: StageStarted, StageCompleted, StageFailed,
StageRetrying, CheckpointCompleted, GitCommit (from on_checkpoint)
- lifecycle/git.rs: CheckpointFailed
- lifecycle/artifact.rs: ArtifactCaptured
- handler/command.rs: CommandStarted, CommandCompleted
- handler/prompt.rs: Prompt, PromptCompleted
- handler/agent.rs: Prompt, PromptCompleted
- handler/fan_in.rs: Prompt, PromptCompleted
- handler/human.rs: InterviewStarted, InterviewTimeout,
InterviewInterrupted, InterviewCompleted
- handler/llm/api.rs: Failover, Agent (via spawn_event_forwarder
which now carries a StageScope across the tokio::spawn boundary)
- handler/llm/cli.rs: AgentCliStarted, AgentCliCompleted
- handler/parallel.rs: ParallelBranchStarted, ParallelBranchCompleted
StallWatchdogTimeout stays on plain emit() because the watchdog
fires from an error path without a live stage context.
Deleted the local StageEventScope struct + current_stage_event_scope
helper from handler/llm/api.rs; it's generalized into StageScope.
Tests: two new unit tests in event.rs --
stage_scope_populates_stage_id_on_non_stage_events verifies
CommandStarted / Prompt / GitCommit all pick up stage_id from scope,
run_level_events_without_scope_leave_stage_id_absent confirms
run.* events still get no stage scope. Updated all test fixtures
across fabro-workflow, fabro-cli to drop the removed Event variant
fields. Accepted two insta snapshot updates in
fabro-cli/tests/it/cmd/{attach,run}.rs that now include the
formerly-missing stage_id fields on checkpoint and interview events.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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.
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>
Replace the slow CLI integration tests that waited on worker shutdown
grace periods with focused coverage that still checks the important
behavior. The attach JSON test now finishes the gated run cleanly,
the rm force test uses a mocked server contract, and the Ctrl-C cancel
path is covered at the attach layer instead of through a full live run.
Add a cooperative subprocess cancel control message so cancel and delete
can abort pending interviews without relying only on the 5 second hard
kill fallback.
Drop the dead artifact storage capability split from run records,
run.created events, and workflow/server create paths. Worker artifact
upload is now unconditional, and tests/snapshots no longer encode a
legacy object-backed distinction.
Persist submitted run manifests and accepted run definitions as SHA256
blob refs on run events, remove workflow_bundle.json from the runtime
path, and stop deleting shared CAS blobs when removing runs.
Collapse expensive CLI smoke coverage into scenario tests, replace the
slow doctor no-color integration check with a unit-level render test,
and remove duplicate attach coverage. Also fix local Unix-socket
autostart so missing daemons don't spend the full 5s readiness wait
before startup.
The commit includes the measured slow-test report updates for the work
landed here.
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.
Guard server worker cleanup against superseded subprocesses so rewind and
resume flows do not append a synthetic failure from an older worker. Update
CLI snapshots for the current interview events and give the shared test
session lock more time to cover daemon startup and shutdown.
Tighten the worker upload path so object-backed runs only fail when an
artifact upload is actually attempted without a token, and update CLI
snapshots for the new artifact storage metadata.
Fold in the workspace test and clippy fixes needed to verify the final
artifact upload implementation cleanly across Rust and web targets.
Store large context payloads in the global CAS and keep durable
checkpoint state as blob://sha256 refs instead of execution-local file
paths. Resolve and materialize blob refs at execution, output, and export
time so resumed and remote runs can read legacy and new artifacts
consistently.
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.
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.
Replace the attach polling loop with the existing run attach SSE endpoint.
Seed from stored history once, fetch interview questions only when needed,
and keep completed-run replay behavior intact.
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.