Skip to content

feat(compat): a floor, not an equality — and the ordering trap under it - #17

Merged
emooreatx merged 1 commit into
mainfrom
feat/compatibility-floor
Aug 28, 2026
Merged

feat(compat): a floor, not an equality — and the ordering trap under it#17
emooreatx merged 1 commit into
mainfrom
feat/compatibility-floor

Conversation

@emooreatx

Copy link
Copy Markdown
Contributor

Closes #16. The client half of CIRISServer#497.

Yes to the decoupling. The measurement behind it is sound and the sequencing is right — a range without this ships a permanent nag.

The part that would have shipped quietly

Equality never ordered anything, so it could not be wrong about order. A floor is nothing but order, and string comparison is wrong at the versions this repo actually ships:

"0.5.9"   > "0.5.190"   lexically, because '9' > '1'
"0.5.191" < "0.5.9"     lexically

Three-digit patch numbers make that the common case here, not an edge — a floor written the obvious way decides a 0.5.9 node clears a 0.5.190 floor. compareVersions is numeric per component and the tests name those exact pairs.

Two directions, which equality conflated

question who answers why
node too old for this client? MIN_NODE_VERSION, held here a node predating the idea of declaring a floor can't tell us anything about itself
client too old for this node? declared by the node only the node knows which clients it supports; no client-held constant can learn that a newer node needs a newer client — it would call itself compatible and be wrong

Nothing declares the second today, so it's optional, and absent means "did not say", not "is happy". The floor this side holds still applies, which preserves the property you asked us to keep: a genuinely incompatible pair still says so.

One thing I want your view on

MIN_NODE_VERSION = 0.5.190 is not earned the way your floor was, and the code says so in place. You mutation-tested both ends — 0.5.186 fails the id gate, 0.5.188 passes it and fails the wheel gate, so neither gate alone would have blessed a safe floor. Ours is just the pairing both sides are standardising on; nothing here proves this client cannot drive an older node.

To match your rigour this needs a gate that installs the floor and exercises the API surface the client actually calls. Until then it's your phrase exactly: an untested bound is a guess with a version number on it. Happy to build that gate — it's the natural next corner for the walk-test harness in #10.

Behaviour changes, recorded rather than deleted

  • version_mismatch_ignores_the_v_prefix paired 0.5.176 with itself and asserted no flag — true only under equality. A 0.5.176 node is now genuinely too old, so the prefix is demonstrated at versions where the answer isn't about age.
  • version_mismatch_fires_on_a_real_difference..._on_a_node_below_the_floor. It still fires, for a different reason; a mere difference no longer fires at all.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EGE52kPzjGFiPzGcs63ZC1

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

CIRISServer#497 moves the server's dependency to `ciris-client>=0.5.190,<0.6` so
the client can ship for the agent team without a paired server cut.
CIRISClient#16 is the other half: under a range, comparing the node's version to
ours for EQUALITY fires on every legitimately-mixed pair. A 0.5.192 node with a
0.5.193 client is the point of decoupling, and it would have greeted its
operator with a permanent nag. That trades a release dance for a nag, which is
worse.

It was never a bug while the versions were locked. They were always equal, the
comparison was always true, and the branch was never exercised. Widening the
range is what turns a held-still assumption into a defect.

THE ORDERING TRAP, WHICH IS THE PART THAT WOULD HAVE SHIPPED QUIETLY. Equality
never ORDERED anything, so it could not be wrong about order. A floor is nothing
but order, and string comparison is wrong at the versions this repo actually
ships:

    "0.5.9"   > "0.5.190"   lexically, because '9' > '1'
    "0.5.191" < "0.5.9"     lexically

Three-digit patch numbers make that the common case here, not an edge — a floor
written the obvious way decides a 0.5.9 node clears a 0.5.190 floor.
`compareVersions` is numeric per component, and the test names the exact pairs.

TWO DIRECTIONS, WHICH EQUALITY CONFLATED:

  node too old for this client    MIN_NODE_VERSION, held here, because a node
                                  that predates the idea of declaring a floor
                                  cannot tell us anything about itself.
  client too old for this node    declared BY THE NODE, because only the node
                                  knows which clients it supports. No
                                  client-held constant can learn that a newer
                                  node needs a newer client — it would call
                                  itself compatible and be wrong.

Nothing declares the second today, so it is optional, and absent means "did not
say" rather than "is happy". The floor this side holds still applies, which
keeps the signal the nag exists for: a genuinely incompatible pair still says
so. Replacing equality with "never complain" would have deleted it, and someone
shipped a mismatched pair once already.

MIN_NODE_VERSION IS NOT YET EARNED, AND THE CODE SAYS SO. The server
mutation-tested its floor at both ends — 0.5.186 fails the id gate, 0.5.188
passes it and fails the wheel gate, so neither gate alone would have caught a
floor that breaks Android. Ours is the pairing both sides are standardising on
and nothing here proves this client cannot drive an older node. Until a gate
installs the floor and exercises the API surface this client calls, it is what
they called it: an untested bound is a guess with a version number on it.

Two existing tests changed meaning, and are rewritten rather than deleted.
`version_mismatch_ignores_the_v_prefix` paired 0.5.176 with itself and asserted
no flag — true only under equality; a 0.5.176 node is now genuinely too old, so
the prefix is demonstrated at versions where the answer is not about age.
`version_mismatch_fires_on_a_real_difference` is renamed to
`..._on_a_node_below_the_floor`: it still fires, for a different reason, and a
mere difference no longer fires at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EGE52kPzjGFiPzGcs63ZC1
@emooreatx
emooreatx force-pushed the feat/compatibility-floor branch from daa7261 to 646f130 Compare August 28, 2026 02:31
@emooreatx
emooreatx merged commit 6802d33 into main Aug 28, 2026
3 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.

isVersionMismatch should compare against a compatibility floor, not the node's exact version

1 participant