Skip to content

feat(config): Tolerate schema evolution in stored ConfigDeltas#501

Merged
JeanMertz merged 1 commit intomainfrom
prr70
Apr 2, 2026
Merged

feat(config): Tolerate schema evolution in stored ConfigDeltas#501
JeanMertz merged 1 commit intomainfrom
prr70

Conversation

@JeanMertz
Copy link
Copy Markdown
Collaborator

When AppConfig schema evolves (fields renamed, removed, or typed differently), old conversation streams fail to deserialize because PartialConfig types use deny_unknown_fields. This causes jp to refuse to open any conversation that was saved with a now-removed config field.

A new compat module in jp_conversation adds schema-aware stripping before deserialization: the raw JSON for each config_delta event is walked alongside the current AppConfig schema and any keys not present in the schema are removed. Structs with flattened fields (e.g. ToolsConfig, which holds per-tool overrides as sibling keys) are skipped during stripping to avoid wiping valid data. If deserialization still fails after stripping (e.g. a field's type changed), the delta falls back to an empty one with just the timestamp preserved, so the stream as a whole continues to load.

To support this, AppConfig now exposes a schema() method returning the full Schema tree via Schematic::build_schema, and the relevant schematic types are re-exported from jp_config.

When `AppConfig` schema evolves (fields renamed, removed, or typed
differently), old conversation streams fail to deserialize because
`PartialConfig` types use `deny_unknown_fields`. This causes jp to
refuse to open any conversation that was saved with a now-removed config
field.

A new `compat` module in `jp_conversation` adds schema-aware stripping
before deserialization: the raw JSON for each `config_delta` event is
walked alongside the current `AppConfig` schema and any keys not present
in the schema are removed. Structs with flattened fields (e.g.
`ToolsConfig`, which holds per-tool overrides as sibling keys) are
skipped during stripping to avoid wiping valid data. If deserialization
still fails after stripping (e.g. a field's type changed), the delta
falls back to an empty one with just the timestamp preserved, so the
stream as a whole continues to load.

To support this, `AppConfig` now exposes a `schema()` method returning
the full `Schema` tree via `Schematic::build_schema`, and the relevant
`schematic` types are re-exported from `jp_config`.

Signed-off-by: Jean Mertz <git@jeanmertz.com>
@JeanMertz JeanMertz merged commit 5986645 into main Apr 2, 2026
12 checks passed
@JeanMertz JeanMertz deleted the prr70 branch April 2, 2026 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant