Commit graph

334 commits

Author SHA1 Message Date
Bryan Helmkamp
895735a829
Redact the driver's event ids in CLI snapshots and leave a local sandbox unnamed
The attach snapshots now carry the driver's create events, whose event
source and operation ids are minted per process and whose durations run
to the nanosecond, and the local sandbox's id is derived from a temporary
directory; the shared snapshot filters cover all three. A local sandbox's
ready event no longer names that id: the record already holds the
directory, and the id is nothing a person reads.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 13:55:45 -06:00
Bryan Helmkamp
d986fc9751
Keep the sandbox driver's events whole as run events
A bridge translated the driver's events into thirteen lifecycle variants
of fabro's own (start, stop, and delete phases, image pulls, snapshot
builds) and dropped everything else the driver reported, pairing an image
pull's first progress report with the create's completion to invent a
duration. The driver's event is now stored as the run event itself, under
a name derived from it: subject, action, and phase (sandbox.stop.completed,
sandbox.create.progress for an image pull, snapshot.create.started), or
<subject>.state and <subject>.notice. Every operation the driver performs
on the run's sandbox lands on the run, including creates and state
observations the bridge skipped. The CLI reads image pulls and snapshot
builds from the driver's event for its setup progress and pretty output,
the thirteen variants and their props go, and a run stored under the old
names still reads as an unknown body. Checkpoint file numbers in a dump
shift because the run records more events before each checkpoint.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 13:32:54 -06:00
Bryan Helmkamp
5588ae3648
Run the agent's MCP servers and the service listing through the driver's services facet
The agent launched a sandbox MCP server with its own setsid wrapper and
PID handling, polled ss for the port in a shell loop, and read a fixed log
file for failures; the server listed a sandbox's services with its own ss
and procfs scripts and parsers, and told the API which one it had used.
The driver's services facet now does both: the agent spawns the server as
a service, waits for the port, reads the service's logs on failure, and
stops it on cancellation; the server lists the driver's listening ports,
grouped by port with the process names the sandbox can give. The
discovery source leaves the API and the web panel's iproute2 tip goes
with it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 13:18:02 -06:00
Bryan Helmkamp
d24042ba64
Put the driver's sandbox status on the API instead of a projection
Fabro projected the driver's status into its own state enum, resource,
network, and timestamp types for the run sandbox and inventory endpoints,
losing the provider's state string, the network policy, the sandbox kind,
and the driver's own vocabulary along the way. The API now carries the
driver's SandboxStatus itself: SandboxDetails is fabro's run record beside
the status, SandboxInfo is the provider beside the status, and the
OpenAPI schema describes the driver's types (state, resources in the
units the driver reports, the network policy, the sandbox kind, workspace
ownership) which fabro-api reuses through with_replacement with round
trip tests proving identity and JSON parity. The projection types and
their conversion go; the web sandbox page and summary panel read the
status directly, and the TypeScript client is regenerated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 13:07:00 -06:00
Bryan Helmkamp
7d9d2bf5f3
Run fabro's git plumbing through the driver's verbs
Fabro assembled its own hardened git command lines (maintenance, hooks,
fsmonitor, path quoting, signing, the file transport, external diff
drivers) in three crates and parsed raw diff, numstat, cat-file, and log
output itself. The driver's git facet now carries fetch, rev-parse,
ancestry, diff entries, numstat, patch, log, blob sizes and contents,
config, untracked files, and stage-all, hardened by default and typed, so
the checkpoint commit, the run diffs, the Run Files listing and blob
reads, the commit log, the fork fetch, the agent's changed-files
detection, and the git identity setup go through it. The parsers and the
command prefixes go; the per-run capability probe keeps its own plumbing
script. Checkpoint commits never run repository hooks now, so
skip_git_hooks and commit_timeout are accepted for compatibility only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 12:55:39 -06:00
Bryan Helmkamp
370a6c96d5
Give the checkout its GitHub credentials through the driver's store
The GitHub App token reached the agent's git commands through the origin
URL: after the clone fabro ran `git remote set-url origin` with the
token embedded, then tracked which generation the URL carried, held an
embed lease across every push so a refresh could not rewrite the URL
mid-operation, re-embedded on the first auth-shaped push failure in
case the agent had rewritten origin, and redacted the URL out of every
log line and output tail. The token showed in `git remote -v` and
`.git/config`.

The driver now installs ambient credentials for a checkout: one
credential-store line beside the checkout and a `credential.helper`
entry pointing at it, with the remote URL untouched. Fabro's part is
`credentials.rs`: the token source, one mint for the clone, one resolve
per push operation, and the facet call. The clone carries the token per
call and installs it afterwards; the ACP refresh tick rewrites the store
instead of the URL; fabro's own pushes pin one resolved token for the
whole operation and pass it per call, so nothing is ever re-embedded and
a retry after replication lag presents the same token by construction.

Gone with the URL: `push_credentials.rs`, `redact.rs`, the lease and
drift repair in `git_push`, `RefreshOutcome`, and the `credential_action`
and `refresh_error` fields on push attempt events. Stored events that
carry those keys still read. A failed store install after the clone now
fails setup, where a failed `set-url` used to be logged and repaired by
the first push. The one remaining caller of the URL redactor, the
server's repository probe, uses `DisplaySafeUrl::redact_in`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 10:02:01 -06:00
Bryan Helmkamp
91d905813f
Map an environment straight onto the driver's sandbox spec
SandboxOptions was an intermediate between the environment's settings and
the driver's SandboxSpec that mirrored the spec field for field: image
and Dockerfile for the source, cpu and byte sizes for the resources,
auto-stop for the timers, plus the two clone fields. Every provider
overlay then read the options a second time to fill the spec.

The environment now maps onto the driver spec once, in
sandbox_spec_for_environment, and the overlays read the spec: Docker
takes its image from the source and clears the timers it cannot honor,
Daytona takes its snapshot inputs from the source and resources and its
auto-stop from the timers. A plugin gets the spec trimmed to the network
and timer capabilities it declares. The snapshot carries the resources a
Daytona sandbox is sized by, so the overlay clears them from the spec
the sandbox is created with; the driver refuses them there, which the
options path never reached in a live run.

The clone selectors, depth, and skip flag travel as one CloneRequest
beside the spec instead of five loose parameters and two option fields,
so provider_sandbox takes six arguments instead of nine. The two helpers
that read environment settings for a local run, its working directory
and its unresolved variables, become methods on RunEnvironmentSettings
in fabro-types, where the settings live.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 16:44:21 -06:00
Bryan Helmkamp
33cdbf6afc
Finish the merge with the fixes the merge commit left out
The merge commit was made from the staged hunk resolutions and did not
include the changes that followed them: the BTreeMap import the kept
Combine impl needs, main's four new session tests ported to the mock
helper, a duplicated truncation import removed, the boxed event future
the CLI runner needs to stay under clippy's size budget, the formatting
of a merged import list, and the lock refreshed after the merge. Without
these the merge commit does not compile. This is the tree the merge was
verified on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 13:41:35 -06:00
Bryan Helmkamp
2e8d6b8a3d
Merge origin/main into the sandbox-driver adoption
Both sides rewrote the same crates. This branch replaced fabro's sandbox
layer with the sandbox driver: one RunSandbox, no Sandbox trait, driver
events consumed directly, MockSandbox over the driver's doubles. Main
replaced fabro's LLM layer with lithos-llm: fabro-model deleted, the
catalog and provider ids from lithos, credentials through the lithos
CredentialProvider, clients built with build_client.

Every conflict was one of those two renames meeting in an import list or
a signature, so the rule was mechanical: sandbox names resolve to this
branch, LLM names to main. Where main's newer code still used the old
sandbox API — new session tests over Arc::new(MockSandbox), the SDK
example's LocalSandbox, test fakes typed as Arc<dyn Sandbox> — it is
ported to RunSandbox and the mock helper. Where this branch still used
fabro-model or Client::from_source, main's replacement stands. One
combined future in the CLI runner crossed clippy's size budget and is
boxed at its call.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 13:37:11 -06:00
Bryan Helmkamp
619cb44e3c
Import lithos-llm types directly instead of through fabro-types
fabro-types no longer re-exports the lithos catalog and request types
(ProviderId, ModelId, ModelHandle, Message, ContentPart, TokenCounts,
Cost, Speed, ReasoningEffort, ReasoningOutput, and the rest). Every
crate that uses them depends on lithos-llm and names them there, and
the fabro-api progenitor replacements point at the lithos paths.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 10:03:08 -06:00
Bryan Helmkamp
fa3e485c95
Name built-in providers through lithos catalog::builtin
lithos-llm now ships the built-in provider ids and constructors, so
fabro-types drops its provider_ids module and every caller uses
lithos_llm::catalog::builtin directly. The crates that name a provider
now depend on lithos-llm themselves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 09:48:37 -06:00
Bryan Helmkamp
f8df58966b
Use the lithos closest_supported_effort on ModelCapabilities
The reasoning-effort substitution rule now lives on lithos's
ModelCapabilities, so the workflow fallback planner calls it directly
and fabro-types drops its controls module. ReasoningEffort is re-exported
from lithos alongside the other request types.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 09:44:11 -06:00
Bryan Helmkamp
87e1e3a00f
Replace fabro-llm helper modules with lithos-llm equivalents
Delete fabro-llm's attachments, reasoning, and structured modules and
the LlmError newtype and ErrorFacts trait. lithos-llm now provides all
of them: InlineLocalFiles under the local-files feature, ReasoningOutput
with Response::reasoning(), Client::complete_object, and the retry,
auth, cancel, and failover predicates directly on Error and ErrorData.
fabro-llm keeps only failure_signature_hint, which is Fabro's own loop
detection policy.

Store ErrorData directly in the agent and workflow error enums, boxed
where the variant would otherwise dominate the enum size. Repin
lithos-llm to a1e3fd3 for these additions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 09:41:20 -06:00
Bryan Helmkamp
3510d5081d
Implement the lithos CredentialProvider trait directly
fabro-auth defined its own `CredentialSource` trait beside the lithos
`CredentialProvider`, with a parallel `ResolveError` and an adapter between
them, because lithos had no way to ask which providers a store can serve
right now. It does now: `credentials::readiness`, `ClientBuilder::build_ready`,
and `CredentialError::Unusable`.

- The vault, SQL vault, API-key, and extra-headers stores implement
  `CredentialProvider` directly. Material that is present but unusable (an
  expired token with no refresh, a wrong-typed vault entry, a header secret
  that did not resolve, a store read failure) is `CredentialError::Unusable`
  with the operator-facing reason; its `Display` replaces
  `auth_issue_message`. `is_configured` is the cheap presence check.
- `fabro_llm::build_client` calls `build_ready`; `FabroClient::auth_issues`
  carries `CredentialError`. `fabro_llm::configured_providers` replaces the
  per-store `configured_providers` method.
- `CredentialSource`, `ResolvedCredentials`, `lithos_credentials`,
  `ResolveError`, and `auth_issue_message` are deleted. Twenty files that
  held `Arc<dyn CredentialSource>` hold `Arc<dyn CredentialProvider>`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 09:16:39 -06:00
Bryan Helmkamp
3f721dd032
Consume the sandbox driver's events directly in the workflow
Fabro-sandbox carried its own SandboxEvent enum and a callback for it.
The run sandbox wrapped every lifecycle call to emit a start, completed,
or failed variant with its own clock, and re-described the driver's
create-time progress as snapshot events through an observer that lived
next to the run sandbox. The workflow then converted that enum to the
wire. The driver already reports every operation it performs, so the
enum was a second, hand-maintained copy of that stream.

The workflow now observes the driver's events directly. A run's sandbox
is created or attached with a driver EventContext whose observer is the
new SandboxEventBridge in the workflow's event module. The bridge turns
the driver's start, stop, and delete operations, its image pull inside a
create, and its snapshot builds into the workflow's SandboxLifecycle
events, stamping fabro's provider name so the run keeps recording
`local` rather than the driver's `host`. The pipeline emits the
initializing, ready, and failed events itself around bringing the sandbox
up, since that composite step — create, activate, prepare the workspace
— is the pipeline's, not the driver's. Fabro-sandbox emits no events of
its own any more; the run sandbox gained console_url for the ready
event, and a local sandbox can be created with an event context.

The wire keeps every name the CLI reads. Two families go: the cleanup
events, which only the server's manifest validation could have produced
and it passed no callback, and the git clone events, which nothing read
and whose facts the sandbox.initialized event and tracing already carry.
The ready event drops the cpu and memory fields no provider ever
populated. Daytona snapshot events now come from the driver's ensure
call, so a snapshot that already exists and is active reports nothing
rather than a creating-and-ready pair that did no work.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 00:37:42 -06:00
Bryan Helmkamp
7ef09968f2
Document the catalog overlay without the metadata.fabro namespace
The `[llm]` reference now describes `enabled`, `api_key_url`,
`stands_in_for`, `small_default`, `probe`, `family`, and the cutoffs as
lithos fields, the coding harness under `metadata.agent`, and the secret
names lithos derives for operator-defined providers. Secret-bearing
headers go in `default_headers` as `{{ secrets.NAME }}` tokens. The
integration guides enable a provider with `enabled = true` on its table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 23:26:48 -06:00
Bryan Helmkamp
b82b3dd48e
Use the lithos control enum API instead of local spellings
lithos-llm now exposes `ReasoningEffort::ALL`, `Speed::ALL`, `as_str`,
`Display`, and `FromStr` on its request-control enums. Fabro's
`controls` module kept parallel name tables and parsers for them; only
the nearest-supported-effort rule is Fabro's own, so that is what stays.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 23:23:49 -06:00
Bryan Helmkamp
2a2fc41807
Read catalog policy from lithos core fields and metadata.agent
Fabro's policy layer restated the lithos built-ins under `metadata.fabro`:
enabled flags, credentials, display facts, probe and small-default roles,
and agent profiles. lithos-llm now carries every one of those as a core
field or under the shared `metadata.agent` namespace, so the layer and its
typed view go:

- Delete `fabro-policy.toml` and `FABRO_POLICY_TOML`. The catalog is the
  lithos built-ins plus the operator's `[llm]` overlay, nothing between.
- Delete `fabro_types::catalog_policy`. `enabled`, `stands_in_for`,
  `api_key_url`, `family`, the cutoffs, `estimated_output_tps`,
  `small_default`, and `probe` are read from lithos accessors; the agent
  profile and `reasoning_by_default` come from `metadata.agent`, which
  Pebble reads too.
- `catalog::provider`, `enabled_providers`, and `listed_providers` return
  the lithos `CatalogProvider` directly; `ModelEntry` loses its policy
  field and gains `agent_profile()`.
- Test fixtures move `[providers.x.metadata.fabro] enabled = true` onto
  the provider table, drop `credentials` lists in favor of the secret name
  lithos derives from the provider id, and spell `agent_profile` as
  `metadata.agent.profile`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 23:20:12 -06:00
Bryan Helmkamp
6dfc96d3fd
Resolve provider secrets through lithos conventional credentials
lithos-llm now owns which named secrets each provider reads and how they
shape into its auth scheme, including a derived `<PROVIDER>_API_KEY` for
operator-defined providers. Fabro's job shrinks to supplying the store:
`VaultCredentialSource` hands lithos a lookup that reads the process
environment, then the vault, under the same conventional names.

What Fabro still adds on top: the Codex OAuth credential in the vault,
refreshed and persisted when it expires; `{{ secrets.NAME }}` tokens in a
provider's `default_headers`, resolved against the vault and re-sent as
credential headers; and OpenAI organization and project headers from the
environment.

Deleted with the `metadata.fabro.credentials` list: `CredentialRef`,
`CredentialResolver`, `EnvCredentialSource` (now
`VaultCredentialSource::environment_only`), and the `env_var_names` /
`expected_vault_secret_name` helpers, replaced by `secret_names` and
`expected_secret_name` over the lithos table. `openai-codex` joins the
first-party provider id constants.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 23:00:08 -06:00
Bryan Helmkamp
7d5f33ab16
Add the gpt6 agent profile kind
lithos ships `metadata.agent.profile = "gpt6"` on the GPT-6 Astra row.
Fabro runs it on the GPT-5.6 harness: the same Codex core tool set,
memory filenames, question tool, and command timeout. The shared
`uses_codex_core_tools` predicate replaces the `== Gpt56` checks so the
two kinds cannot drift apart.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 22:37:28 -06:00
Bryan Helmkamp
5b035d3f75
Name plugins by their configured kind and drop out-of-process bundled kinds
sandbox-driver PR #9 removes the check that a plugin's declared kind match
the configured one: an operator who configures a path and pins its
checksum has already chosen the executable, so the configured kind is
fabro's name for whatever it serves. With that in the driver, fabro no
longer needs plugin settings on a bundled kind to reach Docker over the
wire. Bundled kinds reject plugin keys again, `connect_provider` links a
bundled kind in-process and launches everything else, and the CLI
scenarios run the Docker executable under the non-bundled `docker-plugin`
kind. The driver pin moves to the PR head until it merges.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 20:13:50 -06:00
Bryan Helmkamp
030e653abf
Run plugin providers end to end and gate them in CI
Closes the sandbox-driver adoption: any provider a sandbox-driver plugin
executable serves can now host a fabro run, and fabro's own bundled
providers can be served the same way.

- `SandboxSpec::Plugin` builds a normalized driver spec from the
  environment (image or Dockerfile source, or a provider-managed
  directory; resources; network policy; labels; env) and lays fabro's
  repository checkout out inside the provider's working directory. The
  layout is recorded on the run through the new `workspace_layout` trait
  method.
- Plugin settings on a bundled kind (`[server.sandbox.providers.docker]
  path = ...`) serve that kind out of process through the driver's
  executable; the config layer no longer rejects them.
- `ProviderAccess` carries the server's provider settings and the vault's
  Daytona credentials to every reconnect: run resume, sandbox details,
  terminals, previews, and the worker's start path. The worker receives
  the settings through `StartServices`. No "plugin not wired" errors
  remain.
- The CLI worker requires GitHub credentials only when a repository will
  be cloned; a `none` target on a clone-based provider creates an empty
  workspace and needs none.
- fabro-db tracks its migrations directory so a new migration file
  recompiles the crate; the environment provider migration had been
  silently missing from stale builds. Environment store 500s now log
  their cause.
- The CLI workflow scenarios run against `host-plugin` (the driver's
  Host executable under the non-bundled `host` kind) and `docker-plugin`
  (the bundled `docker` kind served over stdio), each on an isolated
  server, printing the server log on failure. A live Daytona gate runs
  the native git clone over the JSON-RPC wire. A new CI job runs the
  plugin scenarios and the driver-backed Docker integration tests with
  the plugin executables built.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 19:54:57 -06:00
Bryan Helmkamp
6c1dc96af6
Stop Debug-formatting credentials in fabro-auth resolve tests
Three test panics printed the resolved `Credentials` value with `{:?}`.
The lithos credential types redact secrets in their Debug output, but
CodeQL's cleartext-logging rule cannot see that and flagged each site.
The variant name is enough to diagnose a failing test, so drop the value.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 19:09:22 -06:00
Bryan Helmkamp
5f49cdf9da
Delete fabro-model and document the lithos catalog overlay
fabro-model's ids and billing rollup now live in fabro-types, and its
pricing, catalog, provider TOMLs, and legacy index are replaced by the
lithos built-in catalog plus the Fabro policy layer.

Regenerate the configuration reference for the `[llm]` overlay and
`metadata.fabro`, and rewrite the SDK, models, and integration docs for
the lithos provider and model shapes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 17:26:57 -06:00
Bryan Helmkamp
75e5f34c0d
Expose lithos request and response shapes through the API, server, CLI, and web
The OpenAPI spec adopts the lithos request, response, content part,
tool, usage, and cost schemas. The completions endpoint returns the
lithos `Response` JSON verbatim and SSE carries lithos `StreamEvent`s
verbatim. The models and providers endpoints serve the fabro-types
catalog views, and the install and model-test flows probe providers
through fabro-llm.

The CLI builds its catalog from the operator overlay, drives `fabro exec`
through the server gateway adapter, and parses reasoning effort with the
shared controls. The web app reads content parts as lithos-tagged
objects. The TypeScript client is regenerated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 17:26:57 -06:00
Bryan Helmkamp
f37151ed46
Adopt lithos vocabulary in fabro-types, fabro-auth, and fabro-config
fabro-types re-exports the lithos request, response, content, tool, and
stream types and absorbs the identifiers, billing rollup, provider ids,
controls, catalog API views, and Fabro catalog policy (`metadata.fabro`)
that lived in fabro-model. Stored and wire formats use the lithos serde
shapes directly with no compatibility shims.

fabro-auth becomes a lithos `CredentialProvider`: `CredentialSource`
resolves credentials per catalog provider, with env, vault, SQL vault,
extra-headers, and API-key sources.

fabro-config's `[llm]` settings become an opaque TOML overlay layer
(`LlmLayer`) that is applied on top of the lithos built-ins and the Fabro
policy layer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 17:26:57 -06:00
Bryan Helmkamp
580bb85f5b
Depend on lithos-llm and the published twin-openai crate
Add lithos-llm as a pinned git dependency and replace the in-repo
`test/twin/openai` crate with the published `twins` revision that
lithos-llm verifies its codecs against. Move the Fabro policy overlay
(`fabro-policy.toml`) into fabro-llm so Fabro owns its own catalog
policy layer.

Drop the twin-openai nextest overrides and CI package filter now that the
crate is no longer a workspace member.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 17:26:57 -06:00
Bryan Helmkamp
80bc51c40e
Open sandbox provider identity to plugin kinds
SandboxProviderKind is now a validated string newtype instead of a
closed enum. The bundled kinds (local, docker, daytona) keep their
constants and a BundledProvider enum for the code paths that still
dispatch on them; any other well-formed sandbox-driver kind name is
accepted and names a plugin executable. EnvironmentProvider is gone:
environment settings carry SandboxProviderKind directly, and
is_clone_based is replaced by a workspace policy where local runs in a
designated directory and every other provider clones.

Server sandbox policy is keyed by kind. [server.sandbox.providers.<kind>]
accepts the bundled kinds with `enabled` and any plugin kind with its
launch settings (path, sha256, dev, args, env, inherit_env); bundled
kinds reject the plugin keys and a kind with no entry is disabled. The
OpenAPI schema, generated Rust and TypeScript clients, web settings
pages, and docs follow. The environments table drops its provider CHECK
enumeration in favour of the kind name rules so a plugin environment
can be stored.

Bundled-only code paths (run start, preflight, reconnect, terminal,
details) now fail with an explicit message for a plugin kind until the
driver construction function lands in the next step.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 15:17:32 -06:00
Bryan Helmkamp
a7ed03e175
Rebuild conclusion stage summaries from run events 2026-09-05 11:44:23 -04:00
Scott Werner
a00b95abe0 Report available environments when default is missing
When `fabro run` or `fabro create` omits `--environment` and the server
has no environment named `default`, the CLI previously failed with only
"could not retrieve environment `default`". It now lists the server's
environment catalog in the error so the user can pass an explicit
`--environment <id>` or create the missing `default` entry. Explicit
`--environment` lookups keep their existing not-found message.

Adds `Client::list_environments` to fabro-client for the catalog read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 12:56:47 -04:00
Scott Werner
c437dc012d Simplify pre-start worker failure handling
Move the "which failures can happen before Starting" classification onto
FailureReason as an exhaustive predicate and use it for every
Runnable -> Failed transition, replacing the hand-maintained allowlist.
Give the pending-cancel precedence rule a single owner shared by the
worker launch and worker exit paths.

Test cleanups: share the Notify wait loop, server record fixture, and
post-failure assertions; simplify the pre-start test runtime's hold
flag; and parameterize the slate run.failed payload helper.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 11:45:47 -04:00
Scott Werner
54666632c8 Persist pre-start worker failures 2026-09-02 17:29:41 -04:00
Scott Werner
694d1981ff Simplify the CLI run-intent create path
Quality pass over the intent-producer changes, no behavior changes
intended:

- Move the TOML->JSON scalar conversion into fabro-types as
  toml_scalar_to_json_value, next to its inverse, with typed errors and
  round-trip tests; the CLI now calls the shared helper.
- Reuse goal_layer_from_args for --goal/--goal-file resolution instead
  of a second copy of the exclusivity check and cwd anchoring.
- Delete the dead run_manifest_args helper and the test that kept it
  compiling; preflight_manifest_args is the remaining real builder.
- Make run_target_for_environment a pure (provider, cwd) -> target
  mapping using is_clone_based(), warning at the call site, and default
  the environment id from DEFAULT_ENVIRONMENT_ID instead of a literal.
- Resolve the parent run and retrieve the environment concurrently.
- Drop the ResolvedCommandSettings pass-through struct and the
  duplicated parse-error mapping in the project settings presence read.
- Share the environment/workflow-version/git test mocks from the cmd
  test support module instead of three per-file copies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 16:14:34 -04:00
Scott Werner
afe1133878 Fix CLI RunIntent producer CI failures 2026-09-01 16:14:34 -04:00
Scott Werner
2507a0075f Create CLI runs from immutable workflow intents 2026-09-01 16:14:34 -04:00
Scott Werner
1a2d8a9056 Move run queries and pull request recovery to SQLite 2026-09-01 14:06:06 -04:00
Scott Werner
563ca26b98
Merge pull request #829 from fabro-sh/codex/session-ownership-index
Index session ownership from creation events
2026-09-01 14:03:25 -04:00
Scott Werner
0fd4714da7
Merge pull request #825 from swerner/codex/automation-workflow-sources
Add independent workflow sources to automations
2026-09-01 12:22:20 -04:00
Scott Werner
7a3f58c87e Tighten automation Git validation types 2026-08-31 18:02:08 -04:00
Scott Werner
5af791c812 Align remote workflow selectors with run targets 2026-08-31 17:13:20 -04:00
Scott Werner
bf05b01ba6 Simplify session owner lookup and migration preflight
- Drop the post-decode run_id/session_id/event-body checks in
  find_session_owner: decode_event_row already verifies every stored
  column against the decoded envelope, and the WHERE clause pins
  session_id and event_name to the requested values.
- Build the lookup query from SELECT_EVENT_COLUMNS like the sibling
  event queries instead of duplicating the column list.
- Carry the stored run_id text in the unparseable-id error instead of
  an "<invalid>" placeholder.
- Fetch applied migration versions once per migrate() and share the
  set between the session-owner preflight and the pre-migration
  snapshot; check the applied version first so steady-state startups
  skip the sqlite_master probe. Mark the preflight as removable with
  the run-history compatibility window.
- Restore session_by_id_key as a #[cfg(test)] helper so tests stop
  hand-rolling the legacy reverse-index key shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 14:59:38 -04:00
Scott Werner
3411199103 Index session ownership from creation events 2026-08-31 14:16:58 -04:00
Scott Werner
b4165a3b4e Update workflow source migration test version 2026-08-31 13:55:02 -04:00
Scott Werner
b7d32b9e83 Renumber automation workflow source migration 2026-08-31 13:51:07 -04:00
Scott Werner
39c018c430 Harden automation workflow source handling 2026-08-31 13:45:08 -04:00
Scott Werner
b61d309aa3
Merge pull request #827 from fabro-sh/codex/run-intent-registration-spine
Add RunIntent registration support
2026-08-31 13:44:08 -04:00
Scott Werner
7514e676e0 Prove workflow version registration order 2026-08-31 13:24:25 -04:00
Scott Werner
05fb173767
Merge pull request #823 from fabro-sh/codex/sqlite-run-history-activation
Activate atomic SQLite run history storage
2026-08-31 12:53:11 -04:00
Scott Werner
c68e67c841 Harden SQLite activation and run history consistency 2026-08-31 12:19:16 -04:00
Scott Werner
637d6a0d84 Simplify workflow version registration client and tests
Move the content-derived id check into create_workflow_version so every
caller gets it, drop the redundant expected_id parameter from
register_workflow_versions, and collapse the duplicated httpmock setups
and ordering machinery in the client tests. Use in-scope imports and the
neighbouring reader idiom in the server intent tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 10:20:58 -04:00