Skip to content

Add a shared round-trip corpus + fix the wire-fidelity bugs it caught (SessionStatus bitset, changeset range, origin)#204

Merged
connor4312 merged 3 commits into
microsoft:mainfrom
joshmouch:pr/round-trip-corpus
Jun 10, 2026
Merged

Add a shared round-trip corpus + fix the wire-fidelity bugs it caught (SessionStatus bitset, changeset range, origin)#204
connor4312 merged 3 commits into
microsoft:mainfrom
joshmouch:pr/round-trip-corpus

Conversation

@joshmouch

@joshmouch joshmouch commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

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.

⚠️ Two SemVer-major fixes (the clients were already non-conformant)

  • SessionStatus is a numeric bitset on the wire (the Bitset JSDoc marker in types/channels-session/state.ts), but Rust modeled it as a closed enum and Kotlin as a signed Int — 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: Rust u32, Kotlin UInt, Swift UInt32 (Go was already uint32).
  • ChangesetOperationTarget range is range: TextRange per 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 canonical TextRange (TypeScript already did).

Non-breaking fixes

  • ActionEnvelope.origin is omitted (not null) when absent, per the spec (origin: ActionOrigin | undefined; undefined = server-originated).
  • Forward-compat: unknown discriminants on evolvable unions round-trip instead of throwing.

Corpus conventions

  • Each fixture is one input object + an acceptableOutputs array (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.
  • Exact-match, key-order-independent but key-presence-sensitive: null is not normalized to absent, so e.g. the origin omit-vs-null distinction is genuinely tested.
  • Group A = every client agrees. Group B (017/019) = a known type carries unknown wire keys; runtime decoders drop them (acceptableOutputs[0]) and TypeScript preserves them (typescriptOutput) — a real type-system difference, documented in KNOWN-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.

@joshmouch joshmouch force-pushed the pr/round-trip-corpus branch 2 times, most recently from 30bc0af to f55ef6c Compare June 9, 2026 15:41
@joshmouch joshmouch changed the title Add shared round-trip corpus + wire-fidelity fixes (Swift/Rust/Kotlin) Add a shared round-trip corpus + non-breaking Swift fidelity fixes Jun 9, 2026
@joshmouch joshmouch force-pushed the pr/round-trip-corpus branch from 2fc29ca to 85b561d Compare June 9, 2026 16:52
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".
@joshmouch joshmouch force-pushed the pr/round-trip-corpus branch from 85b561d to 3fa261a Compare June 9, 2026 17:07
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".
@joshmouch joshmouch force-pushed the pr/round-trip-corpus branch from 3fa261a to 0eafdf3 Compare June 9, 2026 19:29
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".
Comment thread types/test-cases/round-trips/019-channel-scoped-notification-uri.json Outdated
…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.
@joshmouch joshmouch force-pushed the pr/round-trip-corpus branch from 0eafdf3 to feeb502 Compare June 10, 2026 08:32
@joshmouch joshmouch changed the title Add a shared round-trip corpus + non-breaking Swift fidelity fixes Add a shared round-trip corpus + fix the wire-fidelity bugs it caught (SessionStatus bitset, changeset range, origin) Jun 10, 2026
@joshmouch

joshmouch commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Done. Every fixture is now a single input + acceptableOutputs array, uniform across the five clients.

I held acceptableOutputs to one entry on purpose: allowing several "acceptable" forms lets clients diverge instead of pinning a single wire shape, which is the whole point of the corpus. The one case where clients legitimately differ (a known type carrying unknown future keys, which runtime decoders drop and TS can't) is a group: "B" + typescriptOutput field, so that type-system difference is explicit rather than a second blessed output.

Comment thread types/test-cases/round-trips/KNOWN-FIDELITY-GAPS.md Outdated
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.

@connor4312 connor4312 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

@connor4312 connor4312 enabled auto-merge (squash) June 10, 2026 17:58
@connor4312 connor4312 disabled auto-merge June 10, 2026 17:58
@connor4312 connor4312 enabled auto-merge June 10, 2026 17:58
@connor4312 connor4312 merged commit f3c0375 into microsoft:main Jun 10, 2026
8 checks passed
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.

3 participants