Rename the candidate file validator to forbidden files

This commit is contained in:
2026-11-01 16:02:51 +00:00
parent 792bfc4eca
commit ac78e7db73
16 changed files with 39 additions and 39 deletions

View File

@@ -59,7 +59,7 @@ type ReleaseRequest struct {
// module and shared-file inputs. Independent nested modules form automatic // module and shared-file inputs. Independent nested modules form automatic
// input boundaries; vendored files remain included. Referenced destinations // input boundaries; vendored files remain included. Referenced destinations
// remain available for targeted inspection without expanding the input set. // remain available for targeted inspection without expanding the input set.
// UntrackedFiles checks explicitly selected repository paths independently of // ForbiddenFiles checks explicitly selected repository paths independently of
// those file inputs. // those file inputs.
type ValidationSpec struct { type ValidationSpec struct {
// SharedFiles adds repository-relative committed files to optional file // SharedFiles adds repository-relative committed files to optional file
@@ -68,10 +68,10 @@ type ValidationSpec struct {
// Explicit files replace the defaults and must exist even when file checks // Explicit files replace the defaults and must exist even when file checks
// are off. // are off.
SharedFiles []string SharedFiles []string
// UntrackedFiles lists literal repository-relative file paths that must be // ForbiddenFiles lists literal repository-relative file paths that must be
// absent from the captured commit, independently of module/shared inputs. // absent from the captured commit, independently of module/shared inputs.
// An empty list disables the check. File contents and history are not read. // An empty list disables the check. File contents and history are not read.
UntrackedFiles []string ForbiddenFiles []string
Go GoValidationSpec Go GoValidationSpec
// GoFormat checks the formatting of committed regular Go source files. It // GoFormat checks the formatting of committed regular Go source files. It
// does not modify repository contents and is disabled when false. // does not modify repository contents and is disabled when false.
@@ -143,7 +143,7 @@ type GoValidationSpec struct {
// of test-time vet, and Validation.Go.CompileTargets compiles all packages for // of test-time vet, and Validation.Go.CompileTargets compiles all packages for
// each selected GOOS/GOARCH pair after tests and vet with cgo disabled. Package // each selected GOOS/GOARCH pair after tests and vet with cgo disabled. Package
// compilation produces no artifacts and does not execute target binaries. // compilation produces no artifacts and does not execute target binaries.
// Validation.UntrackedFiles checks that selected repository-relative files are // Validation.ForbiddenFiles checks that selected repository-relative files are
// absent from the captured commit after Go checks and before other file checks. // absent from the captured commit after Go checks and before other file checks.
// Validation.GoFormat checks regular committed Go source after those selected // Validation.GoFormat checks regular committed Go source after those selected
// Go checks and before AdditionalChecks. Its zero value disables formatting. // Go checks and before AdditionalChecks. Its zero value disables formatting.
@@ -197,7 +197,7 @@ func candidateRequest(request ReleaseRequest) candidate.Request {
FullVet: request.Validation.Go.FullVet, FullVet: request.Validation.Go.FullVet,
CompileTargets: make([]validation.Target, len(request.Validation.Go.CompileTargets)), CompileTargets: make([]validation.Target, len(request.Validation.Go.CompileTargets)),
SharedFiles: copyStringSlice(request.Validation.SharedFiles), SharedFiles: copyStringSlice(request.Validation.SharedFiles),
UntrackedFiles: copyStringSlice(request.Validation.UntrackedFiles), ForbiddenFiles: copyStringSlice(request.Validation.ForbiddenFiles),
GoFormat: request.Validation.GoFormat, GoFormat: request.Validation.GoFormat,
Markdown: internalMarkdownSettings(request.Validation.Markdown), Markdown: internalMarkdownSettings(request.Validation.Markdown),
ModuleDistribution: validation.ModuleDistributionSettings{ ModuleDistribution: validation.ModuleDistributionSettings{

View File

@@ -154,7 +154,7 @@ type ValidationSettings struct {
// SharedFiles selects committed files relative to the repository root. // SharedFiles selects committed files relative to the repository root.
// Nil uses optional defaults; a non-nil empty slice selects no shared files. // Nil uses optional defaults; a non-nil empty slice selects no shared files.
SharedFiles []string `json:"shared_files"` SharedFiles []string `json:"shared_files"`
UntrackedFiles []string `json:"untracked_files,omitempty"` ForbiddenFiles []string `json:"forbidden_files,omitempty"`
Go GoValidationSettings `json:"go,omitempty"` Go GoValidationSettings `json:"go,omitempty"`
// GoFormat checks the formatting of committed regular Go source files. // GoFormat checks the formatting of committed regular Go source files.
// Omitted or false disables the check. // Omitted or false disables the check.

View File

@@ -182,7 +182,7 @@ func validationSpec(settings *cli.ValidationSettings) releasekit.ValidationSpec
} }
result := releasekit.ValidationSpec{ result := releasekit.ValidationSpec{
SharedFiles: copyStringsPreservingEmpty(settings.SharedFiles), SharedFiles: copyStringsPreservingEmpty(settings.SharedFiles),
UntrackedFiles: copyStringsPreservingEmpty(settings.UntrackedFiles), ForbiddenFiles: copyStringsPreservingEmpty(settings.ForbiddenFiles),
GoFormat: settings.GoFormat, GoFormat: settings.GoFormat,
ModuleDistribution: releasekit.ModuleDistributionValidationSpec{ ModuleDistribution: releasekit.ModuleDistributionValidationSpec{
WorkspaceFiles: settings.ModuleDistribution.WorkspaceFiles, WorkspaceFiles: settings.ModuleDistribution.WorkspaceFiles,

View File

@@ -53,11 +53,11 @@ func TestDeliveryTokenFileSelection(t *testing.T) {
} }
} }
func TestCheckReadsUntrackedFilesSelectionFromProfile(t *testing.T) { func TestCheckReadsForbiddenFilesSelectionFromProfile(t *testing.T) {
repository := newCLIRepository(t) repository := newCLIRepository(t)
writeDeliveryProfile(t, repository, cli.Settings{ writeDeliveryProfile(t, repository, cli.Settings{
Product: "Example Product", Product: "Example Product",
Validation: &cli.ValidationSettings{UntrackedFiles: []string{"token-file"}}, Validation: &cli.ValidationSettings{ForbiddenFiles: []string{"token-file"}},
Delivery: &cli.DeliverySettings{BaseURL: "https://gitea.example", Owner: "owner", Repository: "example", TokenFile: "missing-token-file"}, Delivery: &cli.DeliverySettings{BaseURL: "https://gitea.example", Owner: "owner", Repository: "example", TokenFile: "missing-token-file"},
}) })
writeRepositoryFile(t, repository, "token-file", "fixture-secret\n") writeRepositoryFile(t, repository, "token-file", "fixture-secret\n")
@@ -65,7 +65,7 @@ func TestCheckReadsUntrackedFilesSelectionFromProfile(t *testing.T) {
cliGitRun(t, repository, "commit", "-m", "capture a selected token path") cliGitRun(t, repository, "commit", "-m", "capture a selected token path")
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
status := run(context.Background(), []string{"check", "--repo", repository, "v1.2.3"}, &stdout, &stderr) status := run(context.Background(), []string{"check", "--repo", repository, "v1.2.3"}, &stdout, &stderr)
if status != 1 || !strings.Contains(stderr.String(), "untracked-files") || !strings.Contains(stderr.String(), "token-file") { if status != 1 || !strings.Contains(stderr.String(), "forbidden-files") || !strings.Contains(stderr.String(), "token-file") {
t.Fatalf("check status=%d output=%s diagnostics=%s", status, &stdout, &stderr) t.Fatalf("check status=%d output=%s diagnostics=%s", status, &stdout, &stderr)
} }
if strings.Contains(stdout.String()+stderr.String(), "fixture-secret") { if strings.Contains(stdout.String()+stderr.String(), "fixture-secret") {

View File

@@ -141,9 +141,9 @@ symlink size is the committed link text. Gitlinks have no measured size. Only
the captured candidate is checked, one offending file is reported at a time, the captured candidate is checked, one offending file is reported at a time,
and historical or aggregate repository size is not measured. and historical or aggregate repository size is not measured.
`validation.untracked_files` is an optional list of literal repository-relative `validation.forbidden_files` is an optional list of literal repository-relative
file paths that must be absent from the captured commit. It runs after the Go file paths that must be absent from the captured commit. It runs after the Go
checks and before other file checks, using the `untracked-files` identity and checks and before other file checks, using the `forbidden-files` identity and
reporting selected committed paths. Omission, `null`, or `[]` disables it. reporting selected committed paths. Omission, `null`, or `[]` disables it.
Paths use slash separators and ordinary relative normalization and must stay Paths use slash separators and ordinary relative normalization and must stay
within the repository. They are checked against the whole captured repository, within the repository. They are checked against the whole captured repository,
@@ -166,7 +166,7 @@ the absence of both credential sources, returns status 2. Omission of
Credential loading runs only for `deliver` and `ci-deliver`. `check`, `publish`, Credential loading runs only for `deliver` and `ci-deliver`. `check`, `publish`,
and `build` do not read the token file or require a token. The CLI passes the and `build` do not read the token file or require a token. The CLI passes the
resolved value explicitly to the library operation. Selecting a token file and resolved value explicitly to the library operation. Selecting a token file and
selecting the optional untracked-file validator are independent settings. selecting the optional forbidden-file validator are independent settings.
`expected_push_url`, when nonempty, is compared exactly with Git's resolved `expected_push_url`, when nonempty, is compared exactly with Git's resolved
effective push URL during `publish`, before candidate validation. It applies effective push URL during `publish`, before candidate validation. It applies

View File

@@ -98,9 +98,9 @@ The `Candidate` supplies both `RepositoryDir` and `ModuleDir`, plus the declared
module-local product checks. ReleaseKit calls `AdditionalChecks` once after module-local product checks. ReleaseKit calls `AdditionalChecks` once after
shared validation when supplied. shared validation when supplied.
For local token files, set `Validation.UntrackedFiles` to the repository-relative For local token files, set `Validation.ForbiddenFiles` to the repository-relative
paths that should stay out of the committed candidate, such as paths that should stay out of the committed candidate, such as
`[]string{".releasekit/gitea-token"}`. Failures use `untracked-files` and carry the `[]string{".releasekit/gitea-token"}`. Failures use `forbidden-files` and carry the
selected committed paths in `ValidationError.Findings`. This selection is selected committed paths in `ValidationError.Findings`. This selection is
independent of module and shared-file inputs; see [GoDoc](../../check.go) for independent of module and shared-file inputs; see [GoDoc](../../check.go) for
the API contract and the [configuration reference](../config.md) for CLI use. the API contract and the [configuration reference](../config.md) for CLI use.

View File

@@ -90,7 +90,7 @@ as `.releasekit/gitea-token` and configure:
{ {
"product": "Example", "product": "Example",
"validation": { "validation": {
"untracked_files": [".releasekit/gitea-token"] "forbidden_files": [".releasekit/gitea-token"]
}, },
"delivery": { "delivery": {
"base_url": "https://gitea.example", "base_url": "https://gitea.example",

View File

@@ -59,9 +59,9 @@ workspace-file check using the same candidate inventory. Resolved module bodies
are parsed once per blob object ID; only owned filesystem-replacement targets are parsed once per blob object ID; only owned filesystem-replacement targets
and line numbers remain cached. Each declaration path receives findings from and line numbers remain cached. Each declaration path receives findings from
that summary in its own path context. that summary in its own path context.
The optional untracked-file check uses the captured tree's full path lookup The optional forbidden-file check uses the captured tree's full path lookup
before the other file checks. It normalizes the explicitly selected before the other file checks. It normalizes the explicitly selected
repository-relative names and reports matching entries through `untracked-files` repository-relative names and reports matching entries through `forbidden-files`
without loading their blobs. Candidate-tree cleanup covers this failure path. without loading their blobs. Candidate-tree cleanup covers this failure path.
The optional file-size check applies normalized literal file/directory The optional file-size check applies normalized literal file/directory
exclusions with path-boundary matching, then requests local Git sizes for the exclusions with path-boundary matching, then requests local Git sizes for the

View File

@@ -18,7 +18,7 @@ For local hosted-page delivery, save a Gitea personal access token in
`.releasekit/gitea-token` with owner-only file permissions. The file contains `.releasekit/gitea-token` with owner-only file permissions. The file contains
only the token. ReleaseKit's profile selects this file as a fallback to only the token. ReleaseKit's profile selects this file as a fallback to
`RELEASEKIT_GITEA_TOKEN`; `.releasekit/` is already ignored. Its optional `RELEASEKIT_GITEA_TOKEN`; `.releasekit/` is already ignored. Its optional
`validation.untracked_files` selection checks that this path is absent from the `validation.forbidden_files` selection checks that this path is absent from the
committed candidate. Source publication requires no delivery token. The committed candidate. Source publication requires no delivery token. The
[configuration reference](config.md) owns credential selection and validator [configuration reference](config.md) owns credential selection and validator
semantics. semantics.

View File

@@ -27,8 +27,8 @@ const (
MarkdownLinks Check = "markdown-links" MarkdownLinks Check = "markdown-links"
// SharedFiles identifies a requested shared file that is unavailable. // SharedFiles identifies a requested shared file that is unavailable.
SharedFiles Check = "shared-files" SharedFiles Check = "shared-files"
// UntrackedFiles identifies selected paths present in the committed source. // ForbiddenFiles identifies selected paths present in the committed source.
UntrackedFiles Check = "untracked-files" ForbiddenFiles Check = "forbidden-files"
) )
// Finding describes a validation observation at an optional source location. // Finding describes a validation observation at an optional source location.

View File

@@ -5,7 +5,7 @@ import (
"strings" "strings"
) )
func checkUntrackedFiles(tree *CandidateTree, names []string) error { func checkForbiddenFiles(tree *CandidateTree, names []string) error {
var findings []Finding var findings []Finding
for _, name := range names { for _, name := range names {
clean := path.Clean(name) clean := path.Clean(name)
@@ -20,5 +20,5 @@ func checkUntrackedFiles(tree *CandidateTree, names []string) error {
if len(findings) == 0 { if len(findings) == 0 {
return nil return nil
} }
return &Failure{Check: UntrackedFiles, Findings: findings} return &Failure{Check: ForbiddenFiles, Findings: findings}
} }

View File

@@ -8,7 +8,7 @@ import (
"testing" "testing"
) )
func TestUntrackedFilesUsesCapturedRepositoryPaths(t *testing.T) { func TestForbiddenFilesUsesCapturedRepositoryPaths(t *testing.T) {
repository := newTreeRepository(t) repository := newTreeRepository(t)
writeTreeFile(t, repository, "go.mod", []byte("module example.com/root\n")) writeTreeFile(t, repository, "go.mod", []byte("module example.com/root\n"))
writeTreeFile(t, repository, "nested/go.mod", []byte("module example.com/nested\n")) writeTreeFile(t, repository, "nested/go.mod", []byte("module example.com/nested\n"))
@@ -27,21 +27,21 @@ func TestUntrackedFilesUsesCapturedRepositoryPaths(t *testing.T) {
if err := Run(context.Background(), request); err != nil { if err := Run(context.Background(), request); err != nil {
t.Fatal(err) t.Fatal(err)
} }
request.Settings.UntrackedFiles = []string{".releasekit/token", "absent-token"} request.Settings.ForbiddenFiles = []string{".releasekit/token", "absent-token"}
if err := Run(context.Background(), request); err != nil { if err := Run(context.Background(), request); err != nil {
t.Fatal(err) t.Fatal(err)
} }
request.Settings.UntrackedFiles = []string{"nested/./token"} request.Settings.ForbiddenFiles = []string{"nested/./token"}
err := Run(context.Background(), request) err := Run(context.Background(), request)
failure, ok := err.(*Failure) failure, ok := err.(*Failure)
if !ok || failure.Check != UntrackedFiles || len(failure.Findings) != 1 || failure.Findings[0].Path != "nested/token" { if !ok || failure.Check != ForbiddenFiles || len(failure.Findings) != 1 || failure.Findings[0].Path != "nested/token" {
t.Fatalf("untracked-files error = %v, want committed nested path despite worktree deletion", err) t.Fatalf("forbidden-files error = %v, want committed nested path despite worktree deletion", err)
} }
if strings.Contains(err.Error(), "fixture-secret") { if strings.Contains(err.Error(), "fixture-secret") {
t.Fatal("validation exposed committed token bytes") t.Fatal("validation exposed committed token bytes")
} }
for _, invalid := range []string{"", "../token", "/token"} { for _, invalid := range []string{"", "../token", "/token"} {
request.Settings.UntrackedFiles = []string{invalid} request.Settings.ForbiddenFiles = []string{invalid}
if err := Run(context.Background(), request); err == nil { if err := Run(context.Background(), request); err == nil {
t.Fatalf("invalid path %q was accepted", invalid) t.Fatalf("invalid path %q was accepted", invalid)
} }

View File

@@ -21,7 +21,7 @@ type Settings struct {
FullVet bool FullVet bool
CompileTargets []Target CompileTargets []Target
SharedFiles []string SharedFiles []string
UntrackedFiles []string ForbiddenFiles []string
GoFormat bool GoFormat bool
Markdown *MarkdownSettings Markdown *MarkdownSettings
ModuleDistribution ModuleDistributionSettings ModuleDistribution ModuleDistributionSettings
@@ -135,7 +135,7 @@ func Run(ctx context.Context, request Request) (result error) {
} }
} }
needsSharedSelection := len(request.Settings.SharedFiles) != 0 needsSharedSelection := len(request.Settings.SharedFiles) != 0
if request.Settings.GoFormat || request.Settings.ModuleDistribution.WorkspaceFiles || request.Settings.ModuleDistribution.LocalReplacements || request.Settings.FileSize != nil || request.Settings.Markdown != nil || needsSharedSelection || len(request.Settings.UntrackedFiles) != 0 { if request.Settings.GoFormat || request.Settings.ModuleDistribution.WorkspaceFiles || request.Settings.ModuleDistribution.LocalReplacements || request.Settings.FileSize != nil || request.Settings.Markdown != nil || needsSharedSelection || len(request.Settings.ForbiddenFiles) != 0 {
tree, err := request.ReadCandidateTree(ctx) tree, err := request.ReadCandidateTree(ctx)
if err != nil { if err != nil {
var failure *Failure var failure *Failure
@@ -155,8 +155,8 @@ func Run(ctx context.Context, request Request) (result error) {
if request.Settings.GoFormat { if request.Settings.GoFormat {
check = GoFormat check = GoFormat
} }
if len(request.Settings.UntrackedFiles) != 0 { if len(request.Settings.ForbiddenFiles) != 0 {
check = UntrackedFiles check = ForbiddenFiles
} }
return &Failure{Check: check, Cause: err} return &Failure{Check: check, Cause: err}
} }
@@ -170,7 +170,7 @@ func Run(ctx context.Context, request Request) (result error) {
} }
} }
}() }()
if err := checkUntrackedFiles(tree, request.Settings.UntrackedFiles); err != nil { if err := checkForbiddenFiles(tree, request.Settings.ForbiddenFiles); err != nil {
return err return err
} }
if request.Settings.GoFormat { if request.Settings.GoFormat {

View File

@@ -2,7 +2,7 @@
"product": "ReleaseKit", "product": "ReleaseKit",
"remote": "origin", "remote": "origin",
"validation": { "validation": {
"untracked_files": [".releasekit/gitea-token"] "forbidden_files": [".releasekit/gitea-token"]
}, },
"delivery": { "delivery": {
"base_url": "https://forge.maximumdirect.net", "base_url": "https://forge.maximumdirect.net",

View File

@@ -16,7 +16,7 @@ func TestCommittedTokenFilePreventsCheckAndPublication(t *testing.T) {
gitRun(t, repo.work, "add", "local-token") gitRun(t, repo.work, "add", "local-token")
gitRun(t, repo.work, "commit", "-m", "capture token path") gitRun(t, repo.work, "commit", "-m", "capture token path")
request := releasekit.ReleaseRequest{RepositoryDir: repo.work, Product: "Sample App", Version: "v1.2.3", request := releasekit.ReleaseRequest{RepositoryDir: repo.work, Product: "Sample App", Version: "v1.2.3",
Validation: releasekit.ValidationSpec{UntrackedFiles: []string{"local-token"}}, Validation: releasekit.ValidationSpec{ForbiddenFiles: []string{"local-token"}},
} }
var err error var err error
if operation == "check" { if operation == "check" {
@@ -27,8 +27,8 @@ func TestCommittedTokenFilePreventsCheckAndPublication(t *testing.T) {
_, err = releasekit.Publish(context.Background(), request) _, err = releasekit.Publish(context.Background(), request)
} }
var failure *releasekit.ValidationError var failure *releasekit.ValidationError
if !errors.As(err, &failure) || failure.Check != "untracked-files" || len(failure.Findings) != 1 || failure.Findings[0].Path != "local-token" { if !errors.As(err, &failure) || failure.Check != "forbidden-files" || len(failure.Findings) != 1 || failure.Findings[0].Path != "local-token" {
t.Fatalf("operation error=%v, want public untracked-files diagnostic", err) t.Fatalf("operation error=%v, want public forbidden-files diagnostic", err)
} }
if gitText(t, repo.work, "tag", "--list", "v1.2.3") != "" { if gitText(t, repo.work, "tag", "--list", "v1.2.3") != "" {
t.Fatal("token-file validation created a release tag") t.Fatal("token-file validation created a release tag")

View File

@@ -9,7 +9,7 @@ import (
// ValidationCheck identifies a shared validation check that failed. // ValidationCheck identifies a shared validation check that failed.
// Current checks include "go-test", "go-vet", "package-compile", "go-format", // Current checks include "go-test", "go-vet", "package-compile", "go-format",
// "workspace-files", "local-replacements", "untracked-files", "file-size", // "workspace-files", "local-replacements", "forbidden-files", "file-size",
// and "markdown-links". // and "markdown-links".
type ValidationCheck string type ValidationCheck string