Skip to content

test(core): persisted-schema round-trip fuzz + reject future checkpoint versions (#31)#49

Merged
ZhiXiao-Lin merged 1 commit into
mainfrom
chore/cluster-hardening-followups
May 29, 2026
Merged

test(core): persisted-schema round-trip fuzz + reject future checkpoint versions (#31)#49
ZhiXiao-Lin merged 1 commit into
mainfrom
chore/cluster-hardening-followups

Conversation

@ZhiXiao-Lin
Copy link
Copy Markdown
Contributor

Follow-up #31 (persisted-schema round-trip fuzz across versions).

What

  • New core/tests/test_persisted_schema_roundtrip.rs — a dependency-free, seeded (xorshift) round-trip fuzz over the persisted graph: LoopCheckpoint, RunRecord (incl. all three 3.3.0 AgentEvent variants), SubagentTaskSnapshot, TraceEvent, VerificationReport, Message/ContentBlock, and the largest root SessionData. Asserts serialize→deserialize→re-serialize stability (stronger than PartialEq, which several of these don't derive). Plus cross-version coverage:

    • Backward-compat: pre-3.3.0 payloads missing schema_version / the identity fields (tenant_id/principal/agent_template_id/correlation_id) / using the legacy todos alias all load with correct defaults.
    • Forward-compat: unknown extra fields are ignored — the invariant that keeps rolling, mixed-version cluster upgrades safe (would break the day a persisted type gains #[serde(deny_unknown_fields)]).
    • The #[serde(untagged)] ToolResultContentField and the persisted untagged PermissionRule (bare-string vs {rule:...}) hazards.
  • Fix: reject future checkpoint schema versions. LoopCheckpoint documented that loads from a future, incompatible schema version must be rejected, but no impl enforced it. Adds LoopCheckpoint::ensure_loadable(), called in both the file and memory store load paths — so resume_run surfaces the error and the live-run sink starts fresh rather than misinterpreting a checkpoint whose field semantics may have changed.

Verification

  • 10/10 tests pass. The fuzz already earned its keep — it caught a real Some(serde_json::Value::Null)None asymmetry in Option<Value> fields (a serde property, fixed in the generator, not a code defect).
  • No regressions (loop_checkpoint 5/5, store 36/36). CI gates green locally: cargo fmt --all --check, cargo clippy --workspace --lib --bins -D warnings; new test is clippy-clean.
  • Scope/coverage was hardened in response to an adversarial review (added SessionData + PermissionRule coverage that an initial pass missed).

Pointer-stable, additive; no public API change beyond the new LoopCheckpoint::ensure_loadable() method.

…nt versions (#31)

Adds core/tests/test_persisted_schema_roundtrip.rs — a dependency-free,
seeded round-trip fuzz over the persisted graph (LoopCheckpoint, RunRecord
incl. all three 3.3.0 AgentEvent variants, SubagentTaskSnapshot, TraceEvent,
VerificationReport, Message/ContentBlock, and the largest root SessionData),
asserting serialize→deserialize→re-serialize stability. Plus cross-version
coverage: backward-compat (pre-3.3.0 payloads missing schema_version /
identity fields / using the `todos` alias load with defaults), forward-compat
(unknown fields ignored — guards rolling cluster upgrades against a stray
deny_unknown_fields), the untagged ToolResultContentField and the persisted
untagged PermissionRule dual-form hazards.

Closes a real gap surfaced while writing it: LoopCheckpoint documented that
loads from a future schema version must be rejected, but no impl enforced it.
Adds LoopCheckpoint::ensure_loadable() and calls it in both the file and
memory store load paths, so resume_run surfaces the error and the live-run
sink starts fresh rather than misinterpreting an incompatible checkpoint.
@ZhiXiao-Lin ZhiXiao-Lin merged commit 8e709c5 into main May 29, 2026
1 check passed
@ZhiXiao-Lin ZhiXiao-Lin deleted the chore/cluster-hardening-followups branch May 29, 2026 06:42
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.

2 participants