An integer that is the integer you sent
- Status
- To do
- Component
- server
- Priority
- High — it is the half of ISS-35 that fixes rather than refuses
- Blocked by
- SAPE-29
- Blocks
- —
What is wrong today
number is one type and it is float64. Measured against a real daemon, sent
through invoke and read back out of the file:
sent 9007199254740993 stored 9007199254740992
sent 9223372036854775807 stored 9223372036854776000
sent 1234567890123456789 stored 1234567890123456800
Every one answered changed 1. ISS-35 is that measurement and
its refusal; this ticket is the type that makes the refusal unnecessary for the values people
actually meant.
Where the exactness is lost
Not at the last moment — there is nowhere in the current path where it still exists.
Thirty-eight call sites decode JSON with json.Unmarshal and exactly one uses
UseNumber, so a JSON number is a float64 at the door. The key
encoding agrees: internal/keys writes tagNumber followed by the eight bytes
of the float's bits, so an indexed number is float-shaped on disk too.
What it needs
- A decode path that keeps the digits. The number arrives as text and has to stay
text until something decides what it is. That is the change with the widest blast radius, and
counting the thirty-eight sites is the first task rather than an afterthought.
- A tag of its own in the key encoding, so an integer sorts as an integer. There
are 249 free byte values and the existing tags are spaced on purpose —
0x30 for
number, 0x40 for string — so a tag between them is available. Where it sorts
relative to number is a decision SAPE-29 has to make, not
this ticket.
- A rule for the boundary. What happens when an integer is compared with a
number, in a range scan that spans both. Silence here is how two callers end up with different
answers to the same question.
- A mapping in the TypeScript generator, which is the thing that turns a declared
type into something a caller can hold. JavaScript's own number is the same float64, so the honest
emit is probably
bigint or a string — and whichever it is, it is a decision the
client's users live with.
Acceptance criteria
- The three values above round-trip exactly, sent through a real daemon and read
back from the file, not asserted in a unit test alone.
- An integer declared as a primary key sorts correctly across the 253 boundary —
a scan returns …992, …993, …994 in that order, which is the case float64 cannot represent at all.
- Existing
number declarations are untouched: a value that round-trips today still
round-trips, and nothing that is valid becomes invalid. That is the policy line and a test watches
it.
- The TypeScript client generates a type for it that does not silently narrow to
number, proved by a value above 253 surviving the generated client.
- Every new guard watched failing before it is believed.
Out of scope
- The refusal in ISS-35. It stays, as the answer for values that still
cannot be represented — a safety net, not a substitute.
- Unsigned integers, or any width other than what the decision picks.
- Decimal and fixed-point. Money is a different problem and gets its own answer or none.