Skip to content

fix: pre-v0.6.0 release hardening#126

Open
buffcode wants to merge 11 commits intomasterfrom
fix/pre-v6-release
Open

fix: pre-v0.6.0 release hardening#126
buffcode wants to merge 11 commits intomasterfrom
fix/pre-v6-release

Conversation

@buffcode
Copy link
Copy Markdown
Owner

Summary

Pre-release hardening and test scaffolding for v0.6.0. Nine atomic commits grouped by concern:

Core library fixes

  • Move cache / singleton state onto class instances (6cbb103) — fixes cross-contamination between independent new NtpTimeSync(...) constructions
  • Rewrite createPacket with Buffer.writeBigUInt64BE (6b2b18f) — removes deprecated substr usage, ends per-packet string allocations, bit-accurate timestamps
  • Narrow NTP reply field validation in acceptResponse (4602083) — replaces ! assertions on transmitTimestamp / receiveTimestamp / precision
  • Register message listener before send (f8377c3) — closes a race where fast replies were missed
  • Increment retry on no-progress rounds (fdc1924) — previously only zero-result rounds triggered retry
  • Harden socket and listener cleanup (893ed71) — wraps sync throws in createPacket, uses removeAllListeners before close

Public API

  • Switch src/index.ts from export * to explicit named exports; deep-freeze NtpTimeSyncDefaultOptions (5c1032e). Surface area unchanged (5 names), runtime mutation now blocked.

Tests

  • 22 unit tests covering packet construction, response validation, sample math, instance isolation, retry bounds, and socket-leak regression (4091071). Runs in ~1s, fully offline via in-process UDP fixture.

CI

  • New verify-package job runs npm pack --dry-run, publint, and @arethetypeswrong/cli on Node 22 (1d20851). Catches exports/files/types regressions before publish.
  • This commit bumps repository.url with git+ prefix for publint compliance.

Test plan

  • yarn build exits 0
  • yarn test:unit — 22/22 passing
  • yarn verify:package — pack + publint + attw all green
  • yarn test:integration:cjs and yarn test:integration:esm — passing
  • CI matrix green across all advertised Node versions
  • Manual: bump version in package.json to 0.6.0 before tagging

buffcode and others added 10 commits April 22, 2026 10:05
Add pre-publish artifact verification to catch broken files/exports,
wrong entry paths, or incorrect types resolution before 0.6.0 ships.
Runs as a dedicated verify-package job on Node 22 LTS (artifact check,
not runtime check) that needs the build matrix to succeed first.

- npm pack --dry-run: confirm tarball manifest is producible
- publint: lint package.json exports/main/module/types/files
- @arethetypeswrong/cli: verify TS consumers (node16/bundler) resolve
  working types for both CJS and ESM entrypoints

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace Array(48) + pad()/parseInt()/substr() chain with Buffer.alloc(48)
plus direct byte assignment and buf.writeBigUInt64BE for the origin and
transmit timestamps. Drops the now-unused pad() helper.

The new implementation splits the NTP 64-bit timestamp into whole seconds
and fractional seconds before combining via BigInt so the fractional
portion no longer loses low-order bits to the Number mantissa during the
legacy (seconds * 2**32) float round-trip.
acceptResponse previously validated only originTimestamp, leaving
transmitTimestamp, receiveTimestamp, and precision as non-null-asserted
in collectSamples. Add explicit undefined checks for those fields in
acceptResponse (throwing the same Format error pattern as existing
checks) so malformed replies are rejected before they become samples,
and drop the ! assertions in collectSamples. A guard in the forEach
provides type narrowing that is now backed by the validator.
The previous ordering registered client.once("message") inside the
send() callback, so an NTP reply that arrived after the packet was on
the wire but before Node fired the send callback would hit no listener
and be silently dropped; the caller would then wait the full
replyTimeout before rejecting. Attach the message listener up front and
keep hasFinished/timeoutHandler guards so the timeout, send errors, and
socket errors still short-circuit the promise correctly.
The retry counter only advanced when ntpResults was still empty, so any
partial success that produced fewer than numSamples usable packets
(e.g. one server responding, the rest persistently timing out) could
spin the do/while loop indefinitely because retry stayed at 0. Snapshot
the result count before each round and bump retry when the post-round
count is unchanged, keeping the existing retry < 3 cap.
Two related leaks: (1) dgram.send can throw synchronously when the
socket is in a bad state, and the throw was not caught, so the timeout
handler, UDP socket, and registered listeners all leaked and the
surrounding promise was never rejected; wrap the send call so we clear
the timeout, run NtpTimeSync.cleanup, and reject exactly once. (2)
NtpTimeSync.cleanup only called client.close(), leaving "message" and
"error" listeners attached on an abandoned socket where a late event
could still fire resolve/reject or prevent the process from exiting;
call client.removeAllListeners() before close().
@buffcode buffcode self-assigned this Apr 22, 2026
….lock

The cache key used hashFiles('**/yarn.lock'), but yarn.lock is
gitignored and thus absent from CI checkouts. hashFiles returned an
empty hash, collapsing the key to a constant (e.g. Linux-modules--22)
that never invalidated across commits.

This caused verify-package to restore a stale node_modules from
before commit 1d20851 and skip install (cache-hit guard), so newly
added devDependencies publint and @arethetypeswrong/cli were missing:
/bin/sh: 1: publint: not found (exit 127).

Switch all three jobs (build, verify-package, deno) to key on
package.json, which is tracked and changes when deps change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant