# Shared validation mechanics `internal/validation` owns reusable checks for one captured candidate. `internal/candidate` captures the clean source revision and keeps the validation sequence shared by local checking and publication. It calls the shared executor, runs product-specific `AdditionalChecks` once, and owns the post-validation build callback and local-state recheck. `internal/publish` adds its endpoint guards, tag creation, push, and recovery around that same candidate path. The executor runs one offline Go suite from the captured module directory, optionally with the race detector when selected. When full vet is selected, it disables test-time vet for that suite and runs native `go vet -mod=readonly ./...` from the same directory after the suite succeeds. It then runs `go build -mod=readonly -o ./...` there in static mode for each selected package-compilation target, preserving target order and stopping on the first failure. This compiles source without producing an artifact or executing a target binary. Test, vet, and package-compilation failures retain their `go-test`, `go-vet`, and `package-compile` identities, selected target where applicable, and underlying causes. Its request carries the captured repository directory and commit, output writer, and internal settings. When selected, the Go-format check loads the candidate tree once, reads regular Go blobs from the captured commit, and compares them with go/format output. It uses the selected inputs, including vendor and explicit shared files, and skips symlink blobs, reports all unformatted paths, and stops with a contextual cause on parse or inspection errors. It runs before product checks and build callbacks. `Request.ReadCandidateTree` lets selected file checks request candidate contents; the Go-only path does not enumerate the tree. ## Selected validation inputs `CandidateTree` keeps the full committed repository lookup separate from the ordered file input set. The selected module's committed inputs exclude independently released nested modules, while vendored contents stay in scope. Selection indexes boundary directories once and checks each entry's ancestors, so unrelated boundaries are not scanned for each file. The [scope benchmark](../../internal/validation/scope_performance_test.go) varies boundary count while retaining 20,000 input files. Explicit shared-file selections are normalized from repository-root paths, added as individual files, and deduplicated with module inputs by path. File-size exclusions are normalized from the selected module directory. Link resolution can still inspect a committed destination outside the selected input set without adding that destination's content to the checks. The public selection, nil/empty behavior, and profile path bases belong to the [configuration reference](../config.md) and the root facade's GoDoc. The workspace-file check uses that same candidate inventory and reports blob entries named go.work or go.work.sum, including nested paths and symlink blobs. It does not read workspace contents or depend on the workspace selected for Go commands. The local-replacement check parses selected go.mod blobs with `golang.org/x/mod/modfile`, including vendor and explicitly shared declarations. It follows committed symlink declarations through the captured tree and fails with an inspection cause when their selected contents are unavailable. A replacement with no parsed version is reported with its go.mod path, line, directive, and target; versioned replacements are allowed. It runs after the workspace-file check using the same candidate inventory. Resolved module bodies are parsed once per blob object ID; only owned filesystem-replacement targets and line numbers remain cached. Each declaration path receives findings from that summary in its own path context. The optional file-size check applies normalized literal file/directory exclusions with path-boundary matching, then requests local Git sizes for the remaining selected blobs. Sizes include symlink link-text bytes; gitlinks are omitted. The check returns the first oversized path with `FileSizeMeasurement`. The optional Markdown check follows file-size inspection and uses the same candidate tree. It parses regular Markdown blobs, resolves local destinations, and checks supported anchors; outside-checkout and submodule observations are written to the shared output writer. A nil writer keeps library use quiet. `CandidateTree` enumerates full repository path, type, mode, and object metadata without requiring blob contents or sizes. Checks acquire sizes and bytes for their selected inputs and required references. Unreferenced blobs in excluded modules remain outside those reads. Destinations resolve without consulting the worktree or neighboring files. Each tree owns one lazy Git blob reader; direct tree users must close it when finished. The shared validation executor closes its tree on every return before product checks or build callbacks run. A metadata-only selection never starts the reader. Per-read contexts cancel an in-flight blob exchange. The context supplied when creating the tree governs the reader lifetime, including shutdown and child reaping. Regular-file reads return independent caller-owned byte slices and are not retained by the tree. Symlink targets are retained as immutable owned strings by blob object ID; explicitly reading a symlink still returns independent bytes. The Go-format check caches only whether each successfully inspected object is formatted, then reports every unformatted path that refers to it. The internal Markdown parser uses Goldmark to record inline, reference, and autolinks, images, titles, and their source use sites, and to build heading anchors from visible AST text. It supports ATX and Setext headings, decodes Markdown text through the parser, and assigns collision-free numeric suffixes to repeated anchors. Link locations use one-based line and byte-column values. The Markdown cache shares parsed records and anchors by regular blob object ID, with a path lookup for callers. Retained destinations and titles own their string storage, and parsed records contain content-relative locations only. `checkMarkdownLinks` examines regular `.md` blobs from the captured tree. It skips schemed and protocol-relative URIs, strips query components, and decodes percent escapes once in local paths and fragments. It resolves destinations through `CandidateTree.ResolveDestination` relative to the current source path; decoded fragments are compared in lowercase against anchors from the resolved regular Markdown document. Missing paths, symlink cycles, and missing anchors produce `markdown-links` findings at the source use site. Outside-checkout links produce out-of-scope observations by default and findings when boundary enforcement is selected. Submodule-content observations remain out of scope under either setting. Observations go to the provided writer; a nil writer keeps library use quiet. Public selection is defined in the [configuration reference](../config.md) and GoDoc. Committed-tree metadata and blob reads disable lazy fetching and Git transports for those child commands. Missing local objects produce inspection errors; source preparation retains its separate network-capable operations. See Git's [lazy-fetch controls](https://git-scm.com/docs/git) for the command mechanism. Candidate and delivery release-note reads use the same local object boundary. Public validation errors convert their diagnostic fields to the internal failure model for shared formatting. Converted failures retain their original contextual cause and reuse its rendering when it already contains the same diagnostics. The CLI presents the complete error chain, including candidate context around callback-supplied diagnostics. ## Adding a shared check When an optional check is accepted for implementation: 1. Define its typed Go setting in the root facade and its equivalent CLI-owned JSON field. Specify zero-value and omitted-profile behavior in GoDoc and the configuration reference. Keep future settings in the roadmap until their check is implemented. 2. Add the corresponding internal setting and explicit conversions. Use the same root request conversion for `Check` and `Publish`, and the shared CLI request conversion for profile-based candidate commands. 3. Implement a plain check function owned by `internal/validation`. Keep its rule semantics in the capability owner and pass only the captured candidate inputs it needs. A file-oriented check can call `Request.ReadCandidateTree` and read only the blobs it inspects. 4. Return an internal `*Failure` with the check identity, repository-relative findings, useful locations where available, and the underlying cause. Let the root facade convert those records to its own diagnostic types. Preserve cancellation and process errors through `Unwrap`. 5. Add the check to the shared executor at the ordering point selected by its capability. Stop on failure. Keep product assertions in `AdditionalChecks` and profile `checks`; keep artifact building in `internal/build`. 6. Cover observable behavior through both the Go facade and profile path when both are supported. Check default behavior, useful findings, cause preservation, cancellation where relevant, and failure stopping. Reuse the existing local Git and CLI fixtures. 7. Update only the canonical owners for behavior that has landed: GoDoc for the API, configuration and CLI references for profile behavior, and this inventory for internal ownership. Record proposed checks under `docs/roadmap/`. Do not add a public validator registration interface. The public specification selects ReleaseKit-owned checks; `AdditionalChecks` remains the extension point for product behavior. ## Go command environment reuse The current executor uses the [shared Go command environment](gotool.md) for its offline suite, vet pass, and selected-target package compilation. Reuse `internal/gotool` for native and static environments and target handling. Keep toolchain and module-cache discovery in `gotool`; do not reproduce its environment logic in a checker. Artifact output names, version injection, native smoke checks, and artifact digests remain owned by `internal/build`. ## File-check coordination File checks consume the captured tree and return findings to the shared error model. They do not read sibling worktrees or follow live symlink targets. `CandidateTree.ResolveDestination` returns data describing entries, directories, missing paths, checkout boundaries, submodule paths, and symlink cycles; the caller selects which observations fail validation or are reported as out of scope. The shared Git path resolver walks committed components and symlink targets in order using the tree's indexed metadata and lazy symlink reader. Active symlink expansion detects cycles, and traversal honors cancellation. Absolute destinations and symlink targets within the repository directory map back to candidate-relative paths; existence still comes from committed entries. The public selection is owned by the facade and profile; the internal checker uses the committed-file and destination-resolution primitives described here.