Files
ReleaseKit/docs/roadmap/implementation.md

572 lines
27 KiB
Markdown

# Validation model implementation plan
Status: Foundation complete; individual v0.2.0 validation capabilities and
their settings remain future work.
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
**Completion:** Complete. Check and publication share profile conversion, and
the CLI presents typed validation errors through one diagnostic path while
preserving existing status, publication-state, and phase reporting.
**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
**Completion:** Complete. Internal guidance now records setting and checker
ownership, Go command coordination, and candidate-file integration. The
prospective examples below compare the agreed declarations with the read-only
consumer review.
**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.
### Prospective consumer shapes
These examples show the agreed model before its optional settings are
implemented. The `Validation` field and the profile `validation` object are
prospective until their corresponding checkers land.
#### Audita: retain the existing artifact build
The zero-value validation selection preserves current defaults while the
existing Linux application artifacts remain declared separately:
```go
request := releasekit.ReleaseRequest{
Product: "Audita",
Version: version,
Validation: releasekit.ValidationSpec{}, // prospective; zero value keeps current validation
Build: &releasekit.BuildSpec{
MainPackage: "./cmd/audita",
BinaryName: "audita",
Targets: []releasekit.Target{
{OS: "linux", Arch: "amd64"},
{OS: "linux", Arch: "arm64"},
},
},
}
```
Its profile keeps the existing build declaration and omits `validation`:
```json
{
"product": "Audita",
"remote": "origin",
"build": {
"main_package": "./cmd/audita",
"binary_name": "audita",
"targets": ["linux/amd64", "linux/arm64"]
},
"delivery": {
"base_url": "https://gitea.maximumdirect.net",
"owner": "eric",
"repository": "audita",
"token_env": "RELEASEKIT_GITEA_TOKEN"
}
}
```
#### ConfigKit: select release compatibility checks
The declaration expresses its race test, separate full vet, and source-only
Linux compilation commitments without selecting executable artifacts:
```go
request := releasekit.ReleaseRequest{
Product: "ConfigKit",
Version: version,
Validation: releasekit.ValidationSpec{
Go: releasekit.GoValidationSpec{
Race: true,
FullVet: true,
CompileTargets: []releasekit.Target{
{OS: "linux", Arch: "amd64"},
{OS: "linux", Arch: "arm64"},
},
},
},
}
```
```json
{
"product": "ConfigKit",
"validation": {
"go": {
"race": true,
"full_vet": true,
"compile_targets": ["linux/amd64", "linux/arm64"]
}
}
}
```
The consumer's baseline-toolchain requirement remains its compatibility
decision. The selection above describes checks, not a promise to run them with
every Go toolchain.
#### PromptKit: share mechanics and keep example assertions
Shared settings cover the maintained race and vet passes, Go formatting,
checkout-bounded Markdown links, and the two distribution rules. Substantive
example assertions remain in the existing `AdditionalChecks` extension point
or profile `checks`:
```go
request := releasekit.ReleaseRequest{
Product: "Promptkit",
Version: version,
Validation: releasekit.ValidationSpec{
Go: releasekit.GoValidationSpec{Race: true, FullVet: true},
GoFormat: true,
Markdown: &releasekit.MarkdownValidationSpec{EnforceCheckoutBoundary: true},
ModuleDistribution: releasekit.ModuleDistributionValidationSpec{
WorkspaceFiles: true,
LocalReplacements: true,
},
},
AdditionalChecks: checkExamples,
}
```
```json
{
"product": "Promptkit",
"validation": {
"go": {"race": true, "full_vet": true},
"go_format": true,
"markdown": {"enforce_checkout_boundary": true},
"module_distribution": {
"workspace_files": true,
"local_replacements": true
}
},
"checks": [
{"args": ["python3", "scripts/check-examples.py"]}
]
}
```
The example-check command is illustrative of a product-owned check retained
from PromptKit's current release assertions; it is not a file in the reviewed
checkout.
### Read-only consumer review
Reviewed on 2026-10-07. All three local worktrees were clean. The declarations
were compared with Audita `bd62f36`, ConfigKit `dc09959`, and PromptKit
`7310ef3`; no sibling repository was modified.
- Audita's `releasekit.json` already declares its product, remote, Linux binary
build, and Gitea destination. Its nine-line `scripts/release.sh` builds the
module-selected ReleaseKit executable and delegates to it. It has no
additional candidate-validation block to remove; omitting optional
selections preserves its current validation. Module-selected CI bootstrap
remains separate later work.
- ConfigKit's 29-line `scripts/release-check.sh` selects one race test run with
test-time vet disabled, runs full vet, and compiles all packages for Linux
amd64 and arm64. Those invocation and target-loop mechanics correspond to
the shared Go settings. Its 133-line publication script also checks an
expected remote URL and advances `main` before publishing a tag. The
endpoint expectation is a separate publication capability; the main-branch
publication flow remains product-specific. Its `CONFIGKIT_BASELINE_TOOLCHAIN`
requirement covers cached supported Go 1.25 patch releases and remains a
consumer commitment.
- PromptKit's 152-line `scripts/release-check.sh` owns the race suite, separate
vet, tracked-Go formatting check, Markdown link and heading checks,
workspace-file and local-replacement checks, a vendor-directory rejection,
and substantive example output assertions. The shared validation settings
correspond to race, vet, formatting, Markdown, workspace-file, and
replacement mechanics; vendor and example assertions remain product checks.
Its 57-line `scripts/release.sh` also checks release-note sections and
publishes an annotated tag, which remain in the product's release procedure.
The file-size capability has no corresponding local check in these reviewed
scripts. Consumer migrations and measurements of realized code removal remain
outside this implementation plan.
## Stage 8: Verify and document the completed foundation
**Completion:** Complete. `Check` and `Publish` now use one shared conversion
from the facade request into candidate validation. The full Go suite and public
API/dependency check pass, and the roadmap distinguishes the completed
foundation from the optional capabilities still to come.
**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.