Documentation
¶
Overview ¶
Package reconcile compares one dataset across N database instances in a single streaming pass and classifies every difference against an explicit consistency policy.
Alignment reuses DALgo recordops.DiffFunc, a K-way merge over key-ordered streams that retains one record per input: memory is O(N) records plus the bounded detail lists, and the cost is one pass over every input rather than N×(N−1)/2 pairwise diffs. Every source is a merge candidate against an empty baseline, so each emitted ID carries each instance's full post-policy row and authority is applied here, by policy, not by merge position.
The package is read-only by construction: it consumes rows and returns a verdict. It never writes, repairs or synchronizes data.
Index ¶
- Constants
- Variables
- func SortCandidates(candidates []Candidate)
- type Candidate
- type ColumnStatus
- type FieldDifference
- type FieldPolicy
- type Instance
- type InstanceResult
- type InstanceSelector
- type Mode
- type Presence
- type Profile
- type RecordDiscrepancy
- type Result
- type Rules
- type Source
- type Status
- type Summary
- type Unavailable
- type UnavailableReason
- type ValueDisclosure
- type ValueGroup
Constants ¶
const ( // DefaultLimit bounds each detail list when Rules.Limit is zero. DefaultLimit = 100 // MaximumLimit is the largest accepted Rules.Limit. MaximumLimit = 10000 )
const ProfilesFolder = "reconciliations"
ProfilesFolder is the project folder that holds reconciliation profiles, one `<id>.json` per profile.
const SnapshotCaveat = "instances were read independently, not from one snapshot: writes that landed between reads can appear as differences"
SnapshotCaveat is reported when instances were not read from one snapshot.
Variables ¶
var ErrInvalidRules = errors.New("reconcile: invalid rules")
ErrInvalidRules reports a policy that cannot be evaluated.
Functions ¶
func SortCandidates ¶
func SortCandidates(candidates []Candidate)
SortCandidates orders candidates by environment then catalog.
Types ¶
type Candidate ¶
type Candidate struct {
Environment string `json:"environment"`
Catalog string `json:"catalog"`
Family string `json:"family"`
Kind datatug.EnvironmentKind `json:"kind,omitempty"`
Tenants []string `json:"tenants,omitempty"`
}
Candidate is one catalog in one environment, as the project records it.
type ColumnStatus ¶
type ColumnStatus struct {
Column string `json:"column"`
Policy FieldPolicy `json:"policy"`
MissingIn []string `json:"missingIn,omitempty"`
HiddenIn []string `json:"hiddenIn,omitempty"`
Types map[string]string `json:"types,omitempty"`
// Compared is false when a type conflict excluded the column entirely.
Compared bool `json:"compared"`
}
ColumnStatus reports schema drift for one non-key column: instances that lack it, hide it by policy, or declare a different type. Columns that are present with one type everywhere are not listed.
type FieldDifference ¶
type FieldDifference struct {
Column string `json:"column"`
Policy FieldPolicy `json:"policy"`
Expected bool `json:"expected"`
Groups []ValueGroup `json:"groups"`
// Deviating lists instances attributed with the difference: in baseline
// mode those outside the baseline's group; in consensus mode those
// outside the unique largest group. Empty with NoConsensus when the
// largest groups tie.
Deviating []string `json:"deviating,omitempty"`
NoConsensus bool `json:"noConsensus,omitempty"`
}
FieldDifference is one field whose values disagree across instances.
type FieldPolicy ¶
type FieldPolicy string
FieldPolicy states the consistency expectation for one field.
const ( // FieldIdentical requires byte-for-byte equal typed values. FieldIdentical FieldPolicy = "identical" // FieldNormalized compares strings after trimming, collapsing inner // whitespace and case-folding; other types compare as identical. FieldNormalized FieldPolicy = "normalized" // FieldInstanceSpecific expects values to differ: differences are // reported as expected, never as violations. FieldInstanceSpecific FieldPolicy = "instance-specific" // FieldIgnored excludes the field from comparison and from results. FieldIgnored FieldPolicy = "ignored" )
type Instance ¶
type Instance struct {
ID string `json:"id"`
Tenant string `json:"tenant,omitempty"`
Environment string `json:"environment,omitempty"`
Family string `json:"family,omitempty"`
TenantScope string `json:"tenantScope,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
Instance identifies one compared database instance along independent dimensions. Tenant and environment are labels, not a hierarchy: one tenant may have several instances, and one instance may hold several tenants (TenantScope then names the slice compared, for example a WHERE predicate already applied by the caller).
type InstanceResult ¶
type InstanceResult struct {
Instance Instance `json:"instance"`
Status Status `json:"status"`
Authoritative bool `json:"authoritative,omitempty"`
ReadAt string `json:"readAt,omitempty"`
RowCount int `json:"rowCount"`
RowsFiltered bool `json:"rowsFiltered,omitempty"`
HiddenColumns []string `json:"hiddenColumns,omitempty"`
// DriftColumns lists columns this instance could not be fully compared on
// because of schema drift (see Result.Columns).
DriftColumns []string `json:"driftColumns,omitempty"`
// Counts are per (record, instance) attributions.
Missing int `json:"missing"`
Extra int `json:"extra"`
Conflicts int `json:"conflicts"`
Expected int `json:"expected"`
Inconclusive int `json:"inconclusive"`
}
InstanceResult is one instance's share of the verdict.
type InstanceSelector ¶
type InstanceSelector struct {
Family string `json:"family,omitempty"`
Kind datatug.EnvironmentKind `json:"kind,omitempty"`
Tenants []string `json:"tenants,omitempty"`
Environments []string `json:"environments,omitempty"`
}
InstanceSelector chooses instances either by dimension (family, optional kind and tenants) or by an explicit list of environment ids. Both may be combined: an explicit list narrows the dimension match.
func (InstanceSelector) Select ¶
func (s InstanceSelector) Select(candidates []Candidate) ([]Candidate, error)
Select returns the candidates matching the selector, one per environment, sorted by environment id. Two catalogs of the same family in one environment are ambiguous and refused: name the environment's catalog in the project instead of guessing.
func (InstanceSelector) Validate ¶
func (s InstanceSelector) Validate() error
Validate requires a family or an explicit environment list.
type Mode ¶
type Mode string
Mode selects how authority is assigned.
const ( // ModeBaseline compares every instance to one explicitly named // authoritative instance. Deviations are attributed to the candidate. ModeBaseline Mode = "baseline" // ModeConsensus groups equal values and reports minority groups as // outliers. Consensus never declares the majority correct. ModeConsensus Mode = "consensus" )
type Presence ¶
type Presence string
Presence states which instances must hold each record.
const ( // PresenceRequired: every instance holds every record. PresenceRequired Presence = "required" // PresenceSubset (baseline mode): candidates may lack baseline records but // must not hold records the baseline lacks. PresenceSubset Presence = "subset" // PresenceSuperset (baseline mode): candidates must hold every baseline // record and may hold more. PresenceSuperset Presence = "superset" // PresenceAny: presence differences are expected. PresenceAny Presence = "any" )
type Profile ¶
type Profile struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
QueryID string `json:"queryId"`
Instances InstanceSelector `json:"instances"`
Rules Rules `json:"rules"`
}
Profile is a saved, reviewable reconciliation: which dataset query, which instances, which rule. It is a plain project file.
type RecordDiscrepancy ¶
type RecordDiscrepancy struct {
// Ordinal is the record's 1-based position in key order. It identifies
// the record in groups-only results, where Key is omitted.
Ordinal int `json:"ordinal"`
Key []apicontract.TypedValue `json:"key,omitempty"`
// Present lists instances holding the record.
Present []string `json:"present"`
// Missing lists instances lacking the record; MissingExpected says
// whether the presence rule allows it.
Missing []string `json:"missing,omitempty"`
MissingExpected bool `json:"missingExpected,omitempty"`
// Extra lists instances holding a record the baseline lacks.
Extra []string `json:"extra,omitempty"`
ExtraExpected bool `json:"extraExpected,omitempty"`
// PresenceNoConsensus: in consensus mode, as many instances hold the
// record as lack it; both sides are listed and neither is presumed right.
PresenceNoConsensus bool `json:"presenceNoConsensus,omitempty"`
// Inconclusive lists instances whose absence cannot be judged because
// policy filtered their rows.
Inconclusive []string `json:"inconclusive,omitempty"`
Fields []FieldDifference `json:"fields,omitempty"`
}
RecordDiscrepancy is one logical record that is not consistent everywhere.
type Result ¶
type Result struct {
Mode Mode `json:"mode"`
Baseline string `json:"baseline,omitempty"`
Key []string `json:"key"`
Presence Presence `json:"presence"`
Disclosure ValueDisclosure `json:"disclosure"`
Status Status `json:"status"`
Instances []InstanceResult `json:"instances"`
Columns []ColumnStatus `json:"columns"`
Summary Summary `json:"summary"`
// Unexpected, Expected and Inconclusive hold the first Limit records of
// each class in key order.
Unexpected []RecordDiscrepancy `json:"unexpected"`
Expected []RecordDiscrepancy `json:"expected"`
Inconclusive []RecordDiscrepancy `json:"inconclusive"`
Truncated bool `json:"truncated"`
Caveats []string `json:"caveats"`
}
Result is the structured outcome of one dataset reconciliation.
type Rules ¶
type Rules struct {
// Key is the semantic identity: column names whose values identify the
// same logical record across instances. Never assume surrogate primary
// keys match across databases.
Key []string `json:"key"`
Mode Mode `json:"mode"`
Baseline string `json:"baseline,omitempty"`
Presence Presence `json:"presence,omitempty"`
DefaultField FieldPolicy `json:"defaultField,omitempty"`
Fields map[string]FieldPolicy `json:"fields,omitempty"`
Disclosure ValueDisclosure `json:"disclosure,omitempty"`
// Limit bounds each detail list (unexpected, expected, inconclusive).
// Counts in Summary are always complete.
Limit int `json:"limit,omitempty"`
}
Rules is the reconciliation policy for one dataset.
type Source ¶
type Source struct {
Instance Instance
Columns []apicontract.Column
Rows recordsetcompare.OrderedRows
Info apicontract.CompareSideInfo
// ReadAt is when the source was read (RFC 3339), reported back verbatim.
ReadAt string
// Snapshot is true when every row came from one consistent snapshot of
// this instance. Cross-instance reads are never one snapshot.
Snapshot bool
}
Source is one instance's post-policy recordset. Rows must be strictly ordered by Rules.Key using apicontract.TypedKeySortKey (see recordsetcompare.OrderRecordset for callers holding a materialized recordset). Info.Limitations carries the access policy already applied by the caller: hidden columns are never compared, and filtered rows make absences inconclusive.
type Status ¶
type Status string
Status is a reconciliation verdict.
const ( StatusConsistent Status = "consistent" StatusDiscrepancies Status = "discrepancies" // StatusPartial: no unexpected differences in what was visible, but // policy hid rows or columns, so the result is not a full proof. StatusPartial Status = "partial" // StatusInconclusive: the instance (or run) could not be compared. StatusInconclusive Status = "inconclusive" )
type Summary ¶
type Summary struct {
Instances int `json:"instances"`
ComparedInstances int `json:"comparedInstances"`
Records int `json:"records"`
Consistent int `json:"consistent"`
WithUnexpected int `json:"withUnexpected"`
WithExpectedOnly int `json:"withExpectedOnly"`
WithInconclusive int `json:"withInconclusive"`
MissingRecords int `json:"missingRecords"`
ExtraRecords int `json:"extraRecords"`
ConflictingRecords int `json:"conflictingRecords"`
ExpectedDifferences int `json:"expectedDifferences"`
}
Summary carries complete counts regardless of Limit.
type Unavailable ¶
type Unavailable struct {
}
Unavailable marks a source that could not be read. An unavailable source is never treated as matching: its instance is reported inconclusive.
type UnavailableReason ¶
type UnavailableReason string
UnavailableReason says why a source contributed no rows.
const ( )
type ValueDisclosure ¶
type ValueDisclosure string
ValueDisclosure controls whether observed values appear in results.
const ( // DiscloseValues includes post-policy visible values in value groups. DiscloseValues ValueDisclosure = "values" // DiscloseGroupsOnly reports only which instances agree, never values, // hashes of values or record keys (records are identified by ordinal). Use it when results leave the trust boundary (for // example to an AI service). DiscloseGroupsOnly ValueDisclosure = "groups-only" )
type ValueGroup ¶
type ValueGroup struct {
Value *apicontract.TypedValue `json:"value,omitempty"`
Instances []string `json:"instances"`
Authoritative bool `json:"authoritative,omitempty"`
}
ValueGroup is a set of instances holding the same (normalized) value.