Documentation
¶
Overview ¶
Package validate provides post-build inspectors that audit a constructed graph for integrity issues. Two complementary stages exist: SchemaValidator (deterministic, fast — checks empty values, FK consistency, edge-type semantic invariants) and LLMValidator (LLM-as-judge, slower — for cases where deterministic rules can't capture intent, such as "this calls edge looks like it should be an invokes edge").
The Validator interface is the shared contract so `ckg validate` can run any subset over the same graph without touching the orchestrator each time a new check is added.
Index ¶
Constants ¶
const ( SeverityError = "error" SeverityWarning = "warning" SeverityInfo = "info" )
Severity levels for validation issues. Error and Warning are operator- actionable; Info is purely descriptive (e.g. "skipped 2 stdlib refs").
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Citation ¶
Citation is a single file:line reference. Snippet is optional — the dry-run path leaves it empty (the operator looks up the file themselves). The future wired path will populate Snippet via store.GetBlob so the LLM sees the actual source text.
type Issue ¶
type Issue struct {
// Severity classifies how the operator should react.
Severity string
// Code is a stable identifier for the check (e.g. "empty-qname",
// "dangling-edge"). Used by CLI filters and dashboards.
Code string
// Message is a human-readable description of what went wrong.
Message string
// NodeID, when set, points to the offending node.
NodeID string
// EdgeKey, when set, identifies an edge as "type:src:dst:line".
EdgeKey string
// FilePath helps the operator locate the issue in source.
FilePath string
}
Issue is one finding from a validator. Most fields are optional — a validator should fill what it can and leave the rest empty.
type LLMValidator ¶
type LLMValidator struct {
// DryRun, when true (the default), causes Validate to emit prompts
// as Info issues and never make a network call. When false, it
// returns a single Error issue documenting that real wiring is
// pending — Validate still does NOT touch the network in V0.
DryRun bool
// MaxPrompts caps the number of prompts emitted per Validate call.
// Default 10 (set by NewLLMValidator). Higher values multiply
// future LLM cost linearly.
MaxPrompts int
// Endpoint is the LLM API URL — reserved for V1 wiring; unused in V0.
Endpoint string
// Model is the LLM model identifier — reserved for V1 wiring;
// unused in V0.
Model string
}
LLMValidator is the LLM-as-judge stage. It surfaces findings that the deterministic SchemaValidator cannot express — e.g. "this calls edge looks like it should be a defer/recover relationship given the source snippet" or "interface X is heavily imported but has zero implements edges, did the analyzer miss something?".
V0 (this cycle) is dry-run only: it samples suspicious edges/nodes from the graph and emits each candidate as a Prompt encoded into an Info issue. The operator can copy the Question + Citations into any LLM chat and apply the answer manually. Real API wiring is V1+ — the Endpoint/Model fields are reserved for that change so this struct does not have to be re-shaped later.
The decision to default to DryRun=true (per dogfood plan instruction ④) means `--llm` is immediately useful: an operator running ckg validate gets actionable prompts without any external dependency or network access.
func NewLLMValidator ¶
func NewLLMValidator() *LLMValidator
NewLLMValidator returns a dry-run LLMValidator with MaxPrompts=10. The defaults are chosen so `--llm` produces a useful, bounded prompt list out of the box without any operator configuration.
func (*LLMValidator) Name ¶
func (v *LLMValidator) Name() string
Name returns the validator identifier.
func (*LLMValidator) Validate ¶
func (v *LLMValidator) Validate(ctx context.Context, g *graph.Graph, store persist.StoreReader) (*Report, error)
Validate samples suspicious edges/nodes and produces prompts. In DryRun mode (default) every prompt becomes an Info issue with code "llm-prompt-dry-run". In non-DryRun mode a single Error issue is returned because real LLM wiring is not in this build — Validate never opens a network connection regardless of mode.
type Prompt ¶
type Prompt struct {
// Task names the check kind. Stable identifiers a future router
// can switch on: "edge-plausibility", "sparse-subgraph",
// "citation-freshness".
Task string
// Subject is a short ID describing what is being judged. For edge
// checks it is "calls:src=<qname>:dst=<qname>"; for node checks it
// is the qualified name of the node.
Subject string
// Citations are the file:line references the LLM should consult.
// At least one entry is required; samplers that cannot produce a
// citation MUST skip the candidate rather than emit a citationless
// prompt.
Citations []Citation
// Question is the actual prompt text. Kept concise (50-150 chars)
// because the model still needs context budget for the citations.
Question string
// ResponseSchema is a description (or JSON-schema fragment) of the
// expected response shape. The wiring layer passes this as the
// system message so the LLM returns parseable JSON instead of prose.
ResponseSchema string
}
Prompt is the typed unit the LLMValidator emits (in dry-run mode) or would send to a real LLM (in V1+ wired mode). The shape mirrors the citation-first pattern used by pkg/smartctx: every Prompt MUST cite at least one source location so the operator (or eventual LLM) can verify the claim independently. Fields are deliberately simple strings/slices to keep JSON-encoding for future API calls trivial.
Why this exists separately from Issue: an Issue is a finding (already judged), a Prompt is a request for judgment. Keeping them distinct lets dry-run mode emit prompts as Info issues today and lets a future wiring layer post the same struct to an LLM endpoint without rewriting the sampler. Both layers share the Subject string so a real LLM response can be rejoined with the prompt that produced it.
func SampleSuspiciousFromGraph ¶
SampleSuspiciousFromGraph runs every V0 sampler and concatenates their outputs, capped at maxPrompts globally. Sampler order is fixed (edge-plausibility first, then sparse-subgraph) so prompt lists are reproducible across runs given the same graph.
V1+ TODO: add sampleCitationFreshness — needs store.GetBlob to fetch the actual source text and ask the LLM whether the file:line really declares the qname claimed by the node. Skipped here because the V0 dry-run path has no way to surface the snippet to the operator without dumping arbitrarily large blobs into the issue list; we would rather defer than ship a half-useful sampler.
type Report ¶
Report aggregates issues from a single validator pass.
func (*Report) CountBySeverity ¶
CountBySeverity groups issues by their Severity. Cheap helper for CLI output ("N errors / M warnings").
type SchemaValidator ¶
type SchemaValidator struct{}
SchemaValidator runs deterministic structural checks on a graph:
- Empty required fields (id, name, qualified_name, file_path where it applies, confidence). Empty values on a node mean a parser branch emitted a placeholder it never finished filling.
- Dangling edges (src or dst not present in node set). Reuses graph.Inspect so the rule definition lives in one place.
- Unknown node/edge types. Schema bumps are catastrophic if a parser emits a type the persist layer doesn't recognise.
- Edge-type semantic invariants (V1 baseline):
- implements src must be a Struct or TypeAlias, dst must be Interface
- listens_on src must be Function/Method, dst must be Endpoint
- calls/invokes src and dst must be Function/Method
The validator is deterministic, fast, and dependency-free — safe to run on every build. Findings inform Citation Enforcement and the LLM validator (which uses these as priors).
func NewSchemaValidator ¶
func NewSchemaValidator() *SchemaValidator
NewSchemaValidator returns a stateless schema validator instance.
func (*SchemaValidator) Name ¶
func (v *SchemaValidator) Name() string
Name returns the validator identifier.
func (*SchemaValidator) Validate ¶
func (v *SchemaValidator) Validate(ctx context.Context, g *graph.Graph, store persist.StoreReader) (*Report, error)
Validate executes all schema checks and returns one report aggregating every issue found. ctx and store are accepted for interface conformance; SchemaValidator does not block on either.
type Validator ¶
type Validator interface {
// Name returns a short stable identifier (e.g. "schema", "llm").
// Surfaced in CLI output and JSON reports.
Name() string
// Validate produces a Report. ctx is honoured for cancellation;
// long-running validators (LLM) should poll ctx.Done(). store may be
// nil when the caller has only an in-memory graph — validators that
// need persisted data (manifest, blobs) should detect nil and return
// an Info issue documenting the skipped check.
Validate(ctx context.Context, g *graph.Graph, store persist.StoreReader) (*Report, error)
}
Validator inspects a graph for integrity issues. Implementations MUST be read-only — never mutate g or store. Concurrent calls to a single Validator are not required to be safe; the orchestrator runs validators sequentially.