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
- func NormalizeLimit(limit int) int
- type BackupReader
- type BackupWriter
- type ClaimRepository
- type CommentFilter
- type CommentRepository
- type DatabaseRepository
- type HistoryFilter
- type HistoryRepository
- type IssueFilter
- type IssueListItem
- type IssueOrderBy
- type IssueRepository
- type IssueSummary
- type LabelFilter
- type RelationshipRepository
- type Transactor
- type UnitOfWork
- type UnitOfWorkFactory
Constants ¶
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 ¶
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 ¶
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.