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.