docs: Document COALESCE/NULLIF clone-local field protection pattern

Expands the inline comment to serve as a reference for when to use the
COALESCE(NULLIF(?, zero), column) defensive idiom in JSONL imports.

Pattern protects clone-local state (pinned, gate fields) from being
overwritten when importing JSONL where omitempty causes zero values to
represent "field absent" rather than "field is zero".

🤖 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:45:59 -08:00
parent 53921d75c8
commit 9dc0da9fdd

View File

@@ -330,12 +330,23 @@ func (s *SQLiteStorage) upsertIssueInTx(ctx context.Context, tx *sql.Tx, issue *
}
if existingHash != issue.ContentHash {
// Pinned field fix (bd-phtv): Use COALESCE(NULLIF(?, 0), pinned) to preserve
// existing pinned=1 when incoming pinned=0 (which means field was absent in
// JSONL due to omitempty). This prevents auto-import from resetting pinned issues.
// Gate field fix (bd-gr4q): Same pattern for await_type, await_id, timeout_ns, waiters.
// Gates are wisps and aren't exported to JSONL, so importing an issue with the same
// ID would clear these fields without this protection.
// Clone-local field protection pattern (bd-phtv, bd-gr4q):
//
// Some fields are clone-local state that shouldn't be overwritten by JSONL import:
// - pinned: Local hook attachment (not synced between clones)
// - await_type, await_id, timeout_ns, waiters: Gate state (wisps, never exported)
//
// Problem: Go's omitempty causes zero values to be absent from JSONL.
// When importing, absent fields unmarshal as zero, which would overwrite local state.
//
// Solution: COALESCE(NULLIF(incoming, zero_value), existing_column)
// - For strings: COALESCE(NULLIF(?, ''), column) -- preserve if incoming is ""
// - For integers: COALESCE(NULLIF(?, 0), column) -- preserve if incoming is 0
//
// When to use this pattern:
// 1. Field is clone-local (not part of shared issue ledger)
// 2. Field uses omitempty (so zero value means "absent", not "clear")
// 3. Accidental clearing would cause data loss or incorrect behavior
_, err = tx.ExecContext(ctx, `
UPDATE issues SET
content_hash = ?, title = ?, description = ?, design = ?,