driven

package
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: Apr 13, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package driven defines the driven port interfaces — the contracts that the core requires from its persistence and backup layers. Adapters (SQLite, in-memory, JSONL) implement these interfaces; the core depends only on the abstractions.

Index

Constants

View Source
const DefaultLimit = 20

DefaultLimit is the default maximum number of items for list operations when the caller does not specify a limit.

Variables

This section is empty.

Functions

func NormalizeLimit

func NormalizeLimit(limit int) int

NormalizeLimit applies the default limit when the caller passes zero. A zero limit is treated as "use the default", not "unlimited". To request all results without truncation, pass a negative limit.

Types

type BackupReader

type BackupReader interface {
	// ReadHeader reads and returns the backup metadata header. Must be
	// called exactly once before any NextRecord calls.
	ReadHeader() (domain.BackupHeader, error)

	// NextRecord returns the next issue record from the backup stream.
	// Returns false when no more records are available. If an error
	// occurs, the second return value is non-nil and iteration should
	// stop.
	NextRecord() (domain.BackupIssueRecord, bool, error)

	// Close releases resources held by the reader. The caller must
	// call Close when done reading, even if NextRecord returned an
	// error.
	Close() error
}

BackupReader deserialises a backup stream produced by a BackupWriter. Implementations must return the header first, then yield records one at a time via NextRecord until no more remain.

type BackupWriter

type BackupWriter interface {
	// WriteHeader writes the backup metadata header. Must be called
	// exactly once before any WriteRecord calls.
	WriteHeader(header domain.BackupHeader) error

	// WriteRecord writes a single issue record to the backup stream.
	WriteRecord(record domain.BackupIssueRecord) error

	// Close flushes any buffered data and releases resources. The
	// caller must call Close after all records have been written.
	Close() error
}

BackupWriter serialises a database snapshot to an external format. Implementations must be safe to call sequentially: WriteHeader once, then WriteRecord for each issue, then Close.

type ClaimRepository

type ClaimRepository interface {
	// CreateClaim persists a new claim.
	CreateClaim(ctx context.Context, c domain.Claim) error

	// GetClaimByIssue retrieves the active claim for an domain.
	// Returns domain.ErrNotFound if no active claim exists.
	GetClaimByIssue(ctx context.Context, issueID domain.ID) (domain.Claim, error)

	// GetClaimByID retrieves a claim by its claim ID.
	// Returns domain.ErrNotFound if not found.
	GetClaimByID(ctx context.Context, claimID string) (domain.Claim, error)

	// InvalidateClaim removes the active claim from an domain.
	InvalidateClaim(ctx context.Context, claimID string) error

	// UpdateClaimStaleAt updates the stale-at timestamp on a claim,
	// effectively extending the claim's lifetime. Replaces the former
	// UpdateClaimLastActivity and UpdateClaimThreshold methods.
	UpdateClaimStaleAt(ctx context.Context, claimID string, staleAt time.Time) error

	// ListStaleClaims returns all claims that are stale as of the given time.
	ListStaleClaims(ctx context.Context, now time.Time) ([]domain.Claim, error)

	// ListActiveClaims returns all claims that are not stale as of the given
	// time.
	ListActiveClaims(ctx context.Context, now time.Time) ([]domain.Claim, error)
}

ClaimRepository defines the persistence interface for claims.

type CommentFilter

type CommentFilter struct {
	// Author filters comments by author.
	Author domain.Author
	// Authors filters comments by any of the listed authors (OR'd).
	Authors []domain.Author
	// CreatedAfter filters to comments created after this timestamp.
	CreatedAfter time.Time
	// AfterCommentID filters to comments with ID greater than this.
	AfterCommentID int64
	// IssueID scopes the search to a specific issue (zero = global).
	IssueID domain.ID
	// IssueIDs scopes the search to specific issues (OR'd with IssueID).
	IssueIDs []domain.ID
	// ParentIDs scopes to comments on issues that are direct children of
	// the specified parents (OR'd with other issue scopes).
	ParentIDs []domain.ID
	// TreeIDs scopes to comments on all issues in the tree rooted at the
	// specified IDs — ancestors through descendants (OR'd with other scopes).
	TreeIDs []domain.ID
	// LabelFilters scopes to comments on issues matching these labels.
	LabelFilters []LabelFilter
	// FollowRefs expands the scope to include all issues referenced (via
	// relationships) by any issue already in scope.
	FollowRefs bool
}

CommentFilter defines filtering criteria for comment listings.

type CommentRepository

type CommentRepository interface {
	// CreateComment persists a new comment and returns the assigned ID.
	CreateComment(ctx context.Context, c domain.Comment) (int64, error)

	// GetComment retrieves a comment by ID. Returns domain.ErrNotFound if not found.
	GetComment(ctx context.Context, id int64) (domain.Comment, error)

	// ListComments returns comments for an issue with optional filters.
	// Limit semantics match IssueRepository.ListIssues.
	ListComments(ctx context.Context, issueID domain.ID, filter CommentFilter, limit int) (items []domain.Comment, hasMore bool, err error)

	// SearchComments performs full-text search on comment bodies.
	// Limit semantics match IssueRepository.ListIssues.
	SearchComments(ctx context.Context, query string, filter CommentFilter, limit int) (items []domain.Comment, hasMore bool, err error)
}

CommentRepository defines the persistence interface for comments.

type DatabaseRepository

type DatabaseRepository interface {
	// InitDatabase creates the database schema and stores the prefix.
	InitDatabase(ctx context.Context, prefix string) error

	// GetPrefix retrieves the stored prefix.
	GetPrefix(ctx context.Context) (string, error)

	// GC physically removes deleted (and optionally closed) issue data.
	// Returns the number of deleted issues removed and, when
	// includeClosedIssues is true, the number of closed issues removed.
	GC(ctx context.Context, includeClosedIssues bool) (deletedCount int, closedCount int, err error)

	// IntegrityCheck runs database-level integrity validation (e.g. SQLite
	// PRAGMA integrity_check). Returns nil if the database is healthy.
	IntegrityCheck(ctx context.Context) error

	// CountDeletedRatio returns the total number of issues and the number of
	// soft-deleted issues, for GC threshold calculations.
	CountDeletedRatio(ctx context.Context) (total, deleted int, err error)

	// CountVirtualLabelsInTable returns the number of rows in the labels
	// table where the key matches a virtual label key (e.g., "idempotency-key").
	// Virtual labels should be stored in their respective columns, not in
	// the labels table — any rows found indicate data integrity issues.
	CountVirtualLabelsInTable(ctx context.Context) (int, error)

	// ClearAllData removes all data from every table (issues, comments,
	// claims, relationships, history, labels, FTS, and metadata).
	// Used by restore to prepare a clean slate before inserting backup
	// data. Foreign-key constraints are temporarily disabled.
	ClearAllData(ctx context.Context) error

	// RestoreIssueRaw inserts a raw issue row without going through
	// domain constructors. Used by restore to faithfully recreate
	// arbitrary states (closed, deferred, deleted, etc.).
	RestoreIssueRaw(ctx context.Context, rec domain.BackupIssueRecord) error

	// RestoreCommentRaw inserts a comment row with an explicit ID,
	// bypassing the auto-increment assignment. Used by restore to
	// preserve original comment IDs.
	RestoreCommentRaw(ctx context.Context, issueID string, rec domain.BackupCommentRecord) error

	// RestoreClaimRaw inserts a claim row directly. Used by restore.
	RestoreClaimRaw(ctx context.Context, issueID string, rec domain.BackupClaimRecord) error

	// RestoreRelationshipRaw inserts a relationship row directly.
	// Used by restore.
	RestoreRelationshipRaw(ctx context.Context, sourceID string, rec domain.BackupRelationshipRecord) error

	// RestoreHistoryRaw inserts a history entry row with an explicit
	// ID. Used by restore to preserve original entry IDs and
	// revisions.
	RestoreHistoryRaw(ctx context.Context, issueID string, rec domain.BackupHistoryRecord) error

	// RestoreLabelRaw inserts a label row directly.
	// Used by restore.
	RestoreLabelRaw(ctx context.Context, issueID string, rec domain.BackupLabelRecord) error

	// RebuildFTS repopulates the full-text search tables from the
	// canonical data tables. Must be called after all issue and comment
	// data has been restored.
	RebuildFTS(ctx context.Context) error
}

DatabaseRepository defines database-level operations.

type HistoryFilter

type HistoryFilter struct {
	// Author filters entries by author.
	Author domain.Author
	// After filters entries created after this timestamp.
	After time.Time
	// Before filters entries created before this timestamp.
	Before time.Time
}

HistoryFilter defines filtering criteria for history listings.

type HistoryRepository

type HistoryRepository interface {
	// AppendHistory adds a history entry for an issue and returns the
	// assigned entry ID.
	AppendHistory(ctx context.Context, entry history.Entry) (int64, error)

	// ListHistory returns history entries for an issue with optional filters.
	// Limit semantics match IssueRepository.ListIssues.
	ListHistory(ctx context.Context, issueID domain.ID, filter HistoryFilter, limit int) (items []history.Entry, hasMore bool, err error)

	// CountHistory returns the number of history entries for an issue
	// (used to compute revision).
	CountHistory(ctx context.Context, issueID domain.ID) (int, error)

	// GetLatestHistory returns the most recent history entry for an issue
	// (used to derive the issue's current author).
	GetLatestHistory(ctx context.Context, issueID domain.ID) (history.Entry, error)
}

HistoryRepository defines the persistence interface for history entries.

type IssueFilter

type IssueFilter struct {
	// Roles filters by one or more issue roles (empty means no filter).
	Roles []domain.Role
	// States filters by one or more states (empty means no filter).
	States []domain.State
	// Ready filters to only ready issues when true.
	Ready bool
	// ParentIDs filters to children of one or more parent epics.
	// When multiple IDs are provided, issues matching any parent are included.
	ParentIDs []domain.ID
	// DescendantsOf recursively filters to all descendants of an domain.
	DescendantsOf domain.ID
	// AncestorsOf filters to the parent chain of an issue (up to the root).
	AncestorsOf domain.ID
	// LabelFilters specifies label-based filters.
	LabelFilters []LabelFilter
	// Orphan filters to issues that have no parent epic.
	Orphan bool
	// Blocked filters to issues that have at least one unresolved blocked_by
	// relationship (target is neither closed nor deleted).
	Blocked bool
	// ExcludeClosed hides closed issues from results when true. Ignored when
	// States explicitly includes StateClosed — an explicit state filter
	// represents intentional user selection and takes precedence.
	ExcludeClosed bool
	// IncludeDeleted includes soft-deleted issues when true.
	IncludeDeleted bool
}

IssueFilter defines filtering criteria for issue list and search.

type IssueListItem

type IssueListItem struct {
	ID        domain.ID
	Role      domain.Role
	State     domain.State
	Priority  domain.Priority
	Title     string
	ParentID  domain.ID
	CreatedAt time.Time
	IsDeleted bool
	// IsBlocked is true when the issue has at least one unresolved
	// blocked_by relationship or a blocked/deferred ancestor. This is a
	// computed display concern — the underlying state machine does not change.
	IsBlocked bool
	// BlockerIDs contains the IDs of non-closed issues that directly block
	// this issue via blocked_by relationships. Empty when IsBlocked is false.
	BlockerIDs []domain.ID
	// SecondaryState is the computed list-view secondary state for this item.
	// Populated by the service layer after retrieval from the repository.
	SecondaryState domain.SecondaryState
}

IssueListItem is a lightweight projection of an issue for list views.

func (IssueListItem) DisplayStatus

func (item IssueListItem) DisplayStatus() string

DisplayStatus returns the human-readable status for display purposes in the format "primary (secondary)" — e.g., "open (ready)", "open (blocked)", "deferred (blocked)". When no secondary state applies (claimed, closed), it returns just the primary state string.

type IssueOrderBy

type IssueOrderBy int

IssueOrderBy specifies the sort order for issue listings.

const (
	// OrderByPriority sorts by priority (highest urgency first), then by
	// family-anchored creation time (COALESCE of parent's and issue's
	// created_at), then by issue created_at within a family, then by
	// issue ID as a deterministic tiebreaker.
	OrderByPriority IssueOrderBy = iota

	// OrderByCreatedAt sorts by family-anchored creation time (oldest
	// family first), then by issue created_at within a family, then by
	// issue ID as a deterministic tiebreaker.
	OrderByCreatedAt

	// OrderByUpdatedAt sorts by family-anchored creation time (most
	// recent family first), then by issue created_at descending, then
	// by issue ID as a deterministic tiebreaker.
	OrderByUpdatedAt
)

type IssueRepository

type IssueRepository interface {
	// CreateIssue persists a new domain. Returns the created domain.
	CreateIssue(ctx context.Context, t domain.Issue) error

	// GetIssue retrieves an issue by ID. Returns domain.ErrNotFound if
	// not found or if soft-deleted (unless includeDeleted is true).
	GetIssue(ctx context.Context, id domain.ID, includeDeleted bool) (domain.Issue, error)

	// UpdateIssue persists changes to an existing domain.
	UpdateIssue(ctx context.Context, t domain.Issue) error

	// ListIssues returns a filtered, ordered list of issues. A positive limit
	// caps the result size; a negative limit returns all matching results.
	// The hasMore return value indicates whether additional results exist
	// beyond the limit.
	ListIssues(ctx context.Context, filter IssueFilter, orderBy IssueOrderBy, limit int) (items []IssueListItem, hasMore bool, err error)

	// SearchIssues performs full-text search on title, description, and
	// acceptance criteria. Limit semantics match ListIssues.
	SearchIssues(ctx context.Context, query string, filter IssueFilter, orderBy IssueOrderBy, limit int) (items []IssueListItem, hasMore bool, err error)

	// GetChildStatuses returns the completion-relevant status of all direct
	// children of an epic, for deriving epic completion.
	GetChildStatuses(ctx context.Context, epicID domain.ID) ([]domain.ChildStatus, error)

	// GetDescendants returns all descendants of an epic (recursively),
	// with claim status, for recursive deletion checks.
	GetDescendants(ctx context.Context, epicID domain.ID) ([]domain.DescendantInfo, error)

	// HasChildren reports whether an epic has any children.
	HasChildren(ctx context.Context, epicID domain.ID) (bool, error)

	// GetAncestorStatuses returns the states of all ancestor epics of a
	// issue, walking up the parent chain, for readiness propagation.
	GetAncestorStatuses(ctx context.Context, id domain.ID) ([]domain.AncestorStatus, error)

	// GetParentID returns the parent ID of an issue (for cycle detection).
	GetParentID(ctx context.Context, id domain.ID) (domain.ID, error)

	// IssueIDExists reports whether an issue ID already exists (for
	// collision detection during ID generation).
	IssueIDExists(ctx context.Context, id domain.ID) (bool, error)

	// ListDistinctLabels returns all unique label key-value pairs
	// across non-deleted issues.
	ListDistinctLabels(ctx context.Context) ([]domain.Label, error)

	// GetIssueByIdempotencyKey retrieves an issue by its idempotency key.
	// Returns domain.ErrNotFound if no issue exists with that key.
	GetIssueByIdempotencyKey(ctx context.Context, key string) (domain.Issue, error)

	// GetIssueSummary returns aggregate issue counts by primary state and
	// computed readiness/blocked status. Excludes soft-deleted issues. Ready
	// and blocked counts follow the same rules as the Ready and Blocked
	// filters in ListIssues.
	GetIssueSummary(ctx context.Context) (IssueSummary, error)
}

IssueRepository defines the persistence interface for issues.

type IssueSummary

type IssueSummary struct {
	Open     int
	Claimed  int
	Deferred int
	Closed   int
	Ready    int
	Blocked  int
}

IssueSummary holds aggregate counts of issues grouped by primary state and computed readiness/blocked status. Designed for dashboard display — avoids loading individual issues into memory.

func (IssueSummary) Total

func (s IssueSummary) Total() int

Total returns the total number of issues across all primary states.

type LabelFilter

type LabelFilter struct {
	// Key is the label key to match.
	Key string
	// Value is the label value to match. Empty for wildcard ("key:*").
	Value string
	// Negate inverts the filter — exclude issues matching this label.
	Negate bool
}

LabelFilter specifies a single label-based filter criterion.

type RelationshipRepository

type RelationshipRepository interface {
	// CreateRelationship creates a relationship if it does not already exist.
	// Returns true if created, false if it already existed (idempotent).
	CreateRelationship(ctx context.Context, rel domain.Relationship) (bool, error)

	// DeleteRelationship removes a relationship if it exists.
	// Returns true if deleted, false if it did not exist (idempotent).
	DeleteRelationship(ctx context.Context, sourceID, targetID domain.ID, relType domain.RelationType) (bool, error)

	// ListRelationships returns all relationships for an issue (both
	// directions).
	ListRelationships(ctx context.Context, issueID domain.ID) ([]domain.Relationship, error)

	// GetBlockerStatuses returns the blocker statuses for readiness checks.
	GetBlockerStatuses(ctx context.Context, issueID domain.ID) ([]domain.BlockerStatus, error)
}

RelationshipRepository defines the persistence interface for relationships.

type Transactor

type Transactor interface {
	// WithTransaction executes fn within a transaction. If fn returns nil,
	// the transaction is committed. If fn returns an error, the transaction
	// is rolled back and the error is returned.
	WithTransaction(ctx context.Context, fn func(uow UnitOfWork) error) error

	// WithReadTransaction executes fn within a read-only transaction.
	WithReadTransaction(ctx context.Context, fn func(uow UnitOfWork) error) error

	// Vacuum reclaims disk space and defragments the database file. Must be
	// called outside any transaction.
	Vacuum(ctx context.Context) error
}

Transactor provides a higher-level API for executing work within a transaction. It handles commit/rollback automatically.

type UnitOfWork

type UnitOfWork interface {
	// Issues returns the issue repository within this transaction.
	Issues() IssueRepository

	// Comments returns the comment repository within this transaction.
	Comments() CommentRepository

	// Claims returns the claim repository within this transaction.
	Claims() ClaimRepository

	// Relationships returns the relationship repository within this transaction.
	Relationships() RelationshipRepository

	// History returns the history repository within this transaction.
	History() HistoryRepository

	// Database returns the database-level repository within this transaction.
	Database() DatabaseRepository
}

UnitOfWork represents a transactional scope. All repository operations within a unit of work are atomic — they either all succeed or all fail.

type UnitOfWorkFactory

type UnitOfWorkFactory interface {
	// Begin starts a new unit of work (transaction). The caller must call
	// Commit or Rollback on the returned UnitOfWork.
	Begin(ctx context.Context) (UnitOfWork, error)

	// ReadOnly starts a read-only unit of work.
	ReadOnly(ctx context.Context) (UnitOfWork, error)
}

UnitOfWorkFactory creates new units of work.

Jump to

Keyboard shortcuts

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