database

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package database provides SQLite-backed storage for servers, manifests, and approval records. Manifest content is append-only: the only mutation ever performed on an existing manifest row is a state transition — see Store.UpdateManifestState.

Index

Constants

View Source
const (
	StatePending    = "PENDING"
	StateApproved   = "APPROVED"
	StateRejected   = "REJECTED"
	StateSuperseded = "SUPERSEDED"
)

Manifest lifecycle states.

View Source
const (
	DecisionApproved = "APPROVED"
	DecisionRejected = "REJECTED"
)

Approval decisions.

View Source
const (
	EventManifestPending  = "manifest.pending"
	EventManifestApproved = "manifest.approved"
	EventManifestRejected = "manifest.rejected"
)

Notification event types written to the outbox. These strings are part of the webhook payload's public contract (the "event" field), so they are defined once here rather than spelled out at each call site.

Variables

View Source
var ErrNotFound = errors.New("database: not found")

ErrNotFound is returned by every Store lookup when the row does not exist. There is exactly one absence convention: no lookup returns a nil object with a nil error, so callers use errors.Is rather than a nil check that is easy to forget and fails as a nil dereference when they do.

Functions

This section is empty.

Types

type Approval

type Approval struct {
	ID         int64
	ManifestID int64
	Decision   string
	Username   string
	Reason     string
	CreatedAt  time.Time
}

type ManifestRecord

type ManifestRecord struct {
	ID            int64
	ServerID      int64
	Hash          string
	CanonicalJSON string
	State         string
	DiffJSON      string // "" if no prior baseline existed
	CreatedAt     time.Time
}

type OutboxRow added in v0.1.1

type OutboxRow struct {
	ID            int64
	EventType     string
	ManifestID    int64
	Attempts      int
	NextAttemptAt time.Time
	DeliveredAt   *time.Time // nil until a 2xx has been observed
	LastError     string
	CreatedAt     time.Time
}

OutboxRow is one pending notification. It carries no payload: the event body is composed from the manifest at delivery time, so a change to the payload format never has to migrate rows already queued. ID doubles as the receiver's idempotency key, which is what makes at-least-once delivery safe to deduplicate on the far side.

type SQLiteStore

type SQLiteStore struct {
	// contains filtered or unexported fields
}

SQLiteStore is the top-level Store implementation backed by a *sql.DB.

func Open

func Open(path string) (*SQLiteStore, error)

Open opens (creating if necessary) a SQLite database at path, applies the schema, and configures WAL journaling and foreign keys. The parent directory is created if it does not exist.

func (*SQLiteStore) Close

func (s *SQLiteStore) Close() error

func (SQLiteStore) CreateServer

func (q SQLiteStore) CreateServer(ctx context.Context, name, endpoint string) (*Server, error)

func (SQLiteStore) DueNotifications added in v0.1.1

func (q SQLiteStore) DueNotifications(ctx context.Context, now time.Time, maxAttempts, limit int) ([]OutboxRow, error)

func (SQLiteStore) EnqueueNotification added in v0.1.1

func (q SQLiteStore) EnqueueNotification(ctx context.Context, eventType string, manifestID int64) (int64, error)

func (SQLiteStore) GetApprovedManifest

func (q SQLiteStore) GetApprovedManifest(ctx context.Context, serverID int64) (*ManifestRecord, error)

func (SQLiteStore) GetManifestByHash

func (q SQLiteStore) GetManifestByHash(ctx context.Context, serverID int64, hash string) (*ManifestRecord, error)

func (SQLiteStore) GetManifestByID

func (q SQLiteStore) GetManifestByID(ctx context.Context, id int64) (*ManifestRecord, error)

func (SQLiteStore) GetServerByID

func (q SQLiteStore) GetServerByID(ctx context.Context, id int64) (*Server, error)

func (SQLiteStore) GetServerByName

func (q SQLiteStore) GetServerByName(ctx context.Context, name string) (*Server, error)

func (SQLiteStore) InsertApproval

func (q SQLiteStore) InsertApproval(ctx context.Context, a *Approval) (int64, error)

func (SQLiteStore) InsertManifest

func (q SQLiteStore) InsertManifest(ctx context.Context, m *ManifestRecord) (int64, error)

func (SQLiteStore) ListApprovalsForManifest

func (q SQLiteStore) ListApprovalsForManifest(ctx context.Context, manifestID int64) ([]Approval, error)

func (SQLiteStore) ListPendingManifests

func (q SQLiteStore) ListPendingManifests(ctx context.Context) ([]ManifestRecord, error)

func (SQLiteStore) ListServers

func (q SQLiteStore) ListServers(ctx context.Context) ([]Server, error)

func (SQLiteStore) ListUndeliveredNotifications added in v0.1.1

func (q SQLiteStore) ListUndeliveredNotifications(ctx context.Context, minAttempts int) ([]OutboxRow, error)

func (SQLiteStore) MarkNotificationDelivered added in v0.1.1

func (q SQLiteStore) MarkNotificationDelivered(ctx context.Context, id int64, now time.Time) error

func (SQLiteStore) MarkNotificationFailed added in v0.1.1

func (q SQLiteStore) MarkNotificationFailed(ctx context.Context, id int64, nextAttempt time.Time, lastError string) error

func (SQLiteStore) UpdateManifestState

func (q SQLiteStore) UpdateManifestState(ctx context.Context, id int64, newState string) error

func (*SQLiteStore) WithTx

func (s *SQLiteStore) WithTx(ctx context.Context, fn func(Store) error) error

type Server

type Server struct {
	ID        int64
	Name      string
	Endpoint  string
	CreatedAt time.Time
}

type Store

type Store interface {
	CreateServer(ctx context.Context, name, endpoint string) (*Server, error)
	GetServerByName(ctx context.Context, name string) (*Server, error)
	GetServerByID(ctx context.Context, id int64) (*Server, error)
	ListServers(ctx context.Context) ([]Server, error)

	InsertManifest(ctx context.Context, m *ManifestRecord) (int64, error)
	GetManifestByHash(ctx context.Context, serverID int64, hash string) (*ManifestRecord, error)
	GetManifestByID(ctx context.Context, id int64) (*ManifestRecord, error)
	GetApprovedManifest(ctx context.Context, serverID int64) (*ManifestRecord, error)
	ListPendingManifests(ctx context.Context) ([]ManifestRecord, error)
	UpdateManifestState(ctx context.Context, id int64, newState string) error

	InsertApproval(ctx context.Context, a *Approval) (int64, error)
	ListApprovalsForManifest(ctx context.Context, manifestID int64) ([]Approval, error)

	// Notification outbox. EnqueueNotification is deliberately a plain
	// INSERT with no network or filesystem work of its own, so it can be
	// called inside the transaction that records a manifest without
	// lengthening it measurably. Nothing else in this interface may be
	// added to that transaction.
	EnqueueNotification(ctx context.Context, eventType string, manifestID int64) (int64, error)
	DueNotifications(ctx context.Context, now time.Time, maxAttempts, limit int) ([]OutboxRow, error)
	MarkNotificationDelivered(ctx context.Context, id int64, now time.Time) error
	MarkNotificationFailed(ctx context.Context, id int64, nextAttempt time.Time, lastError string) error
	ListUndeliveredNotifications(ctx context.Context, minAttempts int) ([]OutboxRow, error)

	WithTx(ctx context.Context, fn func(Store) error) error
}

Store is the persistence interface used by the approval workflow and API layers. Lookups return ErrNotFound when the row does not exist; no method returns a nil object with a nil error. Note what is deliberately absent: there is no method that updates a manifest's hash or canonical_json after insert. UpdateManifestState is the only mutation on an existing manifest row, so manifest content is physically immutable once written, not just immutable by convention.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL