graph

package
v0.0.0-...-4f53d2e Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: GPL-3.0 Imports: 18 Imported by: 0

Documentation

Overview

Package graph implements the typed property graph the scan engine expands: content-addressed nodes, typed relations, positional props and the single writer that applies operator deltas. See docs/DESIGN.md.

Index

Constants

View Source
const ExcludePrefix = "^"

ExcludePrefix marks an id as one to remove rather than keep (§12.10).

'^' rather than '-' because "-whois" is indistinguishable from a flag to an argument parser, and rather than '!' or '~' because both are shell metacharacters that would need quoting.

View Source
const VariantRel = "VARIANT_OF"

VariantRel is the relation name that marks a generated variation.

Variables

This section is empty.

Functions

func Selection

func Selection(ids []string) (set map[string]bool, exclude bool, err error)

Selection reports the ids a --algorithm/--collect selection names and whether it names them to exclude.

Exported for the callers that have to re-examine a selection after the plan is compiled. SelectOperators promises that an id the user named is never silently dropped, but it can only keep that promise over the set it is handed: compilation prunes operators the seed type cannot reach, afterwards, and a named operator lost there is lost just as quietly.

Types

type Analysis

type Analysis struct {
	// contains filtered or unexported fields
}

Analysis is the read-only surface analyzers get. It exposes nodes, edges, status, provenance, plugin scores and the truncation ledger — but not the engine's belief.

Withholding belief is what makes "the execution model never contributes to a reported number" true by construction rather than by convention: an analyzer that could read it could launder it into a Severity and no reviewer would spot it. A plugin score is different — it describes the entity, not the traversal — so those are visible.

func (*Analysis) Assertions

func (a *Analysis) Assertions(id NodeID) []Assertion

func (*Analysis) Depth

func (a *Analysis) Depth(id NodeID) int

func (*Analysis) Edges

func (a *Analysis) Edges() []*Edge

func (*Analysis) Existence

func (a *Analysis) Existence(id NodeID) Existence

Existence rolls a node's statuses up. Three values, not two: collapsing "confirmed absent" into "unregistered" discards the distinction the whole status vocabulary exists to preserve — a variant nobody could resolve is not a variant proven free.

func (*Analysis) InClosure

func (a *Analysis) InClosure(id NodeID) bool

func (*Analysis) Incoming

func (a *Analysis) Incoming(id NodeID, rel string) []*Edge

Incoming returns edges pointing at a node, optionally filtered by relation. Analyzers need this to cluster: "which variants share this IP" is an in-edge query, and it is only answerable because infrastructure is nodes.

func (*Analysis) Ledger

func (a *Analysis) Ledger() []LedgerRow

func (*Analysis) Node

func (a *Analysis) Node(id NodeID) (*Node, bool)

func (*Analysis) Nodes

func (a *Analysis) Nodes() []*Node

func (*Analysis) Outgoing

func (a *Analysis) Outgoing(id NodeID, rel string) []*Edge

Outgoing returns a node's outgoing edges of a relation.

func (*Analysis) Rejections

func (a *Analysis) Rejections() []Rejection

func (*Analysis) Score

func (a *Analysis) Score(id NodeID, key string) (float64, bool)

func (*Analysis) Scores

func (a *Analysis) Scores(id NodeID) []ScoreRow

Scores enumerates a node's plugin-model scores, sorted by key. Engine belief is deliberately absent — see the type comment.

func (*Analysis) Status

func (a *Analysis) Status(id NodeID, op string) (Status, bool)

func (*Analysis) Statuses

func (a *Analysis) Statuses(id NodeID) []StatusRow

Statuses enumerates a node's recorded outcomes, sorted by operator. Status(id, op) answers a question you already know to ask; a report has to enumerate, and iterating the side table directly would expose map order.

func (*Analysis) Truncations

func (a *Analysis) Truncations() []RunTruncation

type Analyzer

type Analyzer interface {
	Id() string
	Exec(ctx context.Context, a *Analysis) ([]Finding, error)
}

Analyzer runs once, over the whole graph, after expansion stops — for any reason, including an interrupt. There is exactly one lifetime.

type Assertion

type Assertion struct {
	Field string
	Value Value
	By    Provenance
	Won   bool
}

Assertion is one operator's claim about a field, retained whether or not it won the merge. Disagreement between two sources is signal, not noise.

type BeliefModel

type BeliefModel interface {
	// Initial is the seed's prior and starting state, the seed having no parent.
	Initial() (float64, State)
	// Step is the forward filtering step: the parent's state pushed through the
	// relation that admitted this node, then conditioned on its props. It
	// returns the child's scalar belief and the state its own children inherit.
	//
	// parent is whatever this model returned for the parent node, or nil if the
	// parent has none yet; a model must treat nil as its initial state.
	Step(parent State, rel string, v View) (float64, State)
}

BeliefModel scores a node for execution control only: frontier ordering, pruning and operator gating. It never contributes to a reported number, which is why analyzers cannot see belief at all.

Belief is a pure function of the parent's belief and the node's props as of the current barrier. Nothing here reaches sideways into the graph, so the same run recomputes the same values in the same order.

type Budgets

type Budgets struct {
	Global  int
	PerType map[string]int
}

Budgets cap how many nodes may be admitted. Zero means unbounded.

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache stores operator results. The key covers everything the operator reads, so a hit is only served when nothing it depends on has changed.

A hit still occupies its round: the delta is applied at the same point a live call's would be, so a warm run and a cold run produce the same rounds, the same barriers and the same graph. A cache that short-circuited the round structure would make plan pinning depend on cache state.

func NewCache

func NewCache() *Cache

func (*Cache) Get

func (c *Cache) Get(k CacheKey) (Delta, Outcome, bool)

Get returns a cached result.

func (*Cache) Key

func (c *Cache) Key(op Operator, id NodeID, readDigest [32]byte) CacheKey

Key builds the cache key for one dispatch.

func (*Cache) Put

func (c *Cache) Put(k CacheKey, d Delta, o Outcome)

Put stores a result. Transient failures are never cached — the scheduler filters those before calling.

func (*Cache) SetModels

func (c *Cache) SetModels(operator string, cids []string)

SetModels records the model CIDs an operator reads.

func (*Cache) SetResourceConfig

func (c *Cache) SetResourceConfig(resource, digest string)

SetResourceConfig records the config digest for a resource class — the nameservers for dns, the registry URL and token for npm, the proxy and UA policy for http.

type CacheKey

type CacheKey [32]byte

CacheKey identifies one operator result.

type Canonical

type Canonical func(string) (string, error)

Canonical normalizes a raw key into the single form the graph converges on. Returning an error refuses the candidate outright.

type Capability

type Capability uint8

Capability classifies what may be done to a node type. Nameable is necessary but not sufficient for variant expansion — eligibility also requires seed-closure membership, which the applier enforces.

const (
	// Nameable types can be the root of variant generation.
	Nameable Capability = iota + 1
	// Observed types are only ever discovered, never varied.
	Observed
)

func (Capability) String

func (c Capability) String() string

type Class

type Class uint8

Class is a relation's edge class. It decides depth accounting and whether an edge extends the seed closure. The dividing line is whether producing the edge required a network call.

const (
	// Structural edges come from parsing the target string alone.
	Structural Class = iota + 1
	// Variant edges connect an origin to a generated variation.
	Variant
	// Observation edges required a lookup against some external service.
	Observation
)

func (Class) DepthCost

func (c Class) DepthCost() int

DepthCost is what traversing this class adds to a node's depth. Only observation hops count; structural and variant edges are free, so a composite seed does not spend its depth budget on decomposition.

func (Class) String

func (c Class) String() string

type Condition

type Condition interface {
	// contains filtered or unexported methods
}

Condition is an extra requirement on the matched node. Conditions are data conditions, never producer dependencies: "there is an IP", not "the ip operator has run".

func BeliefAbove

func BeliefAbove(t float64) Condition

BeliefAbove requires the execution model's belief to clear a threshold. It is evaluated only at a barrier, never during delta-driven re-dispatch, so it takes no part in the read-set digest.

func HasEdge

func HasEdge(rel string) Condition

HasEdge requires at least one outgoing edge of a relation.

func HasProp

func HasProp(field string) Condition

HasProp requires a field to be set on the matched node.

func InClosure

func InClosure() Condition

InClosure requires seed-closure membership. The engine already refuses out-of-closure variant edges at the applier; this lets an operator avoid the wasted dispatch as well.

type Delta

type Delta struct {
	Nodes []NodeRef
	Edges []EdgeRef
	Props []PropSet
}

Delta is everything one operator produced. Operators never mutate the graph; they return a Delta and the applier is the single writer. Deltas are additive only — nothing removes a node, edge or prop — which is what makes the graph monotonic within a run and a delta safely replayable.

type Edge

type Edge struct {
	ID    EdgeID
	From  NodeID
	To    NodeID
	Rel   *Rel
	Props Props
}

Edge is an admitted relation between two nodes.

func (*Edge) Addressed

func (e *Edge) Addressed() ([]byte, cid.Cid, error)

Addressed returns the edge's content-addressed encoding, as [from, relation, to, [values...]].

type EdgeID

type EdgeID [32]byte

EdgeID is hash(from, relation, to).

func (EdgeID) String

func (id EdgeID) String() string

type EdgeRef

type EdgeRef struct {
	From NodeRef
	Rel  string
	To   NodeRef
}

EdgeRef is how an operator names an edge.

type EdgeView

type EdgeView struct {
	Rel  string
	To   NodeRef
	ToID NodeID
	// contains filtered or unexported fields
}

EdgeView is one edge as an operator sees it: the far node and the edge's own props. Relation props carry data operators need — VARIANT_OF holds the algorithm and edit distance — which bare neighbour nodes would hide.

func (EdgeView) Prop

func (e EdgeView) Prop(field string) (Value, bool)

Prop returns an edge prop by name.

type Effects

type Effects struct {
	Nodes []string
	Rels  []string
	Props []string
}

Effects declares everything an operator may produce. It covers relations and props, not just node types, so plan compilation can see a prop-only operator and can detect a Where nothing in the plan will ever satisfy.

type Existence

type Existence uint8

Existence is the three-valued rollup of a node's observation statuses.

const (
	// Live — at least one observation operator returned ok.
	Live Existence = iota + 1
	// Absent — none did, and at least one authoritatively determined absence.
	Absent
	// Unknown — every attempt failed, timed out or was skipped.
	Unknown
)

func (Existence) String

func (e Existence) String() string

type Field

type Field struct {
	// contains filtered or unexported fields
}

Field is a resolved handle to a declared field. Operators obtain one at registration, so an unknown name fails then rather than returning a per-access boolean at runtime.

func (Field) Kind

func (f Field) Kind() Kind

func (Field) Name

func (f Field) Name() string

func (Field) Owner

func (f Field) Owner() string

type FieldDef

type FieldDef struct {
	Name       string
	Kind       Kind
	Merge      MergePolicy
	Deprecated bool
}

FieldDef declares one field of a node or relation type. Order in the declaring slice is the field's stable index and part of the on-disk contract: fields are append-only, and removal is by tombstone (Deprecated).

type Finding

type Finding struct {
	Kind     string
	Severity Severity
	Nodes    []NodeID    // admitted nodes this concerns
	Declined []LedgerRef // ledger rows this concerns
	Summary  string
	Evidence []Provenance
}

Finding is an analyzer's conclusion.

type Graph

type Graph struct {
	// contains filtered or unexported fields
}

Graph is the applier and the single writer. It is not safe for concurrent use; the scheduler serialises deltas through it.

func New

func New(reg *Registry) *Graph

New returns an empty graph bound to a registry.

func (*Graph) AddFindings

func (g *Graph) AddFindings(f ...Finding)

Findings stores an analyzer's conclusions in the side table.

func (*Graph) Analyze

func (g *Graph) Analyze() *Analysis

Analyze returns the read-only analysis surface.

func (*Graph) Apply

func (g *Graph) Apply(by Provenance, subject NodeID, d Delta) Result

Apply applies one operator's delta. subject is the node the operator ran on; nodes it emits inherit that depth, and edges adjust it by their class.

func (*Graph) Assertions

func (g *Graph) Assertions(id NodeID) []Assertion

Assertions returns every claim made about a node's fields, winning or not.

func (*Graph) Belief

func (g *Graph) Belief(id NodeID) float64

Belief returns a node's current belief.

func (*Graph) Decline

func (g *Graph) Decline(typeName, rawKey string, depth int, belief float64, r Reason, by Provenance) error

Decline records a candidate the engine refused to admit, and denies it thereafter. Truncation of every kind lands here, which is what makes "pruning is irreversible" true even when a second operator finds the same candidate later.

func (*Graph) Depth

func (g *Graph) Depth(id NodeID) int

Depth returns a node's shortest observation distance from the seed.

func (*Graph) Edge

func (g *Graph) Edge(id EdgeID) (*Edge, bool)

Edge returns an admitted edge.

func (*Graph) Edges

func (g *Graph) Edges() []*Edge

Edges returns every admitted edge sorted by (from, relation, to).

func (*Graph) Findings

func (g *Graph) Findings() []Finding

Findings returns every finding, most severe first, then stably by kind and summary so two runs render identically.

func (*Graph) InClosure

func (g *Graph) InClosure(id NodeID) bool

InClosure reports seed-closure membership: the seed plus everything reachable from it by structural edges. Only members may root variant generation.

func (*Graph) InScope

func (g *Graph) InScope(id NodeID) bool

InScope reports whether a node may root a variant under the current scope. The scheduler uses it to skip dispatching variant operators it knows will be rejected; the rejection in the applier remains the invariant.

func (*Graph) Ledger

func (g *Graph) Ledger() []LedgerRow

Ledger returns the truncation ledger in report order.

func (*Graph) Live

func (g *Graph) Live(id NodeID) bool

Live reports whether any observation operator returned ok for a node.

func (*Graph) Node

func (g *Graph) Node(id NodeID) (*Node, bool)

Node returns an admitted node.

func (*Graph) Nodes

func (g *Graph) Nodes() []*Node

Nodes returns every admitted node sorted by (type, key) — the canonical report order, and what makes two runs byte-comparable.

func (*Graph) NoteTruncation

func (g *Graph) NoteTruncation(r Reason, round int, detail string)

NoteTruncation records a run-level limit.

func (*Graph) Observers

func (g *Graph) Observers() []string

Observers returns the registered observer set, sorted.

It exists so the set can be persisted with the scan. Existence is computed from it, so a graph rebuilt without it answers differently from the run that produced it — the same bytes yielding two different verdicts, which is the one thing this store exists to rule out.

func (*Graph) Parent

func (g *Graph) Parent(id NodeID) (NodeID, string, bool)

Parent returns the node's tree parent and the relation that admitted it.

func (*Graph) Rejections

func (g *Graph) Rejections() []Rejection

Rejections returns every invariant violation refused so far.

func (*Graph) Risk

func (g *Graph) Risk(id NodeID) Severity

Risk is the maximum severity among findings referencing a node. It is what --filter risk>N and --fail-on select on, and the only user-facing score in the system.

func (*Graph) RunAnalyzers

func (g *Graph) RunAnalyzers(ctx context.Context, analyzers []Analyzer) error

RunAnalyzers runs every analyzer once and records their findings.

func (*Graph) Score

func (g *Graph) Score(id NodeID, key string) (float64, bool)

Score returns a plugin model's score for an entity.

func (*Graph) Seed

func (g *Graph) Seed(typeName, rawKey string) (NodeID, error)

Seed admits the target node at depth 0 and opens the seed closure. It is the only admission that does not descend from an existing node.

func (*Graph) SeedID

func (g *Graph) SeedID() NodeID

Seed returns the target node. The seed cannot be inferred from the graph: structural edges cost no depth, so a composite target puts several nodes at depth 0 inside the closure and "the one at depth 0" names no single node.

func (*Graph) SetBeliefModel

func (g *Graph) SetBeliefModel(m BeliefModel)

SetBeliefModel installs the execution model.

func (*Graph) SetBudgets

func (g *Graph) SetBudgets(b Budgets)

SetBudgets caps admissions. Exceeding a budget declines the candidate to the truncation ledger rather than dropping it silently.

func (*Graph) SetFrontier

func (g *Graph) SetFrontier(n int)

SetFrontier caps how many nodes may be admitted in one round. Zero is unbounded.

The cap applies in admission order, which the scheduler has already made deterministic — work is sorted by (depth, type, key, operator) and applied in that order rather than in completion order — so the same scan truncates at the same place on every run.

DESIGN §8 specifies the survivors as the prefix of candidates sorted by (-belief, depth, type, key). While the belief model is uniform those two orderings are the same list, so this is that rule with the constant factored out. A model that returns anything other than 1 makes them differ, and at that point the frontier has to become a queue drained at the barrier rather than a counter checked at admission.

func (*Graph) SetObservers

func (g *Graph) SetObservers(ids []string)

SetObservers names the operators whose status attests to a node's existence (§9): those that actually looked something up.

The distinction is load-bearing and was missing. Existence rolls up "did any operator return ok", and a decomposer returns ok when it successfully *parses* a name — which says nothing about whether that name exists. Without this, every syntactically valid variant read as live, so "-google.com" and "'oogle.com" were reported as live typosquats on the strength of having been parsed, with every DNS and whois lookup against them empty.

Membership is "the operator declared a rate-limit resource", i.e. it talks to something outside the process. A pure computation cannot attest to existence no matter what it returns.

func (*Graph) SetScope

func (g *Graph) SetScope(types []string)

SetScope restricts which node types may root a variant. An empty or nil list means every Nameable type in the seed closure, which is the default.

This is the CLI's scope positional (§12), and it is enforced here rather than only at dispatch for the same reason the closure rule is: dispatch-side gating is an optimization an operator can be written around, while an applier rejection is an invariant it cannot. Scope that was merely validated and printed — compiled into the plan, shown by --explain, and then ignored by execution — would make `typo username bob@example.com` silently identical to the unscoped run, which is the one thing the positional exists to prevent.

func (*Graph) SetScore

func (g *Graph) SetScore(id NodeID, key string, v float64)

SetScore records a plugin model's judgement about an entity. Scores are side table state, not props: as props they would make every node's CID depend on a model version and break cross-run diffing the moment anything is retrained.

func (*Graph) SetStatus

func (g *Graph) SetStatus(id NodeID, op string, s Status)

SetStatus records the terminal outcome of a (node, operator) pair. Skipped is not terminal and does not close the pair.

func (*Graph) Status

func (g *Graph) Status(id NodeID, op string) (Status, bool)

Status returns the recorded status of a pair.

func (*Graph) Truncations

func (g *Graph) Truncations() []RunTruncation

Truncations returns run-level limits that bound this expansion.

type Kind

type Kind uint8

Kind is the closed set of value kinds a prop may hold. Keeping it closed is what lets props encode deterministically; see docs/DESIGN.md §1.3.

const (
	KindString Kind = iota + 1
	KindInt
	KindFloat
	KindBool
	KindBytes
	KindTime
)

func (Kind) String

func (k Kind) String() string

type LedgerRef

type LedgerRef struct {
	Type string
	Key  string
}

LedgerRef names a declined candidate.

type LedgerRow

type LedgerRow struct {
	Type   string
	Key    string // canonical
	Depth  int
	Belief float64
	Reason Reason
	By     Provenance
}

LedgerRow records a candidate the engine declined to admit. The ledger is reported like any other section — a truncated graph that reads as complete is a correctness bug — and it doubles as a denylist, so a later operator re-emitting the same candidate cannot quietly resurrect it.

type Limiter

type Limiter struct {
	// contains filtered or unexported fields
}

Limiter throttles operator calls per resource class. A single global delay is meaningless once one run talks to DNS, whois, npm, PyPI and GitHub at once: the limit protecting the strictest service would throttle everything else to the same crawl.

This is the operator-declared layer. Per-host limiting belongs beneath it in the transport.

func NewLimiter

func NewLimiter() *Limiter

func (*Limiter) Acquire

func (l *Limiter) Acquire(ctx context.Context, resource string)

Acquire blocks until this resource class may be called again.

func (*Limiter) Set

func (l *Limiter) Set(resource string, minInterval time.Duration)

Set gives a resource class a minimum interval between calls.

func (*Limiter) SetSleep

func (l *Limiter) SetSleep(fn func(context.Context, time.Duration))

SetSleep replaces the sleep function. Tests use it to keep rate limiting observable without spending wall-clock time.

type Limits

type Limits struct {
	MaxDepth   int            // observation hops from the seed
	MaxRounds  int            // backstop for a type-flow that never converges
	Revisions  int            // per-pair re-runs; default 3
	Attempts   int            // per-pair attempts within a round; default 2
	Workers    int            // concurrent operator calls; default 1
	NodeBudget int            // global admitted-node cap
	TypeBudget map[string]int // per-type admitted-node cap
	Frontier   int            // cap on candidates admitted per round
	// OpTimeout bounds one Exec call. Zero means unbounded. It lives here
	// rather than inside each operator so a slow resource cannot stall a round
	// past the scheduler's knowledge — an operator holding its own private
	// deadline is one the barrier cannot reason about.
	OpTimeout time.Duration
}

Limits bound expansion. Zero means unbounded except where noted.

type MergePolicy

type MergePolicy struct {
	// contains filtered or unexported fields
}

MergePolicy resolves competing assertions of the same field. Resolution must not depend on arrival order — under concurrent dispatch that would be decided by network timing. See docs/DESIGN.md §1.4.

func Precedence

func Precedence(operators ...string) MergePolicy

Precedence declares operator ids in priority order, highest first. Operators not listed rank behind every listed one, and ties break on lowest id.

type Node

type Node struct {
	ID    NodeID
	Type  *NodeType
	Key   string // canonical
	Props Props
}

Node is an admitted graph node. It carries no provenance, status or findings: those live in side tables, so that two identical scans produce identical content addresses.

func (*Node) Addressed

func (n *Node) Addressed() ([]byte, cid.Cid, error)

Addressed returns the node's content-addressed encoding: the dag-cbor block and its CID. The form is a positional list — [type, key, [values...]] — not a map, so field names are not repeated per node and no key-sort step is needed to make encoding deterministic.

type NodeID

type NodeID [32]byte

NodeID is a node's stable identity: hash(type, canonical key). It is fixed for the node's whole life and is what the scheduler, seen-set, cache, edges and side tables key on. It is deliberately *not* the content address — props accumulate, so the CID changes while the identity does not.

func (NodeID) IsZero

func (id NodeID) IsZero() bool

IsZero reports the zero identity, which no real node has.

func (NodeID) String

func (id NodeID) String() string

type NodeRef

type NodeRef struct {
	Type string
	Key  string
}

NodeRef is how an operator names a node. It carries a *raw* key: operators cannot compute a NodeID because canonicalization belongs to the registry and the applier, and letting a plugin mint an identity is how convergence quietly breaks.

type NodeType

type NodeType struct {
	// contains filtered or unexported fields
}

NodeType is a registered node type handle.

func (*NodeType) Cap

func (t *NodeType) Cap() Capability

func (*NodeType) Field

func (t *NodeType) Field(n string) (Field, bool)

func (*NodeType) Name

func (t *NodeType) Name() string

func (*NodeType) Version

func (t *NodeType) Version() int

type NodeTypeDef

type NodeTypeDef struct {
	Name      string
	Cap       Capability
	Version   int
	Canonical Canonical
	Fields    []FieldDef
}

NodeTypeDef declares a node type. Version rises only when Fields is appended to or a field is tombstoned.

type OpBinding

type OpBinding struct {
	Id       string            `json:"id"`
	Version  int               `json:"version"`
	Resource string            `json:"resource,omitempty"`
	OnTypes  []string          `json:"on_types,omitempty"`
	OnCaps   []string          `json:"on_caps,omitempty"`
	Where    []string          `json:"where,omitempty"`
	Reads    Reads             `json:"reads"`
	Emits    Effects           `json:"emits"`
	Config   map[string]string `json:"config,omitempty"`
	Models   []string          `json:"models,omitempty"`

	// Dead marks an operator whose Where can never be satisfied by anything
	// this plan produces. It is listed, not run, and not fatal: usually it
	// means the user narrowed scope.
	Dead    bool   `json:"dead,omitempty"`
	DeadWhy string `json:"dead_why,omitempty"`
}

OpBinding is one operator as the plan sees it. This is the single definition; §5 and §10.6 of the design refer to it rather than restating it.

type Operator

type Operator interface {
	Id() string
	Version() int
	Trigger() Trigger
	Emits() Effects
	// Resource names the rate-limit class this operator's calls belong to.
	Resource() string
	// Exec does the work. The Outcome is the operator's own judgement of what
	// happened — an authoritative absence is Empty, not Failed — because how a
	// lookup failed is itself the finding.
	//
	// ctx carries the round deadline and the interrupt. An operator that makes
	// network calls must pass it down: without it the scheduler can only cancel
	// *between* attempts, so Ctrl-C waits for an in-flight whois rather than
	// stopping at the round boundary (§6.2, §12.4). Pure operators may ignore it.
	Exec(ctx context.Context, v View) (Delta, Outcome)
}

Operator is a unit of work bound to a pattern in the graph.

func SelectOperators

func SelectOperators(all []Operator, ids []string) ([]Operator, error)

SelectOperators filters a set by id, for --algorithm and --collect.

nil or empty   every operator
"dns", "ptr"   only these
"^whois"       everything except this

The two forms cannot be mixed in one call. A precedence rule between "keep only these" and "drop these" would have to be memorised to be used safely, and the combination expresses nothing the two forms cannot express separately.

An unknown id is an error rather than a silent omission. A run that quietly dropped half of what the user asked for — or, worse, selected nothing and reported a clean result — would present doing nothing as a finding of nothing.

type Outcome

type Outcome struct {
	Status Status
	Err    error
}

Outcome is what an operator reports alongside its delta. The status is the operator's own judgement: NXDOMAIN is StatusEmpty, not StatusFailed, and the difference is the signal a squatting scanner exists to collect.

func Empty

func Empty() Outcome

func Failed

func Failed(e error) Outcome

func OK

func OK() Outcome

OK, Empty, Failed and Timeout are shorthand for the common outcomes.

func Timeout

func Timeout(e error) Outcome

type Plan

type Plan struct {
	Hash      string      `json:"hash"`
	Seed      SeedSpec    `json:"seed"`
	Operators []OpBinding `json:"operators"`
	Model     string      `json:"model,omitempty"` // engine model CID
	Limits    Limits      `json:"limits"`

	// Pruned lists operators dropped as unreachable from the seed. Recording
	// them is what keeps a narrow plan from reading like a complete one.
	Pruned []string `json:"pruned,omitempty"`
	// contains filtered or unexported fields
}

Plan is the compiled, inspectable, pinnable answer to "what will this do?".

func Compile

func Compile(reg *Registry, ops []Operator, in PlanInput) (*Plan, error)

Compile turns a registry, a seed and an operator set into a plan.

Pruning is reachability over the type-flow graph, not a topological walk: that graph is cyclic, so reachability is transitive closure. It is an over-approximation because Emits is a *may*, not a *must*, and the plan says so rather than implying certainty.

func ReadPlan

func ReadPlan(r io.Reader) (*Plan, error)

ReadPlan loads a pinned plan.

func (*Plan) Explain

func (p *Plan) Explain(w io.Writer) error

Explain renders the plan: the type flow in SCC-condensed layers, then the operators, then anything dead or pruned.

func (*Plan) Select

func (p *Plan) Select(ops []Operator) []Operator

Select returns the operators named by the plan, in plan order. Execution dispatches only these; runtime never reaches past the plan.

func (*Plan) Write

func (p *Plan) Write(w io.Writer) error

Write serializes the plan for --plan FILE.

type PlanInput

type PlanInput struct {
	Seed   SeedSpec
	Limits Limits
	Model  string                       // engine model CID
	Config map[string]map[string]string // operator id -> flag values
	Models map[string][]string          // operator id -> model CIDs
}

PlanInput is everything compilation needs besides the operators themselves.

type PropSet

type PropSet struct {
	Node  *NodeRef
	Edge  *EdgeRef
	Field string
	Value Value
}

PropSet is a single field assertion against a node or an edge. Exactly one of Node or Edge must be set. Every prop write goes through this so the field's merge policy applies uniformly.

type Props

type Props struct {
	// contains filtered or unexported fields
}

Props holds one value per declared field, addressed positionally. Order is a property of the type rather than something imposed at encode time, so identical values encode identically by construction.

func (Props) Each

func (p Props) Each(fn func(Field, Value))

Each yields every set field in declaration order.

func (Props) Get

func (p Props) Get(f Field) (Value, bool)

Get returns the materialized value of f and whether it has been set.

func (Props) Setter

func (p Props) Setter(f Field) (string, bool)

Setter returns the operator id whose assertion is currently materialized.

type Provenance

type Provenance struct {
	Operator string
	Round    int
}

Provenance records who asserted something and when. It is kept out of the content-addressed form so identical scans produce identical CIDs.

type Reads

type Reads struct {
	Fields []string
	Rels   []string
}

Reads declares the props and relations an operator consumes. It does double duty: it scopes the View, and it is the input to the read-set digest that decides re-dispatch and cache validity.

type Reason

type Reason uint8

Reason explains why a candidate was declined.

const (
	// ReasonBelief — below the execution model's threshold.
	ReasonBelief Reason = iota + 1
	// ReasonBudget — a per-type or global node budget was exhausted.
	ReasonBudget
	// ReasonFrontier — the in-flight candidate bound was reached.
	ReasonFrontier
	// ReasonRoundCap — the round cap was hit.
	ReasonRoundCap
	// ReasonDeadline — the round deadline passed before it could be admitted.
	ReasonDeadline
)

func (Reason) String

func (r Reason) String() string

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds every registered node and relation type. It is not safe for concurrent registration; register during init, then read.

func NewRegistry

func NewRegistry() *Registry

func (*Registry) AddRel

func (r *Registry) AddRel(d RelDef) (*Rel, error)

AddRel registers a relation type.

func (*Registry) AddType

func (r *Registry) AddType(d NodeTypeDef) (*NodeType, error)

AddType registers a node type.

func (*Registry) Rel

func (r *Registry) Rel(name string) (*Rel, bool)

Rel looks up a registered relation.

func (*Registry) Rels

func (r *Registry) Rels() []*Rel

Rels returns every registered relation, ordered by name.

func (*Registry) Type

func (r *Registry) Type(name string) (*NodeType, bool)

Type looks up a registered node type.

func (*Registry) Types

func (r *Registry) Types() []*NodeType

Types returns every registered node type, ordered by name.

type RejectKind

type RejectKind uint8

RejectKind classifies an invariant violation. Unlike a ledger row, a rejection does not deny the candidate forever: a node refused as the source of a VARIANT_OF edge may still be admitted legitimately by another edge.

const (
	// RejectCanonical — the key could not be canonicalized.
	RejectCanonical RejectKind = iota + 1
	// RejectUnknownType — no such node type is registered.
	RejectUnknownType
	// RejectUnknownRel — no such relation is registered.
	RejectUnknownRel
	// RejectUnknownField — the field is not declared by the target's type.
	RejectUnknownField
	// RejectKindMismatch — the value's kind is not the field's kind.
	RejectKindMismatch
	// RejectClosure — a variant edge whose source is outside the seed closure.
	RejectClosure
	// RejectSelfVariant — a variant edge whose source and target canonicalize
	// to the same node.
	RejectSelfVariant
	// RejectScope — a variant edge whose source type the run's scope excludes.
	// Distinct from RejectClosure: the node is a legitimate variant root, the
	// user simply asked for a narrower scan, and conflating the two would make
	// a scoped run look like an invariant violation.
	RejectScope
	// RejectDenied — the candidate is in the truncation ledger.
	RejectDenied
	// RejectMissingNode — an edge or prop referenced a node that was refused.
	RejectMissingNode
)

func (RejectKind) String

func (k RejectKind) String() string

type Rejection

type Rejection struct {
	Kind   RejectKind
	Type   string
	Key    string
	Rel    string
	Field  string
	Detail string
	By     Provenance
}

Rejection records one refused item from a delta.

type Rel

type Rel struct {
	// contains filtered or unexported fields
}

Rel is a registered relation handle.

func (*Rel) Class

func (r *Rel) Class() Class

func (*Rel) Field

func (r *Rel) Field(n string) (Field, bool)

func (*Rel) Name

func (r *Rel) Name() string

func (*Rel) Version

func (r *Rel) Version() int

type RelDef

type RelDef struct {
	Name    string
	Class   Class
	Version int
	Fields  []FieldDef
}

RelDef declares a relation type. Relations carry their own ordered field list, encoded exactly like a node's.

type Result

type Result struct {
	Nodes    []NodeID
	Edges    []EdgeID
	Changed  []NodeID
	Rejected []Rejection
}

Result reports what an Apply admitted and what it refused.

type RunTruncation

type RunTruncation struct {
	Reason Reason
	Round  int
	Detail string
}

RunTruncation is a run-level limit that bound expansion, as opposed to a per-candidate ledger row. Hitting the round cap is reported like any other truncation: a truncated graph that reads as complete is a correctness bug.

type Scheduler

type Scheduler struct {

	// Stats, for progress and tests.
	Dispatched int
	CacheHits  int
	Retries    int
	Rounds     int
	// contains filtered or unexported fields
}

Scheduler drives expansion. It responds to three kinds of event — a delta applied, a timer for a retriable failure, and a round barrier — and every irreversible decision happens at a barrier.

func NewScheduler

func NewScheduler(g *Graph, ops []Operator, lim Limits) *Scheduler

NewScheduler binds a scheduler to a graph and an operator set.

func (*Scheduler) Cache

func (s *Scheduler) Cache() *Cache

Cache exposes the result cache so a caller can pre-warm or inspect it.

func (*Scheduler) Limiter

func (s *Scheduler) Limiter() *Limiter

Limiter exposes the rate limiter so resource classes can be configured.

func (*Scheduler) Round

func (s *Scheduler) Round() int

Round is the current round number.

func (*Scheduler) Run

func (s *Scheduler) Run(ctx context.Context) error

Run expands until a round produces no new eligible work, or a limit binds. Cancelling ctx stops at the end of the current round, so the barrier still runs and parents, belief and the ledger are finalized rather than left half computed.

type ScoreRow

type ScoreRow struct {
	Key   string
	Value float64
}

ScoreRow pairs a plugin model's key with its score.

type SeedSpec

type SeedSpec struct {
	Type  string   `json:"type"`
	Key   string   `json:"key"`   // canonical
	Scope []string `json:"scope"` // nameable types to vary; empty means all in closure
}

SeedSpec is the target a plan was compiled for.

type Selector

type Selector struct {
	Types []string
	Caps  []Capability
}

Selector chooses which nodes an operator binds to, by type or by capability. Binding by capability is what lets one omission algorithm cover every Nameable type instead of being registered once per type.

type Severity

type Severity uint8

Severity is an ordered, named level. It is deliberately not a bare int: `--fail-on high` has to mean the same thing in every release, and an integer scale drifts silently the first time someone inserts a level.

const (
	SeverityInfo Severity = iota + 1
	SeverityLow
	SeverityMedium
	SeverityHigh
	SeverityCritical
)

func ParseSeverity

func ParseSeverity(s string) (Severity, bool)

ParseSeverity resolves a level name, for --fail-on.

func (Severity) String

func (s Severity) String() string

type State

type State any

State is a model's latent state, carried between a parent and its children. The graph stores and forwards it without ever inspecting it.

It exists because §10.1 specifies a hidden Markov model, and forward filtering in an HMM propagates a *distribution over latent states* — a vector. An earlier version of this interface passed the parent's scalar belief instead, which forced a model to reconstruct a plausible distribution from one number and collapse it again on the way out. That round trip is exact for a two-state model and lossy for three or more, so the interface silently answered §16's open question about state cardinality as "two" — not by anyone's decision, but as a consequence of a type signature. Numbers from a larger model would have looked entirely reasonable and been wrong.

The scalar is still what the engine ranks and gates on; the state is the model's own business.

type Status

type Status uint8

Status is the terminal outcome of one (node, operator) pair. How a lookup failed is itself the finding: NXDOMAIN proves a name is free, a timeout proves nothing at all, and collapsing the two would discard the signal a squatting scanner exists to collect.

const (
	// StatusOK means the operator learned something positive.
	StatusOK Status = iota + 1
	// StatusEmpty means it authoritatively determined absence.
	StatusEmpty
	// StatusFailed means the lookup itself broke.
	StatusFailed
	// StatusTimeout means nothing was learned.
	StatusTimeout
	// StatusSkipped means it was never attempted. Unlike the others this is
	// not terminal: a pair gated off at one barrier may run at a later one.
	StatusSkipped
)

func (Status) String

func (s Status) String() string

func (Status) Terminal

func (s Status) Terminal() bool

Terminal reports whether recording this status closes the pair. Skipped never does; recording it terminally would make the first belief gate permanent.

type StatusRow

type StatusRow struct {
	Operator string
	Status   Status
}

StatusRow pairs an operator with its terminal outcome for one node.

type Trigger

type Trigger struct {
	On    Selector
	Where []Condition
	Reads Reads
}

Trigger is when an operator runs.

type Value

type Value struct {
	// contains filtered or unexported fields
}

Value is a typed prop value. Times are held as Unix nanoseconds so the encoded form is an integer and carries no location or formatting.

func Bool

func Bool(b bool) Value

func Bytes

func Bytes(b []byte) Value

func Float

func Float(f float64) Value

func Int

func Int(i int64) Value

func String

func String(s string) Value

func Time

func Time(t time.Time) Value

func (Value) Flag

func (v Value) Flag() bool

func (Value) IsZero

func (v Value) IsZero() bool

func (Value) Kind

func (v Value) Kind() Kind

func (Value) Num

func (v Value) Num() int64

func (Value) Raw

func (v Value) Raw() []byte

func (Value) Real

func (v Value) Real() float64

func (Value) Str

func (v Value) Str() string

func (Value) Time

func (v Value) Time() time.Time

type View

type View interface {
	// ID is the matched node's identity.
	ID() NodeID
	// Type is the matched node's type name.
	Type() string
	// Key is the matched node's canonical key.
	Key() string
	// Depth is the node's observation distance from the seed.
	Depth() int
	// Prop returns a declared field's value, and whether it is set. Reading an
	// undeclared field always reports unset.
	Prop(field string) (Value, bool)
	// Edges returns outgoing edges of a declared relation, with their props.
	// Reading an undeclared relation always returns nothing.
	Edges(rel string) []EdgeView
	// Ref names this node for use in a Delta.
	Ref() NodeRef
}

View is what an operator sees: the matched node, and exactly the props and relations its trigger declared it reads. Both are filtered, not just relations — the read-set digest is built from the same declaration, so a field an operator could read without declaring could change without changing the digest, and the operator would be served a stale cached result forever.

Directories

Path Synopsis
Package dag lays out a directed graph for presentation.
Package dag lays out a directed graph for presentation.

Jump to

Keyboard shortcuts

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