29 KiB
Code Quality and Deduplication Audit
1. Executive summary
Overall code quality is strong. The repository has clear package boundaries, good current-behavior documentation, focused adapter packages, and tests close to most implemented behavior. The most important cleanup opportunities are narrow and behavior-preserving rather than architectural.
Top three refactoring targets:
- HTTP upload admission, body staging, and archive validation are split across
internal/appandinternal/ingestin a way that duplicates size and content-type policy and buffers uploads in memory. - Runtime config loading, default config path selection, secret loading, and warning projection are repeated across app entrypoints.
- Run orchestration mixes destination processing, failure aggregation, warning recording, and report event ordering in one large loop, making future changes harder to review safely.
The codebase appears ready for a limited cleanup pass. I do not see a major architectural risk that requires a redesign before the next release.
2. Repository map reviewed
Reviewed policy and current-behavior documentation:
AGENTS.mdREADME.mddocs/policy/architecture.mddocs/policy/development.mddocs/policy/documentation.mddocs/config.mddocs/cli.mddocs/operations.mddocs/troubleshooting.mddocs/internal/*.mddocs/roadmap/http.mddocs/roadmap/implementation.md
Reviewed implementation areas:
cmd/distributor: executable entrypoint.internal/cli: root command,version,run,serve,validate,inspect, andmanifest createparsing.internal/app: run orchestration, configured source diagnostics, backend factory, manifest creation, CLI output, HTTP upload server, upload coordinator, and pipeline coordinator.internal/config: config structs, defaults, validation, quantity parsing, S3/SSH helpers, and secrets resolver.internal/bundle: storage-backed source discovery and validation.pkg/bundle: public manifest model, digest logic, manifest building, local validation, and local bundle writer.internal/storageandinternal/storage/fake: backend interface, path helpers, walk helpers, typed errors, and fake backend.internal/adapters/local,internal/adapters/ssh, andinternal/adapters/s3: runtime storage adapters.internal/ingest: HTTP upload archive staging.internal/publish: destination planning, output selection, link projection, state writing, cleanup, and force replacement.internal/state: destination state parsing, validation, comparison, and JSON projection.internal/transformandinternal/transform/markdown: transform registry and Markdown rendering.internal/link,internal/notify,internal/logging, andinternal/testutil.examples, package tests, and packagetestdata.
Requested areas that are absent as separate packages:
internal/stageinternal/modulesinternal/validatorsinternal/artifactsinternal/manifestinternal/schemainternal/report- public
pkgpackages other thanpkg/bundle
Those absences are consistent with current architecture policy; the corresponding behavior lives in narrower existing packages.
3. High-confidence deduplication opportunities
HTTP upload body handling should be owned by ingestion
Affected files/packages:
internal/app/upload_http.gointernal/app/upload_coordinator.gointernal/ingest/archive.gointernal/app/upload_http_test.gointernal/app/upload_http_integration_test.gointernal/ingest/archive_test.go
Duplicated or near-duplicated behavior:
internal/app/upload_http.govalidates upload content types insupportedUploadContentType, whileinternal/ingest/archive.govalidates the same content types inarchiveFormat.internal/app/upload_http.goenforces upload size inreadUploadBody, whileinternal/ingest/archive.goenforces upload size again inwriteLimited.- The HTTP handler reads the full upload body into memory before submission, then the coordinator passes a
bytes.Readerto ingestion.
Why it matters:
- The app transport layer now partially owns archive policy that should belong to
internal/ingest. - Large accepted uploads are buffered in memory even though ingestion already has streaming-to-disk mechanics.
- Future archive formats, content types, or upload limit changes would need coordinated edits in multiple packages.
Recommended refactor:
- Move supported content-type checking behind an ingestion-owned helper, for example
ingest.ValidateContentTypeoringest.IsSupportedContentType. - Change upload admission so the request body is streamed to staging exactly once before the HTTP handler returns
202 Accepted. - Keep queue-full rejection before reading the body.
- Queue a staged local bundle root, not an unread request body. This preserves async distribution while keeping HTTP request lifetime separate from later pipeline execution.
- Keep
UploadCoordinatorresponsible for queueing, status, per-pipeline serialization, and execution. Keepinternal/ingestresponsible for archive format, size, extraction, cleanup, and source bundle validation.
Suggested tests:
- HTTP handler rejects full queues without reading the body.
- HTTP handler streams a valid body to ingestion and returns
202only after staging succeeds. - Unsupported content types are rejected through the ingestion-owned content-type policy.
- Oversized uploads are rejected without retaining a staged run.
- Accepted upload status still transitions through queued/running/succeeded or failed without depending on an open HTTP request body.
Risk level:
- Medium. The behavior change is internal but touches admission timing and async execution boundaries. It should be implemented in a focused prompt with existing HTTP integration tests extended first.
Runtime config and secret setup should have one app-level helper
Affected files/packages:
internal/app/run.gointernal/app/source_select.gointernal/app/serve.gointernal/app/backends.gointernal/app/run_warnings.gointernal/configinternal/app/*_test.go
Duplicated or near-duplicated behavior:
- Defaulting an empty config path to
config.DefaultConfigPathappears inRun,RunPipeline,RunPipelineWithLocalSource, andServe. - Config loading and secret loading are separate repeated steps in
buildRunReport,selectSourceBundlesFromConfig, andServe. - Secret conflict warnings are projected in run and configured source diagnostics, while serve loads secrets without using or exposing conflict warning metadata.
- Backend factory construction from a config environment is repeated through provider plumbing.
Why it matters:
- Config and secret precedence is a public operational policy.
- A future change to config discovery, secret conflict reporting, or runtime environment construction could drift between
run,serve,validate, andinspect. - Tests for secrets and credential resolution need to cover several entrypoints today.
Recommended refactor:
- Add a small app-level runtime setup helper, for example
loadRuntimeConfig(optionsConfigPath string) (runtimeConfig, error). - The helper should own default config path selection,
config.LoadFile,config.LoadSecretEnvironment, and conversion of secret conflicts intoOutputWarningvalues. - Keep config parsing and validation in
internal/config; the helper should not duplicate config policy. - Let
run, configuredvalidate/inspect, andservecall the helper and then apply command-specific behavior.
Suggested tests:
- One focused app test proving default config path selection remains unchanged where injection permits it.
- Existing secret conflict JSON/text warning tests for
run,validate, andinspect. - Serve startup test proving duplicate and missing upload tokens still fail without leaking values.
- S3 explicit credential tests proving the resolver is still used through the helper.
Risk level:
- Low to medium. This is a straightforward centralization but touches several command entrypoints.
Run destination processing needs a narrow helper boundary
Affected files/packages:
internal/app/run.gointernal/app/run_output.gointernal/app/run_failures.gointernal/app/run_selection.gointernal/publishinternal/app/run_test.go
Duplicated or near-duplicated behavior:
- Destination backend open failures and publish planning/execution failures each manually add
runFailures, record summary failure counts, appendRunActionRecord, and append pipeline event indexes. publish.Builderror handling patches missingPlanidentity fields inline before converting the plan to a run action.- Fixed-path warning emission is interleaved with destination selection and publish plan handling.
Why it matters:
run --format jsondepends on exact action ordering, warnings, partial failures, and summary counters.- Future changes to actions, links, notifications, or HTTP upload reports could accidentally update one failure path but not another.
- The current loop is correct but dense enough that small behavior changes are hard to review.
Recommended refactor:
- Extract a narrow
runDestinationordestinationRunnerhelper that processes one destination and returns action records, warnings, summary deltas, and failures. - Add a helper for recording a destination-scoped failure that updates
runFailures,runSummary,RunReport.Actions, and pipeline events in one place. - Add a helper that normalizes partial
publish.Planidentity fields before action projection. - Do not introduce a generic workflow engine or stage abstraction.
Suggested tests:
- Preserve existing run text output golden assertions.
- Preserve JSON partial-result behavior when planning fails after destination processing begins.
- Add one focused test where destination open fails for multiple selected bundles and verify action records, output errors, and summary counters stay aligned.
- Add one fixed-path dry-run warning test after extraction to verify event ordering.
Risk level:
- Medium. The refactor is behavior-preserving but touches the most important user-facing workflow.
Archive path validation duplicates source path policy with a different error surface
Affected files/packages:
internal/ingest/archive.gopkg/bundle/path.gointernal/storage/path.gointernal/ingest/archive_test.go
Duplicated or near-duplicated behavior:
cleanArchivePath,pkg/bundle.ValidateSourcePath, andstorage.ValidatePathall enforce clean slash-separated relative paths with no backslashes, no absolute paths, and no dot segments.- Archive staging needs slightly different policy because directories are allowed and
manifest.jsonis allowed only at the root, so the duplication is not completely mechanical.
Why it matters:
- Path safety is high-risk behavior.
- Future changes to source path rules could miss archive extraction, especially around backslashes, reserved names, or dot segments.
Recommended refactor:
- Keep archive-specific rules in
internal/ingest, but use a shared path-checking primitive where possible. - A good shape is an exported
pkg/bundle.ValidatePathSegmentedonly if it fits the public producer API, or an internal helper in ingestion that delegates file-entry validation topkg/bundle.ValidateSourcePathfor regular files after handling directory-specific exceptions. - Preserve current archive-specific errors and tests.
Suggested tests:
- Table tests shared or mirrored across bundle path validation and archive path cleaning for absolute paths, traversal, backslashes, dot segments, empty names, root
manifest.json, nestedmanifest.json, and.distributor.json. - Regression tests proving directories are still accepted in archives but symlinks and hardlinks remain rejected.
Risk level:
- Low to medium. Path validation changes need careful tests, but the desired change can be small.
4. Medium-confidence opportunities
Source and destination backend config shapes could expose a normalized view
Affected files/packages:
internal/config/config.gointernal/config/defaults.gointernal/config/validate.gointernal/app/backends.gointernal/config/*_test.gointernal/app/backends_test.go
Duplicated or near-duplicated behavior:
config.Backendandconfig.Destinationduplicate backend fields for local, SSH, S3, and credentials.- Defaults for source backends and destination backends are implemented in separate functions.
- App backend opening converts both shapes into
backendOpenSpec.
Why it matters:
- New backend fields must be added to both YAML structs, defaulting paths, validation paths, app open-spec conversion, docs, and tests.
- The current pattern is easy to understand but likely to drift as more backend-specific fields are added.
Recommended refactor:
- Keep the YAML shape unchanged for compatibility.
- Add package-local helpers in
internal/configthat return a normalized backend view for either source or destination. - Use that view for shared backend defaulting and validation where it improves clarity.
- Keep destination-only fields such as
publish,transfer,links, andpath_mappingonDestination.
Suggested tests:
- Existing source and destination backend validation tests should continue to pass.
- Add a table test that validates equivalent local, SSH, and S3 source/destination backend field requirements through the shared view.
- Add a test that
http_uploadremains source-only.
Risk level:
- Medium. This reduces future drift, but the current duplication is understandable and does not need to be the first cleanup.
CLI command scaffolding is mostly shared, but manifest create has special parsing
Affected files/packages:
internal/cli/run.gointernal/cli/serve.gointernal/cli/source_mode.gointernal/cli/manifest.gointernal/cli/version.gointernal/cli/root_test.go
Duplicated or near-duplicated behavior:
- Several commands repeat
flag.NewFlagSet,SetOutput, help handling, format parsing, and usage exit handling. manifest createusessplitManifestCreateArgsto allow a positional bundle path before flags, unlike Go's defaultflagbehavior.
Why it matters:
- CLI syntax and error behavior are public.
- A broad CLI helper could accidentally obscure command-specific parsing, but a narrow helper could reduce repeated setup and invalid-format handling.
Recommended refactor:
- Do not introduce a CLI framework.
- Consider a tiny helper for common
FlagSetcreation and output-format parsing after higher-value app/config cleanup. - Keep
manifest createcustom parsing local unless another command needs the same interspersed positional behavior.
Suggested tests:
- Preserve current CLI usage-error tests.
- Add explicit tests for
manifest create <path> --id x,manifest create --id x <path>, and invalid missing flag values before any parser cleanup.
Risk level:
- Low if kept narrow; medium if over-generalized.
Output DTOs repeat bundle metadata projection
Affected files/packages:
internal/app/validate.gointernal/app/inspect.gointernal/app/manifest.gointernal/app/run_output.gointernal/app/output.go
Duplicated or near-duplicated behavior:
inspectandmanifest createboth project bundle file metadata into command-specific JSON structs.validate,inspect, andmanifest createeach define local result types and file record types.- RFC3339 formatting uses both
time.RFC3339and the equivalent literal layout string.
Why it matters:
- JSON output is now a public interface.
- Repeated projection can drift in field names, timestamp formatting, or path display rules.
Recommended refactor:
- Add a small app-local projection helper for bundle summaries and manifest file records.
- Use
time.RFC3339instead of literal RFC3339 layouts. - Keep command-specific result structs where the command output semantics differ.
Suggested tests:
- JSON structural tests for
validate,inspect, andmanifest createbefore and after the helper. - A timestamp-format assertion using an offset timestamp to confirm current behavior is preserved.
Risk level:
- Low.
PipelineRunCoordinator overlaps conceptually with UploadCoordinator
Affected files/packages:
internal/app/run_coordinator.gointernal/app/upload_coordinator.godocs/internal/app.mdinternal/app/run_coordinator_test.gointernal/app/upload_coordinator_test.go
Duplicated or near-duplicated behavior:
- Both coordinators define run records, statuses, timestamps, status transitions, context handling, and active pipeline protection.
- The upload coordinator additionally queues, stages, expires status records, and serializes same-pipeline upload execution.
Why it matters:
- The concepts are similar enough to confuse future contributors.
- However, the behavior is not identical: one rejects duplicate active runs, while the other queues accepted uploads.
Recommended refactor:
- Do not merge the coordinators now.
- Review whether
PipelineRunCoordinatoris still needed as an exported app-level helper. If it is intended for future transports, document that role clearly. If not, remove it and its tests in a separate dead-code cleanup. - If both remain, extract only tiny shared timestamp/status helpers if a real third coordinator appears.
Suggested tests:
- If retained, keep existing duplicate-run tests.
- If removed, run
go test ./internal/app ./internal/cliand verify no current behavior depended on it.
Risk level:
- Low for documentation clarification, medium for removal because it is exported from an internal package and documented for maintainers.
5. Boundary and responsibility concerns
The major boundaries are sound:
- CLI parsing stays in
internal/cli. - Config defaults and validation stay in
internal/config. - Backend-specific filesystem, SFTP, and S3 behavior stays in adapters.
- Manifest semantics are centralized in
pkg/bundle, withinternal/bundleadding storage-backed discovery and validation. - Destination state comparison stays in
internal/state. - Publish planning/execution stays in
internal/publish. - Transform implementation is behind
internal/transform.
Concerns worth addressing:
- HTTP upload request-body staging currently crosses the app/ingest boundary. The app transport layer should not own body buffering and archive size enforcement beyond admission and HTTP status projection.
- Runtime config setup is app-layer behavior, but it is repeated rather than named. A runtime setup helper would clarify the boundary between
internal/configand command-specific execution. internal/app/run.goowns too many destination-loop details. Extracting a destination processing helper would keep orchestration in app while reducing local complexity.
Recommended homes:
- Upload archive policy:
internal/ingest. - HTTP route/auth/status mapping:
internal/app/upload_http.go. - Queueing/status/execution:
internal/app/upload_coordinator.go. - Runtime config plus secret setup: a small helper in
internal/app, usinginternal/config. - Path and state filenames: keep in
internal/storage.
6. Path, key, and naming construction review
Centralized and healthy areas:
storage.StateFileName,storage.StatePath,storage.ManagedBundleTargets,storage.Join,storage.DisplayPath, and logical path validation are used in core publication and tests.- S3 object-key mapping is contained in
internal/adapters/s3. - SSH and local native path conversion stay inside their adapters.
- Link URL construction is isolated in
internal/publish/links.goand URL validation ininternal/link. - Manifest name and schema version are centralized in
pkg/bundle, withinternal/bundlealiases.
Areas needing cleanup:
- Archive path cleaning duplicates much of source/storage path policy and should either delegate to a shared primitive or be tightly covered by mirrored tests.
- Upload run ID construction is isolated, but the shape is partly policy. Keep tests around
<pipeline_id>.<utc_timestamp>.<random_suffix>before changing coordinator code. - Some app tests still construct destination state and source paths locally.
internal/testutilalready covers many cases; additional helper use should be opportunistic, not a sweeping test rewrite.
7. Resolution and catalog review
Named concept resolution is mostly consistent:
- Backend names are defined in
internal/config/defaults.go. - Runtime backend construction is app-owned through
backendFactoryand the storage registry. - Transform names are defined in
internal/transform, and app wiring owns concrete registration. - Publish/transform policy combinations use
config.ValidatePublishTransformPolicy. - Configured source selection for
validateandinspectis shared insource_select.go.
Potential refinements:
- A normalized backend config view would make backend field resolution less repetitive across source and destination config.
- Transform and backend registries should remain separate; there is no evidence that a generic registry abstraction would help.
- No separate catalog package is needed for the current feature set.
8. Config and command-loading review
Config loading is reliable and strict:
- YAML known-field checking is enabled.
- Defaults are applied before validation.
- Validation collects multiple field errors.
- Secrets are loaded without mutating
os.Environ. - Explicit S3 credential references use the config-owned resolver.
Likely accidental duplication:
- Default config path selection and
config.LoadFileare repeated in several app entrypoints. - Secret loading is repeated in run, source diagnostics, and serve.
- Secret conflict warning projection is not represented by one runtime setup result.
Intentional differences:
serveloads upload tokens and does not produce CLI JSON output.validateandinspectsupport local-path shortcut mode, whilerunandserveare config-driven.manifest createis local filesystem producer tooling and does not load app config.
Recommended cleanup:
- Centralize runtime config and secret setup in
internal/app. - Keep CLI flag parsing local to command files.
- Keep
manifest createoutside runtime config loading.
9. State, manifest, or progress handling review
Manifest handling is in good shape:
pkg/bundleowns manifest parsing, digest grammar, source path validation, canonical bundle digest, local manifest building, and local bundle writing.internal/bundledelegates normalized manifest semantics topkg/bundleand adds storage-backed validation.- Destination state embeds the normalized manifest and validates through
internal/bundle/pkg/bundle.
State handling is in good shape:
.distributor.jsonparsing, validation, JSON projection, and comparison live ininternal/state.- Publish execution writes destination state only after outputs are written.
- Managed replacement deletes only state-listed outputs plus
.distributor.json; forced replacement is explicit and bounded.
Progress/status handling:
RunReportis the core run result model and supports JSON partial-result output.- HTTP upload status is memory-only and documented as such.
PipelineRunCoordinatorandUploadCoordinatoroverlap conceptually but have different policies. Avoid merging unless product behavior converges.
Gaps:
- HTTP upload staging currently stores the request body in memory before queueing. This is both a quality gap and a mismatch with the intended ingestion boundary.
- There is no durable upload status, but this is documented as deferred work and should not be addressed in cleanup.
10. Refactors to avoid
Avoid these changes in the cleanup pass:
- Do not introduce a generic workflow engine or stage framework. The current explicit workflow is easier to audit.
- Do not add a CLI framework. The standard-library CLI is sufficient and policy-approved.
- Do not merge local, SSH, S3, and fake adapters behind a shared implementation layer. Their semantics differ enough that generic helpers would likely hide important behavior.
- Do not collapse
pkg/bundleandinternal/bundle. The public producer API and storage-backed distributor validation have different responsibilities. - Do not move destination state comparison into
publishor app orchestration. - Do not redesign JSON output envelopes while doing cleanup.
- Do not add durable queues, retry workers, HTTP TLS, zstd, or browser UI under the banner of refactoring. These are feature work.
- Do not rewrite tests wholesale to use a new fixture system. Add helpers only where they reduce immediate duplication around changed code.
11. Recommended implementation sequence
-
HTTP upload staging boundary cleanup.
- Move supported content-type policy to
internal/ingest. - Stop buffering accepted uploads in
upload_http.go. - Queue staged bundle roots rather than request bodies.
- Extend HTTP upload tests first.
- Move supported content-type policy to
-
Runtime config setup helper.
- Add an app-level helper for default config path, config load, secret load, environment resolver, and secret warnings.
- Use it from
run, configuredvalidate/inspect, andservewhere applicable. - Preserve command-specific behavior.
-
Run destination processing extraction.
- Add small helpers for destination-scoped failure recording and plan identity normalization.
- Extract one-destination processing only if the helper remains readable.
- Preserve action ordering and report output.
-
Backend config normalized view.
- Add source/destination backend view helpers in
internal/config. - Use them for defaulting and validation if tests show the shape remains clear.
- Keep YAML structs and public config unchanged.
- Add source/destination backend view helpers in
-
Bundle output projection cleanup.
- Add app-local helpers for file record and bundle summary projection.
- Use
time.RFC3339consistently. - Preserve command-specific JSON field names.
-
Archive/source path validation test alignment.
- Add mirrored path safety tests around ingestion and bundle path validation.
- Only centralize code if the helper does not blur archive directory semantics.
-
Coordinator intent cleanup.
- Decide whether
PipelineRunCoordinatoris retained for internal future use. - If retained, clarify comments/docs. If removed, do it as a separate dead-code commit.
- Decide whether
-
Test helper cleanup.
- Expand
internal/testutilonly for repeated setup touched by the previous refactors. - Avoid moving every test fixture.
- Expand
12. Test strategy
Tests to add before refactoring:
- HTTP upload handler test proving queue-full rejection does not consume the body.
- HTTP upload test proving accepted upload staging completes before
202 Accepted. - Ingestion content-type policy tests exposed through the new helper.
- Run report test covering destination open failure for multiple selected bundles.
- CLI JSON tests for
inspectandmanifest createtimestamp formatting before projection cleanup.
Tests to run with each cleanup stage:
- HTTP upload cleanup:
go test ./internal/ingest ./internal/app ./internal/cli - Config setup cleanup:
go test ./internal/config ./internal/app ./internal/cli - Run processing cleanup:
go test ./internal/app ./internal/publish ./internal/state - Backend config view cleanup:
go test ./internal/config ./internal/app - Output projection cleanup:
go test ./internal/app ./internal/cli - Path validation cleanup:
go test ./pkg/bundle ./internal/bundle ./internal/ingest ./internal/storage - Final cleanup validation:
go test ./...
Useful read-only checks:
rg -n "LoadFile\\(|LoadSecretEnvironment\\(|DefaultConfigPath" internal/app internal/clirg -n "application/x-tar|application/gzip|application/x-gzip" internal docsrg -n "2006-01-02T15:04:05Z07:00" internal pkgrg -n "manifest.json|\\.distributor.json|StatePath|DisplayPath" internal pkg
13. Appendix: findings not worth acting on
Adapter ReadFile and WriteFile wrappers:
- Local, SSH, S3, and fake backends each implement byte helpers in terms of stream helpers. This is small duplication but appropriate because each adapter owns error translation and metadata semantics.
Adapter traversal implementation:
- Local filesystem walking, SFTP walking, and S3 pagination look similar at the interface level but are semantically different. Keep traversal mechanics in adapters and shared callback behavior in
storage.WalkEmitter.
State and manifest raw JSON parsing:
pkg/bundleandinternal/stateboth parse raw JSON with pointer fields to detect missing required fields. The schemas and error contexts differ, so a generic required-field parser would not be worth the complexity.
CLI help text:
- Help text repeats command names and flags. This is acceptable in a small hand-written CLI and keeps command files readable.
Test fixture strings:
- Some tests inline YAML snippets or expected output strings despite
internal/testutil. Inline data is often clearer for edge cases. Only centralize fixture setup when tests are already being changed for a behavior-preserving refactor.
HTTP JSON response helpers:
- HTTP API responses use simple JSON objects rather than the CLI JSON envelope. This is intentional because HTTP status codes and route-specific responses are not the same public interface as CLI command output.
Public and internal bundle validation:
pkg/bundle.ValidateBundleis local-filesystem producer validation;internal/bundle.Validateis storage-backed distributor validation. Keep both, with shared manifest semantics delegated throughpkg/bundle.