configdiff

package
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package configdiff compares the canonical .cloop/config.yaml against the SQLite-mirrored copy stored by Save() (see pkg/config). The two stores diverge whenever:

  • The user runs `cloop config set` against a binary that mirrors, then restores the YAML from a backup that predates the SQLite mirror.
  • A different cloop binary (one without the mirror code) writes config.yaml, leaving the SQLite copy stale.
  • Direct SQL edits or migrations mutate the metadata blob without touching the YAML.
  • The YAML is hand-edited but cloop hasn't run Save() yet.

Silent drift between the two leads to "why did my config change not take effect" bug reports — Load() prefers YAML when present, but tools that query the SQLite mirror (analytics, the doctor command, third-party inspection scripts) see the stale copy. This package exposes the drift explicitly so users can resolve it deliberately.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsSensitive

func IsSensitive(path string) bool

IsSensitive returns true when the given dotted config path likely names a secret. Used by Render() to mask values before printing the diff.

func MaskSecret

func MaskSecret(s string) string

MaskSecret returns a redacted form of a secret value suitable for human display. Mirrors the masking convention of cmd/config_cmd.go's maskSecret — keep the first/last 4 chars when long enough, otherwise return ****.

func Render

func Render(rep *Report) string

Render formats a Report as a multi-line, human-readable string. Secret values (api keys, tokens) are masked. Returns an empty string when no drift is present (callers usually short-circuit before calling Render).

func Sync

func Sync(workdir string, dir Direction) error

Sync resolves drift in the chosen direction. Returns an error when the chosen source is not available (e.g. FromDB with no mirror, FromYAML with no YAML file). The opposite store is overwritten atomically — for FromYAML via config.Save (which already mirrors); for FromDB via writing the blob out and calling config.Save to round-trip via the validated, atomic path.

We deliberately go through config.Save rather than writing raw bytes so:

  • The validateAndClamp pass runs on the rehydrated config — operators can't accidentally restore a stale-and-invalid blob.
  • 0o600 perms and atomic rename behaviour are inherited.
  • The reverse mirror is also refreshed (FromDB writes YAML + re-mirrors, so any missing fields in the blob are normalised on the way through).

Types

type DiffKind

type DiffKind string

DiffKind describes how a key differs between the two stores.

const (
	// Added means the key exists in the right side (DB) but not the left (YAML).
	Added DiffKind = "added"
	// Removed means the key exists in the left side (YAML) but not the right (DB).
	Removed DiffKind = "removed"
	// Changed means the key exists in both with different values.
	Changed DiffKind = "changed"
)

type Direction

type Direction string

Direction selects which source wins during Sync.

const (
	// FromYAML overwrites the SQLite mirror with the YAML contents (default).
	// This is the natural recovery path because YAML is the canonical
	// human-edited source — Load() already prefers it.
	FromYAML Direction = "from-yaml"

	// FromDB overwrites the YAML with the SQLite mirror. Used to recover
	// when the YAML has been corrupted or accidentally truncated and the
	// SQLite blob has the last good copy.
	FromDB Direction = "from-db"
)

type Entry

type Entry struct {
	Path      string
	Kind      DiffKind
	YAMLValue string
	DBValue   string
}

Entry is a single keyed difference between YAML and DB.

Path is a dotted key path into the YAML structure (e.g. "anthropic.api_key", "rate_limit.burst"). YAMLValue and DBValue are the human-readable string forms of each side; either may be empty when the kind is Added/Removed.

type Report

type Report struct {
	// YAMLPresent is true when .cloop/config.yaml exists on disk.
	YAMLPresent bool
	// DBPresent is true when .cloop/state.db exists AND has a non-empty config blob.
	DBPresent bool
	// Entries lists every keyed difference, ordered by Path.
	Entries []Entry
}

Report is the result of comparing the two stores.

func Compute

func Compute(workdir string) (*Report, error)

Compute compares the two stores and returns a structured Report. Returns an error only on unexpected I/O or parse failures; "absent" is normal state and reflected via the booleans on Report.

func (*Report) HasDrift

func (r *Report) HasDrift() bool

HasDrift returns true when the two stores disagree in any way the user should care about. Special cases the "DB has no blob yet" scenario as non-drift — a fresh project that hasn't been mirrored yet isn't drift, just uninitialised state.

func (*Report) Summary

func (r *Report) Summary() string

Summary returns a one-line human-readable summary suitable for embedding in doctor reports or CLI output. Empty string when there's no drift.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL