Documentation
¶
Overview ¶
Package postgres is the PostgreSQL adapter under internal/storage.
M1: package skeleton (doc.go only). M3 lands the storage layer:
- migrations runner (golang-migrate v4, database/pgx/v5 driver, source/iofs embedded migrations; one atomic transaction per migration) — migrate.go, lock.go
- bounded advisory-lock waiting (session-level statement_timeout; DB_MIGRATION_LOCK_TIMEOUT on expiry) — lock.go
- administrative migration commands backing `gseer db migrate {status,diagnose,force}` — admin.go
- pgxpool lifecycle (created AFTER migrations succeed) — pool.go
- reachability + PG version checks (E_DB_UNREACHABLE, PD-8) — reachability.go, version.go
- credential redaction for diagnostics (M3 discipline until SEC-6 lands in M10) — redact.go
- doctor migration-status checks (QS-4) — doctor.go
M4 lands the repositories and the search_index union view.
Search ≠ Scan (ADR-0010): this package contains no egress code and imports no collector package. The M1 import-graph test pins that invariant; it must hold for every future change here.
Index ¶
- Constants
- Variables
- func ApplyRuntimeGrants(ctx context.Context, dsn string) ([]string, error)
- func BootstrapRoles(ctx context.Context, dsn string) ([]string, error)
- func CheckPGVersion(ctx context.Context, rawDSN string, minMajor int) (int, error)
- func CheckReachable(ctx context.Context, rawDSN string) error
- func ForceMigrationVersion(rawDSN string, lockTimeoutMs, version int) error
- func HeadVersion() (uint, error)
- func MigrationFiles() ([]string, error)
- func MustRedactDSN(rawDSN string) string
- func NewPool(ctx context.Context, rawDSN string) (*pgxpool.Pool, error)
- func PGMajorVersion(ctx context.Context, conn *pgx.Conn) (int, error)
- func PingPool(ctx context.Context, pool *pgxpool.Pool) error
- func RedactDSN(rawDSN string) (string, error)
- func RoleExists(ctx context.Context, dsn, role string) (bool, error)
- func RunMigrations(rawDSN string, lockTimeoutMs int) (applied int, head uint, err error)
- func SetAppRolePassword(ctx context.Context, dsn, password string) error
- func VerifyRuntimeIdentity(ctx context.Context, pool *pgxpool.Pool) error
- type AuditReport
- type CheckOutcome
- type CheckResult
- type CollectionJob
- type DBError
- type DataSourceRow
- type DeadLetterRecord
- type Diagnosis
- type Fact
- type FactResult
- type IngestStats
- type MigrationStatus
- type PurgeStats
- type RetractStats
- type Store
- func (s *Store) AddDeadLetter(ctx context.Context, sourceID int64, payload map[string]any, reason string) error
- func (s *Store) AddSuppression(ctx context.Context, in SuppressInput) (rule suppress.Rule, existed bool, err error)
- func (s *Store) DeadLetterStats(ctx context.Context) (map[int64]int, error)
- func (s *Store) DiscardDeadLetters(ctx context.Context, sourceID int64, limit int) (int64, error)
- func (s *Store) EnsureFixtureSource(ctx context.Context) (sourceID int64, err error)
- func (s *Store) EnsureSource(ctx context.Context, name, connector, termsRef string, cfg map[string]any) (int64, error)
- func (s *Store) FailDeadLetter(ctx context.Context, id int64, failure string) error
- func (s *Store) FinishCollectionJob(ctx context.Context, jobID int64, status string, stats map[string]any, ...) error
- func (s *Store) GetCheckpoint(ctx context.Context, sourceID int64, key string) (string, error)
- func (s *Store) GetSource(ctx context.Context, name string) (DataSourceRow, error)
- func (s *Store) IndexSeedValues(ctx context.Context, kind string, limit int) ([]string, error)
- func (s *Store) IngestFact(ctx context.Context, f *Fact) (FactResult, error)
- func (s *Store) IngestFacts(ctx context.Context, facts []Fact) (IngestStats, error)
- func (s *Store) InsertObservation(ctx context.Context, o *storage.Observation) (id int64, inserted bool, err error)
- func (s *Store) ListSources(ctx context.Context) ([]DataSourceRow, error)
- func (s *Store) ListSuppressions(ctx context.Context) ([]suppress.Rule, error)
- func (s *Store) LoadSuppressions(ctx context.Context) ([]suppress.Rule, error)
- func (s *Store) PendingDeadLetters(ctx context.Context, sourceID int64, limit int) ([]DeadLetterRecord, error)
- func (s *Store) RemoveSuppression(ctx context.Context, id int64) (bool, error)
- func (s *Store) ResolveDeadLetter(ctx context.Context, id int64, note string) error
- func (s *Store) RetractSuppressed(ctx context.Context, rule suppress.Rule) (RetractStats, error)
- func (s *Store) SetCheckpoint(ctx context.Context, sourceID int64, key, cursor string) error
- func (s *Store) SetFingerprintEngine(reg *fprule.Registry)
- func (s *Store) SetSourceEnabled(ctx context.Context, name string, enabled bool) error
- func (s *Store) StartCollectionJob(ctx context.Context, sourceID int64, kind string) (int64, error)
- func (s *Store) UpsertAsset(ctx context.Context, a *storage.Asset) (id int64, inserted bool, err error)
- func (s *Store) UpsertService(ctx context.Context, sv *storage.Service) (id int64, inserted bool, err error)
- type SuppressInput
Constants ¶
const ( StateUninitialized = "UNINITIALIZED" // no schema_migrations table yet StateAtHead = "AT_HEAD" StateBehind = "BEHIND" StateAhead = "AHEAD" StateDirty = "DIRTY" )
Migration states reported by GetMigrationStatus.
const ( // CodeDBUnreachable — PostgreSQL is not reachable (SPEC-03 §7). CodeDBUnreachable = "E_DB_UNREACHABLE" // CodeDBVersion — the PostgreSQL major version is below the // supported floor (PD-8: >= 15). CodeDBVersion = "E_DB_VERSION" // CodeDBMigrationFailed — a migration failed mid-execution; its // transaction was rolled back and the version is marked dirty. CodeDBMigrationFailed = "E_DB_MIGRATION_FAILED" // CodeDBMigrationDirty — the schema_migrations table is in a dirty // state from a previously failed migration; recovery is operator- // driven via `gseer db migrate diagnose` + `force`. CodeDBMigrationDirty = "E_DB_MIGRATION_DIRTY" // CodeDBMigrationLockTimeout — the migration advisory lock could not // be acquired within the configured timeout (owner directive §1, // M3 authorization round). Named per the owner's exact identifier. CodeDBMigrationLockTimeout = "DB_MIGRATION_LOCK_TIMEOUT" // CodeDBUsage — an administrative command was invoked with invalid // arguments (e.g. force --version beyond head). CodeDBUsage = "E_DB_USAGE" // CodeDBSuppressed — a write was refused because the record matches // an enabled suppression rule (migration 0005; M13). Surfaced by // the DB backstop trigger (custom ERRCODE GSUP1) when a write path // bypasses the Go gate; the gate itself skips suppressed facts // cleanly and reports them via IngestStats.Suppressed. CodeDBSuppressed = "E_DB_SUPPRESSED" )
Stable storage-layer error codes.
const ( RoleApp = "gseer_app" RolePolicyOwner = "gseer_policy_owner" )
Hardened role names (pinned by the artifacts; not configurable).
const ( DefaultPoolMaxConns = 4 DefaultPoolMinConns = 1 DefaultPoolMaxConnLifetime = time.Hour DefaultPoolMaxConnIdleTime = 15 * time.Minute DefaultPoolHealthCheck = 30 * time.Second )
Default pool settings (conservative for the v0.0.1 dev/preview; owner decision D-M3-9).
const RetractLimit = 100_000
RetractLimit bounds one retraction run. A rule that matches more assets than this is a scope mistake (a /8, a TLD-wide suffix); the operator narrows the rule instead of the tool silently deleting millions of rows.
const ServerVersionNumQuery = `SELECT current_setting('server_version_num')::int`
ServerVersionNumQuery returns the numeric server version (e.g. 170002 for 17.2); the major is num / 10000.
Variables ¶
var ErrSourceNotFound = errors.New("data source not registered")
ErrSourceNotFound — registry lookup miss.
Functions ¶
func ApplyRuntimeGrants ¶ added in v0.2.0
ApplyRuntimeGrants executes the pinned §5.2 grant script (step 3). The pinned artifact names the default database (`ON DATABASE gseer`); for a deployment database with another name, exactly that token is substituted with the actual (identifier-quoted) database name — the only transformation this runner performs, and it reports the substitution as a notice so the change is never silent.
func BootstrapRoles ¶ added in v0.2.0
BootstrapRoles executes the pinned role-bootstrap script (step 1). Idempotent; refuse-first on unsafe pre-existing roles. Must run as a credential that may create roles (the database owner in the guided deployments).
func CheckPGVersion ¶
CheckPGVersion opens a connection, verifies the server major version is at least minMajor (PD-8 floor), and closes. It returns the detected major version on success (even when too low — callers may report it) and an E_DB_VERSION DBError when below the floor.
func CheckReachable ¶
CheckReachable verifies the database is reachable. It returns nil on success or an E_DB_UNREACHABLE DBError.
func ForceMigrationVersion ¶
ForceMigrationVersion sets schema_migrations to (version, dirty=false) WITHOUT executing any SQL migration (owner directive §2: the force operation "only changes migration version metadata", "never executes or repairs SQL migrations").
The CLI layer (internal/cli/db.go) adds the confirmation gate; this primitive enforces the argument contract: 0 <= version <= head.
func HeadVersion ¶
HeadVersion returns the highest embedded migration version. Doctor compares it against the database's recorded version to classify AT_HEAD / BEHIND / AHEAD (owner directive §2.5).
func MigrationFiles ¶
MigrationFiles returns the embedded up-migration file names in ascending version order (used by status/diagnose output).
func MustRedactDSN ¶
MustRedactDSN is the panic-free convenience wrapper for rendering paths where a malformed DSN means the diagnostic omits the DSN segment rather than failing the whole command. It returns the redacted form, or "" when the DSN cannot be parsed.
func NewPool ¶
NewPool builds a pgxpool with the GSEER defaults. The DSN is the operator's raw connection string (never logged — redact.go).
func PGMajorVersion ¶
PGMajorVersion queries the connected server's major version.
func PingPool ¶
PingPool is a helper for the server-start readiness path: the pool is created after migrations; this re-verifies liveness.
func RedactDSN ¶
RedactDSN strips the userinfo (user, password) from a URL-shaped PostgreSQL DSN and returns the display-safe form:
postgres://host:port/dbname?sslmode=...
The M2 fmtDSN builds DSNs via net/url, so operator-provided database.dsn values are expected to be URL-shaped too (documented in .env.example and docs/INSTALL.md). A non-URL DSN returns an error — callers then fall back to omitting the DSN from output entirely (never a partial or best-effort redaction).
func RoleExists ¶ added in v0.2.0
RoleExists reports whether a login role exists (used by doctor to keep its SKIP line honest when the hardened roles were never provisioned).
func RunMigrations ¶
RunMigrations applies pending embedded migrations to the database at rawDSN. It returns the number of migrations applied (0 when the schema was already at head — idempotent re-run).
Error translation (owner directives):
- advisory lock not acquired within the timeout → DB_MIGRATION_LOCK_TIMEOUT (lock.go);
- a migration failed mid-execution → E_DB_MIGRATION_FAILED (the failed migration's transaction was rolled back; the version is dirty — recovery via `gseer db migrate diagnose`);
- the database was already dirty → E_DB_MIGRATION_DIRTY.
func SetAppRolePassword ¶ added in v0.2.0
SetAppRolePassword sets the gseer_app login password. It is used by `gseer setup --self-hosted` at role-creation time (the bootstrap script deliberately never handles credentials). The statement is a bare ALTER ROLE — the password lives only in this call and the caller's own secret channel.
func VerifyRuntimeIdentity ¶ added in v0.2.0
VerifyRuntimeIdentity asserts the pool's connecting user IS the gseer_app role (fail-closed). The pinned audit evaluates gseer_app's effective privileges; this check closes the remaining gap — a superuser or operator DSN configured as the runtime credential would otherwise serve traffic with privileges the grant matrix never intended. The identity check is cheap (one round trip) and runs on the same pool the server will serve from.
Types ¶
type AuditReport ¶ added in v0.2.0
type AuditReport struct {
// Passed is true when the audit completed without failures.
Passed bool
// Notices carries the audit's RAISE NOTICE lines (reachable-role
// walk, TEMPORARY source, the final PASSED line) in order.
Notices []string
// Failures is the complete GSUPF failure list (empty when passed).
Failures []string
}
AuditReport is the structured result of one privilege-audit run.
func RunPrivilegeAudit ¶ added in v0.2.0
func RunPrivilegeAudit(ctx context.Context, dsn string) (AuditReport, error)
RunPrivilegeAudit executes the pinned fail-closed audit (step 4). The audit is read-only. A GSUPF failure is returned as a populated AuditReport (Passed=false, Failures=complete list) with a nil error — the CALLER decides the fail-closed action (refuse startup); a nil error with Passed=true means the gate is open. Any other error (connectivity, syntax, unexpected SQLSTATE) is returned as a real error: an audit that could not RUN is not an audit that passed.
type CheckOutcome ¶
type CheckOutcome string
CheckOutcome classifies a doctor check line.
const ( CheckPass CheckOutcome = "PASS" CheckFail CheckOutcome = "FAIL" CheckHint CheckOutcome = "HINT" CheckSkip CheckOutcome = "SKIP" )
type CheckResult ¶
type CheckResult struct {
Name string
Outcome CheckOutcome
Detail string // human detail; built from redacted DSN only
Fix string // exact next command on FAIL/HINT
// Err carries the structured DBError for FAIL outcomes (nil other-
// wise) so the CLI can render the full Code/Why/Try shape.
Err error
}
CheckResult is one doctor line: what was checked, how it fared, and the fix instruction on failure.
func DoctorDBChecks ¶
func DoctorDBChecks(ctx context.Context, rawDSN string, minMajor int) []CheckResult
DoctorDBChecks runs the M3 database checks in QS-4 order: reachable → PG version → migrations. Each check that depends on a live connection SKIPs when the reachability check fails (doctor never hides the root cause behind a cascade of secondary failures).
type CollectionJob ¶
type CollectionJob struct {
ID int64
SourceID int64
Kind string
Status string
StartedAt time.Time
FinishedAt *time.Time
Stats map[string]any
Error string
}
CollectionJob is one connector run row.
type DBError ¶
DBError is the structured storage-layer error. It mirrors the config.Error shape so the CLI renders one consistent style.
type DataSourceRow ¶
type DataSourceRow struct {
ID int64
Name string
Connector string
TermsRef string
ConfigJSON []byte
Enabled bool
CreatedAt time.Time
}
DataSourceRow mirrors the data_sources row with the config kept as raw JSON (decoded by the connector layer).
type DeadLetterRecord ¶
type DeadLetterRecord struct {
ID int64
SourceID int64
Payload map[string]any
Reason string
Attempts int
FirstSeen time.Time
LastSeen time.Time
}
DeadLetterRecord is one pending dead-letter row handed to the re-drive.
type Diagnosis ¶
type Diagnosis struct {
Status *MigrationStatus
FailedFile string // embedded up-file for the dirty version, if any
InspectionSQL []string // suggested read-only inspection queries
ForceHint string // the exact force command, when applicable
}
Diagnosis is the read-only recovery guidance for a dirty database.
type Fact ¶
Fact, FactResult and IngestStats moved to internal/storage in v0.0.2 (M14: the collector framework names them without importing the pgx-heavy store package). These aliases keep every reference in this package — and every caller — compiling unchanged; []storage.Fact IS []Fact because the types are identical aliases.
type FactResult ¶
type FactResult = storage.FactResult
FactResult reports what one IngestFact applied (see storage.FactResult).
type IngestStats ¶
type IngestStats = storage.IngestStats
IngestStats aggregates an IngestFacts run (see storage.IngestStats).
type MigrationStatus ¶
type MigrationStatus struct {
State string // one of the State* constants
Version int64 // recorded version; -1 when uninitialized
Dirty bool
Head uint // highest embedded migration version
Pending []string // up-file names above the recorded version
}
MigrationStatus is the read-only migration state snapshot.
func GetMigrationStatus ¶
func GetMigrationStatus(ctx context.Context, rawDSN string) (*MigrationStatus, error)
GetMigrationStatus reads schema_migrations (if it exists) and classifies the state against the embedded head version. Read-only: SELECT queries only.
type PurgeStats ¶
PurgeStats reports what PurgeFixtureData removed.
func PurgeFixtureData ¶
PurgeFixtureData removes every row the fixture loader wrote, in ONE transaction, scoped so real observations are NEVER deleted:
- the observation DELETE binds to the fixture source's registry id AND the provenance='fixture' tag — a real source's observation is unreachable by both predicates;
- services are deleted only if a fixture observation referenced them AND no observation references them anymore AFTER the observation deletion (separate statement so it sees the post-deletion state) — a real source's service always has a live reference and survives;
- assets are deleted only if they are fixture-sourced AND have no observation AND no service left — a real source's asset keeps its merged sources[] and/or live references and survives;
- surviving assets have 'fixture' stripped from sources[] (registry honesty: the fixture source no longer claims them).
The fixture source registry row itself is left in place (it is configuration, not data); FixtureSourcePresent reports whether it existed. A database with no fixture source row is a no-op success (nothing was ever loaded), not an error.
type RetractStats ¶
type RetractStats struct {
AssetsRetracted int
ServicesRemoved int
ObservationsRemoved int
AuditRows int
}
RetractStats reports one RetractSuppressed run.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the M4 write-side repository over the application pool (the pool is created after migrations succeed — D-M3-8).
func (*Store) AddDeadLetter ¶
func (s *Store) AddDeadLetter(ctx context.Context, sourceID int64, payload map[string]any, reason string) error
AddDeadLetter preserves a record that could not be ingested. An identical pending payload (same source + reason + payload hash) bumps attempts/last_seen instead of adding a new row (bounded growth under retry storms).
func (*Store) AddSuppression ¶
func (s *Store) AddSuppression(ctx context.Context, in SuppressInput) (rule suppress.Rule, existed bool, err error)
AddSuppression inserts a rule (value canonicalized first) and returns it with its database id. existed=true when an identical (kind, value) rule was already present — the existing row is returned unchanged (idempotent re-adds are not audit events).
v0.2.0 policy path (migration 0016): direct DML on public.suppressions is refused by the gseer_policy statement trigger (ERRCODE GSUP2) for EVERY role, so the write goes through the SECURITY DEFINER function gseer_policy.suppression_insert. The call runs in ONE transaction that first takes the SAME advisory lock the 0016 statement trigger takes (pg_advisory_xact_lock(0x47534552, 0x504F4C31) — "GSER"/"POL1", OD-6 keys), which serializes concurrent rule mutations and keeps the existed determination exact (a concurrent re-add observes the winner's committed row instead of racing past it).
func (*Store) DeadLetterStats ¶
DeadLetterStats counts the pending dead letters per source.
func (*Store) DiscardDeadLetters ¶
DiscardDeadLetters marks pending rows discarded (operator's explicit judgment that a record will never apply — e.g. a malformed payload from a retired source). Returns the number of rows marked.
func (*Store) EnsureFixtureSource ¶
EnsureFixtureSource upserts the fixture source registry row and returns its id (D-M3-1: the fixture source is itself a data_sources row). Re-running refreshes terms_ref but deliberately leaves enabled untouched: v0.0.1 sources are never enabled (SPEC-09 §1).
func (*Store) EnsureSource ¶
func (s *Store) EnsureSource(ctx context.Context, name, connector, termsRef string, cfg map[string]any) (int64, error)
EnsureSource upserts a data_sources registry row and returns its id. Enablement is NEVER flipped here: it is an explicit operator decision (config + review status), never a side effect of registration.
func (*Store) FailDeadLetter ¶
FailDeadLetter records a failed re-drive attempt (attempts bump, still pending — the record is never silently dropped).
func (*Store) FinishCollectionJob ¶
func (s *Store) FinishCollectionJob(ctx context.Context, jobID int64, status string, stats map[string]any, errMsg string) error
FinishCollectionJob closes a job with status + counters + error text.
func (*Store) GetCheckpoint ¶
GetCheckpoint returns the stored cursor for (source, key); "" when none.
func (*Store) IndexSeedValues ¶
IndexSeedValues returns up to limit asset values of the given kind, freshest first (seed_mode "index": the connector enriches identifiers ALREADY in the operator's index — no third-party target list is ever consulted; P1 honesty contract 1).
func (*Store) IngestFact ¶
IngestFact applies one Fact atomically: asset upsert → optional service upsert → observation insert, all in ONE transaction. The observation write is last, so a failure there (e.g. an FK violation for an unknown source) rolls the whole fact back — a service can never be left without its asset, nor an observation without its service.
Suppression gate (M13): the fact is checked against the compiled rule set BEFORE any write — source exclusion first, then asset rules. A suppressed fact writes nothing (not even asset metadata): it returns FactResult{Suppressed: true} with a nil error, and one ingest-block row lands in suppression_applications.
Deliberate side effect: the caller's f.Observation.AssetID, f.Service.AssetID and — when a service is present — f.Observation.ServiceID are updated to the resolved row ids, so follow-up code can reference them without a re-read.
func (*Store) IngestFacts ¶
IngestFacts applies facts one at a time — each fact commits separately because facts are independent: a failure aborts only its own transaction, and a re-run is idempotent (L1/L2 identities) — and aggregates the results. On the first error it stops and returns the stats accumulated so far with the error. Service counters advance only for facts that carry a service (an asset-level fact is neither a service insert nor a service update).
The suppression rule set is compiled ONCE for the whole batch (the gate then applies per fact, writing the ingest-block audit rows); suppressed facts count into IngestStats.Suppressed and are skipped.
func (*Store) InsertObservation ¶
func (s *Store) InsertObservation(ctx context.Context, o *storage.Observation) (id int64, inserted bool, err error)
InsertObservation inserts one observation at pool scope. The id is CALLER-PROVIDED — observations.id has no sequence default because the fixture corpus loader assigns deterministic ids (D-M3-2) — and is passed through as-is.
Dedup semantics: on any unique-index conflict the already-stored row WINS (its content and provenance are kept) and inserted=false is returned with the surviving row's id, resolved via the L1 delivery lookup first, then the L2 fact lookup. An empty Provenance defaults to the fixture tag (QS-6: unlabeled rows must never look real).
func (*Store) ListSources ¶
func (s *Store) ListSources(ctx context.Context) ([]DataSourceRow, error)
ListSources returns every registered source.
func (*Store) ListSuppressions ¶
ListSuppressions returns every rule (including disabled and expired — audit sees everything; the Matcher applies the filters).
func (*Store) LoadSuppressions ¶
LoadSuppressions reads every rule row (enabled, disabled and expired — the Matcher applies the filters so a rule flip is visible in one place).
func (*Store) PendingDeadLetters ¶
func (s *Store) PendingDeadLetters(ctx context.Context, sourceID int64, limit int) ([]DeadLetterRecord, error)
PendingDeadLetters lists pending dead-letter rows, oldest activity last (re-drive order: oldest first). sourceID 0 = every source.
func (*Store) RemoveSuppression ¶
RemoveSuppression deletes rule id via the 0016 policy function (gseer_policy.suppression_delete). Returns false when the id does not exist (the function raises GSUP2 on zero rows — fail-closed, never a silent zero-row success). NOTE: removing a rule does NOT resurrect previously retracted records — they were deleted with audit rows in suppression_applications.
func (*Store) ResolveDeadLetter ¶
ResolveDeadLetter marks a dead letter resolved after a successful re-drive (the note appends to the reason for the audit trail).
func (*Store) RetractSuppressed ¶
RetractSuppressed removes the already-indexed records matched by rule and writes one retroactive-retract audit row per retracted asset (plus one summary row for source retractions). The whole retraction is ONE transaction: either the matching records leave the index together with their audit rows, or nothing changes.
Scope by rule kind:
- ip/cidr: kind='ip' assets by exact / containment match
- domain: kind='domain'|'hostname' assets by exact name
- domain_suffix: those kinds by strict-label suffix
- source: every observation of that source, then orphaned services, then the source name stripped from assets.sources[], then assets left with no observation, no service and no source name
A rule matching more than RetractLimit assets is refused (E_DB_USAGE): narrowing a runaway rule is an operator decision, never a silent bulk delete. Retraction deletes by MATCH only — provenance is never read — so the fixture purge (provenance='fixture'-scoped) and this path stay orthogonal (fixture-purge isolation directive).
func (*Store) SetCheckpoint ¶
SetCheckpoint stores the cursor for (source, key) upsert-style.
func (*Store) SetFingerprintEngine ¶
SetFingerprintEngine attaches the rule engine used to enrich ingested observation evidence. A nil engine disables enrichment. The engine only fires when the evidence has http/tls/ssh payload and no source-provided `fingerprint` key (the source always wins).
func (*Store) SetSourceEnabled ¶
SetSourceEnabled flips the enablement flag — the explicit operator action. The collect runner re-verifies the connector's review verdict before actually fetching, so a hand-flipped row cannot bypass the terms gate.
func (*Store) StartCollectionJob ¶
StartCollectionJob opens a 'running' job row and returns its id.
func (*Store) UpsertAsset ¶
func (s *Store) UpsertAsset(ctx context.Context, a *storage.Asset) (id int64, inserted bool, err error)
UpsertAsset upserts one asset at pool scope (single-statement auto-commit; callers wanting per-fact atomicity use IngestFact).
Validation: Kind must be one of ip|domain|hostname, the kind's typed pointer (IP/Domain/Hostname) must be non-nil and Value non-empty — usage errors are E_DB_USAGE, not raw SQLSTATEs from the schema's assets_kind_fields_check.
On return a.ID, a.FirstSeen and a.LastSeen carry the stored row's values (on conflict FirstSeen stays at the original, since RETURNING exposes the new row and first_seen is not in the update set); a.Sources is NOT re-read — the DB-side union may differ in order from the caller's slice. A zero LastSeen means "seen now" (the column is written explicitly, so the year-1 zero time must never reach SQL). ASN 0 = unset (the CASE preserves the stored value on conflict).
func (*Store) UpsertService ¶
func (s *Store) UpsertService(ctx context.Context, sv *storage.Service) (id int64, inserted bool, err error)
UpsertService upserts one service at pool scope. Validation: AssetID must be a positive asset id, Port 1..65535, Transport tcp|udp and Protocol non-empty (E_DB_USAGE otherwise). On return sv.ID, sv.FirstSeen and sv.LastSeen carry the stored row's values; a zero LastSeen means "seen now".
No suppression gate here BY DESIGN: a service hangs off an existing asset, and the asset's identifier was gated when the asset was written (and is re-gated by the backstop trigger on every asset write). A suppression retraction removes the asset row, cascading its services — so no service can outlive a suppression that covers its asset.