Files
ReleaseKit/docs/internal/validation.md

147 lines
8.7 KiB
Markdown

# 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, 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 ./...` after the suite succeeds. It then
runs `go build -mod=readonly -o <os.DevNull> ./...` 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 includes vendor and nested-module files, 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.
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 every committed go.mod blob with
`golang.org/x/mod/modfile`, including nested modules and vendor 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.
The optional file-size check uses each blob's captured Git size, including
symlink link-text bytes, and skips entries without a blob size such as gitlinks.
It applies normalized literal file/directory exclusions with path-boundary
matching, then 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` gets metadata and bytes from Git objects, reads blob bytes
lazily, and resolves destinations without consulting the worktree or
neighboring files.
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.
A per-path cache retains parsed records and anchors for candidate documents.
`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`; 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.
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. Keep later feature contracts in the
product 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. The
[implementation roadmap](../roadmap/implementation.md#execution-and-ownership)
records the shared ordering across validation and later file checks.
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.
Resolution walks committed components and symlink targets in order. 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 [implementation plan](../roadmap/implementation.md) records the completed
Markdown integration. The public selection is owned by the facade and profile;
the internal checker uses the committed-file and destination-resolution
primitives described here.