Allow composition within command internal helpers

This commit is contained in:
2026-10-06 01:08:34 +00:00
parent 791152370c
commit ce3a85e3e6
3 changed files with 51 additions and 3 deletions

View File

@@ -358,6 +358,8 @@ fetch endpoint reports absence. Focused publication/wrapper tests pass.
### R10 — Medium: The boundary checker bans valid command-internal composition
Status: Resolved.
Category: Inadvertently strengthened architectural prohibition.
**Evidence:** [checkImportDirections](../../internal/apicheck/main.go), around
@@ -381,6 +383,12 @@ turning current package inventory into a fixed future architecture.
internal imports of command code and internal-helper imports of public facades
remain rejected.
**Resolution:** The import checker identifies each executable’s internal helper
subtree and permits collaboration inside it. Library-to-command imports,
helper-to-controller imports, and internal-to-facade imports remain rejected.
Positive and negative dependency fixtures and the actual compiled repository
boundary checks pass.
## Remediation boundaries
Address these findings before treating the initial implementation as complete

View File

@@ -112,7 +112,10 @@ func checkImportDirections(packages []listedPackage) error {
continue
}
for _, imported := range pkg.Imports {
if imported == modulePath || imported == modulePath+"/buildinfo" || strings.HasPrefix(imported, modulePath+"/cmd/") {
commandDependency := strings.HasPrefix(imported, modulePath+"/cmd/")
owner := commandInternalRoot(pkg.ImportPath)
ownHelper := owner != "" && strings.HasPrefix(imported+"/", owner)
if imported == modulePath || imported == modulePath+"/buildinfo" || commandDependency && !ownHelper {
violations = append(violations, fmt.Sprintf("internal package %s imports public or command package %s", pkg.ImportPath, imported))
}
}
@@ -150,8 +153,21 @@ func checkImportDirections(packages []listedPackage) error {
}
func isInternalPackage(path string) bool {
return strings.HasPrefix(path, modulePath+"/internal/") ||
strings.HasPrefix(path, modulePath+"/") && strings.Contains(strings.TrimPrefix(path, modulePath), "/internal/")
return strings.HasPrefix(path, modulePath+"/") &&
(strings.Contains(strings.TrimPrefix(path, modulePath), "/internal/") || strings.HasSuffix(path, "/internal"))
}
// commandInternalRoot identifies the helper subtree owned by one executable.
func commandInternalRoot(path string) string {
if !strings.HasPrefix(path, modulePath+"/cmd/") {
return ""
}
relative := strings.TrimPrefix(path, modulePath+"/cmd/")
owner, _, ok := strings.Cut(relative+"/", "/internal/")
if !ok {
return ""
}
return modulePath + "/cmd/" + owner + "/internal/"
}
func forbiddenRuntimeDependency(path string) bool {

View File

@@ -182,3 +182,27 @@ func typecheckWithImporter(t *testing.T, fset *token.FileSet, path, source strin
}
return pkg
}
func TestCommandInternalCompositionPreservesLibraryBoundaries(t *testing.T) {
for _, test := range []struct {
name, from, to string
reject bool
}{
{"command helpers", "/cmd/releasekit/internal/cli/parser", "/cmd/releasekit/internal/cli/settings", false},
{"other executable helpers", "/cmd/future/internal/parser", "/cmd/future/internal/settings", false},
{"helper root", "/cmd/releasekit/internal", "/cmd/releasekit/internal/cli", false},
{"controller uses facade", "/cmd/releasekit", "", false},
{"library imports controller", "/internal/build", "/cmd/releasekit", true},
{"library imports helper", "/internal/build", "/cmd/releasekit/internal/cli", true},
{"helper imports facade", "/cmd/releasekit/internal/cli", "", true},
{"helper imports runtime facade", "/cmd/releasekit/internal/cli", "/buildinfo", true},
{"helper imports controller", "/cmd/releasekit/internal/cli", "/cmd/releasekit", true},
} {
t.Run(test.name, func(t *testing.T) {
err := checkImportDirections([]listedPackage{{ImportPath: modulePath + test.from, Imports: []string{modulePath + test.to}}})
if (err != nil) != test.reject {
t.Fatalf("dependency %s -> %s: %v", test.from, test.to, err)
}
})
}
}