feat: add command domain invariants

This commit is contained in:
2026-08-31 08:09:28 +00:00
parent e23737de0d
commit 49627a9f5a
14 changed files with 869 additions and 1 deletions
+124
View File
@@ -0,0 +1,124 @@
// Package domain contains transport- and storage-independent RVBox rules.
package domain
import (
"crypto/rand"
"errors"
"fmt"
"io"
"sync"
"time"
googleuuid "github.com/google/uuid"
)
var (
ErrInvalidUUIDv7 = errors.New("invalid canonical UUIDv7")
ErrUUIDTimeRange = errors.New("UUIDv7 time is outside its 48-bit range")
)
const maxUUIDv7UnixMilli = uint64(1<<48 - 1)
// UUID is the canonical binary form used by RVBox domain and storage code.
type UUID [16]byte
// String returns the lowercase RFC 9562 canonical representation.
func (u UUID) String() string {
return googleuuid.UUID(u).String()
}
// ParseUUIDv7 accepts only the lowercase, hyphenated RFC 9562 form.
func ParseUUIDv7(value string) (UUID, error) {
if len(value) != 36 {
return UUID{}, fmt.Errorf("%w: expected 36 characters", ErrInvalidUUIDv7)
}
parsed, err := googleuuid.Parse(value)
if err != nil || parsed.String() != value {
return UUID{}, fmt.Errorf("%w: non-canonical encoding", ErrInvalidUUIDv7)
}
if parsed.Version() != 7 || parsed.Variant() != googleuuid.RFC4122 {
return UUID{}, fmt.Errorf("%w: expected RFC variant and version 7", ErrInvalidUUIDv7)
}
return UUID(parsed), nil
}
// UUIDv7Generator emits lexically increasing UUIDv7 values even when the wall
// clock stalls or moves backwards. Calls are safe from multiple goroutines.
type UUIDv7Generator struct {
mu sync.Mutex
now func() time.Time
entropy io.Reader
started bool
lastMS uint64
sequence uint16
}
// NewUUIDv7Generator creates a generator. Nil arguments select the system clock
// and crypto/rand.Reader. Supplying dependencies keeps boundary tests hermetic.
func NewUUIDv7Generator(now func() time.Time, entropy io.Reader) *UUIDv7Generator {
if now == nil {
now = time.Now
}
if entropy == nil {
entropy = rand.Reader
}
return &UUIDv7Generator{now: now, entropy: entropy}
}
var defaultUUIDv7Generator = NewUUIDv7Generator(nil, nil)
// NewUUIDv7 uses the process-wide generator.
func NewUUIDv7() (UUID, error) {
return defaultUUIDv7Generator.New()
}
// New returns a canonical RFC 9562 UUIDv7.
func (g *UUIDv7Generator) New() (UUID, error) {
g.mu.Lock()
defer g.mu.Unlock()
instant := g.now()
millis := instant.UnixMilli()
if millis < 0 || uint64(millis) > maxUUIDv7UnixMilli {
return UUID{}, ErrUUIDTimeRange
}
var random [10]byte
if _, err := io.ReadFull(g.entropy, random[:]); err != nil {
return UUID{}, fmt.Errorf("read UUIDv7 entropy: %w", err)
}
wallMS := uint64(millis)
logicalMS := wallMS
sequence := uint16(random[0]&0x0f)<<8 | uint16(random[1])
if g.started && wallMS <= g.lastMS {
logicalMS = g.lastMS
sequence = g.sequence + 1
if sequence > 0x0fff {
if logicalMS == maxUUIDv7UnixMilli {
return UUID{}, ErrUUIDTimeRange
}
logicalMS++
sequence = 0
}
}
var result UUID
result[0] = byte(logicalMS >> 40)
result[1] = byte(logicalMS >> 32)
result[2] = byte(logicalMS >> 24)
result[3] = byte(logicalMS >> 16)
result[4] = byte(logicalMS >> 8)
result[5] = byte(logicalMS)
result[6] = 0x70 | byte(sequence>>8)
result[7] = byte(sequence)
result[8] = 0x80 | random[2]&0x3f
copy(result[9:], random[3:])
g.started = true
g.lastMS = logicalMS
g.sequence = sequence
return result, nil
}