Skip to content

Preserve nanosecond precision in TestClock wall time - #6950

Merged
tim-smart merged 3 commits into
mainfrom
audit/repro-testing-test-clock-nanos-precision
Aug 5, 2026
Merged

Preserve nanosecond precision in TestClock wall time#6950
tim-smart merged 3 commits into
mainfrom
audit/repro-testing-test-clock-nanos-precision

Conversation

@fubhy

@fubhy fubhy commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

TestClock reports an incorrect nanosecond value at valid safe-integer millisecond timestamps.

Important

This PR starts with focused failing reproduction tests. Add the implementation fix to this same branch; CI is expected to fail until that fix is included.

Wall-clock nanoseconds lose precision

Module: TestClock
Audit ID: core-s-z-testing-test-clock-nanosecond-precision
Severity / confidence: medium / high

What happens

TestClock reports an incorrect nanosecond value at valid safe-integer millisecond timestamps.

Why it happens

currentTimeNanosUnsafe multiplies the number timestamp before converting to bigint, so IEEE-754 rounding loses precision that conversion before multiplication would preserve.

Expected behavior

The inherited Clock accessors expose the current Unix time in milliseconds and nanoseconds, and setTime sets that current timestamp.

Relevant implementation

These links and excerpts are pinned to audit base c9b56ab507f224426ee8388dc450da447ec4715f.

View problematic code at packages/effect/src/testing/TestClock.ts:253-258
  function currentTimeMillisUnsafe(): number {
    return currentTimestamp
  }

  function currentTimeNanosUnsafe(): bigint {
    return BigInt(Math.floor(currentTimestamp * 1000000))

View exact lines on GitHub

Reproduction

NODE_OPTIONS=--preserve-symlinks pnpm test --run packages/effect/test/TestClock.test.ts -t "preserves wall-clock nanoseconds"

Observed failure: result is 64 ns low

Implementation handoff

The initial reproduction tests on this branch are the regression specification for the implementation fix that should follow in this PR.

  1. Start with the pinned implementation excerpts and the Why it happens analysis above.
  2. Change the implementation so it satisfies the stated Expected behavior; do not weaken or remove the reproduction assertions.
  3. Run the focused reproduction command(s) and confirm the observed failures become passing tests:
NODE_OPTIONS=--preserve-symlinks pnpm test --run packages/effect/test/TestClock.test.ts -t "preserves wall-clock nanoseconds"
  1. Run the affected package's existing tests, then the repository lint and type checks before requesting review.

Audit provenance

  • Audit base: c9b56ab507f224426ee8388dc450da447ec4715f
  • Reproduction base: c9b56ab507f224426ee8388dc450da447ec4715f
  • Findings: core-s-z-testing-test-clock-nanosecond-precision
  • Initial patch: focused reproduction tests; implementation fix pending

Closes EFF-396

@fubhy fubhy added the audit Findings originating from the Effect runtime correctness audit label Aug 4, 2026
@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7a13dfd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 30 packages
Name Type
effect Patch
@effect/ai-anthropic Patch
@effect/ai-openai Patch
@effect/ai-openai-compat Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/openapi-generator Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-deno Patch
@effect/platform-node Patch
@effect/platform-node-shared Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/vitest Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@effect-slopcop effect-slopcop Bot added 4.0 bug Something isn't working labels Aug 4, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

This PR currently adds only the regression test; the implementation fix in packages/effect/src/testing/TestClock.ts is still outstanding.

Reviewed changes

  • Added a focused regression test in packages/effect/test/TestClock.test.ts that asserts nanosecond precision for a large wall-clock timestamp.

⚠️ Implementation fix still needed

The diff only touches the test file. The bug is in packages/effect/src/testing/TestClock.ts at currentTimeNanosUnsafe():

function currentTimeNanosUnsafe(): bigint {
  return BigInt(Math.floor(currentTimestamp * 1000000))
}

Because currentTimestamp * 1_000_000 is computed as a number before the BigInt cast, large millisecond timestamps lose low-order nanosecond bits to IEEE-754 rounding. I verified the new test reproduces this: for timestamp 1_000_000_000_001, actual is 1000000000000999936n instead of the expected 1000000000001000000n.

The likely fix is to convert to BigInt before scaling:

function currentTimeNanosUnsafe(): bigint {
  return BigInt(Math.floor(currentTimestamp)) * 1_000_000n
}

This preserves the existing "floor to whole milliseconds" behaviour shown in the neighbouring test, while avoiding precision loss for any timestamp within Number.MAX_SAFE_INTEGER milliseconds.

Matching adjacent tests is welcome, but there is no implementation change to review yet. Please push the TestClock.ts fix to this same branch and I’ll re-review.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Fix it ➔View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

@tim-smart
tim-smart enabled auto-merge (squash) August 4, 2026 21:59

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Since the prior Pullfrog review at d9ee962a, the branch added the missing implementation fix and a changeset:

  • Fixed TestClock.currentTimeNanosUnsafe() in packages/effect/src/testing/TestClock.ts:258 to convert the millisecond timestamp to BigInt before scaling (BigInt(Math.floor(currentTimestamp)) * BigInt(1_000_000)), eliminating IEEE-754 rounding loss for large safe-integer millisecond timestamps.
  • Added a changeset (.changeset/precise-clocks-rest.md) describing the patch-level effect fix.

The regression test already on the branch (packages/effect/test/TestClock.test.ts:64-70) now passes, as does the full TestClock.test.ts suite. pnpm lint and pnpm check also pass. This resolves the outstanding request from the previous review.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

# Conflicts:
#	packages/effect/src/testing/TestClock.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Since the prior Pullfrog review at e7a848f, the branch picked up the merge of main (7a13dfd). The merge commit did not change the substantive implementation. The incremental diff shows the intended final form of the fix:

  • Extracted millisToNanos helper in packages/effect/src/testing/TestClock.ts:208-212, splitting the millisecond timestamp into whole and fractional parts and converting each to BigInt before combining them. This preserves nanosecond precision for large safe-integer timestamps while keeping the existing floor-to-whole-millisecond semantics.
  • Updated setTime/adjust wall-clock path in packages/effect/src/testing/TestClock.ts:352 to use millisToNanos(timestamp) instead of BigInt(Math.floor(timestamp * 1_000_000)).
  • Regression test at packages/effect/test/TestClock.test.ts:64-70 covers the large-timestamp case, and the neighbouring fractional-millisecond test verifies flooring behaviour.
  • Changeset .changeset/precise-clocks-rest.md describes the patch-level effect fix.

Validation: pnpm --filter effect test --run test/TestClock.test.ts passes; pnpm lint passes. The prior implementation fix addressed all outstanding review feedback.

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | View workflow run | Using Kimi K2 (free via Pullfrog for OSS) | 𝕏

@tim-smart
tim-smart merged commit 3c5e429 into main Aug 5, 2026
19 of 20 checks passed
@tim-smart
tim-smart deleted the audit/repro-testing-test-clock-nanos-precision branch August 5, 2026 00:08
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
basic.ts 7.06 KB 7.06 KB 0.00 KB (0.00%)
batching.ts 9.86 KB 9.86 KB 0.00 KB (0.00%)
brand.ts 6.34 KB 6.34 KB 0.00 KB (0.00%)
cache.ts 10.71 KB 10.71 KB 0.00 KB (0.00%)
config.ts 20.60 KB 20.60 KB 0.00 KB (0.00%)
differ.ts 20.20 KB 20.20 KB 0.00 KB (0.00%)
http-client.ts 21.53 KB 21.53 KB 0.00 KB (0.00%)
logger.ts 10.84 KB 10.84 KB 0.00 KB (0.00%)
metric.ts 8.98 KB 8.98 KB 0.00 KB (0.00%)
optic.ts 7.18 KB 7.18 KB 0.00 KB (0.00%)
pubsub.ts 14.99 KB 14.99 KB 0.00 KB (0.00%)
queue.ts 11.66 KB 11.66 KB 0.00 KB (0.00%)
schedule.ts 10.83 KB 10.83 KB 0.00 KB (0.00%)
schema-class.ts 19.14 KB 19.14 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 28.96 KB 28.96 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 25.29 KB 25.29 KB 0.00 KB (0.00%)
schema-string-transformation.ts 13.38 KB 13.38 KB 0.00 KB (0.00%)
schema-string.ts 10.94 KB 10.94 KB 0.00 KB (0.00%)
schema-template-literal.ts 15.17 KB 15.17 KB 0.00 KB (0.00%)
schema-toArbitraryLazy.ts 21.94 KB 21.94 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 24.34 KB 24.34 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 19.18 KB 19.18 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 19.01 KB 19.01 KB 0.00 KB (0.00%)
schema-toFormatter.ts 18.87 KB 18.87 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 22.60 KB 22.60 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 19.52 KB 19.52 KB 0.00 KB (0.00%)
schema.ts 18.41 KB 18.41 KB 0.00 KB (0.00%)
stm.ts 12.63 KB 12.63 KB 0.00 KB (0.00%)
stream.ts 9.80 KB 9.80 KB 0.00 KB (0.00%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 audit Findings originating from the Effect runtime correctness audit bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants