fabro/lib/crates/fabro-config/src
fabro-sh-0530[bot] c81fcc2a27
Auto-migrate legacy [run.sandbox] config files to named-environment syn… (#375)
## Summary

On startup, Fabro now detects confidently migratable pre-v1.0
`[run.sandbox]` config files and rewrites them in-place to the
`[run.environment]` + `[environments.default]` named-environment syntax.
The original file is preserved as a sibling
`*.legacy-sandbox-migration.bak` before any write. Unsupported or
ambiguous keys produce a targeted error listing exact key paths rather
than a generic TOML unknown-field failure.

### Plan Summary

- **New module** `legacy_sandbox_migration.rs` owns all detection,
rewriting, backup logic, and unsupported-key diagnostics — isolated so
it can be deleted before v1.0.
- **`load.rs` hook** catches parse failures on file loads and attempts
migration before re-raising the original error, leaving in-memory
`SettingsLayer` parsing strict and unchanged.
- **Field mappings** cover Daytona (snapshot, volumes, labels,
lifecycle, `auto_stop_interval`) and Docker (image, `memory_limit`,
`cpu_quota` divisible by 100 000, `skip_clone`).
- **Ambiguity guard** rejects files that already contain
`[run.environment]` or `[environments.default]` alongside
`[run.sandbox]`.
- **Docs** add a `<Warning>` block to `environments.mdx` and a new
`2026-05-23.mdx` changelog entry.

This PR also bundles two unrelated improvements that landed in the same
branch: additional sort keys (`repo`, `title`, `workflow`, `changes`)
for the runs list API and UI, and a test isolation fix in `user.rs` that
wraps path assertions in `with_var` to avoid `FABRO_HOME` leakage.

### Migration flow

```mermaid
flowchart TB
    A[load_settings_path] --> B{parse SettingsLayer}
    B -- ok --> G[resolve paths / return]
    B -- err --> C{migrate_settings_path}
    C -- no legacy sandbox --> D[return original parse error]
    C -- has new env config --> E[error: ambiguous, manual fix required]
    C -- unsupported keys --> F[error: list unsupported keys]
    C -- success --> H[write .bak, rewrite file, warn]
    H --> I[parse migrated SettingsLayer]
    I --> G
```


### Fabro Details

<details>
<summary>Ran 0 stages in 53m 26s for $16.82</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| **Total** | **53m 26s** | **$16.82** | **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 && ! 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>
Co-authored-by: Fabro <fabro@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
2026-05-23 19:34:43 -04:00
..
layers Add small_default model role and async generated run titles (#368) 2026-05-23 13:48:39 -04:00
resolve Replace run-scoped sandbox config with named environments (#360) 2026-05-23 13:03:21 -04:00
tests Replace run-scoped sandbox config with named environments (#360) 2026-05-23 13:03:21 -04:00
bind.rs refactor(config): move Bind and ServerDaemon into fabro-config 2026-04-22 16:44:59 -04:00
builders.rs Add small_default model role and async generated run titles (#368) 2026-05-23 13:48:39 -04:00
daemon.rs refactor(config): move Bind and ServerDaemon into fabro-config 2026-04-22 16:44:59 -04:00
defaults.rs route fabro-config parsing through settings fromstr 2026-04-23 19:20:02 -04:00
defaults.toml Replace run-scoped sandbox config with named environments (#360) 2026-05-23 13:03:21 -04:00
envfile.rs refactor(server): lock down server secrets 2026-04-23 07:15:27 -04:00
error.rs fmt: apply nightly rustfmt after merge 2026-04-11 13:43:30 -04:00
home.rs refactor(home): move Home to fabro-util so all crates can share it 2026-04-06 13:08:35 -04:00
input_overrides.rs refactor(llm): simplify catalog cleanup paths (#261) 2026-05-14 09:35:42 -04:00
legacy_sandbox_migration.rs Auto-migrate legacy [run.sandbox] config files to named-environment syn… (#375) 2026-05-23 19:34:43 -04:00
lib.rs Auto-migrate legacy [run.sandbox] config files to named-environment syn… (#375) 2026-05-23 19:34:43 -04:00
load.rs Auto-migrate legacy [run.sandbox] config files to named-environment syn… (#375) 2026-05-23 19:34:43 -04:00
logging.rs refactor(config): share log destination resolution 2026-04-26 17:47:22 -04:00
parse.rs Replace run-scoped sandbox config with named environments (#360) 2026-05-23 13:03:21 -04:00
project.rs fix(validate): pick up sibling workflow.toml inputs for bare .fabro path (#242) 2026-05-12 12:05:21 -04:00
run.rs Move GitHub token permissions to [run.integrations.github.permissions] (#215) 2026-05-05 15:33:31 -04:00
storage.rs refactor(server): unify daemon runtime metadata 2026-04-22 16:07:52 -04:00
user.rs Auto-migrate legacy [run.sandbox] config files to named-environment syn… (#375) 2026-05-23 19:34:43 -04:00