search

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Nov 8, 2026 License: AGPL-3.0 Imports: 12 Imported by: 0

Documentation

Overview

Package search implements the SearchService: validate → plan → execute → shape, applying the mode filter (default: service, PD-1) with no mode semantics in any caller (RS-3, SPEC-04).

M1: package skeleton only (this doc.go). Full implementation lands in M10. The SearchService depends only on the read-side SearchIndex interface; it has no dependency on any collector, egress, or P2 package (ADR-0010 Search ≠ Scan; AC-3.1 import-graph test).

Index

Constants

View Source
const (
	FacetPort       = "port"
	FacetProtocol   = "protocol"
	FacetTechnology = "technology"
	FacetService    = "service"
	FacetCountry    = "country"
	FacetSource     = "source"
	FacetFreshness  = "freshness"
)

Facet dimension names. The set is fixed (facet SQL is per-dimension by design); unknown names are rejected with E_QUERY_FACET.

View Source
const (
	DefaultFacetValues = 10
	MaxFacetValues     = 50
)

Defaults for facet cardinality (PD-consistent with the search caps).

Variables

FacetNames lists the facet dimensions in API order.

Functions

func AsTimeout

func AsTimeout(err error) bool

AsTimeout / AsUnavailable are the classification helpers surfaces use.

func AsUnavailable

func AsUnavailable(err error) bool

Types

type Facet

type Facet struct {
	Name      string       `json:"facet"`
	Values    []FacetValue `json:"values"`
	Truncated bool         `json:"truncated"` // more distinct values exist than returned
}

Facet is one computed dimension.

type FacetValue

type FacetValue struct {
	Value string `json:"value"`
	Count int64  `json:"count"`
}

FacetValue is one bucket.

type FacetsInfo

type FacetsInfo struct {
	GQL       string `json:"gql"`
	Mode      string `json:"mode"`
	TookMs    int64  `json:"took_ms"`
	MaxValues int    `json:"max_values"`
}

FacetsInfo describes the executed facet request.

type FacetsQuery

type FacetsQuery struct {
	// GQL is the raw filter expression (required; same language as
	// search — an empty filter is NOT allowed, facets always narrow).
	GQL string
	// Mode: "service" (default) or "asset" — mirrors search.
	Mode string
	// Timeout: 0 = the service default.
	Timeout time.Duration
	// MaxValues per facet: 0 = DefaultFacetValues; hard cap
	// MaxFacetValues.
	MaxValues int
	// Facets: which dimensions to compute; empty = all of them.
	Facets []string
}

FacetsQuery is one faceted-search request.

type FacetsResult

type FacetsResult struct {
	Facets []Facet    `json:"facets"`
	Query  FacetsInfo `json:"query"`
}

FacetsResult is the response of one Facets call.

type IndexMeta

type IndexMeta struct {
	Assets       int64      `json:"assets"`
	Services     int64      `json:"services"`
	Observations int64      `json:"observations"`
	FixtureObs   int64      `json:"fixture_observations"`
	LastObserved *time.Time `json:"last_observed_at,omitempty"`
	FixtureOnly  bool       `json:"fixture_only"`
	Empty        bool       `json:"empty"`
}

IndexMeta is the freshness snapshot attached to results (and served by the API's /api/v1/stats).

func (*IndexMeta) Notice

func (m *IndexMeta) Notice() string

Notice renders the caller-facing freshness notice ("" when the index carries real data — which cannot happen in v0.0.1, but the contract is written for the v0.0.2 collectors too).

type Query

type Query struct {
	// GQL is the raw query text (required).
	GQL string
	// Mode: "service" (default, PD-1) or "asset".
	Mode string
	// Sort: comma-separated "field:dir" pairs (dir asc|desc, optional).
	// Empty = server default (last_seen DESC).
	Sort string
	// Limit: 0 = default.
	Limit int
	// Timeout: 0 = default.
	Timeout time.Duration
	// Cursor: opaque pagination cursor from a prior Result.
	Cursor string
	// Fields: optional projection (canonical paths or column names).
	Fields []string
	// WithStats: attach index freshness metadata to the Result.
	WithStats bool
}

Query is one search request. Zero fields fall back to the service defaults; Mode/Sort strings are the user-facing spellings.

type QueryError

type QueryError struct {
	Code   string
	Detail string
	Why    string
	Next   string
}

QueryError is a user-query failure (bad GQL, bad options). The CLI maps it to ExitInvalidInput (2); the API to 400.

func AsQuery

func AsQuery(err error) (*QueryError, bool)

AsQuery extracts a *QueryError (surfaces render Code/Detail/Next).

func (*QueryError) Error

func (e *QueryError) Error() string

type QueryInfo

type QueryInfo struct {
	GQL       string             `json:"gql"`
	Mode      string             `json:"mode"`
	Branch    string             `json:"branch"`
	Limit     int                `json:"limit"`
	TimeoutMs int64              `json:"timeout_ms"`
	Sort      []planner.SortSpec `json:"sort"`
	TookMs    int64              `json:"took_ms"`
	RowCount  int                `json:"row_count"`
	// Fixtures is true when the index the query ran against is
	// fixture-labeled (copied from Index when WithStats was set).
	Fixtures bool `json:"fixtures"`
}

QueryInfo describes the executed query.

type Result

type Result struct {
	Rows []Row
	// NextCursor is the cursor for the next page, "" when exhausted.
	// Derived from the LAST row (sort value + composite row key), so
	// callers must not build it themselves.
	NextCursor string
	// HasMore is len(rows) == the effective limit (keyset pagination
	// heuristic; one extra round-trip on the tail page is avoided).
	HasMore bool
	// Query describes how the query was compiled (mode, branch, took).
	Query QueryInfo
	// Index carries freshness metadata when Query.WithStats was set.
	Index *IndexMeta
	// Explain carries the rendered plan when requested (Query flag via
	// the Explain result field on the CLI path; see Plan()).
	Explain string
	// Notice is a caller-facing notice (e.g. empty index guidance,
	// fixture-data label).  Never empty when the index needs context.
	Notice string
}

Result is one search response.

type Row

type Row struct {
	RowID   int64  `json:"row_id"`
	RowKind string `json:"row_kind"` // service | asset-only
	AssetID int64  `json:"asset_id"`

	AssetKind  string `json:"asset_kind"` // ip | domain | hostname
	AssetValue string `json:"asset_value"`

	IP       *string `json:"ip,omitempty"`
	Domain   *string `json:"domain,omitempty"`
	Hostname *string `json:"hostname,omitempty"`

	ASN    *int    `json:"asn,omitempty"`
	ASNOrg *string `json:"asn_org,omitempty"`

	GeoCountry *string `json:"geo_country,omitempty"`
	GeoRegion  *string `json:"geo_region,omitempty"`
	GeoCity    *string `json:"geo_city,omitempty"`

	Port      *int    `json:"port,omitempty"`
	Transport *string `json:"transport,omitempty"`
	Protocol  *string `json:"protocol,omitempty"`
	Name      *string `json:"name,omitempty"`
	Product   *string `json:"product,omitempty"`
	Version   *string `json:"version,omitempty"`

	HTTPStatus *int    `json:"http_status,omitempty"`
	HTTPTitle  *string `json:"http_title,omitempty"`
	HTTPServer *string `json:"http_server,omitempty"`

	TLSVersion       *string    `json:"tls_version,omitempty"`
	TLSCertSubjectCN *string    `json:"tls_cert_subject_cn,omitempty"`
	TLSCertIssuerCN  *string    `json:"tls_cert_issuer_cn,omitempty"`
	TLSCertExpiry    *time.Time `json:"tls_cert_expiry,omitempty"`

	Technologies []string `json:"technologies,omitempty"`
	Sources      []string `json:"sources,omitempty"`

	FirstSeen time.Time `json:"first_seen"`
	LastSeen  time.Time `json:"last_seen"`
	Stale     bool      `json:"stale"`
	// contains filtered or unexported fields
}

Row is one search_index row (the v0.0.1 service-granular result). JSON tags are the API wire shape (SPEC-07).

type Service

type Service struct {

	// Defaults (PD-1 and SPEC-03 §3); a Query's explicit values win.
	DefaultMode    planner.Mode
	DefaultLimit   int
	DefaultTimeout time.Duration
	// contains filtered or unexported fields
}

Service executes GQL searches against the search_index view.

func New

func New(pool *pgxpool.Pool) *Service

New returns a Service over pool with the standard defaults.

func (*Service) Facets

func (s *Service) Facets(ctx context.Context, q FacetsQuery) (*FacetsResult, error)

Facets computes the requested dimensions over the GQL-filtered row set. Every dimension runs inside ONE transaction with the query timeout applied transaction-locally (same pattern as Search.exec — AC-2.8: the timeout never leaks into pooled session state).

func (*Service) Plan

func (s *Service) Plan(q Query) (*planner.Plan, error)

Plan compiles a query WITHOUT executing it — the `--validate` and `--explain` paths. Returns the compiled plan (or the first lexer/parser/validator/planner error).

func (*Service) Search

func (s *Service) Search(ctx context.Context, q Query) (*Result, error)

Search validates, plans, executes, and shapes one query.

func (*Service) Stats

func (s *Service) Stats(ctx context.Context) (*IndexMeta, error)

Stats snapshots the index counts. One round-trip, read-only.

type TimeoutError

type TimeoutError struct{ Err error }

TimeoutError is a query that exceeded its statement_timeout. The transaction was rolled back; the timeout applied only inside it. CLI → ExitUnavailable (4); API → 503 with API_E_QUERY_TIMEOUT.

func (*TimeoutError) Error

func (e *TimeoutError) Error() string

func (*TimeoutError) Unwrap

func (e *TimeoutError) Unwrap() error

type UnavailableError

type UnavailableError struct{ Err error }

UnavailableError is a search that could not reach the database (the pool is down, the connection broke). CLI → ExitUnavailable (4) with a doctor pointer; API → 503 with API_E_DB_UNAVAILABLE.

func (*UnavailableError) Error

func (e *UnavailableError) Error() string

func (*UnavailableError) Unwrap

func (e *UnavailableError) Unwrap() error

Jump to

Keyboard shortcuts

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