Validate client-minted record IDs, fix generator bugs (#55)#79
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stack.create()andScopedStack.create()now accept an optional client-mintedid, validated as 12-char lowercase Crockford base-32 with no reserved_prefix; a duplicateidsurfaces asStackConflictErrorinstead of a silent overwrite.ScopedStack.create()additionally applies a configurable timestamp-skew check (StackOptions.idTimestampSkewMs, default 24h, passnullto disable) since a grantee is the untrusted actor who could otherwise mint an ID that forges its sort position. UnscopedStack.create()stays format-only (full-trust context).id.ts: the random-suffix modulus was off by one (zzzwas unreachable, slight bias), and a backward wall-clock step could silently break monotonic ID ordering — now clamped to the highest timestamp seen in-process.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-authoritativecreatedAt, auto-retry-on-409) are left open.Test plan
pnpm --filter @haverstack/core typecheckpnpm --filter @haverstack/core lintpnpm --filter @haverstack/core test— 245 tests passing (30 new)pnpm -r buildandpnpm -r testacross the full workspace — all greenprettier --checkon all touched filesGenerated by Claude Code