9.0 KiB
Development Policy
This document defines the day-to-day development workflow for distributor.
Use it with docs/policy/architecture.md and docs/policy/documentation.md.
Repository Layout
cmd/distributor: executable entrypoint only.internal/app: top-level use cases forrun,validate, andinspect.internal/cli: standard-library command parsing, flags, help text, and command wiring.internal/config: YAML configuration structs, loading, defaults, and validation.internal/bundle: source bundle discovery, manifest parsing, digest calculation, and validation.internal/state: destination.distributor.jsonparsing, validation, and comparison.internal/storage: backend interface, registry, logical path rules, typed errors, and shared storage helpers.internal/adapters/local: local filesystem backend.internal/adapters/ssh: SSH/SFTP backend.internal/storage/fake: in-memory backend for tests.internal/publish: destination inspection, output planning, reconciliation, execution, and managed cleanup.internal/transform: transform interface and registry.internal/transform/markdown: Markdown-to-HTML sidecar transform.internal/notify: notification interface and current no-op notifier.internal/testutil: shared test fixtures. Production code must not import this package.docs: current user, operator, policy, internal, and roadmap documentation.examples: copyable example configs and source bundles.
Do not create new top-level package families such as pkg, internal/stage,
internal/modules, or service-specific adapter directories unless the
architecture policy or a current roadmap explicitly calls for them.
Common Commands
Run the full test suite:
go test ./...
Run targeted packages while developing:
go test ./internal/config
go test ./internal/cli ./internal/app
go test ./internal/publish ./internal/state
go test ./internal/transform/markdown
Run the CLI against an example config:
go run ./cmd/distributor run --config examples/local-publish.yml --dry-run
Validate or inspect a local source bundle:
go run ./cmd/distributor validate examples/source-bundle
go run ./cmd/distributor inspect examples/source-bundle
If Go cache permissions fail in a restricted environment, use workspace-safe temporary caches:
GOCACHE=/private/tmp/distributor-gocache GOMODCACHE=/private/tmp/distributor-gomodcache go test ./...
Coding Conventions
- Keep the application small, explicit, and dependency-light.
- Prefer package-local helpers over broad abstractions until behavior is shared by multiple packages.
- Keep CLI parsing in
internal/cli; business decisions belong ininternal/app,internal/bundle,internal/publish,internal/state, and related core packages. - Keep adapter packages thin. Backend-specific filesystem or service behavior belongs in adapters; bundle, state, transform, and publish policy belongs outside adapters.
- Preserve public CLI behavior, config semantics, manifest schema, destination state schema, and local MVP behavior unless the current task explicitly changes them.
- Use
storage.DisplayPath,storage.StateFileName,storage.StatePath, andstorage.ManagedBundleTargetsinstead of duplicating those conventions. - Use
bundle.ValidateManifestfor normalized source manifest semantics, including embedded source manifests in destination state. - Use
config.ValidatePublishTransformPolicyfor publish and transform policy combinations. - Do not import concrete transform implementations from
internal/publish; app-level wiring owns transform registration. - Do not import
internal/testutilfrom production code.
Dependency Policy
The project currently depends on:
gopkg.in/yaml.v3for YAML configuration loading.github.com/yuin/goldmarkfor Markdown rendering.golang.org/x/crypto/ssh,golang.org/x/crypto/ssh/agent, andgolang.org/x/crypto/ssh/knownhostsfor native SSH support.github.com/pkg/sftpfor native SFTP support.
Add external dependencies only when they materially improve correctness, security, interoperability, or implementation complexity. Avoid dependencies for small conveniences. Do not let dependency-specific types leak across internal package boundaries unless that dependency is the explicit package contract.
Configuration Changes
When adding or changing configuration:
- Update
internal/config/config.gostructs and YAML tags. - Add defaults in
internal/config/defaults.goonly for built-in defaults. - Add validation in
internal/config/validate.gowith clear field context. - Update config load and validation tests.
- Update
docs/config.mdin the same change if current user-visible config behavior changes. - Update examples only with configs that are valid and executable for implemented behavior.
Config validation may accept fields for backends that are not executable yet, but user-facing docs and examples must clearly state execution support. At the time of this policy, local and SSH backends are executable.
Credential-consuming code must use the config-owned environment resolver for
explicit credential environment variable references. Do not call os.Getenv
directly for backend credentials, because secrets.directory values are
intentionally available through the resolver without mutating the process
environment.
CLI Changes
The CLI is hand-written with the Go standard library. Do not introduce a CLI framework without a documented reason.
When adding or changing commands or flags:
- Keep parsing and help text in
internal/cli. - Keep command work in
internal/appor a lower-level package. - Add or update CLI tests in
internal/cli. - Update
docs/cli.mdif syntax, flags, output expectations, or workflows change.
validate and inspect are local path commands. run loads configured
pipelines and currently executes local and SSH backends.
Storage Backends
Storage behavior is defined by internal/storage.Backend and shared path rules
in internal/storage.
When adding a backend:
- Implement the storage interface in an adapter package.
- Translate backend-specific errors into storage errors where practical.
- Keep bundle comparison, transform, routing, and replacement policy out of the adapter.
- Register runtime construction through app-level backend factory wiring.
- Add focused adapter tests and app-level wiring tests.
- Update user docs, operations docs, examples, and internal docs only for behavior that is actually implemented.
Do not document S3 execution as available until the corresponding adapter package and app wiring exist.
Transforms
Transforms use internal/transform interfaces and registry wiring.
When adding or changing a transform:
- Keep the transform implementation in its own package under
internal/transform. - Register default runtime transforms from
internal/app. - Keep
internal/publishdependent only on the transform interface or resolver. - Record deterministic output metadata: path, source path, transform name, digest, and size.
- Add transform tests and app or publish tests for wiring and policy behavior.
- Update
docs/internal/transform.mdand any relevant integration docs for implemented behavior.
Tests
Test close to the behavior being changed:
- Use package tests for parsing, validation, comparison, planning, and adapter behavior.
- Use
internal/appandinternal/clitests for user-facing workflows. - Use
internal/testutilfor shared valid fixtures only; keep edge cases near the package under test. - Run
go test ./...after cross-package changes or documentation/example changes tied to tests.
Live integration tests must be opt-in and skipped during normal go test ./...
unless their required environment variables are set. Test-only environment
variables must use this prefix shape:
DISTRIBUTOR_TEST_<BACKEND>_*
Examples include DISTRIBUTOR_TEST_SSH_HOST and
DISTRIBUTOR_TEST_S3_ENDPOINT. Do not use production credential variable names
for test-only controls.
Examples
Examples under examples/ must be valid, maintained, and free of secrets.
They should be copyable for implemented behavior. Do not leave examples that
look runnable but require unsupported backend execution.
When changing examples:
- Keep paths relative to the repository where practical.
- Prefer local examples until remote backend support exists.
- Run
go test ./internal/configbecause config tests load examples. - Update README, CLI, or config docs if links or recommended workflows change.
Documentation
Follow docs/policy/documentation.md.
- Document implemented behavior outside
docs/roadmap/. - Keep future, planned, or aspirational behavior under
docs/roadmap/. - Keep
docs/config.mdcanonical for user-facing config reference. - Keep
docs/cli.mdcanonical for command syntax and workflows. - Keep
docs/operations.mdcanonical for operational and recovery behavior. - Keep
docs/internal/focused on implemented package contracts. - Update docs in the same change as behavior when public behavior, config, CLI, examples, or internal contracts change.