Documentation
¶
Overview ¶
Package identity provides bulk identity resolution ingest functionality.
The ingest path is the single way all writers (live enrichment worker, claude-leaderboard seed, manual curation) write email→login resolutions to the email_resolution table, ensuring consistent conflict resolution.
Package identity provides snapshot capture utilities for email resolution tables.
Example ¶
Example: With real PostgreSQL connection This shows how to run the actual test with a live database
package main
import (
"fmt"
)
func main() {
// This would require a real PostgreSQL connection:
// dbHost := os.Getenv("PGHOST")
// dbUser := os.Getenv("PGUSER")
// etc.
// The test database /tmp/test_seed.db contains:
// - 11 total rows
// - 3 rows with empty logins
// - 8 valid rows
// - Some duplicate pairs for conflict testing
// Expected results when seeded:
// 1. 3 rows skipped (empty logins)
// 2. 8 rows submitted to ingest
// 3. Conflict resolution applied for duplicates
// 4. Final table has unique email entries with winning values
fmt.Println("See test database at /tmp/test_seed.db")
fmt.Println("Run: sqlite3 /tmp/test_seed.db 'SELECT * FROM author_login_cache'")
}
Output:
Index ¶
- func CompareSnapshots(a, b *EmailResolutionSnapshot) (bool, error)
- type CaptureSnapshotOption
- type DB
- type EmailResolutionSnapshot
- type IngestResult
- type Ingester
- func (i *Ingester) GetIngested() int64
- func (i *Ingester) GetProcessed() int64
- func (i *Ingester) GetSkipDetails() map[SkipReason]int64
- func (i *Ingester) GetSkipped() int64
- func (i *Ingester) GetSummary() map[string]interface{}
- func (i *Ingester) IngestResolution(ctx context.Context, rows []ResolutionRow) error
- type ResolutionRow
- type SkipReason
- type Source
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CompareSnapshots ¶
func CompareSnapshots(a, b *EmailResolutionSnapshot) (bool, error)
CompareSnapshots compares two snapshots and returns whether they are byte-for-byte identical.
Returns:
- (true, nil) if snapshots are identical (same row count and hash)
- (false, error) if snapshots differ, with detailed error message
- (false, error) if either snapshot is nil
Types ¶
type CaptureSnapshotOption ¶
type CaptureSnapshotOption func(*captureSnapshotOptions)
CaptureSnapshotOption is a functional option for CaptureSnapshot.
func WithFullRowData ¶
func WithFullRowData() CaptureSnapshotOption
WithFullRowData enables capturing the complete row data in the snapshot. This is useful for debugging and detailed analysis but uses more memory.
type DB ¶
type DB interface {
// IngestEmailResolution performs a bulk upsert of resolution rows.
// The batch must apply the ON CONFLICT rule:
// - Manual source always wins
// - Otherwise, the newer resolved_at wins
// Rows that lose the conflict check are silently skipped.
// Returns IngestResult with counts of ingested and skipped rows.
IngestEmailResolution(ctx context.Context, rows []ResolutionRow) (*IngestResult, error)
}
DB is the database interface required by Ingester. This allows for testing with mocks and supports different database drivers.
type EmailResolutionSnapshot ¶
type EmailResolutionSnapshot struct {
// RowCount is the total number of rows in the email_resolution table
RowCount int
// Hash is the SHA-256 checksum of all row data (sorted by email)
// The hash includes all columns: email, login, source, resolved_at
Hash string
// Rows contains the full row data for debugging and detailed analysis.
// This field is optional and can be nil for snapshots that only need
// hash-based comparison.
Rows []ResolutionRow
}
EmailResolutionSnapshot captures the complete state of the email_resolution table. This includes the row count, a cryptographic hash of all data, and optionally the full row data for debugging and detailed comparison.
func CaptureSnapshot ¶
func CaptureSnapshot(db *sql.DB, opts ...CaptureSnapshotOption) (*EmailResolutionSnapshot, error)
CaptureSnapshot reads all rows from the email_resolution table and returns a snapshot containing the row count, cryptographic hash, and optionally the full row data.
The hash is computed by: 1. Reading all rows sorted by email (ensures consistent ordering) 2. Concatenating row data in a stable format 3. Computing SHA-256 of the concatenated data
This ensures the hash is sensitive to changes in any column (email, login, source, resolved_at) regardless of database storage order.
Parameters:
- db: Database connection (can be PostgreSQL or SQLite)
- opts: Optional configuration (e.g., WithFullRowData())
Returns an error if:
- The database query fails
- A row cannot be scanned
- The timestamp format is invalid
type IngestResult ¶
type IngestResult struct {
// Ingested is the number of rows that were written (inserted or updated).
Ingested int64
// Skipped is the number of rows that were not written due to conflict resolution.
Skipped int64
// SkipDetails provides a breakdown of skip reasons.
SkipDetails map[SkipReason]int64
}
IngestResult describes the outcome of a bulk ingest operation.
type Ingester ¶
type Ingester struct {
Processed int64 // Total number of records processed (seen)
Ingested int64 // Total number of records successfully written (inserted or updated)
Skipped int64 // Total number of records skipped due to conflict resolution
SkipDetails map[SkipReason]int64 // Breakdown of skip reasons
// contains filtered or unexported fields
}
Ingester handles bulk upsert of email resolution rows with counter tracking.
func (*Ingester) GetIngested ¶
GetIngested returns the total number of records successfully ingested.
func (*Ingester) GetProcessed ¶
GetProcessed returns the total number of records processed.
func (*Ingester) GetSkipDetails ¶
func (i *Ingester) GetSkipDetails() map[SkipReason]int64
GetSkipDetails returns the breakdown of skip reasons.
func (*Ingester) GetSkipped ¶
GetSkipped returns the total number of records skipped.
func (*Ingester) GetSummary ¶
GetSummary returns a machine-readable, JSON-marshalable snapshot of the ingester's counters, suitable for logging at the end of an ingest run. Keys: "processed", "ingested", "skipped" (int64), "skip_details" (map[string]int64 keyed by skip reason string).
func (*Ingester) IngestResolution ¶
func (i *Ingester) IngestResolution(ctx context.Context, rows []ResolutionRow) error
IngestResolution performs a bulk upsert of email resolution rows. It validates all rows first, then delegates to the database implementation.
The batch must use the ON CONFLICT rule from the plan:
ON CONFLICT (email) DO UPDATE
SET login = excluded.login, source = excluded.source,
resolved_at = excluded.resolved_at
WHERE excluded.source = 'manual'
OR (email_resolution.source <> 'manual'
AND excluded.resolved_at > email_resolution.resolved_at)
This means:
- A manual source always wins (overwrites any existing row)
- A non-manual source wins only if the existing row is also non-manual AND has an older resolved_at timestamp
- Otherwise the existing row is left unchanged
Rows that lose the conflict check are silently skipped - this is upsert semantics, not an all-or-nothing batch failure.
Returns an error if validation fails or the database operation fails. A partial failure (some rows succeed, some fail) returns an error.
type ResolutionRow ¶
type ResolutionRow struct {
Email string // Email address (primary key)
Login string // Resolved GitHub login
Source Source // Source of this resolution: live, seed, or manual
ResolvedAt time.Time // When this resolution was made
}
ResolutionRow represents a single email→login resolution row.
func (*ResolutionRow) Validate ¶
func (r *ResolutionRow) Validate() error
Validate checks if the row has valid fields.
type SkipReason ¶
type SkipReason string
SkipReason represents why a record was skipped during ingest.
const ( SkipReasonConflictManual SkipReason = "conflict_manual" // Existing manual source won SkipReasonConflictOlder SkipReason = "conflict_older" // Existing record has newer timestamp SkipReasonValidation SkipReason = "validation" // Row failed validation SkipReasonDatabase SkipReason = "database" // Database error during ingest SkipReasonOther SkipReason = "other" // Other skip reasons )
func (SkipReason) String ¶
func (r SkipReason) String() string
String returns the string representation of the skip reason.