The `daytona` provider kind now maps onto the sandbox-driver Daytona
provider instead of fabro's own SDK client. Fabro keeps what is its own:
the HMAC-named snapshot built from the environment's image or Dockerfile,
the explicit 120 minute auto-stop default, the managed labels that gate
destructive operations, the clone decision and layout, and push
credentials. The driver creates the sandbox, clones natively, and serves
exec, files, search, terminal, SSH, preview, and VNC through its facets.
- `daytona.rs` builds the driver `SandboxSpec` (snapshot source,
`/home/daytona/workspace`, labels, timers, network policy, run name),
ensures the snapshot through the driver `SnapshotProvider`, attaches by
persisted id with fabro's label guard, and probes credentials through
the provider health check under fabro's 20 second budget.
- `DriverSandbox` gains a create plan that settles the spec right before
the provider call, records the snapshot a sandbox came from, and
reports the provider console URL on `Ready`.
- Terminals use the driver `Pty` facet; the server's SSH, preview, and
VNC endpoints use the `SshAccess`, `PreviewUrls`, and `Vnc` facets
through the driver-typed reconnect. The preview endpoint now answers
for every provider with a preview facet, so the local sandbox returns
its loopback URL.
- Daytona credentials travel as `DaytonaCredentials` built from the
vault key plus configured URL and organization; nothing reads the
process environment implicitly. The inventory registry uses the shared
`DriverInventoryProvider`.
- The SDK-based `daytona/mod.rs`, `provider/daytona.rs`, the Daytona
terminal, the `daytona` cargo feature, and the direct daytona-sdk,
git2 (in fabro-sandbox), tungstenite, and rustls dependencies are
gone. The live Daytona tests run against the driver-backed sandbox.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Fabro's Docker provider kind now maps onto the sandbox-driver Docker
provider instead of its own bollard implementation. Fabro keeps what is
its own: the clone decision and repository layout, the GitHub App push
credentials embedded in origin, git retry classification, the managed
labels that gate destructive operations, and the run-facing events.
- `clone.rs` performs fabro's clone over the driver `Git` and `Exec`
facets. An exact commit goes through the driver's pinned clone; a tag
pin runs fabro's init/fetch/attach sequence through `Exec` so the
fully qualified tag ref is the only revision consulted. Network
failures retry through `git_retry`, which now classifies driver errors
(exec output, provider retryability) and never replays an operation
whose outcome is unknown.
- `DriverSandbox` gains a pending-create state, a `RepoWorkspace` with
push-credential state, path resolution against the cloned working
directory, git lifecycle methods, image-pull progress mapped from
driver events, and an embedded terminal over the driver `Pty` facet.
- `docker.rs` builds the driver `SandboxSpec` (image, `/workspace`,
fabro labels, env, cpu/memory, network policy, run name) and attaches
by persisted container id, refusing containers without fabro's labels.
- Inventory, details, diagnostics, terminal, and reconnect run over the
driver: a `DriverInventoryProvider` lists by fabro's managed label and
projects `SandboxStatus` into fabro's inventory and details shapes.
- The bollard-based `docker.rs`, `provider/docker.rs`, Docker terminal,
Docker error variants, the `docker` cargo feature, and the bollard and
tar dependencies are gone. The Docker integration tests, the agent
shell test, the workflow artifact test, and the driver benchmark run
against the driver-backed sandbox.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
fabro_sandbox::driver::connect_provider turns one
[server.sandbox.providers.<kind>] entry into an Arc<dyn SandboxProvider>
from the sandbox-driver crates. Bundled kinds link the driver's Host,
Docker, and Daytona providers in-process; Daytona receives its API key
and endpoint explicitly through connect_explicit with a
fabro-sandbox/<version> user agent, never from the process environment.
Any other kind launches the configured plugin executable through a
PluginSupervisor and returns a wrapper that replaces a crashed plugin
for new work only, never replaying a failed call. Disabled entries are
refused at the construction point.
The Host and Docker providers also ship as fabro-sandbox-host and
fabro-sandbox-docker plugin executables so CI can drive the bundled
providers over stdio and a deployment can move one out of process by
configuration alone. An integration test registers the Host executable
under the non-bundled kind `host`, creates a sandbox and runs a command
over the wire, then attaches to it by persisted id from a fresh plugin
process, and checks that a plugin declaring a different kind is refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Fabro pins the sandbox-driver workspace the same way it pins the
Daytona SDK: every crate at one commit on main. The bundled Host,
Docker, and Daytona provider libraries link in-process, and the
protocol crate reaches third-party providers over stdio. Nothing uses
the crates yet; the following commits move fabro onto them one layer
at a time.
The driver pins tracing-subscriber exactly, so the lockfile settles on
that version for the whole workspace.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Both fabro and sandbox-driver now pin the same daytona-sdk-rust commit
on main. The newer SDK adds region and sandbox class fields to snapshot
creation; fabro leaves both unset and keeps its current behavior.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Share canonicalize_location and resolve_existing_workflow_location
between the local package resolver and the version collector, drop the
redundant package-root pre-check and the PackageFileReadError enum in
favor of anyhow context, and read HEAD's SHA from git2 instead of a
separate rev-parse subprocess.
Make GitRunTargetObservation a plain struct, replace the repo-info
tuple with a named struct, tighten the closure view trait, avoid
deep-cloning the root workflow during collected validation, remove the
unused into_closure accessor, and dedupe test helpers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review cleanups for the tools/reasoning-effort model test change:
- Extract a shared parse_query_enum helper in the models handler in
place of two copy-pasted parse-or-400 match blocks.
- Collapse the duplicated basic-probe pipeline in fabro-llm behind a
single basic_probe core; name the shared EXPANDED_MAX_TOKENS budget.
- Pass &ModelTestArgs to test_models_via_server instead of threading
five of its fields positionally.
- Dedupe the two forwarding CLI integration tests behind a helper.
- Derive clap::ValueEnum for ReasoningEffort behind a feature-gated
clap dep (same pattern as MergeStrategy in fabro-types) so --help,
cli.mdx, and error output list effort values from the enum instead
of a hand-written list that drifts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TDdjG18d2AHh7mFWXFkBLn
create_backup re-implemented the staging half of fabro-db's
pre-migration snapshot (remove stale staging file, UTF-8 check,
VACUUM INTO, private permissions), and remove_file_if_exists and
set_private_permissions had been made pub precisely to hand-copy that
sequence. Any future hardening of snapshot staging would have had to
land in two crates and could drift.
fabro-db now exposes write_snapshot_to_staging with a typed
SnapshotStagingError; both the pre-migration snapshot and the
pre-activation backup stage through it, and the hand-copied helpers are
private again. The publish halves stay separate on purpose: migrations
overwrite their snapshot, activation publishes with persist_noclobber
plus integrity validation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brave stays the default. Shops that already vault VENICE_API_KEY
can drop BRAVE_SEARCH_API_KEY by setting
[server.integrations.search] provider = "venice".
Co-authored-by: Cursor <cursoragent@cursor.com>
Every push previously re-minted a fresh GitHub App installation token and
embedded it in the origin URL, so pushes routinely landed inside GitHub's
token-replication lag window (run 01M0DH033P2XSTHAGVBHG6922F failed
terminally on four consecutive fresh-token 404s). Reusing mature tokens
removes the failure trigger and saves two GitHub API calls plus one sandbox
exec per push.
- New fabro_github::token_source::InstallationTokenSource: one cached,
single-flight source per origin repo. Static credentials pass through
(generation 0); App credentials mint through the cache and reuse tokens
until REFRESH_MARGIN (10 min) before expiry. Every resolve returns a
non-secret TokenSnapshot (generation + Minted/Reused/Static provenance),
and the source logs mints at INFO and reuses at DEBUG.
- Docker and Daytona share the source through PushCredentialState: an embed
mutex serializes compare -> set-url -> record, a matching generation skips
the set-url exec, and the generation is recorded only after a successful
exec. The clone still mints its own token, but now seeds the source cache
(generation 1) and the last-embedded state, so a refresh mint failure
falls back to the known embedded token instead of believing nothing was
ever embedded.
- RefreshOutcome now reports the remote action (embedded/unchanged/none)
separately from the token snapshot; git_push_via_exec logs token age and
provenance with each push, and refresh failures log the last embedded
generation.
- The run-metadata writer resolves through the sandbox's shared source
instead of minting per snapshot (with its own cached source on resume).
- The ACP refresh-ahead loop reschedules from the embedded token's
expires_at minus the margin instead of a fixed 45-minute interval, which
a cached source would have broken for long turns; static credentials stop
the loop.
Plan: .ai/plans/git-push-token-resilience.md (PR 1: items 3 and 6).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the WorkflowVersion domain resource with exactly entrypoint, files,
and workflow_dependencies, plus strict WorkflowPath validation and
deterministic canonical raw JSON. Semantic validation of graph imports,
templates, file references, workflow.toml rules, Dockerfile paths, and
exact child-workflow dependency bindings lives in the new
fabro-workflow-version crate, which validates the complete stored
dependency closure through the shared blob store before writing a root.
The authenticated create-only POST /api/v1/workflow-versions endpoint
ships with its OpenAPI contract, Rust type replacements, and generated
TypeScript client.
Squashed from the resource commits of the original combined branch;
the walker unification this builds on landed separately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>