fabro/lib
André Mazoni 6d55875645
fix(store): keep pre-#530 runs loadable (serde default on run.prepare) (#554)
## Summary

Runs created before #530 disappear from the run list after upgrading,
because
their persisted `run.created` event can no longer be deserialized.

#530 renamed `RunPrepareSettings`'s field from `commands: Vec<String>`
to
`steps: Vec<PreparedStep>`. That struct is persisted inside the
`run.created`
event (`WorkflowSettings.run.prepare`). Events written by older versions
carry a
`prepare` object with a `commands` key and **no** `steps` key. Because
`steps`
had no serde default, deserializing such an event fails with:

```
Serialization error: missing field `steps`
```

`warm_projection_cache` catches that error per-run and **skips** the run
(`fabro_store::slate: Skipping run during projection cache warmup`), so
every
pre-#530 run silently vanishes from the run list. The event data is
intact on
disk — it just can't be read back.

This is an event-schema back-compat break: any type persisted in an
event must
stay readable across the field renames/additions that happen after it
was
written.

## Fix

Add `#[serde(default)]` at the container level on `RunPrepareSettings`,
so a
`prepare` object missing `steps` (and/or `timeout_ms`) falls back to the
existing `Default` impl (empty steps, product-default timeout) instead
of
failing the whole run. The unknown legacy `commands` key is ignored (the
struct
has no `deny_unknown_fields`).

- New runs always serialize explicit `steps`, so nothing changes for
them — the
  #530 feature is unaffected.
- Pre-#530 runs load again with an empty prepare phase, which is
faithful: those
  runs already executed; this only rebuilds a read model for display.

`#[serde(default)]` is already the evolution idiom in this same struct
tree
(e.g. `RunModelSettings.controls`).

## Test plan

- [x] `cargo test -p fabro-types` — added two regression tests that
deserialize
the exact pre-#530 event shape (`{ commands, timeout_ms }`, no `steps`)
      and an empty object, asserting both load instead of erroring.
- [x] Built the patched server and pointed it at a real
`~/.fabro/storage` that
had 119 pre-#530 runs being skipped. After the fix, 0 runs are skipped
and
      all 119 appear in `GET /api/v1/runs`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
2026-07-07 22:20:44 -04:00
..
crates fix(store): keep pre-#530 runs loadable (serde default on run.prepare) (#554) 2026-07-07 22:20:44 -04:00
packages/fabro-api-client Resolve run.prepare.steps env and interpolation at the run boundary (#530) 2026-07-01 10:31:26 -04:00