feat: add configuration, logging, metrics, scheduler and storage foundation
- config: defaults -> JSON file -> NOEMA_* env with validation and redaction - observability: slog with secret-redacting handler; Prometheus registry with the metric set from the plan (events, workspace, memory, LLM, tools, queues) - scheduler: supervised Group and bounded Pool so no goroutine is unowned - storage: pgx pool with statement timeout, embedded migration runner (advisory lock, checksums, up/down/status), per-test schema helper - migrations/0001: append-only guard trigger, users, sessions, api tokens, recovery codes, audit log, settings - pkg/ids: UUIDv7 with monotonic counter
This commit is contained in:
+112
@@ -0,0 +1,112 @@
|
||||
// Package ids generates UUIDv7 identifiers.
|
||||
//
|
||||
// UUIDv7 is used for every Noema record because it is globally unique and
|
||||
// time-ordered, which keeps event indexes dense and makes ids sort by creation
|
||||
// time without a second column.
|
||||
package ids
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ID is a 128-bit UUID in canonical textual form.
|
||||
type ID string
|
||||
|
||||
// Nil is the zero ID.
|
||||
const Nil ID = ""
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
lastMS int64
|
||||
lastSeq uint16
|
||||
randPool = rand.Reader
|
||||
)
|
||||
|
||||
// New returns a new UUIDv7. Within the same millisecond ids are made strictly
|
||||
// increasing by a 12-bit counter so ordering is stable even under bursts.
|
||||
func New() ID {
|
||||
var b [16]byte
|
||||
if _, err := randPool.Read(b[6:]); err != nil {
|
||||
panic("ids: crypto/rand unavailable: " + err.Error()) // no safe fallback exists
|
||||
}
|
||||
ms := time.Now().UnixMilli()
|
||||
mu.Lock()
|
||||
if ms <= lastMS {
|
||||
ms = lastMS
|
||||
lastSeq++
|
||||
if lastSeq >= 1<<12 { // counter overflow: borrow a millisecond
|
||||
lastMS++
|
||||
ms = lastMS
|
||||
lastSeq = 0
|
||||
}
|
||||
} else {
|
||||
lastMS = ms
|
||||
lastSeq = uint16(b[6])<<4 | uint16(b[7])>>4 // random start, per RFC 9562 method 1
|
||||
lastSeq &= 0x7ff // leave headroom for increments
|
||||
}
|
||||
seq := lastSeq
|
||||
mu.Unlock()
|
||||
|
||||
b[0] = byte(ms >> 40)
|
||||
b[1] = byte(ms >> 32)
|
||||
b[2] = byte(ms >> 24)
|
||||
b[3] = byte(ms >> 16)
|
||||
b[4] = byte(ms >> 8)
|
||||
b[5] = byte(ms)
|
||||
b[6] = 0x70 | byte(seq>>8) // version 7 + high 4 bits of counter
|
||||
b[7] = byte(seq) // low 8 bits of counter
|
||||
b[8] = 0x80 | (b[8] & 0x3f) // RFC 4122 variant
|
||||
return ID(format(b))
|
||||
}
|
||||
|
||||
func format(b [16]byte) string {
|
||||
var out [36]byte
|
||||
hex.Encode(out[0:8], b[0:4])
|
||||
out[8] = '-'
|
||||
hex.Encode(out[9:13], b[4:6])
|
||||
out[13] = '-'
|
||||
hex.Encode(out[14:18], b[6:8])
|
||||
out[18] = '-'
|
||||
hex.Encode(out[19:23], b[8:10])
|
||||
out[23] = '-'
|
||||
hex.Encode(out[24:36], b[10:16])
|
||||
return string(out[:])
|
||||
}
|
||||
|
||||
// Parse validates the canonical textual form and returns it lower-cased.
|
||||
func Parse(s string) (ID, error) {
|
||||
if len(s) != 36 || s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return Nil, errors.New("ids: invalid uuid format")
|
||||
}
|
||||
var b [16]byte
|
||||
src := s[0:8] + s[9:13] + s[14:18] + s[19:23] + s[24:36]
|
||||
if _, err := hex.Decode(b[:], []byte(src)); err != nil {
|
||||
return Nil, errors.New("ids: invalid uuid hex")
|
||||
}
|
||||
return ID(format(b)), nil
|
||||
}
|
||||
|
||||
// Time extracts the millisecond timestamp embedded in a UUIDv7. It returns the
|
||||
// zero time for ids that are not version 7.
|
||||
func (id ID) Time() time.Time {
|
||||
if len(id) != 36 {
|
||||
return time.Time{}
|
||||
}
|
||||
var b [16]byte
|
||||
src := string(id[0:8] + id[9:13] + id[14:18] + id[19:23] + id[24:36])
|
||||
if _, err := hex.Decode(b[:], []byte(src)); err != nil || b[6]>>4 != 7 {
|
||||
return time.Time{}
|
||||
}
|
||||
ms := int64(b[0])<<40 | int64(b[1])<<32 | int64(b[2])<<24 | int64(b[3])<<16 | int64(b[4])<<8 | int64(b[5])
|
||||
return time.UnixMilli(ms)
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (id ID) String() string { return string(id) }
|
||||
|
||||
// IsZero reports whether the id is unset.
|
||||
func (id ID) IsZero() bool { return id == Nil }
|
||||
@@ -0,0 +1,49 @@
|
||||
package ids
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewIsOrderedUniqueAndParsable(t *testing.T) {
|
||||
const n = 5000
|
||||
seen := make(map[ID]struct{}, n)
|
||||
prev := ID("")
|
||||
for i := 0; i < n; i++ {
|
||||
id := New()
|
||||
if _, dup := seen[id]; dup {
|
||||
t.Fatalf("duplicate id %s", id)
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
if id <= prev {
|
||||
t.Fatalf("ids not increasing: %s after %s", id, prev)
|
||||
}
|
||||
prev = id
|
||||
if p, err := Parse(string(id)); err != nil || p != id {
|
||||
t.Fatalf("parse round trip failed for %s: %v", id, err)
|
||||
}
|
||||
if id[14] != '7' {
|
||||
t.Fatalf("not version 7: %s", id)
|
||||
}
|
||||
}
|
||||
ids := make([]string, 0, n)
|
||||
for id := range seen {
|
||||
ids = append(ids, string(id))
|
||||
}
|
||||
sort.Strings(ids)
|
||||
if d := time.Since(ID(ids[0]).Time()); d < 0 || d > time.Minute {
|
||||
t.Fatalf("embedded time implausible: %v", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRejectsGarbage(t *testing.T) {
|
||||
for _, s := range []string{"", "not-a-uuid", "0192b7e4-3c7a-7c3e-8f1e-0123456789zz", "0192b7e43c7a7c3e8f1e0123456789ab"} {
|
||||
if _, err := Parse(s); err == nil {
|
||||
t.Errorf("expected error for %q", s)
|
||||
}
|
||||
}
|
||||
if _, err := Parse("0192B7E4-3C7A-7C3E-8F1E-0123456789AB"); err != nil {
|
||||
t.Errorf("upper case should parse: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user