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

51
config/config.go Normal file
View File

@@ -0,0 +1,51 @@
// Package config provides path and endpoint configuration for the exo system.
package config
import (
"os"
"path/filepath"
)
// Config holds all runtime configuration for exo.
type Config struct {
// BasePath is the root directory for all exo data.
BasePath string
// DatabasePath is the path to the unified SQLite database.
DatabasePath string
// BlobStorePath is the root of the content-addressable blob store.
BlobStorePath string
// MinioEndpoint is the S3-compatible endpoint for remote blob backup.
MinioEndpoint string
// MinioBucket is the bucket name for blob backup.
MinioBucket string
// GRPCListenAddr is the address exod listens on for gRPC connections.
GRPCListenAddr string
}
// DefaultBasePath returns $HOME/exo.
func DefaultBasePath() string {
return filepath.Join(os.Getenv("HOME"), "exo")
}
// Default returns a Config with sensible defaults rooted at $HOME/exo.
func Default() Config {
base := DefaultBasePath()
return FromBasePath(base)
}
// FromBasePath returns a Config rooted at the given base path.
func FromBasePath(base string) Config {
return Config{
BasePath: base,
DatabasePath: filepath.Join(base, "exo.db"),
BlobStorePath: filepath.Join(base, "blobs"),
MinioEndpoint: "",
MinioBucket: "exo-blobs",
GRPCListenAddr: "localhost:9090",
}
}

39
config/config_test.go Normal file
View File

@@ -0,0 +1,39 @@
package config
import (
"path/filepath"
"strings"
"testing"
)
func TestDefault(t *testing.T) {
cfg := Default()
if cfg.BasePath == "" {
t.Fatal("BasePath is empty")
}
if !strings.HasSuffix(cfg.BasePath, "exo") {
t.Fatalf("BasePath should end with 'exo', got %q", cfg.BasePath)
}
if cfg.DatabasePath == "" {
t.Fatal("DatabasePath is empty")
}
if cfg.BlobStorePath == "" {
t.Fatal("BlobStorePath is empty")
}
if cfg.GRPCListenAddr == "" {
t.Fatal("GRPCListenAddr is empty")
}
}
func TestFromBasePath(t *testing.T) {
cfg := FromBasePath("/tmp/testexo")
if cfg.BasePath != "/tmp/testexo" {
t.Fatalf("expected BasePath '/tmp/testexo', got %q", cfg.BasePath)
}
if cfg.DatabasePath != filepath.Join("/tmp/testexo", "exo.db") {
t.Fatalf("unexpected DatabasePath: %q", cfg.DatabasePath)
}
if cfg.BlobStorePath != filepath.Join("/tmp/testexo", "blobs") {
t.Fatalf("unexpected BlobStorePath: %q", cfg.BlobStorePath)
}
}