Skip to content

test(cross-version-wire): derive skew expectations from the baseline under test - #17178

Merged
nwparker merged 5 commits into
mainfrom
fix-cross-version-derived-expectations
Aug 29, 2026
Merged

nwparker merged 5 commits into
mainfrom
fix-cross-version-derived-expectations

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​538 $\color{#cf222e}{\Huge{\mathbf{−}}}$​152 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​386
Prod 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​39 $\color{#cf222e}{\Huge{\mathbf{−}}}$​4 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​35

ELI5

The cross-version wire compatibility job checks that a new Orca client and an old Orca server still understand each other. To get an "old server" it checks out the newest published release and runs it against your working tree.

The test also contained a written-down list of things the old server doesn't have yet — for example, "an old server never puts terminalOwner on a snapshot frame."

That list has an expiry date nobody set. terminalOwner merged on 2026-08-26 in #16379. On 2026-08-29 v1.4.192 was cut, and it was the first tag containing that commit. From that moment "the newest release" did publish terminalOwner — so the test started failing, on whatever PR happened to be running, with nobody having changed a line of code.

This PR stops writing the list down. Each build is now first paired with a client of its own version to find out what it actually publishes, and the skewed pairings are compared against that. "Old" now means old relative to the field, not old relative to today.

Before / after

Before — cutting a release could turn this job red on unrelated pull requests, because the test compared against "the newest release" and that release now contained the very field it asserted was absent:

// new client against old server
for (const start of record.snapshotStarts) {
  expect(start).not.toHaveProperty('terminalOwner')
  expect(start).not.toHaveProperty('alternateScreen')
}
AssertionError: expected { kind: 'scrollback', … } to not have property "terminalOwner"
- Expected: undefined
+ Received: "shell"

After — the expectation comes from the baseline actually being tested, so only a real incompatibility fails it:

// Direction: the OLD host publishes here, and the new client only reads. Which
// optional fields that release shipped is a property of the release, so it is
// read from the baseline's own pairing rather than named here.
expect(record.snapshotStarts).toEqual(baselineReference.snapshotStarts)

Same tree, same suite:

tree baseline ("old server") result
main default → v1.4.192 1 failed / 4 passed
main v1.4.191 5 passed
main v1.4.184 5 passed
this PR default → v1.4.192 8 passed
this PR v1.4.191 8 passed
this PR v1.4.184 8 passed

What replaced the written-down lists

Direction is now named in every comment, because this failure was misread as "a new client is sending an unknown field" when snapshotStarts filters direction === 'host-to-client' — the old server was publishing it.

  • baselineReference — a baseline client against a baseline host. What that release publishes to its own client.
  • currentReference — a current client against a current host. What this tree publishes to its own client.
  • new client → old host must equal baselineReference — skew must not change what the old host publishes.
  • old client → new host must equal currentReference — skew must not change what the new host publishes.
  • additive-only — no field the old host still publishes may disappear from the new one (published-field-shape.ts).

The guard got stronger, not weaker: the old assertions could only say "these two names are absent", and were blind to any value the two pairings disagreed on.

Same shape elsewhere in the suite

cross-version-agent-session-wire.unit.test.ts had the same class, still latent only because no release has shipped agent-session.structured.v1 yet:

  • expect(baseline.capabilities).not.toContain(STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY)
  • expect(baseline.methodNames.filter(name => name.startsWith('agentSession.'))).toEqual([])
  • every method must answer method_not_found on the old dispatcher
  • four sites passing baseline.capabilities as "a client that never asked for structured sessions"

Those became: each build's advertised capability agrees with what it registers; the old dispatcher must always answer rather than fall silent, with method_not_found asserted for the methods that release genuinely lacks; and "a client too old to know this capability" is derived as the baseline's own list minus the capability.

Simulating the next release shipping that surface (baseline advertises + registers it): main reddens 4 tests, this branch stays 10/10 green.

cross-version-browser-placement.unit.test.ts is already immune — it pins v1.4.184 with a comment saying exactly why. Left alone, and the trade-off is now written down in docs/reference/remote-wire-compatibility.md under Never write down what the old side has.

The guard still bites

A new permanent test pairs a build whose decoder cannot read the Input opcode — the shape a peer too old to know it has on the wire — and asserts the harness stalls and records the rejected frame, rather than passing.

Mutation table, both baselines, mutation verified applied before each run:

# mutation reds
M1 current host drops displayMode (a field the old host still publishes) 1 — additive-only
M2 current host adds an optional field (a release shipping one) 0 ← the whole point
M3 current client changes a value the host echoes back 2 — both derived comparators
M5 harness degrades the reference to the four core fields 2 — both reference tests
A-M1 the structured-session capability gate removed from the host 1
D4 the deliberately-incompatible build made a no-op 1 — the regression case

Each gate is 1-to-1: deleting the new-client/old-host comparator leaves M3 reddening only the other one, and vice versa; deleting additive-only leaves M1 reddening nothing.

Gates

pnpm tc (all projects) clean · oxlint --deny-warnings clean · check-changed-code-quality.mjs 0 new findings · git diff --check clean · formatted with npx oxfmt --write on changed files only.

release-checkout.unit.test.ts > keeps an import live while another colliding release label materializes fails pre-existing on main in a local checkout (git rev-list --merges -1 HEAD is empty on main; CI runs on a merge ref). Verified by stashing this branch entirely and re-running. Not touched here.

…under test

The cross-version wire job pairs current code against whichever release tag is
newest, so a hand-written "the old side does not have X" assertion expires by
itself: v1.4.192 was the first tag containing the SnapshotStart `terminalOwner`
field, and cutting it turned the new-client/old-server pairing red on unrelated
pull requests with no code change anywhere.

Read what each build publishes from that build. Each host is now paired against
a client of its own version to produce a reference, and the skewed pairings are
compared against that reference, so the expectation is whatever the release
actually shipped. The same class of assertion in the agent-session suite —
"the old build advertises no structured capability and registers no structured
method" — becomes "each build's advertisement agrees with what it registers",
and the "client too old to know this capability" is derived by removing the
capability from the baseline's own list.

The guard is unchanged in strength: a field the old host still publishes may not
be dropped, skew may not change what a host puts on the wire, and a new pairing
asserts the oracle still stalls when a peer cannot decode an opcode the other
side sends.
@greptile-apps

greptile-apps Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

Greptile Summary

The PR replaces rolling-baseline assumptions with expectations derived from each build’s same-version behavior.

  • Compares terminal skew journeys with baseline and current reference pairings.
  • Adds additive published-field checks and an intentionally incompatible decoder case.
  • Derives structured-session capabilities and registered methods from the baseline.
  • Exercises every declared structured agent-session method through a release-shaped checkout and validates its host call and response.
  • Documents how rolling cross-version tests should derive rather than hard-code old-side behavior.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
tests/e2e/cross-version-wire/cross-version-agent-session-wire.unit.test.ts Derives legacy capabilities from the baseline and adds per-method execution and result verification that addresses the prior review threads.
tests/e2e/cross-version-wire/cross-version-terminal-wire.unit.test.ts Replaces hard-coded rolling-baseline field assumptions with same-version reference comparisons and stronger compatibility guards.
tests/e2e/cross-version-wire/published-field-shape.ts Adds reusable published-field shape comparison for additive wire evolution.
tests/e2e/cross-version-wire/terminal-skew-journey.ts Expands journey recording so reference and skewed pairings can be compared without hard-coded release assumptions.
tests/e2e/cross-version-wire/versioned-agent-session-wire.ts Extends the versioned-build harness to inspect and execute the structured surface in the selected checkout.
tests/e2e/cross-version-wire/versioned-terminal-wire.ts Adds decoder capability manipulation used to prove incompatible terminal frames stall the harness.
docs/reference/remote-wire-compatibility.md Documents why rolling baseline properties must be derived and when a pinned legacy release is appropriate.

Sequence Diagram

sequenceDiagram
    participant RC as Reference client
    participant H as Host build
    participant SC as Skewed client
    RC->>H: Run same-version journey
    H-->>RC: Reference publications
    SC->>H: Run cross-version journey
    H-->>SC: Skewed publications
    Note over RC,SC: Compare skewed output with the publishing build's reference
    SC->>H: Invoke declared agentSession methods
    H-->>SC: Validate host call and declared result
Loading

Reviews (6): Last reviewed commit: "test(cross-version): make the release-sh..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 845f825d-bc35-40b1-a850-b1e37e2990b6

📥 Commits

Reviewing files that changed from the base of the PR and between b1166e1 and 3f77a09.

📒 Files selected for processing (2)
  • tests/e2e/cross-version-wire/cross-version-agent-session-wire.unit.test.ts
  • tests/e2e/cross-version-wire/versioned-agent-session-wire.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

Cross-version wire tests now derive expectations from same-version current and baseline journeys. New utilities compare published field sets and detect additions or removals. Agent-session tests derive legacy capabilities and registered methods from the baseline. Release loading uses ALL_RPC_METHODS and structured host registries. Terminal journey failures include the stalled step and partial record. The harness can simulate peers without selected opcode support. Documentation explains baseline-derived assertions and assertion direction.

Merge Risk: ⚪ Minimal · up to 3f77a

This PR updates cross-version compatibility tests and related documentation without changing production behavior; no actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides a detailed explanation, rationale, before-and-after examples, testing results, and scope. However, it omits several required template sections, including the linked issue, exp… Add the missing required template sections. Include a valid Linked Issue entry, explicit What Changed and Why sections, Visual Proof with N/A and a reason if applicable, Testing checkboxes and platform details, AI Disclosure, Review, Agent …
Docstring Coverage ⚠️ Warning Docstring coverage is 58.82% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 7 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: deriving cross-version wire skew expectations from the tested baseline.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description provides a detailed explanation, rationale, before-and-after examples, testing results, and scope. However, it omits several required template sections, including the linked issue, explicit What Changed and Why headings, AI Disclosure, Review, Agent skill upstream boundary, Notes, and Checklist.

Resolution

Add the missing required template sections. Include a valid Linked Issue entry, explicit What Changed and Why sections, Visual Proof with N/A and a reason if applicable, Testing checkboxes and platform details, AI Disclosure, Review, Agent skill upstream boundary, Notes, and Checklist entries. Ensure the linked issue is included because the template requires one.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

… host gates

The release-shaped checkout gate accepted any reply that was not
method_not_found, so a registered-but-throwing handler passed it. The
capability gate asserted a shared host spy had been called at all, so the
second method mapped to that spy could stop reaching the host unnoticed.
Comment on lines +380 to +396
it(
'executes methods that a release-shaped checkout actually registers',
async () => {
const releasedCurrent = await loadAgentSessionWireBuild('HEAD')
expect(releasedCurrent.capabilities).toContain(STRUCTURED_AGENT_SESSION_RUNTIME_CAPABILITY)
expect(releasedCurrent.methodNames).toContain('agentSession.createSupport')
const replies = await callBuild(
releasedCurrent,
'agentSession.createSupport',
paramsFor('agentSession.createSupport'),
{ clientKind: 'runtime', clientCapabilities: current.capabilities }
)
expect(replies).toHaveLength(1)
// Anything weaker than the runtime's own answer passes a handler that is
// registered but unusable, which is the gap this test exists to close:
// any refusal other than `method_not_found` would satisfy it.
expect(replies[0]).toMatchObject({ ok: true, result: { supported: true } })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Registered methods remain unexecuted

When the rolling baseline registers structured methods other than agentSession.createSupport, the baseline loop accepts any reply except method_not_found, while this release-shaped execution check invokes only createSupport. An affected handler can therefore return structured_agent_session_unsupported, runtime_error, or another execution error without failing the compatibility suite, leaving registered methods unusable despite passing coverage.

…nt-session manifest

The release-shaped checkout is the only place the "registered means usable"
claim is executable today — the baseline release registers none of these
methods — and it was exercising one of sixteen. A handler registered and
returning an execution error passed the suite.

- Declare each method's result in the manifest, so "answered" is the contract
  rather than "did not say method_not_found".
- Give each build a seam to install a host into its own module slot; a release
  checkout has its own copy, so the working tree's host was never this
  dispatcher's, and every host-backed method answered
  structured_agent_session_unsupported — the capability gate's own words.
- Run one execution contract over both skews instead of two divergent loops.
- Pair the AI Vault never-called spy with a positive control; renaming the
  runtime method it watches left it green.
@nwparker
nwparker merged commit 11d8673 into main Aug 29, 2026
26 checks passed
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
…under test (stablyai#17178)

* test(cross-version-wire): derive skew expectations from the baseline under test

The cross-version wire job pairs current code against whichever release tag is
newest, so a hand-written "the old side does not have X" assertion expires by
itself: v1.4.192 was the first tag containing the SnapshotStart `terminalOwner`
field, and cutting it turned the new-client/old-server pairing red on unrelated
pull requests with no code change anywhere.

Read what each build publishes from that build. Each host is now paired against
a client of its own version to produce a reference, and the skewed pairings are
compared against that reference, so the expectation is whatever the release
actually shipped. The same class of assertion in the agent-session suite —
"the old build advertises no structured capability and registers no structured
method" — becomes "each build's advertisement agrees with what it registers",
and the "client too old to know this capability" is derived by removing the
capability from the baseline's own list.

The guard is unchanged in strength: a field the old host still publishes may not
be dropped, skew may not change what a host puts on the wire, and a new pairing
asserts the oracle still stalls when a peer cannot decode an opcode the other
side sends.

* test(cross-version-wire): exercise release structured methods

* test(cross-version-wire): load the registered method manifest

* test(cross-version-wire): assert execution, not registration, on both host gates

The release-shaped checkout gate accepted any reply that was not
method_not_found, so a registered-but-throwing handler passed it. The
capability gate asserted a shared host spy had been called at all, so the
second method mapped to that spy could stop reaching the host unnoticed.

* test(cross-version): make the release-shaped skew cover the whole agent-session manifest

The release-shaped checkout is the only place the "registered means usable"
claim is executable today — the baseline release registers none of these
methods — and it was exercising one of sixteen. A handler registered and
returning an execution error passed the suite.

- Declare each method's result in the manifest, so "answered" is the contract
  rather than "did not say method_not_found".
- Give each build a seam to install a host into its own module slot; a release
  checkout has its own copy, so the working tree's host was never this
  dispatcher's, and every host-backed method answered
  structured_agent_session_unsupported — the capability gate's own words.
- Run one execution contract over both skews instead of two divergent loops.
- Pair the AI Vault never-called spy with a positive control; renaming the
  runtime method it watches left it green.

---------

Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
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