← 1.0.0 scope SAPE-30

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

  1. 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.
  2. 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.
  3. 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.
  4. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Every new guard watched failing before it is believed.

Out of scope