Skip to main content

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:

  1. Persisted entity IDs and foreign keys MUST be in 1..=PORTABLE_ID_MAX.
  2. Zero is reserved for unassigned/sentinel use and MUST NOT be generated or persisted as an entity identity.
  3. Negative values are invalid.
  4. Values greater than PORTABLE_ID_MAX are invalid even when a language or database type can represent them.
  5. An invalid value MUST be rejected before mutation; it MUST NOT be wrapped, truncated, clamped, or reinterpreted as signed bits.
  6. Primary-key and foreign-key columns SHOULD use a signed 64-bit integer type.
  7. 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​

BoundaryRequired representationValidation
Rust domain/runtimeu64 is permittedRequire 1 <= id && id <= i64::MAX as u64 before persistence or interchange
Java domain/runtimelong / LongRequire a positive value; never use negative long values as unsigned IDs
Go serviceint64 preferred; checked uint64 permitted internallyReject zero and values greater than math.MaxInt64
Python service/toolingintExplicitly require 1 <= id <= 2**63 - 1
SQL schemasigned BIGINT or exact equivalentAdd application validation; use a database check constraint where operationally compatible
Protobufint64Reject zero/negative values for persisted IDs
JSON/HTTPcanonical decimal stringParse 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:

  1. allocate uniquely within its documented scope;
  2. produce a value in 1..=PORTABLE_ID_MAX;
  3. validate the final composed value, including timestamp/node/sequence bit layouts;
  4. return an explicit error when the next value cannot be represented;
  5. avoid wrapping and saturating arithmetic;
  6. preserve database uniqueness constraints as the final collision defense;
  7. 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 u64 or Go uint64 to 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:

  1. Inventory primary keys, foreign keys, ID generators, importers, events, and public API schemas.
  2. Check stored values for null/zero/negative IDs and type mismatches.
  3. Confirm all numeric columns fit signed 64-bit semantics.
  4. Add boundary tests for 0, 1, 2^53 - 1, 2^53, 2^63 - 1, and 2^63.
  5. Test JSON round trips through an actual JavaScript consumer.
  6. Test Java, Rust, and any Go/Python integration using the same fixtures.
  7. Test generator concurrency and verify that an out-of-range result fails before SQL mutation.
  8. Record exceptions and legacy sentinel behavior before enforcing database constraints.