Add a shared round-trip corpus + fix the wire-fidelity bugs it caught (SessionStatus bitset, changeset range, origin)#204
Merged
Conversation
30bc0af to
f55ef6c
Compare
2fc29ca to
85b561d
Compare
joshmouch
added a commit
to joshmouch/agent-host-protocol
that referenced
this pull request
Jun 9, 2026
…tries to [Unreleased], rename corpus discriminator - Copy byte-identical corpus files from microsoft#204 (fixture 002/003 renamed discriminator to "Unknown", KNOWN-FIDELITY-GAPS.md folded 019 into 017 section). - Rust CHANGELOG: move SessionStatus breaking-change (### Changed) and encode/decode fidelity fix (### Fixed) from [0.3.0] to [Unreleased]. - Kotlin CHANGELOG: same — move SessionStatus rawValue Long breaking-change and decode-fidelity fix from [0.3.0] to [Unreleased]. - Kotlin BitsetEnumTest.kt: fix class-level KDoc "wrappers over [Int]" → "[Long]". - Kotlin TypesRoundTripFixtureTest.kt: update StateActionUnknown and CustomizationUnknown variant-name returns from "JsonElement" to "Unknown".
85b561d to
3fa261a
Compare
joshmouch
added a commit
to joshmouch/agent-host-protocol
that referenced
this pull request
Jun 9, 2026
…tries to [Unreleased], rename corpus discriminator - Copy byte-identical corpus files from microsoft#204 (fixture 002/003 renamed discriminator to "Unknown", KNOWN-FIDELITY-GAPS.md folded 019 into 017 section). - Rust CHANGELOG: move SessionStatus breaking-change (### Changed) and encode/decode fidelity fix (### Fixed) from [0.3.0] to [Unreleased]. - Kotlin CHANGELOG: same — move SessionStatus rawValue Long breaking-change and decode-fidelity fix from [0.3.0] to [Unreleased]. - Kotlin BitsetEnumTest.kt: fix class-level KDoc "wrappers over [Int]" → "[Long]". - Kotlin TypesRoundTripFixtureTest.kt: update StateActionUnknown and CustomizationUnknown variant-name returns from "JsonElement" to "Unknown".
3fa261a to
0eafdf3
Compare
joshmouch
added a commit
to joshmouch/agent-host-protocol
that referenced
this pull request
Jun 9, 2026
…tries to [Unreleased], rename corpus discriminator - Copy byte-identical corpus files from microsoft#204 (fixture 002/003 renamed discriminator to "Unknown", KNOWN-FIDELITY-GAPS.md folded 019 into 017 section). - Rust CHANGELOG: move SessionStatus breaking-change (### Changed) and encode/decode fidelity fix (### Fixed) from [0.3.0] to [Unreleased]. - Kotlin CHANGELOG: same — move SessionStatus rawValue Long breaking-change and decode-fidelity fix from [0.3.0] to [Unreleased]. - Kotlin BitsetEnumTest.kt: fix class-level KDoc "wrappers over [Int]" → "[Long]". - Kotlin TypesRoundTripFixtureTest.kt: update StateActionUnknown and CustomizationUnknown variant-name returns from "JsonElement" to "Unknown".
connor4312
reviewed
Jun 9, 2026
…Status, changeset range, origin)
A language-agnostic wire round-trip corpus (types/test-cases/round-trips/*.json) run by all five clients: decode wire bytes, re-encode, assert the single canonical form (exact-match; null is NOT normalized to absent). Go, Rust, Swift, and Kotlin decode via their real generated types -- including Kotlin's JsonRpcMessage, dispatched into its real JsonRpcRequest/Notification/SuccessResponse/ErrorResponse variant classes. TypeScript types are erased at runtime, so its round-trip harness verifies runtime wire behavior + fixture self-consistency, not generated-type correctness. Coverage gaps: types/test-cases/round-trips/KNOWN-FIDELITY-GAPS.md. No mocks.
SessionStatus bitset (SemVer-major). It is a numeric bitset on the wire, but Rust modeled it as a closed enum and Kotlin as a signed Int -- neither could represent combinations (InProgress|IsArchived = 72) or unknown future bits (2147483720, bit 31), failing fixtures 004/005. Now a uniform 32-bit-UNSIGNED bitset across all clients: Rust u32 (was enum), Go uint32, Kotlin UInt (was Int), Swift UInt32 -- every client holds the same value range, within TypeScript's number 53-bit-safe limit. Proven red->green: the Rust harness fails 004/005 on the old enum, passes on u32.
Changeset range (SemVer-major). ChangesetOperationTarget kind=range is 'range: TextRange' per spec (nested {start:{line,character}, end:{line,character}}), but the Go/Rust/Kotlin/Swift generators hardcoded a flat {start,end} int-pair that cannot hold a real range (the in-use nested shape is proven by the annotations reducer fixtures). The generators now reference the canonical TextRange (TS already did); fixture 013 corrected to nested.
Wire-fidelity fixes (non-breaking): ActionEnvelope.origin omitted (not null) when absent, per spec (undefined origin = server-originated) -- Go omitempty, Rust skip_serializing_if. Forward-compat unknown fallback for Customization/StateAction/changeset variants so unknown discriminants round-trip instead of throwing (also closes the Swift reducer fixture-103 gap). Exact-match single canonical outputs; fixtures 017/019 split into a TS-vs-runtime group.
0eafdf3 to
feeb502
Compare
Contributor
Author
|
Done. Every fixture is now a single I held |
connor4312
reviewed
Jun 10, 2026
The group-B field holds the expected output for clients that preserve unknown wire keys on re-encode. Naming it after the behavior rather than the one client that currently produces that form (TypeScript) leaves room for other clients to assert the same field if they adopt unknown-key passthrough later.
TylerLeonhardt
approved these changes
Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Adds a shared, language-agnostic round-trip corpus under
types/test-cases/round-trips/that every client (Go, Rust, Swift, Kotlin, TypeScript) decodes into its real generated types, re-encodes, and asserts against a single canonical form — the wire-fidelity analog of the existing reducer fixtures.Standing it up surfaced three real bugs where the generated clients had drifted from the canonical TS source, so this PR also fixes them.
BitsetJSDoc marker intypes/channels-session/state.ts), but Rust modeled it as a closedenumand Kotlin as a signedInt— neither could hold a flag combination (InProgress | IsArchived= 72) or an unknown forward-compat bit (e.g.2147483720). It is now a uniform 32-bit unsigned bitset: Rustu32, KotlinUInt, SwiftUInt32(Go was alreadyuint32).ChangesetOperationTargetrange isrange: TextRangeper the spec (nested{start: {line, character}, end: {line, character}}), but the generators hardcoded a flat{start, end}integer pair that cannot represent a real range. It now references the canonicalTextRange(TypeScript already did).Non-breaking fixes
ActionEnvelope.originis omitted (notnull) when absent, per the spec (origin: ActionOrigin | undefined;undefined= server-originated).Corpus conventions
inputobject + anacceptableOutputsarray (per @connor4312's suggestion on the previous review). Each array carries exactly one canonical entry — multiple "acceptable" outputs would risk cementing observed-but-wrong client divergence instead of pinning the single intended wire form.nullis not normalized to absent, so e.g. theoriginomit-vs-nulldistinction is genuinely tested.acceptableOutputs[0]) and TypeScript preserves them (typescriptOutput) — a real type-system difference, documented inKNOWN-FIDELITY-GAPS.md, never a skipped assertion.On scope
The corpus is additive; the two SemVer-major fixes are coupled to it (the corpus is what caught them, and their regression fixtures cannot pass without the fixes). I have kept them together as one coherent change, but I am very happy to split the breaking fixes into separate PRs if you would rather review or version them independently — just say the word.