State and test the codec round-trip invariant: reconstruct then capture must land where capture started
- Dominant language
- Rust
- Stars
- 2
- Forks
- 1
- Avg merge
- 21h 32m
- Merged PRs (30d)
- 67
Description
A host codec can capture a value and reconstruct something that no longer
captures the same way, and nothing in deja notices. `ReplayCodec` hands a host
`capture` and `reconstruct` as a pair and states no relationship between them,
so the pair is only as good as the care of whoever wrote it — and the failure is
silent, because a lossy reconstruction produces a well-formed value that
compares cleanly against a differently-shaped recorded one.
## The evidence this is a real gap, not a hypothetical one
Two independent instances, in two codebases, with two authors:
A same-image self-replay of a sandbox recording produced a divergence where an
outbound response's headers were recorded with fifteen entries in wire order
carrying three `set-cookie` values, and reconstructed as thirteen entries in
alphabetical order carrying one. Two values silently dropped, and the
alphabetical ordering says a map keyed on header name, which collapses
legitimately repeated names.
Reviewing hyperswitch-prism#2220 turned up the same collapse. That instance is
the sharper evidence, because it is contained in one pull request by one
author: `deja/http_layer.rs` uses `deja::http::headers` correctly, with a test
asserting that a repeated `x-multi` header keeps both values, while
`deja_codec.rs` hand-rolls the same job with a name-keyed map about two hundred
lines away and loses the duplicates. The same person got it right where we ship
a helper and wrong where we do not.
## Why the helper does not close it, and cannot
`deja::http::headers` (`crates/deja/src/lib.rs:1038`) is already correct: it
accumulates every value for a name into an array, so repeated names survive. But
it is capture-only, and so is everything beside it — `body`, `missing_body`,
`args`. There is no reconstruct counterpart anywhere in the `http` module, and
there cannot be one: reconstruct has to build a concrete `HeaderMap`, and naming
a transport type in a deja library crate is exactly what the boundary contract
exists to prevent.
So the asymmetry is structural. We ship the capture half, hosts write the
reconstruct half alone, and nothing states what the halves owe each other.
## What is missing
`ReplayCodec` (`crates/deja/src/lib.rs:901`) documents `capture` as "serialize
the value to tape JSON" and `reconstruct` as "rebuild the value from recorded
JSON", and stops. It never says that rebuilding and re-capturing must land back
where it started.
That property is expressible with no host types at all, because both sides of it
are `serde_json::Value`:
capture(reconstruct(v).unwrap()) == v
for every `v` a codec's own `capture` can produce. It is deja's accounting
discipline applied to codecs: every field that goes onto the tape comes back, or
the codec says which one did not.
Nothing tests it today. `crates/deja/tests/result_codec.rs` covers `ok_arm_
round_trips` and `err_arm_round_trips_the_same_typed_context`, but those are
tests of `ResultCodec` — deja's own codec — not a property a host codec must
satisfy. A host has nothing to run against its own implementation.
## What would close it
A conformance kit hosts can call on their own codecs: give it a set of
representative values, and it asserts the round trip for each. Both header cases
above fail it immediately, because a captured multimap with two values for one
name does not survive a reconstruct that keys on the name.
## What would not close it
Shipping a header representation, or a reconstruct-side header helper. Prism
used our existing helper correctly two hundred lines from its own bug, so a
second copy of a thing that already works would not have prevented it — and the
reconstruct half cannot live here anyway without dragging a transport type into
a library crate. The `insert`-instead-of-`append` mistake belongs to each host.
The missing invariant is ours.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with ReplayCodec in crates/deja/src/lib.rs:901 and the existing coverage in crates/deja/tests/result_codec.rs. Trace how capture and reconstruct are documented, then determine how a host-callable conformance kit can accept representative values without depending on host types. Done means host codecs can run the kit and it detects any captured value that does not survive reconstruct-then-capture unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend-api-design, testing-qa
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100