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 ¶
- Variables
- type Describer
- type HostScoped
- type Job
- type JobKind
- type JobStatus
- type Mode
- type Result
- type Service
- func (s *Service) CreateApproveAllJob(ctx context.Context, requester string, q suggest.QueueQuery) (Job, error)
- func (s *Service) CreateJob(ctx context.Context, requester, source string, filters [][2]string, ...) (Job, error)
- func (s *Service) DispatchQueued(ctx context.Context) (int, error)
- func (s *Service) GetJob(ctx context.Context, id string) (Job, error)
- func (s *Service) ListJobs(ctx context.Context) ([]Job, error)
- func (s *Service) Names() []string
- func (s *Service) ReapStaleJobs(ctx context.Context) (int, error)
- func (s *Service) Run(ctx context.Context, name string, keep func(*ingest.WorkSummary) bool) (Result, error)
- func (s *Service) RunHosted(ctx context.Context, name string, keep func(*ingest.WorkSummary) bool, ...) (Result, error)
- func (s *Service) RunQueuedJobs(ctx context.Context) (int, error)
- func (s *Service) Targets() map[string]string
- type Source
Constants ¶
This section is empty.
Variables ¶
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.
var ErrJobNotFound = errors.New("enrichment job not found")
ErrJobNotFound reports an unknown job id.
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
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.
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
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
GetJob returns one job. The surface is admin-gated, so there is no requester scoping.
func (*Service) ReapStaleJobs ¶ added in v0.224.1
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
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.