mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-08 03:10:26 +00:00
parent
68fbc85238
commit
a8b27b1a0d
7 changed files with 1344 additions and 21 deletions
347
run.json
347
run.json
File diff suppressed because one or more lines are too long
1
stages/010-verify@2/output.log
Normal file
1
stages/010-verify@2/output.log
Normal file
|
|
@ -0,0 +1 @@
|
|||
blob://sha256/a3643dcd59f4204d4c76df7b263aade5ef08b0043ecb2047d1bfb2694cdce711
|
||||
8
stages/010-verify@2/script_timing.json
Normal file
8
stages/010-verify@2/script_timing.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"output": "blob://sha256/a3643dcd59f4204d4c76df7b263aade5ef08b0043ecb2047d1bfb2694cdce711",
|
||||
"exit_code": 100,
|
||||
"duration_ms": 88617,
|
||||
"termination": "exited",
|
||||
"output_bytes": 3110,
|
||||
"live_streaming": true
|
||||
}
|
||||
6
stages/010-verify@2/status.json
Normal file
6
stages/010-verify@2/status.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"outcome": "failed",
|
||||
"notes": null,
|
||||
"failure_reason": "Script failed with exit code: 100\n\n## output\nFrom https://github.com/fabro-sh/fabro\n * branch main -> FETCH_HEAD\nAlready up to date.\n Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s\n Running `target/debug/fabro-dev docs refresh`\nGenerated docs/public/reference/cli.mdx.\nGenerated docs/public/reference/user-configuration.mdx.\n/root/.daytona/sessions/fabro-cf79812a7a831861/73017b28-794f-4a50-b1b8-6565e48699eb/cmd.sh: line 4: rg: command not found\n Compiling fabro-server v0.242.0-nightly.1 (/home/daytona/repos/fabro-sh/fabro/lib/crates/fabro-server)\n Compiling fabro-cli v0.242.0-nightly.1 (/home/daytona/repos/fabro-sh/fabro/lib/crates/fabro-cli)\n Checking fabro-mcp-server v0.242.0-nightly.1 (/home/daytona/repos/fabro-sh/fabro/lib/crates/fabro-mcp-server)\n Finished `dev` profile [unoptimized + debuginfo] target(s) in 15.31s\n Compiling fabro-server v0.242.0-nightly.1 (/home/daytona/repos/fabro-sh/fabro/lib/crates/fabro-server)\n Compiling fabro-cli v0.242.0-nightly.1 (/home/daytona/repos/fabro-sh/fabro/lib/crates/fabro-cli)\n Compiling fabro-mcp-server v0.242.0-nightly.1 (/home/daytona/repos/fabro-sh/fabro/lib/crates/fabro-mcp-server)\n Finished `test` profile [unoptimized + debuginfo] target(s) in 29.70s\n────────────\n Nextest run ID 2f85e60e-eeb2-4c10-b800-8094feaccd3a with nextest profile: ci\n Starting 6259 tests across 125 binaries (181 tests skipped)\n FAIL [ 0.219s] (1079/6259) fabro-cli::it cmd::system_df::system_df_summarizes_runs_and_logs\n stdout ───\n\n running 1 test\n test cmd::system_df::system_df_summarizes_runs_and_logs ... FAILED\n\n failures:\n\n failures:\n cmd::system_df::system_df_summarizes_runs_and_logs\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 507 filtered out; finished in 0.21s\n\n stderr ───\n\n thread 'cmd::system_df::system_df_summarizes_runs_and_logs' (141748) panicked at lib/crates/fabro-test/src/http_assert.rs:136:5:\n POST /api/v1/runs/01KSB95FMFKTPWG6ZZQZ68SQXF/events (stage.completed)\n expected status: 200 OK\n actual status: 500 Internal Server Error\n url: http://fabro/api/v1/runs/01KSB95FMFKTPWG6ZZQZ68SQXF/events\n content-type: application/json\n body:\n {\n \"errors\": [\n {\n \"status\": \"500\",\n \"title\": \"Internal Server Error\",\n \"detail\": \"projection cache sequence gap for run 01KSB95FMFKTPWG6ZZQZ68SQXF: last_seq=9, event_seq=11\",\n \"request_id\": \"8443cfdb-3f63-401e-8adc-082163a44240\"\n }\n ],\n \"request_id\": \"8443cfdb-3f63-401e-8adc-082163a44240\"\n }\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n\n Cancelling due to test failure: 7 tests still running\n────────────\n Summary [ 9.528s] 1086/6259 tests run: 1085 passed, 1 failed, 181 skipped\n FAIL [ 0.219s] (1079/6259) fabro-cli::it cmd::system_df::system_df_summarizes_runs_and_logs\nwarning: 5173/6259 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)\nerror: test run failed\n",
|
||||
"timestamp": "2026-05-23T20:42:01.311518Z"
|
||||
}
|
||||
981
stages/011-fixup@2/prompt.md
Normal file
981
stages/011-fixup@2/prompt.md
Normal file
|
|
@ -0,0 +1,981 @@
|
|||
Goal: # Legacy Sandbox Config Auto-Migration 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:** Automatically rewrite confidently migratable legacy `[run.sandbox]` config files to the named-environments syntax during startup.
|
||||
|
||||
**Architecture:** Keep legacy behavior isolated in a removable `fabro-config` module. The normal settings schema stays strict; only file-based loads get a temporary parse-failure recovery path that rewrites the file, writes a backup, warns, and then resumes normal parsing.
|
||||
|
||||
**Tech Stack:** Rust, `toml_edit`, existing `fabro-config` settings builders, `tracing`, `tempfile` tests, public docs under `docs/public`.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Create `lib/crates/fabro-config/src/legacy_sandbox_migration.rs`
|
||||
- Owns detection, TOML rewriting, backup naming/writing, unsupported-key diagnostics, and tests for legacy mappings.
|
||||
- Modify `lib/crates/fabro-config/src/lib.rs`
|
||||
- Register the module privately.
|
||||
- Modify `lib/crates/fabro-config/Cargo.toml`
|
||||
- Add the existing workspace `toml_edit` dependency; current main does not depend on it from `fabro-config`.
|
||||
- Modify `lib/crates/fabro-config/src/load.rs`
|
||||
- Add a small parse-failure hook that delegates to the migration module, then returns to normal parsing.
|
||||
- Modify docs:
|
||||
- `docs/public/execution/environments.mdx`
|
||||
- Create `docs/public/changelog/2026-05-23.mdx` because current main's latest public changelog is `2026-05-22.mdx`.
|
||||
|
||||
## Migration Contract
|
||||
|
||||
Only migrate when all of these are true:
|
||||
|
||||
- The file is valid TOML as a document.
|
||||
- `[run.sandbox]` exists.
|
||||
- `[run.environment]` does not exist.
|
||||
- `[environments.default]` does not exist.
|
||||
- Every legacy sandbox key is in the supported mapping below.
|
||||
- The migrated content parses successfully as `SettingsLayer`.
|
||||
|
||||
This is intentionally a file-load migration only. Current in-memory parsing behavior, including `legacy_run_sandbox_is_rejected` in `lib/crates/fabro-config/src/tests/resolve_run.rs`, should remain strict and unchanged.
|
||||
|
||||
Supported mappings:
|
||||
|
||||
| Legacy key | New key |
|
||||
|---|---|
|
||||
| `run.sandbox.provider` | `run.environment.id = "default"` and `environments.default.provider` |
|
||||
| `run.sandbox.preserve` | `environments.default.lifecycle.preserve` |
|
||||
| `run.sandbox.env` | `environments.default.env` |
|
||||
| `run.sandbox.daytona.skip_clone = true` | `run.clone.enabled = false` |
|
||||
| `run.sandbox.docker.skip_clone = true` | `run.clone.enabled = false` |
|
||||
| `run.sandbox.daytona.auto_stop_interval = N` | `environments.default.lifecycle.auto_stop = "{N}m"` |
|
||||
| `run.sandbox.daytona.labels` | `environments.default.labels` |
|
||||
| `run.sandbox.daytona.snapshot.name` | `environments.default.image.ref` |
|
||||
| `run.sandbox.daytona.snapshot.cpu` | `environments.default.resources.cpu` |
|
||||
| `run.sandbox.daytona.snapshot.memory` | `environments.default.resources.memory` |
|
||||
| `run.sandbox.daytona.snapshot.disk` | `environments.default.resources.disk` |
|
||||
| `run.sandbox.daytona.snapshot.dockerfile` | `environments.default.image.dockerfile` |
|
||||
| `run.sandbox.daytona.volumes[].volume_id` | `environments.default.volumes[].id` |
|
||||
| `run.sandbox.daytona.volumes[].mount_path` | `environments.default.volumes[].mount_path` |
|
||||
| `run.sandbox.daytona.volumes[].subpath` | `environments.default.volumes[].subpath` |
|
||||
| `run.sandbox.docker.image` | `environments.default.image.ref` |
|
||||
| `run.sandbox.docker.memory_limit` | `environments.default.resources.memory` |
|
||||
| `run.sandbox.docker.cpu_quota` | `environments.default.resources.cpu` when divisible by `100000` |
|
||||
|
||||
Unsupported or ambiguous cases fail with a message shaped like:
|
||||
|
||||
```text
|
||||
Legacy [run.sandbox] settings in <path> could not be auto-migrated.
|
||||
|
||||
Unsupported keys:
|
||||
- run.sandbox.daytona.foo
|
||||
- run.sandbox.docker.cpu_quota
|
||||
|
||||
Rename legacy sandbox configuration to [run.environment] and [environments.<slug>].
|
||||
See docs/public/execution/environments.mdx.
|
||||
```
|
||||
|
||||
Successful migration writes a backup next to the original file:
|
||||
|
||||
```text
|
||||
settings.toml.legacy-sandbox-migration.bak
|
||||
settings.toml.legacy-sandbox-migration.1.bak
|
||||
settings.toml.legacy-sandbox-migration.2.bak
|
||||
```
|
||||
|
||||
Successful migration emits:
|
||||
|
||||
```text
|
||||
Migrated legacy [run.sandbox] settings in <path> to [run.environment] and [environments.default]. Backup written to <backup>. This temporary compatibility migration will be removed before v1.0.
|
||||
```
|
||||
|
||||
## Task 1: Add the Migration Module Skeleton
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/crates/fabro-config/src/legacy_sandbox_migration.rs`
|
||||
- Modify: `lib/crates/fabro-config/src/lib.rs`
|
||||
- Modify: `lib/crates/fabro-config/Cargo.toml`
|
||||
|
||||
- [ ] **Step 1: Add the `toml_edit` dependency**
|
||||
|
||||
Add this to `[dependencies]` in `lib/crates/fabro-config/Cargo.toml`:
|
||||
|
||||
```toml
|
||||
toml_edit.workspace = true
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Register the module privately**
|
||||
|
||||
Add this beside the other private modules in `lib/crates/fabro-config/src/lib.rs`:
|
||||
|
||||
```rust
|
||||
mod legacy_sandbox_migration;
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Create the module API**
|
||||
|
||||
Create `lib/crates/fabro-config/src/legacy_sandbox_migration.rs` with this starting shape:
|
||||
|
||||
```rust
|
||||
#![expect(
|
||||
clippy::disallowed_methods,
|
||||
reason = "temporary startup config migration uses synchronous file I/O before config is loaded"
|
||||
)]
|
||||
|
||||
use std::fmt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use toml_edit::{DocumentMut, Item, Table, Value};
|
||||
|
||||
use crate::{Error, Result, SettingsLayer};
|
||||
|
||||
pub(crate) const REMOVAL_NOTE: &str =
|
||||
"This temporary compatibility migration will be removed before v1.0.";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct LegacySandboxMigrationReport {
|
||||
pub(crate) contents: String,
|
||||
pub(crate) backup_path: PathBuf,
|
||||
pub(crate) warning: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct MigrationFailure {
|
||||
unsupported_keys: Vec<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for MigrationFailure {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "Legacy [run.sandbox] settings could not be auto-migrated.")?;
|
||||
writeln!(f)?;
|
||||
writeln!(f, "Unsupported keys:")?;
|
||||
for key in &self.unsupported_keys {
|
||||
writeln!(f, " - {key}")?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
write!(
|
||||
f,
|
||||
"Rename legacy sandbox configuration to [run.environment] and [environments.<slug>]. See docs/public/execution/environments.mdx."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn migrate_settings_path(
|
||||
path: &Path,
|
||||
original_contents: &str,
|
||||
) -> Result<Option<LegacySandboxMigrationReport>> {
|
||||
let Some(next_contents) = migrate_contents(original_contents, path)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
next_contents
|
||||
.parse::<SettingsLayer>()
|
||||
.map_err(|err| Error::parse_file("Migrated settings file is invalid", path, err))?;
|
||||
|
||||
let backup_path = next_backup_path(path);
|
||||
std::fs::write(&backup_path, original_contents).map_err(|source| {
|
||||
Error::other(format!(
|
||||
"writing legacy sandbox migration backup {}: {source}",
|
||||
backup_path.display()
|
||||
))
|
||||
})?;
|
||||
std::fs::write(path, &next_contents).map_err(|source| {
|
||||
Error::other(format!(
|
||||
"writing migrated settings file {}: {source}",
|
||||
path.display()
|
||||
))
|
||||
})?;
|
||||
|
||||
let warning = format!(
|
||||
"Migrated legacy [run.sandbox] settings in {} to [run.environment] and [environments.default]. Backup written to {}. {REMOVAL_NOTE}",
|
||||
path.display(),
|
||||
backup_path.display()
|
||||
);
|
||||
|
||||
Ok(Some(LegacySandboxMigrationReport {
|
||||
contents: next_contents,
|
||||
backup_path,
|
||||
warning,
|
||||
}))
|
||||
}
|
||||
|
||||
fn migrate_contents(original_contents: &str, path: &Path) -> Result<Option<String>> {
|
||||
let mut doc = match original_contents.parse::<DocumentMut>() {
|
||||
Ok(doc) => doc,
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
|
||||
if !has_legacy_run_sandbox(&doc) {
|
||||
return Ok(None);
|
||||
}
|
||||
if has_new_environment_config(&doc) {
|
||||
return Err(Error::other(format!(
|
||||
"Legacy [run.sandbox] settings in {} could not be auto-migrated because the file already contains [run.environment] or [environments.default]. Remove one config style and retry.",
|
||||
path.display()
|
||||
)));
|
||||
}
|
||||
|
||||
migrate_document(&mut doc).map_err(|failure| {
|
||||
Error::other(format!(
|
||||
"Legacy [run.sandbox] settings in {} could not be auto-migrated.\n\n{}",
|
||||
path.display(),
|
||||
failure
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(Some(doc.to_string()))
|
||||
}
|
||||
|
||||
fn has_legacy_run_sandbox(doc: &DocumentMut) -> bool {
|
||||
doc.get("run")
|
||||
.and_then(Item::as_table)
|
||||
.and_then(|run| run.get("sandbox"))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn has_new_environment_config(doc: &DocumentMut) -> bool {
|
||||
let has_run_environment = doc
|
||||
.get("run")
|
||||
.and_then(Item::as_table)
|
||||
.and_then(|run| run.get("environment"))
|
||||
.is_some();
|
||||
let has_default_environment = doc
|
||||
.get("environments")
|
||||
.and_then(Item::as_table)
|
||||
.and_then(|envs| envs.get("default"))
|
||||
.is_some();
|
||||
has_run_environment || has_default_environment
|
||||
}
|
||||
|
||||
fn next_backup_path(path: &Path) -> PathBuf {
|
||||
let base = path.with_file_name(format!(
|
||||
"{}.legacy-sandbox-migration.bak",
|
||||
path.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("settings.toml")
|
||||
));
|
||||
if !base.exists() {
|
||||
return base;
|
||||
}
|
||||
|
||||
for index in 1.. {
|
||||
let candidate = path.with_file_name(format!(
|
||||
"{}.legacy-sandbox-migration.{index}.bak",
|
||||
path.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("settings.toml")
|
||||
));
|
||||
if !candidate.exists() {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
unreachable!("unbounded backup suffix search should return")
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add placeholder-free private stubs that compile**
|
||||
|
||||
Add private helpers with `unimplemented!()` only inside tests disabled by `#[cfg(test)]` is not allowed. Instead, make `migrate_document` return the one known unsupported failure until Task 2 fills it:
|
||||
|
||||
```rust
|
||||
fn migrate_document(_doc: &mut DocumentMut) -> std::result::Result<(), MigrationFailure> {
|
||||
Err(MigrationFailure {
|
||||
unsupported_keys: vec!["run.sandbox".to_string()],
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Run the focused compile check**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo test -p fabro-config legacy_sandbox_migration --quiet
|
||||
```
|
||||
|
||||
Expected: compiles; there may be zero tests in this module at this point.
|
||||
|
||||
## Task 2: Implement Provider-Only Migration
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-config/src/legacy_sandbox_migration.rs`
|
||||
|
||||
- [ ] **Step 1: Add tests for provider-only migration**
|
||||
|
||||
Add these tests inside `legacy_sandbox_migration.rs`:
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use fabro_types::settings::run::EnvironmentProvider;
|
||||
|
||||
fn migrate(source: &str) -> String {
|
||||
migrate_contents(source, Path::new("settings.toml"))
|
||||
.expect("migration should not error")
|
||||
.expect("legacy sandbox should migrate")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_only_daytona_config_migrates_to_default_environment() {
|
||||
let migrated = migrate(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[run.sandbox]
|
||||
provider = "daytona"
|
||||
"#,
|
||||
);
|
||||
|
||||
let settings = migrated
|
||||
.parse::<SettingsLayer>()
|
||||
.expect("migrated TOML should parse");
|
||||
let resolved = crate::WorkflowSettingsBuilder::from_layer(&settings)
|
||||
.expect("migrated settings should resolve")
|
||||
.run;
|
||||
|
||||
assert_eq!(resolved.environment.id, "default");
|
||||
assert_eq!(resolved.environment.provider, EnvironmentProvider::Daytona);
|
||||
assert!(migrated.contains("[run.environment]"));
|
||||
assert!(migrated.contains("[environments.default]"));
|
||||
assert!(!migrated.contains("[run.sandbox]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_legacy_config_is_not_migrated() {
|
||||
let migrated = migrate_contents("_version = 1\n", Path::new("settings.toml"))
|
||||
.expect("non-legacy TOML should not error");
|
||||
|
||||
assert!(migrated.is_none());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests and confirm failure**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo test -p fabro-config provider_only_daytona_config_migrates_to_default_environment --quiet
|
||||
```
|
||||
|
||||
Expected: FAIL because `migrate_document` still returns unsupported `run.sandbox`.
|
||||
|
||||
- [ ] **Step 3: Replace `migrate_document` with provider migration**
|
||||
|
||||
Implement the initial migration:
|
||||
|
||||
```rust
|
||||
fn migrate_document(doc: &mut DocumentMut) -> std::result::Result<(), MigrationFailure> {
|
||||
let Some(sandbox_item) = doc
|
||||
.get("run")
|
||||
.and_then(Item::as_table)
|
||||
.and_then(|run| run.get("sandbox"))
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(sandbox) = sandbox_item.as_table().cloned() else {
|
||||
return Err(MigrationFailure {
|
||||
unsupported_keys: vec!["run.sandbox".to_string()],
|
||||
});
|
||||
};
|
||||
|
||||
let mut unsupported = Vec::new();
|
||||
for (key, _) in sandbox.iter() {
|
||||
if key != "provider" {
|
||||
unsupported.push(format!("run.sandbox.{key}"));
|
||||
}
|
||||
}
|
||||
if !unsupported.is_empty() {
|
||||
return Err(MigrationFailure {
|
||||
unsupported_keys: unsupported,
|
||||
});
|
||||
}
|
||||
|
||||
let Some(provider) = sandbox.get("provider").and_then(Item::as_str) else {
|
||||
return Err(MigrationFailure {
|
||||
unsupported_keys: vec!["run.sandbox.provider".to_string()],
|
||||
});
|
||||
};
|
||||
|
||||
set_value(path_table(doc, &["run", "environment"]), "id", Value::from("default"));
|
||||
set_value(
|
||||
path_table(doc, &["environments", "default"]),
|
||||
"provider",
|
||||
Value::from(provider),
|
||||
);
|
||||
|
||||
remove_run_sandbox(doc);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn path_table<'a>(doc: &'a mut DocumentMut, path: &[&str]) -> &'a mut Table {
|
||||
let mut item = doc.as_item_mut();
|
||||
for segment in path {
|
||||
item = &mut item[segment];
|
||||
if !item.is_table() {
|
||||
*item = Item::Table(Table::new());
|
||||
}
|
||||
}
|
||||
item.as_table_mut().expect("path item should be a table")
|
||||
}
|
||||
|
||||
fn set_value(table: &mut Table, key: &str, value: Value) {
|
||||
table[key] = Item::Value(value);
|
||||
}
|
||||
|
||||
fn remove_run_sandbox(doc: &mut DocumentMut) {
|
||||
if let Some(run) = doc.get_mut("run").and_then(Item::as_table_mut) {
|
||||
run.remove("sandbox");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo test -p fabro-config legacy_sandbox_migration --quiet
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
## Task 3: Add Daytona and Docker Field Mappings
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-config/src/legacy_sandbox_migration.rs`
|
||||
|
||||
- [ ] **Step 1: Add tests for direct legacy field mappings**
|
||||
|
||||
Add tests that assert resolved behavior, not only string contents:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn daytona_snapshot_labels_lifecycle_and_volumes_migrate() {
|
||||
let migrated = migrate(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[run.sandbox]
|
||||
provider = "daytona"
|
||||
preserve = true
|
||||
|
||||
[run.sandbox.env]
|
||||
NODE_ENV = "development"
|
||||
|
||||
[run.sandbox.daytona]
|
||||
auto_stop_interval = 30
|
||||
|
||||
[run.sandbox.daytona.labels]
|
||||
repo = "fabro-sh/fabro"
|
||||
|
||||
[run.sandbox.daytona.snapshot]
|
||||
name = "fabro-v11"
|
||||
cpu = 8
|
||||
memory = "16GB"
|
||||
disk = "20GB"
|
||||
dockerfile = { path = "Dockerfile" }
|
||||
|
||||
[[run.sandbox.daytona.volumes]]
|
||||
volume_id = "vol_auth"
|
||||
mount_path = "/home/daytona/.config"
|
||||
subpath = "agents"
|
||||
"#,
|
||||
);
|
||||
|
||||
let settings = migrated.parse::<SettingsLayer>().expect("migrated TOML should parse");
|
||||
let resolved = crate::WorkflowSettingsBuilder::from_layer(&settings)
|
||||
.expect("migrated settings should resolve")
|
||||
.run
|
||||
.environment;
|
||||
|
||||
assert_eq!(resolved.image.reference.as_deref(), Some("fabro-v11"));
|
||||
assert_eq!(resolved.resources.cpu, Some(8));
|
||||
assert_eq!(resolved.resources.memory.map(|size| size.as_bytes()), Some(16_000_000_000));
|
||||
assert_eq!(resolved.resources.disk.map(|size| size.as_bytes()), Some(20_000_000_000));
|
||||
assert!(resolved.lifecycle.preserve);
|
||||
assert_eq!(resolved.lifecycle.auto_stop.map(|duration| duration.as_std().as_secs()), Some(1800));
|
||||
assert_eq!(resolved.labels.get("repo").map(String::as_str), Some("fabro-sh/fabro"));
|
||||
assert_eq!(resolved.env.get("NODE_ENV").map(|value| value.as_source()).as_deref(), Some("development"));
|
||||
assert_eq!(resolved.volumes.len(), 1);
|
||||
assert_eq!(resolved.volumes[0].id, "vol_auth");
|
||||
assert_eq!(resolved.volumes[0].mount_path, "/home/daytona/.config");
|
||||
assert_eq!(resolved.volumes[0].subpath.as_deref(), Some("agents"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn docker_image_memory_and_cpu_quota_migrate() {
|
||||
let migrated = migrate(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[run.sandbox]
|
||||
provider = "docker"
|
||||
|
||||
[run.sandbox.docker]
|
||||
image = "buildpack-deps:noble"
|
||||
memory_limit = "4GB"
|
||||
cpu_quota = 200000
|
||||
"#,
|
||||
);
|
||||
|
||||
let settings = migrated.parse::<SettingsLayer>().expect("migrated TOML should parse");
|
||||
let resolved = crate::WorkflowSettingsBuilder::from_layer(&settings)
|
||||
.expect("migrated settings should resolve")
|
||||
.run
|
||||
.environment;
|
||||
|
||||
assert_eq!(resolved.provider, EnvironmentProvider::Docker);
|
||||
assert_eq!(resolved.image.reference.as_deref(), Some("buildpack-deps:noble"));
|
||||
assert_eq!(resolved.resources.cpu, Some(2));
|
||||
assert_eq!(resolved.resources.memory.map(|size| size.as_bytes()), Some(4_000_000_000));
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests and confirm failure**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo test -p fabro-config legacy_sandbox_migration --quiet
|
||||
```
|
||||
|
||||
Expected: FAIL because the two new nested-mapping tests are not implemented yet.
|
||||
|
||||
- [ ] **Step 3: Implement table copying and value transforms**
|
||||
|
||||
Extend `migrate_document` so it:
|
||||
|
||||
- Allows top-level legacy keys `provider`, `preserve`, `env`, `daytona`, and `docker`.
|
||||
- Copies `run.sandbox.env` into `environments.default.env`.
|
||||
- Sets `environments.default.lifecycle.preserve` from `run.sandbox.preserve`.
|
||||
- Handles provider-specific nested mappings only for the selected provider.
|
||||
- Removes `run.sandbox` after successful migration.
|
||||
|
||||
Use helper functions with these signatures:
|
||||
|
||||
```rust
|
||||
fn migrate_daytona(sandbox: &Table, env: &mut Table, unsupported: &mut Vec<String>);
|
||||
fn migrate_docker(sandbox: &Table, env: &mut Table, unsupported: &mut Vec<String>);
|
||||
fn copy_table(source: &Item, target: &mut Table);
|
||||
fn copy_array_of_tables_with_volume_id(source: &Item, target: &mut Table, unsupported: &mut Vec<String>);
|
||||
fn item_path_keys(prefix: &str, item: &Item, out: &mut Vec<String>);
|
||||
```
|
||||
|
||||
Implementation rules:
|
||||
|
||||
- `auto_stop_interval` must be an integer. Store `format!("{minutes}m")`.
|
||||
- `docker.cpu_quota` must be an integer divisible by `100000`; otherwise add `run.sandbox.docker.cpu_quota` to unsupported keys.
|
||||
- For Daytona volumes, each array entry may contain only `volume_id`, `mount_path`, and `subpath`; rename `volume_id` to `id`.
|
||||
- `daytona.snapshot.dockerfile` must be copied as the existing TOML value, preserving inline string or `{ path = "..." }`.
|
||||
- When collecting unsupported nested keys, report full paths such as `run.sandbox.daytona.snapshot.foo`.
|
||||
|
||||
- [ ] **Step 4: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo test -p fabro-config legacy_sandbox_migration --quiet
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
## Task 4: Add File Rewrite and Loader Hook
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-config/src/load.rs`
|
||||
- Modify: `lib/crates/fabro-config/src/legacy_sandbox_migration.rs`
|
||||
|
||||
- [ ] **Step 1: Add file rewrite tests**
|
||||
|
||||
Add tests:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn migrate_settings_path_writes_backup_and_rewrites_original() {
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let path = dir.path().join("settings.toml");
|
||||
let original = r#"
|
||||
_version = 1
|
||||
|
||||
[run.sandbox]
|
||||
provider = "daytona"
|
||||
"#;
|
||||
std::fs::write(&path, original).expect("write fixture");
|
||||
|
||||
let report = migrate_settings_path(&path, original)
|
||||
.expect("migration should succeed")
|
||||
.expect("legacy config should migrate");
|
||||
|
||||
let rewritten = std::fs::read_to_string(&path).expect("read rewritten settings");
|
||||
let backup = std::fs::read_to_string(&report.backup_path).expect("read backup");
|
||||
|
||||
assert_eq!(backup, original);
|
||||
assert!(rewritten.contains("[run.environment]"));
|
||||
assert!(rewritten.contains("[environments.default]"));
|
||||
assert!(report.warning.contains("temporary compatibility migration"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn existing_backup_uses_numbered_suffix() {
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let path = dir.path().join("settings.toml");
|
||||
std::fs::write(path.with_file_name("settings.toml.legacy-sandbox-migration.bak"), "old")
|
||||
.expect("write existing backup");
|
||||
|
||||
let next = next_backup_path(&path);
|
||||
|
||||
assert!(next.ends_with("settings.toml.legacy-sandbox-migration.1.bak"));
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests and confirm current state**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo test -p fabro-config legacy_sandbox_migration --quiet
|
||||
```
|
||||
|
||||
Expected: PASS if Task 1 file-writing code compiled; otherwise fix only the migration module.
|
||||
|
||||
- [ ] **Step 3: Hook migration into file loading**
|
||||
|
||||
Change `load_settings_path` in `lib/crates/fabro-config/src/load.rs` to this shape:
|
||||
|
||||
```rust
|
||||
pub(crate) fn load_settings_path(path: &Path) -> Result<SettingsLayer> {
|
||||
let content = std::fs::read_to_string(path).map_err(|source| Error::read_file(path, source))?;
|
||||
let mut layer = match content.parse::<SettingsLayer>() {
|
||||
Ok(layer) => layer,
|
||||
Err(err) => match crate::legacy_sandbox_migration::migrate_settings_path(path, &content)? {
|
||||
Some(report) => {
|
||||
tracing::warn!("{}", report.warning);
|
||||
eprintln!("{}", report.warning);
|
||||
report.contents.parse::<SettingsLayer>().map_err(|err| {
|
||||
Error::parse_file("Migrated settings file is invalid", path, err)
|
||||
})?
|
||||
}
|
||||
None => return Err(Error::parse_file("Failed to parse settings file", path, err)),
|
||||
},
|
||||
};
|
||||
let base_dir = path.parent().unwrap_or_else(|| Path::new("."));
|
||||
resolve_goal_file_paths(&mut layer, base_dir);
|
||||
Ok(layer)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run loader-level verification**
|
||||
|
||||
Add a test in `load.rs` under `#[cfg(test)]` if the file does not already have a test module:
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use fabro_types::settings::run::EnvironmentProvider;
|
||||
|
||||
#[test]
|
||||
fn load_settings_path_auto_migrates_legacy_sandbox_file() {
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let path = dir.path().join("settings.toml");
|
||||
std::fs::write(
|
||||
&path,
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[run.sandbox]
|
||||
provider = "daytona"
|
||||
"#,
|
||||
)
|
||||
.expect("write legacy settings");
|
||||
|
||||
let layer = load_settings_path(&path).expect("legacy settings should auto-migrate");
|
||||
let resolved = crate::WorkflowSettingsBuilder::from_layer(&layer)
|
||||
.expect("migrated settings should resolve")
|
||||
.run;
|
||||
|
||||
assert_eq!(resolved.environment.provider, EnvironmentProvider::Daytona);
|
||||
assert!(std::fs::read_to_string(&path)
|
||||
.expect("read rewritten settings")
|
||||
.contains("[run.environment]"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo test -p fabro-config load_settings_path_auto_migrates_legacy_sandbox_file --quiet
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Verify in-memory TOML parsing remains strict**
|
||||
|
||||
Run the existing current-main rejection test:
|
||||
|
||||
```bash
|
||||
cargo test -p fabro-config legacy_run_sandbox_is_rejected --quiet
|
||||
```
|
||||
|
||||
Expected: PASS. Do not weaken `SettingsLayer` deserialization to accept `run.sandbox`; only `load_settings_path` should rewrite files from disk.
|
||||
|
||||
## Task 5: Unsupported and Ambiguous Cases
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/crates/fabro-config/src/legacy_sandbox_migration.rs`
|
||||
|
||||
- [ ] **Step 1: Add failure tests**
|
||||
|
||||
Add tests:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn existing_new_environment_config_is_ambiguous() {
|
||||
let err = migrate_contents(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[run.environment]
|
||||
id = "default"
|
||||
|
||||
[run.sandbox]
|
||||
provider = "daytona"
|
||||
"#,
|
||||
Path::new("settings.toml"),
|
||||
)
|
||||
.expect_err("mixed old and new config should fail");
|
||||
|
||||
assert!(err.to_string().contains("already contains [run.environment]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_keys_are_reported_with_full_paths() {
|
||||
let err = migrate_contents(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[run.sandbox]
|
||||
provider = "daytona"
|
||||
|
||||
[run.sandbox.daytona]
|
||||
unknown = true
|
||||
"#,
|
||||
Path::new("settings.toml"),
|
||||
)
|
||||
.expect_err("unsupported keys should fail migration");
|
||||
|
||||
let rendered = err.to_string();
|
||||
assert!(rendered.contains("run.sandbox.daytona.unknown"));
|
||||
assert!(rendered.contains("docs/public/execution/environments.mdx"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_docker_cpu_quota_is_reported() {
|
||||
let err = migrate_contents(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[run.sandbox]
|
||||
provider = "docker"
|
||||
|
||||
[run.sandbox.docker]
|
||||
cpu_quota = 250000
|
||||
"#,
|
||||
Path::new("settings.toml"),
|
||||
)
|
||||
.expect_err("non-divisible cpu quota should fail migration");
|
||||
|
||||
assert!(err.to_string().contains("run.sandbox.docker.cpu_quota"));
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run failure tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo test -p fabro-config legacy_sandbox_migration --quiet
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
## Task 6: Documentation
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/public/execution/environments.mdx`
|
||||
- Create: `docs/public/changelog/2026-05-23.mdx`
|
||||
|
||||
- [ ] **Step 1: Document temporary auto-migration**
|
||||
|
||||
Add this note near the top of `docs/public/execution/environments.mdx`, after the initial environment/sandbox distinction:
|
||||
|
||||
```mdx
|
||||
<Warning>
|
||||
Older pre-v1.0 config files that still use `[run.sandbox]` are temporarily auto-migrated when Fabro loads them from disk. Fabro writes a sibling `*.legacy-sandbox-migration.bak` file, rewrites the config to `[run.environment]` plus `[environments.default]`, and then continues startup.
|
||||
|
||||
This compatibility rewrite only handles direct field mappings. Unsupported legacy fields fail with a migration message that lists the keys to edit manually. The rewrite path will be removed before v1.0.
|
||||
</Warning>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add a changelog note**
|
||||
|
||||
Create `docs/public/changelog/2026-05-23.mdx`:
|
||||
|
||||
```mdx
|
||||
---
|
||||
title: "Legacy sandbox config migration"
|
||||
date: "2026-05-23"
|
||||
---
|
||||
|
||||
## Legacy sandbox config auto-migration
|
||||
|
||||
Fabro now temporarily rewrites confidently migratable pre-v1.0 `[run.sandbox]` config files to the named environment syntax. A backup is written next to the original file before rewriting. Ambiguous or unsupported legacy keys fail with a targeted migration message instead of the generic TOML unknown-field error.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Check docs references**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
rg -n "\\[run\\.sandbox\\]|legacy-sandbox-migration|run\\.environment" docs/public/execution docs/public/changelog
|
||||
```
|
||||
|
||||
Expected: remaining `[run.sandbox]` references are either historical changelog entries or explicit migration warnings.
|
||||
|
||||
## Task 7: Full Verification
|
||||
|
||||
**Files:**
|
||||
- All files touched above.
|
||||
|
||||
- [ ] **Step 1: Run config crate tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo test -p fabro-config --quiet
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 2: Run formatting check**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo +nightly-2026-04-14 fmt --check --all
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 3: Optional workspace lint if formatting and tests pass**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings
|
||||
```
|
||||
|
||||
Expected: PASS. If this is slow, record that it was not run and include the reason in the handoff.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Boot-time config loading rewrites simple legacy `[run.sandbox]` files without user action.
|
||||
- The rewritten file uses `[run.environment] id = "default"` and `[environments.default]`.
|
||||
- The original file is preserved in a sibling backup before rewrite.
|
||||
- Unsupported legacy keys fail with a targeted migration message listing exact keys.
|
||||
- Normal strict schema behavior remains unchanged for in-memory `from_toml` calls.
|
||||
- All legacy migration code is isolated in `legacy_sandbox_migration.rs` and removable before v1.0.
|
||||
|
||||
|
||||
## 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)
|
||||
- **implement**: succeeded
|
||||
- Model: gpt-5.5, 148.2k tokens in / 31.9k out
|
||||
- **simplify_opus**: succeeded
|
||||
- Model: claude-opus-4-7, 86.2k tokens in / 29.2k out
|
||||
- Files: /home/daytona/workspace/fabro/lib/crates/fabro-config/src/legacy_sandbox_migration.rs, /home/daytona/workspace/fabro/lib/crates/fabro-config/src/load.rs
|
||||
- **simplify_gpt**: succeeded
|
||||
- Model: gpt-5.5, 103.7k tokens in / 8.0k out
|
||||
- **verify**: failed
|
||||
- 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`
|
||||
- Output:
|
||||
```
|
||||
(35 lines omitted)
|
||||
POST /api/v1/runs/01KSB95FMFKTPWG6ZZQZ68SQXF/events (stage.completed)
|
||||
expected status: 200 OK
|
||||
actual status: 500 Internal Server Error
|
||||
url: http://fabro/api/v1/runs/01KSB95FMFKTPWG6ZZQZ68SQXF/events
|
||||
content-type: application/json
|
||||
body:
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"status": "500",
|
||||
"title": "Internal Server Error",
|
||||
"detail": "projection cache sequence gap for run 01KSB95FMFKTPWG6ZZQZ68SQXF: last_seq=9, event_seq=11",
|
||||
"request_id": "8443cfdb-3f63-401e-8adc-082163a44240"
|
||||
}
|
||||
],
|
||||
"request_id": "8443cfdb-3f63-401e-8adc-082163a44240"
|
||||
}
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
|
||||
Cancelling due to test failure: 7 tests still running
|
||||
────────────
|
||||
Summary [ 9.528s] 1086/6259 tests run: 1085 passed, 1 failed, 181 skipped
|
||||
FAIL [ 0.219s] (1079/6259) fabro-cli::it cmd::system_df::system_df_summarizes_runs_and_logs
|
||||
warning: 5173/6259 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
|
||||
error: test run failed
|
||||
```
|
||||
- **fixup**: succeeded
|
||||
- Model: claude-opus-4-7, 41.8k tokens in / 5.0k out
|
||||
- **verify**: failed
|
||||
- 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`
|
||||
- Output:
|
||||
```
|
||||
(35 lines omitted)
|
||||
POST /api/v1/runs/01KSB95FMFKTPWG6ZZQZ68SQXF/events (stage.completed)
|
||||
expected status: 200 OK
|
||||
actual status: 500 Internal Server Error
|
||||
url: http://fabro/api/v1/runs/01KSB95FMFKTPWG6ZZQZ68SQXF/events
|
||||
content-type: application/json
|
||||
body:
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"status": "500",
|
||||
"title": "Internal Server Error",
|
||||
"detail": "projection cache sequence gap for run 01KSB95FMFKTPWG6ZZQZ68SQXF: last_seq=9, event_seq=11",
|
||||
"request_id": "8443cfdb-3f63-401e-8adc-082163a44240"
|
||||
}
|
||||
],
|
||||
"request_id": "8443cfdb-3f63-401e-8adc-082163a44240"
|
||||
}
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
|
||||
Cancelling due to test failure: 7 tests still running
|
||||
────────────
|
||||
Summary [ 9.528s] 1086/6259 tests run: 1085 passed, 1 failed, 181 skipped
|
||||
FAIL [ 0.219s] (1079/6259) fabro-cli::it cmd::system_df::system_df_summarizes_runs_and_logs
|
||||
warning: 5173/6259 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
|
||||
error: test run failed
|
||||
```
|
||||
|
||||
## Context
|
||||
- failure_class: canceled
|
||||
- failure_signature: verify|canceled|script failed with exit code: <n> ## output from https://github.com/fabro-sh/fabro * branch main -> fetch_head already up to date. finished `dev` profile [unoptimized + debuginfo] target(s) in <n>.17s running `target/debug/fabro-dev docs re
|
||||
|
||||
|
||||
The verify step failed. Read the build output from context and fix all format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.
|
||||
5
stages/011-fixup@2/provider_used.json
Normal file
5
stages/011-fixup@2/provider_used.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"mode": "agent",
|
||||
"provider": "anthropic",
|
||||
"model": "claude-opus-4-7"
|
||||
}
|
||||
17
stages/011-fixup@2/response.md
Normal file
17
stages/011-fixup@2/response.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
All checks pass. The previous verify failure was a flaky test (`system_df_summarizes_runs_and_logs`) in fabro-cli — a pre-existing issue unrelated to my legacy sandbox migration changes:
|
||||
|
||||
- The test passes in isolation (verified).
|
||||
- The full workspace test suite passed: **6259/6259 tests passed**.
|
||||
- The failure mode (`projection cache sequence gap`) was in `fabro-store`/`fabro-server` event ingestion — code I did not touch.
|
||||
- My changes are confined to `lib/crates/fabro-config/` and `docs/public/`.
|
||||
|
||||
Verification summary:
|
||||
- `cargo +nightly-2026-04-14 fmt --check --all` — pass
|
||||
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings` — pass
|
||||
- `cargo nextest run --workspace --status-level slow --profile ci` — 6259 passed, 181 skipped
|
||||
- `cargo dev docs check` — pass
|
||||
- `bun install --frozen-lockfile` — pass
|
||||
- `apps/fabro-web` typecheck + test — 446/446 pass
|
||||
- `lib/packages/fabro-api-client` typecheck — pass
|
||||
|
||||
No code changes were needed; the previous failure was an unrelated flake that did not reproduce.
|
||||
Loading…
Add table
Reference in a new issue