fabro/lib/crates/fabro-api/tests
fabro-sh-0530[bot] bd72570437
Add provider-backed sandbox inventory API and rename SandboxProvider to… (#409)
## Summary

Exposes `GET /api/v1/sandboxes` and `GET /api/v1/sandboxes/{id}`
endpoints that query sandbox inventory directly from configured
providers (Docker, Daytona), independent of run projections. Also
renames the existing `SandboxProvider` enum to `SandboxProviderKind`
throughout the codebase to free the name for the new `SandboxProvider`
trait.

### Plan Summary

- **OpenAPI + types**: New `SandboxInfo`, `SandboxListResponse`,
`SandboxListMeta`, `SandboxProviderLookupError`, and
`SandboxProviderKind` schemas added to the API spec; canonical Rust DTOs
added to `fabro-types`.
- **Provider trait and registry**: `SandboxProvider` trait (`list`,
`get`, `create`, `delete`) and `SandboxProviderRegistry` introduced in
`fabro-sandbox/src/provider.rs`. Registry fans out calls across all
configured providers and implements fail-soft semantics for list and
conflict/unavailable detection for get.
- **Provider implementations**: `DockerSandboxProvider` uses Bollard
label-filtered container listing and per-inspect;
`DaytonaSandboxProvider` uses the SDK with paginated label-filtered
listing. Both verify `sh.fabro.managed=true`.
- **Shared detail mapping**: Docker and Daytona inspect-to-`SandboxInfo`
paths extracted into `docker_info_from_inspect` /
`daytona_info_from_sdk_sandbox` so run-scoped `SandboxDetails` and
inventory `SandboxInfo` share the same normalization logic.
- **Monitoring UI**: `RunsInfo` now exposes `scheduler_slots_used`; the
monitoring panel displays "slots used" instead of the raw active-run
count.

## What changed and why

**`SandboxProvider` → `SandboxProviderKind`** is a mechanical rename
across ~20 call sites so the unqualified name `SandboxProvider` can be
claimed by the new trait without collision.

**Registry lookup semantics** for `get_managed_by_native_id`:

| Outcome | HTTP |
|---|---|
| Exactly one provider matches | `200` |
| All providers succeed, none match | `404` |
| Two or more providers match the same id | `409` |
| No match + at least one provider failed | `502` |

List is always fail-soft: partial results are returned and failing
providers appear in `meta.provider_errors`.

**`DockerFields` / `DaytonaFields` structs** were introduced inside
`details.rs` to hold the shared normalization output. Both
`map_docker_inspect` (run-scoped) and `docker_info_from_inspect`
(inventory) now delegate to `docker_fields_from_inspect`, eliminating
duplicate field-extraction logic. Same pattern for Daytona.

**`futures` moved from optional to unconditional** in
`fabro-sandbox/Cargo.toml` because `join_all` / `try_join_all` are now
used in `provider.rs`, which is not feature-gated.

**`local` provider** intentionally returns an empty list and `None` for
get — it has no provider-managed inventory.


### Fabro Details

<details>
<summary>Ran 8 stages in 102m 54s for $41.81</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 6s | – | 0 |
| preflight_lint | 2m 20s | – | 0 |
| implement | 56m 40s | $10.82 | 0 |
| simplify_opus | 27m 50s | $26.24 | 0 |
| simplify_gpt | 5m 2s | $4.76 | 0 |
| verify | 8m 24s | – | 0 |
| **Total** | **102m 54s** | **$41.81** | **0** |

</details>

<details>
<summary>Ran <code>ImplementPlan.fabro</code> (11 nodes and 14
edges)</summary>

```dot
digraph ImplementPlan {
    graph [
        goal="Implement and simplify",
        model_stylesheet="
            * { model: claude-opus-4-7; }
        "
    ]
    rankdir=LR

    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    toolchain         [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
    preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", max_retries=0]
    fix_lints         [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
    implement         [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD.", model="gpt-55", reasoning_effort="xhigh"]
    simplify_opus     [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
    simplify_gpt      [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"]
    verify            [label="Verify", shape=parallelogram, script="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && { command -v rg >/dev/null 2>&1 || { echo 'rg is required for verify'; exit 127; }; } && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 2>&1", goal_gate=true, retry_target="fixup"]
    fixup             [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.", max_visits=3]

    start -> toolchain
    toolchain -> preflight_compile [condition="outcome=succeeded"]
    toolchain -> exit
    preflight_compile -> preflight_lint [condition="outcome=succeeded"]
    preflight_compile -> exit
    preflight_lint -> implement [condition="outcome=succeeded"]
    preflight_lint -> fix_lints
    fix_lints -> preflight_lint
    implement -> simplify_opus -> simplify_gpt -> verify
    verify -> exit  [condition="outcome=succeeded"]
    verify -> fixup
    fixup -> verify
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
2026-05-25 22:41:57 -04:00
..
agent_session_activated_props_round_trip.rs Expose PermissionLevel on StageProjection and add sortable run columns (#373) 2026-05-23 19:41:01 -04:00
billed_token_counts_round_trip.rs refactor(api): tighten shared type schemas 2026-04-30 06:20:05 -04:00
command_termination_round_trip.rs feat(command): distinguish cancelled commands from timeouts 2026-04-30 22:45:37 -04:00
diff_stats_round_trip.rs refactor: unify duplicate API types via with_replacement 2026-04-28 12:29:37 -07:00
diff_summary_round_trip.rs feat(runs): surface diff summary counts 2026-05-07 17:34:32 -07:00
event_envelope_round_trip.rs refactor(api): reuse canonical run events 2026-04-29 20:36:59 -04:00
git_context_round_trip.rs refactor: unify duplicate API types via with_replacement 2026-04-28 12:29:37 -07:00
interview_option_round_trip.rs Migrate sandbox config to named environments; add InterviewOption metad… (#372) 2026-05-23 15:47:33 -04:00
interview_question_record_round_trip.rs Migrate sandbox config to named environments; add InterviewOption metad… (#372) 2026-05-23 15:47:33 -04:00
model_costs_round_trip.rs refactor(api): tighten shared type schemas 2026-04-30 06:20:05 -04:00
model_features_round_trip.rs refactor(llm): split provider identity from adapters (#280) 2026-05-16 13:13:41 -04:00
model_limits_round_trip.rs refactor(api): tighten shared type schemas 2026-04-30 06:20:05 -04:00
model_round_trip.rs Add small_default model role and async generated run titles (#368) 2026-05-23 13:48:39 -04:00
model_test_mode_round_trip.rs refactor(api): tighten shared type schemas 2026-04-30 06:20:05 -04:00
pair_round_trip.rs Stage-based pairing API and fabro_run_pair MCP tool (#344) 2026-05-21 21:57:55 -04:00
pending_interview_record_round_trip.rs Migrate sandbox config to named environments; add InterviewOption metad… (#372) 2026-05-23 15:47:33 -04:00
pre_run_push_outcome_round_trip.rs refactor: unify duplicate API types via with_replacement 2026-04-28 12:29:37 -07:00
principal_round_trip.rs feat(web): show creator avatar on run "Created by" cell (#319) 2026-05-20 09:11:57 -04:00
provider_id_round_trip.rs Add small_default model role and async generated run titles (#368) 2026-05-23 13:48:39 -04:00
provider_round_trip.rs Fix verification fixture drift 2026-05-24 12:14:59 -04:00
pull_request_round_trip.rs feat(pr): support GitHub pull request associations (#270) 2026-05-16 12:47:27 -04:00
question_type_round_trip.rs refactor(api): unify leaf API types 2026-04-29 20:21:23 -04:00
run_billing_stage_round_trip.rs feat: Replace duration/elapsed fields with wall_time_ms and StageTiming (#343) 2026-05-21 21:06:40 -04:00
run_event_round_trip.rs Stage-based pairing API and fabro_run_pair MCP tool (#344) 2026-05-21 21:57:55 -04:00
run_failure_round_trip.rs feat: Replace duration/elapsed fields with wall_time_ms and StageTiming (#343) 2026-05-21 21:06:40 -04:00
run_integrations_round_trip.rs Move GitHub token permissions to [run.integrations.github.permissions] (#215) 2026-05-05 15:33:31 -04:00
run_projection_round_trip.rs Migrate sandbox config to named environments; add InterviewOption metad… (#372) 2026-05-23 15:47:33 -04:00
run_sandbox_round_trip.rs Add provider-backed sandbox inventory API and rename SandboxProvider to… (#409) 2026-05-25 22:41:57 -04:00
run_summary_round_trip.rs Replace queued with pending/runnable and add approval flow (web + API s… (#371) 2026-05-23 15:34:33 -04:00
sandbox_details_round_trip.rs Add provider-backed sandbox inventory API and rename SandboxProvider to… (#409) 2026-05-25 22:41:57 -04:00
sandbox_inventory_round_trip.rs Add provider-backed sandbox inventory API and rename SandboxProvider to… (#409) 2026-05-25 22:41:57 -04:00
sandbox_services_round_trip.rs fix(server): improve sandbox service discovery 2026-05-10 12:40:38 -04:00
secret_metadata_round_trip.rs refactor(auth): split credential sources and vault schemas (#306) 2026-05-18 11:07:42 -04:00
secret_type_round_trip.rs refactor(auth): split credential sources and vault schemas (#306) 2026-05-18 11:07:42 -04:00
server_settings_round_trip.rs feat: add server sandbox provider enablement policy (#389) 2026-05-24 16:49:05 -04:00
session_contract_round_trip.rs feat(api): add ask fabro session endpoints (#342) 2026-05-21 21:26:15 -04:00
stage_completion_round_trip.rs refactor(run-projection): use stage vocabulary 2026-05-01 19:56:22 -04:00
stage_handler_round_trip.rs feat(api): expose stage handlers on run stages 2026-05-08 13:36:02 -07:00
stage_model_usage_round_trip.rs Surface reasoning_effort + speed in stage badge end-to-end (#363) 2026-05-23 13:14:55 -04:00
stage_outcome_round_trip.rs refactor(api): tighten shared type schemas 2026-04-30 06:20:05 -04:00
stage_projection_round_trip.rs feat: expose effective agent tool list via StageProjection.agent_tools (#388) 2026-05-24 16:41:55 -04:00
stage_state_round_trip.rs refactor(api): tighten shared type schemas 2026-04-30 06:20:05 -04:00
status_round_trip.rs Replace queued with pending/runnable and add approval flow (web + API s… (#371) 2026-05-23 15:34:33 -04:00
submit_answer_request_round_trip.rs fix(api): type interview answer submissions 2026-05-08 09:18:35 -07:00
workflow_settings_round_trip.rs feat: add run.checkpoint.skip_git_hooks to bypass Git commit hooks (#355) 2026-05-22 17:26:06 -04:00