sqlite

package
v0.1.6 Latest Latest
Warning

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

Go to latest
Published: Jun 26, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package sqlite provides SQLite implementations of the store ports (AgentStore, CertificateStore, EndpointStore, RenewalStore, RevocationStore, ByocCertificateStore) plus an outbox table used by the RA→TL HTTP client for durable event delivery.

The adapter uses modernc.org/sqlite — a pure-Go transpilation of upstream SQLite — so the binaries cross-compile without CGO. Schema migrations are embedded; on Open() the database is created if missing and migrations are applied. The embedded migration files live in migrations/*.sql relative to this package.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AgentStore

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

AgentStore implements port.AgentStore.

func NewAgentStore

func NewAgentStore(db *DB) *AgentStore

NewAgentStore returns a new SQLite-backed AgentStore.

func (*AgentStore) Delete

func (s *AgentStore) Delete(ctx context.Context, id int64) error

Delete removes a registration by ID.

func (*AgentStore) ExistsByAnsName

func (s *AgentStore) ExistsByAnsName(ctx context.Context, ansName domain.AnsName) (bool, error)

ExistsByAnsName returns true if any row uses the given ANS name.

func (*AgentStore) ExpireLapsedPendingValidation added in v0.1.6

func (s *AgentStore) ExpireLapsedPendingValidation(ctx context.Context, now time.Time) (int64, error)

ExpireLapsedPendingValidation flips lapsed PENDING_VALIDATION rows to EXPIRED in one guarded UPDATE and returns the count.

The WHERE clause is the concurrency guard: a row is expired only while it is still PENDING_VALIDATION, its challenge window (acme_challenge_expires_at_ms — shared between the legacy single-token era and the order era) has lapsed, AND its order is still PENDING. A concurrent verify-acme that advances the row (status → PENDING_DNS, or order → ISSUING/COMPLETED/FAILED) removes it from the match set, so the sweep can never clobber a successfully-validated registration or roll its order columns back. Rows with a NULL expiry (pre-challenge-persistence registrations) never match — they carry no window to expire on and remain cancellable instead. Legacy rows have a NULL cert_order_state and are matched by the IS NULL arm.

func (*AgentStore) FindAllByAgentHost

func (s *AgentStore) FindAllByAgentHost(ctx context.Context, host string) ([]*domain.AgentRegistration, error)

FindAllByAgentHost returns all registrations for a given FQDN, newest first.

func (*AgentStore) FindByAgentID

func (s *AgentStore) FindByAgentID(ctx context.Context, agentID string) (*domain.AgentRegistration, error)

FindByAgentID looks up a registration by agent UUID.

func (*AgentStore) FindByAnsName

func (s *AgentStore) FindByAnsName(ctx context.Context, ansName domain.AnsName) (*domain.AgentRegistration, error)

FindByAnsName looks up a registration by versioned ANS name.

func (*AgentStore) FindByID

func (s *AgentStore) FindByID(ctx context.Context, id int64) (*domain.AgentRegistration, error)

FindByID looks up a registration by primary key.

func (*AgentStore) FindExistingByFQDN

func (s *AgentStore) FindExistingByFQDN(ctx context.Context, fqdn string) ([]*domain.AgentRegistration, error)

FindExistingByFQDN returns ACTIVE or PENDING_* registrations for the FQDN.

func (*AgentStore) ListByOwner

func (s *AgentStore) ListByOwner(
	ctx context.Context,
	ownerID string,
	filter port.ListFilter,
) (*port.CursorPage[*domain.AgentRegistration], error)

ListByOwner returns a cursor-paginated list of owned agents.

func (*AgentStore) Save

func (s *AgentStore) Save(ctx context.Context, agent *domain.AgentRegistration) error

Save inserts or updates an AgentRegistration. Endpoints, server cert, and identity CSR are persisted via their dedicated tables — Save only writes the root aggregate row.

type ByocCertificateStore

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

ByocCertificateStore implements port.ByocCertificateStore.

func NewByocCertificateStore

func NewByocCertificateStore(db *DB) *ByocCertificateStore

NewByocCertificateStore returns a new SQLite-backed store.

func (*ByocCertificateStore) FindByAgentID

func (s *ByocCertificateStore) FindByAgentID(
	ctx context.Context,
	agentID string,
) ([]*domain.ByocServerCertificate, error)

FindByAgentID returns all BYOC certificates for the agent, newest first.

func (*ByocCertificateStore) FindLatestValidByAgentID

func (s *ByocCertificateStore) FindLatestValidByAgentID(
	ctx context.Context,
	agentID string,
) (*domain.ByocServerCertificate, error)

FindLatestValidByAgentID returns the most recently saved BYOC cert that is still within its validity period.

func (*ByocCertificateStore) Save

Save persists a BYOC server certificate.

type CertificateStore

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

CertificateStore implements port.CertificateStore.

func NewCertificateStore

func NewCertificateStore(db *DB) *CertificateStore

NewCertificateStore returns a new SQLite-backed CertificateStore.

func (*CertificateStore) FindCSRByID

func (s *CertificateStore) FindCSRByID(
	ctx context.Context,
	agentID, csrID string,
) (*domain.AgentCSR, error)

FindCSRByID returns the CSR with the given ID, scoped to the agent. Used both by the CSR-status handler (which already knows the agent from the URL) and by the registration flow (which refreshes the aggregate's embedded slot after a status change).

func (*CertificateStore) FindIdentityCertificatesByAgent

func (s *CertificateStore) FindIdentityCertificatesByAgent(
	ctx context.Context,
	agentID string,
) ([]*domain.StoredCertificate, error)

FindIdentityCertificatesByAgent returns all identity certificates for the agent.

func (*CertificateStore) FindLatestPendingCSRByType

func (s *CertificateStore) FindLatestPendingCSRByType(
	ctx context.Context,
	agentID string,
	csrType domain.CSRType,
) (*domain.AgentCSR, error)

FindLatestPendingCSRByType returns the newest PENDING CSR of the given type (IDENTITY | SERVER) for the agent, or (nil, nil) when no such row exists. Used by the verify-acme flow to pick up CSRs submitted at register time for signing once domain control is proven.

func (*CertificateStore) SaveCSR

func (s *CertificateStore) SaveCSR(ctx context.Context, agentID string, csr *domain.AgentCSR) error

SaveCSR persists a new or updated CSR (identity or server). Upserts on csr_id; status transitions (PENDING → SIGNED | REJECTED) replace the previous row in place.

func (*CertificateStore) SaveIdentityCertificate

func (s *CertificateStore) SaveIdentityCertificate(
	ctx context.Context,
	agentID string,
	cert *domain.StoredCertificate,
) error

SaveIdentityCertificate persists a newly issued identity certificate.

func (*CertificateStore) UpdateCertificateStatus

func (s *CertificateStore) UpdateCertificateStatus(
	ctx context.Context,
	cert *domain.StoredCertificate,
) error

UpdateCertificateStatus updates only the status field.

type DB

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

DB wraps a sqlx.DB and is shared by the repository implementations. Use Open to construct one; it applies migrations automatically.

func Open

func Open(ctx context.Context, path string) (*DB, error)

Open creates or opens the database at path and applies pending migrations. path == ":memory:" yields an in-memory DB (useful for tests).

func (*DB) Close

func (d *DB) Close() error

Close releases the underlying database handle.

func (*DB) DBX

func (d *DB) DBX() *sqlx.DB

DBX returns the underlying sqlx.DB for advanced use (transactions).

func (*DB) Run

func (d *DB) Run(ctx context.Context, fn func(ctx context.Context) error) (errR error)

Run implements port.UnitOfWork. It begins a transaction, threads it through fn's context, and commits or rolls back depending on fn's return.

Rollback errors are wrapped together with the original error so neither is lost — a rollback failure usually points at a deeper problem (deadlock detector firing, connection lost) and silently dropping it would mask the real diagnostic.

Implementation note: SQLite serializes writes inside the process (we cap MaxOpenConns at 1 in `Open`), so opening a transaction also serializes other writers against the same DB. Transactions are expected to be short-lived for that reason.

type EndpointStore

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

EndpointStore implements port.EndpointStore.

func NewEndpointStore

func NewEndpointStore(db *DB) *EndpointStore

NewEndpointStore returns a new SQLite-backed EndpointStore.

func (*EndpointStore) FindByAgentID

func (s *EndpointStore) FindByAgentID(ctx context.Context, agentID string) (*domain.AgentEndpoints, error)

FindByAgentID returns the endpoint collection for the given agent.

func (*EndpointStore) FindByAgentIDs

func (s *EndpointStore) FindByAgentIDs(
	ctx context.Context,
	agentIDs []string,
) (map[string]*domain.AgentEndpoints, error)

FindByAgentIDs returns endpoints for multiple agents in a single query.

func (*EndpointStore) Save

func (s *EndpointStore) Save(ctx context.Context, endpoints *domain.AgentEndpoints) error

Save upserts the endpoint collection for the given agent.

type IdentityLinkStore added in v0.1.6

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

IdentityLinkStore implements port.IdentityLinkStore.

func NewIdentityLinkStore added in v0.1.6

func NewIdentityLinkStore(db *DB) *IdentityLinkStore

NewIdentityLinkStore returns a new SQLite-backed IdentityLinkStore.

func (s *IdentityLinkStore) Link(ctx context.Context, identityID, agentID string, now time.Time) (bool, error)

Link upserts a live link for the pair. Idempotent: returns false (and no error) when the pair is already LINKED, so batch calls can skip already-linked agents in the sealed event.

func (*IdentityLinkStore) ListLiveByAgent added in v0.1.6

func (s *IdentityLinkStore) ListLiveByAgent(ctx context.Context, agentID string) ([]*domain.IdentityLink, error)

ListLiveByAgent returns the agent's live links.

func (*IdentityLinkStore) ListLiveByIdentity added in v0.1.6

func (s *IdentityLinkStore) ListLiveByIdentity(ctx context.Context, identityID string) ([]*domain.IdentityLink, error)

ListLiveByIdentity returns the identity's live links.

func (s *IdentityLinkStore) Unlink(ctx context.Context, identityID, agentID string, now time.Time) error

Unlink flips the live link to UNLINKED.

type IdentityStore added in v0.1.6

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

IdentityStore implements port.IdentityStore.

func NewIdentityStore added in v0.1.6

func NewIdentityStore(db *DB) *IdentityStore

NewIdentityStore returns a new SQLite-backed IdentityStore.

func (*IdentityStore) ClaimChallenge added in v0.1.6

func (s *IdentityStore) ClaimChallenge(ctx context.Context, identityID, nonce string, now, staleBefore time.Time) error

ClaimChallenge takes the short-TTL provisional claim that serializes concurrent verify-control attempts across the seal-before-success TL round trip (design §5.6.1). The conditional UPDATE succeeds only while the nonce is live, unconsumed, and not claimed by an attempt fresher than staleBefore — a crashed claimer's stale claim is reclaimable. A claim is NOT consumption.

func (*IdentityStore) ConsumeChallenge added in v0.1.6

func (s *IdentityStore) ConsumeChallenge(ctx context.Context, identityID, nonce string, now time.Time) error

ConsumeChallenge atomically consumes the live challenge nonce. The conditional UPDATE is the TOCTOU guard: only one of two concurrent verify-control calls can flip challenge_consumed_at_ms from NULL, and an expired or superseded nonce matches zero rows.

func (*IdentityStore) ExistsVerified added in v0.1.6

func (s *IdentityStore) ExistsVerified(ctx context.Context, kind domain.IdentifierKind, value string) (bool, error)

ExistsVerified reports whether any owner holds a VERIFIED row for (kind, value).

func (*IdentityStore) FindByID added in v0.1.6

func (s *IdentityStore) FindByID(ctx context.Context, identityID string) (*domain.VerifiedIdentity, error)

FindByID returns the identity with the given identityId.

func (*IdentityStore) FindLive added in v0.1.6

func (s *IdentityStore) FindLive(
	ctx context.Context,
	providerID string,
	kind domain.IdentifierKind,
	value string,
) (*domain.VerifiedIdentity, error)

FindLive returns the owner's non-REVOKED row for (kind, value).

func (*IdentityStore) ListByOwner added in v0.1.6

func (s *IdentityStore) ListByOwner(ctx context.Context, providerID string, limit int, cursor string) (*port.CursorPage[*domain.VerifiedIdentity], error)

ListByOwner returns every identity owned by the principal, newest first.

func (*IdentityStore) MarkRevoked added in v0.1.6

func (s *IdentityStore) MarkRevoked(ctx context.Context, identityID string, now time.Time) error

MarkRevoked is Revoke's conditional Phase C commit: flip VERIFIED → REVOKED, clearing the rotation stage and challenge state, only while the row is still VERIFIED — a verify/rotate that committed during the revoke's seal round trip is never overwritten with the revoker's stale snapshot. Zero rows → conflict (the caller's view was stale; the sealed IDENTITY_REVOKED is the benign residue the retry converges with).

func (*IdentityStore) ReleaseChallenge added in v0.1.6

func (s *IdentityStore) ReleaseChallenge(ctx context.Context, identityID, nonce string) error

ReleaseChallenge releases a provisional claim after a failed attempt (failed attempts never consume — §3.2). Best-effort: a consumed or superseded nonce matches zero rows, which is fine.

func (*IdentityStore) Save added in v0.1.6

Save upserts the aggregate. The partial unique indexes enforce the live-row and proven-uniqueness rules; violations surface as conflict errors (the service maps them to IDENTIFIER_DUPLICATE).

func (*IdentityStore) StageChallenge added in v0.1.6

func (s *IdentityStore) StageChallenge(
	ctx context.Context,
	v *domain.VerifiedIdentity,
	expectedStatus domain.IdentityStatus,
	expectedNonce string,
	staleBefore time.Time,
) error

StageChallenge persists a freshly issued challenge onto an existing row, conditional on the load-time snapshot: the status must be unchanged, the nonce must still be the one observed (a concurrent verify consumes it; a concurrent re-add supersedes it), and no fresh seal claim may be live (an in-flight verify must not have its nonce yanked mid-seal). Status, value, and verified_at are deliberately NOT written — issuing a challenge is not a state transition, so a racing commit can never be clobbered.

type OutboxEvent

type OutboxEvent struct {
	ID            int64
	EventType     string
	AgentID       string
	SchemaVersion string
	PayloadJSON   []byte
	Attempts      int
	LastError     string
	NextAttemptAt time.Time
	SentAt        time.Time
	CreatedAt     time.Time
}

OutboxEvent represents a row from the outbox_events table. It is not a port type (the outbox is an internal adapter concept) but it is used by the RA→TL client worker.

SchemaVersion names the envelope schema the RA serialized the inner-event payload for ("V1" or "V2"). The worker uses it to route to the matching TL ingest lane. Both the column and this field were added together (migration 003) so the worker never sees an empty value.

type OutboxStore

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

OutboxStore manages the outbox_events table used for durable RA→TL delivery.

func NewOutboxStore

func NewOutboxStore(db *DB) *OutboxStore

NewOutboxStore returns a new OutboxStore.

func (*OutboxStore) Claim

func (s *OutboxStore) Claim(ctx context.Context, batchSize int) ([]OutboxEvent, error)

Claim returns up to batchSize pending outbox events whose next_attempt_at_ms has passed. Callers process each event and then call MarkSent or MarkFailed. There is no explicit lease — we rely on the single-writer SQLite setup.

func (*OutboxStore) Enqueue

func (s *OutboxStore) Enqueue(
	ctx context.Context,
	eventType, agentID, schemaVersion string,
	payload []byte,
	earliestAttempt time.Time,
) (int64, error)

Enqueue appends a new outbox event. When the caller wraps this call inside `port.UnitOfWork.Run`, the row is written through the active transaction (via the context-threaded tx in `extx`) and commits or rolls back atomically with whatever domain-state writes preceded it — that's how we guarantee at-least-once delivery without a dual-write window.

schemaVersion must be "V1" or "V2"; the worker reads this value to pick the matching TL ingest lane. An empty value is rejected. Identity events do NOT ride the outbox — they seal synchronously (design §5.6.1: seal-before-success), so there is no "IDENTITY" lane here.

func (*OutboxStore) MarkFailed

func (s *OutboxStore) MarkFailed(ctx context.Context, id int64, attempts int, lastError string, maxDelay time.Duration) error

MarkFailed bumps the attempt counter and schedules the next retry using exponential backoff capped at maxDelay.

func (*OutboxStore) MarkSent

func (s *OutboxStore) MarkSent(ctx context.Context, id int64) error

MarkSent records that the event was successfully delivered.

type RenewalStore

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

RenewalStore implements port.RenewalStore.

func NewRenewalStore

func NewRenewalStore(db *DB) *RenewalStore

NewRenewalStore returns a new SQLite-backed RenewalStore.

func (*RenewalStore) Delete

func (s *RenewalStore) Delete(ctx context.Context, id int64) error

Delete removes a renewal by ID.

func (*RenewalStore) FindByAgentID

func (s *RenewalStore) FindByAgentID(ctx context.Context, agentID string) (*domain.ServerCertificateRenewal, error)

FindByAgentID returns the most recent renewal for the agent.

func (*RenewalStore) FindPendingByAgentID

func (s *RenewalStore) FindPendingByAgentID(ctx context.Context, agentID string) (*domain.ServerCertificateRenewal, error)

FindPendingByAgentID returns a pending (not-completed) renewal.

func (*RenewalStore) ListPendingExpired

func (s *RenewalStore) ListPendingExpired(ctx context.Context) ([]*domain.ServerCertificateRenewal, error)

ListPendingExpired returns renewals whose validation window has elapsed without verification.

func (*RenewalStore) Save

Save inserts or updates a renewal aggregate.

type RevocationStore

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

RevocationStore implements port.RevocationStore.

func NewRevocationStore

func NewRevocationStore(db *DB) *RevocationStore

NewRevocationStore returns a new SQLite-backed RevocationStore.

func (*RevocationStore) FindByAgentID

func (s *RevocationStore) FindByAgentID(ctx context.Context, agentID string) (*domain.AgentRevocation, error)

FindByAgentID returns the revocation record for an agent.

func (*RevocationStore) Save

Save persists a revocation record.

Jump to

Keyboard shortcuts

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