Commit graph

2884 commits

Author SHA1 Message Date
Bryan Helmkamp
fe1d9dc691
test: isolate SQLite checkpoint restoration 2026-08-21 17:57:22 -04:00
Bryan Helmkamp
1669791956
test: update clone depth snapshots 2026-08-21 17:47:02 -04:00
Bryan Helmkamp
6179470eb2
feat: default sandbox clone depth to 100 2026-08-21 17:25:58 -04:00
Bryan Helmkamp
4c467cd6ba
refactor(github): simplify repository access checks 2026-08-21 17:12:27 -04:00
Bryan Helmkamp
438bab29f0
feat: support shallow sandbox clones 2026-08-21 17:11:24 -04:00
Bryan Helmkamp
47954f731e
refactor(github): deduplicate additional-repository access plumbing
Consolidate the copies that review found across the feature:

- One GITHUB_CREDENTIAL_HELPER / GITHUB_CREDENTIAL_HELPER_KEY pair in
  fabro-github, with apply_probe_git_env() for probe commands; the runtime
  git bridge, server preflight probe, and live contract test all consume it
  so the probes exercise exactly what the bridge configures.
- GitHubRepositoryAccess::resolve_verified_token() owns the
  resolve-installations-then-mint choreography shared by server preflight,
  workflow initialization, and the live test.
- A shared lookup_installation() helper backs both the shared-installation
  resolution and the mint's installation lookup.
- The contents = read|write rule lives once as
  RunIntegrationsGithubSettings::contents_permission_allows_repository_access.
- The preflight probe paces retries with fabro-sandbox's exported
  replication_backoff() (3s/9s) instead of a contradicting 1s/2s loop, and
  shares one run_ls_remote() runner with the existing remote-ref check.

Also: collapse the dead Ok(None) arm and repeated error blocks in the
preflight token check, drop the derivable bridge_entry_count(), privatize
resolve_permissions() behind resolve_integration(), make
GitHubRepositorySlug ordering/hashing allocation-free, and use EnvVars
constants for env names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:31:48 -04:00
Scott Werner
891eb43e9a
Merge pull request #761 from fabro-sh/codex/strict-legacy-blob-import
Import legacy blobs strictly into SQLite
2026-08-21 16:30:45 -04:00
Bryan Helmkamp
68e3cb8419
fix(llm): capture Venice top-level costs 2026-08-21 15:55:45 -04:00
Jesse Proudman
53efde3930 feat(search): add Venice backend for web_search
Brave stays the default. Shops that already vault VENICE_API_KEY
can drop BRAVE_SEARCH_API_KEY by setting
[server.integrations.search] provider = "venice".

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-21 12:31:03 -07:00
Bryan Helmkamp
84b75f29f1
docs(api): document additional github repository access
- Add `additional_repositories` to the RunIntegrationsGithubSettings
  OpenAPI schema and reuse the canonical Rust settings types through
  `with_replacement`, with type-identity witnesses and JSON parity
  tests for populated and empty repository sets.
- Regenerate the TypeScript API client.
- Document the feature in the GitHub integration and run-configuration
  guides: exact layer replacement rules, single-token scope, gh/API
  support, App-versus-PAT scope, the same-owner/same-installation
  requirement, validation errors, supported Git URL forms, hard-failure
  semantics for declared repositories, GH_TOKEN precedence, and the
  security boundary (no second server-side repository intersection;
  contents = "write" lets any stage push to any declared repository).
  Correct the earlier claim that injecting GITHUB_TOKEN alone makes
  arbitrary additional private clones work.
- Add a dated changelog entry and an opt-in live GitHub App e2e test
  that verifies a scoped multi-repository token reads every declared
  repository (and that a primary-only token cannot), with repositories
  supplied through the test environment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 15:12:23 -04:00
Bryan Helmkamp
d95b6cace1
feat(server): preflight additional github repository access
When a run declares additional repositories, preflight now proves the
whole effective set works instead of treating a minted token as proof:

- It constructs the same validated `GitHubRepositoryAccess` used by
  runtime initialization, so the two paths cannot disagree.
- In App mode it first resolves every repository's installation with
  the App JWT and requires one shared installation ID, naming any
  repository the App cannot see before the mint; then it mints the one
  scoped token, failing with the raw error on rejection.
- Every effective repository gets a non-interactive
  `git ls-remote <url> HEAD` probe through a shared helper that keeps
  the token out of the URL, argv, and errors (a credential helper reads
  GITHUB_TOKEN from the child environment), retries auth-shaped
  failures with the same token to cover replication lag (classified
  via fabro_sandbox::classify_failure), and reports one check per
  repository in deterministic primary-first order under bounded
  concurrency.
- A resolved run environment that defines GH_TOKEN produces a warning
  (gh prefers it over the managed token) without failing preflight.
- With no additional repositories declared, the primary-only mint
  check is byte-for-byte unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:59:45 -04:00
Bryan Helmkamp
d8edd410f3
feat(workflow): bridge git and gh to the shared token
Carry the resolved GitHub integration (permissions plus declared
additional repositories) as one value from run materialization into
workflow startup, and make the sandbox environment reach every declared
repository through the single managed GITHUB_TOKEN.

- `StartServices.github_permissions` becomes
  `github_integration: ResolvedGithubIntegration`; CLI and server
  workers build it with `resolve_integration()` after interpolation and
  pass it through `SandboxEnvSpec` as one unit.
- `build_sandbox_env` constructs the validated
  `GitHubRepositoryAccess` and scopes the App token source to the whole
  effective set. Missing credentials or a missing origin are hard
  initialization errors when additional repositories are declared;
  legacy permissions-only configuration keeps its best-effort behavior.
- When additional repositories are declared, initialization eagerly
  resolves each repository's App installation (naming any repository
  the App cannot see) and the token itself, so an inaccessible declared
  repository fails before the first workflow stage.
- A new `git_bridge` module injects secret-free `GIT_CONFIG_*` entries
  into the stage environment: a github.com credential helper that reads
  `$GITHUB_TOKEN` at invocation time, per-repository SSH-to-HTTPS
  `insteadOf` rewrites, and `GIT_TERMINAL_PROMPT=0`. Entries append
  after a valid user-provided Git config overlay and fail clearly on a
  malformed one. Contract tests drive the installed git binary against
  local fixtures for the rewrite, credential, prefix-collision, and
  overlay-preservation behaviors.
- The long-running ACP notice now says all declared repository access
  expires together.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:47:31 -04:00
Bryan Helmkamp
7bfed23153
feat(github): mint one installation token for the effective repository set
Add `GitHubRepositoryAccess`, the secret-free validated value describing a
run's effective GitHub repository set: the primary origin repository plus
the declared additional repositories with the shared permission map.

- The constructor normalizes HTTPS and both SSH origin spellings to one
  primary slug, rejects a missing or non-GitHub origin when additional
  repositories are declared, rejects primary duplication and cross-owner
  additional repositories, and re-checks that interpolated permissions
  carry `contents = "read"|"write"` — exposing targets in deterministic
  primary-first order.
- `resolve_shared_installation` resolves every target's App installation
  with the App JWT and requires one shared installation ID, naming the
  repository the App cannot see before any mint.
- The installation-token mint now accepts a repository-name list; the
  single-repository entry points delegate to it, and the request body
  lists every projected name with the shared permissions.
- `InstallationTokenSource::for_access` builds a source over the access
  value; caching, refresh margin, and single-flight are unchanged.
- The scripted `MockHttpClient` and test RSA key move to a shared
  crate-internal `tests_mock` module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:24:24 -04:00
Bryan Helmkamp
f2047ad9a9
feat(config): add validated additional github repositories
Add `additional_repositories` to `[run.integrations.github]`: a list of
full `owner/repository` slugs, beyond the implicit run origin, that the
minted GITHUB_TOKEN must cover.

- `GitHubRepositorySlug` gains FromStr, Display, string serde, and
  case-insensitive Eq/Ord/Hash identity while preserving the submitted
  spelling for display and serialization.
- The config layer keeps raw strings; the higher-precedence list
  replaces the lower one wholesale, with `[]` as an explicit clear,
  resolving independently from the `permissions` map.
- Resolution validates each entry with indexed error paths: slug
  grammar, case-insensitive duplicates, one shared owner, the
  499-repository cap, and a required `contents = "read"|"write"`
  permission (templated values are re-checked at the runtime boundary).
- `RunIntegrationsGithubSettings` resolves permissions and repositories
  together through `resolve_integration()` so consumers cannot pick up
  one without the other; the field is omitted from serialization when
  empty, keeping single-repository settings byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:15:46 -04:00
Bryan Helmkamp
7de3b409ed
Request Packages read permission in the GitHub App manifest
Fabro can mint a scoped sandbox GITHUB_TOKEN via
[run.integrations.github.permissions], but apps registered through the
manifest flow could not grant packages = "read" because the manifest
never requested it. Add Packages (read-only) so freshly registered apps
can download private GitHub Packages (for example npm registry
dependencies) inside sandboxes, mirroring how GitHub Actions workflows
use their built-in GITHUB_TOKEN for registry reads.

Existing apps still need the permission added manually in the app's
settings, as the docs already describe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:12:32 -04:00
Scott Werner
3872c04430 Derive initial refresh token session state
Replace the public stored-token row with an initial-token input that carries only token-specific facts. Bind the token to the session and initialize it as unused inside AuthSessionStore so callers cannot create mismatched session/token rows.
2026-08-21 14:01:30 -04:00
Scott Werner
b964602b0b Harden legacy blob import cleanup 2026-08-21 13:58:04 -04:00
Scott Werner
7edef76d77 Revoke replayed auth sessions transactionally
Delete the owning auth session inside the refresh-token rotation transaction when a spent token is replayed. Return the replay outcome only after the revocation commits, and propagate database failures without claiming the chain was revoked.
2026-08-21 13:55:41 -04:00
Scott Werner
a08dff1ce8 Merge main into feat/refresh-tokens-sqlite 2026-08-21 13:16:14 -04:00
Scott Werner
9d3aa7a4d4 Consolidate workflow-version lineage test coverage
The lineage field's `skip_serializing_if` behavior was asserted five times
across three crates. Keep the two assertions in fabro-types, which owns the
attribute, and drop the duplicates:

- Delete `run_created_omits_absent_workflow_version_id` from event/convert.rs,
  a copy of the test above it that re-checked another crate's serde attribute.
  convert.rs's own responsibility is covered by the existing field assertion.
- Delete `legacy_create_input_persists_without_workflow_version_id`, which ran
  the full create() pipeline to prove a hardcoded `None` literal is `None`.
  `CreateRunInput` has no such field, so no input could change the result.
- Fold `run_spec_omits_absent_workflow_version_id` into the adjacent legacy-spec
  test, which already holds an all-`None` record.
- Drop the off-topic spec re-serialization from run_state.rs's retried_from test.

Add `test_support::test_workflow_version_id()` alongside `test_run_provenance()`
and use it everywhere, replacing eight copies of the same magic seed across five
crates plus two assertion sites that recomputed the hash inline. This also
subsumes retry.rs's private helper of the same shape.

Revert the `run_spec_json` parameterization in the projection round-trip test:
`RunProjection` is a `with_replacement` alias for the canonical type, so the
`Some` and `None` call sites exercise identical code.

Have the two run.created literals that mirror a `RunSpec` read the spec's
lineage field instead of hardcoding `None`, so the mirrors stay accurate once a
producer populates it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 12:39:02 -04:00
Scott Werner
27fd48c603 Persist workflow version lineage on runs 2026-08-21 12:39:02 -04:00
Scott Werner
75fa8eca8b Merge remote-tracking branch 'origin/main' into codex/exact-target-checkout
# Conflicts:
#	lib/components/fabro-sandbox/src/clone_retry.rs
#	lib/components/fabro-sandbox/src/daytona/mod.rs
#	lib/components/fabro-sandbox/src/docker.rs
#	lib/components/fabro-sandbox/src/provider/docker.rs
2026-08-21 12:15:59 -04:00
Scott Werner
5104a787ce Bound Daytona post-clone setup 2026-08-21 12:08:23 -04:00
Bryan Helmkamp
db1faf02ec
Fix catalog dispatch invariant for shared models 2026-08-21 11:19:28 -04:00
Bryan Helmkamp
ded92a215d
Update Venice model catalog 2026-08-21 10:37:04 -04:00
Bryan Helmkamp
a611e00fe6
Rescue release pushes when origin/main moves mid-release
The release push raced any commit that landed on main while the release
smoke ran (~15 minutes): git push was rejected as non-fast-forward and
the whole release failed, as seen on the v0.332.0-nightly.1 attempt.
Worse, the push was not atomic — if the tag ref had been accepted while
the main ref was rejected, the release would have shipped from an
orphan commit and main would never have received the version bump.

Make the push atomic (both refs or neither) and add a bounded rescue
loop: on rejection, drop the bump commit and tag this run created,
fast-forward onto the updated origin/main, recompute the version
against freshly fetched tags, and rebuild the bump commit on the new
tip. The fast-forward uses --ff-only so a genuinely diverged local main
(unpushed commits) fails loudly instead of being reset away.

The retried tag can include commits the smoke did not test; those
commits passed CI to land on main, and the Release workflow re-runs the
full test suite on the tagged commit before publishing anything.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 07:40:13 -04:00
Bryan Helmkamp
e89f03b316
Fix flaky run id vs variable timestamp assertion
RunId is a ULID, so its embedded timestamp is truncated to whole
milliseconds, while Variable.updated_at comes from Utc::now() with
sub-millisecond precision. When the variable write and the run creation
landed in the same millisecond, the run id compared as earlier and the
assertion failed. Truncate the variable timestamp to milliseconds so
both sides use the same precision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 06:21:33 -04:00
Bryan Helmkamp
7d771e9b96
Merge remote-tracking branch 'origin/main' into pr-764
# Conflicts:
#	lib/components/fabro-sandbox/src/daytona/mod.rs
#	lib/components/fabro-sandbox/src/push_credentials.rs
#	lib/components/fabro-sandbox/src/sandbox.rs
2026-08-20 22:07:20 -04:00
Bryan Helmkamp
78cb0d1348
Clean up git push retry handling 2026-08-20 21:59:47 -04:00
Bryan Helmkamp
66dc30424a
Merge pull request #729 from fabro-sh/fix/doctor-timeout-budget
Bound doctor diagnostics within client timeout
2026-08-20 21:58:22 -04:00
Bryan Helmkamp
c021d37155
Merge pull request #740 from fabro-sh/fix/redaction-corrupts-executable-spec
Keep the executable run spec out of reach of event redaction
2026-08-20 21:58:09 -04:00
Release Repro
0355a13db8
Merge origin/main into fix/doctor-timeout-budget 2026-08-20 20:51:17 -04:00
Bryan Helmkamp
a64b65b88c
Update blob hash CLI snapshot 2026-08-20 20:50:41 -04:00
Bryan Helmkamp
ca6d9a46da
Merge remote-tracking branch 'origin/main' into fix/redaction-corrupts-executable-spec
# Conflicts:
#	lib/components/fabro-dump/src/lib.rs
#	lib/components/fabro-store/src/run_state.rs
#	lib/components/fabro-store/tests/serializable_projection.rs
#	lib/components/fabro-workflow/src/billing_rollup.rs
#	lib/components/fabro-workflow/src/run_lookup.rs
#	lib/components/fabro-workflow/src/runtime_store.rs
#	lib/foundation/fabro-api/tests/run_projection_round_trip.rs
#	lib/foundation/fabro-test/src/lib.rs
#	lib/foundation/fabro-types/src/run.rs
#	lib/foundation/fabro-types/src/run_event/run.rs
#	lib/foundation/fabro-types/src/run_projection.rs
#	lib/foundation/fabro-types/tests/run_spec_methods.rs
2026-08-20 20:42:20 -04:00
Bryan Helmkamp
2b095612c8
Address run spec persistence review findings 2026-08-20 20:35:52 -04:00
Bryan Helmkamp
f8dd7b1daf
Merge origin/main into daytona-activate-state-transitions 2026-08-20 20:35:00 -04:00
Release Repro
b7e3b660ff
Simplify diagnostics timeout handling 2026-08-20 20:34:38 -04:00
Bryan Helmkamp
82218a228a
Merge pull request #763 from fabro-sh/feat/github-token-source
Add a cached GitHub installation-token source for push credentials
2026-08-20 20:33:04 -04:00
Bryan Helmkamp
e5c0301ccb
Simplify Daytona lifecycle retries 2026-08-20 19:57:19 -04:00
Bryan Helmkamp
f8a82d6865
fix: harden GitHub token refresh handling 2026-08-20 19:56:06 -04:00
Bryan Helmkamp
03ddb3491a
Merge pull request #769 from fabro-sh/feat/git-exec-spans-main
Label sandbox git execs with git_op tracing spans
2026-08-20 19:55:33 -04:00
Release Repro
9aac811953
Merge remote-tracking branch 'origin/fix/max-visits-off-by-one' into fix/max-visits-off-by-one 2026-08-20 19:38:25 -04:00
Release Repro
4eea9b816a
Clarify visit-limit counter comment 2026-08-20 19:38:10 -04:00
Bryan Helmkamp
2456356a9f
Label sandbox git execs with git_op tracing spans
Sandbox exec logs previously required command_len fingerprinting to tell a
push from a credential refresh or a checkpoint commit. The shared git
helpers now instrument their futures with a git_op span, so Daytona's and
Docker's `exec_command: entered` lines inherit the operation label and the
log renders as `git_op{op=push}: exec_command: entered timeout_ms=...`.

Ops: push (git_push_via_exec), refresh-credentials (both providers'
refresh_push_credentials), checkpoint-commit (checked_git_checkpoint),
fetch (fetch_source_run_ref), and metadata-push (the run-metadata snapshot
write). Spans are attached with #[tracing::instrument] — attached to the
future, never an entered() guard held across an await — so they follow the
task across worker threads. No trait or signature changes.

Plan: .ai/plans/git-push-token-resilience.md (PR 3: item 10).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 19:37:44 -04:00
Bryan Helmkamp
eb57c6bfd3
Merge branch 'main' into fix/max-visits-off-by-one 2026-08-20 19:37:24 -04:00
Bryan Helmkamp
234cac93ef
Merge pull request #728 from fabro-sh/codex/skip-daytona-edit-folder-post
Skip Daytona folder creation for file edits
2026-08-20 18:30:00 -04:00
Bryan Helmkamp
e688ee59a7
Merge main into feat/live-run-billing-totals 2026-08-20 18:20:10 -04:00
Bryan Helmkamp
1dc31771c6
Merge pull request #757 from fabro-sh/fireworks-412-failover
Classify provider 412s as failover-eligible account lockouts
2026-08-20 18:17:10 -04:00
Bryan Helmkamp
6885ed40cb
Merge pull request #768 from fabro-sh/sandbox-errors-transient-infra
Classify sandbox state-change rejections as transient infra
2026-08-20 18:16:47 -04:00
Bryan Helmkamp
f88df59163
Classify sandbox state-change rejections as transient infra
A Daytona "Sandbox state change in progress" rejection surfacing
through the pipeline lifecycle path ("Pipeline lifecycle operation
failed") matched no transient-infra hint, so the run failure was
categorized deterministic. The condition is a provider lifecycle
transition that finishes on its own — the definition of transient
infrastructure — and the deterministic label misinforms retry
machinery and anyone reading the failure.

Add two transient-infra hints: the provider rejection ("state change
in progress") and the bounded-wait timeout an activation reports when
a stop transition outlives its budget ("sandbox stop still in
progress").

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 17:39:19 -04:00