Documentation
¶
Overview ¶
Package governance implements validation rules for GoCell metadata. It checks referential integrity, topological legality, verify closure, format compliance, and advisory warnings across the parsed ProjectMeta.
governance is invoked by `gocell validate` (cmd/gocell/app) and by CI; it is a pure validation tool — no runtime / adapter side effects, no goroutine ownership, no I/O beyond reading filesystem-resident YAML during the parse phase.
Rule numbering: see kernel/governance/CLAUDE.md for the REF / TOPO / VERIFY / FMT / ADV / OUTGUARD series and the ValidationResult schema.
Package governance hosts repository-level audit and validation rules. The helpers in this file expose a single source of truth for "what does git HEAD know about this path" so generatedverify and metricschema cannot drift into divergent definitions of "tracked".
Package governance — select-targets impact analysis. Given a set of changed file paths, TargetSelector computes which slices, cells, contracts, and journeys are potentially affected. This is ADVISORY level — not a completeness guarantee.
Package governance implements validation rules for GoCell metadata. It checks referential integrity, topological legality, verify closure, format compliance, and advisory warnings across the parsed ProjectMeta.
Design ref: kubernetes apimachinery field/errors.go — typed error classification and error accumulation pattern; diverges by using simple string field paths instead of K8s field.Path linked lists.
Index ¶
- Constants
- func CommittedInHEAD(ctx context.Context, root, rel string) (bool, error)
- func EvalExistingPrefix(p string) string
- func HasErrors(results []ValidationResult) bool
- func HasGitMetadata(root string) bool
- func IsGoCellGenerated(content []byte) bool
- func IsWithinRoot(root, target string) bool
- func ListGeneratedInHEAD(ctx context.Context, root string) ([]string, error)
- type AffectedTargets
- type Edge
- type Graph
- type IssueType
- type Phase
- type Rule
- type RuleCode
- type Severity
- type TargetSelector
- type ValidationResult
- type Validator
Constants ¶
const GoGeneratedPrefix = "// Code generated by gocell generate "
GoGeneratedPrefix is the canonical "// Code generated by gocell generate " prefix that gocell-generated Go files start with. The exact phrasing is the boundary contract enforced by writeGeneratedFile (cmd/gocell/app generate.go) on the producer side and by generatedverify reverse enumeration on the verifier side; both must read this constant rather than re-spelling the literal.
const YAMLGeneratedPrefix = "# Generated by gocell generate "
YAMLGeneratedPrefix is the YAML-comment counterpart used for boundary.yaml, metrics-schema.yaml, and any future generated YAML artifact.
Variables ¶
This section is empty.
Functions ¶
func CommittedInHEAD ¶
CommittedInHEAD reports whether rel is committed in HEAD at root. Files that are only `git add`-ed (in the index but not in HEAD) return false so every committed-in-HEAD audit gate uses a uniform predicate.
rel must be a forward-slash repo-relative path. ExitErrors are interpreted as "not committed" (cat-file -e exits non-zero for unknown refs); other errors propagate so the caller fails closed. ctx.Err() takes precedence over the ExitError mapping so a canceled subprocess never gets folded into "not committed".
func EvalExistingPrefix ¶
EvalExistingPrefix resolves symlinks on the longest existing ancestor of p, then appends the non-existent suffix. This handles platforms where intermediate directories are symlinks (e.g., macOS /tmp → /private/tmp).
func HasErrors ¶
func HasErrors(results []ValidationResult) bool
HasErrors returns true if any result has SeverityError.
func HasGitMetadata ¶
HasGitMetadata reports whether root looks like a git work tree (has a .git entry). Test fixtures that operate on plain temp directories return false so callers can degrade gracefully to content-only checks.
func IsGoCellGenerated ¶
IsGoCellGenerated reports whether content carries either of the gocell generator header sentinels. Callers pass the leading bytes of a candidate file.
func IsWithinRoot ¶
IsWithinRoot checks that target resolves to a path inside root. Both sides are normalized to absolute paths, and symlinks are resolved when possible, to prevent both relative-path and symlink-based bypasses.
Exported so cmd/gocell and other callers share a single implementation rather than carrying a duplicate with a hand-maintained `// SYNC:` note.
kernel/metadata.isWithinRoot is a forced duplicate (layering forbids kernel/metadata importing kernel/governance); keep both in sync until the shared-helper extraction tracked in #1255 lands.
func ListGeneratedInHEAD ¶
ListGeneratedInHEAD returns repo-relative paths of every committed file at root whose first line carries a gocell generator header sentinel. Result is filtered to the file extensions generators currently emit (.go, .yml, .yaml); any future generator producing a different extension must extend this filter alongside the generator change so reverse enumeration stays closed.
Implementation uses `git grep` so the entire HEAD tree is scanned in a single subprocess; per-file probing through `git show` would not scale for large work trees.
An empty repository (HEAD does not yet resolve) or "no matches" returns (nil, nil) without error.
Types ¶
type AffectedTargets ¶
type AffectedTargets struct {
Slices []string // affected slice IDs ("cellID/sliceID" format)
Cells []string // affected cell IDs
Journeys []string // affected journey IDs
Contracts []string // affected contract IDs
}
AffectedTargets holds the impact analysis result.
type Graph ¶
Graph is the cell-level directed dependency graph produced by Graph(). Nodes and Edges are deterministically sorted (Nodes alphabetically; Edges by From then To) so callers can byte-compare two Graph values.
type IssueType ¶
type IssueType string
IssueType classifies the kind of validation issue. ref: kubernetes apimachinery field/errors.go — typed error classification
type Phase ¶
type Phase int
Phase selects which command surface a rule participates in. It replaces the former four separate dispatch lists with one declarative attribute, so the strict/base split is a rule property rather than two rule sets (ADR §6.3 root cause: "FMT strict-only 切两套规则集 ... 被错做规则集合分裂").
const ( // PhaseBase rules run on every `gocell validate` invocation. PhaseBase Phase = iota // PhaseStrict rules run only with `gocell validate --strict` (VERIFY-06, // FMT-16/17/19, DOC-NAME-01). PhaseStrict // PhaseDep rules are the cell dependency-graph checks (DEP-01..03). They // run alongside base rules on `gocell validate` but are grouped separately // because their detect functions consult the cell/contract registries. PhaseDep // PhaseHealth rules are the contract-health invariants (CH-01..06) run by // `gocell check`. PhaseHealth )
type Rule ¶
type Rule struct {
Code RuleCode
Phase Phase
Detect func(v *Validator) []ValidationResult
}
Rule is one governance rule expressed as data: classification metadata plus a compiler-checked detect function value. The allRules slice (rules_registry.go) is the sole source of truth; run() iterates it. Detect returns the finished findings (built via the locator constructors). See package doc for the open-source analogs this mirrors.
type RuleCode ¶
type RuleCode string
RuleCode is a named string type that identifies a single governance rule. Exported so cmd/ and tools/ can reference the type when constructing ValidationResult values.
func StrictRuleCodes ¶
func StrictRuleCodes() []RuleCode
StrictRuleCodes returns the codes of the PhaseStrict rules in declaration order — the rules that run only under `gocell validate --strict`. The validate CLI derives its --strict flag help from this, so the help text is a pure projection of the registry and can never drift from the rules that run.
type TargetSelector ¶
type TargetSelector struct {
// contains filtered or unexported fields
}
TargetSelector computes affected targets from file changes. This is ADVISORY level — not a completeness guarantee.
func NewTargetSelector ¶
func NewTargetSelector(project *metadata.ProjectMeta) *TargetSelector
NewTargetSelector creates a TargetSelector for the given project metadata.
func (*TargetSelector) SelectFromFiles ¶
func (ts *TargetSelector) SelectFromFiles(files []string) *AffectedTargets
SelectFromFiles takes changed file paths (relative to project root) and returns affected targets.
Mapping logic:
- File path -> slice (via parsed cell/slice source directories)
- Slice -> cell (via belongsToCell)
- Slice -> contracts (via contractUsages)
- Cell -> journeys (via journey.cells)
- journeys/J-*.yaml -> journey -> cells -> slices + contracts
- assemblies/{id}/assembly.yaml -> assembly -> cells -> slices
ref: K8s kubectl diff — impact analysis across all resource types
func (*TargetSelector) SelectFromSlice ¶
func (ts *TargetSelector) SelectFromSlice(sliceKey string) *AffectedTargets
SelectFromSlice takes a slice key ("cellID/sliceID") and expands to all affected targets.
type ValidationResult ¶
type ValidationResult struct {
Code RuleCode // e.g., codeREF01, codeTOPO03
Severity Severity
IssueType IssueType
File string // YAML file path; empty when Scope is set
Scope string // virtual scope name; empty when File is set
Field string // field path within YAML, e.g. "contractUsages[0].role"
Message string
// Fix is the remediation guidance for a finding ("how to make this rule
// pass"). It is REQUIRED and non-empty for all findings regardless of
// severity: both error and warning results carry structured remediation
// guidance in this typed field. Splitting Fix out of Message replaces the
// old "; fix:" Message-substring convention (INV-3 Soft anchor) with a
// typed field: all findings are constructed exclusively through newError /
// newWarning / newErrorAt / newScopedError, each of which takes fix as a
// mandatory positional argument (GOVERNANCE-RULE-ERROR-FIX-FIELD-01).
// Renderers surface it as a distinct "fix:" line / JSON field, never
// re-concatenated into Message.
Fix string
// Line and Column locate the offending value inside File. They are 1-based
// (matching yaml.v3) and zero when the position is unknown — e.g. the
// ProjectMeta was constructed without FileNodes, or the field path cannot
// be resolved (array index out of range, typo in rule code, etc.). They
// are always zero when Scope is set.
Line int
Column int
}
ValidationResult represents a single validation finding.
File and Scope are mutually exclusive:
- File identifies a real YAML file; the finding points at a concrete location (File plus Line/Column) that an IDE can open.
- Scope names a virtual domain ("project", "cross-file", ...) used by checks that inspect relationships across multiple files and therefore cannot pin the issue to a single file position. CLI renderers must avoid showing Scope with a "file:line:col" prefix because doing so would invite users to try (and fail) to jump to it.
func FilterErrors ¶
func FilterErrors(results []ValidationResult) []ValidationResult
FilterErrors returns only error-severity results.
func FilterWarnings ¶
func FilterWarnings(results []ValidationResult) []ValidationResult
FilterWarnings returns only warning-severity results.
type Validator ¶
type Validator struct {
// contains filtered or unexported fields
}
Validator runs all validation rules against a parsed project. It embeds locator to share locate + the typed constructors (newError/newWarning/newScopedError) and to promote the project field so rule code reads v.project.* directly.
Validator is not safe for concurrent ValidateStrict calls. Build one Validator per concurrent caller.
func NewValidator ¶
NewValidator creates a Validator for the given parsed project metadata. If project is nil, an empty ProjectMeta is used to avoid nil-pointer panics.
func (*Validator) CheckHealth ¶
func (v *Validator) CheckHealth(ctx context.Context) ([]ValidationResult, error)
CheckHealth runs all PhaseHealth rules (CH-01 through CH-06) against the project loaded into v and returns the combined findings. It is the single entry point for `gocell check contract-health`.
The error return mirrors run(): it is non-nil only when ctx is canceled mid-run, carrying the partial findings so the caller can distinguish a clean run from an interrupted one. Callers must propagate it rather than discard it, so a signal-aware ctx (Ctrl-C during a slow handler-file scan) actually stops the check.
func (*Validator) Graph ¶
func (v *Validator) Graph() (Graph, []ValidationResult)
Graph builds the cell dependency graph from the project metadata and returns it together with any resolution errors encountered during construction. The returned Graph is always fully sorted (Nodes and Edges) for determinism. If resolution errors are present the graph may be incomplete, but it still contains all nodes for cells that were resolved cleanly.
func (*Validator) ValidateStrict ¶
func (v *Validator) ValidateStrict(ctx context.Context, strict, failFast bool) ([]ValidationResult, error)
ValidateStrict is the single entry point for governance validation. strict and failFast are orthogonal flags forming a 2x2 matrix:
- strict=false, failFast=false → run all base rules, collect every result
- strict=false, failFast=true → run base rules, stop at the first error
- strict=true, failFast=false → run base + strict-only rules, collect all
- strict=true, failFast=true → run base + strict rules, stop on error
The rule pipeline is driven by allRules (rules_registry.go), filtered by phase: base runs PhaseBase+PhaseDep; strict additionally includes PhaseStrict. The engine loop (run) handles ctx cancellation and implements the fail-fast bailout on the first SeverityError. See engine.go for the loop body.
The error return is non-nil only when ctx.Err() != nil at the time the loop is interrupted; it carries the partial findings collected so far so callers can distinguish "clean run" from "interrupted run".
Validator is not safe for concurrent ValidateStrict calls. Build one Validator per concurrent caller — same expectation as the underlying locator and the verifyJourneyRef closure.
ValidateStrict drives the `gocell validate` surface (PhaseBase/Dep/Strict). The contract-health surface (`gocell check`) is a separate entry point, CheckHealth (contracthealth.go), which runs PhaseHealth through the same engine loop.
Source Files
¶
- advisory_hints.go
- contracthealth.go
- depcheck.go
- doc.go
- emitter_invariant.go
- engine.go
- genheader.go
- git.go
- helpers.go
- locator.go
- rulecodes.go
- rules_command.go
- rules_contract_test_mapping.go
- rules_fmt.go
- rules_http.go
- rules_journey.go
- rules_lifecycle.go
- rules_misc_advisory.go
- rules_misc_consistency.go
- rules_misc_strict.go
- rules_projection_consistency.go
- rules_ref.go
- rules_registry.go
- rules_saga.go
- rules_topo.go
- rules_verify.go
- targets.go
- validate.go