feat(types): add tombstone support for inline soft-delete (bd-fbj)
Add tombstone types and schema migration as foundation for the tombstone epic (bd-vw8) which replaces deletions.jsonl with inline tombstones. Changes: - Add tombstone fields to Issue struct: DeletedAt, DeletedBy, DeleteReason, OriginalType - Add StatusTombstone constant and IsTombstone() helper method - Update Status.IsValid() to accept tombstone status - Create migration 018_tombstone_columns.go for new database columns - Update schema.go with tombstone columns: deleted_at, deleted_by, delete_reason, original_type - Update all issue insert/update/scan operations across: - issues.go (insertIssue, insertIssues) - queries.go (GetIssue, GetIssueByExternalRef, SearchIssues) - dependencies.go (scanIssues, scanIssuesWithDependencyType) - transaction.go (scanIssueRow, GetIssue, SearchIssues) - multirepo.go (import operations) - ready.go (GetReadyWork, GetStaleIssues) - labels.go (GetIssuesByLabel) - Add test for IsTombstone() helper - Update migration test to include tombstone columns Unblocks: bd-olt (TTL logic), bd-3b4 (delete command), bd-0ih (merge updates), bd-dve (import/export) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -224,6 +224,7 @@ func (s *SQLiteStorage) GetDependenciesWithMetadata(ctx context.Context, issueID
|
||||
SELECT i.id, i.content_hash, i.title, i.description, i.design, i.acceptance_criteria, i.notes,
|
||||
i.status, i.priority, i.issue_type, i.assignee, i.estimated_minutes,
|
||||
i.created_at, i.updated_at, i.closed_at, i.external_ref, i.source_repo,
|
||||
i.deleted_at, i.deleted_by, i.delete_reason, i.original_type,
|
||||
d.type
|
||||
FROM issues i
|
||||
JOIN dependencies d ON i.id = d.depends_on_id
|
||||
@@ -244,6 +245,7 @@ func (s *SQLiteStorage) GetDependentsWithMetadata(ctx context.Context, issueID s
|
||||
SELECT i.id, i.content_hash, i.title, i.description, i.design, i.acceptance_criteria, i.notes,
|
||||
i.status, i.priority, i.issue_type, i.assignee, i.estimated_minutes,
|
||||
i.created_at, i.updated_at, i.closed_at, i.external_ref, i.source_repo,
|
||||
i.deleted_at, i.deleted_by, i.delete_reason, i.original_type,
|
||||
d.type
|
||||
FROM issues i
|
||||
JOIN dependencies d ON i.id = d.issue_id
|
||||
@@ -689,12 +691,17 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type
|
||||
var externalRef sql.NullString
|
||||
var sourceRepo sql.NullString
|
||||
var closeReason sql.NullString
|
||||
var deletedAt sql.NullTime
|
||||
var deletedBy sql.NullString
|
||||
var deleteReason sql.NullString
|
||||
var originalType sql.NullString
|
||||
|
||||
err := rows.Scan(
|
||||
&issue.ID, &contentHash, &issue.Title, &issue.Description, &issue.Design,
|
||||
&issue.AcceptanceCriteria, &issue.Notes, &issue.Status,
|
||||
&issue.Priority, &issue.IssueType, &assignee, &estimatedMinutes,
|
||||
&issue.CreatedAt, &issue.UpdatedAt, &closedAt, &externalRef, &sourceRepo, &closeReason,
|
||||
&deletedAt, &deletedBy, &deleteReason, &originalType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan issue: %w", err)
|
||||
@@ -722,6 +729,18 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type
|
||||
if closeReason.Valid {
|
||||
issue.CloseReason = closeReason.String
|
||||
}
|
||||
if deletedAt.Valid {
|
||||
issue.DeletedAt = &deletedAt.Time
|
||||
}
|
||||
if deletedBy.Valid {
|
||||
issue.DeletedBy = deletedBy.String
|
||||
}
|
||||
if deleteReason.Valid {
|
||||
issue.DeleteReason = deleteReason.String
|
||||
}
|
||||
if originalType.Valid {
|
||||
issue.OriginalType = originalType.String
|
||||
}
|
||||
|
||||
issues = append(issues, &issue)
|
||||
issueIDs = append(issueIDs, issue.ID)
|
||||
@@ -754,6 +773,10 @@ func (s *SQLiteStorage) scanIssuesWithDependencyType(ctx context.Context, rows *
|
||||
var assignee sql.NullString
|
||||
var externalRef sql.NullString
|
||||
var sourceRepo sql.NullString
|
||||
var deletedAt sql.NullTime
|
||||
var deletedBy sql.NullString
|
||||
var deleteReason sql.NullString
|
||||
var originalType sql.NullString
|
||||
var depType types.DependencyType
|
||||
|
||||
err := rows.Scan(
|
||||
@@ -761,6 +784,7 @@ func (s *SQLiteStorage) scanIssuesWithDependencyType(ctx context.Context, rows *
|
||||
&issue.AcceptanceCriteria, &issue.Notes, &issue.Status,
|
||||
&issue.Priority, &issue.IssueType, &assignee, &estimatedMinutes,
|
||||
&issue.CreatedAt, &issue.UpdatedAt, &closedAt, &externalRef, &sourceRepo,
|
||||
&deletedAt, &deletedBy, &deleteReason, &originalType,
|
||||
&depType,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -786,6 +810,18 @@ func (s *SQLiteStorage) scanIssuesWithDependencyType(ctx context.Context, rows *
|
||||
if sourceRepo.Valid {
|
||||
issue.SourceRepo = sourceRepo.String
|
||||
}
|
||||
if deletedAt.Valid {
|
||||
issue.DeletedAt = &deletedAt.Time
|
||||
}
|
||||
if deletedBy.Valid {
|
||||
issue.DeletedBy = deletedBy.String
|
||||
}
|
||||
if deleteReason.Valid {
|
||||
issue.DeleteReason = deleteReason.String
|
||||
}
|
||||
if originalType.Valid {
|
||||
issue.OriginalType = originalType.String
|
||||
}
|
||||
|
||||
// Fetch labels for this issue
|
||||
labels, err := s.GetLabels(ctx, issue.ID)
|
||||
|
||||
Reference in New Issue
Block a user