Skip to content

Validate client-minted record IDs, fix generator bugs (#55)#79

Merged
cuibonobo merged 2 commits into
mainfrom
claude/issue-55-design-review-ggq6ex
Jul 10, 2026
Merged

Validate client-minted record IDs, fix generator bugs (#55)#79
cuibonobo merged 2 commits into
mainfrom
claude/issue-55-design-review-ggq6ex

Conversation

@cuibonobo

Copy link
Copy Markdown
Member

Summary

  • Stack.create() and ScopedStack.create() now accept an optional client-minted id, validated as 12-char lowercase Crockford base-32 with no reserved _ prefix; a duplicate id surfaces as StackConflictError instead of a silent overwrite.
  • ScopedStack.create() additionally applies a configurable timestamp-skew check (StackOptions.idTimestampSkewMs, default 24h, pass null to disable) since a grantee is the untrusted actor who could otherwise mint an ID that forges its sort position. Unscoped Stack.create() stays format-only (full-trust context).
  • Fixes two independent bugs in id.ts: the random-suffix modulus was off by one (zzz was unreachable, slight bias), and a backward wall-clock step could silently break monotonic ID ordering — now clamped to the highest timestamp seen in-process.
  • Documents the validation rules and 400/409 semantics in docs/spec.md (new "Record IDs" section + error-table updates), giving a server implementation a normative contract to enforce against.

Addresses #55. Out of scope here (per the issue's own scoping): actual HTTP-layer enforcement lives in haverstack/server; conformance fixtures (#52) aren't built in this repo yet; the two explicitly-open questions from the issue (server-authoritative createdAt, auto-retry-on-409) are left open.

Test plan

  • pnpm --filter @haverstack/core typecheck
  • pnpm --filter @haverstack/core lint
  • pnpm --filter @haverstack/core test — 245 tests passing (30 new)
  • pnpm -r build and pnpm -r test across the full workspace — all green
  • prettier --check on all touched files

Generated by Claude Code

claude added 2 commits July 10, 2026 01:27
Stack.create() and ScopedStack.create() now accept an optional id,
validated against the format core already generates (12-char lowercase
Crockford base-32, no reserved "_" prefix) with duplicates surfacing as
StackConflictError. ScopedStack additionally enforces a configurable
clock-skew tolerance on the timestamp prefix, since a grantee is the
untrusted actor who could otherwise forge sort position.

Also fixes two independent bugs in id.ts: the random-suffix modulus was
off by one (making "zzz" unreachable and biasing the distribution), and
a backward clock step could silently break monotonic ID ordering — now
clamped to the highest timestamp seen.

Documents the validation rules and duplicate/skew semantics in
docs/spec.md so a server implementation has a normative contract to
enforce them against.
ScopedStack needs the configured clock-skew tolerance, but the only
caller of ScopedStack's constructor is Stack.asEntity() itself — so
thread the resolved value through the constructor there (Stack can
read its own private field) instead of adding a public getter that
nothing outside the library needs to read.
@cuibonobo cuibonobo merged commit 96f42bb into main Jul 10, 2026
5 checks passed
@cuibonobo cuibonobo deleted the claude/issue-55-design-review-ggq6ex branch July 10, 2026 02:06
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.

2 participants