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:
Steve Yegge
2025-12-05 15:29:42 -08:00
parent ce119551f6
commit 08e43d9fc7
13 changed files with 295 additions and 25 deletions

View File

@@ -0,0 +1,47 @@
package migrations
import (
"database/sql"
"fmt"
)
// MigrateTombstoneColumns adds tombstone support columns to the issues table.
// These columns support inline soft-delete (bd-vw8) replacing deletions.jsonl:
// - deleted_at: when the issue was deleted
// - deleted_by: who deleted the issue
// - delete_reason: why the issue was deleted
// - original_type: the issue type before deletion (for tombstones)
func MigrateTombstoneColumns(db *sql.DB) error {
columns := []struct {
name string
definition string
}{
{"deleted_at", "TEXT"},
{"deleted_by", "TEXT DEFAULT ''"},
{"delete_reason", "TEXT DEFAULT ''"},
{"original_type", "TEXT DEFAULT ''"},
}
for _, col := range columns {
var columnExists bool
err := db.QueryRow(`
SELECT COUNT(*) > 0
FROM pragma_table_info('issues')
WHERE name = ?
`, col.name).Scan(&columnExists)
if err != nil {
return fmt.Errorf("failed to check %s column: %w", col.name, err)
}
if columnExists {
continue
}
_, err = db.Exec(fmt.Sprintf(`ALTER TABLE issues ADD COLUMN %s %s`, col.name, col.definition))
if err != nil {
return fmt.Errorf("failed to add %s column: %w", col.name, err)
}
}
return nil
}