triage

package
v0.18.33 Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2026 License: AGPL-3.0 Imports: 7 Imported by: 0

Documentation

Overview

Package triage classifies SQLancer soak-log output (and the wadjet server logs captured alongside it) the way wadjet#289's harness actually needs, which is not the way its README long documented.

The README's triage grep was `"counts mismatch" / "mismatch:"`. That matches NoREC's own assertion text, but NOT the TLP family's: ComparatorHelper.assumeResultSetsAreEqual (shared by TLP-WHERE, TLP-HAVING, and QUERY_PARTITIONING's composite) throws "The size of the result sets mismatch (%d and %d)!" or "The content of the result sets mismatch!" — no colon after "mismatch", and not "counts". TLP-Aggregate throws its own "the results mismatch!". A soak triaged with the old grep could not have reported a genuine TLP violation even if one occurred — see wadjet#289's 2026-08-25 methodology finding.

PQS is harder still: PivotedQuerySynthesisBase.reportMissingPivotRow throws `new AssertionError(query)`, where query is a Query whose toString() is just the raw SQL string — byte-for-byte the same shape as the ordinary "wadjet rejected this SQL with a message Postgres's ExpectedErrors list doesn't recognize" AssertionError that SQLQueryAdapter.checkException throws everywhere else in the harness. The only way to tell them apart is the stack trace: a genuine PQS violation's frames include sqlancer.common.oracle.PivotedQuerySynthesisBase.reportMissingPivotRow.

CERT (CERTOracle.check, reachable here because WadjetProvider extends PostgresProvider and doesn't remove it from PostgresOracleFactory) has its own distinct message, "Inconsistent result for query: ...". The README advises against running CERT at all — it compares EXPLAIN plan text wadjet's EXPLAIN doesn't produce in the same shape, so a CERT finding is expected to lean heavily toward false positives — but if one is generated (or an old soak ran it despite the advisory), it must not be filed away as ordinary noise either.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Category

type Category int

Category is the bucket one classified block of log output falls into.

const (
	// CategoryOther is ordinary log content — nothing a triage pass needs
	// to look at twice.
	CategoryOther Category = iota
	// CategoryTLPResultSet is a genuine TLP-WHERE or TLP-HAVING violation:
	// ComparatorHelper.assumeResultSetsAreEqual threw. The two share this
	// category because they share the identical assertion text; a
	// Finding's OracleCheck field (when populated) disambiguates which one
	// actually threw for a given occurrence.
	CategoryTLPResultSet
	// CategoryTLPAggregate is a genuine TLP-Aggregate violation (the
	// "aggregateCheck" arm QUERY_PARTITIONING composes in).
	CategoryTLPAggregate
	// CategoryNoREC is a genuine NoREC violation (NoRECOracle.check).
	CategoryNoREC
	// CategoryPQS is a genuine PQS violation
	// (PivotedQuerySynthesisBase.reportMissingPivotRow).
	CategoryPQS
	// CategoryCERT is a genuine CERT violation (CERTOracle.check). See the
	// package doc for why this needs its own arm rather than falling into
	// CategoryUnexpectedError, and why it still needs corroborating before
	// it's treated as a real defect.
	CategoryCERT
	// CategoryCrashEcho is a wadjet process crash: either observed
	// directly (a Go "panic:"/"fatal error:" line from a wadjet server
	// log) or its symptom on the SQLancer/JDBC side (connection
	// refused/reset, an I/O error sending to the backend) — plus the
	// harness's own JVM heap exhaustion, which reads the same way at
	// triage time (the run is dead, not a wadjet answer). A raw line
	// count here is NOT a crash-event count: one dead wadjet process
	// produces a flood of near-identical connection-refused lines from
	// every query attempted until the session's timeout, so this
	// category exists to keep that flood out of CategoryUnexpectedError,
	// not to size it. The soak supervisor's own restart count (its
	// crashes.log / summary.txt) is the authoritative crash-event count.
	CategoryCrashEcho
	// CategoryUnexpectedError is an AssertionError SQLancer raised because
	// wadjet's response to a generated statement didn't match anything in
	// the Postgres dialect's ExpectedErrors list — a SQL-surface gap or a
	// by-design loud rejection, not an oracle-detected wrong answer.
	CategoryUnexpectedError
)

func (Category) IsGenuineViolation

func (c Category) IsGenuineViolation() bool

IsGenuineViolation reports whether c is one of the oracle-detected wrong-answer categories, as opposed to noise or a crash echo.

func (Category) String

func (c Category) String() string

type Finding

type Finding struct {
	Category Category
	Source   string // file path this was found in
	Line     int    // 1-based line number of the header line
	Header   string // the AssertionError/panic header line itself
	Detail   string // header plus any message-continuation lines, before the stack trace
	Queries  []string
	// OracleCheck names the specific oracle method whose stack frame
	// produced this finding (e.g. "TLP-WHERE", "TLP-HAVING",
	// "TLP-AGGREGATE", "NoREC", "PQS", "CERT"), when one of the frames
	// scanned matched a known oracle class. It exists mainly to
	// disambiguate CategoryTLPResultSet, which TLP-WHERE and TLP-HAVING
	// both report under identically — see wadjet#289: a dedicated WHERE
	// soak reporting 0 violations does not mean TLP-WHERE is clean if its
	// oracle checks never got to run (e.g. starved by a crash-restart
	// loop); this field is what lets a re-triage tell "0 because clean"
	// from "0 because it barely ran" apart from QUERY_PARTITIONING's own
	// mixed results. Empty when no known frame was found.
	OracleCheck string
}

Finding is one classified block of log output worth keeping around — every genuine violation, plus the Go-side panic/fatal-error lines from a crash echo (the connection-refused/reset flood a dead server produces on the SQLancer side is tallied in Counts but not retained; there is nothing more to learn from the 4000th "Connection refused" than the 1st).

type Report

type Report struct {
	Counts   map[Category]int
	Findings []Finding
}

Report accumulates classification results across one or more sources.

func NewReport

func NewReport() *Report

NewReport returns an empty Report ready for Classify/ClassifyFile calls. It is not the only valid way to obtain a usable *Report, though: a bare &Report{} works too — see (*Report).incr.

func (*Report) Classify

func (r *Report) Classify(source string, rd io.Reader) error

Classify reads every line from rd and classifies it into r, attributing findings to source (typically a file path, or "" for an ad hoc snippet).

If rd's underlying reader errors partway through (including a line exceeding maxLineBytes), every line successfully read up to that point is still classified — a soak log with one pathological line should not lose every genuine violation that came before it — and the error is returned so the caller knows this source's counts may be incomplete.

func (*Report) ClassifyFile

func (r *Report) ClassifyFile(path string) error

ClassifyFile opens path (transparently gunzipping a ".gz" suffix) and classifies its lines into r. On error, whatever lines were successfully read before the error are still classified into r (see Classify) — the returned error signals that this file's counts are a floor, not a complete answer, not that nothing was learned from it.

func (*Report) Print

func (r *Report) Print(w io.Writer)

Print writes a human-readable summary of r to w: counts per category, followed by every retained finding (every genuine violation, plus any captured panic/fatal-error lines) with its source location and either its best-effort minimized query (when one was extracted) or its raw captured detail (a crash-echo panic/fatal-error line never has a "query" to extract, so it renders its Header/Detail text instead — omitting that would leave every crash Finding printed with nothing under its header line at all).

Jump to

Keyboard shortcuts

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