Commit graph

4921 commits

Author SHA1 Message Date
Scott Werner
8c45b870b4 Check out exact sandbox commits 2026-08-14 17:10:46 -04:00
fabro-releases[bot]
535e333970 Bump version to 0.325.0-nightly.0
Some checks failed
Rust / Format (push) Has been cancelled
Rust / Clippy (push) Has been cancelled
Rust / Generated Docs (push) Has been cancelled
Rust / Test (Linux) (push) Has been cancelled
Rust / Test (macOS) (push) Has been cancelled
2026-08-14 09:41:55 +00:00
Scott Werner
830ecf57b3
Merge pull request #742 from fabro-sh/codex/workflow-version-resource
Some checks failed
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) Has been cancelled
TypeScript / Test (push) Has been cancelled
TypeScript / Build (push) Has been cancelled
Add immutable workflow version resource
2026-08-13 17:15:18 -04:00
Scott Werner
8c3ff6216c Revert "Serialize workflow-version canonical bytes once at construction"
This reverts commit 8090d7030984862564a929ee9264e93911014e00.

The cached canonical field was optimizing an unmeasured path: without
the (deferred) O(closure) dependency re-validation multiplier, the
repeated serialization is microseconds for realistic versions. Compute
canonical bytes on demand like the environment, automation, and MCP
stores do, rather than carrying a serde-skipped cache field, a
construction bootstrap, and doubled memory for it. Purely in-memory:
stored blobs and version IDs are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:34:52 -04:00
Scott Werner
180330c117 Remove unused WorkflowPath::parent and is_ancestor_of
Neither method has callers anywhere in the workspace: resolve_reference
splits on '/' directly, and the path-collision validator now checks
ancestor prefixes against a path set. parent() also constructed Self
without going through validate(), so dropping it removes an unvalidated
construction path from the wire type's public API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:34:52 -04:00
Scott Werner
75fb1e3a1d Prove workflow-version ID JSON parity with the OpenAPI schema
The round-trip fixtures only used empty workflow_dependencies, so no
WorkflowVersionId value ever appeared on the wire in a fabro-api
assertion and CreateWorkflowVersionResponse had no coverage at all.
Put a real 64-hex id in the fixture, round-trip the response type, and
pin serialization to the schema's ^[0-9a-f]{64}$ pattern including
lowercase normalization of case-insensitive input.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:34:52 -04:00
Scott Werner
cc711027da Parse workflow version IDs case-insensitively
WorkflowVersionId bolted a lowercase-only byte scan onto BlobHash
parsing, giving the same 64-hex concept two parse behaviors across
entry points. Identity is the decoded 32-byte digest and canonical
serialization always emits lowercase, so accepting either case on
input is lossless — the stored-blob canonicality check still rejects
non-canonical bytes independently. Delegate straight to BlobHash.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:34:52 -04:00
Scott Werner
20c9fba0b1 Serialize workflow-version canonical bytes once at construction
WorkflowVersion::new serialized the whole version just to enforce the
size limit and threw the bytes away, the store re-serialized them to
write the blob, and every read re-serialized a third time for the
canonicality comparison. Cache the canonical bytes on the struct at
construction (skipped during serde) and expose them as an infallible
borrow; the now-unconstructable InvalidShape store error variant goes
away with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:34:52 -04:00
Scott Werner
e688bd9876 Return 422 for invalid workflow-version dependencies
DependencyInvalid fell through to the curated 500 even though the
OpenAPI contract promises 422 workflow_version_dependency_not_found for
an absent, invalid, or non-canonical dependency. Route it to that
response alongside DependencyNotFound; the top-level message only names
the caller-supplied path and id, so no internal chain leaks. Drop the
InvalidVersion/InvalidShape arms, which were unreachable from the only
call site.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:34:52 -04:00
Scott Werner
79e44262aa Detect workflow path collisions hidden by sort order
The adjacent-pair scan over the byte-sorted path list missed
file/directory collisions whenever a sibling path sorted between the
ancestor and its descendant (any byte below '/' after the shared
prefix, e.g. "assets.txt" between "assets" and "assets/item.txt").
Replace it with an exhaustive ancestor-prefix lookup over a path set,
which also catches equal paths across files and workflow dependencies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 16:34:52 -04:00
Scott Werner
178320e7a5 Add immutable workflow version resource
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>
2026-08-13 16:34:52 -04:00
Scott Werner
13d09f6e56
Merge pull request #744 from fabro-sh/codex/unify-reference-walkers
Unify the workflow graph reference walkers
2026-08-13 16:10:09 -04:00
Scott Werner
a76e2d7ddc Test graph goal filenames with at prefix 2026-08-13 15:52:54 -04:00
Scott Werner
13755d7c2b Unify the workflow graph reference walkers
Move the static-reference vocabulary out of fabro-workflow so every
consumer shares one definition: ReferenceKind, AttributeScope, and
reference_kind_for_attribute land in fabro-types::graph, and
validate_static_reference plus a new visit_graph_references walker land
in fabro-template. The manifest bundler drops its ad-hoc graph scan and
walks references through the shared walker.

Unifying the walkers forces three semantic alignments, each matching
what the engine actually executes rather than what the old scanners
happened to match:

- stack.child_dotfile is no longer classified as a child-workflow
  reference; the engine never resolved it as one.
- import and stack.child_workflow only count at node scope; graph- and
  edge-level occurrences were scanned but never executed.
- @@-escaped goals flow through the shared walker's escape handling
  instead of the bundler's own prefix stripping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 14:34:23 -04:00
Scott Werner
09c6bd836b Rename SettingsLayer::image_layers to environment_images
Also add environment_images_mut and adopt it in the run compiler's
Dockerfile resolution, replacing the hand-rolled iteration over named
environments plus the run environment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 14:34:23 -04:00
fabro-releases[bot]
d5b3da87fc Bump version to 0.324.0-nightly.0 2026-08-13 09:41:35 +00:00
Scott Werner
3226d845bc
Merge pull request #726 from fabro-sh/codex/extract-working-tree-collector
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
Extract workflow bundling from manifest assembly
2026-08-12 14:01:31 -04:00
Scott Werner
04f45b7c6b Restore lexical root path normalization and simplify bundler internals
Route the root workflow through collect_workflow_entry so relative root
arguments are lexically normalized before reading, matching the pre-refactor
behavior: `..` segments no longer resolve through symlinks to a file other
than the one the manifest key names, and `~`-prefixed references are
rejected again. Adds a symlink regression test for the root argument.

Also:
- collect_workflow_entry/collect_workflow_location return the manifest key,
  so bundle() no longer recomputes the root key
- hold one FilesystemTemplateStore on the bundler instead of rebuilding it
  per template reference
- drop the unused Clone derive on WorkflowScanInput
- replace the hand-rolled JSON literal in the characterization test with an
  insta snapshot per the testing strategy
- share one write_file fixture helper between the lib and bundler test
  modules
- remove the bundler git-push test; the bundler has no git code path, so the
  test could not fail

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 13:44:18 -04:00
Scott Werner
4383ce408d
Merge pull request #743 from fabro-sh/codex/rename-blob-hash
Rename RunBlobId to BlobHash
2026-08-12 12:29:48 -04:00
Scott Werner
62ed7cb8a2 Rename RunBlobId to BlobHash 2026-08-12 11:33:41 -04:00
Scott Werner
0a40061783
Merge pull request #741 from fabro-sh/codex/share-global-blob-store
Share one blob store across run handles
2026-08-12 11:32:32 -04:00
Scott Werner
2ee6109006 Warn on malformed blob keys and drop structural sharing test
Skipping a malformed key under blobs/sha256 during listing now emits a
warn! so operators get a signal when the CAS namespace contains garbage,
matching the projection-cache warmup skip path. Also removes the
runs_share_database_blob_store test, which asserted Arc pointer identity
of internal wiring rather than any observable behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 11:26:35 -04:00
Scott Werner
f773a24758 Simplify run handle construction and blob store test fixtures
- Collapse RunDatabase::open_writer/open_reader wrappers into one
  pub(crate) build, with a Database::open_run_database helper that
  gathers the shared-store dependencies in one place
- Stop fetching the blob store on open_run's active-cache hit path
- Share a raw-db test fixture between the two BlobStore raw-key tests
- Evict the cached writer in open_run_reader_is_read_only so the test
  exercises the real reader construction path

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 10:51:19 -04:00
Scott Werner
10499e707c Share one blob store across run handles 2026-08-10 15:32:47 -04:00
Scott Werner
996c7ade80 Retry code analysis 2026-08-07 08:48:10 -04:00
Bryan Helmkamp
3421c4f06f
Keep the executable run spec out of reach of event redaction
Two root-cause fixes for the sandbox failure where an inline Dockerfile
came back from the store as `ARG REDACTED` and the Daytona snapshot
build died on the unset variable.

Entropy redaction measures values, not assignment pairs. The detector
matched `NAME=value` as one token, so an uppercase name merged its
charset into a pure-hex value (which alone can never exceed 4.0 bits)
and pushed the pair over the 4.5-bit threshold — then replaced the
whole pair, destroying the name. `find_entropy_regions` now strips an
identifier-shaped `NAME=` prefix before measuring and redacts only the
value, matching the gitleaks layer's `key=REDACTED` shape.

Execution no longer reads redacted content. Every stored event passes
through the redaction sink, and `load_from_store` rehydrated the
worker's RunSpec from the projection folded from those events — so a
redactor false positive silently rewrote the spec the sandbox builds
from (and changed its snapshot identity). The creation path now writes
the exact spec bytes to the content-addressed blob store and records
`spec_blob` on run.created; `load_from_store` loads the spec from the
blob, keeping the event stream authoritative for run identity,
provenance, and event-recorded blob ids. Retry and fork carry the
source run's `spec_blob` forward, so derived runs stop inheriting the
redacted copy. Runs created before the blob existed fall back to the
folded spec.

The projection and every API surface keep serving the redacted fold;
blobs were already stored unredacted (the workflow bundle carries the
same bytes), so this adds no new exposure at rest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 21:44:46 -04:00
Bryan Helmkamp
4e24dcb68a
Add failing tests for run-spec redaction corruption
The entropy redactor rewrites NAME=<hex> assignment pairs to a bare
REDACTED, and the worker rehydrates its executable RunSpec from the
projection folded from redacted stored events. Together these broke
Daytona snapshot builds for any run definition whose inline Dockerfile
pins a git SHA: the spec came back as `ARG REDACTED`, the build died on
the unset variable under `set -eu`, and the environment's snapshot
identity silently changed.

Pin the intended contracts with red tests:

- fabro-redact: an assignment whose value alone is below the entropy
  threshold survives redaction (pure hex cannot exceed 4.0 bits; only
  the name+value charset merge crosses 4.5), and a genuinely
  high-entropy value is redacted without destroying the key name.
- fabro-workflow: the spec that load_from_store rehydrates round-trips
  byte-identical through the store, including content that looks like
  a secret — event redaction must not reach execution.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 21:10:49 -04:00
Scott Werner
7971c80fd4 Retry code analysis 2026-08-06 16:44:48 -04:00
Scott Werner
227e520400 Simplify workflow bundling extraction 2026-08-06 16:21:10 -04:00
Scott Werner
17bc48acf1 Merge remote-tracking branch 'origin/main' into codex/extract-working-tree-collector 2026-08-06 16:01:24 -04:00
Bryan Helmkamp
57547ed7b6
Populate repo and workflow filters in runs list view
The Repo and Workflow dropdowns on the runs page derived their options
from the board query, which is disabled in list view. With ?view=list,
the options were always empty even when runs were visible.

Derive the options from whichever data source the current view loads:
the board query in columns view, or the current page of the paginated
list query in list view. Extract the option-building into an exported
buildFilterOptions helper that also keeps the active selection in the
options when no loaded run matches it, so the filter button never
renders an undefined label while paginating.

A future change will replace page-derived options with a facets
endpoint plus server-side repo/workflow query params.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 11:54:38 -04:00
Bryan Helmkamp
a4db43a889
Report live billing totals for in-progress runs
Run summaries previously populated billing only from the terminal
conclusion event, so the web UI's size chip showed dollar amounts only
after a run completed — even though the size letter was already derived
from live per-stage usage. Derive billing from the same projected total
the size uses. projected_billing already prefers the conclusion's
billing once a run concludes, so completed runs still report the
authoritative final total.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 11:54:32 -04:00
Bryan Helmkamp
f6932529fa
Let a node execute max_visits times before the cycle guard fires
The executor incremented a node's visit count on entry and refused the
visit once the count reached the limit, so a node with max_visits=N
executed at most N-1 times. The documented contract in
stages-and-nodes.mdx is "Max times this node can execute in a run",
and both published examples describe bounded retry loops under that
reading. A graph with max_visits=2 on a designed
one-correction loop therefore failed as "stuck in a cycle" before the
correction could run.

Check the completed-visit count before entry instead: a node with
max_visits=N now executes exactly N times, and the refused entry is
not reported as a visit, so the error's count names the executions
that actually happened. Also correct the nlspec example prose, which
claimed the workflow "moves on with the best result" at the limit;
exceeding max_visits fails the run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 21:01:53 -04:00
Bryan Helmkamp
e11d268e30
Bound doctor diagnostics within client timeout 2026-08-05 08:43:41 -04:00
fabro-releases[bot]
0abf2297c0 Bump version to 0.316.0-nightly.0
Some checks failed
Rust / Format (push) Has been cancelled
Rust / Clippy (push) Has been cancelled
Rust / Generated Docs (push) Has been cancelled
Rust / Test (Linux) (push) Has been cancelled
Rust / Test (macOS) (push) Has been cancelled
2026-08-05 10:08:51 +00:00
Scott Werner
8c95011fbf Rename bundler intermediates as records 2026-08-04 15:49:48 -04:00
Scott Werner
2ff54bfc1b Model bundle collection as workflow bundler 2026-08-04 15:41:50 -04:00
Bryan Helmkamp
751824b9f2
Merge pull request #715 from fabro-sh/feat/async-pr-create
Some checks failed
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) Has been cancelled
TypeScript / Test (push) Has been cancelled
TypeScript / Build (push) Has been cancelled
Make pull request creation durable and asynchronous
2026-08-04 15:10:03 -04:00
Bryan Helmkamp
6dfe1c49d2
Merge remote-tracking branch 'origin/main' into feat/async-pr-create
# Conflicts:
#	lib/foundation/fabro-api/src/lib.rs
#	lib/foundation/fabro-client/src/client.rs
2026-08-04 15:04:24 -04:00
Bryan Helmkamp
6dfeeca45e
perf(agent): skip Daytona folder request for edits 2026-08-04 15:01:19 -04:00
Bryan Helmkamp
646d7e8a29
Merge pull request #721 from fabro-sh/fix/interrupt-steering-task-reminder
Keep task reminders transactional across interrupts
2026-08-04 14:59:19 -04:00
Scott Werner
afd06bf560 Rename collector to workflow bundle 2026-08-04 14:54:56 -04:00
Bryan Helmkamp
5c6289df80
Simplify async pull request creation
Structural cleanup of the durable pull request creation feature, from a
three-agent review (reuse, quality, efficiency) of the branch:

- Move the supervisor out of handler/ into server/pull_request_supervisor.rs,
  collapse its double bookkeeping into one task-id map, and fold the five
  copy-pasted failure arms into attempt_pull_request_creation.
- Tag pull_request.failed events with the creation id they resolve, so a
  publish-stage failure can never fail an unrelated explicit creation. The
  reducer gains PullRequestCreation::succeed/fail transition methods.
- Scan pending creations through a narrow projection-cache accessor instead
  of materializing every run summary, raise the scan interval to 30s (notify
  covers the live path), and cap retries for runs whose worker cannot even
  record a failure.
- Answer "creation already pending" POSTs before taking the per-run create
  lock, which a worker can hold for the whole creation.
- Replace the hand-rolled per-run lock map with fabro_store::KeyedMutex.
- Reuse cheap Arc'd projections (cached_run_projection) on the poll endpoint
  and in the worker instead of deep-cloning run summaries and diffs.
- Merge ExistingPullRequest into fabro_github::CreatedPullRequest and
  extract one reconcile_existing_pull_request helper for both call sites.
- Give the client poll loop a 15-minute deadline; document that Retry-After
  and the poll interval are the same constant.
- Resolve a wedged pending creation (run already has a pull request) as a
  durable failure instead of skipping it forever.
- Tests: shared wait_for_pull_request_creation helper, a pinned generation-
  failure assertion, and a new pipeline test proving reconciliation adopts
  an existing PR without an LLM call or create request.

Verified: cargo build --workspace, cargo nextest run --workspace (7,767
passed), nightly clippy -D warnings, fmt --check, insta (no pending), bun
typecheck in fabro-api-client.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 14:51:29 -04:00
Scott Werner
0e703a7770 Simplify working-tree collector and manifest assembly
Apply cleanup review findings on the collector extraction:

- Deduplicate the lexical path-normalization loop: normalize_absolute_path
  now delegates to lexically_normalize_access_path, and it plus
  manifest_path_from_absolute live in working_tree.rs so the module
  dependency points one way (projection -> collector). Drop the redundant
  re-normalization in collect_bundled_file.
- Extract collect_bundled_template_includes to replace the copy-pasted
  goal/prompt template-closure sequence, seed_config_document for the
  duplicated config seeding, and read_source_input for the duplicated
  config reader closures (with the user-settings is_file check hoisted).
- Replace ~100 lines of trivial getters on the Collected* output structs
  with pub(super) fields; keep the CollectedPath newtype encapsulated.
- Assemble the manifest by value, moving collected sources into the wire
  types instead of deep-copying every file a second time; drop two full
  DraftDocument clones that only satisfied the borrow checker; stop
  recomputing manifest paths per file in template-dependency verification.
- Resolve the root workflow once in assemble_current_manifest, removing an
  unreachable duplicate error path; flatten single-use CollectionNamespace
  into a finalize_documents free function.

No behavior change; fabro-manifest tests, clippy, and fmt pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 14:45:28 -04:00
Bryan Helmkamp
9c152ccddf
refactor(agent): simplify task reminder staging and test fixtures
Stage the pending task reminder as a Message and add
Message::to_llm_message so durable history and the round-staged turn
share one turn-to-wire conversion. Replace the one-off
BlockingAfterFirstOutputProvider with request capture and an
EventsThenPending variant on ScriptedStreamProvider, add a shared
make_session_with_provider_and_tools helper, and assert the reminder
tests against task_reminder::TASK_REMINDER_TEXT instead of a
substring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 14:20:32 -04:00
Bryan Helmkamp
120f0fa80b
Merge pull request #725 from fabro-sh/codex/fireworks-kimi-k3-fast
Add Fireworks Kimi K3 and Kimi K3 Fast support
2026-08-04 14:03:23 -04:00
Scott Werner
9f13611e83 Extract working-tree collection from manifest assembly 2026-08-04 13:59:48 -04:00
Bryan Helmkamp
4dddbcee75
Merge pull request #720 from fabro-sh/codex/add-qwen3-8-max-openrouter
Add Qwen3.8 Max to OpenRouter
2026-08-04 13:56:58 -04:00
Bryan Helmkamp
2b29dddb33
feat(models): add Fireworks Kimi K3 Fast 2026-08-04 13:55:48 -04:00
Bryan Helmkamp
5ed32c1d20
Merge pull request #724 from zaibon/fix/doctor-health-timeout
fix(cli): raise doctor health check timeout to 1s
2026-08-04 11:59:42 -04:00