Fix bd-c6cf: Force full export when export_hashes is cleared

When validateJSONLIntegrity() clears export_hashes due to hash mismatch
or missing JSONL, the subsequent export now correctly exports ALL issues
instead of only dirty ones, preventing permanent database divergence.

Changes:
- validateJSONLIntegrity() returns (needsFullExport, error) to signal when
  export_hashes was cleared
- flushToJSONL() moved integrity check BEFORE isDirty gate so integrity
  issues trigger export even when nothing is dirty
- Missing JSONL treated as non-fatal force-full-export case
- Increased scanner buffer from 64KB to 2MB to handle large JSON lines
- Added scanner.Err() check to catch buffer overflow errors
- Updated all tests to verify needsFullExport flag

Fixes database divergence issue where clearing export_hashes didn't
trigger re-export, causing 5 issues to disappear from JSONL in fred clone.

Amp-Thread-ID: https://ampcode.com/threads/T-bf2fdcd6-7bbd-4c30-b1db-746b928c93b8
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-11-01 20:29:13 -07:00
parent 0c1738c976
commit 5fabb5fdcc
3 changed files with 117 additions and 40 deletions

View File

@@ -2,6 +2,8 @@ package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"os"
"path/filepath"
"testing"
@@ -70,6 +72,14 @@ func TestExportIntegrityAfterJSONLTruncation(t *testing.T) {
t.Fatalf("failed to read JSONL: %v", err)
}
// Compute and store the JSONL file hash
hasher := sha256.New()
hasher.Write(jsonlData)
fileHash := hex.EncodeToString(hasher.Sum(nil))
if err := testStore.SetJSONLFileHash(ctx, fileHash); err != nil {
t.Fatalf("failed to set JSONL file hash: %v", err)
}
initialSize := len(jsonlData)
// Step 2: Simulate git operation that truncates JSONL (the bd-160 scenario)
@@ -92,9 +102,13 @@ func TestExportIntegrityAfterJSONLTruncation(t *testing.T) {
defer func() { store = oldStore }()
// This should detect the mismatch and clear export_hashes
if err := validateJSONLIntegrity(ctx, jsonlPath); err != nil {
needsFullExport, err := validateJSONLIntegrity(ctx, jsonlPath)
if err != nil {
t.Fatalf("integrity validation failed: %v", err)
}
if !needsFullExport {
t.Fatalf("expected needsFullExport=true after truncation")
}
// Step 4: Export all issues again
exportedIDs2, err := writeJSONLAtomic(jsonlPath, allIssues)
@@ -180,7 +194,16 @@ func TestExportIntegrityAfterJSONLDeletion(t *testing.T) {
}
// Store JSONL hash (would happen in real export)
_ , _ = os.ReadFile(jsonlPath)
jsonlData, err := os.ReadFile(jsonlPath)
if err != nil {
t.Fatalf("failed to read JSONL: %v", err)
}
hasher := sha256.New()
hasher.Write(jsonlData)
fileHash := hex.EncodeToString(hasher.Sum(nil))
if err := testStore.SetJSONLFileHash(ctx, fileHash); err != nil {
t.Fatalf("failed to set JSONL file hash: %v", err)
}
// Set global store
oldStore := store
@@ -194,12 +217,16 @@ func TestExportIntegrityAfterJSONLDeletion(t *testing.T) {
// Integrity validation should detect missing file
// (In real system, this happens before next export)
if err := validateJSONLIntegrity(ctx, jsonlPath); err != nil {
needsFullExport, err := validateJSONLIntegrity(ctx, jsonlPath)
if err != nil {
// Error is OK if file doesn't exist
if !os.IsNotExist(err) {
t.Fatalf("unexpected error: %v", err)
}
}
if !needsFullExport {
t.Fatalf("expected needsFullExport=true after JSONL deletion")
}
// Export again should recreate JSONL
_, err = writeJSONLAtomic(jsonlPath, []*types.Issue{issue})