P4: contrib/store — second module (pure-Go SQLite), budget store
Establish the nested persistence module — the architectural reason the core stays lean: a SEPARATE go.mod carrying modernc.org/sqlite (pure Go, no cgo), so the SQLite driver NEVER enters the executus core go.sum. A static-binary host (gadfly) importing only the core stays static; a host wanting turnkey persistence imports contrib/store. - sqlite.go: store.Open(dsn) -> *DB (one SQLite file), accessor-per-seam. - budget_store.go: db.Budget() satisfies budget.BudgetStorage; Add() does the 7-day window rollover atomically inside a transaction (concurrent Adds can't race the read-modify-write — the in-memory store's one weak spot). - Conformance test: budget.NewDBBudget over the SQLite store passes the SAME rolling-window contract as the in-memory store. - CI: a new step builds + tests contrib/store on its own AND asserts it carries the sqlite driver the core forbids (proof the split works). Verified: core go.sum has 0 sqlite refs; contrib/store go.sum has it. persona/skill/audit SQLite stores follow next (same JSON-blob + indexed-columns pattern, sidestepping the three-layer field-loss footgun). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
// Package store provides durable, pure-Go SQLite implementations of executus's
|
||||
// battery store seams (audit, budget, persona, skill). It is a SEPARATE nested
|
||||
// module so the SQLite driver (modernc.org/sqlite — pure Go, no cgo) never
|
||||
// enters the executus core go.sum: a static-binary host (gadfly) that imports
|
||||
// only the core stays static, while a host that wants turnkey persistence
|
||||
// imports this module and gets every *Store seam backed by one SQLite file.
|
||||
//
|
||||
// db, _ := store.Open("file:executus.db?_pragma=busy_timeout(5000)")
|
||||
// defer db.Close()
|
||||
// budgetStore := db.Budget() // satisfies budget.BudgetStorage
|
||||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "modernc.org/sqlite" // pure-Go driver, registered as "sqlite"
|
||||
)
|
||||
|
||||
// DB is a handle to one SQLite database backing the executus store seams. Each
|
||||
// accessor (Budget(), …) returns a seam implementation sharing this connection.
|
||||
// Safe for concurrent use (SQLite serializes writes; busy_timeout handles
|
||||
// contention). Construct with Open; close with Close.
|
||||
type DB struct {
|
||||
sql *sql.DB
|
||||
}
|
||||
|
||||
// Open opens (creating if absent) a SQLite database at dsn and returns a DB. A
|
||||
// dsn of ":memory:" yields an ephemeral in-memory database. The caller owns the
|
||||
// returned DB and must Close it.
|
||||
func Open(dsn string) (*DB, error) {
|
||||
sqldb, err := sql.Open("sqlite", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("store: open %q: %w", dsn, err)
|
||||
}
|
||||
if err := sqldb.Ping(); err != nil {
|
||||
sqldb.Close()
|
||||
return nil, fmt.Errorf("store: ping %q: %w", dsn, err)
|
||||
}
|
||||
return &DB{sql: sqldb}, nil
|
||||
}
|
||||
|
||||
// Close closes the underlying database.
|
||||
func (d *DB) Close() error { return d.sql.Close() }
|
||||
|
||||
// SQL exposes the underlying *sql.DB for hosts that need direct access.
|
||||
func (d *DB) SQL() *sql.DB { return d.sql }
|
||||
Reference in New Issue
Block a user