Initial commit: project setup and db package

- Project scaffolding: go.mod, Makefile, .golangci.yaml, doc.go
- README, ARCHITECTURE, PROJECT_PLAN, PROGRESS documentation
- db package: Open (WAL, FK, busy timeout, 0600 permissions),
  Migrate (sequential, transactional, idempotent),
  SchemaVersion, Snapshot (VACUUM INTO)
- 11 tests covering open, migrate, and snapshot

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 14:17:17 -07:00
commit 8b4db22c93
12 changed files with 1611 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
# runtime data
/srv
# IDE
.idea/
.vscode/

94
.golangci.yaml Normal file
View File

@@ -0,0 +1,94 @@
# golangci-lint v2 configuration for mcdsl.
# Principle: fail loudly. Security and correctness issues are errors, not warnings.
version: "2"
run:
timeout: 5m
tests: true
linters:
default: none
enable:
# --- Correctness ---
- errcheck
- govet
- ineffassign
- unused
# --- Error handling ---
- errorlint
# --- Security ---
- gosec
- staticcheck
# --- Style / conventions ---
- revive
settings:
errcheck:
check-blank: false
check-type-assertions: true
govet:
enable-all: true
disable:
- shadow
- fieldalignment
gosec:
severity: medium
confidence: medium
excludes:
- G104
errorlint:
errorf: true
asserts: true
comparison: true
revive:
rules:
- name: error-return
severity: error
- name: unexported-return
severity: error
- name: error-strings
severity: warning
- name: if-return
severity: warning
- name: increment-decrement
severity: warning
- name: var-naming
severity: warning
- name: range
severity: warning
- name: time-naming
severity: warning
- name: indent-error-flow
severity: warning
- name: early-return
severity: warning
# exported and package-comments enabled — this is a shared library,
# exported symbols should have documentation.
- name: exported
severity: warning
formatters:
enable:
- gofmt
- goimports
issues:
max-issues-per-linter: 0
max-same-issues: 0
exclusions:
paths:
- vendor
rules:
- path: "_test\\.go"
linters:
- gosec
text: "G101"

611
ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,611 @@
# MCDSL Architecture
Metacircular Dynamics Standard Library — Technical Design Document
---
## 1. Overview
MCDSL is a Go module providing the shared infrastructure that every
Metacircular service needs. It is not a framework — services are not structured
around it. It is a collection of well-tested packages that each solve one
problem, usable independently or together.
### Design Principles
- **Extract, don't invent.** Every package in MCDSL is extracted from patterns
already proven across multiple services. No speculative abstractions.
- **Optional composition.** Services import only the packages they need. No
package depends on another MCDSL package unless absolutely necessary.
- **Minimal API surface.** Each package exposes the smallest possible public
API. Configuration is via structs with sensible defaults.
- **Zero magic.** No init() functions, no global state, no reflection-based
wiring. Explicit construction, explicit errors.
- **Stdlib-compatible types.** Functions accept and return `*sql.DB`,
`http.Handler`, `*slog.Logger`, `context.Context` — not custom wrappers.
### Module Path
```
git.wntrmute.dev/kyle/mcdsl
```
### Dependencies
| Dependency | Purpose |
|------------|---------|
| `modernc.org/sqlite` | Pure-Go SQLite driver |
| `github.com/go-chi/chi/v5` | HTTP router |
| `github.com/pelletier/go-toml/v2` | TOML config parsing |
| `google.golang.org/grpc` | gRPC server |
| `github.com/klauspost/compress/zstd` | Zstandard compression for archives |
| `git.wntrmute.dev/kyle/mcias/clients/go` | MCIAS client library |
All dependencies are already used by existing services. MCDSL adds no new
dependencies to the platform.
---
## 2. Package: `auth`
MCIAS token validation with caching. Extracted from the `internal/auth/`
packages in metacrypt, mcr, and mcat.
### Types
```go
// TokenInfo holds the validated identity of an authenticated caller.
type TokenInfo struct {
Username string
Roles []string
IsAdmin bool
}
// Config holds MCIAS connection settings. Matches the standard [mcias]
// TOML section used by all services.
type Config struct {
ServerURL string `toml:"server_url"`
CACert string `toml:"ca_cert"`
ServiceName string `toml:"service_name"`
Tags []string `toml:"tags"`
}
// Authenticator validates MCIAS bearer tokens with a short-lived cache.
type Authenticator struct { /* unexported fields */ }
```
### API
```go
func New(cfg Config, logger *slog.Logger) (*Authenticator, error)
func (a *Authenticator) ValidateToken(token string) (*TokenInfo, error)
func (a *Authenticator) Login(username, password, totpCode string) (token string, expiresAt time.Time, err error)
func (a *Authenticator) Logout(token string) error
```
### Cache Behavior
- Key: SHA-256 of the raw token, stored as `[32]byte`.
- TTL: 30 seconds (hardcoded, matches platform standard).
- Eviction: Lazy — expired entries are replaced on next lookup. No background
goroutine.
- Thread safety: `sync.RWMutex`. Reads take a read lock; cache misses
promote to write lock.
### Admin Detection
`IsAdmin` is set by scanning the roles list for the string `"admin"`. This
matches MCIAS's role model — admin is a role, not a flag.
### Errors
```go
var (
ErrInvalidToken = errors.New("auth: invalid token")
ErrInvalidCredentials = errors.New("auth: invalid credentials")
ErrForbidden = errors.New("auth: forbidden by policy")
)
```
---
## 3. Package: `db`
SQLite connection setup, migration runner, and snapshot utilities. Extracted
from the `internal/db/` packages across all services.
### Opening a Database
```go
func Open(path string) (*sql.DB, error)
```
Opens a SQLite database with the standard Metacircular pragmas:
```sql
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
```
File permissions are set to `0600` (owner read/write only). The function
creates the file if it does not exist.
Returns a standard `*sql.DB` — no wrapper type. Services use it directly
with `database/sql`.
### Migrations
```go
type Migration struct {
Version int
Name string
SQL string
}
func Migrate(db *sql.DB, migrations []Migration) error
```
Migrations are applied sequentially in a transaction. Each migration is
recorded in a `schema_migrations` table:
```sql
CREATE TABLE IF NOT EXISTS schema_migrations (
version INTEGER PRIMARY KEY,
name TEXT NOT NULL,
applied_at TEXT NOT NULL
);
```
Already-applied migrations (by version number) are skipped. Timestamps are
stored as RFC 3339 UTC.
Services define their migrations as a `[]Migration` slice — no embedded SQL
files, no migration DSL. The slice is the schema history.
### Snapshots
```go
func Snapshot(db *sql.DB, destPath string) error
```
Executes `VACUUM INTO` to create a consistent, standalone copy of the
database at `destPath`. This is the standard backup mechanism for all
Metacircular services.
---
## 4. Package: `config`
TOML configuration loading with environment variable overrides. Extracted from
the `internal/config/` packages across all services.
### Standard Sections
```go
// Base contains the configuration sections common to all services.
// Services embed this in their own config struct.
type Base struct {
Server ServerConfig `toml:"server"`
Database DatabaseConfig `toml:"database"`
MCIAS auth.Config `toml:"mcias"`
Log LogConfig `toml:"log"`
}
type ServerConfig struct {
ListenAddr string `toml:"listen_addr"`
GRPCAddr string `toml:"grpc_addr"`
TLSCert string `toml:"tls_cert"`
TLSKey string `toml:"tls_key"`
ReadTimeout time.Duration `toml:"read_timeout"`
WriteTimeout time.Duration `toml:"write_timeout"`
IdleTimeout time.Duration `toml:"idle_timeout"`
ShutdownTimeout time.Duration `toml:"shutdown_timeout"`
}
type DatabaseConfig struct {
Path string `toml:"path"`
}
type LogConfig struct {
Level string `toml:"level"`
}
```
### Loading
```go
func Load[T any](path string, envPrefix string) (*T, error)
```
1. Reads the TOML file at `path`.
2. Unmarshals into `*T`.
3. Applies environment variable overrides using `envPrefix` (e.g., prefix
`"MCR"` maps `MCR_SERVER_LISTEN_ADDR` to `Server.ListenAddr`).
4. Applies defaults for unset optional fields.
5. Validates required fields.
Environment overrides use reflection to walk the struct, converting field
paths to `PREFIX_SECTION_FIELD` format. Only string, int, bool, duration,
and string slice fields are supported.
### Defaults
| Field | Default |
|-------|---------|
| `Log.Level` | `"info"` |
| `Server.ReadTimeout` | `30s` |
| `Server.WriteTimeout` | `30s` |
| `Server.IdleTimeout` | `120s` |
| `Server.ShutdownTimeout` | `60s` |
### Validation
`Load` returns an error if any required field is empty:
- `Server.ListenAddr`
- `Server.TLSCert`
- `Server.TLSKey`
- `Database.Path`
- `MCIAS.ServerURL` (if the MCIAS section is present)
Services add their own validation by implementing an optional `Validate()`
method on their config type.
---
## 5. Package: `httpserver`
TLS HTTP server setup with chi, standard middleware, and graceful shutdown.
Extracted from `internal/server/` across all services.
### Types
```go
type Server struct {
Router *chi.Mux
Logger *slog.Logger
// unexported: httpSrv, cfg
}
```
### API
```go
func New(cfg config.ServerConfig, logger *slog.Logger) *Server
func (s *Server) ListenAndServeTLS(certFile, keyFile string) error
func (s *Server) Shutdown(ctx context.Context) error
```
`New` creates a chi router and configures the underlying `http.Server` with:
- TLS 1.3 minimum (`tls.VersionTLS13`)
- Read/write/idle timeouts from config
- The chi router as handler
Services access `s.Router` to register their routes.
### Standard Middleware
```go
func (s *Server) LoggingMiddleware(next http.Handler) http.Handler
```
Wraps the response writer to capture the status code, logs after the
request completes:
```
level=INFO msg=http method=GET path=/v1/status status=200 remote=10.0.0.1:54321
```
### StatusWriter
```go
type StatusWriter struct {
http.ResponseWriter
Status int
}
```
Exported for use in custom middleware that needs the response status code.
### JSON Helpers
```go
func WriteJSON(w http.ResponseWriter, status int, v any)
func WriteError(w http.ResponseWriter, status int, message string)
```
`WriteError` writes `{"error": "message"}` — the standard Metacircular
error format.
---
## 6. Package: `grpcserver`
gRPC server setup with TLS, interceptor chain, and method-map
authentication. Extracted from `internal/grpcserver/` in mcias, mcr, and
mc-proxy.
### Types
```go
// MethodMap classifies gRPC methods for access control.
type MethodMap struct {
Public map[string]bool // No auth required
AuthRequired map[string]bool // Valid MCIAS token required
AdminRequired map[string]bool // Admin role required
}
type Server struct {
GRPCServer *grpc.Server
Logger *slog.Logger
// unexported: listener, auth
}
```
### API
```go
func New(cfg config.ServerConfig, auth *auth.Authenticator, methods MethodMap, logger *slog.Logger) (*Server, error)
func (s *Server) Serve() error
func (s *Server) Stop()
```
`New` builds a gRPC server with:
- TLS 1.3 from cert/key in config
- Unary interceptor chain: logging → auth (using MethodMap) → user handler
- Services register their implementations on `s.GRPCServer`
### Auth Interceptor
The auth interceptor uses the `MethodMap` to determine the required access
level for each RPC:
1. If the method is in `Public` — pass through, no auth.
2. If the method is in `AuthRequired` — validate the bearer token from
metadata, populate `TokenInfo` in context.
3. If the method is in `AdminRequired` — validate token and require
`IsAdmin == true`.
4. If the method is not in any map — **deny by default**. This is a safety
net: forgetting to register a new RPC results in a denied request, not
an open one.
### Context Helpers
```go
func TokenInfoFromContext(ctx context.Context) *auth.TokenInfo
```
---
## 7. Package: `csrf`
HMAC-SHA256 double-submit cookie CSRF protection. Extracted from the web
server packages in metacrypt, mcr, and mcat.
### Types
```go
type Protect struct {
// unexported: secret, cookieName, fieldName
}
```
### API
```go
func New(secret []byte, cookieName, fieldName string) *Protect
func (p *Protect) Middleware(next http.Handler) http.Handler
func (p *Protect) SetToken(w http.ResponseWriter) string
func (p *Protect) TemplateFunc(w http.ResponseWriter) template.FuncMap
```
### Token Format
```
base64(nonce) "." base64(HMAC-SHA256(secret, nonce))
```
Nonce is 32 bytes from `crypto/rand`. The token is set as a cookie and must
be submitted as a form field on mutating requests (POST, PUT, PATCH, DELETE).
### Validation
The middleware:
1. Skips safe methods (GET, HEAD, OPTIONS).
2. Reads the token from the cookie and the form field.
3. Verifies both are present and equal.
4. Verifies the HMAC signature is valid.
5. Returns 403 on any failure.
### Template Integration
`TemplateFunc` returns a `template.FuncMap` with a `csrfField` function that
renders the hidden input:
```html
<input type="hidden" name="csrf_token" value="...">
```
---
## 8. Package: `web`
Session cookie management, auth middleware, and template rendering helpers
for htmx web UIs. Extracted from `internal/webserver/` across services.
### Session Cookies
```go
func SetSessionCookie(w http.ResponseWriter, name, token string)
func ClearSessionCookie(w http.ResponseWriter, name string)
func GetSessionToken(r *http.Request, name string) string
```
All session cookies are set with: `HttpOnly`, `Secure`,
`SameSite=Strict`, `Path=/`.
### Auth Middleware
```go
func RequireAuth(auth *auth.Authenticator, cookieName string, loginPath string) func(http.Handler) http.Handler
```
Extracts the session token from the cookie, validates it via the
Authenticator, and either:
- Sets `TokenInfo` in the request context and calls the next handler.
- Redirects to `loginPath` if the token is missing or invalid.
### Context Helpers
```go
func TokenInfoFromContext(ctx context.Context) *auth.TokenInfo
```
### Template Helpers
```go
func RenderTemplate(w http.ResponseWriter, fs embed.FS, name string, data any, funcs ...template.FuncMap)
```
Parses `templates/layout.html` and `templates/<name>` from the embedded FS,
merges any provided FuncMaps, and executes the `layout` template.
---
## 9. Package: `archive`
Service directory snapshot and restore using tar.zst, with SQLite-aware
handling. This is new functionality for MCP, not extracted from existing
services.
### Snapshot
```go
type SnapshotOptions struct {
ServiceDir string // e.g., /srv/myservice
DBPath string // e.g., /srv/myservice/myservice.db (for VACUUM INTO)
DB *sql.DB // live database connection (for VACUUM INTO)
ExcludePatterns []string // additional glob patterns to exclude
}
func Snapshot(opts SnapshotOptions) (io.ReadCloser, error)
```
1. Runs `VACUUM INTO` to create a consistent DB copy in a temp file.
2. Walks the service directory, excluding:
- `*.db`, `*.db-wal`, `*.db-shm` (live database files)
- `backups/` directory
- Any patterns in `ExcludePatterns`
3. Adds the VACUUM INTO copy as `<basename>.db` in the archive.
4. Returns a streaming tar.zst reader.
The archive is produced as a stream — it does not need to be fully buffered
in memory. This allows piping directly over a network connection.
### Restore
```go
func Restore(r io.Reader, destDir string) error
```
Extracts a tar.zst archive into `destDir`. Creates the directory if it does
not exist. Overwrites existing files. Preserves file permissions.
### Compression
Zstandard compression via `github.com/klauspost/compress/zstd`. Default
compression level (3) balances speed and ratio for the typical service
directory size (SQLite DB + config + certs, usually under 100 MB).
---
## 10. Package: `health`
Standard health check implementation for both gRPC and REST. New
functionality to standardize what services already do ad-hoc.
### gRPC
Implements `grpc.health.v1.Health` (the standard gRPC health checking
protocol). Services register it on their gRPC server:
```go
health.RegisterGRPC(grpcServer)
```
### REST
```go
func Handler(db *sql.DB) http.HandlerFunc
```
Returns a handler for `GET /healthz` (or whatever path the service mounts
it on) that:
1. Pings the database.
2. Returns `200 {"status": "ok"}` or `503 {"status": "unhealthy", "error": "..."}`.
---
## 11. Inter-Package Dependencies
```
archive ──→ db (for Snapshot)
auth ──→ (mcias client library)
config ──→ auth (for auth.Config type)
csrf ──→ (stdlib only)
db ──→ (modernc.org/sqlite)
grpcserver ──→ auth, config
health ──→ db
httpserver ──→ config
web ──→ auth, csrf
```
No circular dependencies. Each package can be imported independently except
where noted above.
---
## 12. What MCDSL Does Not Provide
- **Business logic.** Policy engines, engine registries, OCI handlers — these
are service-specific and stay in each service's `internal/` packages.
- **Proto definitions.** Each service owns its own proto files and generated
code.
- **CLI scaffolding.** Cobra command wiring is minimal and service-specific.
- **Database schemas.** Each service defines its own migrations. MCDSL
provides the runner, not the SQL.
- **Templates and static assets.** Each service's web UI is its own. MCDSL
provides rendering helpers, not content.
---
## 13. Migration Path
Existing services adopt MCDSL incrementally — one package at a time:
1. Replace `internal/auth/` with `mcdsl/auth`.
2. Replace database open/pragma code with `mcdsl/db.Open`.
3. Replace migration runner with `mcdsl/db.Migrate`.
4. Replace config loading with `mcdsl/config.Load`.
5. Replace CSRF implementation with `mcdsl/csrf`.
6. Replace server setup with `mcdsl/httpserver` and `mcdsl/grpcserver`.
Each step is independent. Services can adopt one package without adopting
all of them. The old `internal/` code can be removed after each migration.
---
## 14. Security Considerations
- **Token caching** uses SHA-256 of the token as the cache key. The raw token
is never used as a map key to prevent timing attacks on map lookup.
- **CSRF secrets** must be generated from `crypto/rand` and should be unique
per service instance. MCDSL does not generate them — the service provides
them.
- **Session cookies** are always `HttpOnly`, `Secure`, `SameSite=Strict`.
These flags are not configurable — relaxing them would be a security defect.
- **gRPC method maps** default to deny. An unregistered method is rejected,
not allowed. This is the most important safety property in the library.
- **File permissions** on databases are `0600`. This is not configurable.
- **TLS 1.3 minimum** is not configurable. Services that need TLS 1.2 (there
should be none) cannot use `httpserver` or `grpcserver`.

18
Makefile Normal file
View File

@@ -0,0 +1,18 @@
.PHONY: build test vet lint clean all
build:
go build ./...
test:
go test ./...
vet:
go vet ./...
lint:
golangci-lint run ./...
clean:
go clean ./...
all: vet lint test build

34
PROGRESS.md Normal file
View File

@@ -0,0 +1,34 @@
# MCDSL Progress
## Current State
Phase 1 complete. The `db` package is implemented and tested.
## Completed
### Phase 0: Project Setup (2026-03-25)
- Initialized Go module (`git.wntrmute.dev/kyle/mcdsl`)
- Created `.golangci.yaml` matching platform standard (with `exported` rule
enabled since this is a shared library)
- Created `Makefile` with standard targets (build, test, vet, lint, all)
- Created `.gitignore`
- Created `doc.go` package doc
- `make all` passes clean
### Phase 1: `db` — SQLite Foundation (2026-03-25)
- `Open(path string) (*sql.DB, error)` — opens with WAL, FK, busy timeout
5000ms, 0600 permissions, creates parent dirs
- `Migration` type with Version, Name, SQL fields
- `Migrate(database *sql.DB, migrations []Migration) error` — sequential,
transactional, idempotent, records name and timestamp in schema_migrations
- `SchemaVersion(database *sql.DB) (int, error)` — highest applied version
- `Snapshot(database *sql.DB, destPath string) error` — VACUUM INTO with
0600 permissions, creates parent dirs
- 11 tests: open (pragmas, permissions, parent dir, existing DB), migrate
(fresh, idempotent, incremental, records name), schema version (empty),
snapshot (data integrity, permissions, parent dir)
- `make all` passes clean (vet, lint 0 issues, 11/11 tests, build)
## Next Steps
- Phase 2: `auth` package (MCIAS token validation with caching)

192
PROJECT_PLAN.md Normal file
View File

@@ -0,0 +1,192 @@
# MCDSL Project Plan
Implementation phases for the Metacircular Dynamics Standard Library.
Each phase produces a usable, tested package. Phases are ordered by
dependency (foundational packages first) and by value (most-duplicated
code first).
---
## Phase 0: Project Setup
- [ ] Initialize Go module (`git.wntrmute.dev/kyle/mcdsl`)
- [ ] Create `.golangci.yaml` (matching platform standard)
- [ ] Create `Makefile` with standard targets (build, test, vet, lint, all)
- [ ] Create `.gitignore`
**Acceptance criteria:** `make all` passes on an empty module.
---
## Phase 1: `db` — SQLite Foundation
The most universally needed package. Every service with a database uses
identical open/pragma/migration code.
- [ ] `Open(path string) (*sql.DB, error)` — open with WAL, FK, busy timeout,
0600 permissions
- [ ] `Migration` type and `Migrate(db *sql.DB, migrations []Migration) error`
— sequential, transactional, idempotent, schema_migrations tracking
- [ ] `Snapshot(db *sql.DB, destPath string) error` — VACUUM INTO wrapper
- [ ] Tests: open, migrate fresh DB, migrate existing DB (idempotent), snapshot
produces valid DB, file permissions
**Acceptance criteria:** A service can replace its `internal/db/` open and
migrate code with `mcdsl/db` and pass its existing tests.
---
## Phase 2: `auth` — MCIAS Token Validation
The second most duplicated package. Every authenticated service has its own
copy of the cache-and-validate logic.
- [ ] `Config` type matching `[mcias]` TOML section
- [ ] `TokenInfo` type (Username, Roles, IsAdmin)
- [ ] `New(cfg Config, logger *slog.Logger) (*Authenticator, error)`
- [ ] `ValidateToken(token string) (*TokenInfo, error)` with 30s SHA-256 cache
- [ ] `Login(username, password, totpCode string) (token string, expiresAt time.Time, err error)`
- [ ] `Logout(token string) error`
- [ ] Error types: `ErrInvalidToken`, `ErrInvalidCredentials`, `ErrForbidden`
- [ ] Tests: cache hit, cache miss, cache expiry, admin detection, concurrent
access, error propagation
**Acceptance criteria:** A service can replace its `internal/auth/` with
`mcdsl/auth` and pass its existing tests.
---
## Phase 3: `config` — TOML Configuration
- [ ] `Base` type with standard sections (Server, Database, MCIAS, Log)
- [ ] `ServerConfig`, `DatabaseConfig`, `LogConfig` types
- [ ] `Load[T any](path string, envPrefix string) (*T, error)` — generic
loader with TOML parse, env overrides, defaults, validation
- [ ] Environment override via reflection (`PREFIX_SECTION_FIELD`)
- [ ] Required field validation (listen addr, TLS paths, DB path)
- [ ] Default application (timeouts, log level)
- [ ] Optional `Validate()` interface for service-specific validation
- [ ] Tests: load valid config, missing required fields, env overrides, defaults,
custom validation
**Acceptance criteria:** A service can replace its `internal/config/` with
`mcdsl/config` embedding `config.Base`, and pass its existing tests.
---
## Phase 4: `httpserver` — HTTP Server Setup
- [ ] `Server` type wrapping chi + `http.Server`
- [ ] `New(cfg config.ServerConfig, logger *slog.Logger) *Server`
- [ ] `ListenAndServeTLS(certFile, keyFile string) error`
- [ ] `Shutdown(ctx context.Context) error`
- [ ] `LoggingMiddleware` — captures status code, logs request metadata
- [ ] `StatusWriter` — exported response writer wrapper
- [ ] `WriteJSON` and `WriteError` helpers
- [ ] Tests: server starts and shuts down cleanly, logging middleware captures
status, JSON helpers produce correct output
**Acceptance criteria:** A service can replace its server setup and middleware
boilerplate with `mcdsl/httpserver`.
---
## Phase 5: `csrf` — CSRF Protection
- [ ] `New(secret []byte, cookieName, fieldName string) *Protect`
- [ ] `Middleware(next http.Handler) http.Handler`
- [ ] `SetToken(w http.ResponseWriter) string`
- [ ] `TemplateFunc(w http.ResponseWriter) template.FuncMap`
- [ ] Token format: `base64(nonce).base64(HMAC-SHA256(secret, nonce))`
- [ ] Tests: token generation, validation, middleware rejects missing/invalid
tokens, safe methods pass through
**Acceptance criteria:** A service can replace its CSRF implementation with
`mcdsl/csrf` and its web UI continues to work.
---
## Phase 6: `web` — Session and Template Helpers
- [ ] `SetSessionCookie`, `ClearSessionCookie`, `GetSessionToken`
- [ ] `RequireAuth` middleware (validates token, redirects to login)
- [ ] `TokenInfoFromContext` context helper
- [ ] `RenderTemplate` helper for layout + page template pattern
- [ ] Tests: cookie setting/clearing, auth middleware redirect, template
rendering
**Acceptance criteria:** A service can replace its web session/auth
boilerplate with `mcdsl/web`.
---
## Phase 7: `grpcserver` — gRPC Server Setup
- [ ] `MethodMap` type (Public, AuthRequired, AdminRequired)
- [ ] `New(cfg config.ServerConfig, auth *auth.Authenticator, methods MethodMap, logger *slog.Logger) (*Server, error)`
- [ ] `Serve() error` and `Stop()`
- [ ] Auth interceptor using MethodMap (default deny for unmapped methods)
- [ ] Logging interceptor
- [ ] `TokenInfoFromContext` context helper
- [ ] Tests: public method allowed, auth method requires token, admin method
requires admin, unmapped method denied, logging
**Acceptance criteria:** A service can replace its gRPC server setup and
interceptor logic with `mcdsl/grpcserver`.
---
## Phase 8: `health` — Health Checks
- [ ] `RegisterGRPC(srv *grpc.Server)` — register `grpc.health.v1.Health`
- [ ] `Handler(db *sql.DB) http.HandlerFunc` — REST health endpoint
- [ ] Tests: healthy response, unhealthy response (closed DB)
**Acceptance criteria:** Services have a standard health check that MCP can
query.
---
## Phase 9: `archive` — Service Directory Snapshots
- [ ] `SnapshotOptions` type
- [ ] `Snapshot(opts SnapshotOptions) (io.ReadCloser, error)` — streaming
tar.zst with DB exclusion/injection
- [ ] `Restore(r io.Reader, destDir string) error`
- [ ] Exclude patterns: `*.db`, `*.db-wal`, `*.db-shm`, `backups/`
- [ ] Tests: snapshot roundtrip (snapshot then restore produces identical
files), DB consistency (VACUUM INTO copy matches), excludes work,
streaming (no full buffer)
**Acceptance criteria:** MCP agent can snapshot and restore a service
directory using this package.
---
## Phase 10: Service Migration (First Adopter)
Pick one service (mcat is the simplest) and migrate it to use MCDSL:
- [ ] Replace `internal/auth/` with `mcdsl/auth`
- [ ] Replace `internal/config/` with `mcdsl/config`
- [ ] Replace web session/CSRF code with `mcdsl/csrf` and `mcdsl/web`
- [ ] Verify `make all` passes
- [ ] Document the migration process for other services
**Acceptance criteria:** mcat works identically using MCDSL, with its
`internal/` packages reduced to service-specific logic only.
---
## Phase 11: Broader Adoption
Migrate remaining services one at a time:
- [ ] metacrypt
- [ ] mcr
- [ ] mc-proxy (subset: db, config — no web/csrf)
- [ ] mcias (subset: db, config, httpserver — owns the auth client, not a consumer)
Each migration follows the same pattern as Phase 10. Services are migrated
independently — there is no big-bang cutover.

96
README.md Normal file
View File

@@ -0,0 +1,96 @@
# MCDSL — Metacircular Dynamics Standard Library
MCDSL is a shared Go library for Metacircular Dynamics services. It extracts
the common patterns that every service implements independently — MCIAS
authentication, SQLite database setup, TLS server bootstrapping, CSRF
protection, configuration loading, and service data snapshots — into a single,
tested, reusable module.
## Why
Every Metacircular service follows the same patterns (see
`engineering-standards.md`). Today, each service copy-pastes these patterns
into its own `internal/` packages. This means:
- Bug fixes must be applied N times (once per service).
- Subtle divergences accumulate (e.g., CSRF tokens use base64 in one service,
hex in another; auth cache keys are `[32]byte` in some, hex strings in
others).
- New services require copying and adapting boilerplate from an existing
service.
MCDSL extracts the 95%+ identical code into a shared library. Services import
it and provide only their service-specific logic.
## Module Path
```
git.wntrmute.dev/kyle/mcdsl
```
## Packages
| Package | Purpose |
|---------|---------|
| `auth` | MCIAS token validation with 30-second SHA-256 cache |
| `db` | SQLite connection setup (WAL, FK, busy timeout), migration runner, VACUUM INTO snapshots |
| `config` | TOML config loading with environment variable overrides, standard section types |
| `httpserver` | TLS 1.3 HTTP server setup with chi, graceful shutdown, logging middleware |
| `grpcserver` | gRPC server setup with TLS, interceptor chain helpers, method map auth |
| `csrf` | HMAC-SHA256 double-submit cookie CSRF protection |
| `web` | Session cookie management, template rendering helpers, auth middleware |
| `archive` | tar.zst service directory snapshot and restore with SQLite-aware handling |
| `health` | Standard health check implementation (gRPC Health/v1 + REST) |
## Quick Start
```go
import (
"git.wntrmute.dev/kyle/mcdsl/auth"
"git.wntrmute.dev/kyle/mcdsl/db"
"git.wntrmute.dev/kyle/mcdsl/config"
"git.wntrmute.dev/kyle/mcdsl/httpserver"
)
// Load config with standard sections + service-specific fields.
type MyConfig struct {
config.Base
MyService MyServiceConfig `toml:"my_service"`
}
cfg, err := config.Load[MyConfig]("my-service.toml", "MYSERVICE")
// Open database with standard pragmas and run migrations.
database, err := db.Open(cfg.Database.Path)
migrations := []db.Migration{
{Version: 1, Name: "initial schema", SQL: `CREATE TABLE ...`},
}
db.Migrate(database, migrations)
// Set up MCIAS authentication with token caching.
mcauth, err := auth.New(cfg.MCIAS)
// Start TLS server with standard middleware.
srv := httpserver.New(cfg.Server, logger)
srv.Route(func(r chi.Router) {
r.Use(srv.LoggingMiddleware)
// register routes...
})
srv.ListenAndServeTLS()
```
## Build and Test
```bash
go build ./...
go test ./...
go vet ./...
golangci-lint run ./...
```
## Documentation
- [ARCHITECTURE.md](ARCHITECTURE.md) — full library specification
- [PROJECT_PLAN.md](PROJECT_PLAN.md) — implementation phases
- [PROGRESS.md](PROGRESS.md) — development status
- [../engineering-standards.md](../engineering-standards.md) — platform-wide standards

181
db/db.go Normal file
View File

@@ -0,0 +1,181 @@
// Package db provides SQLite database setup, migrations, and snapshots
// for Metacircular services.
//
// All databases are opened with the standard Metacircular pragmas (WAL mode,
// foreign keys, busy timeout) and restrictive file permissions (0600).
package db
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"time"
_ "modernc.org/sqlite" // SQLite driver (pure Go, no CGo).
)
// Open opens or creates a SQLite database at path with the standard
// Metacircular pragmas:
//
// PRAGMA journal_mode = WAL;
// PRAGMA foreign_keys = ON;
// PRAGMA busy_timeout = 5000;
//
// The file is created with 0600 permissions (owner read/write only).
// The parent directory is created if it does not exist.
//
// Open returns a standard [*sql.DB] — no wrapper types. Services use it
// directly with database/sql.
func Open(path string) (*sql.DB, error) {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0700); err != nil {
return nil, fmt.Errorf("db: create directory %s: %w", dir, err)
}
// Pre-create the file with restrictive permissions if it does not exist.
if _, err := os.Stat(path); os.IsNotExist(err) {
f, createErr := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600) //nolint:gosec // path is caller-provided config, not user input
if createErr != nil {
return nil, fmt.Errorf("db: create file %s: %w", path, createErr)
}
_ = f.Close()
}
database, err := sql.Open("sqlite", path)
if err != nil {
return nil, fmt.Errorf("db: open %s: %w", path, err)
}
pragmas := []string{
"PRAGMA journal_mode = WAL",
"PRAGMA foreign_keys = ON",
"PRAGMA busy_timeout = 5000",
}
for _, p := range pragmas {
if _, execErr := database.Exec(p); execErr != nil {
_ = database.Close()
return nil, fmt.Errorf("db: %s: %w", p, execErr)
}
}
// Ensure permissions are correct even if the file already existed.
if err := os.Chmod(path, 0600); err != nil {
_ = database.Close()
return nil, fmt.Errorf("db: chmod %s: %w", path, err)
}
return database, nil
}
// Migration is a numbered, named schema change. Services define their
// migrations as a []Migration slice — the slice is the schema history.
type Migration struct {
// Version is the migration number. Must be unique and should be
// sequential starting from 1.
Version int
// Name is a short human-readable description (e.g., "initial schema").
Name string
// SQL is the DDL/DML to execute. Multiple statements are allowed
// (separated by semicolons). Each migration runs in a transaction.
SQL string
}
// Migrate applies all pending migrations from the given slice. It creates
// the schema_migrations tracking table if it does not exist.
//
// Each migration runs in its own transaction. Already-applied migrations
// (identified by version number) are skipped. Timestamps are stored as
// RFC 3339 UTC.
func Migrate(database *sql.DB, migrations []Migration) error {
_, err := database.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
version INTEGER PRIMARY KEY,
name TEXT NOT NULL DEFAULT '',
applied_at TEXT NOT NULL DEFAULT ''
)`)
if err != nil {
return fmt.Errorf("db: create schema_migrations: %w", err)
}
for _, m := range migrations {
applied, checkErr := migrationApplied(database, m.Version)
if checkErr != nil {
return checkErr
}
if applied {
continue
}
tx, txErr := database.Begin()
if txErr != nil {
return fmt.Errorf("db: begin migration %d (%s): %w", m.Version, m.Name, txErr)
}
if _, execErr := tx.Exec(m.SQL); execErr != nil {
_ = tx.Rollback()
return fmt.Errorf("db: migration %d (%s): %w", m.Version, m.Name, execErr)
}
now := time.Now().UTC().Format(time.RFC3339)
if _, execErr := tx.Exec(
`INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, ?)`,
m.Version, m.Name, now,
); execErr != nil {
_ = tx.Rollback()
return fmt.Errorf("db: record migration %d: %w", m.Version, execErr)
}
if commitErr := tx.Commit(); commitErr != nil {
return fmt.Errorf("db: commit migration %d: %w", m.Version, commitErr)
}
}
return nil
}
// SchemaVersion returns the highest applied migration version, or 0 if
// no migrations have been applied.
func SchemaVersion(database *sql.DB) (int, error) {
var version sql.NullInt64
err := database.QueryRow(`SELECT MAX(version) FROM schema_migrations`).Scan(&version)
if err != nil {
return 0, fmt.Errorf("db: schema version: %w", err)
}
if !version.Valid {
return 0, nil
}
return int(version.Int64), nil
}
// Snapshot creates a consistent backup of the database at destPath using
// SQLite's VACUUM INTO. The destination file is created with 0600
// permissions.
func Snapshot(database *sql.DB, destPath string) error {
dir := filepath.Dir(destPath)
if err := os.MkdirAll(dir, 0700); err != nil {
return fmt.Errorf("db: create snapshot directory %s: %w", dir, err)
}
if _, err := database.Exec("VACUUM INTO ?", destPath); err != nil {
return fmt.Errorf("db: snapshot: %w", err)
}
if err := os.Chmod(destPath, 0600); err != nil {
return fmt.Errorf("db: chmod snapshot %s: %w", destPath, err)
}
return nil
}
func migrationApplied(database *sql.DB, version int) (bool, error) {
var count int
err := database.QueryRow(
`SELECT COUNT(*) FROM schema_migrations WHERE version = ?`, version,
).Scan(&count)
if err != nil {
return false, fmt.Errorf("db: check migration %d: %w", version, err)
}
return count > 0, nil
}

304
db/db_test.go Normal file
View File

@@ -0,0 +1,304 @@
package db
import (
"database/sql"
"os"
"path/filepath"
"testing"
)
func TestOpen(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.db")
database, err := Open(path)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer func() { _ = database.Close() }()
// Verify WAL mode is enabled.
var journalMode string
if err := database.QueryRow("PRAGMA journal_mode").Scan(&journalMode); err != nil {
t.Fatalf("query journal_mode: %v", err)
}
if journalMode != "wal" {
t.Fatalf("journal_mode = %q, want %q", journalMode, "wal")
}
// Verify foreign keys are enabled.
var fk int
if err := database.QueryRow("PRAGMA foreign_keys").Scan(&fk); err != nil {
t.Fatalf("query foreign_keys: %v", err)
}
if fk != 1 {
t.Fatalf("foreign_keys = %d, want 1", fk)
}
// Verify busy timeout.
var timeout int
if err := database.QueryRow("PRAGMA busy_timeout").Scan(&timeout); err != nil {
t.Fatalf("query busy_timeout: %v", err)
}
if timeout != 5000 {
t.Fatalf("busy_timeout = %d, want 5000", timeout)
}
}
func TestOpenFilePermissions(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.db")
database, err := Open(path)
if err != nil {
t.Fatalf("Open: %v", err)
}
_ = database.Close()
info, err := os.Stat(path)
if err != nil {
t.Fatalf("Stat: %v", err)
}
perm := info.Mode().Perm()
if perm != 0600 {
t.Fatalf("permissions = %o, want 0600", perm)
}
}
func TestOpenCreatesParentDir(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "sub", "dir", "test.db")
database, err := Open(path)
if err != nil {
t.Fatalf("Open: %v", err)
}
_ = database.Close()
if _, err := os.Stat(path); err != nil {
t.Fatalf("database file does not exist: %v", err)
}
}
func TestOpenExistingDB(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.db")
// Create and populate.
db1, err := Open(path)
if err != nil {
t.Fatalf("Open (first): %v", err)
}
if _, err := db1.Exec("CREATE TABLE t (id INTEGER PRIMARY KEY)"); err != nil {
t.Fatalf("create table: %v", err)
}
if _, err := db1.Exec("INSERT INTO t (id) VALUES (42)"); err != nil {
t.Fatalf("insert: %v", err)
}
_ = db1.Close()
// Reopen and verify data persists.
db2, err := Open(path)
if err != nil {
t.Fatalf("Open (second): %v", err)
}
defer func() { _ = db2.Close() }()
var id int
if err := db2.QueryRow("SELECT id FROM t").Scan(&id); err != nil {
t.Fatalf("select: %v", err)
}
if id != 42 {
t.Fatalf("id = %d, want 42", id)
}
}
var testMigrations = []Migration{
{
Version: 1,
Name: "create users",
SQL: `CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)`,
},
{
Version: 2,
Name: "add email",
SQL: `ALTER TABLE users ADD COLUMN email TEXT NOT NULL DEFAULT ''`,
},
}
func TestMigrate(t *testing.T) {
database := openTestDB(t)
if err := Migrate(database, testMigrations); err != nil {
t.Fatalf("Migrate: %v", err)
}
// Verify both migrations applied.
version, err := SchemaVersion(database)
if err != nil {
t.Fatalf("SchemaVersion: %v", err)
}
if version != 2 {
t.Fatalf("schema version = %d, want 2", version)
}
// Verify schema is correct.
if _, err := database.Exec("INSERT INTO users (name, email) VALUES ('a', 'a@b.c')"); err != nil {
t.Fatalf("insert into migrated schema: %v", err)
}
}
func TestMigrateIdempotent(t *testing.T) {
database := openTestDB(t)
// Run twice.
if err := Migrate(database, testMigrations); err != nil {
t.Fatalf("Migrate (first): %v", err)
}
if err := Migrate(database, testMigrations); err != nil {
t.Fatalf("Migrate (second): %v", err)
}
version, err := SchemaVersion(database)
if err != nil {
t.Fatalf("SchemaVersion: %v", err)
}
if version != 2 {
t.Fatalf("schema version = %d, want 2", version)
}
}
func TestMigrateIncremental(t *testing.T) {
database := openTestDB(t)
// Apply only the first migration.
if err := Migrate(database, testMigrations[:1]); err != nil {
t.Fatalf("Migrate (first only): %v", err)
}
version, err := SchemaVersion(database)
if err != nil {
t.Fatalf("SchemaVersion: %v", err)
}
if version != 1 {
t.Fatalf("schema version = %d, want 1", version)
}
// Now apply all — should pick up only migration 2.
if err := Migrate(database, testMigrations); err != nil {
t.Fatalf("Migrate (all): %v", err)
}
version, err = SchemaVersion(database)
if err != nil {
t.Fatalf("SchemaVersion: %v", err)
}
if version != 2 {
t.Fatalf("schema version = %d, want 2", version)
}
}
func TestMigrateRecordsName(t *testing.T) {
database := openTestDB(t)
if err := Migrate(database, testMigrations); err != nil {
t.Fatalf("Migrate: %v", err)
}
var name string
err := database.QueryRow(
`SELECT name FROM schema_migrations WHERE version = 1`,
).Scan(&name)
if err != nil {
t.Fatalf("query migration name: %v", err)
}
if name != "create users" {
t.Fatalf("migration name = %q, want %q", name, "create users")
}
}
func TestSchemaVersionEmpty(t *testing.T) {
database := openTestDB(t)
// Create the table but apply no migrations.
if err := Migrate(database, nil); err != nil {
t.Fatalf("Migrate(nil): %v", err)
}
version, err := SchemaVersion(database)
if err != nil {
t.Fatalf("SchemaVersion: %v", err)
}
if version != 0 {
t.Fatalf("schema version = %d, want 0", version)
}
}
func TestSnapshot(t *testing.T) {
database := openTestDB(t)
// Create some data.
if _, err := database.Exec("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT)"); err != nil {
t.Fatalf("create table: %v", err)
}
if _, err := database.Exec("INSERT INTO t (val) VALUES ('hello')"); err != nil {
t.Fatalf("insert: %v", err)
}
// Snapshot.
dir := t.TempDir()
snapPath := filepath.Join(dir, "snap.db")
if err := Snapshot(database, snapPath); err != nil {
t.Fatalf("Snapshot: %v", err)
}
// Verify snapshot file permissions.
info, err := os.Stat(snapPath)
if err != nil {
t.Fatalf("Stat snapshot: %v", err)
}
if perm := info.Mode().Perm(); perm != 0600 {
t.Fatalf("snapshot permissions = %o, want 0600", perm)
}
// Open snapshot and verify data.
snapDB, err := sql.Open("sqlite", snapPath)
if err != nil {
t.Fatalf("open snapshot: %v", err)
}
defer func() { _ = snapDB.Close() }()
var val string
if err := snapDB.QueryRow("SELECT val FROM t").Scan(&val); err != nil {
t.Fatalf("select from snapshot: %v", err)
}
if val != "hello" {
t.Fatalf("val = %q, want %q", val, "hello")
}
}
func TestSnapshotCreatesParentDir(t *testing.T) {
database := openTestDB(t)
dir := t.TempDir()
snapPath := filepath.Join(dir, "sub", "snap.db")
if err := Snapshot(database, snapPath); err != nil {
t.Fatalf("Snapshot: %v", err)
}
if _, err := os.Stat(snapPath); err != nil {
t.Fatalf("snapshot file does not exist: %v", err)
}
}
func openTestDB(t *testing.T) *sql.DB {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "test.db")
database, err := Open(path)
if err != nil {
t.Fatalf("Open: %v", err)
}
t.Cleanup(func() { _ = database.Close() })
return database
}

7
doc.go Normal file
View File

@@ -0,0 +1,7 @@
// Package mcdsl is the Metacircular Dynamics Standard Library.
//
// It provides shared infrastructure packages for Metacircular services:
// authentication, database setup, configuration loading, HTTP/gRPC server
// bootstrapping, CSRF protection, session management, health checks, and
// service directory snapshots.
package mcdsl

17
go.mod Normal file
View File

@@ -0,0 +1,17 @@
module git.wntrmute.dev/kyle/mcdsl
go 1.25.7
require modernc.org/sqlite v1.47.0
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/sys v0.42.0 // indirect
modernc.org/libc v1.70.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)

51
go.sum Normal file
View File

@@ -0,0 +1,51 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.47.0 h1:R1XyaNpoW4Et9yly+I2EeX7pBza/w+pmYee/0HJDyKk=
modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=