Compile-time footgun-prevention guardrails for Go. Two packages that make it impossible to forget a context on database calls or git invocations:
safedb— wraps*sql.DBand*sql.Txto expose only context-aware methods. Code that calls the bareQuery/Exec/QueryRowvariants doesn't compile.safecmd— timeout-boundedexec.CommandContextwrappers for git operations, with injected commit identity for headless/daemon worktrees.
The noctx linter catches context-free database calls at CI time — helpful,
but optional. A compiled SDK surfaces as a type error: the dangerous method
simply doesn't exist on the type. Useful when you're shipping primitives to
consumers who may not lint, or when you want a stronger guarantee for an
autonomous agent fleet.
github.com/leonletto/thrum_safe
import "github.com/leonletto/thrum_safe/safedb"
import "github.com/leonletto/thrum_safe/safecmd"go get github.com/leonletto/thrum_safe@latestraw, _ := sql.Open("sqlite", "app.db")
db := safedb.New(raw)
// Only context-aware methods exist on *safedb.DB:
rows, err := db.QueryContext(ctx, "SELECT id, name FROM users WHERE active = ?", true)
// Transactions are also wrapped — bare Exec/Query/QueryRow don't compile:
tx, err := db.BeginTx(ctx, nil)
_, err = tx.ExecContext(ctx, "UPDATE users SET name = ? WHERE id = ?", name, id)
_ = tx.Commit()
// Need DDL or a migration library? Raw() is the documented escape hatch:
db.Raw().Exec("CREATE TABLE ...") // red-flag in code review, intentionally verbosedb.QueryContext(ctx, ...) // ✓ compiles
db.ExecContext(ctx, ...) // ✓ compiles
db.QueryRowContext(ctx, ...) // ✓ compiles
db.BeginTx(ctx, nil) // ✓ compiles — returns *safedb.Tx
db.Query(...) // ✗ compile error — method does not exist
db.Exec(...) // ✗ compile error — method does not exist
db.QueryRow(...) // ✗ compile error — method does not exist
tx.ExecContext(ctx, ...) // ✓ compiles
tx.QueryContext(ctx, ...) // ✓ compiles
tx.QueryRowContext(ctx, ...) // ✓ compiles
tx.Commit() // ✓ compiles
tx.Rollback() // ✓ compiles
tx.Exec(...) // ✗ compile error — method does not exist// Run a git command with a 5-second timeout + injected commit identity:
out, err := safecmd.Git(ctx, "/path/to/repo", "status", "--short")
// For network operations (push, fetch) — 10-second timeout:
out, err = safecmd.GitLong(ctx, "/path/to/repo", "push", "origin", "main")
// Read effective git config without identity injection:
name, err := safecmd.GitConfig(ctx, "/path/to/repo", "user.name")
// List all worktree paths for a repo:
paths := safecmd.WorktreePaths(ctx, "/path/to/repo")Every Git/GitLong call prepends -c user.name=... -c user.email=... args
to prevent commit failures in headless or daemon-spawned worktrees where no
global git identity is configured. Override the default at package init:
func init() {
safecmd.DefaultGitIdentity = []string{
"-c", "user.name=my-agent",
"-c", "user.email=my-agent@local",
}
}GitConfig intentionally does NOT inject these overrides, so you can read the
real repo config values.
thrum_safe uses only the Go standard library at runtime. Test files use
modernc.org/sqlite for an in-process SQLite driver; it is not a runtime
dependency of packages that import thrum_safe.
See docs/enforcement.md for the enforcement rules that instruct an AI
coding fleet to route all database/sql access through safedb and all
git exec through safecmd.
User-facing guides live in docs/:
- Getting started — wrap
*sql.DBonce, pass*safedb.DB, overrideDefaultGitIdentityat init. - safedb — full API reference:
DBandTxmethods,PingContext,Raw()escape hatch, migration/DDL pattern. - safecmd —
Git,GitLong,GitConfig,WorktreePaths, timeouts, and goroutine-safety note onDefaultGitIdentity. - Testing —
go test -race ./..., therequireGitskip pattern, and what each package's test suite covers. - Enforcement — agent-fleet rules for routing all database and git access through these wrappers.
Contributing? See CONTRIBUTING.md. Security? See SECURITY.md.