fix: Allow tombstones to retain closed_at timestamp

Previously, tombstones could not have a closed_at timestamp due to:
1. Go validation: `if status != closed && closed_at != nil` failed
2. SQL CHECK constraint: `(status = 'closed') = (closed_at IS NOT NULL)`

This caused import failures for tombstones that were closed before being
deleted - a valid scenario where we want to preserve the historical
closed_at timestamp for audit purposes.

Changes:
- internal/types/types.go: Updated validation to allow tombstones with
  closed_at (line 253)
- internal/storage/sqlite/schema.go: Updated CHECK constraint to allow
  closed AND tombstone statuses to have closed_at
- internal/storage/sqlite/migrations/028_tombstone_closed_at.go: Migration
  to update existing databases with the new constraint
- .beads/issues.jsonl: Fixed bd-6s61 status from 'closed' to 'tombstone'

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-26 00:57:59 -08:00
parent 9dc0da9fdd
commit 64d2f372a3
7 changed files with 184 additions and 1687 deletions

View File

@@ -246,10 +246,11 @@ func (i *Issue) ValidateWithCustomStatuses(customStatuses []string) error {
return fmt.Errorf("estimated_minutes cannot be negative")
}
// Enforce closed_at invariant: closed_at should be set if and only if status is closed
// Exception: tombstones may retain closed_at from before deletion
if i.Status == StatusClosed && i.ClosedAt == nil {
return fmt.Errorf("closed issues must have closed_at timestamp")
}
if i.Status != StatusClosed && i.ClosedAt != nil {
if i.Status != StatusClosed && i.Status != StatusTombstone && i.ClosedAt != nil {
return fmt.Errorf("non-closed issues cannot have closed_at timestamp")
}
// Enforce tombstone invariants (bd-md2): deleted_at must be set for tombstones, and only for tombstones