Skip to content

P0: ship .d.ts + deterministic export - #1

Merged
ViperJuice merged 1 commit into
mainfrom
gb/p0-dts-determinism
Jun 21, 2026
Merged

ViperJuice merged 1 commit into
mainfrom
gb/p0-dts-determinism

Conversation

@ViperJuice

Copy link
Copy Markdown
Collaborator

P0 — Deterministic export + ship types

Implements Phase P0 from the spec audit roadmap (roadmaps/graphbase.md) and the consolidated review (code-reviews/graphbase/CONSOLIDATED.md). Two gating issues, both fixed, with enforcement so they can't silently regress.

1. DTS / typecheck build break — types silently did not ship

src/core/Graph.ts:18 imported TraverseDirection as a value but used it only inside a JSDoc fenced @example. With noUnusedLocals: true, tsc --noEmit exited 2 (TS6133), the tsup DTS phase failed, and no dist/index.d.ts was produced — yet tsup exits 0, so naive CI shipped an untyped package despite package.json declaring "types": "dist/index.d.ts".

  • Dropped the unused value import (the JSDoc prose needs no symbol resolution).
  • Build now asserts the declaration file ships. build runs a check:dts postbuild step (fs.accessSync on dist/index.d.ts) that fails the build if the d.ts is missing — necessary because tsup swallows DTS errors. Added .github/workflows/ci.yml (npm ci / typecheck / test / build + an explicit d.ts existence assertion).

2. Deterministic / canonical export — was insertion-order + wall-clock dependent

src/bundle/exporter.ts preserved Map insertion order and injected new Date().toISOString() into metadata, so two semantically identical graphs produced different bytes / hashes.

  • Sort nodes by id, edges by (source, type, target, id), schemas by type key.
  • New src/bundle/canonicalize.ts recursively canonicalizes object-key ordering (including nested property and schema shapes). It is a deliberately small, isolated seam intended to be replaced by the spine's canon v1. It also deep-clones, so the returned bundle no longer shares references with storage (defensive at the boundary).
  • Dropped the injected new Date() — the hashed envelope must not depend on production time. createdAt is now caller-supplied or absent; stale JSDoc updated.
  • Added a round-trip determinism suite: the same logical graph built in two different insertion orders (including nested property key order) exports to byte-identical JSON. Verified non-vacuous — temporarily removing canonicalize turns the test red.

Scope notes (beyond the literal one-liner ask, surfaced explicitly)

  • Removed the broken lint script (no ESLint config present; roadmap-sanctioned). eslint devDeps left untouched.
  • Added the full CI workflow (the team-lead asked only for a build assertion; check:dts already satisfies that — the workflow is the roadmap's "CI-verified" wording, additive).
  • Synced the stale package name in package-lock.json (core-graph → graphbase).
  • canonicalize/canonicalStringify are exported from ./bundle but intentionally not re-exported at the package root — the canonical-serialization surface is provisional pending spine canon v1 and the P2 CanonicalGraphBundle envelope, so this PR avoids committing to it as a root-level public contract.

Exit gate — met

  • ✅ Byte-identical export across differently-ordered builds (nested shapes included); no wall-clock in hashed content.
  • ✅ dist/index.d.ts produced (79KB; canonicalize/canonicalStringify present in it) and CI-asserted.
  • ✅ tsc --noEmit clean.
  • ✅ Tests: 356 passing. 347 of the original 348 unchanged; the lone rewrite (auto-generate createdAt) is required because P0-2 removes wall-clock injection; +8 net new tests.

Does not include P1/P2 (import safety, facade updates, vendored-copy consolidation).

🤖 Generated with Claude Code

https://claude.ai/code/session_01G6NtqFtBPRyge7mZuLyrbs

Fixes two P0 issues from the spec audit (roadmaps/graphbase.md, code-reviews/graphbase/CONSOLIDATED.md).

1. DTS / typecheck build break (types silently did not ship)
   - src/core/Graph.ts: TraverseDirection was imported as a *value* but used
     only inside a JSDoc fenced @example, tripping noUnusedLocals (TS6133).
     tsc --noEmit exited 2, the tsup DTS phase failed, and no dist/index.d.ts
     was produced -- yet tsup exits 0, so naive CI shipped an untyped package.
     Dropped the unused value import (the JSDoc text needs no symbol).
   - Build now asserts the declaration file: `build` runs a `check:dts`
     postbuild step (fs.accessSync on dist/index.d.ts) so the type contract is
     enforced even though tsup swallows DTS errors. Added .github/workflows/ci.yml
     (npm ci / typecheck / test / build + explicit d.ts existence assertion).

2. Deterministic / canonical export (was insertion-order + wall-clock dependent)
   - src/bundle/exporter.ts: sort nodes by id, edges by (source,type,target,id),
     schemas by type key; recursively canonicalize object-key order via a new
     small src/bundle/canonicalize.ts (the seam intended to be replaced by the
     spine's canon v1). canonicalize also deep-clones, so the bundle no longer
     shares references with storage.
   - Dropped the injected `new Date().toISOString()` from metadata: the hashed
     envelope must not depend on production time. createdAt is now caller-supplied
     or absent. Updated the stale JSDoc that promised auto-generation.
   - Added a round-trip determinism suite: same logical graph built in two
     different insertion orders (including nested property key order) exports to
     byte-identical JSON; verified non-vacuous (removing canonicalize turns the
     test red).

Misc: removed the broken `lint` script (no ESLint config present); synced the
stale package name in package-lock.json (core-graph -> graphbase).

Tests: 356 passing. 347 of the original 348 unchanged; the lone rewrite
(`auto-generate createdAt`) is required because P0-2 removes wall-clock
injection; +8 net new tests. tsc --noEmit clean; dist/index.d.ts produced (79KB,
exports verified).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6NtqFtBPRyge7mZuLyrbs
@ViperJuice
ViperJuice merged commit b650bf0 into main Jun 21, 2026
1 check passed
@ViperJuice
ViperJuice deleted the gb/p0-dts-determinism branch June 21, 2026 04:22
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