Commit graph

226 commits

Author SHA1 Message Date
Scott Werner
39c018c430 Harden automation workflow source handling 2026-08-31 13:45:08 -04:00
Scott Werner
305381e2c2 Simplify automation workflow-source materialization
Collapse the role-paired materializer error variants into
`Credentials`/`Checkout` tagged with a `CheckoutRole`, route both
checkouts through one resolve-then-prepare helper, and replace the
test-only clone-URL field on the production materializer with a
`GitRemote` resolver seam. A workflow source in the target's repository
now reuses the already-resolved credentials instead of minting a second
token.

Also inline the one-line workflow-source normalizer, drop the `as_str`
wrapper on the new kind enum, remove the unused migration constant, move
rather than clone scheduler fields, and deduplicate the web form's
ref-validity rule and per-kind copy into a single table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 13:17:15 -04:00
Scott Werner
03f81d1f25 Add independent workflow sources to automations 2026-08-30 13:14:11 -04:00
Scott Werner
88dae80c6d Consolidate automation environment helpers and trim redundant work
- Share the clone-based provider predicate and provider label between the
  automation form and environment settings instead of duplicating them
- Hoist repeated environments query state in the new-automation route
- Normalize empty environment ids to None so validation needs one check
- Merge the scheduler's record/clear error helpers and skip the clearing
  write when no error is stored
- Guard the environment backfill with a cheap existence query
- Drop an unneeded id clone and a no-op migrator comment

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 12:25:16 -04:00
Scott Werner
e87130ae23 Require a server-managed environment for automations
Automations now store an environment_id that must reference an enabled
Docker or Daytona environment. Each trigger fire resolves the current
environment definition and snapshots its settings into the run, and
deleting an environment still referenced by an automation is rejected
with a conflict.

Existing automations are backfilled conservatively: a compatible
environment named default is selected when present, otherwise the sole
compatible environment. Anything ambiguous is left incomplete and cannot
run until an operator selects an environment in the web UI.

Scheduler failures are recorded on the automation as last_error and
cleared after the next successful scheduled run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 09:58:13 -04:00
Bryan Helmkamp
9dd47a8cd3
Merge pull request #819 from fabro-sh/codex/daytona-image-snapshots
Allow Daytona snapshots from Docker images
2026-08-28 16:24:55 -04:00
Scott Werner
5cd69e9c66
Merge pull request #820 from fabro-sh/codex/automation-run-intent
Move automation runs to workflow versions and RunIntent
2026-08-28 15:43:31 -04:00
Scott Werner
9bd499cdbe
Merge pull request #817 from fabro-sh/codex/run-record-sql-foundation
Some checks failed
Rust / Test (Linux) (push) Has been cancelled
Rust / Test (macOS) (push) Has been cancelled
Rust / Format (push) Has been cancelled
Rust / Clippy (push) Has been cancelled
Rust / Generated Docs (push) Has been cancelled
TypeScript / Typecheck (push) Has been cancelled
TypeScript / Test (push) Has been cancelled
TypeScript / Build (push) Has been cancelled
Add inactive SQL run event storage foundation
2026-08-27 16:30:05 -04:00
Scott Werner
077d020223 Simplify automation workflow-version packaging
Share the RunIntent shape between the scheduler and the API trigger via
AutomationRunMaterialized::into_run_intent, drop the pass-through
packaging wrappers and the unreachable VersionIdMismatch error, and move
the config-path and version-ID derivations onto WorkflowVersion so the
server, validator, and collector stop re-deriving them.

The collector now owns the collected sources (moving file contents
instead of cloning them), resolves the workflow location once, and shares
the not-found probe with build_run_manifest. The bundler reads a goal
file once instead of twice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 16:29:45 -04:00
Scott Werner
57bbb923c2 Keep the RunSummaryStore name until the SQL cutover
Revert the run summary -> run record rename. The SQLite store is still
the summary read model today; it only grows an inactive events table
here. Renaming it now made the store file show as a delete plus add and
touched nine unrelated files. The final rename happens once, when the
SQL store becomes the run authority.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 15:43:56 -04:00
Scott Werner
e86dd3bea4 Move automation runs to workflow versions 2026-08-27 15:24:07 -04:00
Bryan Helmkamp
b34f2b8207
Allow Daytona snapshots from Docker images 2026-08-27 15:02:50 -04:00
Scott Werner
ff2aef4564 Simplify SQL run record store write path and test fixtures
Share one bind helper across the runs insert/upsert/update statements,
compute the next event sequence once per append, and decode stored
sequence columns through a single helper. Check the run head before
decoding events, rewrite the first-visit stage listing as a UNION ALL so
each arm uses its partial index, and share the run_events insert SQL
with the test seeder.

Collapse the duplicated in-memory pool fixture, remove two tests that
only asserted Arc sharing, and fold the fabro-db test row helpers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 14:59:34 -04:00
Scott Werner
b62e458289 Add inactive SQL run storage foundation 2026-08-27 13:32:01 -04:00
Scott Werner
50fed849f3 Simplify automation run-target plumbing
- Materializer derives the manifest GitContext from RunTarget::validate()
  instead of hand-building it and re-parsing the repository slug
- Drop parse_github_repository_slug and InvalidRepositorySlug, now unused
- Store reuses Automation::git_target() instead of a private duplicate
- Legacy TOML import returns the target directly rather than a tuple
- Automation target migration updates columns with a single UPDATE ... FROM
- Web: share gitTarget(), targetFromFormValues(), and one SHA validator
  across the automation form, list, detail, new, and edit views

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 18:17:22 -04:00
Scott Werner
a65c4ff779 Migrate automations to canonical run targets 2026-08-26 17:35:36 -04:00
Scott Werner
56e759d470
Merge pull request #812 from fabro-sh/codex/git-run-target-tags
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Waiting to run
TypeScript / Test (push) Waiting to run
TypeScript / Build (push) Waiting to run
Add tag support to Git run targets
2026-08-26 15:20:23 -04:00
Bryan Helmkamp
eaba019acb
Warn when run title generation fails 2026-08-26 13:18:39 -04:00
Scott Werner
fbba98defd Add tag support to Git run targets 2026-08-26 11:16:37 -04:00
Scott Werner
34014e6dce
Merge pull request #790 from fabro-sh/codex/run-intent-folder-target
Add local folder run target
2026-08-26 10:34:59 -04:00
Scott Werner
ca9fb1d262 Align folder target warn logs with tracing style
Capture the error with Debug so the source chain stays visible, and use
lowercase fixed message strings per the logging guidelines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 15:40:29 -04:00
Scott Werner
9dc39ce9fa Simplify folder target admission and startup checks
Collapse the triple Folder dispatch in run-intent admission into a single
prepare_intent_target call that canonicalizes and observes Git under one
provider gate, and stop feeding target/git into the compiler input only to
overwrite them afterwards. In run start, hoist the duplicated Folder
rejection out of the Docker and Daytona arms, restore kind_name() for the
Git/None arm, and drop the unreachable absolute/symlink checks that follow
canonicalize. Dedupe the folder-target test fixtures in both crates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 14:53:31 -04:00
Bryan Helmkamp
32d6be7ea5
refactor(model): simplify model test plumbing
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
2026-08-25 14:49:34 -04:00
Scott Werner
b5d517e4b5 Gate folder path access behind Local admission 2026-08-25 14:10:24 -04:00
Bryan Helmkamp
5ead0145b9
feat(model): separate tool and reasoning tests 2026-08-25 13:36:51 -04:00
Scott Werner
c396a6cf6f Add local folder run target 2026-08-25 11:59:37 -04:00
Scott Werner
679d20cb52
Merge pull request #792 from fabro-sh/codex/sqlite-authorization-codes
Move pending CLI authorizations to SQLite
2026-08-25 11:46:10 -04:00
Scott Werner
0001cfba02
Merge pull request #789 from fabro-sh/codex/run-intent-none-target
Add empty workspace run target
2026-08-25 11:25:41 -04:00
Scott Werner
dc1f235c48 Keep retired Slate helpers test-only 2026-08-24 17:31:15 -04:00
Scott Werner
f3ff7f27a4 Keep auth code store naming consistent 2026-08-24 17:26:17 -04:00
Scott Werner
68ef8c7e89 Leave old SlateDB authorization-code records in place
Drop the startup retirement of the auth/code keyspace instead of
carrying one-shot cleanup code forever. The records it deleted are
inert: at most a handful exist at cutover, every binary (old or new)
rejects them within 60 seconds of issue via the expiry check, and
nothing reads the keyspace after the move to SQLite. The refresh-token
retirement keeps its original inline shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 17:25:59 -04:00
Scott Werner
a2f0167844 Simplify SQLite authorization-code cutover
Cleanup pass over the pending-CLI-authorization move to SQLite:

- Extract a shared Database::retire_keyspace helper; the refresh-token
  and authorization-code retirements are now one-line wrappers over it.
- Inline the startup retirement call (dropping the single-use wrapper,
  its context-chain test, and the test_close_slate hook it required)
  and run both SlateDB retirement scans concurrently. Error policies
  are unchanged: authorization codes fatal, refresh tokens best-effort.
- Add a shared sqlite_row module with typed identity/timestamp row
  decoding, used by both AuthorizationCodeStore and AuthSessionStore;
  the session store's stringly Error::Other corruption errors become
  the typed InvalidStoredIdentity/InvalidStoredTimestamp variants.
- Delete Repository::gc, which had no production callers left and was
  kept alive by its own test; update the record-layer docs to match.
- Deduplicate the SQLite test-support bootstrap into sqlite_test_pool,
  reuse issue() in the invalid-timestamp test instead of a copied
  INSERT, and fold the new table into the existing existence-check loop
  in the fabro-db schema test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 17:25:38 -04:00
Scott Werner
05999036aa Move pending CLI authorizations to SQLite 2026-08-24 17:25:19 -04:00
Scott Werner
3e0adde73d Simplify the empty workspace run target plumbing
- Use a derived deserializer for RunTarget by making `None` an empty struct
  variant, which keeps `deny_unknown_fields` strict without a hand-rolled impl
- Make clone_source_for_run the single owner of the empty-workspace decision
  and drop the duplicated target checks in RunSession::new
- Collapse duplicated target/provider compatibility matches in admission and
  start into single matches, using a strum-derived kind name for messages
- Drop the redundant git override in persist_create_run
- Extract a shared helper for the duplicated unavailable-integration test loop

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 17:21:54 -04:00
Scott Werner
2d292c28f8
Merge pull request #783 from fabro-sh/codex/sqlite-blob-startup-activation
Activate verified SQLite blob storage at server startup
2026-08-24 16:32:59 -04:00
Scott Werner
2f3b6477f2 Add empty workspace run target 2026-08-24 14:49:03 -04:00
Scott Werner
fb833294bf Remove stale auth-session test import 2026-08-24 14:40:38 -04:00
Bryan Helmkamp
c042b9abdc
Merge remote-tracking branch 'origin/main' into feature/bounded-agent-tool-output
# Conflicts:
#	lib/components/fabro-sandbox/src/clone_source.rs
2026-08-24 14:08:04 -04:00
Scott Werner
0e580f0a43 Adapt run intents to activated blob storage
Use the synchronous blob authority established at server startup and remove the obsolete per-request store-open error path.
2026-08-24 14:07:51 -04:00
Scott Werner
776e719383 Harden SQLite blob activation safety
Keep VACUUM snapshots private until permissions and durability are established. Refuse to recreate a missing rollback backup after import has begun, and preserve secondary cleanup failures in startup logs.
2026-08-24 14:02:35 -04:00
Scott Werner
f71d077221 Register the SQLite blob activation bridge as a server migration
The activation module described itself as a temporary compatibility
bridge but bypassed the structure the migrations strategy prescribes: no
dated migrations/ file, no src/migrations.rs registry entry, no
REMOVAL_DEADLINE, and no removal_deadline log field. The strategy doc's
removal checklist (grep REMOVAL_DEADLINE, explicit registry ordering)
would never have surfaced it, letting the bridge silently outlive its
window as a second, parallel migration mechanism in serve.rs.

The module now lives at migrations/2026082301_sqlite_blob_activation.rs,
is registered and re-exported through src/migrations.rs like the two
existing server migrations, carries a REMOVAL_DEADLINE eligibility floor
(removal still requires the evidence and explicit approval in the module
docs), and logs removal_deadline on every activation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 14:02:35 -04:00
Scott Werner
5629dcd7d0 Make snapshot and backup publication durable across power loss
Neither the pre-activation backup nor the pre-migration snapshot fsynced
the staged file contents or the parent directory around the publishing
rename. A crash after the import committed could lose the retained
'.pre-blob-activation.bak' (whose directory entry was never made
durable), and the next activation would then write a new backup that
already contains the imported blobs, silently breaking the documented
pre-activation rollback boundary; a torn staging file could likewise
wedge later boots in backup validation.

write_snapshot_to_staging now syncs the staged file before handing it to
the caller, and both publishers sync the destination's parent directory
after their rename (fabro-db on a blocking task, activation inside its
existing blocking publication task).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 14:02:35 -04:00
Scott Werner
e067de9382 Share one SQLite snapshot-staging helper between fabro-db and activation
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>
2026-08-24 14:02:35 -04:00
Scott Werner
31c7a670d5 Continue startup when the final WAL truncate checkpoint reports busy
PRAGMA wal_checkpoint(TRUNCATE) returning busy=1 aborted server startup.
Any external reader that outlives the pool's five-second busy timeout (a
replication agent, a backup tool, an operator sqlite3 shell) would crash
the boot, and a supervisor restart would loop into the same abort while
the reader persisted, over a condition that threatens no data integrity.

A busy truncate now logs a warning and startup continues; a later
checkpoint truncates the WAL once the reader is gone. Adds the
failure-path coverage the relocated checkpoint lost: a held read
snapshot blocks the truncate and activation still succeeds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 14:02:35 -04:00
Scott Werner
e1abecc9f4 Skip the blob activation disk preflight when no mount matches the database
available_space_for_path returning None aborted startup with a fatal
UnknownFilesystem error, even on a fresh install with zero legacy rows.
Hosts with tmpfs or squashfs roots, network-filesystem data paths, or an
unreadable mount table would fail every boot with no operator override,
while the resource sampler already treats the identical condition as
benign (supported: false) and keeps running.

The preflight now logs a warning and is skipped when free space cannot
be determined; the import, verification, and integrity checks still run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 14:02:35 -04:00
Scott Werner
bccc5750a4 Size the blob activation disk preflight to the remaining import work
The preflight demanded ~1.5x the full legacy inventory bytes free on
every startup, with no credit for rows already imported. Because the
first activation itself consumes about twice the legacy bytes (the
SQLite copy plus the retained backup) and the legacy keyspace stays in
place for the whole retention window, a successfully activated server
could fall below the requirement and become unable to restart until an
operator freed space the server would never write.

The legacy inventory now checks each row's hash against the SQLite blobs
table and reports pending rows and bytes, and the preflight requires
1.5x only the pending bytes plus the backup reserve and fixed headroom.
A warm restart with nothing left to import needs only the headroom.
Also updates the server operations doc for this and for the
verification pass now running only on boots that import rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 14:02:35 -04:00
Scott Werner
94e3d46754 Cut redundant blob scans and hashing from server startup
Startup previously scanned the legacy SlateDB keyspace three times and
SHA-256-hashed every value in each pass (inventory, import,
verification), then read and rehashed every row of the live SQLite blobs
table — on every boot, even a warm restart with nothing to import. With
a large object-store-backed legacy keyspace that makes restart time
proportional to total blob bytes for the whole retention window.

The inventory pass now only validates key shapes and sizes the keyspace;
digests are still validated by the import pass before any row persists.
The independent verification sweep now runs only on boots whose import
actually inserted rows: the import pass itself byte-compares every
already-present legacy row each boot, so a no-op restart is already
fully cross-checked without a third scan or a full-table rehash.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 14:02:35 -04:00
Scott Werner
b56aee570c Pass the SQLite pool directly to the legacy blob import and verification
import_legacy_blobs_into and verify_legacy_blobs_in took a &BlobStore and
extracted its pool through sqlite_pool_for_legacy_import, an Option that
was statically always Some in production (the None arm existed only for
the test-only Slate backend). That accessor forced a clippy
unnecessary_wraps suppression and two WrongTargetBackend error variants
no production caller could ever hit, and the activation path round-tripped
a pool it already owned through a BlobStore it had just built.

Both functions now take &SqlitePool, deleting the accessor, the
suppression, both unreachable variants, and their rejection test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 14:02:35 -04:00
Scott Werner
eb54a8d0f8 Forward fabro-store/test-support through dependent test-support features
fabro-workflow's and fabro-server's src/test_support.rs import
fabro_store::test_support, but their test-support features never enabled
fabro-store/test-support. Workspace builds passed only through feature
unification from other members' dev-dependencies, while per-crate builds
such as `cargo check -p fabro-cli --tests` or
`cargo check -p fabro-server --features test-support` failed with E0432.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 14:02:35 -04:00
Scott Werner
d65785d888 Simplify blob activation and share the test store fixture
Blob activation cleanups:
- Reuse fabro-db's append_to_path, remove_file_if_exists, and
  set_private_permissions instead of local duplicates.
- Return the store directly from activate_blob_storage; the report
  wrapper existed only to be logged internally and then discarded.
- Collapse compute_disk_preflight to return the required free bytes
  instead of echoing its inputs back through a struct.
- Deduplicate the "exactly one ok row" PRAGMA integrity_check protocol
  into one executor-generic helper used by the backup and live checks.
- Skip re-validating a freshly published backup; the staging copy was
  validated immediately before the atomic rename, so only a
  concurrently published file needs its own validation.
- Replace the manual anyhow wrapping plus duplicate error log in
  serve.rs with a plain .context(), matching other startup errors.
- Extract the disk-candidate enumeration in resource_sampler.rs that
  available_space_for_path had copy-pasted from sample_disk_resources.

Test fixture cleanups:
- Route all hand-assembled Database::new(..., test_blob_store()) test
  fixtures (32 sites) through fabro_store::test_support::test_database,
  and make that helper infallible instead of returning an unconditional
  Ok.
- Install the test blob schema from fabro_db::BLOBS_MIGRATION_SQL via a
  test-support-gated optional dependency instead of a four-level
  relative include_str! into fabro-db's migrations directory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 14:02:35 -04:00