enrich

package
v0.232.0 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package enrich executes enrichment sources against the deployment's grain store in one of two modes: direct (auto-approve -- assertions land in the source's enrichment:<name> graph) or queue (candidates become PIPELINE-provenance suggestions for moderation). The mode is a per-source deployment decision; the enrichers themselves are mode-blind.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnknownSource names a source the deployment has not configured.
	ErrUnknownSource = errors.New("unknown enrichment source")
	// ErrMisconfigured marks a source whose configuration cannot run
	// (invalid mode, queue mode without the suggestion service).
	ErrMisconfigured = errors.New("enrichment source misconfigured")
)

Run error classes, so the HTTP surface can map failure cause to status: the caller's mistake (ErrUnknownSource), the deployment's mistake (ErrMisconfigured), the upstream service's fault (ingest.ErrEnricher), or -- unwrapped -- a storage fault.

View Source
var ErrJobNotFound = errors.New("enrichment job not found")

ErrJobNotFound reports an unknown job id.

View Source
var ErrValidation = errors.New("enrich: invalid request")

ErrValidation reports a caller mistake in a run/job request (bad host, hosts on a source that takes none); the HTTP layer answers 400.

Functions

This section is empty.

Types

type Describer added in v0.215.0

type Describer interface {
	Describe() string
}

Describer is an optional Enricher capability: Describe names what the source talks to, for humans -- the bibliocommons peer subdomains, a crosswalk's target vocabulary -- so a job card can say which library a three-hour run is pulling from.

type HostScoped added in v0.212.0

type HostScoped interface {
	ForHosts(hosts []string) ingest.Enricher
}

HostScoped is an optional Enricher capability: ForHosts returns a per-run view of the enricher scoped to the given peer hosts (the bibliocommons harvest), so one job can sweep a different peer list without a restart.

type Job added in v0.195.0

type Job struct {
	ID string `json:"id"`
	// Kind is the empty enrichment kind unless this is a bulk queue action.
	Kind   JobKind `json:"kind,omitempty"`
	Source string  `json:"source"`
	// Approve is the queue scope for a QUEUE_APPROVE job (nil otherwise): the
	// snapshot of pending rows the run approves is taken from this filter.
	Approve *suggest.QueueQuery `json:"approve,omitempty"`
	// Done/Total are generic progress for a QUEUE_APPROVE run (enrichment uses
	// Stats instead): rows acted on so far, out of the kick-time snapshot.
	Done  int `json:"done,omitempty"`
	Total int `json:"total,omitempty"`
	// ApproveResult is the completed bulk-approve tally (DONE, QUEUE_APPROVE).
	ApproveResult *suggest.ApproveAllResult `json:"approveResult,omitempty"`
	// Filters are the run's [key, value] scope terms (ingest.MatchExtras
	// semantics -- the same scoping the synchronous run and the audit use).
	Filters [][2]string `json:"filters,omitempty"`
	// Hosts is the per-job peer-host override, for sources that take one
	// (the bibliocommons harvest): sweep a different peer list without a
	// restart. Empty keeps the source's configured hosts.
	Hosts []string `json:"hosts,omitempty"`
	// Target names what this run talks to (the source's descriptor at
	// creation, host overrides applied) -- stamped on the record so a
	// finished job still says what it pulled after the config changes.
	Target    string    `json:"target,omitempty"`
	Requester string    `json:"requester"`
	Status    JobStatus `json:"status"`
	// Stats is the live progress while RUNNING (updated per statsInterval
	// when the source reports counters) and the final tallies after.
	Stats *ingest.EnrichStats `json:"stats,omitempty"`
	// Result is the completed run's summary (DONE only).
	Result *Result `json:"result,omitempty"`
	// Error is the failure, classified the same way the synchronous
	// endpoint classifies it (FAILED only; generic, detail in the log).
	Error      string    `json:"error,omitempty"`
	CreatedAt  time.Time `json:"createdAt"`
	StartedAt  time.Time `json:"startedAt,omitzero"`
	FinishedAt time.Time `json:"finishedAt,omitzero"`
	// HeartbeatAt is the worker's liveness signal, rewritten on every stats
	// tick while the run is in flight; a RUNNING record whose heartbeat goes
	// stale is an orphan (its process died mid-run) and the next drain
	// fails it rather than leaving it RUNNING forever.
	HeartbeatAt time.Time `json:"heartbeatAt,omitzero"`
}

Job is one asynchronous enrichment run: kicked with a source and scope, drained by the worker, its record carrying live batch counters while it runs so a poller can show progress on an hours-long corpus pass.

type JobKind added in v0.228.0

type JobKind string

JobKind discriminates what a job does. The empty kind is an enrichment run (the original and only kind), kept empty so existing records deserialize unchanged; QUEUE_APPROVE is a filter-scoped bulk queue approve-all.

const (
	JobKindEnrich     JobKind = ""
	JobKindApproveAll JobKind = "QUEUE_APPROVE"
)

type JobStatus added in v0.195.0

type JobStatus string

JobStatus is the async-run lifecycle.

const (
	JobQueued  JobStatus = "QUEUED"
	JobRunning JobStatus = "RUNNING"
	JobDone    JobStatus = "DONE"
	JobFailed  JobStatus = "FAILED"
)

type Mode

type Mode string

Mode selects how a source's results land.

const (
	// ModeQueue routes candidates through moderation (the approval gate).
	ModeQueue Mode = "queue"
	// ModeDirect writes the source's enrichment graph outright
	// (auto-approve on import).
	ModeDirect Mode = "direct"
)

type Result

type Result struct {
	Source string `json:"source"`
	Mode   Mode   `json:"mode"`
	// Works is the number of Works enriched (direct) or with candidates
	// queued (queue).
	Works int `json:"works"`
	// Suggestions is the exact count of NEW queue rows this run created
	// (queue mode): pairs already suggested, tombstoned, or resolved are
	// silent no-ops and do not count.
	Suggestions int `json:"suggestions,omitempty"`
	// Scope names the run's filter ("" when the whole corpus).
	Scope string `json:"scope,omitempty"`
	// Stats carries the enricher's own run counters (batches, skips,
	// resolved, elapsed) when the source reports them.
	Stats *ingest.EnrichStats `json:"stats,omitempty"`
}

Result summarizes one run.

type Service

type Service struct {
	Blob        blob.Store
	GrainPrefix string
	Queue       *suggest.Service
	Sources     map[string]Source
	// Summaries, when set, is the shared maintained summary source
	// (workindex) queue-mode runs read instead of a per-run
	// corpus walk; nil falls back to ScanSummaries.
	Summaries ingest.SummarySource
	// DB, when set, enables the async job surface (jobs.go): kick returns a
	// job id, a worker drains, GET polls live progress. Nil keeps runs
	// synchronous-only.
	DB store.Store
	// MaxParallel caps how many jobs a single drain runs at once across
	// distinct sources; 0 is unlimited (one per queued source). The drain
	// never runs two jobs of the SAME source concurrently regardless.
	MaxParallel int
	// Now overrides the job clock (tests).
	Now func() time.Time
	// contains filtered or unexported fields
}

Service runs configured sources.

func (*Service) CreateApproveAllJob added in v0.228.0

func (s *Service) CreateApproveAllJob(ctx context.Context, requester string, q suggest.QueueQuery) (Job, error)

CreateApproveAllJob kicks a filter-scoped bulk queue approve-all as an async job on the same board (so it lists, heartbeats, and is orphan-reaped like an enrichment run). It claims and runs in a detached goroutine -- approving tens of thousands of rows is minutes of CAS writes, not a request -- while the claim keeps a concurrent container worker from double-running it.

func (*Service) CreateJob added in v0.195.0

func (s *Service) CreateJob(ctx context.Context, requester, source string, filters [][2]string, hosts []string) (Job, error)

CreateJob queues an asynchronous run. The source must exist (the caller's mistake surfaces at kick time, not first drain); execution happens on the worker via RunQueuedJobs.

func (*Service) DispatchQueued added in v0.224.2

func (s *Service) DispatchQueued(ctx context.Context) (int, error)

DispatchQueued tops up the running set WITHOUT joining and returns how many jobs it launched this tick. It is the continuous container worker: the ticker calls it every tick, and it launches one goroutine per idle source (no live RUNNING record and none of this process's goroutines already on it), so a QUEUED job for an idle source dispatches on the next tick no matter how long a sibling source's job runs. This is what keeps a slow job (a rate-limited multi-host bibliocommons crawl) from starving every later-queued job. Same-source stays serial. MaxParallel caps the concurrent in-flight count.

func (*Service) GetJob added in v0.195.0

func (s *Service) GetJob(ctx context.Context, id string) (Job, error)

GetJob returns one job. The surface is admin-gated, so there is no requester scoping.

func (*Service) ListJobs added in v0.195.0

func (s *Service) ListJobs(ctx context.Context) ([]Job, error)

ListJobs returns every live job, newest first.

func (*Service) Names

func (s *Service) Names() []string

Names lists the configured sources.

func (*Service) ReapStaleJobs added in v0.224.1

func (s *Service) ReapStaleJobs(ctx context.Context) (int, error)

ReapStaleJobs fails every RUNNING record whose heartbeat has gone stale and returns how many it reaped. It runs on a cadence INDEPENDENT of RunQueuedJobs: the parallel drain joins its launched jobs, so a legit long run (a multi-hour TLC crawl) keeps that call from returning and the drain's own inline reap never fires again -- leaving a hung sibling orphaned until a full process restart. A standalone reaper on its own ticker recovers such orphans while the drain is still joined.

func (*Service) Run

func (s *Service) Run(ctx context.Context, name string, keep func(*ingest.WorkSummary) bool) (Result, error)

Run executes one configured source by name. A non-nil keep scopes the run to the summaries it accepts: only those works are handed to the enricher (an external-service source queries for exactly the scoped set) and only their grains gain statements; out-of-scope works keep what they have.

func (*Service) RunHosted added in v0.212.0

func (s *Service) RunHosted(ctx context.Context, name string, keep func(*ingest.WorkSummary) bool, hosts []string) (Result, error)

RunHosted is Run with an optional per-run peer-host override for sources that take one.

func (*Service) RunQueuedJobs added in v0.195.0

func (s *Service) RunQueuedJobs(ctx context.Context) (int, error)

RunQueuedJobs drains QUEUED jobs once -- the worker-loop body for container deployments (a ticker) and scheduled serverless drains alike. Job failures land in the job record; the returned error is store trouble only.

Jobs for DISTINCT sources run concurrently: the sources hit independent external services with independent rate limits, so a whole-catalog Vega crawl need not block a BiblioCommons consensus run queued behind it. Jobs for the SAME source stay serial -- two runs sharing the caller IP would trip a peer's per-IP limiter -- enforced two ways: a source with a live RUNNING record is skipped this tick, and only its oldest QUEUED job is dispatched. MaxParallel optionally caps the concurrency across sources.

The drain also reaps orphans: a RUNNING record whose heartbeat has gone stale belongs to a process that died between claim and completion, and nothing else would ever finish it.

func (*Service) Targets added in v0.215.0

func (s *Service) Targets() map[string]string

Targets maps each configured source to its human descriptor (what it talks to), for the sources that expose one.

type Source

type Source struct {
	Enricher ingest.Enricher
	Mode     Mode
	// Scheme keys the queued TermRefs (e.g. "lcsh"); queue mode only.
	Scheme string
}

Source pairs an enricher with its deployment mode.

Jump to

Keyboard shortcuts

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