{"record":{"id":"c9f35270cafea9b3","repo":"clockworklabs/SpacetimeDB","slug":"fromcounterv7-timestamp-before-unix-epoch","errorCode":null,"errorMessage":"`fromCounterV7` `timestamp` before unix epoch","messagePattern":"`fromCounterV7` `timestamp` before unix epoch","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/bindings-typescript/src/lib/uuid.ts","lineNumber":167,"sourceCode":"   *   uuid.toString() === \"0000647e-5180-7000-8000-000200000000\"\n   * );\n   * ```\n   */\n  static fromCounterV7(\n    counter: { value: number },\n    now: Timestamp,\n    randomBytes: Uint8Array\n  ): Uuid {\n    if (randomBytes.length !== 4) {\n      throw new Error('`fromCounterV7` requires `randomBytes.length == 4`');\n    }\n\n    if (counter.value < 0) {\n      throw new Error('`fromCounterV7` uuid `counter` must be non-negative');\n    }\n\n    if (now.__timestamp_micros_since_unix_epoch__ < 0) {\n      throw new Error('`fromCounterV7` `timestamp` before unix epoch');\n    }\n\n    // 31-bit monotonic counter with wraparound\n    const counterVal = counter.value;\n    counter.value = (counterVal + 1) & 0x7fff_ffff;\n\n    // 48-bit unix timestamp (ms)\n    const tsMs = now.toMillis() & 0xffff_ffff_ffffn;\n\n    const bytes = new Uint8Array(16);\n\n    // unix_ts_ms (48 bits)\n    bytes[0] = Number((tsMs >> 40n) & 0xffn);\n    bytes[1] = Number((tsMs >> 32n) & 0xffn);\n    bytes[2] = Number((tsMs >> 24n) & 0xffn);\n    bytes[3] = Number((tsMs >> 16n) & 0xffn);\n    bytes[4] = Number((tsMs >> 8n) & 0xffn);\n    bytes[5] = Number(tsMs & 0xffn);","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/6dee26c6efc2856793e12b148a59742964f5d783/crates/bindings-typescript/src/lib/uuid.ts#L149-L185","documentation":"A UUIDv7 layout begins with a 48-bit unix-millisecond timestamp, which cannot represent times before 1970-01-01. fromCounterV7 checks now.__timestamp_micros_since_unix_epoch__ < 0 and rejects any pre-epoch Timestamp before encoding.","triggerScenarios":"Passing a Timestamp built from negative milliseconds/micros: a pre-1970 Date, date arithmetic that underflows (subtracting a duration from a small timestamp), or a mocked clock returning negative time.","commonSituations":"Test fixtures with historical dates; timestamps computed as differences from an epoch that go negative; devices or CI containers with misconfigured system clocks; data migrated from systems using non-unix epochs.","solutions":["Build the Timestamp from the current time (e.g. derived from Date.now()) instead of a computed historical value","Validate micros-since-epoch >= 0 before calling fromCounterV7","Clamp incoming times to the unix epoch minimum at your data boundary and log when clamping occurs"],"exampleFix":"// before\nconst now = tsFromMicros(-1_000_000); // before unix epoch\nconst id = Uuid.fromCounterV7(counter, now, rand); // throws\n\n// after\nconst now = tsFromMicros(Date.now() * 1000);\nconst id = Uuid.fromCounterV7(counter, now, rand);","handlingStrategy":"validation","validationCode":"if (now.__timestamp_micros_since_unix_epoch__ < 0) {\n  throw new Error('timestamp must be at or after the unix epoch');\n}\nconst id = Uuid.fromCounterV7(counter, now, randomBytes);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Derive the Timestamp from the current time, not from subtractions that can go negative","Clamp pre-epoch inputs at your data boundary and log the clamp","Keep clock mocks in tests at realistic post-1970 values"],"tags":["uuid","typescript","timestamp","argument-validation"],"backgroundTag":"timestamp-out-of-range","analyzedSha":"6dee26c6efc2856793e12b148a59742964f5d783","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}