Files
beads/internal/export/config.go
Steve Yegge e3e0a04496 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>
2025-11-23 20:21:51 -08:00

118 lines
3.6 KiB
Go

package export
import (
"context"
"fmt"
"strconv"
"github.com/steveyegge/beads/internal/storage"
)
// ConfigStore defines the minimal storage interface needed for config
type ConfigStore interface {
GetConfig(ctx context.Context, key string) (string, error)
SetConfig(ctx context.Context, key, value string) error
}
// LoadConfig reads export configuration from storage
func LoadConfig(ctx context.Context, store ConfigStore, isAutoExport bool) (*Config, error) {
cfg := &Config{
Policy: DefaultErrorPolicy,
RetryAttempts: DefaultRetryAttempts,
RetryBackoffMS: DefaultRetryBackoffMS,
SkipEncodingErrors: DefaultSkipEncodingErrors,
WriteManifest: DefaultWriteManifest,
IsAutoExport: isAutoExport,
}
// Load error policy
if isAutoExport {
// Check auto-export specific policy first
if val, err := store.GetConfig(ctx, ConfigKeyAutoExportPolicy); err == nil && val != "" {
policy := ErrorPolicy(val)
if policy.IsValid() {
cfg.Policy = policy
}
}
}
// Fall back to general export policy if not set or not auto-export
if cfg.Policy == DefaultErrorPolicy {
if val, err := store.GetConfig(ctx, ConfigKeyErrorPolicy); err == nil && val != "" {
policy := ErrorPolicy(val)
if policy.IsValid() {
cfg.Policy = policy
}
}
}
// Load retry attempts
if val, err := store.GetConfig(ctx, ConfigKeyRetryAttempts); err == nil && val != "" {
if attempts, err := strconv.Atoi(val); err == nil && attempts >= 0 {
cfg.RetryAttempts = attempts
}
}
// Load retry backoff
if val, err := store.GetConfig(ctx, ConfigKeyRetryBackoffMS); err == nil && val != "" {
if backoff, err := strconv.Atoi(val); err == nil && backoff > 0 {
cfg.RetryBackoffMS = backoff
}
}
// Load skip encoding errors flag
if val, err := store.GetConfig(ctx, ConfigKeySkipEncodingErrors); err == nil && val != "" {
if skip, err := strconv.ParseBool(val); err == nil {
cfg.SkipEncodingErrors = skip
}
}
// Load write manifest flag
if val, err := store.GetConfig(ctx, ConfigKeyWriteManifest); err == nil && val != "" {
if write, err := strconv.ParseBool(val); err == nil {
cfg.WriteManifest = write
}
}
return cfg, nil
}
// SetPolicy sets the error policy for exports
func SetPolicy(ctx context.Context, store storage.Storage, policy ErrorPolicy, autoExport bool) error {
if !policy.IsValid() {
return fmt.Errorf("invalid error policy: %s (valid: strict, best-effort, partial, required-core)", policy)
}
key := ConfigKeyErrorPolicy
if autoExport {
key = ConfigKeyAutoExportPolicy
}
return store.SetConfig(ctx, key, string(policy))
}
// SetRetryAttempts sets the number of retry attempts
func SetRetryAttempts(ctx context.Context, store storage.Storage, attempts int) error {
if attempts < 0 {
return fmt.Errorf("retry attempts must be non-negative")
}
return store.SetConfig(ctx, ConfigKeyRetryAttempts, strconv.Itoa(attempts))
}
// SetRetryBackoff sets the initial retry backoff in milliseconds
func SetRetryBackoff(ctx context.Context, store storage.Storage, backoffMS int) error {
if backoffMS <= 0 {
return fmt.Errorf("retry backoff must be positive")
}
return store.SetConfig(ctx, ConfigKeyRetryBackoffMS, strconv.Itoa(backoffMS))
}
// SetSkipEncodingErrors sets whether to skip issues with encoding errors
func SetSkipEncodingErrors(ctx context.Context, store storage.Storage, skip bool) error {
return store.SetConfig(ctx, ConfigKeySkipEncodingErrors, strconv.FormatBool(skip))
}
// SetWriteManifest sets whether to write export manifests
func SetWriteManifest(ctx context.Context, store storage.Storage, write bool) error {
return store.SetConfig(ctx, ConfigKeyWriteManifest, strconv.FormatBool(write))
}