Documentation
¶
Overview ¶
Package guardrail evaluates declarative rules over a worktree diff and returns the tripped rules. Rules are data (globs + thresholds + RE2 patterns), never code, so growing from 2 rules to 20 is a config change. Evaluation is pure and allocation-light.
THE NON-ECHO INVARIANT (security, frozen — P5-design.md §1.1): no hit Message, default or computed, ever contains matched line content or the matched token. Content rules (added_pattern, entropy) report the rule's message plus file + line + a match COUNT only. This is what keeps secrets out of the SSE stream, the JSON API, wtd's logs, and desktop notifications.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GlobMatch ¶
GlobMatch reports whether pattern matches path. Supports:
- "*" matches any run of non-slash characters
- "**" matches any run of characters including slashes
- "?" matches a single non-slash character
It is anchored (must match the whole path).
Matching runs a bounded dynamic-programming scan over (pattern tokens × path bytes) — O(len(pattern)*len(path)) time, never exponential. This replaced a recursive "try every split point at every '*'/'**'" matcher: against an adversarial pattern (repeated "**a", or even repeated "*a") matched against a long non-matching path, that matcher ran superpolynomially — the same catastrophic-backtracking class RE2 was chosen to avoid for added_pattern regexes, left open here until this fix (BLOCKER-1). A pack's globs are semi-trusted (checked-in .wtcockpit.toml) input evaluated on every refresh; Eval must never be able to wedge on one.
Types ¶
type Effective ¶ added in v0.5.0
type Effective struct {
WorktreeID string `json:"worktreeId"`
RepoPath string `json:"repoPath"`
PackPath string `json:"packPath"` // "" when no pack file is present
PackStatus string `json:"packStatus"` // "none" | "ok" | "error: <msg>"
Rules []RuleWithSource `json:"rules"`
}
Effective is GET /api/rules's payload and `wt rules --json`'s frozen output: the fully-resolved rule set for one worktree's owning repo, with enough pack metadata to make precedence confusion debuggable in one command (P5-design.md §1.3).
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine holds compiled, validated rules. Build with Compile.
func Compile ¶ added in v0.5.0
Compile validates rules and returns a ready-to-Eval Engine. It replaces the old can't-fail New: growing the rule language to include regex patterns and threshold combinations means a hand-edited (or agent-authored) rule pack can now be self-contradictory or fail to compile, and that must be caught at load time with a message naming the offending rule — the same "typo fails fast" philosophy config.Load already applies to the TOML shape itself.
Validated, in order: severity enum; path_glob/path_globs mutual exclusion; per-file vs worktree-wide condition mixing (including exclude_globs on a worktree-wide rule); min_token_entropy requires min_token_len >= 8; added_pattern must compile as RE2; rule names must be unique once blank names are auto-filled ("rule-<1-based position>").
func (*Engine) Eval ¶
func (e *Engine) Eval(d model.Diff) []model.GuardrailHit
Eval returns every guardrail hit for the given diff. Per-file rules emit one hit per matching file (at most one, even when a content condition matches several lines — see scanContent); worktree-wide rules emit a single hit with an empty File.
type Pack ¶ added in v0.5.0
Pack is a per-repo rule-pack file: a checked-in .wtcockpit.toml at a repo's root (P5-design.md §1.3). Rules use the exact same shape (and TOML tags) as config.Config's own [[rules]] table.
func ParsePack ¶ added in v0.5.0
ParsePack decodes pack TOML bytes strictly: malformed TOML or an unrecognised key (top-level or inside a [[rules]] entry) is an error — mirroring config.Load's own "a typo in a hand-edited guardrails file is a real footgun otherwise" philosophy. The caller (Resolver) is what turns a parse error into the fail-closed-to-global behaviour; ParsePack itself just reports it.
type Resolver ¶ added in v0.5.0
type Resolver struct {
// contains filtered or unexported fields
}
Resolver resolves the effective rule engine for a repo, folding in that repo's optional .wtcockpit.toml pack. It holds the compiled global engine (config [[rules]] or DefaultRules()) plus a per-repo cache invalidated by the pack file's mtime+size — one os.Stat per worktree per refresh, negligible (P5-design.md §1.3's "Mechanics").
func NewResolver ¶ added in v0.5.0
NewResolver compiles the global rule set once (source is "default" or "global", for Effective's provenance tag) and returns a Resolver ready for For/Effective. An error here means the global rules themselves are invalid; config.Load already validates via Compile before this is ever called in production, so this is a defensive check, not a load-bearing one.
func (*Resolver) Effective ¶ added in v0.5.0
Effective returns worktreeID's owning repo's fully-resolved, provenance- tagged rule set — GET /api/rules and `wt rules`'s payload.
func (*Resolver) For ¶ added in v0.5.0
For returns the compiled rule engine to evaluate a diff from repoPath against: the global engine when no pack exists there, or the pack-merged engine otherwise. A malformed pack fails closed to the global engine — this never returns nil and never disables guardrails outright.
type Rule ¶
type Rule struct {
Name string `json:"name" toml:"name"` // auto-filled "rule-<n>" when empty; duplicates = load error
Severity string `json:"severity" toml:"severity"` // "" | "warn" | "danger" — validated at Compile
Message string `json:"message" toml:"message"`
// Per-file conditions: all conditions a rule sets must hold for a file to
// trip it. A rule whose only set field is ExcludeGlobs has no real
// condition and so matches nothing (hasFileCond).
PathGlob string `json:"pathGlob,omitempty" toml:"path_glob"` // e.g. "migrations/**"; mutually exclusive with PathGlobs
PathGlobs []string `json:"pathGlobs,omitempty" toml:"path_globs"` // any-of
ExcludeGlobs []string `json:"excludeGlobs,omitempty" toml:"exclude_globs"` // exemption: a matching file skips this rule
Status string `json:"status,omitempty" toml:"status"` // "modified"|"added"|"deleted"|"renamed"
Binary bool `json:"binary,omitempty" toml:"binary"` // file must be a binary diff
MinNetDeleted int `json:"minNetDeleted,omitempty" toml:"min_net_deleted"` // del-add >= N in one file
MinChangedLines int `json:"minChangedLines,omitempty" toml:"min_changed_lines"` // add+del >= N in one file
AddedPattern string `json:"addedPattern,omitempty" toml:"added_pattern"` // RE2 over added lines' content
MinTokenEntropy float64 `json:"minTokenEntropy,omitempty" toml:"min_token_entropy"` // bits/char threshold; requires MinTokenLen >= 8
MinTokenLen int `json:"minTokenLen,omitempty" toml:"min_token_len"`
// Worktree-wide conditions: a rule using any of these must not also set a
// per-file condition above (Compile rejects the mix). All that are set on
// one rule are ANDed, same as the per-file conditions.
MinDeleteAddRatio float64 `json:"minDeleteAddRatio,omitempty" toml:"min_delete_add_ratio"` // del >= ratio*add
MinFilesChanged int `json:"minFilesChanged,omitempty" toml:"min_files_changed"` // len(files) >= N
MinTotalChanged int `json:"minTotalChanged,omitempty" toml:"min_total_changed"` // sum(add+del) >= N
}
Rule is one declarative guardrail. A rule may combine several per-file conditions (ANDed) — see hasFileCond — or exactly one worktree-wide class (ratio / files-changed / total-churn); mixing the two classes on one rule is a Compile error.
func DefaultRules ¶
func DefaultRules() []Rule
DefaultRules is the sensible starter set shipped when no config is present (P5-design.md §1.2). The v0.2 six survive under their original names (the "-root" + nested glob pairs collapse into one path_globs list each); the rest are new: secrets (pattern + entropy), lockfile/manifest churn, file-count/total-churn thresholds, a CI-workflow delete, and an added binary file.
type RuleWithSource ¶ added in v0.5.0
RuleWithSource is one entry of an Effective payload: a Rule plus where it came from.
func Merge ¶ added in v0.5.0
func Merge(global []Rule, globalSource string, pack Pack) []RuleWithSource
Merge computes the effective, provenance-tagged rule list for one repo: global rules (tagged globalSource — "default" or "global") minus any name in pack.DisableRules, plus pack.Rules appended — a pack rule whose name matches a surviving global rule REPLACES it in place, tagged "pack" (the frozen precedence, P5-design.md §1.3). Pure: no I/O, no validation — the caller Compiles the resulting rules (via RuleWithSource.Rule) separately, which is what catches a pack rule that's individually fine but breaks the combined set (e.g. a duplicate name against a rule Merge didn't replace).
A name is only ever replaced-in-place once per Merge call: the first pack rule matching a surviving global name overrides it as before, but a LATER pack rule reusing that same name (a duplicate [[rules]] entry within the pack itself, not an override of anything global) is appended as a second, distinct entry instead of silently overwriting the first — that's what lets the caller's subsequent Compile catch it as a duplicate rule name, exactly like the identical mistake in config.toml's [[rules]] list already does, rather than one pack rule vanishing with no error anywhere.