Portable Entity ID Range
This page defines the cross-stack TeaQL contract for persistence identities and the foreign keys that reference them. It does not define business-facing numbers such as order numbers, account codes, or external partner identifiers.
Evidence status: provisional. The range is the adopted design contract and
matches Java Long, signed SQL BIGINT, and the checked signed bindings in
current Rust provider paths. A complete audit proving that every generator,
provider, import, and serialization entry point enforces the contract has not
yet been recorded. Applications must validate custom and external ID sources.
Normative Range
The portable numeric domain is:
PORTABLE_ID_MAX = 9,223,372,036,854,775,807 = 2^63 - 1
The following rules apply:
- Persisted entity IDs and foreign keys MUST be in
1..=PORTABLE_ID_MAX. - Zero is reserved for unassigned/sentinel use and MUST NOT be generated or persisted as an entity identity.
- Negative values are invalid.
- Values greater than
PORTABLE_ID_MAXare invalid even when a language or database type can represent them. - An invalid value MUST be rejected before mutation; it MUST NOT be wrapped, truncated, clamped, or reinterpreted as signed bits.
- Primary-key and foreign-key columns SHOULD use a signed 64-bit integer type.
- Business identifiers MUST use a separate field and lifecycle contract.
Null or an option type represents absence. Code must not silently translate between null and zero.
Language and Storage Mapping
| Boundary | Required representation | Validation |
|---|---|---|
| Rust domain/runtime | u64 is permitted | Require 1 <= id && id <= i64::MAX as u64 before persistence or interchange |
| Java domain/runtime | long / Long | Require a positive value; never use negative long values as unsigned IDs |
| Go service | int64 preferred; checked uint64 permitted internally | Reject zero and values greater than math.MaxInt64 |
| Python service/tooling | int | Explicitly require 1 <= id <= 2**63 - 1 |
| SQL schema | signed BIGINT or exact equivalent | Add application validation; use a database check constraint where operationally compatible |
| Protobuf | int64 | Reject zero/negative values for persisted IDs |
| JSON/HTTP | canonical decimal string | Parse digits, reject non-canonical forms, then check the range |
Do not choose a SQL unsigned column merely because one provider offers it. The portable contract is defined by the shared range, not the widest capability of one provider.
JSON Contract
IDs exposed through JSON MUST be decimal strings:
{
"id": "481923487123456789",
"ownerId": "7234567890123456789"
}
This rule avoids precision loss in JavaScript, whose Number type represents
integers exactly only through 2^53 - 1.
The canonical persisted-ID form:
- contains ASCII digits only;
- has no sign, whitespace, decimal point, or exponent;
- has no leading zero;
- parses to a value in
1..=PORTABLE_ID_MAX.
An endpoint that needs an explicit unassigned value may accept null. It
SHOULD NOT publish "0" as an entity identity.
Generator Requirements
Every built-in or custom generator MUST:
- allocate uniquely within its documented scope;
- produce a value in
1..=PORTABLE_ID_MAX; - validate the final composed value, including timestamp/node/sequence bit layouts;
- return an explicit error when the next value cannot be represented;
- avoid wrapping and saturating arithmetic;
- preserve database uniqueness constraints as the final collision defense;
- expose allocation failures and high-water marks through logs and metrics.
A sequential allocator must use an atomic database sequence, atomic update, or
equivalent concurrency-safe mechanism. SELECT MAX(id) + 1 is not a safe
allocator under concurrent writes.
A Snowflake-style allocator must calculate its own timestamp horizon and validate worker/datacenter assignments. The size of the general signed 64-bit space does not override the smaller capacity of an individual bit layout.
Checked Arithmetic
Code that derives, offsets, or otherwise performs arithmetic on an ID must check both machine overflow and the portable maximum.
// Illustrative boundary helper; adapt the error type to the application.
fn portable_id(value: u64) -> Result<u64, &'static str> {
if (1..=i64::MAX as u64).contains(&value) {
Ok(value)
} else {
Err("entity ID is outside the portable range")
}
}
fn checked_next(current: u64) -> Result<u64, &'static str> {
let next = current.checked_add(1).ok_or("entity ID overflow")?;
portable_id(next)
}
Do not use ordinary arithmetic, wrapping operations, or saturating operations as an exhaustion strategy. Exhaustion is a capacity failure that must stop new identity allocation and trigger an operational alert.
Validation Points
Validate the range at every trust or representation boundary:
- immediately after custom ID generation;
- when decoding an API, event, import, or command payload;
- before binding an ID or foreign key to SQL;
- when reading legacy or externally managed tables;
- before converting Rust
u64or Gouint64to a signed value; - before emitting an ID through a contract that declares this policy.
Database constraints provide defense in depth. For a table that does not use zero as a legacy sentinel, the conceptual constraint is:
CHECK (id > 0)
Signed BIGINT already enforces the upper machine bound. Confirm the exact
database type and migration behavior before adding constraints to an existing
table.
Failure Contract
Out-of-range input is a validation error. Generator exhaustion or an out-of-range generated result is an ID-generation/capacity error. Both must be distinguishable from a primary-key collision.
When allocation reaches the boundary:
- reject creation of new entities requiring an ID;
- keep existing entities readable;
- allow updates when they do not allocate another identity;
- emit a high-severity alert with generator and entity-type context;
- migrate the allocation scheme deliberately rather than rolling over.
Error responses and logs should report the violated range without exposing unrelated record data.
Adoption and Migration Check
Before declaring a service compliant:
- Inventory primary keys, foreign keys, ID generators, importers, events, and public API schemas.
- Check stored values for null/zero/negative IDs and type mismatches.
- Confirm all numeric columns fit signed 64-bit semantics.
- Add boundary tests for
0,1,2^53 - 1,2^53,2^63 - 1, and2^63. - Test JSON round trips through an actual JavaScript consumer.
- Test Java, Rust, and any Go/Python integration using the same fixtures.
- Test generator concurrency and verify that an out-of-range result fails before SQL mutation.
- Record exceptions and legacy sentinel behavior before enforcing database constraints.