Commit graph

314 commits

Author SHA1 Message Date
Scott Werner
3ab584691d Drop the branch-head compatibility wrapper
`branch_head_sha` existed to keep callers compiling while the branch-head
lookup moved onto the repository reader. Its last caller now opens a
reader directly, so the wrapper only made the typed API worse: it joined
an already-validated owner and repo into a slug so the reader could split
them apart again, invented an "invalid repository coordinate" error for a
value validated upstream, opened a fresh credential session per call, and
flattened `RepositoryReadError` into `anyhow` while keeping one variant —
leaving callers unable to tell a rate limit from a rejected token.

Its integration test pinned the wrapper rather than the behavior. Replace
it with one that asserts the same 404-means-not-observable semantics
through `resolve_commit`, which is where that contract actually lives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 15:43:08 -04:00
Scott Werner
8dfda7ede0 Reduce duplication in GitHub repository reads
Share the twin's handler scaffolding, collapse the reader's parallel URL
and error machinery, and resolve branch-head credentials once per verify.

Twin GitHub server:
- Add handlers/support.rs holding the response envelope, installation-token
  authorization, Accept matching, and commit-SHA checks. The commits and
  contents handlers carried byte-identical copies of all six items, and
  pulls.rs had its own copy of the two response mappers.
- Add AppState::find_repository and repository_mut, replacing four
  open-coded repository lookups.
- Add head_refs and heads_selector so the heads/{branch} mapping is
  spelled once instead of in add_repository, the fixture conversion, and
  the branch handler.
- Key repository files by commit SHA then path rather than by a
  (String, String) tuple, which drops two allocations and two full-map
  scans per content request.

Repository reader:
- Use DisplaySafeUrl, which removes the file-scope disallowed_types
  suppression and the direct url dependency. The suppression covered the
  whole module and everything later added to it.
- Build {api_base}/repos/{owner}/{repo} once when the session opens, so
  the URL builders become infallible methods and three unreachable
  cannot-be-a-base error paths disappear.
- Collapse the per-operation NotFound and Unavailable variants into ones
  carrying the operation, derive its rendering with strum, and mark the
  error non_exhaustive.
- Return the status classification as one Err(match), size the body
  buffer from Content-Length, and lowercase the resolved SHA in place.

Pull request pipeline:
- Open one reader before the branch-head retry loop instead of once per
  attempt. With App credentials each attempt previously minted a fresh
  installation token, costing two extra round trips per retry. Only the
  ref lookup is retried now; credential failures surface immediately.

Tests keep their coverage: one helper opens readers across eight call
sites, and the repository file fixtures become a table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 15:15:43 -04:00
Scott Werner
13bc1f35de Add bounded GitHub repository reads 2026-08-17 12:56:09 -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
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
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
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
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
62ed7cb8a2 Rename RunBlobId to BlobHash 2026-08-12 11:33:41 -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
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
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
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
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
Scott Werner
9f13611e83 Extract working-tree collection from manifest assembly 2026-08-04 13:59:48 -04:00
Bryan Helmkamp
2b29dddb33
feat(models): add Fireworks Kimi K3 Fast 2026-08-04 13:55:48 -04:00
Bryan Helmkamp
6ce418a415
Merge pull request #723 from fabro-sh/remove-pre-run-push-outcome
Remove the recorded pre-run push outcome while preserving the push
2026-08-04 11:45:32 -04:00
Scott Werner
3d77d371c6 Remove the recorded pre-run push outcome, keep the push
The manifest builder's best-effort pre-run push converted every result
into a PreRunPushOutcome that was serialized into GitContext, expanded
into five OpenAPI union arms, and generated into API clients — but no
production path ever read it; every field read was a test.

Delete the concept while preserving the behavior:

- Drop the PreRunPushOutcome enum and GitContext.push_outcome from
  fabro-types; GitContext keeps origin_url, branch, optional sha, and
  dirty, which remain real execution inputs and provenance.
- Rename the manifest outcome builder to push_manifest_branch_best_effort,
  a side-effect-only helper with the same decision rules: skip without an
  origin, skip on configured-repository mismatch, skip when the branch is
  already synced, otherwise push noninteractively and discard the result
  without failing manifest creation or logging raw Git stderr.
- Prove the push through repository state instead of the deleted enum: a
  branch ahead of a local bare origin is pushed during manifest build, a
  mismatched configured repository is not, and a failing remote helper
  still cannot fail manifest creation.
- Remove push_outcome from GitContext in OpenAPI, delete the five-arm
  union schemas, and drop the fabro-api type replacement and re-export.
- Keep one regression proving historical run.created events with a nested
  push_outcome still deserialize through ordinary unknown-field tolerance
  and reserialize to the reduced shape. No migration or event rewrite.

Old JSON carrying the removed field stays readable. Newly generated
clients omit a field older servers required, so new-client-to-old-server
compatibility is intentionally not promised for this pre-1.0 contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 19:27:33 -04:00
Scott Werner
e38f372eeb Move input scalar coercion to shared types 2026-08-03 19:17:03 -04:00
Bryan Helmkamp
b6fdd9df72
Keep task reminders transactional across interrupts 2026-08-03 16:56:54 -04:00
Bryan Helmkamp
c673ef4fde
Merge pull request #719 from fabro-sh/remove-manifest-display-metadata
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
TypeScript / Typecheck (push) Has been cancelled
TypeScript / Test (push) Has been cancelled
TypeScript / Build (push) Has been cancelled
Remove unused run-manifest display and provenance metadata
2026-08-03 16:45:32 -04:00
Bryan Helmkamp
53d96e27f9
Merge pull request #718 from fabro-sh/github-coordinate-shared-types
Move GitHub coordinate validation to shared types
2026-08-03 16:45:23 -04:00
Scott Werner
2ecb8e0366 Test unknown run event properties generically 2026-08-03 16:23:00 -04:00
Scott Werner
d728ad5508 Remove unused run-manifest display and provenance metadata
Drop ManifestTarget.identifier (the raw token the user typed) and
ManifestGoal.path (the original goal-file path) from the OpenAPI
manifest schema, the Rust manifest builder, the regenerated Rust and
TypeScript client types, and every canonical test fixture. Neither
field had a production reader: the server selects the workflow by
target.path and consumes only the resolved goal type and text.

Target path, goal type/text, manifest versioning, and submitted-byte
persistence are unchanged. Old request bodies that still carry the
removed properties remain accepted through unknown-field tolerance,
pinned by a dedicated public-route regression test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 15:42:18 -04:00
Scott Werner
85dfcd36f9 Scope synchronous fixture I/O allowance to tests 2026-08-03 15:32:36 -04:00
Scott Werner
4194ff4a26 Update run creation event fixtures and docs 2026-08-03 15:09:32 -04:00
Scott Werner
cff0a2461a Trim unused run creation event fields 2026-08-03 15:09:25 -04:00
Scott Werner
cdc88b3158 Move GitHub coordinate validation to shared types
GitHub repository slug and git ref selector syntax now has one owner:
fabro-types::repository defines GitHubRepositorySlug with a try_new
constructor and the is_valid_github_ref_selector predicate.
fabro-automation keeps its public type path as a re-export of the same
type and delegates its existing parser and ref validation to the shared
grammar, preserving its exact error variants and messages. Server
checkout and materialization code imports the type from its canonical
owner. No wire, API, or behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 14:53:46 -04:00
Scott Werner
5a4a25ccc7 Remove the run-creation reopen fallback 2026-08-03 12:34:37 -04:00
Scott Werner
7104570049 Remove duplicate-run handling from store creation 2026-08-03 12:34:32 -04:00
Bryan Helmkamp
60cf4fdd8f
Merge pull request #712 from fabro-sh/remove-client-run-id
Make the server the only allocator of run IDs
2026-08-03 11:17:47 -04:00
Bryan Helmkamp
30ce02f2a8
Make pull request creation durable and asynchronous 2026-08-01 19:28:52 -04:00
Bryan Helmkamp
a966de25b3
Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Bryan Helmkamp <19+brynary@users.noreply.github.com>
2026-08-01 12:17:16 -04:00
Bryan Helmkamp
885bc92122
Fail runs that bypass goal gates 2026-08-01 12:10:12 -04:00
Scott Werner
96bfeb156f Make the server allocate all run IDs 2026-08-01 11:47:11 -04:00
Scott Werner
be91a5ef89 Remove run IDs from create manifest producers 2026-08-01 11:47:11 -04:00
Bryan Helmkamp
f212594875
Merge pull request #694 from fabro-sh/feat/reusable-subagent-sessions
Reuse completed subagent sessions
2026-08-01 10:21:41 -04:00
Bryan Helmkamp
a617eaa5eb
docs: record why a resumed turn's send cannot hang an agent
Review asked twice whether `permit.send` can leave an agent Running with
no turn on its way. It cannot, and the reasoning is not local to the call,
so state it there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:17:10 -04:00
Bryan Helmkamp
41cd1aac49
Merge pull request #693 from fabro-sh/fix/human-input-timeout-accounting
Pause workflow timeouts during human input
2026-08-01 10:13:49 -04:00
Bryan Helmkamp
8c19b1e94a
fix: keep forwarding a reused child's events after a broadcast lag
The subagent event forwarder left its loop on any `recv` error, including
`Lagged`. A lagged broadcast receiver stays usable, so one transient lag
silenced the child for the rest of its life while the task completed
normally and shutdown joined it without noticing. Session reuse widens
that window from a single turn to the whole parent session.

Also borrow each result's output when rendering a parent notification
instead of cloning it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:13:27 -04:00