diff --git a/run.json b/run.json index ce8cb2241..0c43f28a6 100644 --- a/run.json +++ b/run.json @@ -508,7 +508,7 @@ "kind": "running" }, "status_updated_at": "2026-05-28T04:28:33.524509Z", - "last_event_at": "2026-05-28T04:30:55.468096Z", + "last_event_at": "2026-05-28T05:01:49.027788Z", "pending_control": null, "checkpoints": [ { @@ -693,9 +693,9 @@ } }, { - "seq": 0, + "seq": 48, "checkpoint": { - "timestamp": "2026-05-28T04:33:18.678385Z", + "timestamp": "2026-05-28T04:33:22.200981Z", "current_node": "preflight_lint", "completed_nodes": [ "start", @@ -704,29 +704,140 @@ "preflight_lint" ], "node_retries": {}, + "context_values": { + "graph.rankdir": "LR", + "failure_signature": "", + "internal.node_visit_count": 1, + "internal.fidelity": "compact", + "internal.retry_count.preflight_compile": 0, + "thread.start.current_node": "toolchain", + "internal.retry_count.preflight_lint": 0, + "internal.retry_count.start": 0, + "internal.run_id": "01KSPDE8E9ATTVY1VBCS3YRBHT", + "thread.preflight_compile.current_node": "preflight_lint", + "graph.model_stylesheet": "\n * { model: claude-opus-4-7; }\n ", + "internal.retry_count.toolchain": 0, + "current_node": "preflight_lint", + "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", + "graph.goal": "# Server-Owned Environments Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.\n\n**Goal:** Move environment definitions from layered run settings into server-owned TOML resources with CRUD API management, matching the Automation store pattern.\n\n**Architecture:** Add a concrete `EnvironmentStore` that loads one environment TOML file per id from a sibling `environments/` directory next to the active server settings file. Runs continue to select an environment by id through `[run.environment]` or `--environment`, but server-side run creation resolves the id from `EnvironmentStore`; project/workflow/user config can no longer define environment catalogs or environment field overrides. The web UI is intentionally deferred.\n\n**Tech Stack:** Rust, Axum, serde/TOML, `toml_edit`, Tokio file I/O, OpenAPI/progenitor, generated TypeScript API client, cargo-nextest.\n\n---\n\n## File Structure\n\n- Create `lib/crates/fabro-environment/`: environment ids, revisions, API/domain DTOs, TOML persistence, canonicalization, validation, and `EnvironmentStore`.\n- Modify workspace manifests: root `Cargo.toml`, `lib/crates/fabro-server/Cargo.toml`, `lib/crates/fabro-api/build.rs`, and generated API/client package files.\n- Modify `lib/crates/fabro-server/src/server.rs`, `lib/crates/fabro-server/src/server/handler/mod.rs`, and a new `lib/crates/fabro-server/src/server/handler/environments.rs` to wire the store and API.\n- Modify `lib/crates/fabro-config/src/builders.rs`, `lib/crates/fabro-config/src/load.rs`, `lib/crates/fabro-config/src/migrations.rs`, and config tests to treat `[environments]` as migration-only, not runtime configuration.\n- Modify `lib/crates/fabro-manifest/src/lib.rs`, `lib/crates/fabro-server/src/run_manifest.rs`, and CLI run/preflight/graph/validate paths so environment ids are resolved only by the server.\n- Modify install/repo-init/docs/OpenAPI artifacts so new examples use server environment files and run configs only select ids.\n\n## Decisions\n\n- Environment definitions are server-owned operator policy. Project and workflow files may request an id but cannot define or override environment fields.\n- `default`, `local`, `docker`, and `daytona` are seeded if missing. Existing files are never overwritten.\n- `default` is protected from deletion. Other seeded files can be edited or deleted.\n- Environment ids use `[a-z0-9][a-z0-9-]{0,62}`.\n- Environment revisions are SHA-256 hashes of the persisted TOML bytes, returned in JSON as `revision` and in `ETag`.\n- `PUT` and `DELETE` require `If-Match`, following `AutomationStore`.\n- `image.dockerfile = { path = \"Dockerfile\" }` is accepted in persisted files and API input, resolved relative to the environment file or request context, and converted to inline content for runtime use. API writes canonical inline TOML.\n- `--preserve-sandbox` remains a CLI/server argument override. TOML `[run.environment.lifecycle]` is rejected.\n- `--docker-image` is rejected with a targeted message directing operators to create or update a server environment.\n- Existing dense `WorkflowSettings.environments` stays in the API for compatibility and is populated from the server environment catalog during run resolution.\n\n## Task 1: Add `fabro-environment` Store Crate\n\n**Files:**\n- Create: `lib/crates/fabro-environment/Cargo.toml`\n- Create: `lib/crates/fabro-environment/src/lib.rs`\n- Create: `lib/crates/fabro-environment/src/id.rs`\n- Create: `lib/crates/fabro-environment/src/model.rs`\n- Create: `lib/crates/fabro-environment/src/store.rs`\n- Create: `lib/crates/fabro-environment/src/error.rs`\n- Modify: root `Cargo.toml`\n\n- [ ] Create a workspace crate named `fabro-environment`, modeled after `fabro-automation`.\n- [ ] Define `EnvironmentId`, `EnvironmentRevision`, and parse/validation errors.\n- [ ] Define public DTOs:\n - `Environment`: `id`, `revision`, `provider`, `image`, `resources`, `network`, `lifecycle`, `labels`, `volumes`, `env`.\n - `EnvironmentDraft`: `id` plus environment fields.\n - `EnvironmentReplace`: environment fields without id.\n- [ ] Use the existing environment field types from `fabro_types::settings::run` for dense API fields.\n- [ ] Use existing sparse `fabro_config::EnvironmentLayer` only for TOML input/output and conversion; do not create a second environment field vocabulary.\n- [ ] Add conversion helpers that resolve an `EnvironmentLayer` into dense `EnvironmentSettings` using the same provider/network/image validation rules as `fabro-config`.\n- [ ] Implement canonical TOML serialization for persisted files. Omit `id` and `revision`; the filename is the id and the file bytes determine revision.\n- [ ] Implement `EnvironmentStore` with `load_or_seed(dir)`, `list`, `get`, `create`, `replace`, `delete`, and `catalog_layer`.\n- [ ] Seed missing `default`, `local`, `docker`, and `daytona` files from the current built-in defaults. Do not overwrite existing files.\n- [ ] Protect `default` from deletion with a typed store error.\n- [ ] Resolve Dockerfile path references relative to the environment file directory during load and relative to the active settings directory during API create/replace. Store runtime values with inline Dockerfile content.\n- [ ] Add unit tests for loading an absent directory, seeding built-ins, sorted listing, invalid ids, invalid provider, invalid network mode, missing Dockerfile path, create conflict, replace stale revision, default delete rejection, delete success, and canonical revision changes.\n\nRun:\n\n```bash\ncargo nextest run -p fabro-environment\n```\n\nExpected: all `fabro-environment` tests pass.\n\n## Task 2: Make Config Environments Migration-Only\n\n**Files:**\n- Modify: `lib/crates/fabro-config/src/parse.rs`\n- Modify: `lib/crates/fabro-config/src/builders.rs`\n- Modify: `lib/crates/fabro-config/src/load.rs`\n- Modify: `lib/crates/fabro-config/src/migrations.rs`\n- Create: `lib/crates/fabro-config/migrations/2026052801_settings_environments_to_server_files.rs`\n- Modify: `lib/crates/fabro-config/src/defaults.toml`\n- Modify: `lib/crates/fabro-config/src/tests/resolve_run.rs`\n- Modify: `lib/crates/fabro-config/src/tests/resolve_root.rs`\n\n- [ ] Keep `SettingsLayer.environments` in this pass so old files can parse and migrate, but remove environment catalog entries from `defaults.toml`.\n- [ ] Add source-aware validation that rejects `SettingsLayer.environments` for project, workflow, and direct run config layers with this message shape: `[environments.] is now server-managed; move this definition to the server environments directory`.\n- [ ] Add validation that rejects TOML-provided `run.environment.image`, `resources`, `network`, `lifecycle`, `labels`, `volumes`, and `env`. Keep `run.environment.id`.\n- [ ] Ensure CLI/server argument layers can still set `run.environment.lifecycle.preserve` for `--preserve-sandbox`; the rejection applies only to parsed TOML sources.\n- [ ] Add a settings-file migration that extracts top-level `[environments.]` entries from the active `settings.toml` into sibling `environments/.toml` files.\n- [ ] Migration must write a backup before editing `settings.toml`, preserve `[run.environment] id`, remove the top-level `[environments]` table, and fail without changing files if any target environment file already exists.\n- [ ] Chain the existing legacy `[run.sandbox]` migration before the new extraction migration so legacy sandbox settings become a server `default` environment file.\n- [ ] Update run settings tests to assert that `RunSettingsBuilder` no longer resolves a selected environment without an injected server catalog.\n- [ ] Add tests proving project/workflow `[environments]` definitions produce targeted errors rather than silent ignores.\n\nRun:\n\n```bash\ncargo nextest run -p fabro-config\n```\n\nExpected: config tests pass, including migration coverage.\n\n## Task 3: Wire EnvironmentStore Into Server Run Resolution\n\n**Files:**\n- Modify: `lib/crates/fabro-server/src/server.rs`\n- Modify: `lib/crates/fabro-server/src/serve.rs`\n- Modify: `lib/crates/fabro-server/src/run_manifest.rs`\n- Modify: `lib/crates/fabro-server/src/server/handler/runs.rs`\n- Modify: `lib/crates/fabro-server/src/manifest_validation.rs`\n- Modify: `lib/crates/fabro-server/src/test_support.rs`\n\n- [ ] Add `environment_store: Arc` to `AppState`, loaded from `active_config_path.parent().join(\"environments\")`.\n- [ ] Replace `manifest_environment_defaults` from `ServerRuntimeSettings` with `environment_store.catalog_layer()` when preparing manifests on the server.\n- [ ] Keep the dense run snapshot unchanged: `prepared.settings.run.environment` contains the resolved environment fields, and `prepared.settings.environments` contains the server catalog used for resolution.\n- [ ] Convert unknown environment ids into `400 Bad Request` during run creation/preflight/graph preparation.\n- [ ] Keep sandbox provider policy checks after environment resolution, so disabled providers still reject runs.\n- [ ] Apply `--preserve-sandbox` after selected environment resolution.\n- [ ] Remove server reliance on `[environments]` in `settings.toml`.\n- [ ] Update server test support so tests can inject environment files or use seeded defaults.\n- [ ] Add server tests for default environment run creation, custom server environment selection, unknown environment id, disabled provider policy, `--preserve-sandbox`, and rejected TOML environment field overrides.\n\nRun:\n\n```bash\ncargo nextest run -p fabro-server\n```\n\nExpected: server API and run-manifest tests pass.\n\n## Task 4: Add Environment CRUD API\n\n**Files:**\n- Modify: `docs/public/api-reference/fabro-api.yaml`\n- Modify: `lib/crates/fabro-api/build.rs`\n- Create: `lib/crates/fabro-server/src/server/handler/environments.rs`\n- Modify: `lib/crates/fabro-server/src/server/handler/mod.rs`\n- Add tests: `lib/crates/fabro-server/tests/it/api/environments.rs`\n- Update generated Rust and TypeScript API artifacts after spec changes.\n\n- [ ] Add OpenAPI tag `Environments`.\n- [ ] Add schemas for `Environment`, `CreateEnvironmentRequest`, `ReplaceEnvironmentRequest`, and `EnvironmentListResponse`.\n- [ ] Reuse existing environment schemas for provider/image/resources/network/lifecycle/volumes/env.\n- [ ] Add endpoints:\n - `GET /api/v1/environments`\n - `POST /api/v1/environments`\n - `GET /api/v1/environments/{id}`\n - `PUT /api/v1/environments/{id}`\n - `DELETE /api/v1/environments/{id}`\n- [ ] Return `ETag` on retrieve and replace.\n- [ ] Require `If-Match` on replace and delete.\n- [ ] Map store errors to API responses:\n - invalid id: `400`\n - duplicate create: `409`\n - stale revision: `409`\n - validation error: `422`\n - missing resource: `404`\n - protected default delete: `409`\n - persistence failure: `500`\n- [ ] Add route tests for empty-seeded list, create, retrieve with ETag, replace, stale replace, missing `If-Match`, delete, protected default delete, invalid provider, invalid CIDR, and missing Dockerfile path.\n- [ ] Regenerate `fabro-api` and TypeScript client artifacts.\n\nRun:\n\n```bash\ncargo build -p fabro-api\ncd lib/packages/fabro-api-client && bun run generate\ncargo nextest run -p fabro-server --test it -- api::environments\n```\n\nExpected: generated artifacts are updated and environment API tests pass.\n\n## Task 5: Adjust CLI And Manifest Behavior\n\n**Files:**\n- Modify: `lib/crates/fabro-cli/src/commands/run/overrides.rs`\n- Modify: `lib/crates/fabro-cli/src/commands/preflight.rs`\n- Modify: `lib/crates/fabro-cli/src/commands/graph.rs`\n- Modify: `lib/crates/fabro-cli/src/commands/validate.rs`\n- Modify: `lib/crates/fabro-cli/src/commands/repo/init.rs`\n- Modify: `lib/crates/fabro-manifest/src/lib.rs`\n- Modify CLI integration tests under `lib/crates/fabro-cli/tests/it/`\n\n- [ ] Reject `--docker-image` in run, create, preflight, graph, and validate commands with this message shape: `--docker-image is no longer supported; create or update a server environment and select it with --environment`.\n- [ ] Keep `--environment` as an id-only selector in manifest args.\n- [ ] Stop collecting Dockerfile path references from `[environments.]` in project/workflow config because those definitions are invalid.\n- [ ] Keep collecting Dockerfile references for any remaining CLI-created run environment override only when it comes from allowed argument paths; with `--docker-image` rejected, no normal user path should add one.\n- [ ] Update local preflight/graph/validate flows so they either call server preflight for environment resolution or print a clear message that server-owned environment resolution requires a running server.\n- [ ] Update `fabro repo init` to write only `[run.environment] id = \"local\"` and no `[environments.local]` block.\n- [ ] Update CLI tests for manifest args, repo init output, rejected `--docker-image`, and server-owned environment selection.\n\nRun:\n\n```bash\ncargo nextest run -p fabro-cli\n```\n\nExpected: CLI tests pass and no generated workflow config contains `[environments.*]`.\n\n## Task 6: Update Install, Docs, And Generated References\n\n**Files:**\n- Modify install persistence code in `lib/crates/fabro-cli/src/commands/install.rs` and server install handlers/tests.\n- Modify docs: `docs/public/execution/environments.mdx`, `docs/public/execution/run-configuration.mdx`, `docs/public/reference/user-configuration.mdx`, `docs/public/administration/server-configuration.mdx`, `docs/public/administration/sandboxing.mdx`, `docs/public/integrations/daytona.mdx`, and examples that currently define `[environments.]`.\n- Modify generated settings reference if applicable.\n\n- [ ] Update install flows to write server environment files instead of `[environments.default]` into `settings.toml`.\n- [ ] Keep install-written `[run.environment] id = \"default\"` when a default run environment selection is still needed.\n- [ ] Update tests that assert `settings.toml` contains `[environments.default]` to assert the sibling environment file exists and settings no longer contains `[environments]`.\n- [ ] Rewrite public docs so environment definitions are server-owned TOML files and run configs only select ids.\n- [ ] Add a compatibility note explaining that project/workflow `[environments]` definitions now fail and must be moved to the server.\n- [ ] Keep `Settings > Environments` UI documentation out of this pass.\n\nRun:\n\n```bash\ncargo nextest run -p fabro-server --test it -- api::install\ncargo nextest run -p fabro-cli --test it\n```\n\nExpected: install tests pass and docs no longer present project/workflow environment definitions as valid.\n\n## Task 7: Workspace Verification\n\n**Files:**\n- No new files unless test snapshots require reviewed updates.\n\n- [ ] Run Rust formatting check:\n\n```bash\ncargo +nightly-2026-04-14 fmt --check --all\n```\n\n- [ ] Run clippy:\n\n```bash\ncargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings\n```\n\n- [ ] Run workspace tests:\n\n```bash\ncargo nextest run --workspace\n```\n\n- [ ] Run TypeScript checks if API client changes affect the web package:\n\n```bash\ncd apps/fabro-web && bun run typecheck\ncd apps/fabro-web && bun test\n```\n\n- [ ] Inspect generated files and snapshots before accepting any snapshot changes.\n\n## Acceptance Criteria\n\n- Server startup creates or loads `environments/default.toml`, `local.toml`, `docker.toml`, and `daytona.toml`.\n- `GET /api/v1/environments` returns seeded environments with revisions.\n- API-created environments persist as TOML files and survive server restart.\n- Runs using `[run.environment] id = \"cloud\"` resolve from server files only.\n- Project/workflow/user `[environments.]` definitions no longer affect runs.\n- Existing runs keep their dense environment snapshot after environment files change.\n- `--environment` still works.\n- `--preserve-sandbox` still works.\n- `--docker-image` no longer works and produces the targeted replacement guidance.\n- Web UI changes are not included.\n", + "thread.toolchain.current_node": "preflight_compile", + "outcome": "succeeded", + "internal.work_dir": "/home/daytona/workspace/fabro", + "internal.thread_id": "preflight_compile", + "failure_class": "" + }, + "node_outcomes": { + "preflight_lint": { + "status": "succeeded", + "context_updates": { + "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126" + }, + "notes": "Script completed: cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", + "usage": null, + "timing": { + "wall_time_ms": 0, + "inference_time_ms": 0, + "tool_time_ms": 143203, + "active_time_ms": 143203 + } + }, + "toolchain": { + "status": "succeeded", + "context_updates": { + "command.output": "blob://sha256/fc14b2ba2d770e5cd3169df7a29525c962adfc4cfa3097b9098c63ebd61a748c" + }, + "notes": "Script completed: 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", + "usage": null, + "timing": { + "wall_time_ms": 0, + "inference_time_ms": 0, + "tool_time_ms": 1316, + "active_time_ms": 1316 + } + }, + "preflight_compile": { + "status": "succeeded", + "context_updates": { + "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126" + }, + "notes": "Script completed: cargo check -q --workspace 2>&1", + "usage": null, + "timing": { + "wall_time_ms": 0, + "inference_time_ms": 0, + "tool_time_ms": 131121, + "active_time_ms": 131121 + } + }, + "start": { + "status": "succeeded", + "usage": null + } + }, + "next_node_id": "implement", + "git_commit_sha": "43a9a01aee81160c662d7523e358b128cdf476d0", + "node_visits": { + "start": 1, + "preflight_compile": 1, + "toolchain": 1, + "preflight_lint": 1 + } + }, + "diff": { + "summary": { + "files_changed": 0, + "additions": 0, + "deletions": 0 + } + } + }, + { + "seq": 0, + "checkpoint": { + "timestamp": "2026-05-28T05:01:49.528777Z", + "current_node": "implement", + "completed_nodes": [ + "start", + "toolchain", + "preflight_compile", + "preflight_lint", + "implement" + ], + "node_retries": {}, "context_values": { "internal.retry_count.toolchain": 0, "thread.preflight_compile.current_node": "preflight_lint", "internal.retry_count.preflight_lint": 0, - "internal.thread_id": "preflight_compile", - "current_node": "preflight_lint", + "internal.thread_id": "preflight_lint", + "current_node": "implement", "graph.goal": "# Server-Owned Environments Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.\n\n**Goal:** Move environment definitions from layered run settings into server-owned TOML resources with CRUD API management, matching the Automation store pattern.\n\n**Architecture:** Add a concrete `EnvironmentStore` that loads one environment TOML file per id from a sibling `environments/` directory next to the active server settings file. Runs continue to select an environment by id through `[run.environment]` or `--environment`, but server-side run creation resolves the id from `EnvironmentStore`; project/workflow/user config can no longer define environment catalogs or environment field overrides. The web UI is intentionally deferred.\n\n**Tech Stack:** Rust, Axum, serde/TOML, `toml_edit`, Tokio file I/O, OpenAPI/progenitor, generated TypeScript API client, cargo-nextest.\n\n---\n\n## File Structure\n\n- Create `lib/crates/fabro-environment/`: environment ids, revisions, API/domain DTOs, TOML persistence, canonicalization, validation, and `EnvironmentStore`.\n- Modify workspace manifests: root `Cargo.toml`, `lib/crates/fabro-server/Cargo.toml`, `lib/crates/fabro-api/build.rs`, and generated API/client package files.\n- Modify `lib/crates/fabro-server/src/server.rs`, `lib/crates/fabro-server/src/server/handler/mod.rs`, and a new `lib/crates/fabro-server/src/server/handler/environments.rs` to wire the store and API.\n- Modify `lib/crates/fabro-config/src/builders.rs`, `lib/crates/fabro-config/src/load.rs`, `lib/crates/fabro-config/src/migrations.rs`, and config tests to treat `[environments]` as migration-only, not runtime configuration.\n- Modify `lib/crates/fabro-manifest/src/lib.rs`, `lib/crates/fabro-server/src/run_manifest.rs`, and CLI run/preflight/graph/validate paths so environment ids are resolved only by the server.\n- Modify install/repo-init/docs/OpenAPI artifacts so new examples use server environment files and run configs only select ids.\n\n## Decisions\n\n- Environment definitions are server-owned operator policy. Project and workflow files may request an id but cannot define or override environment fields.\n- `default`, `local`, `docker`, and `daytona` are seeded if missing. Existing files are never overwritten.\n- `default` is protected from deletion. Other seeded files can be edited or deleted.\n- Environment ids use `[a-z0-9][a-z0-9-]{0,62}`.\n- Environment revisions are SHA-256 hashes of the persisted TOML bytes, returned in JSON as `revision` and in `ETag`.\n- `PUT` and `DELETE` require `If-Match`, following `AutomationStore`.\n- `image.dockerfile = { path = \"Dockerfile\" }` is accepted in persisted files and API input, resolved relative to the environment file or request context, and converted to inline content for runtime use. API writes canonical inline TOML.\n- `--preserve-sandbox` remains a CLI/server argument override. TOML `[run.environment.lifecycle]` is rejected.\n- `--docker-image` is rejected with a targeted message directing operators to create or update a server environment.\n- Existing dense `WorkflowSettings.environments` stays in the API for compatibility and is populated from the server environment catalog during run resolution.\n\n## Task 1: Add `fabro-environment` Store Crate\n\n**Files:**\n- Create: `lib/crates/fabro-environment/Cargo.toml`\n- Create: `lib/crates/fabro-environment/src/lib.rs`\n- Create: `lib/crates/fabro-environment/src/id.rs`\n- Create: `lib/crates/fabro-environment/src/model.rs`\n- Create: `lib/crates/fabro-environment/src/store.rs`\n- Create: `lib/crates/fabro-environment/src/error.rs`\n- Modify: root `Cargo.toml`\n\n- [ ] Create a workspace crate named `fabro-environment`, modeled after `fabro-automation`.\n- [ ] Define `EnvironmentId`, `EnvironmentRevision`, and parse/validation errors.\n- [ ] Define public DTOs:\n - `Environment`: `id`, `revision`, `provider`, `image`, `resources`, `network`, `lifecycle`, `labels`, `volumes`, `env`.\n - `EnvironmentDraft`: `id` plus environment fields.\n - `EnvironmentReplace`: environment fields without id.\n- [ ] Use the existing environment field types from `fabro_types::settings::run` for dense API fields.\n- [ ] Use existing sparse `fabro_config::EnvironmentLayer` only for TOML input/output and conversion; do not create a second environment field vocabulary.\n- [ ] Add conversion helpers that resolve an `EnvironmentLayer` into dense `EnvironmentSettings` using the same provider/network/image validation rules as `fabro-config`.\n- [ ] Implement canonical TOML serialization for persisted files. Omit `id` and `revision`; the filename is the id and the file bytes determine revision.\n- [ ] Implement `EnvironmentStore` with `load_or_seed(dir)`, `list`, `get`, `create`, `replace`, `delete`, and `catalog_layer`.\n- [ ] Seed missing `default`, `local`, `docker`, and `daytona` files from the current built-in defaults. Do not overwrite existing files.\n- [ ] Protect `default` from deletion with a typed store error.\n- [ ] Resolve Dockerfile path references relative to the environment file directory during load and relative to the active settings directory during API create/replace. Store runtime values with inline Dockerfile content.\n- [ ] Add unit tests for loading an absent directory, seeding built-ins, sorted listing, invalid ids, invalid provider, invalid network mode, missing Dockerfile path, create conflict, replace stale revision, default delete rejection, delete success, and canonical revision changes.\n\nRun:\n\n```bash\ncargo nextest run -p fabro-environment\n```\n\nExpected: all `fabro-environment` tests pass.\n\n## Task 2: Make Config Environments Migration-Only\n\n**Files:**\n- Modify: `lib/crates/fabro-config/src/parse.rs`\n- Modify: `lib/crates/fabro-config/src/builders.rs`\n- Modify: `lib/crates/fabro-config/src/load.rs`\n- Modify: `lib/crates/fabro-config/src/migrations.rs`\n- Create: `lib/crates/fabro-config/migrations/2026052801_settings_environments_to_server_files.rs`\n- Modify: `lib/crates/fabro-config/src/defaults.toml`\n- Modify: `lib/crates/fabro-config/src/tests/resolve_run.rs`\n- Modify: `lib/crates/fabro-config/src/tests/resolve_root.rs`\n\n- [ ] Keep `SettingsLayer.environments` in this pass so old files can parse and migrate, but remove environment catalog entries from `defaults.toml`.\n- [ ] Add source-aware validation that rejects `SettingsLayer.environments` for project, workflow, and direct run config layers with this message shape: `[environments.] is now server-managed; move this definition to the server environments directory`.\n- [ ] Add validation that rejects TOML-provided `run.environment.image`, `resources`, `network`, `lifecycle`, `labels`, `volumes`, and `env`. Keep `run.environment.id`.\n- [ ] Ensure CLI/server argument layers can still set `run.environment.lifecycle.preserve` for `--preserve-sandbox`; the rejection applies only to parsed TOML sources.\n- [ ] Add a settings-file migration that extracts top-level `[environments.]` entries from the active `settings.toml` into sibling `environments/.toml` files.\n- [ ] Migration must write a backup before editing `settings.toml`, preserve `[run.environment] id`, remove the top-level `[environments]` table, and fail without changing files if any target environment file already exists.\n- [ ] Chain the existing legacy `[run.sandbox]` migration before the new extraction migration so legacy sandbox settings become a server `default` environment file.\n- [ ] Update run settings tests to assert that `RunSettingsBuilder` no longer resolves a selected environment without an injected server catalog.\n- [ ] Add tests proving project/workflow `[environments]` definitions produce targeted errors rather than silent ignores.\n\nRun:\n\n```bash\ncargo nextest run -p fabro-config\n```\n\nExpected: config tests pass, including migration coverage.\n\n## Task 3: Wire EnvironmentStore Into Server Run Resolution\n\n**Files:**\n- Modify: `lib/crates/fabro-server/src/server.rs`\n- Modify: `lib/crates/fabro-server/src/serve.rs`\n- Modify: `lib/crates/fabro-server/src/run_manifest.rs`\n- Modify: `lib/crates/fabro-server/src/server/handler/runs.rs`\n- Modify: `lib/crates/fabro-server/src/manifest_validation.rs`\n- Modify: `lib/crates/fabro-server/src/test_support.rs`\n\n- [ ] Add `environment_store: Arc` to `AppState`, loaded from `active_config_path.parent().join(\"environments\")`.\n- [ ] Replace `manifest_environment_defaults` from `ServerRuntimeSettings` with `environment_store.catalog_layer()` when preparing manifests on the server.\n- [ ] Keep the dense run snapshot unchanged: `prepared.settings.run.environment` contains the resolved environment fields, and `prepared.settings.environments` contains the server catalog used for resolution.\n- [ ] Convert unknown environment ids into `400 Bad Request` during run creation/preflight/graph preparation.\n- [ ] Keep sandbox provider policy checks after environment resolution, so disabled providers still reject runs.\n- [ ] Apply `--preserve-sandbox` after selected environment resolution.\n- [ ] Remove server reliance on `[environments]` in `settings.toml`.\n- [ ] Update server test support so tests can inject environment files or use seeded defaults.\n- [ ] Add server tests for default environment run creation, custom server environment selection, unknown environment id, disabled provider policy, `--preserve-sandbox`, and rejected TOML environment field overrides.\n\nRun:\n\n```bash\ncargo nextest run -p fabro-server\n```\n\nExpected: server API and run-manifest tests pass.\n\n## Task 4: Add Environment CRUD API\n\n**Files:**\n- Modify: `docs/public/api-reference/fabro-api.yaml`\n- Modify: `lib/crates/fabro-api/build.rs`\n- Create: `lib/crates/fabro-server/src/server/handler/environments.rs`\n- Modify: `lib/crates/fabro-server/src/server/handler/mod.rs`\n- Add tests: `lib/crates/fabro-server/tests/it/api/environments.rs`\n- Update generated Rust and TypeScript API artifacts after spec changes.\n\n- [ ] Add OpenAPI tag `Environments`.\n- [ ] Add schemas for `Environment`, `CreateEnvironmentRequest`, `ReplaceEnvironmentRequest`, and `EnvironmentListResponse`.\n- [ ] Reuse existing environment schemas for provider/image/resources/network/lifecycle/volumes/env.\n- [ ] Add endpoints:\n - `GET /api/v1/environments`\n - `POST /api/v1/environments`\n - `GET /api/v1/environments/{id}`\n - `PUT /api/v1/environments/{id}`\n - `DELETE /api/v1/environments/{id}`\n- [ ] Return `ETag` on retrieve and replace.\n- [ ] Require `If-Match` on replace and delete.\n- [ ] Map store errors to API responses:\n - invalid id: `400`\n - duplicate create: `409`\n - stale revision: `409`\n - validation error: `422`\n - missing resource: `404`\n - protected default delete: `409`\n - persistence failure: `500`\n- [ ] Add route tests for empty-seeded list, create, retrieve with ETag, replace, stale replace, missing `If-Match`, delete, protected default delete, invalid provider, invalid CIDR, and missing Dockerfile path.\n- [ ] Regenerate `fabro-api` and TypeScript client artifacts.\n\nRun:\n\n```bash\ncargo build -p fabro-api\ncd lib/packages/fabro-api-client && bun run generate\ncargo nextest run -p fabro-server --test it -- api::environments\n```\n\nExpected: generated artifacts are updated and environment API tests pass.\n\n## Task 5: Adjust CLI And Manifest Behavior\n\n**Files:**\n- Modify: `lib/crates/fabro-cli/src/commands/run/overrides.rs`\n- Modify: `lib/crates/fabro-cli/src/commands/preflight.rs`\n- Modify: `lib/crates/fabro-cli/src/commands/graph.rs`\n- Modify: `lib/crates/fabro-cli/src/commands/validate.rs`\n- Modify: `lib/crates/fabro-cli/src/commands/repo/init.rs`\n- Modify: `lib/crates/fabro-manifest/src/lib.rs`\n- Modify CLI integration tests under `lib/crates/fabro-cli/tests/it/`\n\n- [ ] Reject `--docker-image` in run, create, preflight, graph, and validate commands with this message shape: `--docker-image is no longer supported; create or update a server environment and select it with --environment`.\n- [ ] Keep `--environment` as an id-only selector in manifest args.\n- [ ] Stop collecting Dockerfile path references from `[environments.]` in project/workflow config because those definitions are invalid.\n- [ ] Keep collecting Dockerfile references for any remaining CLI-created run environment override only when it comes from allowed argument paths; with `--docker-image` rejected, no normal user path should add one.\n- [ ] Update local preflight/graph/validate flows so they either call server preflight for environment resolution or print a clear message that server-owned environment resolution requires a running server.\n- [ ] Update `fabro repo init` to write only `[run.environment] id = \"local\"` and no `[environments.local]` block.\n- [ ] Update CLI tests for manifest args, repo init output, rejected `--docker-image`, and server-owned environment selection.\n\nRun:\n\n```bash\ncargo nextest run -p fabro-cli\n```\n\nExpected: CLI tests pass and no generated workflow config contains `[environments.*]`.\n\n## Task 6: Update Install, Docs, And Generated References\n\n**Files:**\n- Modify install persistence code in `lib/crates/fabro-cli/src/commands/install.rs` and server install handlers/tests.\n- Modify docs: `docs/public/execution/environments.mdx`, `docs/public/execution/run-configuration.mdx`, `docs/public/reference/user-configuration.mdx`, `docs/public/administration/server-configuration.mdx`, `docs/public/administration/sandboxing.mdx`, `docs/public/integrations/daytona.mdx`, and examples that currently define `[environments.]`.\n- Modify generated settings reference if applicable.\n\n- [ ] Update install flows to write server environment files instead of `[environments.default]` into `settings.toml`.\n- [ ] Keep install-written `[run.environment] id = \"default\"` when a default run environment selection is still needed.\n- [ ] Update tests that assert `settings.toml` contains `[environments.default]` to assert the sibling environment file exists and settings no longer contains `[environments]`.\n- [ ] Rewrite public docs so environment definitions are server-owned TOML files and run configs only select ids.\n- [ ] Add a compatibility note explaining that project/workflow `[environments]` definitions now fail and must be moved to the server.\n- [ ] Keep `Settings > Environments` UI documentation out of this pass.\n\nRun:\n\n```bash\ncargo nextest run -p fabro-server --test it -- api::install\ncargo nextest run -p fabro-cli --test it\n```\n\nExpected: install tests pass and docs no longer present project/workflow environment definitions as valid.\n\n## Task 7: Workspace Verification\n\n**Files:**\n- No new files unless test snapshots require reviewed updates.\n\n- [ ] Run Rust formatting check:\n\n```bash\ncargo +nightly-2026-04-14 fmt --check --all\n```\n\n- [ ] Run clippy:\n\n```bash\ncargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings\n```\n\n- [ ] Run workspace tests:\n\n```bash\ncargo nextest run --workspace\n```\n\n- [ ] Run TypeScript checks if API client changes affect the web package:\n\n```bash\ncd apps/fabro-web && bun run typecheck\ncd apps/fabro-web && bun test\n```\n\n- [ ] Inspect generated files and snapshots before accepting any snapshot changes.\n\n## Acceptance Criteria\n\n- Server startup creates or loads `environments/default.toml`, `local.toml`, `docker.toml`, and `daytona.toml`.\n- `GET /api/v1/environments` returns seeded environments with revisions.\n- API-created environments persist as TOML files and survive server restart.\n- Runs using `[run.environment] id = \"cloud\"` resolve from server files only.\n- Project/workflow/user `[environments.]` definitions no longer affect runs.\n- Existing runs keep their dense environment snapshot after environment files change.\n- `--environment` still works.\n- `--preserve-sandbox` still works.\n- `--docker-image` no longer works and produces the targeted replacement guidance.\n- Web UI changes are not included.\n", "command.output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", "internal.retry_count.preflight_compile": 0, - "outcome": "succeeded", + "outcome": "failed", "internal.run_id": "01KSPDE8E9ATTVY1VBCS3YRBHT", "internal.node_visit_count": 1, "thread.toolchain.current_node": "preflight_compile", "internal.fidelity": "compact", "internal.work_dir": "/home/daytona/workspace/fabro", + "thread.preflight_lint.current_node": "implement", + "internal.retry_count.implement": 0, "graph.rankdir": "LR", - "failure_class": "", + "failure_class": "deterministic", "internal.retry_count.start": 0, "thread.start.current_node": "toolchain", - "failure_signature": "", + "failure_signature": "implement|deterministic|api_deterministic|openai|invalid_request", "graph.model_stylesheet": "\n * { model: claude-opus-4-7; }\n " }, "node_outcomes": { + "implement": { + "status": "failed", + "failure": { + "message": "LLM error: Invalid request to openai: No tool call found for function call output with call_id call_DUxNRWlboQi3OOOvhg0NhD3h.", + "category": "deterministic", + "signature": "api_deterministic|openai|invalid_request" + }, + "usage": null + }, "preflight_compile": { "status": "succeeded", "context_updates": { @@ -774,12 +885,13 @@ } } }, - "next_node_id": "implement", + "next_node_id": "simplify_opus", "node_visits": { "start": 1, "toolchain": 1, "preflight_compile": 1, - "preflight_lint": 1 + "preflight_lint": 1, + "implement": 1 } }, "diff": {} @@ -893,11 +1005,296 @@ }, "state": "succeeded" }, + "implement@1": { + "first_event_seq": 51, + "prompt": null, + "response": null, + "completion": null, + "provider_used": { + "mode": "agent", + "provider": "openai", + "model": "gpt-5.5", + "reasoning_effort": "xhigh" + }, + "diff": null, + "script_invocation": null, + "script_timing": null, + "parallel_results": null, + "output": null, + "started_at": "2026-05-28T04:33:22.202480Z", + "handler": "agent", + "usage": { + "input_tokens": 2380540, + "output_tokens": 38445, + "total_tokens": 25456584, + "reasoning_tokens": 15519, + "cache_read_tokens": 23022080, + "cache_write_tokens": 0 + }, + "model": { + "provider": "openai", + "model_id": "gpt-5.5" + }, + "todos": { + "kind": "openai_plan", + "list_id": "openai_plan:5ff7e769-1627-4ae3-8719-4881ba9c7d3e", + "items": [ + { + "id": "d611d499f5118e5f", + "status": "completed", + "order": 0, + "subject": "Inspect existing environment/settings/run/API/store patterns and identify touchpoints" + }, + { + "id": "3d32418cee56c51b", + "status": "completed", + "order": 1, + "subject": "Implement fabro-environment crate with store, persistence, validation, and tests" + }, + { + "id": "692618ec77c94fa9", + "status": "completed", + "order": 2, + "subject": "Make config environments migration-only and update config tests" + }, + { + "id": "47c843470b579827", + "status": "in_progress", + "order": 3, + "subject": "Wire EnvironmentStore into server run resolution and tests" + }, + { + "id": "e7569666b3b8d963", + "status": "pending", + "order": 4, + "subject": "Add Environment CRUD OpenAPI/server API and regenerate clients" + }, + { + "id": "9556d8afc4b16df9", + "status": "pending", + "order": 5, + "subject": "Adjust CLI/manifest behavior and tests" + }, + { + "id": "3864fb367e039b2d", + "status": "pending", + "order": 6, + "subject": "Update install flows, docs, generated references" + }, + { + "id": "bfa49e76ebdf4ca8", + "status": "pending", + "order": 7, + "subject": "Run targeted and workspace validation, fix regressions" + } + ] + }, + "subagents": [ + { + "agent_id": "5af7e16a", + "depth": 1, + "task": "Investigate existing automation store pattern, API routes, ETag/If-Match handling, and server test patterns. Report relevant files/functions and suggested implementation approach for Environment CRUD mirroring AutomationStore. Do not modify files.", + "status": { + "kind": "running" + } + }, + { + "agent_id": "214b24ab", + "depth": 1, + "task": "Investigate existing config environment types, defaults, builders, migrations, and tests. Report how EnvironmentLayer/EnvironmentSettings are defined and resolved, and where to change behavior to make [environments] migration-only. Do not modify files.", + "status": { + "kind": "running" + } + }, + { + "agent_id": "7f21ea3f", + "depth": 1, + "task": "Investigate CLI run/preflight/graph/validate/repo init handling of --environment and --docker-image, plus manifest Dockerfile collection. Report relevant files/functions and test locations. Do not modify files.", + "status": { + "kind": "running" + } + } + ], + "permission_level": "full", + "agent_tools": [ + { + "name": "apply_patch", + "description": "Use the `apply_patch` tool to edit files. This is a FREEFORM tool, so do not wrap the patch in JSON.", + "source": { + "kind": "native" + }, + "category": "write", + "invoked": true + }, + { + "name": "close_agent", + "description": "Close a running subagent that is no longer needed.", + "source": { + "kind": "native" + }, + "category": "subagent", + "invoked": false + }, + { + "name": "glob", + "description": "Find files by file names using a glob pattern. Use path to choose the search root. Prefer this over shell find or ls when locating repository files.", + "source": { + "kind": "native" + }, + "category": "read", + "invoked": true + }, + { + "name": "grep", + "description": "Search file contents with a regex pattern. Use path to choose the search root, glob_filter to limit matching files, case_insensitive for case folding, and max_results to cap output.", + "source": { + "kind": "native" + }, + "category": "read", + "invoked": true + }, + { + "name": "read_file", + "description": "Read files before editing them. Returns line-numbered text and supports offset/limit for large files. Use this instead of shell cat, head, tail, or sed when inspecting repository files.", + "source": { + "kind": "native" + }, + "category": "read", + "invoked": true + }, + { + "name": "request_user_input", + "description": "Ask the human one or more questions and wait for their answers before continuing this stage.", + "source": { + "kind": "native" + }, + "category": "other", + "invoked": false + }, + { + "name": "send_input", + "description": "Send a follow-up message to a running subagent when new information or corrected instructions are needed.", + "source": { + "kind": "native" + }, + "category": "subagent", + "invoked": false + }, + { + "name": "shell", + "description": "Execute shell commands for terminal operations, package managers, tests and builds. Use dedicated tools for file reads, file edits, filename searches, and content searches. Provide timeout_ms for long-running commands.", + "source": { + "kind": "native" + }, + "category": "shell", + "invoked": true + }, + { + "name": "spawn_agent", + "description": "Spawn a subagent for independent work or context isolation. Use it for tasks that can proceed separately, and avoid duplicating the same work in the parent session.", + "source": { + "kind": "native" + }, + "category": "subagent", + "invoked": true + }, + { + "name": "update_plan", + "description": "Update the multi-step plan for the current task. Submit the entire plan; existing steps are reconciled by exact step text.", + "source": { + "kind": "native" + }, + "category": "other", + "invoked": true + }, + { + "name": "wait", + "description": "Wait for a subagent to complete, then use the result to synthesize the outcome for the user.", + "source": { + "kind": "native" + }, + "category": "subagent", + "invoked": false + }, + { + "name": "web_fetch", + "description": "Fetch content from a URL that starts with http:// or https://. Pass a prompt to extract specific information or summarize the page; omit prompt to return the page content.", + "source": { + "kind": "native" + }, + "category": "other", + "invoked": false + }, + { + "name": "web_search", + "description": "Search the web using Brave Search when current external information is needed. Returns result titles, URLs, and descriptions; use web_fetch for a specific URL.", + "source": { + "kind": "native" + }, + "category": "other", + "invoked": false + }, + { + "name": "write_file", + "description": "Create new files, or overwrite an existing file only when replacement is explicitly intended. Prefer edit_file for targeted changes to existing files because write_file overwrites the full file content.", + "source": { + "kind": "native" + }, + "category": "write", + "invoked": false + } + ], + "context_window": { + "provider": "openai", + "model": "gpt-5.5", + "context_window_tokens": 272000, + "input_tokens": 217541, + "usage_percent": 79.97830882352942, + "count_method": "response_usage_scaled_breakdown", + "staleness": "live", + "generated_at": "2026-05-28T05:00:04.419324Z", + "event_seq": 717, + "breakdown": [ + { + "category": "system_prompt", + "tokens": 882, + "usage_percent": 0.32426470588235295 + }, + { + "category": "tools", + "tokens": 1257, + "usage_percent": 0.4621323529411765 + }, + { + "category": "memory", + "tokens": 2993, + "usage_percent": 1.1003676470588235 + }, + { + "category": "conversation", + "tokens": 212403, + "usage_percent": 78.08933823529412 + }, + { + "category": "other", + "tokens": 6, + "usage_percent": 0.0022058823529411764 + } + ], + "warnings": [] + }, + "state": "running" + }, "preflight_lint@1": { "first_event_seq": 41, "prompt": null, "response": null, - "completion": null, + "completion": { + "outcome": "succeeded", + "notes": "Script completed: cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", + "failure_reason": null, + "timestamp": "2026-05-28T04:33:18.677731Z" + }, "provider_used": null, "diff": null, "script_invocation": { @@ -905,11 +1302,27 @@ "command": "exec 2>&1\ncargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", "language": "shell" }, - "script_timing": null, + "script_timing": { + "output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", + "exit_code": 0, + "duration_ms": 143203, + "termination": "exited", + "output_bytes": 0, + "live_streaming": false + }, "parallel_results": null, "output": null, + "output_bytes": 0, + "live_streaming": false, + "termination": "exited", "started_at": "2026-05-28T04:30:55.467797Z", "handler": "command", + "timing": { + "wall_time_ms": 143209, + "inference_time_ms": 0, + "tool_time_ms": 143203, + "active_time_ms": 143203 + }, "usage": { "input_tokens": 0, "output_tokens": 0, @@ -918,7 +1331,7 @@ "cache_read_tokens": 0, "cache_write_tokens": 0 }, - "state": "running" + "state": "succeeded" }, "preflight_compile@1": { "first_event_seq": 31, diff --git a/stages/004-preflight_lint@1/output.log b/stages/004-preflight_lint@1/output.log new file mode 100644 index 000000000..d87ba9545 --- /dev/null +++ b/stages/004-preflight_lint@1/output.log @@ -0,0 +1 @@ +blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126 \ No newline at end of file diff --git a/stages/004-preflight_lint@1/script_timing.json b/stages/004-preflight_lint@1/script_timing.json new file mode 100644 index 000000000..e247cccd0 --- /dev/null +++ b/stages/004-preflight_lint@1/script_timing.json @@ -0,0 +1,8 @@ +{ + "output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126", + "exit_code": 0, + "duration_ms": 143203, + "termination": "exited", + "output_bytes": 0, + "live_streaming": false +} \ No newline at end of file diff --git a/stages/004-preflight_lint@1/status.json b/stages/004-preflight_lint@1/status.json new file mode 100644 index 000000000..e4b19c7ce --- /dev/null +++ b/stages/004-preflight_lint@1/status.json @@ -0,0 +1,6 @@ +{ + "outcome": "succeeded", + "notes": "Script completed: cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", + "failure_reason": null, + "timestamp": "2026-05-28T04:33:18.677731Z" +} \ No newline at end of file diff --git a/stages/005-implement@1/prompt.md b/stages/005-implement@1/prompt.md new file mode 100644 index 000000000..ce56538c3 --- /dev/null +++ b/stages/005-implement@1/prompt.md @@ -0,0 +1,281 @@ +Goal: # Server-Owned Environments Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Move environment definitions from layered run settings into server-owned TOML resources with CRUD API management, matching the Automation store pattern. + +**Architecture:** Add a concrete `EnvironmentStore` that loads one environment TOML file per id from a sibling `environments/` directory next to the active server settings file. Runs continue to select an environment by id through `[run.environment]` or `--environment`, but server-side run creation resolves the id from `EnvironmentStore`; project/workflow/user config can no longer define environment catalogs or environment field overrides. The web UI is intentionally deferred. + +**Tech Stack:** Rust, Axum, serde/TOML, `toml_edit`, Tokio file I/O, OpenAPI/progenitor, generated TypeScript API client, cargo-nextest. + +--- + +## File Structure + +- Create `lib/crates/fabro-environment/`: environment ids, revisions, API/domain DTOs, TOML persistence, canonicalization, validation, and `EnvironmentStore`. +- Modify workspace manifests: root `Cargo.toml`, `lib/crates/fabro-server/Cargo.toml`, `lib/crates/fabro-api/build.rs`, and generated API/client package files. +- Modify `lib/crates/fabro-server/src/server.rs`, `lib/crates/fabro-server/src/server/handler/mod.rs`, and a new `lib/crates/fabro-server/src/server/handler/environments.rs` to wire the store and API. +- Modify `lib/crates/fabro-config/src/builders.rs`, `lib/crates/fabro-config/src/load.rs`, `lib/crates/fabro-config/src/migrations.rs`, and config tests to treat `[environments]` as migration-only, not runtime configuration. +- Modify `lib/crates/fabro-manifest/src/lib.rs`, `lib/crates/fabro-server/src/run_manifest.rs`, and CLI run/preflight/graph/validate paths so environment ids are resolved only by the server. +- Modify install/repo-init/docs/OpenAPI artifacts so new examples use server environment files and run configs only select ids. + +## Decisions + +- Environment definitions are server-owned operator policy. Project and workflow files may request an id but cannot define or override environment fields. +- `default`, `local`, `docker`, and `daytona` are seeded if missing. Existing files are never overwritten. +- `default` is protected from deletion. Other seeded files can be edited or deleted. +- Environment ids use `[a-z0-9][a-z0-9-]{0,62}`. +- Environment revisions are SHA-256 hashes of the persisted TOML bytes, returned in JSON as `revision` and in `ETag`. +- `PUT` and `DELETE` require `If-Match`, following `AutomationStore`. +- `image.dockerfile = { path = "Dockerfile" }` is accepted in persisted files and API input, resolved relative to the environment file or request context, and converted to inline content for runtime use. API writes canonical inline TOML. +- `--preserve-sandbox` remains a CLI/server argument override. TOML `[run.environment.lifecycle]` is rejected. +- `--docker-image` is rejected with a targeted message directing operators to create or update a server environment. +- Existing dense `WorkflowSettings.environments` stays in the API for compatibility and is populated from the server environment catalog during run resolution. + +## Task 1: Add `fabro-environment` Store Crate + +**Files:** +- Create: `lib/crates/fabro-environment/Cargo.toml` +- Create: `lib/crates/fabro-environment/src/lib.rs` +- Create: `lib/crates/fabro-environment/src/id.rs` +- Create: `lib/crates/fabro-environment/src/model.rs` +- Create: `lib/crates/fabro-environment/src/store.rs` +- Create: `lib/crates/fabro-environment/src/error.rs` +- Modify: root `Cargo.toml` + +- [ ] Create a workspace crate named `fabro-environment`, modeled after `fabro-automation`. +- [ ] Define `EnvironmentId`, `EnvironmentRevision`, and parse/validation errors. +- [ ] Define public DTOs: + - `Environment`: `id`, `revision`, `provider`, `image`, `resources`, `network`, `lifecycle`, `labels`, `volumes`, `env`. + - `EnvironmentDraft`: `id` plus environment fields. + - `EnvironmentReplace`: environment fields without id. +- [ ] Use the existing environment field types from `fabro_types::settings::run` for dense API fields. +- [ ] Use existing sparse `fabro_config::EnvironmentLayer` only for TOML input/output and conversion; do not create a second environment field vocabulary. +- [ ] Add conversion helpers that resolve an `EnvironmentLayer` into dense `EnvironmentSettings` using the same provider/network/image validation rules as `fabro-config`. +- [ ] Implement canonical TOML serialization for persisted files. Omit `id` and `revision`; the filename is the id and the file bytes determine revision. +- [ ] Implement `EnvironmentStore` with `load_or_seed(dir)`, `list`, `get`, `create`, `replace`, `delete`, and `catalog_layer`. +- [ ] Seed missing `default`, `local`, `docker`, and `daytona` files from the current built-in defaults. Do not overwrite existing files. +- [ ] Protect `default` from deletion with a typed store error. +- [ ] Resolve Dockerfile path references relative to the environment file directory during load and relative to the active settings directory during API create/replace. Store runtime values with inline Dockerfile content. +- [ ] Add unit tests for loading an absent directory, seeding built-ins, sorted listing, invalid ids, invalid provider, invalid network mode, missing Dockerfile path, create conflict, replace stale revision, default delete rejection, delete success, and canonical revision changes. + +Run: + +```bash +cargo nextest run -p fabro-environment +``` + +Expected: all `fabro-environment` tests pass. + +## Task 2: Make Config Environments Migration-Only + +**Files:** +- Modify: `lib/crates/fabro-config/src/parse.rs` +- Modify: `lib/crates/fabro-config/src/builders.rs` +- Modify: `lib/crates/fabro-config/src/load.rs` +- Modify: `lib/crates/fabro-config/src/migrations.rs` +- Create: `lib/crates/fabro-config/migrations/2026052801_settings_environments_to_server_files.rs` +- Modify: `lib/crates/fabro-config/src/defaults.toml` +- Modify: `lib/crates/fabro-config/src/tests/resolve_run.rs` +- Modify: `lib/crates/fabro-config/src/tests/resolve_root.rs` + +- [ ] Keep `SettingsLayer.environments` in this pass so old files can parse and migrate, but remove environment catalog entries from `defaults.toml`. +- [ ] Add source-aware validation that rejects `SettingsLayer.environments` for project, workflow, and direct run config layers with this message shape: `[environments.] is now server-managed; move this definition to the server environments directory`. +- [ ] Add validation that rejects TOML-provided `run.environment.image`, `resources`, `network`, `lifecycle`, `labels`, `volumes`, and `env`. Keep `run.environment.id`. +- [ ] Ensure CLI/server argument layers can still set `run.environment.lifecycle.preserve` for `--preserve-sandbox`; the rejection applies only to parsed TOML sources. +- [ ] Add a settings-file migration that extracts top-level `[environments.]` entries from the active `settings.toml` into sibling `environments/.toml` files. +- [ ] Migration must write a backup before editing `settings.toml`, preserve `[run.environment] id`, remove the top-level `[environments]` table, and fail without changing files if any target environment file already exists. +- [ ] Chain the existing legacy `[run.sandbox]` migration before the new extraction migration so legacy sandbox settings become a server `default` environment file. +- [ ] Update run settings tests to assert that `RunSettingsBuilder` no longer resolves a selected environment without an injected server catalog. +- [ ] Add tests proving project/workflow `[environments]` definitions produce targeted errors rather than silent ignores. + +Run: + +```bash +cargo nextest run -p fabro-config +``` + +Expected: config tests pass, including migration coverage. + +## Task 3: Wire EnvironmentStore Into Server Run Resolution + +**Files:** +- Modify: `lib/crates/fabro-server/src/server.rs` +- Modify: `lib/crates/fabro-server/src/serve.rs` +- Modify: `lib/crates/fabro-server/src/run_manifest.rs` +- Modify: `lib/crates/fabro-server/src/server/handler/runs.rs` +- Modify: `lib/crates/fabro-server/src/manifest_validation.rs` +- Modify: `lib/crates/fabro-server/src/test_support.rs` + +- [ ] Add `environment_store: Arc` to `AppState`, loaded from `active_config_path.parent().join("environments")`. +- [ ] Replace `manifest_environment_defaults` from `ServerRuntimeSettings` with `environment_store.catalog_layer()` when preparing manifests on the server. +- [ ] Keep the dense run snapshot unchanged: `prepared.settings.run.environment` contains the resolved environment fields, and `prepared.settings.environments` contains the server catalog used for resolution. +- [ ] Convert unknown environment ids into `400 Bad Request` during run creation/preflight/graph preparation. +- [ ] Keep sandbox provider policy checks after environment resolution, so disabled providers still reject runs. +- [ ] Apply `--preserve-sandbox` after selected environment resolution. +- [ ] Remove server reliance on `[environments]` in `settings.toml`. +- [ ] Update server test support so tests can inject environment files or use seeded defaults. +- [ ] Add server tests for default environment run creation, custom server environment selection, unknown environment id, disabled provider policy, `--preserve-sandbox`, and rejected TOML environment field overrides. + +Run: + +```bash +cargo nextest run -p fabro-server +``` + +Expected: server API and run-manifest tests pass. + +## Task 4: Add Environment CRUD API + +**Files:** +- Modify: `docs/public/api-reference/fabro-api.yaml` +- Modify: `lib/crates/fabro-api/build.rs` +- Create: `lib/crates/fabro-server/src/server/handler/environments.rs` +- Modify: `lib/crates/fabro-server/src/server/handler/mod.rs` +- Add tests: `lib/crates/fabro-server/tests/it/api/environments.rs` +- Update generated Rust and TypeScript API artifacts after spec changes. + +- [ ] Add OpenAPI tag `Environments`. +- [ ] Add schemas for `Environment`, `CreateEnvironmentRequest`, `ReplaceEnvironmentRequest`, and `EnvironmentListResponse`. +- [ ] Reuse existing environment schemas for provider/image/resources/network/lifecycle/volumes/env. +- [ ] Add endpoints: + - `GET /api/v1/environments` + - `POST /api/v1/environments` + - `GET /api/v1/environments/{id}` + - `PUT /api/v1/environments/{id}` + - `DELETE /api/v1/environments/{id}` +- [ ] Return `ETag` on retrieve and replace. +- [ ] Require `If-Match` on replace and delete. +- [ ] Map store errors to API responses: + - invalid id: `400` + - duplicate create: `409` + - stale revision: `409` + - validation error: `422` + - missing resource: `404` + - protected default delete: `409` + - persistence failure: `500` +- [ ] Add route tests for empty-seeded list, create, retrieve with ETag, replace, stale replace, missing `If-Match`, delete, protected default delete, invalid provider, invalid CIDR, and missing Dockerfile path. +- [ ] Regenerate `fabro-api` and TypeScript client artifacts. + +Run: + +```bash +cargo build -p fabro-api +cd lib/packages/fabro-api-client && bun run generate +cargo nextest run -p fabro-server --test it -- api::environments +``` + +Expected: generated artifacts are updated and environment API tests pass. + +## Task 5: Adjust CLI And Manifest Behavior + +**Files:** +- Modify: `lib/crates/fabro-cli/src/commands/run/overrides.rs` +- Modify: `lib/crates/fabro-cli/src/commands/preflight.rs` +- Modify: `lib/crates/fabro-cli/src/commands/graph.rs` +- Modify: `lib/crates/fabro-cli/src/commands/validate.rs` +- Modify: `lib/crates/fabro-cli/src/commands/repo/init.rs` +- Modify: `lib/crates/fabro-manifest/src/lib.rs` +- Modify CLI integration tests under `lib/crates/fabro-cli/tests/it/` + +- [ ] Reject `--docker-image` in run, create, preflight, graph, and validate commands with this message shape: `--docker-image is no longer supported; create or update a server environment and select it with --environment`. +- [ ] Keep `--environment` as an id-only selector in manifest args. +- [ ] Stop collecting Dockerfile path references from `[environments.]` in project/workflow config because those definitions are invalid. +- [ ] Keep collecting Dockerfile references for any remaining CLI-created run environment override only when it comes from allowed argument paths; with `--docker-image` rejected, no normal user path should add one. +- [ ] Update local preflight/graph/validate flows so they either call server preflight for environment resolution or print a clear message that server-owned environment resolution requires a running server. +- [ ] Update `fabro repo init` to write only `[run.environment] id = "local"` and no `[environments.local]` block. +- [ ] Update CLI tests for manifest args, repo init output, rejected `--docker-image`, and server-owned environment selection. + +Run: + +```bash +cargo nextest run -p fabro-cli +``` + +Expected: CLI tests pass and no generated workflow config contains `[environments.*]`. + +## Task 6: Update Install, Docs, And Generated References + +**Files:** +- Modify install persistence code in `lib/crates/fabro-cli/src/commands/install.rs` and server install handlers/tests. +- Modify docs: `docs/public/execution/environments.mdx`, `docs/public/execution/run-configuration.mdx`, `docs/public/reference/user-configuration.mdx`, `docs/public/administration/server-configuration.mdx`, `docs/public/administration/sandboxing.mdx`, `docs/public/integrations/daytona.mdx`, and examples that currently define `[environments.]`. +- Modify generated settings reference if applicable. + +- [ ] Update install flows to write server environment files instead of `[environments.default]` into `settings.toml`. +- [ ] Keep install-written `[run.environment] id = "default"` when a default run environment selection is still needed. +- [ ] Update tests that assert `settings.toml` contains `[environments.default]` to assert the sibling environment file exists and settings no longer contains `[environments]`. +- [ ] Rewrite public docs so environment definitions are server-owned TOML files and run configs only select ids. +- [ ] Add a compatibility note explaining that project/workflow `[environments]` definitions now fail and must be moved to the server. +- [ ] Keep `Settings > Environments` UI documentation out of this pass. + +Run: + +```bash +cargo nextest run -p fabro-server --test it -- api::install +cargo nextest run -p fabro-cli --test it +``` + +Expected: install tests pass and docs no longer present project/workflow environment definitions as valid. + +## Task 7: Workspace Verification + +**Files:** +- No new files unless test snapshots require reviewed updates. + +- [ ] Run Rust formatting check: + +```bash +cargo +nightly-2026-04-14 fmt --check --all +``` + +- [ ] Run clippy: + +```bash +cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings +``` + +- [ ] Run workspace tests: + +```bash +cargo nextest run --workspace +``` + +- [ ] Run TypeScript checks if API client changes affect the web package: + +```bash +cd apps/fabro-web && bun run typecheck +cd apps/fabro-web && bun test +``` + +- [ ] Inspect generated files and snapshots before accepting any snapshot changes. + +## Acceptance Criteria + +- Server startup creates or loads `environments/default.toml`, `local.toml`, `docker.toml`, and `daytona.toml`. +- `GET /api/v1/environments` returns seeded environments with revisions. +- API-created environments persist as TOML files and survive server restart. +- Runs using `[run.environment] id = "cloud"` resolve from server files only. +- Project/workflow/user `[environments.]` definitions no longer affect runs. +- Existing runs keep their dense environment snapshot after environment files change. +- `--environment` still works. +- `--preserve-sandbox` still works. +- `--docker-image` no longer works and produces the targeted replacement guidance. +- Web UI changes are not included. + + +## Completed stages +- **toolchain**: succeeded + - 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` + - Output: + ``` + cargo 1.95.0 (f2d3ce0bd 2026-03-21) + ``` +- **preflight_compile**: succeeded + - Script: `cargo check -q --workspace 2>&1` + - Output: (empty) +- **preflight_lint**: succeeded + - Script: `cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1` + - Output: (empty) + + +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. \ No newline at end of file diff --git a/stages/005-implement@1/provider_used.json b/stages/005-implement@1/provider_used.json new file mode 100644 index 000000000..c57772db6 --- /dev/null +++ b/stages/005-implement@1/provider_used.json @@ -0,0 +1,6 @@ +{ + "mode": "agent", + "provider": "openai", + "model": "gpt-5.5", + "reasoning_effort": "xhigh" +} \ No newline at end of file