Documentation
¶
Overview ¶
Package domain defines the core business types, error categories, and identity types for the nitpicking issue tracker. It includes the Issue entity and its associated value types (ID, State, Role, Priority, Label, Relationship), state machine transitions, readiness and completion derivation, parent hierarchy validation, soft-deletion rules, Author validation, agent name generation, agent instructions, backup serialisation structures (BackupHeader, BackupIssueRecord, etc.), and typed domain errors so that adapters (CLI, HTTP, etc.) can map them to appropriate exit codes or status codes without inspecting error messages.
Index ¶
- Constants
- Variables
- func GenerateAgentName() string
- func HashClaimID(token string) string
- func IsVirtualLabelKey(key string) bool
- func NormalizeCrockford(s string) string
- func ResetKeyGenerate() string
- func ResetKeyHash(key string) (string, error)
- func Transition(current, next State) error
- func ValidatePrefix(prefix string) error
- type AncestorStatus
- type Author
- type BackupClaimRecord
- type BackupCommentRecord
- type BackupFieldChangeRecord
- type BackupHeader
- type BackupHistoryRecord
- type BackupIssueRecord
- type BackupLabelRecord
- type BackupRelationshipRecord
- type BlockerStatus
- type ChildStatus
- type Claim
- type ClaimConflictError
- type Comment
- type DatabaseError
- type DescendantInfo
- type ID
- type Issue
- func (t Issue) AcceptanceCriteria() string
- func (t Issue) CreatedAt() time.Time
- func (t Issue) Description() string
- func (t Issue) ID() ID
- func (t Issue) IdempotencyKey() string
- func (t Issue) IsDeleted() bool
- func (t Issue) IsEpic() bool
- func (t Issue) IsTask() bool
- func (t Issue) Labels() LabelSet
- func (t Issue) ParentID() ID
- func (t Issue) Priority() Priority
- func (t Issue) Role() Role
- func (t Issue) State() State
- func (t Issue) Title() string
- func (t Issue) WithAcceptanceCriteria(ac string) Issue
- func (t Issue) WithDeleted() Issue
- func (t Issue) WithDescription(desc string) Issue
- func (t Issue) WithLabels(fs LabelSet) Issue
- func (t Issue) WithParentID(parentID ID) Issue
- func (t Issue) WithPriority(p Priority) Issue
- func (t Issue) WithState(s State) Issue
- func (t Issue) WithTitle(title string) (Issue, error)
- type Label
- type LabelSet
- type LineError
- type NewClaimParams
- type NewCommentParams
- type NewEpicParams
- type NewTaskParams
- type ParseError
- type Priority
- type RawLine
- type RelationType
- type Relationship
- type Role
- type SecondaryState
- type SecondaryStateResult
- type State
- type ValidatedRecord
- type ValidationError
- type ValidationResult
Constants ¶
const BackupAlgorithmVersion = 2
BackupAlgorithmVersion identifies the backup format version. Restore implementations dispatch on this value to select the correct deserialisation path.
Version history:
1 — initial format; included claim rows in the backup. 2 — claims are transient and excluded from backup.
const DefaultPriority = P2
DefaultPriority is the priority assigned to issues that do not specify one.
const DefaultStaleThreshold = 2 * time.Hour
DefaultStaleThreshold is the default duration after which a claim becomes stale and eligible for stealing.
const MaxStaleThreshold = 24 * time.Hour
MaxStaleThreshold is the maximum allowed stale threshold.
const VirtualKeyIdempotency = "idempotency-key"
Virtual label keys — convention: system/internal labels use hyphens
Virtual labels are backed by dedicated columns on the issues table, not by rows in the labels table. They appear in label output and support filtering, but reads and writes are redirected to their columns.
Naming convention: system/internal labels use hyphen-separated keys (e.g., "idempotency-key") to distinguish them from user-defined labels, which conventionally use colon-separated key:value pairs with short, alphanumeric keys (e.g., "kind:bug", "area:auth"). Hyphens are valid label key characters but are uncommon in user-defined keys, reducing collision risk. New virtual labels should follow the same pattern.
Variables ¶
var ( // ErrNotFound indicates a requested entity (issue, comment, etc.) does not // exist or has been soft-deleted. ErrNotFound = errors.New("not found") // ErrIllegalTransition indicates a state machine transition that violates // the defined transition rules. ErrIllegalTransition = errors.New("illegal state transition") // ErrCycleDetected indicates a parent assignment or relationship would // create a cycle in the hierarchy. ErrCycleDetected = errors.New("cycle detected") // ErrDeletedIssue indicates an operation was attempted on a soft-deleted // issue, which is immutable. ErrDeletedIssue = errors.New("issue is deleted") // ErrTerminalState indicates an operation was attempted on an issue in a // terminal state (closed or deleted) that forbids further mutations. ErrTerminalState = errors.New("issue is in a terminal state") // ErrDepthExceeded indicates a parent assignment would exceed the maximum // hierarchy depth (3 levels). ErrDepthExceeded = errors.New("hierarchy depth exceeded") // ErrStaleClaim indicates an operation was attempted with a claim that has // passed its stale-at timestamp. The caller must re-claim the issue before // retrying the operation. ErrStaleClaim = errors.New("claim is stale") // ErrSchemaMigrationRequired indicates the database is at an older schema // version (v1) and must be upgraded before most commands can operate. The // caller should instruct the user to run 'np admin upgrade'. ErrSchemaMigrationRequired = errors.New("database schema migration required") )
Sentinel errors for domain failure categories. Adapters use errors.Is to classify these into exit codes or HTTP status codes.
Functions ¶
func GenerateAgentName ¶
func GenerateAgentName() string
GenerateAgentName produces a Docker-style random agent name in the format "adjective-noun-modifier" (e.g., "dashing-storage-glitter"). Each invocation returns a fresh random name using the package-level PCG generator.
func HashClaimID ¶
HashClaimID computes the SHA-512 hash of a claim token after Crockford Base32 normalization. The token is normalized (lowercased, with I/L→1 and O→0 substitutions applied) before hashing, so that confusable variants of the same token produce the same hash. Returns the hex-encoded hash string.
func IsVirtualLabelKey ¶
IsVirtualLabelKey reports whether the given label key is backed by a column on the issues table rather than the labels table.
func NormalizeCrockford ¶
NormalizeCrockford applies Crockford Base32 decoding normalization to s. Per the spec (https://www.crockford.com/base32.html), decoding is case-insensitive and maps commonly confused characters to their intended values:
- I/i → 1
- L/l → 1
- O/o → 0
All other alphabetic characters are lowercased. Digits pass through unchanged. The result is suitable for validation against the canonical Crockford alphabet.
func ResetKeyGenerate ¶
func ResetKeyGenerate() string
ResetKeyGenerate produces a cryptographically random reset key encoded as lowercase Crockford Base32. Uses math/rand/v2, which is backed by crypto/rand by default in Go 1.22+. The key encodes 128 bits of entropy — identical to the claim ID generation algorithm.
func ResetKeyHash ¶
ResetKeyHash computes the SHA-512 hash of the binary value encoded by a Crockford Base32 reset key. The key is first normalized (lowercased, with I/L→1 and O→0 substitutions) and then decoded back to 128-bit binary before hashing. Returns the hex-encoded hash string. Returns an error if the key is not a valid 26-character Crockford Base32 string.
func Transition ¶
Transition validates a state transition for any issue role. Returns ErrIllegalTransition if the transition is not allowed, or ErrTerminalState if the current state is terminal.
func ValidatePrefix ¶
ValidatePrefix checks that a prefix is 1–10 uppercase ASCII letters.
Types ¶
type AncestorStatus ¶
type AncestorStatus struct {
// ID identifies the ancestor issue.
ID ID
// State is the ancestor's current state.
State State
// IsBlocked is true when the ancestor has at least one unresolved
// blocked_by relationship. A blocked ancestor gates readiness for
// all descendants, mirroring the behavior of deferred ancestors.
IsBlocked bool
}
AncestorStatus summarizes an ancestor's state for readiness propagation.
type Author ¶
type Author struct {
// contains filtered or unexported fields
}
Author represents a validated, NFC-normalized author identifier. Authors are case-sensitive — "alice" and "Alice" are distinct. An Author is immutable after construction.
func NewAuthor ¶
NewAuthor validates and NFC-normalizes the given string into an Author.
Validation rules (per §4.8):
- At least one alphanumeric character.
- Maximum 64 Unicode runes (measured after normalization).
- No whitespace — no Unicode whitespace characters.
func (Author) Equal ¶
Equal reports whether two authors are identical. Comparison is case-sensitive on the NFC-normalized form.
type BackupClaimRecord ¶
type BackupClaimRecord struct {
// ClaimSHA512 is the hex-encoded SHA-512 hash of the original claim
// token. The plaintext token is not recoverable from the backup.
ClaimSHA512 string `json:"claim_sha512"`
// Author is the claim holder's name.
Author string `json:"author"`
// StaleThreshold is the claim duration in nanoseconds, derived from
// StaleAt minus ClaimedAt. The JSON field name is retained for backup
// format compatibility.
StaleThreshold int64 `json:"stale_threshold"`
// LastActivity is the timestamp when the claim was created (claimedAt).
// The JSON field name is retained for backup format compatibility.
LastActivity time.Time `json:"last_activity"`
}
BackupClaimRecord is a snapshot of an active claim on an issue.
type BackupCommentRecord ¶
type BackupCommentRecord struct {
// CommentID is the auto-increment integer ID.
CommentID int64 `json:"comment_id"`
// Author is the comment author's name.
Author string `json:"author"`
// CreatedAt is the comment creation timestamp.
CreatedAt time.Time `json:"created_at"`
// Body is the comment text.
Body string `json:"body"`
}
BackupCommentRecord is a snapshot of a single comment.
type BackupFieldChangeRecord ¶
type BackupFieldChangeRecord struct {
// Field is the name of the changed field.
Field string `json:"field"`
// Before is the value before the change.
Before string `json:"before"`
// After is the value after the change.
After string `json:"after"`
}
BackupFieldChangeRecord is a single field-level before/after pair within a history entry.
type BackupHeader ¶
type BackupHeader struct {
// Prefix is the issue-ID prefix of the source database (e.g. "NP").
Prefix string `json:"prefix"`
// Timestamp is the UTC moment the backup was initiated.
Timestamp time.Time `json:"timestamp"`
// Version is the backup algorithm version. Restore implementations
// use this to select the correct deserialisation logic.
Version int `json:"version"`
}
BackupHeader is the first record in a backup file. It contains metadata about the backup itself — when it was taken, from which database prefix, and which algorithm version was used to produce it.
type BackupHistoryRecord ¶
type BackupHistoryRecord struct {
// EntryID is the auto-increment integer ID.
EntryID int64 `json:"entry_id"`
// Revision is the zero-based revision index within the issue's
// history.
Revision int `json:"revision"`
// Author is the actor who performed the mutation.
Author string `json:"author"`
// Timestamp is when the mutation occurred.
Timestamp time.Time `json:"timestamp"`
// EventType is the event type string (e.g. "created", "updated").
EventType string `json:"event_type"`
// Changes is the list of field-level changes recorded by this entry.
Changes []BackupFieldChangeRecord `json:"changes"`
}
BackupHistoryRecord is a snapshot of a single history entry.
type BackupIssueRecord ¶
type BackupIssueRecord struct {
// IssueID is the unique identifier (e.g. "NP-a3bxr").
IssueID string `json:"issue_id"`
// Role is "task" or "epic".
Role string `json:"role"`
// Title is the issue title.
Title string `json:"title"`
// Description is the issue body text.
Description string `json:"description"`
// AcceptanceCriteria is the issue's acceptance criteria text.
AcceptanceCriteria string `json:"acceptance_criteria"`
// Priority is the priority string (e.g. "P1", "P2").
Priority string `json:"priority"`
// State is the lifecycle state (e.g. "open", "closed", "deferred").
State string `json:"state"`
// ParentID is the parent epic's ID, or empty when unparented.
ParentID string `json:"parent_id"`
// CreatedAt is the issue creation timestamp in RFC 3339 with
// nanosecond precision.
CreatedAt time.Time `json:"created_at"`
// IdempotencyKey is the optional deduplication key set at creation.
IdempotencyKey string `json:"idempotency_key,omitempty"`
// Labels is the set of key–value label pairs attached to the issue.
Labels []BackupLabelRecord `json:"labels"`
// Comments is the ordered list of comments on the issue.
Comments []BackupCommentRecord `json:"comments"`
// Relationships is the list of relationships where this issue is
// the source.
Relationships []BackupRelationshipRecord `json:"relationships"`
// Claims is the list of active claims on the issue. Typically zero
// or one, but modelled as a slice for forward-compatibility.
Claims []BackupClaimRecord `json:"claims"`
// History is the ordered list of history entries for the issue.
History []BackupHistoryRecord `json:"history"`
}
BackupIssueRecord is a self-contained snapshot of a single issue and all of its associated data: labels, comments, relationships, claims, and history. Each non-deleted issue in the database produces exactly one BackupIssueRecord in the backup.
type BackupLabelRecord ¶
BackupLabelRecord is a single key–value label on an issue.
type BackupRelationshipRecord ¶
type BackupRelationshipRecord struct {
// TargetID is the ID of the related issue.
TargetID string `json:"target_id"`
// RelType is the relationship type string (e.g. "blocked_by").
RelType string `json:"rel_type"`
}
BackupRelationshipRecord is a snapshot of a single directed relationship.
type BlockerStatus ¶
type BlockerStatus struct {
// IsClosed is true if the blocker is closed (resolved).
IsClosed bool
// IsDeleted is true if the blocker has been soft-deleted.
IsDeleted bool
}
BlockerStatus summarizes a blocked_by target's state for readiness checks.
type ChildStatus ¶
type ChildStatus struct {
// State is the child's current state.
State State
// IsBlocked is true when the child has at least one unresolved
// blocked_by relationship. Used by epic progress bars to distinguish
// open-and-blocked from open-and-ready.
IsBlocked bool
}
ChildStatus summarizes a child issue's state for parent-close validation and epic progress computation.
type Claim ¶
type Claim struct {
// contains filtered or unexported fields
}
Claim represents active ownership of an issue. Claims are immutable value objects — "extending" or "updating stale deadline" produces a new Claim.
A claim carries two identifiers: the token (Crockford Base32 string returned to the caller) and the hash ID (SHA-512 hash of the token's raw bytes, hex-encoded, used for database storage and lookups). For freshly created claims, both are populated. For claims reconstructed from the database, only the hash ID is available — the plaintext token is not recoverable.
func NewClaim ¶
func NewClaim(p NewClaimParams) (Claim, error)
NewClaim creates a new claim with a randomly generated claim ID.
func ReconstructClaim ¶
func ReconstructClaim(id string, issueID ID, author Author, claimedAt time.Time, staleAt time.Time) Claim
ReconstructClaim rebuilds a Claim from persisted data without generating a new ID. Used by the storage layer when loading claims from the database.
func (Claim) ID ¶
ID returns the SHA-512 hash of the claim token, hex-encoded. This is the value stored in the database and used for all persistence operations.
type ClaimConflictError ¶
type ClaimConflictError struct {
// IssueID is the ID of the issue that could not be claimed.
IssueID string
// CurrentHolder is the author who holds the active claim.
CurrentHolder string
// StaleAt is the timestamp at which the current claim becomes stale
// and eligible for stealing.
StaleAt time.Time
}
ClaimConflictError indicates an issue is already claimed and the claim is not stale. It carries structured context so that callers (especially AI agents) can decide whether to wait or steal.
func (*ClaimConflictError) Error ¶
func (e *ClaimConflictError) Error() string
Error returns a human-readable description of the claim conflict.
func (*ClaimConflictError) Is ¶
func (e *ClaimConflictError) Is(target error) bool
Is reports whether target is a *ClaimConflictError, enabling errors.Is checks against a nil *ClaimConflictError sentinel.
type Comment ¶
type Comment struct {
// contains filtered or unexported fields
}
Comment represents a comment attached to an issue. Comments are immutable after creation. IDs are auto-assigned sequential integers, displayed as "comment-<integer>" (e.g., "comment-368"). IDs are global across the database, not scoped per issue.
func NewComment ¶
func NewComment(p NewCommentParams) (Comment, error)
NewComment creates a validated Comment. The body must be non-empty.
type DatabaseError ¶
type DatabaseError struct {
// Op describes the operation that failed (e.g., "create issue", "begin transaction").
Op string
// Err is the underlying storage error.
Err error
}
DatabaseError wraps a storage-layer error with additional context. The adapter layer maps this to exit code 5.
func (*DatabaseError) Error ¶
func (e *DatabaseError) Error() string
Error returns the operation and underlying error message.
func (*DatabaseError) Is ¶
func (e *DatabaseError) Is(target error) bool
Is reports whether target is a *DatabaseError, enabling errors.Is checks against a nil *DatabaseError sentinel.
func (*DatabaseError) Unwrap ¶
func (e *DatabaseError) Unwrap() error
Unwrap returns the underlying error for use with errors.Is and errors.As.
type DescendantInfo ¶
type DescendantInfo struct {
// ID is the descendant's issue ID.
ID ID
// IsClaimed is true if the descendant is currently claimed.
IsClaimed bool
// ClaimedBy is the author of the active claim, if any.
ClaimedBy string
}
DescendantInfo describes a descendant issue for recursive deletion checks.
type ID ¶
type ID struct {
// contains filtered or unexported fields
}
ID represents an issue identifier in the form PREFIX-random (e.g., "NP-a3bxr"). The prefix is uppercase ASCII letters; the random portion is lowercase Crockford Base32 characters. IDs are immutable after construction.
func GenerateID ¶
GenerateID creates a new random issue ID with the given prefix. The collisionCheck callback returns true if the generated ID already exists; on collision, the function regenerates and retries up to maxRetries times.
func ParseID ¶
ParseID parses a string of the form "PREFIX-random" into an ID. It validates that the prefix is 1–10 uppercase ASCII letters and the random portion is exactly 5 lowercase Crockford Base32 characters.
func ResolveID ¶
ResolveID parses an issue ID string that may be either a full ID (PREFIX-random) or a bare random part (just the 5-char Crockford string). If the input contains a separator, it is parsed as a full ID. Otherwise, the given prefix is prepended and the result is parsed as a full ID.
type Issue ¶
type Issue struct {
// contains filtered or unexported fields
}
Issue represents the core domain entity — either an Epic or a Task. Issues are immutable after construction; all mutation methods return a new Issue value. Revision and author are derived from history at read time, not stored on the struct.
func NewEpic ¶
func NewEpic(p NewEpicParams) (Issue, error)
NewEpic creates a new epic issue. The title must contain at least one alphanumeric character. The ID must be valid. Priority defaults to P2 if zero.
func NewTask ¶
func NewTask(p NewTaskParams) (Issue, error)
NewTask creates a new task issue. The title must contain at least one alphanumeric character. The ID must be valid. Priority defaults to P2 if zero.
func (Issue) AcceptanceCriteria ¶
AcceptanceCriteria returns the issue's acceptance criteria.
func (Issue) Description ¶
Description returns the issue's description.
func (Issue) IdempotencyKey ¶
IdempotencyKey returns the optional idempotency key used at creation.
func (Issue) WithAcceptanceCriteria ¶
WithAcceptanceCriteria returns a new issue with the updated acceptance criteria.
func (Issue) WithDeleted ¶
WithDeleted returns a new issue marked as soft-deleted.
func (Issue) WithDescription ¶
WithDescription returns a new issue with the updated description.
func (Issue) WithLabels ¶
WithLabels returns a new issue with the updated label set.
func (Issue) WithParentID ¶
WithParentID returns a new issue with the updated parent epic ID. Pass a zero ID to remove the parent.
func (Issue) WithPriority ¶
WithPriority returns a new issue with the updated priority.
type Label ¶
type Label struct {
// contains filtered or unexported fields
}
Label represents a validated key–value pair attached to an issue for filtering and agent coordination.
type LabelSet ¶
type LabelSet struct {
// contains filtered or unexported fields
}
LabelSet is an ordered collection of labels with unique keys. Setting an existing key overwrites the previous value. LabelSet is immutable — all mutation methods return a new LabelSet.
func LabelSetFrom ¶
LabelSetFrom creates a LabelSet from a slice of Labels. If duplicate keys appear, the last value wins.
func (LabelSet) All ¶
All returns an iterator over all labels in the set. Iteration order is not guaranteed.
type LineError ¶
LineError represents a validation error on a specific line of the import file. Line is the zero-based index into the input slice.
type NewClaimParams ¶
type NewClaimParams struct {
IssueID ID
Author Author
StaleDuration time.Duration
// StaleAt is an optional absolute timestamp at which the claim becomes
// stale. When non-zero, it takes precedence over StaleDuration. The
// caller is responsible for ensuring StaleAt is in the future and within
// MaxStaleThreshold of Now.
StaleAt time.Time
Now time.Time
}
NewClaimParams holds the parameters for creating a new claim.
type NewCommentParams ¶
NewCommentParams holds the parameters for creating a new comment.
type NewEpicParams ¶
type NewEpicParams struct {
ID ID
Title string
Description string
AcceptanceCriteria string
Priority Priority
ParentID ID
Labels LabelSet
CreatedAt time.Time
IdempotencyKey string
}
NewEpicParams holds the required and optional parameters for creating a new epic.
type NewTaskParams ¶
type NewTaskParams struct {
ID ID
Title string
Description string
AcceptanceCriteria string
Priority Priority
ParentID ID
Labels LabelSet
CreatedAt time.Time
IdempotencyKey string
}
NewTaskParams holds the required and optional parameters for creating a new task.
type ParseError ¶
ParseError represents a JSON parsing error on a specific line of the import file. Line is the 1-based line number.
func (ParseError) Unwrap ¶
func (e ParseError) Unwrap() error
Unwrap returns the underlying error for use with errors.Is/As.
type Priority ¶
type Priority int
Priority represents the urgency of an issue. Lower numbers indicate higher urgency. The default priority is P2.
const ( // P0 is the highest urgency — critical, drop-everything priority. // The enum starts at iota+1 so that the zero value of Priority is not a // valid priority, allowing constructors to distinguish "not set" from P0. P0 Priority = iota + 1 // P1 is high urgency — should be addressed soon. P1 // P2 is normal urgency — the default for new issues. P2 // P3 is low urgency — address when convenient. P3 // P4 is the lowest urgency — nice-to-have. P4 )
func ParsePriority ¶
ParsePriority parses a priority string into a Priority. Accepts canonical form ("P0"–"P4"), lowercase ("p0"–"p4"), and bare numeric ("0"–"4").
func (Priority) IsHigherThan ¶
IsHigherThan reports whether p has higher urgency than other. Lower numeric value means higher urgency.
type RawLine ¶
type RawLine struct {
IdempotencyKey string `json:"idempotency_key"`
Role string `json:"role"`
Title string `json:"title"`
Description string `json:"description"`
AcceptanceCriteria string `json:"acceptance_criteria"`
Priority string `json:"priority"`
State string `json:"state"`
Author string `json:"author"`
Comment string `json:"comment"`
Claim bool `json:"claim"`
Labels map[string]string `json:"labels"`
Parent string `json:"parent"`
BlockedBy []string `json:"blocked_by"`
Blocks []string `json:"blocks"`
Refs []string `json:"refs"`
}
RawLine represents a single parsed but unvalidated line from a JSONL import file. Field names match the JSON schema defined in the JSONL import format specification. All fields are strings or string collections to defer validation to the Validate function.
type RelationType ¶
type RelationType int
RelationType represents the kind of relationship between two issues.
const ( // RelBlockedBy indicates the source issue cannot make progress until // the target issue is closed. RelBlockedBy RelationType = iota + 1 // RelBlocks is the inverse of RelBlockedBy — the source blocks the target. RelBlocks // RelCites indicates the source issue references the target as relevant // context. RelCites // RelCitedBy is the inverse of RelCites — the source is cited by the target. RelCitedBy // RelRefs indicates the two issues are contextually related. Unlike // cites/cited_by, refs is symmetric — there is no directional distinction. // If A refs B, then B refs A implicitly. RelRefs // RelParentOf indicates the source issue is the parent epic of the target. // This type is synthetic — it is not stored in the relationships table but // derived from the issues.parent_id column for display purposes. RelParentOf // RelChildOf indicates the source issue is a child of the target epic. // This type is synthetic — derived from issues.parent_id for display. RelChildOf )
func ParseRelationType ¶
func ParseRelationType(s string) (RelationType, error)
ParseRelationType parses a relationship type string into a RelationType.
func (RelationType) Inverse ¶
func (rt RelationType) Inverse() RelationType
Inverse returns the inverse relationship type.
func (RelationType) IsSymmetric ¶
func (rt RelationType) IsSymmetric() bool
IsSymmetric reports whether the relationship type is symmetric — i.e., if A relates to B, then B implicitly relates to A with the same type.
func (RelationType) String ¶
func (rt RelationType) String() string
String returns the canonical string representation.
type Relationship ¶
type Relationship struct {
// contains filtered or unexported fields
}
Relationship represents a directional link between two issues.
func NewRelationship ¶
func NewRelationship(sourceID, targetID ID, relType RelationType) (Relationship, error)
NewRelationship creates a validated relationship. It rejects self-relationships.
func SyntheticRelationship ¶
func SyntheticRelationship(sourceID, targetID ID, relType RelationType) Relationship
SyntheticRelationship creates a relationship for display purposes that is not stored in the relationships table. Used for parent-child links derived from issues.parent_id.
func (Relationship) SourceID ¶
func (r Relationship) SourceID() ID
SourceID returns the ID of the issue initiating the relationship.
func (Relationship) TargetID ¶
func (r Relationship) TargetID() ID
TargetID returns the ID of the issue referenced by the relationship.
func (Relationship) Type ¶
func (r Relationship) Type() RelationType
Type returns the relationship type.
type Role ¶
type Role int
Role identifies whether an issue is an Epic (organizer) or a Task (leaf work unit). Role is immutable after creation.
type SecondaryState ¶
type SecondaryState int
SecondaryState qualifies the primary state with additional context about an issue's readiness, progress, or blocking status. The zero value (SecondaryNone) indicates no secondary qualifier — used for closed issues or when the primary state does not warrant a qualifier.
const ( // SecondaryNone indicates no secondary state. Used for closed issues or // when the primary state does not warrant a qualifier. SecondaryNone SecondaryState = iota // SecondaryClaimed indicates an open issue has an active (non-stale) claim. // Claimed takes precedence over ready and blocked in display priority. SecondaryClaimed // SecondaryReady indicates an issue is available for work (tasks) or // decomposition (epics with no children). SecondaryReady // SecondaryBlocked indicates the issue has unresolved blockers or a // blocked/deferred ancestor. SecondaryBlocked // SecondaryUnplanned indicates an epic has no children and needs // decomposition. Used in detail views alongside SecondaryBlocked when // an unplanned epic is also blocked. SecondaryUnplanned // SecondaryActive indicates an epic has children but not all are closed. SecondaryActive // SecondaryCompleted indicates an epic has children and all are closed. SecondaryCompleted )
func ParseSecondaryState ¶
func ParseSecondaryState(s string) (SecondaryState, error)
ParseSecondaryState parses a secondary state string. Parsing is case-sensitive. The empty string is not accepted — callers should check for SecondaryNone explicitly rather than parsing it.
func (SecondaryState) String ¶
func (s SecondaryState) String() string
String returns the canonical string representation. Returns an empty string for SecondaryNone.
type SecondaryStateResult ¶
type SecondaryStateResult struct {
// ListState is the single secondary state for list views, chosen by
// priority: completed > claimed > blocked > ready > active.
ListState SecondaryState
// DetailStates is the ordered set of secondary conditions applicable in
// detail views. May contain multiple entries (e.g., [blocked, active]
// for a blocked epic with children).
DetailStates []SecondaryState
}
SecondaryStateResult carries the computed secondary state for both list and detail views. ListState is a single secondary state chosen by priority rules for compact list displays. DetailStates is the full set of applicable secondary conditions for rich detail views.
func (SecondaryStateResult) HasSecondary ¶
func (r SecondaryStateResult) HasSecondary() bool
HasSecondary reports whether this result carries any secondary state.
type State ¶
type State int
State represents the lifecycle state of an issue. All issue roles (task and epic) share the same state machine.
func DefaultState ¶
func DefaultState() State
DefaultState returns the initial state for any newly created issue.
func ParseState ¶
ParseState parses a state string into a State. Parsing is case-sensitive.
func (State) IsTerminal ¶
IsTerminal reports whether the state is terminal — no further transitions are allowed. No states are currently terminal; closed issues can be reopened. "Deleted" is a separate concept checked independently.
type ValidatedRecord ¶
type ValidatedRecord struct {
IdempotencyKey string
Role Role
Title string
Description string
AcceptanceCriteria string
Priority Priority
State State
Author string
Comment string
// Claim indicates the imported issue should be immediately claimed after
// creation. Only valid when State is open; the import service returns an
// error if Claim is true for a non-open record.
Claim bool
Labels []Label
Parent string
BlockedBy []string
Blocks []string
Refs []string
}
ValidatedRecord is a successfully validated import line with parsed domain types. It retains all data needed by the import pass to create issues and relationships without re-parsing.
type ValidationError ¶
type ValidationError struct {
// Fields maps field names to human-readable descriptions of why
// validation failed. Multiple fields may fail in a single operation.
Fields map[string]string
}
ValidationError carries structured detail about which fields failed validation and why, enabling self-describing error responses per §9.
func NewMultiValidationError ¶
func NewMultiValidationError(fields map[string]string) *ValidationError
NewMultiValidationError creates a ValidationError for multiple fields.
func NewValidationError ¶
func NewValidationError(field, reason string) *ValidationError
NewValidationError creates a ValidationError for a single field.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
Error returns a summary of all validation failures.
func (*ValidationError) Is ¶
func (e *ValidationError) Is(target error) bool
Is reports whether target is a *ValidationError, enabling errors.Is checks against a nil *ValidationError sentinel.
type ValidationResult ¶
type ValidationResult struct {
Errors []LineError
Records []ValidatedRecord
}
ValidationResult holds the outcome of validating an entire import file. Errors contains per-line validation failures. Records contains the successfully validated lines — only populated for lines with no errors.
func Validate ¶
func Validate(lines []RawLine, prefix string) ValidationResult
Validate performs a two-pass validation of the given import lines.
Pass 1 collects all idempotency keys and their line indices, detecting duplicates. Pass 2 validates each line's fields and resolves references.
The prefix parameter is the database's issue ID prefix (e.g., "NP"), used to distinguish issue ID references from idempotency key references.
func (ValidationResult) HasErrors ¶
func (r ValidationResult) HasErrors() bool
HasErrors reports whether the validation produced any errors.