Add Phase 1 foundation: Go module, core types, DB infrastructure, config

Establish the project foundation with three packages:
- core: shared types (Header, Metadata, Value, ObjectType, UUID generation)
- db: SQLite migration framework, connection management (WAL, FK, busy
  timeout), transaction helpers (StartTX/EndTX), time conversion
- config: runtime configuration (DB path, blob store, Minio, gRPC addr)

Includes initial schema migration (001_initial.sql) with 13 tables covering
shared infrastructure, bibliographic data, and artifact repository. Full test
coverage for all packages, strict linting (.golangci.yaml), and Makefile.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-21 09:46:08 -07:00
parent 98990c6d76
commit bb2c7f7ef3
13 changed files with 1038 additions and 4 deletions

111
core/core.go Normal file
View File

@@ -0,0 +1,111 @@
// Package core defines shared types used across both the artifact repository
// and knowledge graph pillars.
package core
import (
"errors"
"time"
"github.com/google/uuid"
)
// ObjectType enumerates the kinds of persistent objects in the system.
type ObjectType string
const (
ObjectTypeArtifact ObjectType = "artifact"
ObjectTypeSnapshot ObjectType = "snapshot"
ObjectTypeCitation ObjectType = "citation"
ObjectTypePublisher ObjectType = "publisher"
ObjectTypeNode ObjectType = "node"
ObjectTypeCell ObjectType = "cell"
)
// Header is attached to every persistent object.
type Header struct {
Meta Metadata
Categories []string
Tags []string
ID string
Type ObjectType
Created int64
Modified int64
}
// NewHeader creates a Header with a new UUID and current timestamps.
func NewHeader(objType ObjectType) Header {
now := time.Now().UTC().Unix()
return Header{
ID: NewUUID(),
Type: objType,
Created: now,
Modified: now,
Meta: Metadata{},
}
}
// Touch updates the Modified timestamp to now.
func (h *Header) Touch() {
h.Modified = time.Now().UTC().Unix()
}
const (
// ValueTypeUnspecified is used when the value type hasn't been explicitly
// set. It should be interpreted as a string.
ValueTypeUnspecified = "UNSPECIFIED"
// ValueTypeString is used when the value type should be explicitly
// interpreted as a string.
ValueTypeString = "String"
// ValueTypeInt is used when the value should be interpreted as an integer.
ValueTypeInt = "Int"
)
// Value stores a typed key-value entry. Contents is always stored as a string;
// the Type field tells consumers how to interpret it.
type Value struct {
Contents string
Type string
}
// Val creates a new Value with an unspecified type.
func Val(contents string) Value {
return Value{Contents: contents, Type: ValueTypeUnspecified}
}
// Vals creates a new Value with a string type.
func Vals(contents string) Value {
return Value{Contents: contents, Type: ValueTypeString}
}
// Metadata holds additional information that isn't explicitly part of a data
// definition. Keys are arbitrary strings; values carry type information.
type Metadata map[string]Value
// NewUUID returns a new random UUID string.
func NewUUID() string {
return uuid.NewString()
}
// ErrNoID is returned when a lookup is done on a struct that has no identifier.
var ErrNoID = errors.New("missing UUID identifier")
// MapFromList converts a string slice into a map[string]bool for set-like
// membership testing.
func MapFromList(list []string) map[string]bool {
m := make(map[string]bool, len(list))
for _, s := range list {
m[s] = true
}
return m
}
// ListFromMap converts a map[string]bool (set) back to a sorted slice.
func ListFromMap(m map[string]bool) []string {
list := make([]string, 0, len(m))
for k := range m {
list = append(list, k)
}
return list
}