Add configurable export error handling policies (bd-exug)
Implements flexible error handling for export operations with four policies: - strict: Fail-fast on any error (default for user exports) - best-effort: Skip errors with warnings (default for auto-exports) - partial: Retry then skip with manifest tracking - required-core: Fail on core data, skip enrichments Key features: - Per-project configuration via `bd config set export.error_policy` - Separate policy for auto-exports: `auto_export.error_policy` - Retry with exponential backoff (configurable attempts/delay) - Optional export manifests documenting completeness - Per-issue encoding error handling This allows users to choose the right trade-off between data integrity and system availability for their specific project needs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
65
internal/export/manifest.go
Normal file
65
internal/export/manifest.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package export
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// WriteManifest writes an export manifest alongside the JSONL file
|
||||
func WriteManifest(jsonlPath string, manifest *Manifest) error {
|
||||
// Derive manifest path from JSONL path
|
||||
manifestPath := strings.TrimSuffix(jsonlPath, ".jsonl") + ".manifest.json"
|
||||
|
||||
// Marshal manifest
|
||||
data, err := json.MarshalIndent(manifest, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal manifest: %w", err)
|
||||
}
|
||||
|
||||
// Create temp file for atomic write
|
||||
dir := filepath.Dir(manifestPath)
|
||||
base := filepath.Base(manifestPath)
|
||||
tempFile, err := os.CreateTemp(dir, base+".tmp.*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create temp manifest file: %w", err)
|
||||
}
|
||||
tempPath := tempFile.Name()
|
||||
defer func() {
|
||||
_ = tempFile.Close()
|
||||
_ = os.Remove(tempPath)
|
||||
}()
|
||||
|
||||
// Write manifest
|
||||
if _, err := tempFile.Write(data); err != nil {
|
||||
return fmt.Errorf("failed to write manifest: %w", err)
|
||||
}
|
||||
|
||||
// Close before rename
|
||||
_ = tempFile.Close()
|
||||
|
||||
// Atomic replace
|
||||
if err := os.Rename(tempPath, manifestPath); err != nil {
|
||||
return fmt.Errorf("failed to replace manifest file: %w", err)
|
||||
}
|
||||
|
||||
// Set appropriate file permissions (0600: rw-------)
|
||||
if err := os.Chmod(manifestPath, 0600); err != nil {
|
||||
// Non-fatal, just log
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to set manifest permissions: %v\n", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewManifest creates a new export manifest
|
||||
func NewManifest(policy ErrorPolicy) *Manifest {
|
||||
return &Manifest{
|
||||
ExportedAt: time.Now(),
|
||||
ErrorPolicy: string(policy),
|
||||
Complete: true, // Will be set to false if any data is missing
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user