guardy

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Apr 2, 2026 License: MIT Imports: 13 Imported by: 1

README

Guardy

Go Reference Build Coverage

TL;DR — guardy is a lightweight guardrails library for LLM applications in Go. It provides a two-phase pipeline for validating prompts and model responses: a sequential fast path (WAF, PII redaction, wordlist) and a parallel slow path (semantic/LLM checks), with easy extension via custom validators.


Requirements

  • Go 1.26+

Installation

go get github.com/skosovsky/guardy

Quick Start

Build a pipeline with fast-path validators and validate text:

package main

import (
	"context"
	"fmt"

	"github.com/skosovsky/guardy"
	"github.com/skosovsky/guardy/ext"
)

func main() {
	lengthV := ext.NewLengthValidator(0, 2048, ext.WithCode("TOO_LONG"))
	wordlistV := ext.NewWordlistValidator([]string{"bad", "spam"}, ext.Blocklist, ext.WithCode("FORBIDDEN"))
	piiV := ext.NewPIIValidator()

	pipeline := guardy.NewPipeline(
		guardy.WithFastPath(ext.MustTagSanitizerValidator(""), piiV, wordlistV, lengthV),
	)

	ctx := context.Background()
	text := "Contact me at user@example.com"
	result, err := pipeline.Run(ctx, text)
	if err != nil {
		panic(err)
	}
	report := result.Decision()
	switch report.Action {
	case guardy.ActionBlock:
		fmt.Println("blocked:", report.Reason)
	case guardy.ActionRedact:
		fmt.Println("ok (redacted):", result.Output)
	case guardy.ActionPass:
		fmt.Println("ok:", result.Output)
	}
}

Key abstractions

Validator

Validators implement the generic Validator[T] interface:

type Validator[T any] interface {
	Validate(ctx context.Context, input T) (T, *Report, error)
}

For string validation: Validator[string]. The pipeline returns the mutated text as the first value; on ActionRedact the validator provides the cleaned string. Report holds Action (ActionPass, ActionBlock, ActionRedact, ActionRetry), Validator, Code, Severity, Reason, Feedback, MutatedText, Score, ShadowMode.

Pipeline (two-phase)
  • Construction: NewPipeline[string](WithFastPath(...), WithSlowPath(...)).
  • Execution: Run(ctx, text) returns (RunResult[string], error). Use result.Output for mutated text and result.Decision() for the outcome Report. result.Reports holds all validator reports for telemetry.

pipeline.Use() is immutable in v2-style API: it returns a new pipeline instance and does not mutate the original.

Phase 1 — Fast path (sequential)
Validators that may redact or block run one after another. The text is passed along the chain; each redact step replaces it with MutatedText. On block (and not shadow), the pipeline returns immediately. Use for: TagSanitizerValidator, PIIValidator, WordlistValidator, RegexValidator, LengthValidator.

Phase 2 — Slow path (parallel)
Heavy validators that only block or pass run in parallel via errgroup on the final text from phase 1. Decision() priority: Block > Retry > last Redact > last Pass. Context is cancelled only on Block (not Retry) so all reports are collected. On validator error, a partial RunResult with gathered reports is returned (telemetry preserved). Use for: SemanticValidator, LLMJudge.

Recommended order in fast path: WAF (TagSanitizerValidator) → PII (PIIValidator) → WordlistValidator → RegexValidator/LengthValidator.

Report

Report holds: Action, Validator, Code, Severity, Reason, Feedback, Score, ShadowMode, MutatedText. After Run, use the first return value (mutated text) and report.Action.

Stream (GuardWriter)

Use GuardWriter to validate streaming output in chunks:

  • Buffers until a semantic boundary (space, newline, punctuation) or a delimiterless hard cap is reached; runs the pipeline on each chunk. UTF-8 safe; index-based buffering; overlap prevents boundary bypass.
  • On Block — returns ErrBlocked.
  • On Retry — returns ErrRetryRequested (orchestrator should retry with Feedback).
  • On Redact — writes the mutated text for that chunk.
  • On Pass — writes the original chunk.
  • In JSON-aware mode, incomplete JSON is never validated/written; Close() on incomplete JSON returns ErrValidatorFailed.

Options: WithChunkSize (preferred natural boundary target, default 4096), WithMaxChunkSize (delimiterless hard cap, default 2048), WithJSONAwareSplitter (for streamed JSON/tool-call payloads), WithContext, WithTimeout.

gw := guardy.NewGuardWriter(
	w,
	pipeline,
	guardy.WithChunkSize(4096),
	guardy.WithMaxChunkSize(2048),
)
_, _ = gw.Write(data)
_ = gw.Close()
HTTP Guard (http_guard.go)

Guard wraps an HTTP handler: the request body is read once; the extractor turns it into text for the pipeline. On Block or Retry — 422 JSON response. On Redact — replaces body with MutatedText and calls next. On Pass — restores the original request body (not the extractor’s return value) and calls next. Use ReportFromContext(ctx) in the next handler to get the report.

extractor := func(r *http.Request) (string, error) {
	body, _ := io.ReadAll(r.Body)
	return string(body), nil
}
handler := guardy.Guard(pipeline, extractor, guardy.PlainTextInjector())(yourHandler)
Generic decorators (interceptor.go)

WrapInput runs a pipeline on the request value before your func(context.Context, Req) (Res, error). WrapOutput runs after your function on the result. Block maps to an error wrapping ErrBlocked; retry maps to RetryError (unwraps to ErrRetryRequested). See examples/generic_decorator.

Built-in validators (ext)

Validator Description
TagSanitizerValidator Blocks on system-tag injection (e.g. <system>, </system>). ext.NewTagSanitizerValidator(pattern) or ext.MustTagSanitizerValidator("").
PIIValidator Redacts or blocks email, phone, credit card. ext.NewPIIValidator(...) with ext.WithAction, ext.WithCode, ext.WithSeverity, ext.WithRedactionReplacement, ext.WithTokenVault.
WordlistValidator Blocklist or allowlist; block or redact. ext.NewWordlistValidator(words, mode, ...) with ext.WithAction, ext.WithCode, ext.WithLowercase, ext.WithRedactionReplacement, ext.WithTokenVault.
RegexValidator Match pattern; block or redact. ext.NewRegexValidator(pattern, ...) with ext.WithAction, ext.WithCode, ext.WithSeverity, ext.WithRedactionReplacement.
LengthValidator Min/max rune length. ext.NewLengthValidator(min, max, ...) with ext.WithCode, ext.WithSeverity, ext.WithName.
JSON Schema Optional submodule guardy/ext/jsonschema — validates JSON strings against a schema; returns ActionRetry with Feedback on violation.
Structured Output / JSON Schema

For low-level control you can still provide a raw JSON Schema string:

validator, _ := jsonschemaext.NewJSONSchemaValidator(`{
  "type": "object",
  "properties": {
    "name": {"type": "string"}
  },
  "required": ["name"]
}`)

jsonschema also accepts shared v2 rule options (ext.WithCode, ext.WithSeverity, ext.WithReason, ext.WithName). It is an intrinsically ActionRetry validator; mutation options (WithTokenVault, WithRedactionReplacement, WithLowercase) are rejected at construction time.

For the common case, prefer generating the schema from a Go struct:

package main

import jsonschemaext "github.com/skosovsky/guardy/ext/jsonschema"

type User struct {
	Name string `json:"name" jsonschema:"required"`
	Age  int    `json:"age" jsonschema:"minimum=18"`
}

validator, _ := jsonschemaext.NewJSONSchemaValidatorFromStruct(&User{})

NewJSONSchemaValidatorFromStruct keeps the schema in sync with your Go type and still returns ActionRetry with detailed Feedback for invalid JSON or schema violations.

Token Vault (Reversible Redaction)

Use TokenVault when you need reversible redaction ([GUARDY_TOKEN_...]) and later restoration in model output:

vault := ext.NewInMemoryTokenVault()
piiV := ext.NewPIIValidator(
	ext.WithAction(guardy.ActionRedact),
	ext.WithTokenVault(vault),
)
result, _ := guardy.NewPipeline(guardy.WithFastPath(piiV)).Run(ctx, "email: a@b.com")
restored := ext.UnredactText("model: "+result.Output, vault)

Built-in validators write namespaced canonical tokens such as [GUARDY_TOKEN_PII_1] and [GUARDY_TOKEN_WORDLIST_1] through TokenVault.Store(namespace, original).

Multi-turn Adapter (MapSlice)

Use MapSlice for BYOT message slices ([]T) without introducing framework-specific message types into guardy:

type Msg struct{ Content string }
base, _ := ext.NewRegexValidator(`(?i)secret`, ext.WithAction(guardy.ActionRedact))
multi := ext.MapSlice(
	func(m Msg) string { return m.Content },
	func(m Msg, s string) Msg { m.Content = s; return m },
	base,
)

If any item returns ActionBlock or ActionRetry, MapSlice blocks the whole collection by default.

Telemetry (Optional ext/guardyotel)

For OpenTelemetry integration without adding heavy deps to root module, use github.com/skosovsky/guardy/ext/guardyotel:

import "github.com/skosovsky/guardy/ext/guardyotel"

pipeline := guardy.NewPipeline(guardy.WithFastPath(v))
pipeline = pipeline.Use(guardyotel.NewMiddleware[string](
	guardyotel.WithIncludePayloads(false), // default secure mode
))

Fast path exports counters/histograms; slow path emits spans. Raw payload capture is opt-in.

Map (Lens adapter)

Use Map[T,U] to adapt Validator[U] to Validator[T] for domain structs:

type AgentState struct { Text string }
regexV, _ := ext.NewRegexValidator(`(?i)bad`, ext.WithAction(guardy.ActionRedact), ext.WithCode("X"), ext.WithRedactionReplacement("[REDACTED]"))
v := guardy.Map(regexV, func(s *AgentState) string { return s.Text },
	func(s *AgentState, t string) *AgentState { s.Text = t; return s })

Core validators (guardy)

  • SemanticValidator — wraps a Matcher and threshold; use for similarity/embedding checks (slow path).
  • LLMJudge — wraps a Judge; use for LLM-as-judge (slow path). Both support shadow mode (block is logged but does not short-circuit).

Testing with guardytest

Use guardy/guardytest for unit tests:

  • *FakeValidator(name, guardy.Report) — validator that always returns the given report (nil or zero = pass).
  • FailingValidator(name, err) — validator that always returns the given error.
  • MustPass, MustBlock, MustRedact — assert report.Action.
v := guardytest.FakeValidator("mock", &guardy.Report{Action: guardy.ActionBlock, Reason: "TEST"})
pipeline := guardy.NewPipeline(guardy.WithFastPath(v))
result, _ := pipeline.Run(ctx, "x")
guardytest.MustBlock(t, result.Decision())

Error handling

  • ErrBlocked — returned by GuardWriter when report.Action == ActionBlock.
  • ErrRetryRequested — returned by GuardWriter when report.Action == ActionRetry.
  • ErrValidatorFailed — wraps a validator’s system error from Run.

Packages

  • guardy — core types (Action, Report, Validator), Pipeline, SemanticValidator, LLMJudge, GuardWriter, Guard middleware, errors.
  • guardy/ext — TagSanitizerValidator, PIIValidator, WordlistValidator, RegexValidator, LengthValidator, TokenVault, MapSlice, MLValidator.
  • guardy/ext/jsonschema — optional JSON Schema validator with raw-schema and struct-derived constructors.
  • guardy/ext/guardyotel — optional OTel middleware module (metrics + tracing).
  • guardy/guardytest — FakeValidator, FailingValidator, MustPass/MustBlock/MustRedact.

See .cursor/docs/task9.md for the full v2 technical specification.

v2 Migration Highlights

  • Pipeline.Use(...) is immutable and returns a new pipeline.
  • Report includes Code and typed Severity.
  • StreamOption was renamed to GuardWriterOption.
  • PIIMasking APIs were renamed to PIIValidator / NewPIIValidator.
  • ext/jsonschema.NewValidatorFromStruct was renamed to NewJSONSchemaValidatorFromStruct.
  • Built-in ext validators use options for common rule metadata (WithAction, WithCode, WithSeverity, WithReason, ...).

Wordlist Benchmark Evidence (Task9 DoD #3)

Run the reproducible redact comparison benchmark:

go test -bench '^BenchmarkWordlist_Blocklist_Redact_BaselineComparison$' -benchmem ./ext -run '^$'

This benchmark includes:

  • baseline_a16279e_runtime_compile: frozen pre-v2 redact path copied from commit a16279e (runtime regex compile on each hit).
  • v2_precompiled: current v2 validator with precompiled matchers from constructor.

Latest local run on this workspace (March 28, 2026):

  • baseline_a16279e_runtime_compile: ~197k ns/op, ~234k B/op, ~1678 allocs/op
  • v2_precompiled: ~3.2k ns/op, ~587 B/op, ~15 allocs/op
  • Throughput ratio: ~61x faster for v2 on redact path.

Development

make test
make lint

License

See LICENSE.

Documentation

Overview

Package guardy provides a pipeline engine for AI guardrails: validation, intervention actions (pass, block, redact, retry), and two-phase execution (sequential Fast-Path for mutations, parallel Slow-Path via errgroup).

See .cursor/docs/task9.md for the v2 technical specification.

Index

Examples

Constants

View Source
const DefaultMaxBodyBytes = 1 << 20

DefaultMaxBodyBytes is the default request body size limit for Guard (1MB, DoS protection).

View Source
const DefaultStreamChunkSize = 4096

DefaultStreamChunkSize is the default buffer size for GuardWriter.

View Source
const DefaultStreamMaxChunkSize = 2048

DefaultStreamMaxChunkSize is the hard cap for delimiterless chunks.

Variables

View Source
var (
	// ErrBlocked is returned when the pipeline result is Block (e.g. by GuardWriter, WrapInput, WrapOutput).
	// Match with [errors.Is] against err and ErrBlocked. WrapInput/WrapOutput may wrap it with [fmt.Errorf]("%w: ...", ErrBlocked, reason).
	ErrBlocked = errors.New("guardy: input blocked")

	// ErrRetryRequested is returned when the pipeline result is Retry; the orchestrator should retry with Feedback.
	// WrapInput/WrapOutput return *RetryError, which unwraps to ErrRetryRequested — use [errors.Is] for a quick check,
	// or [errors.As] into *RetryError for Feedback and Report.
	ErrRetryRequested = errors.New("guardy: retry requested")

	// ErrValidatorFailed is returned when a validator returns a system error.
	ErrValidatorFailed = errors.New("guardy: validator failed")
)

Functions

func Guard

func Guard[T any](
	p *Pipeline[T],
	extractor func(*http.Request) (T, error),
	injector func(*http.Request, T) error,
) func(http.Handler) http.Handler

Guard returns net/http middleware that runs the pipeline on the request body. It is HTTP-specific; for generic func(context.Context, Req) (Res, error) wrapping use WrapInput / WrapOutput. Extractor reads the request and returns value of type T to validate. Injector applies mutated T to the request body on ActionRedact (required; format-aware). On Block/Retry returns 422. On Pass restores original body. Use Guard[string] with PlainTextInjector for string-based pipelines.

Example
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/skosovsky/guardy"
	"github.com/skosovsky/guardy/ext"
)

func main() {
	regexV, _ := ext.NewRegexValidator(`(?i)ignore`, ext.WithCode("INJECT"))
	pipeline := guardy.NewPipeline(guardy.WithFastPath(regexV))

	extractor := func(r *http.Request) (string, error) {
		body, _ := io.ReadAll(r.Body)
		return string(body), nil
	}

	next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
	})

	handler := guardy.Guard(pipeline, extractor, guardy.PlainTextInjector())(next)

	req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("hello"))
	rec := httptest.NewRecorder()
	handler.ServeHTTP(rec, req)
	fmt.Println(rec.Code)
}
Output:
200

func PlainTextInjector added in v0.5.0

func PlainTextInjector() func(*http.Request, string) error

PlainTextInjector returns an injector that replaces the request body with the mutated string. Syncs Body, ContentLength, Header[Content-Length], and GetBody for proxy/retry compatibility.

func WrapInput added in v0.7.0

func WrapInput[Req, Res any](
	p *Pipeline[Req],
	next func(context.Context, Req) (Res, error),
) func(context.Context, Req) (Res, error)

WrapInput runs the pipeline on the request value before calling next. Deadlines and cancellation come from ctx (e.g. wrap with context.WithTimeout at the call site); no implicit timeout is applied.

On ActionRedact, next receives the mutated Output from the pipeline. On ActionBlock, next is not called; the error wraps ErrBlocked (use errors.Is). On ActionRetry, next is not called; the error is *RetryError (use errors.As), which unwraps to ErrRetryRequested.

Composing WrapOutput(outPipeline, WrapInput(inPipeline, fn)) yields input and output guards around fn without net/http.

func WrapOutput added in v0.7.0

func WrapOutput[Req, Res any](
	p *Pipeline[Res],
	next func(context.Context, Req) (Res, error),
) func(context.Context, Req) (Res, error)

WrapOutput runs next first, then validates the result with the pipeline. If next returns a non-nil error, the result is (res, err) with res as returned by next (possibly a zero value, e.g. nil for pointer types). Deadlines for the output pipeline use the same ctx as for next.

On ActionRedact, the returned value is the pipeline Output (mutated). On ActionBlock or ActionRetry, behavior matches WrapInput (errors and no replacement of a successful next result in the success path — the error is returned instead).

Types

type Action

type Action int

Action is the intervention outcome from a validator or pipeline.

const (
	ActionPass   Action = iota // Validation passed
	ActionBlock                // Content should be blocked
	ActionRedact               // Content was redacted (see MutatedText)
	ActionRetry                // Orchestrator should retry with Feedback (e.g. LLM correction)
)

Supported pipeline/validator actions.

func (Action) String added in v0.5.0

func (a Action) String() string

String returns the canonical name for the action.

type GuardWriter

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

GuardWriter wraps an io.Writer and runs the pipeline on buffered chunks. On Block it returns ErrBlocked; on Redact it writes the mutated text. Uses index-based buffering to avoid O(N^2) copy; overlap prevents boundary bypass.

func NewGuardWriter

func NewGuardWriter(w io.Writer, p *Pipeline[string], opts ...GuardWriterOption) *GuardWriter

NewGuardWriter creates a GuardWriter that validates data in chunks before writing.

Example
package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/skosovsky/guardy"
)

func main() {
	v := &examplePassValidator{}
	pipeline := guardy.NewPipeline(guardy.WithFastPath(v))

	var out strings.Builder
	gw := guardy.NewGuardWriter(&out, pipeline, guardy.WithChunkSize(64))
	_, _ = gw.Write([]byte("streaming text "))
	_ = gw.Close()
	fmt.Println(out.String())
}

type examplePassValidator struct{}

func (examplePassValidator) Validate(_ context.Context, input string) (string, *guardy.Report, error) {
	return input, &guardy.Report{Action: guardy.ActionPass}, nil
}
Output:
streaming text

func (*GuardWriter) Close

func (g *GuardWriter) Close() error

Close flushes the remaining buffer and validates it.

func (*GuardWriter) Write

func (g *GuardWriter) Write(p []byte) (int, error)

Write buffers data and runs the pipeline when a splitter boundary is reached.

type GuardWriterOption added in v0.6.0

type GuardWriterOption func(*guardWriterConfig)

GuardWriterOption configures GuardWriter behavior.

func WithChunkSize

func WithChunkSize(n int) GuardWriterOption

WithChunkSize sets the buffer size in bytes before validation runs (default 4096).

func WithContext

func WithContext(ctxFn func() (context.Context, context.CancelFunc)) GuardWriterOption

WithContext sets the context factory for chunk validation.

func WithJSONAwareSplitter added in v0.6.0

func WithJSONAwareSplitter() GuardWriterOption

WithJSONAwareSplitter switches GuardWriter to JSON-aware chunk splitting.

func WithMaxChunkSize added in v0.5.5

func WithMaxChunkSize(n int) GuardWriterOption

WithMaxChunkSize sets the hard cap for delimiterless chunks (default 2048).

func WithTimeout

func WithTimeout(d time.Duration) GuardWriterOption

WithTimeout sets a timeout for each chunk validation.

type Judge added in v0.4.0

type Judge interface {
	Evaluate(ctx context.Context, text string) (Report, error)
}

Judge evaluates text (e.g. via LLM) and returns a Report.

type LLMJudge added in v0.4.0

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

LLMJudge is a Slow-Path validator that delegates to a Judge.

func NewLLMJudge added in v0.4.0

func NewLLMJudge(j Judge, shadow bool) *LLMJudge

NewLLMJudge builds a validator that calls j.Evaluate. If shadow is true and the judge returns block, the report is marked ShadowMode.

func (*LLMJudge) Validate added in v0.4.0

func (l *LLMJudge) Validate(ctx context.Context, input string) (string, *Report, error)

Validate runs the judge and returns its result.

type Matcher added in v0.4.0

type Matcher interface {
	Match(ctx context.Context, text string) (score float64, err error)
}

Matcher returns a similarity score for the text (e.g. from a vector search). Higher score means more likely to block; threshold is applied by SemanticValidator.

type Observer added in v0.4.1

type Observer func(ctx context.Context, rep *Report)

Observer is a callback invoked for non-blocking shadow block reports. It receives the request context and the report; intended for telemetry.

type Pipeline

type Pipeline[T any] struct {
	// contains filtered or unexported fields
}

Pipeline orchestrates the execution of multiple Validators.

THREAD SAFETY: A Pipeline is safe for concurrent use. Configuration method Use returns a new instance and never mutates the original pipeline.

func NewPipeline

func NewPipeline[T any](opts ...PipelineOption[T]) *Pipeline[T]

NewPipeline builds a pipeline from options.

Example
package main

import (
	"context"
	"fmt"

	"github.com/skosovsky/guardy"
	"github.com/skosovsky/guardy/ext"
)

func main() {
	regexV, _ := ext.NewRegexValidator(`(?i)(ignore previous|system prompt)`, ext.WithCode("PROMPT_INJECTION"))
	lengthV := ext.NewLengthValidator(0, 10000, ext.WithCode("TOO_LONG"))

	pipeline := guardy.NewPipeline(
		guardy.WithFastPath(regexV, lengthV),
	)

	ctx := context.Background()
	result, err := pipeline.Run(ctx, "Hello, what is the weather?")
	if err != nil {
		panic(err)
	}
	report := result.Decision()
	if report != nil {
		switch report.Action {
		case guardy.ActionBlock:
			// handle block
		case guardy.ActionPass, guardy.ActionRedact:
			_ = result.Output
			_ = report.MutatedText
		case guardy.ActionRetry:
			_ = report.Feedback
		}
	}
	fmt.Println("configured")
}
Output:
configured

func (*Pipeline[T]) Run

func (p *Pipeline[T]) Run(ctx context.Context, input T) (RunResult[T], error)

Run executes the pipeline. Block and Retry short-circuit immediately. Returns RunResult with Output and all Reports for telemetry.

Example
package main

import (
	"context"
	"fmt"

	"github.com/skosovsky/guardy"
	"github.com/skosovsky/guardy/ext"
)

func main() {
	wordlistV := ext.NewWordlistValidator([]string{"bad"}, ext.Blocklist, ext.WithCode("FORBIDDEN"))
	pipeline := guardy.NewPipeline(guardy.WithFastPath(wordlistV))
	ctx := context.Background()
	result, err := pipeline.Run(ctx, "this is bad")
	if err != nil {
		panic(err)
	}
	report := result.Decision()
	if report != nil && report.Action == guardy.ActionBlock {
		fmt.Println("blocked:", report.Reason)
	}
}
Output:
blocked: blocklisted word found

func (*Pipeline[T]) Use added in v0.5.0

func (p *Pipeline[T]) Use(mw ...ValidatorMiddleware[T]) *Pipeline[T]

Use appends middleware and returns a new immutable pipeline instance. The original pipeline is not modified.

type PipelineOption

type PipelineOption[T any] func(*Pipeline[T])

PipelineOption configures a Pipeline.

func WithFastPath added in v0.4.0

func WithFastPath[T any](v ...Validator[T]) PipelineOption[T]

WithFastPath adds validators that run sequentially and may return redact.

func WithObserver added in v0.4.0

func WithObserver[T any](o Observer) PipelineOption[T]

WithObserver registers a callback for shadow block reports.

func WithSlowPath added in v0.4.0

func WithSlowPath[T any](v ...Validator[T]) PipelineOption[T]

WithSlowPath adds validators that run in parallel (read-only, no redact).

type Report

type Report struct {
	Action      Action   // ActionPass, ActionBlock, ActionRedact, ActionRetry
	Validator   string   // Name of the validator that produced this report
	Code        string   // Machine-readable rule code (for alerting/telemetry)
	Severity    Severity // Risk level for the report
	Reason      string   // Human-readable reason
	Feedback    string   // Message for LLM retry (when Action == ActionRetry)
	Score       float64  // Confidence or distance (optional)
	ShadowMode  bool     // If true, block was logged but did not stop the pipeline
	MutatedText string   // Text after redaction (when Action == ActionRedact)
}

Report is the single result returned by a validator or the pipeline. It maps to metry/security attributes for telemetry. When Action == ActionRetry, Feedback contains the message for the LLM/orchestrator.

func ReportFromContext

func ReportFromContext(ctx context.Context) (Report, bool)

ReportFromContext returns the Report attached by HTTP Guard, if any.

func (*Report) Clone added in v0.6.0

func (r *Report) Clone() *Report

Clone returns a shallow copy of report.

func (*Report) CloneWithoutState added in v0.6.0

func (r *Report) CloneWithoutState() *Report

CloneWithoutState returns a copy without input-specific runtime state.

type RetryError added in v0.7.0

type RetryError struct {
	Feedback string
	Report   Report
}

RetryError carries pipeline retry metadata from WrapInput or WrapOutput when Decision() is ActionRetry.

func (*RetryError) Error added in v0.7.0

func (e *RetryError) Error() string

Error implements error.

func (*RetryError) Unwrap added in v0.7.0

func (e *RetryError) Unwrap() error

Unwrap returns ErrRetryRequested so errors.Is matches *RetryError when the second argument is ErrRetryRequested.

type RunResult added in v0.5.0

type RunResult[T any] struct {
	Output  T        // Mutated output (after redactions)
	Reports []Report // All validator reports for telemetry
}

RunResult holds the output and all reports from Pipeline.Run.

func (*RunResult[T]) Decision added in v0.5.0

func (r *RunResult[T]) Decision() *Report

Decision returns the report that determines the pipeline outcome. Priority (per task9): Block > Retry > (last Redact) > (last Pass). Reports order is nondeterministic in slow path; must scan entire slice.

type SemanticValidator added in v0.4.0

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

SemanticValidator is a Slow-Path validator that blocks when score exceeds threshold.

func NewSemanticValidator added in v0.4.0

func NewSemanticValidator(m Matcher, threshold float64, shadow bool) *SemanticValidator

NewSemanticValidator builds a validator that blocks when m.Match returns score > threshold. If shadow is true, block reports are marked ShadowMode so the pipeline does not short-circuit.

func (*SemanticValidator) Validate added in v0.4.0

func (s *SemanticValidator) Validate(ctx context.Context, input string) (string, *Report, error)

Validate runs the matcher and returns block when score > threshold.

type Severity added in v0.6.0

type Severity string

Severity describes risk/importance of a rule hit. It is string-based for interoperability while still supporting typed constants.

const (
	SeverityLow      Severity = "low"
	SeverityMedium   Severity = "medium"
	SeverityHigh     Severity = "high"
	SeverityCritical Severity = "critical"
)

Canonical severity values for built-in validators and telemetry.

type ValidationPhase added in v0.6.0

type ValidationPhase string

ValidationPhase identifies which pipeline phase is currently executing.

const (
	ValidationPhaseFast ValidationPhase = "fast"
	ValidationPhaseSlow ValidationPhase = "slow"
)

Known pipeline phases for telemetry/middleware.

func ValidationPhaseFromContext added in v0.6.0

func ValidationPhaseFromContext(ctx context.Context) (ValidationPhase, bool)

ValidationPhaseFromContext extracts the current validation phase from context.

type Validator

type Validator[T any] interface {
	Validate(ctx context.Context, input T) (T, *Report, error)
}

Validator is the generic contract for validating input of type T. Returns (possibly mutated) input, a Report pointer, and an error for infrastructure failures. Report.Validator should be set by the implementation to identify the validator.

func Map added in v0.5.0

func Map[T any, U any](v Validator[U], extract func(T) U, inject func(T, U) T) Validator[T]

Map returns a Validator[T] that wraps Validator[U] with extract (getter) and inject (setter). When the inner validator returns ActionRedact, inject is called to apply the mutation to T.

type ValidatorFunc added in v0.5.0

type ValidatorFunc[T any] func(ctx context.Context, input T) (T, *Report, error)

ValidatorFunc adapts a function to Validator[T] for use in tests and middleware.

func (ValidatorFunc[T]) Validate added in v0.5.0

func (f ValidatorFunc[T]) Validate(ctx context.Context, input T) (T, *Report, error)

Validate implements Validator[T].

type ValidatorMiddleware added in v0.5.0

type ValidatorMiddleware[T any] func(next Validator[T]) Validator[T]

ValidatorMiddleware wraps a Validator with cross-cutting logic (metrics, logging).

Directories

Path Synopsis
ext
Package ext provides built-in validators for the guardy pipeline.
Package ext provides built-in validators for the guardy pipeline.
jsonschema module
Package guardytest provides test helpers for guardy pipelines and validators.
Package guardytest provides test helpers for guardy pipelines and validators.

Jump to

Keyboard shortcuts

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