21 KiB
Validation model implementation plan
Status: In progress; the shared executor, internal diagnostics, and committed-file reader are complete.
This plan implements the enabling work in model.md, guided by ADR 0003. It is written for a Luna coding agent executing one stage per prompt, in numerical order. The v0.2.0 roadmap owns the scope and defaults of the individual capabilities. Their checker implementations, module-selected CI tooling, and the publication endpoint expectation are subsequent feature work.
The foundation provides shared execution, diagnostics, committed-file access, and a concrete design for equivalent Go and profile declarations. Completion of this plan establishes that foundation; completion of the individual validation features makes their selections operational.
Instructions for each stage
- Start with development.md, the agent policy, and the policies applicable to the stage. Inspect the worktree and reuse relevant validation already available.
- Use the codebase graph to locate current symbols. The paths below identify the inspected baseline and may change during implementation.
- Implement the stage's focused outcome with its behavioral coverage and applicable documentation. Commit completed units according to the agent policy and report the resulting commit and remaining work.
- Continue through the sequence using local fixtures and automated checks. Consumer migrations, hosted actions, intermediate releases, and human review are outside these stages. Architecture review and next-step decisions follow the completed implementation.
- Keep internal collaboration proportionate: ordinary structs, functions, and
explicit conversions. Preserve the root and
buildinfofacade boundaries. Current API contracts and selected user decisions determine compatibility.
Design to carry through the stages
API rollout
Complete the shared refactor, diagnostics, candidate readers, and conversion
boundaries in this plan first. Introduce each public validation setting with
its functioning capability. The first capability introduces ValidationSpec
and the request/profile field; subsequent capabilities add their supported
selections. Go and profile behavior remain equivalent, and each feature owns
its behavioral coverage. The model's full target is reached as the capability
implementations land.
Validation declarations
The intended complete facade shape is:
type ValidationSpec struct {
Go GoValidationSpec
GoFormat bool
Markdown *MarkdownValidationSpec
ModuleDistribution ModuleDistributionValidationSpec
FileSize *FileSizeValidationSpec
}
type GoValidationSpec struct {
Race bool
FullVet bool
CompileTargets []Target
}
type MarkdownValidationSpec struct {
EnforceCheckoutBoundary bool
}
type ModuleDistributionValidationSpec struct {
WorkspaceFiles bool
LocalReplacements bool
}
type FileSizeValidationSpec struct {
LimitBytes *int64
Exclusions []string
}
ReleaseRequest.Validation holds a ValidationSpec value. Target is the
existing facade-owned operating-system/architecture pair. The corresponding
profile keys are validation.go.race, validation.go.full_vet,
validation.go.compile_targets, validation.go_format,
validation.markdown.enforce_checkout_boundary,
validation.module_distribution.workspace_files,
validation.module_distribution.local_replacements,
validation.file_size.limit_bytes, and validation.file_size.exclusions.
Profile compilation targets use the existing GOOS/GOARCH string convention.
Value groups preserve zero-value defaults. A non-nil Markdown or file-size group enables its check; an empty JSON object has the same meaning. Each distribution boolean independently selects inspection for the corresponding distribution rule. Omitted optional groups select current default validation. A nil byte limit selects 1,000,000 bytes when file-size checking is enabled; an explicit zero selects zero bytes. Negative byte limits are invalid because byte counts are nonnegative. Exclusions identify literal repository-relative file or directory paths; a directory exclusion includes its committed descendants. Preserve Git path spelling and use slash-separated paths.
These declarations describe the complete intended model. Public settings acquire operational behavior with their capability implementations under the incremental rollout above. Current documentation describes the implemented boundary precisely.
Execution and ownership
internal/candidateretains capture and rechecks. Its existingValidateentry point delegates shared validation tointernal/validationand then invokesAdditionalChecksonce with the captured candidate.internal/validationowns internal settings, the shared executor, command coordination, and diagnostic records. It receives the captured repository directory and commit explicitly. It depends on internal Go and Git helpers; internal production packages retain internal contracts.- Root facade helpers perform request and diagnostic conversions.
CheckandPublishshare those helpers. The CLI maps its own JSON settings to the root facade incmd/releasekit, preserving its helper-package dependency boundary. - Shared validation runs one base Go suite, selected full vet, selected package compilation, and selected file checks, followed by product-specific checks. Artifact builds retain their existing after-validation position. The foundation executes the currently implemented portion of this procedure.
- Native commands retain the current offline environment. Future race
selection changes that one suite; full vet adds
-vet=offto testing and a separate vet invocation. Package compilation uses static target mode withCGO_ENABLED=0, independently of artifact declarations. - Preserve fail-stop behavior, context propagation, candidate rechecks, local
Checkbehavior, and publication phase/state meanings. Cross-check ordering is internal procedure except for the relationships selected in the roadmap.
Diagnostic model
Add facade-owned ValidationCheck (a string type), ValidationFinding, and
ValidationError. The error carries Check, Findings, and Cause, implements
Error() string and Unwrap() error, and is returned as a pointer. Findings
carry Path, Line, Column, and Message, with Target and Directive for
applicable file checks. A nullable FileSizeMeasurement containing
Bytes and LimitBytes supplies structured size details. Each named type
belongs to the root facade, with corresponding internal records and conversions.
Use check identities go-test, go-vet, package-compile, go-format,
markdown-links, workspace-files, local-replacements, and file-size as
their checks land. Findings use repository-relative paths and one-based source
locations where available; zero locations mean unavailable. Human-readable
messages supply detail without a promise of exact wording.
The shared error represents a failed validation check and preserves its underlying cause. Candidate guards, artifact failures, and publication outcomes retain their existing classification. Existing additional-check errors retain their cause and current wrapping; custom callbacks can return facade errors without losing their identity. Successful out-of-scope Markdown observations will use the existing output writer when that feature lands, leaving successful validation successful.
Committed-file access
Provide an internal candidate-tree reader using the captured commit. Enumerate the full tree with NUL-delimited Git output and record path, object ID, entry kind/mode, and blob size where available. Read blob bytes by object ID, preserving binary bytes and filenames containing spaces, tabs, or newlines. Cache reusable metadata within the validation operation; read contents when needed.
Regular tracked files include executable files, vendor contents, and nested modules. The inventory applies no feature-specific exclusion. Symlinks expose their committed link text as blob contents. Gitlinks expose the recorded submodule commit and carry no parent-tree file size; the reader enumerates the parent candidate's committed tree. Traversing a submodule checkout would inspect different source and require a separately defined capability.
Formatting and Markdown source parsing operate on regular source blobs. File size can measure regular and symlink blobs. Supply an internal path-resolution helper for future Markdown destinations that resolves symlink text within the candidate tree, classifies destinations outside the checkout, and reports missing entries or cycles. A submodule's contents are outside the parent candidate's inspected tree. The Markdown feature owns whether a resulting observation is a finding or an out-of-scope report under its selected rule. The helpers read neither symlink targets nor submodule contents from the live filesystem. Their tests use synthetic trees and local Git repositories.
Stage 1: Extract the shared validation executor
Completion: Complete. The default Go suite now runs through
internal/validation, candidate capture and callbacks remain in
internal/candidate, and Check and Publish share the public callback
adapter.
Outcome: One internal owner runs current default validation, with current candidate and publication behavior preserved.
- Inspect
internal/candidate/check.go,internal/publish/publish.go,internal/gotool, and their existing tests. Record the capture, validation, additional-check, build, and recheck boundaries in implementation notes. - Move default Go validation into
internal/validation. Accept context, captured repository/commit, output, and internal settings explicitly. Start with ordinary zero-value settings and the existing default invocation. - Keep
candidate.Validateas the shared bridge used by local checks and publication. Retain additional checks and after-validation builds in their existing responsibilities. Reusegotool.Newand its environment handling. - Consolidate root callback/request conversion where
check.goandpublish.gocurrently duplicate it, using small private helpers.
Verification: Run affected candidate, publication, and facade tests. Add coverage only for gaps in default suite execution, callback ordering, failure stopping before builds/tag creation, and cancellation. Existing publication recovery tests retain ownership of remote-state behavior. Update the internal inventory to reflect the implemented executor.
Stage 2: Introduce internal validation diagnostics
Completion: Complete. Shared Go setup and test failures carry the internal
go-test identity and preserve their underlying errors, including cancellation
and subprocess exit errors.
Outcome: The executor returns a small structured failure with an underlying cause, independently of public error conversion.
- Add internal check identity, finding, measurement, and failure records to
internal/validation, corresponding to the diagnostic design above. - Attribute Go setup/test failures to the base test check. Preserve process
errors and canceled contexts through
Unwrapand ordinary wrapping. - Keep candidate commit context in the candidate wrapper. Keep additional callback errors and artifact failures in their current flow.
- Define one internal formatting routine for the failure's useful summary; avoid duplicating diagnostic prose at each call site.
Verification: Protect errors.Is for cancellation and sentinel causes,
errors.As for subprocess errors, and distinguish shared failures from callback
and artifact failures. Test semantic fields and cause preservation rather than
complete error text.
Stage 3: Add committed candidate-file enumeration
Completion: Complete. internal/git enumerates committed tree entries and
blob sizes, while internal/validation exposes a lazy candidate-tree reader
that serves blob bytes by object ID.
Outcome: File-oriented validators can share an offline reader bound to the captured commit.
- Add Git tree/blob operations to the existing internal Git boundary and a
small candidate-tree reader used by
internal/validation. Select a separate internal package only if it clarifies that responsibility. - Implement enumeration and lazy blob reads according to the committed-file design. Bind queries to the captured commit and preserve Git/process causes.
- Represent regular files, symlinks, and submodules explicitly. Keep feature filtering in the respective future checker.
- Make the reader available to the executor for selected file checks. Default Go-only validation does not need to load all repository blobs.
Verification: Use real temporary Git repositories to show that bytes and sizes come from the selected commit despite changed worktree files, that unusual filenames survive enumeration, and that vendor/nested-module files are present. Include symlink and gitlink entries without requiring a network or neighboring checkout. Confirm inspection leaves repository contents unchanged.
Stage 4: Add candidate destination-resolution primitives
Outcome: Future Markdown validation can resolve committed local destinations and classify checkout boundaries without filesystem traversal.
- Implement a small internal resolver for repository-relative destinations against the candidate inventory. Keep URL decoding and Markdown parsing in the later Markdown capability.
- Resolve committed symlink text relative to its containing path. Distinguish existing entries, missing entries, outside-checkout paths, submodule contents, and symlink cycles. Classification is data for the caller.
- Preserve ordinary directory destinations where the committed tree contains descendants. Return the resolved identity needed for future fragment lookup.
- Keep inspection errors separate from normal resolution observations.
Verification: Cover relative paths, parent traversal within and outside the root, existing directories, symlinks pointing inside/outside the root, cycles, and submodule traversal. Show identical classifications with neighboring files present and absent. These tests own path resolution; later Markdown tests own syntax, anchors, and selected failure policy.
Stage 5: Add facade declarations and shared request conversion
Outcome: The public model has an agreed representation, shared conversion,
and cause-preserving errors across Check and Publish.
- Implement the public diagnostic types now and retain
the complete specification above as the blueprint for capability stages.
Introduce
ReleaseRequest.Validation,ValidationSpec, and supported settings with the first capability implementation in subsequent feature work. - Define internal counterparts for the intended settings where required by actual coordination code. Use explicit field/target conversions through the same root helper for both operations. Add remaining fields as used.
- Convert internal failures into facade-owned
*ValidationError, using typed recognition and explicit diagnostic-field conversion. Retain the original wrapped error as the cause so candidate context, underlying identity, anderrors.Is/errors.Asbehavior survive the conversion. Render the useful failure once rather than repeating the same context at each wrapper. - Keep source publication result conversion unchanged. Extend GoDoc for the errors and any implemented settings, with links to current consumer guidance.
- Extend compiled public API checks for facade-owned named types and dependency
direction. Preserve the small independent
buildinfodependency graph.
Verification: External-package tests cover typed error access and
errors.Is/errors.As through both facades. Reuse existing integration fixtures
for a failing suite and an additional-check cause; avoid a new generic test
framework. Run go run ./internal/apicheck.
Stage 6: Establish equivalent profile mapping and CLI presentation
Outcome: Profile conversion has one owner and the CLI reports structured validation failures consistently while retaining current exit classification.
- Inspect
cmd/releasekit/internal/cli/config.goandcmd/releasekit/main.go. Share profile-to-request conversion for check and publication through a small command-layer helper. Preserve additional-command argument/environment handling and build/delivery mapping. - Under the incremental rollout, add each
validationsetting with its capability. Use CLI-owned JSON structs and explicit conversion at the command boundary. Reuse ordinary JSON decoding and existing unknown-field rejection. The schema blueprint above guides later additions; profile settings acquire behavior at the same time as their Go counterparts. - Add shared CLI presentation for facade validation errors. Show check identity, useful findings and cause context once. Retain publication phase/state and uncertain-outcome reporting. Present validation failures through the existing status 1 path and malformed profile settings through the existing status 2 path; preserve other statuses.
- Update current CLI documentation for the diagnostic behavior that has landed. Update configuration documentation only for implemented settings.
Verification: Exercise default/omitted settings and existing additional commands. Check useful diagnostics and exit classifications for both operations, including a publication validation failure before tag creation. Capability work will add setting-specific mapping/equivalence cases at this established boundary.
Stage 7: Record capability integration recipes and consumer shapes
Outcome: Subsequent feature prompts can use the foundation without redesigning its ownership or inventing a public extension framework.
- Add focused internal guidance for adding a typed setting, facade/profile conversion, ordinary checker function, identity/findings, and behavioral coverage through the shared executor. Update the inventory and link to that owner for mechanics.
- Record the Go coordination recipe: construct one test command with selected flags; select native full vet when requested; use static package compilation for declared targets after test/vet. Reuse target/environment handling while keeping artifact metadata and version smoke tests in the builder.
- Record how file checks consume the candidate reader and return findings. Keep individual rule semantics in the release roadmap until implemented. Markdown adopts PromptKit's current conventions as its starting specification; parser/anchor implementation belongs to the Markdown feature prompt.
- Add prospective Go/profile examples under this roadmap for the three consumer
shapes: Audita with omitted optional validation and existing artifact builds;
ConfigKit with race, full vet, and two Linux compilation targets; PromptKit
with shared selections plus substantive example assertions in
checks. Label prospective fields clearly. Maintained copyable consumer examples gain selections when those selections are implemented. - Compare those declarations with local consumer scripts and profiles read-only. Record which duplicated mechanics each later capability removes. ConfigKit's separate toolchain-support commitments remain consumer compatibility decisions.
Verification: Check the proposed declaration pairs against the schema design
and the ownership table in model.md. Confirm product checks still fit the
existing callback/profile extension point. Runtime tests are self-contained and
independent of the three sibling checkouts.
Stage 8: Verify and document the completed foundation
Outcome: The enabling refactor is complete, its current boundary is clear, and later feature implementation can proceed through the shared model.
- Review the assembled request flow, cause conversions, candidate inspection, CLI presentation, and facade imports against ADR 0003 and the architecture policy. Address concrete defects within the stages' scope.
- Run
go test ./...andgo run ./internal/apicheckfrom the repository root. Add focused integration coverage only for a remaining risk, such as default behavior diverging between API/profile or check/publication paths. - Update current GoDoc, consumer guidance, CLI reference, and internal owners for implemented behavior. Keep future setting examples and pending capability contracts in roadmaps. Check local links and Markdown whitespace.
- Update
model.mdand this plan's status to identify the completed foundation and the individual capability work remaining inv0.2.0.md. A foundation completion is distinct from a completed v0.2.0 release. - Commit the completed unit and report its verification, remaining capability work, and clean or preserved worktree state. The subsequent architecture review and testing/next-step decisions belong to the user after this sequence.