# Validation model implementation plan Status: In progress; shared execution, committed-file inspection, destination resolution, and facade diagnostics are complete. This plan implements the enabling work in [model.md](model.md), guided by [ADR 0003](../adr/0003-declarative-validation.md). It is written for a Luna coding agent executing one stage per prompt, in numerical order. The [v0.2.0 roadmap](v0.2.0.md) 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](../development.md), the [agent policy](../policy/agents.md), 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 `buildinfo` facade 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: ```go 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/candidate` retains capture and rechecks. Its existing `Validate` entry point delegates shared validation to `internal/validation` and then invokes `AdditionalChecks` once with the captured candidate. - `internal/validation` owns 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. `Check` and `Publish` share those helpers. The CLI maps its own JSON settings to the root facade in `cmd/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=off` to testing and a separate vet invocation. Package compilation uses static target mode with `CGO_ENABLED=0`, independently of artifact declarations. - Preserve fail-stop behavior, context propagation, candidate rechecks, local `Check` behavior, 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. 1. 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. 2. 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. 3. Keep `candidate.Validate` as the shared bridge used by local checks and publication. Retain additional checks and after-validation builds in their existing responsibilities. Reuse `gotool.New` and its environment handling. 4. Consolidate root callback/request conversion where `check.go` and `publish.go` currently 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. 1. Add internal check identity, finding, measurement, and failure records to `internal/validation`, corresponding to the diagnostic design above. 2. Attribute Go setup/test failures to the base test check. Preserve process errors and canceled contexts through `Unwrap` and ordinary wrapping. 3. Keep candidate commit context in the candidate wrapper. Keep additional callback errors and artifact failures in their current flow. 4. 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. 1. 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. 2. Implement enumeration and lazy blob reads according to the committed-file design. Bind queries to the captured commit and preserve Git/process causes. 3. Represent regular files, symlinks, and submodules explicitly. Keep feature filtering in the respective future checker. 4. 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 **Completion:** Complete. The candidate tree resolves committed relative paths and symlinks, preserving directory identities and classifying missing, outside-checkout, submodule, and cycle observations without reading the live filesystem. **Outcome:** Future Markdown validation can resolve committed local destinations and classify checkout boundaries without filesystem traversal. 1. Implement a small internal resolver for repository-relative destinations against the candidate inventory. Keep URL decoding and Markdown parsing in the later Markdown capability. 2. 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. 3. Preserve ordinary directory destinations where the committed tree contains descendants. Return the resolved identity needed for future fragment lookup. 4. 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 **Completion:** Complete. The root facade exposes its own validation diagnostic types, and both `Check` and `Publish` convert shared failures while preserving candidate context and the original error chain. **Outcome:** The public model has an agreed representation, shared conversion, and cause-preserving errors across `Check` and `Publish`. 1. 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. 2. 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. 3. 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, and `errors.Is`/`errors.As` behavior survive the conversion. Render the useful failure once rather than repeating the same context at each wrapper. 4. Keep source publication result conversion unchanged. Extend GoDoc for the errors and any implemented settings, with links to current consumer guidance. 5. Extend compiled public API checks for facade-owned named types and dependency direction. Preserve the small independent `buildinfo` dependency 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. 1. Inspect `cmd/releasekit/internal/cli/config.go` and `cmd/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. 2. Under the incremental rollout, add each `validation` setting 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. 3. 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. 4. 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. 1. 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. 2. 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. 3. 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. 4. 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. 5. 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. 1. 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. 2. Run `go test ./...` and `go run ./internal/apicheck` from the repository root. Add focused integration coverage only for a remaining risk, such as default behavior diverging between API/profile or check/publication paths. 3. 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. 4. Update `model.md` and this plan's status to identify the completed foundation and the individual capability work remaining in `v0.2.0.md`. A foundation completion is distinct from a completed v0.2.0 release. 5. 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.