23 KiB
Distributor Implementation Roadmap
This roadmap defines a staged implementation plan for the distributor MVP. Each stage is intended to map cleanly to one Codex implementation prompt.
The roadmap assumes the project includes these planning documents before implementation begins:
docs/policy/architecture.mddocs/policy/documentation.mddocs/roadmap/packages.mddocs/roadmap/contracts.mddocs/roadmap/config.md
The MVP goal is a domain-agnostic bundle distributor that discovers source bundles, validates manifest.json, optionally transforms Markdown to HTML, publishes selected outputs to one or more destinations, and records destination state in .distributor.json.
Global Implementation Rules
All stages should preserve these invariants:
- Producer applications own source bundle creation.
distributorowns validation, transformation, publication, destination state, and future notification hooks.- Source bundle state is defined by
manifest.json. - Destination publication state is defined by
.distributor.json. manifest.jsonis not copied to the destination as destination state.- Pipelines have exactly one source and one or more destinations.
- Transform and publish policy are destination-specific.
- Destructive replacement is allowed only inside a managed destination bundle path. Unsafe force or unmanaged overwrite behavior is deferred.
- Dry-run behavior should be implemented before broad remote write behavior.
- Config, bundle, state, publish planning, storage adapters, transforms, and CLI wiring should remain separate packages.
Unless a stage explicitly says otherwise, each implementation prompt should:
- read the project policy and roadmap documents;
- implement only the current stage;
- add or update tests for the current stage;
- run the relevant test suite;
- update documentation only when the implemented behavior now exists;
- avoid implementing future roadmap stages early.
Stage 1: Project Skeleton, CLI Shell, and Baseline Tooling
Goal
Create the initial Go application structure and a minimal executable distributor command with no business behavior beyond version/help output and placeholder commands.
Scope
Implement the accepted package skeleton from docs/roadmap/packages.md at the level needed for compilation.
Create:
cmd/distributor/main.go
internal/cli/
internal/app/
internal/config/
internal/logging/
Initial CLI commands:
distributor --helpdistributor versiondistributor rundistributor validatedistributor inspect
At this stage, run, validate, and inspect may return clear “not implemented” errors, but the command structure should be present.
Notes
Prefer a small CLI dependency only if the project already standardizes on one. Otherwise, the standard library is acceptable for the first pass.
Add a version variable that can later be set at build time.
Tests
Add tests for:
- command construction if testable;
- version string behavior if exposed through a package;
- basic package compilation.
Completion Criteria
go test ./...passes.go run ./cmd/distributor --helpworks.go run ./cmd/distributor versionworks.- Placeholder operational commands fail clearly and intentionally.
Stage 2: Config Schema, Loading, Defaults, and Validation
Goal
Implement the MVP config.yml schema described in docs/roadmap/config.md.
Scope
Create config structs for:
- top-level config;
- pipelines;
- source backend config;
- destination backend config;
- validation policy;
- publish policy;
- transform policy;
- transfer/replacement policy;
- backend-specific local, SSH, and S3 fields.
Support loading YAML from a file path.
Implement validation for:
- required top-level
pipelines; - unique pipeline ids;
- required pipeline
id,source, and non-emptydestinations; - unique destination ids within a pipeline;
- supported backend names:
local,ssh,s3; - required backend fields;
- supported validation action:
fail; - supported transfer actions;
- valid
publishpolicy; - valid Markdown-to-HTML transform config.
Default behavior should match docs/roadmap/config.md.
CLI Integration
Add --config to run.
For this stage, distributor run --config config.yml --dry-run may only load and validate config, then print a concise summary of configured pipelines and destinations.
Tests
Add unit tests for:
- valid minimal local-to-local config;
- valid fan-out config;
- valid local, SSH, and S3 backend configs;
- duplicate pipeline ids;
- duplicate destination ids;
- missing required fields;
- unsupported backend;
- invalid transfer action;
- invalid validation action, including
warn.
Completion Criteria
- Config load/default/validate behavior is implemented and tested.
distributor run --config <file> --dry-runvalidates config and prints a summary.- No bundle discovery or publication occurs yet.
Stage 3: Storage Abstraction, Local Backend, and Fake Backend
Goal
Introduce the storage backend abstraction before bundle validation so source discovery, validation, and publication are backend-agnostic from the start.
Scope
Create:
internal/storage/backend.go
internal/storage/registry.go
internal/storage/path.go
internal/storage/errors.go
internal/adapters/local/backend.go
internal/storage/fake/
Define backend operations needed by the MVP, including:
- read file;
- write file;
- test existence;
- list files or tree entries;
- read destination state file if present;
- create directories/prefixes as needed;
- delete explicit managed files safely;
- write files using temporary/staged writes where practical.
The fake backend should exist for unit tests of config, bundle, state, and publish logic without real local, SSH, or S3 IO.
Safety Requirements
The local backend must:
- clean and join paths safely;
- reject path traversal;
- reject unsafe destructive deletion requests;
- avoid following symlinks for source bundle files unless explicitly supported;
- avoid deleting configured roots;
- classify destination bundle emptiness deterministically.
Tests
Add tests for:
- backend read/write/list/exists behavior;
- safe path joining;
- traversal rejection;
- symlink rejection for source reads;
- explicit-file deletion guard behavior;
- local destination emptiness detection;
- fake backend behavior sufficient for core package tests.
Completion Criteria
- Local backend implements the storage interface.
- Fake backend can support bundle and publish tests without external services.
go test ./...passes.- No SSH or S3 implementation exists yet.
Stage 4: Source Bundle Manifest, Digest, Validation, and Discovery
Goal
Implement the source bundle contract from docs/roadmap/contracts.md through the storage abstraction.
Scope
Create:
internal/bundle/manifest.go
internal/bundle/digest.go
internal/bundle/validate.go
internal/bundle/discover.go
Implement:
- parsing
manifest.json; - strict required field validation, including
schema_version: 1; - RFC3339
createdparsing; - lowercase
sha256:<64 hex>digest validation; - source file path safety checks;
- duplicate logical file path rejection;
- per-file SHA256 validation;
- per-file size validation;
- bundle digest validation using the canonical ordered file-record algorithm;
- deterministic storage-backed bundle discovery under a source root;
- nested manifest detection and failure.
Discovery and validation should use internal/storage rather than direct os APIs. The local CLI path should be adapted to the local backend.
CLI Integration
Implement:
distributor validate <path>
distributor inspect <path>
For local paths:
validateshould validate either a single bundle directory or a tree containing bundles.inspectshould print a concise normalized summary of discovered bundle ids, relative paths, created timestamps, digest values, and files.
Tests
Add fixture bundles under a testdata directory.
Test:
- valid bundle;
- invalid JSON;
- missing required fields;
- invalid schema version;
- invalid timestamp;
- invalid digest format;
- unsafe file paths;
- duplicate normalized file paths;
- missing files;
- size mismatch;
- per-file digest mismatch;
- bundle digest mismatch;
- canonical bundle digest reference fixture;
- multiple discovered bundles in deterministic order;
- nested manifests fail.
Completion Criteria
- Storage-backed bundle validation is deterministic and well-tested.
distributor validate <path>works for local bundle fixtures.distributor inspect <path>works for local bundle fixtures.- No destination publication occurs yet.
Stage 5: Destination State Contract and Comparison Logic
Goal
Implement .distributor.json parsing, validation, and source-to-destination comparison.
Scope
Create:
internal/state/distributor.go
internal/state/compare.go
internal/state/validate.go
Implement the destination state schema from docs/roadmap/contracts.md, including:
schema_version;- optional
distributor_version; pipeline_id;destination_id;published_at;- embedded normalized source manifest;
- outputs array;
- output file metadata.
Implement comparison outcomes:
- destination absent;
- destination unmanaged/non-empty;
- same source manifest;
- same source id, destination older;
- same source id, destination newer;
- same source id and same created but different digest;
- different source id;
- invalid destination state.
Tests
Add unit tests for every comparison outcome.
Test validation for:
- valid state;
- missing fields;
- invalid schema version;
- invalid embedded source manifest;
- invalid output metadata;
- malformed published timestamp.
Completion Criteria
- Destination state can be parsed and validated independently.
- Source manifest to destination state comparison is deterministic and fully tested.
- No publication execution occurs yet.
Stage 6: Publish Planning, Dry-Run, and Local-to-Local Publication Without Transform
Goal
Implement the core publish planner and execute local-to-local publication for source files only.
Scope
Create:
internal/publish/plan.go
internal/publish/reconcile.go
internal/publish/safety.go
internal/publish/output.go
internal/publish/execute.go
Implement planning for one source bundle to one destination based on:
- source manifest;
- destination config;
- publish policy;
- transfer policy;
- existing
.distributor.json; - destination path state.
Actions should include:
- publish new;
- replace older destination;
- skip same;
- skip destination newer;
- fail conflict;
- fail unmanaged destination.
Implement local-to-local execution for publish.source: true and publish.html: false.
Execution should:
- copy listed source files selected by publish policy;
- write
.distributor.jsonwith copied source output metadata; - avoid copying source
manifest.jsonas destination state; - preserve relative bundle paths from source root beneath destination root;
- detect destination output collisions before writing;
- use staging or equivalent cleanup behavior for local writes;
- support fan-out to multiple local destinations;
- support dry-run without writes.
CLI Integration
distributor run --config <file> should now execute local-to-local pipelines when configured.
--dry-run should print the planned action for each discovered bundle and destination.
Tests
Add integration-style tests using temp directories for:
- new local publication;
- no-op when destination state matches;
- replacement when destination state is older;
- skip when destination state is newer;
- fail on conflict;
- fail on unmanaged non-empty destination;
- fail on output path collision;
- fan-out from one source to two local destinations;
- failed local write does not leave a destination that appears unmanaged on retry;
- dry-run performs no writes;
.distributor.jsonis written correctly.
Completion Criteria
- Local-to-local source-file publication works end to end.
- Dry-run produces meaningful planned actions.
- Destination state is authoritative.
- No Markdown-to-HTML transform exists yet.
Stage 7: Markdown-to-HTML Transform and Destination-Specific Publish Policy
Goal
Add MVP Markdown-to-HTML transformation and destination-specific source/html output selection.
Scope
Create:
internal/transform/transform.go
internal/transform/registry.go
internal/transform/plan.go
internal/transform/markdown/markdown.go
internal/transform/markdown/template.go
Implement only:
transform:
markdown_to_html:
enabled: true
mode: sidecar
MVP sidecar behavior:
- for each listed source artifact ending in
.md, generate a same-directory.htmlsidecar; - preserve the original Markdown file unchanged;
- do not generate HTML for non-Markdown files;
- escape or disable raw HTML embedded in Markdown;
- fail before writing when generated output paths collide with copied source outputs or other generated outputs;
- record generated output metadata in
.distributor.json; - if
publish.source: false, do not publish source files; - if
publish.html: true, publish generated HTML files; - if
publish.html: truebut transform is disabled or no Markdown files exist, fail with a clear error unless config later defines another behavior.
Use a well-maintained Markdown renderer. Keep HTML templating minimal and deterministic.
Tests
Add tests for:
- Markdown sidecar generation;
- source-only destination;
- HTML-only destination;
- source-plus-HTML destination;
- no mutation of source bundle;
- generated output metadata in
.distributor.json; - failure when HTML publication is requested without transform support;
- failure when generated HTML collides with a source artifact path;
- raw HTML in Markdown is escaped or disabled consistently;
- deterministic output for a fixture Markdown file.
Completion Criteria
- Local-to-local publication supports source-only, HTML-only, and source-plus-HTML destinations.
- Generated outputs are recorded in destination state.
- Dry-run reports transform outputs that would be generated.
Stage 8: No-Op Notification Stage and Pipeline Orchestration Polish
Goal
Add the internal no-op notification stage and polish orchestration around per-destination outcomes.
Scope
Create:
internal/notify/notify.go
internal/notify/noop.go
Integrate a no-op notifier after successful publication/skip handling as appropriate.
Clarify orchestration behavior when one destination fails. For MVP, fan-out should be deterministic and sequential. Continue planning and reporting later destinations where safe, but return non-zero if any destination fails.
Improve run summary output:
- pipeline id;
- source backend;
- discovered bundle count;
- destination ids;
- action per bundle/destination;
- final status.
Tests
Add tests for:
- notifier is invoked at the expected orchestration point where testable;
- pipeline failure when a destination fails;
- run summary contains meaningful status information;
- dry-run does not invoke write-side effects.
Completion Criteria
- The pipeline shape includes notification as an internal no-op stage.
- Run output is useful for unattended operation logs.
- Local MVP behavior remains passing.
Stage 9: Native SSH/SFTP Backend
Goal
Implement SSH/SFTP storage backend support for sources and destinations.
Scope
Create:
internal/adapters/ssh/backend.go
internal/adapters/ssh/config.go
Implement the storage backend interface over native SSH/SFTP.
Required config:
backend: ssh
uri: ssh://user@example.com:22
path: /remote/root
Authentication expectations:
- prefer SSH agent by default;
- use known_hosts validation by default where practical;
- do not require passwords in YAML;
- optional key-file support may be implemented if straightforward, but should not distract from agent-based auth.
Support SSH/SFTP backend as both source and destination:
- local -> ssh;
- ssh -> local;
- ssh -> ssh where feasible through staging or streaming.
Safety Requirements
The SSH backend must enforce the same logical path safety rules as the local backend.
Deletion must remain limited to managed destination bundle paths guarded by valid .distributor.json.
Tests
Unit-test path handling and config validation.
If practical, add integration tests that can be skipped unless an SSH test endpoint is configured through environment variables. Do not require a live SSH server for normal go test ./....
Completion Criteria
- SSH/SFTP backend compiles and satisfies the storage interface.
- Backend config validation is tested.
- Normal tests do not depend on a live SSH server.
- At least local-to-SSH and SSH-to-local flows are documented or manually testable.
Stage 10: S3-Compatible Backend
Goal
Implement S3-compatible backend support for sources and destinations.
Scope
Create:
internal/adapters/s3/backend.go
internal/adapters/s3/config.go
Required config should align with docs/roadmap/config.md:
backend: s3
endpoint: https://s3.example.com
bucket: reports
prefix: some/prefix
region: us-east-1
force_path_style: true
credentials:
access_key_id_env: DISTRIBUTOR_S3_ACCESS_KEY_ID
secret_access_key_env: DISTRIBUTOR_S3_SECRET_ACCESS_KEY
Implement storage operations over S3 object keys:
- read object;
- write object;
- exists;
- list prefix;
- delete managed prefix or listed managed files;
- read/write
.distributor.json.
Set reasonable content types where available:
.md:text/markdown; charset=utf-8;.html:text/html; charset=utf-8;.json:application/json;.txt:text/plain; charset=utf-8.
Support S3 backend as both source and destination.
Safety Requirements
Treat S3 prefixes as object trees. Do not assume real directories exist.
Deletion must be limited to destination bundle prefixes that are confirmed managed by .distributor.json.
Tests
Add unit tests for:
- config validation;
- key/prefix normalization;
- content type selection;
- path traversal rejection;
- publish planning with S3 destination state fixtures.
If practical, add integration tests gated by environment variables or a local S3-compatible test service. Normal go test ./... must not require live S3 credentials.
Completion Criteria
- S3 backend compiles and satisfies the storage interface.
- S3 source and destination flows are supported through the common pipeline path.
- Normal tests do not require live S3.
Stage 11: Cross-Backend End-to-End Coverage and Hardening
Goal
Harden the MVP across backend combinations, destination policies, and failure cases.
Scope
Add end-to-end coverage for representative scenarios:
- local source -> local archive destination;
- local source -> local HTML destination;
- local source -> two destinations with different publish policies;
- local source -> SSH destination, where integration credentials exist;
- local source -> S3 destination, where integration credentials exist;
- S3 source -> local destination, where integration credentials exist;
- SSH source -> local destination, where integration credentials exist.
Improve logging and error messages for:
- invalid config;
- invalid source manifest;
- digest mismatch;
- destination conflict;
- unmanaged destination path;
- backend read/write/list failures;
- transform failures.
Ensure all destructive paths have tests or explicit safeguards.
Tests
Add or expand tests for:
- dry-run across multiple destinations;
- partial failure behavior;
- repeated run idempotency;
- older/newer destination state behavior;
- destination state output metadata accuracy;
- generated HTML output metadata accuracy.
Completion Criteria
- MVP behavior is reliable across implemented backend types.
- Error messages identify pipeline id, destination id, bundle id, and reason where practical.
- Idempotent repeated runs behave as expected.
Stage 12: User-Facing Documentation Sync
Goal
Update documentation to reflect implemented MVP behavior.
Scope
Following docs/policy/documentation.md, create or update user-facing documentation only for implemented features.
Likely docs:
README.md
docs/config.md
docs/cli.md
docs/policy/architecture.md
docs/internal/bundles.md
docs/internal/backends.md
Document:
- what
distributordoes; - bundle contract summary;
.distributor.jsonrole;- example source bundle;
- example local-to-local config;
- example local-to-S3 config;
- example local-to-SSH config;
run,validate, andinspectcommands;- dry-run behavior;
- replacement and safety rules;
- Markdown-to-HTML transform behavior;
- environment-variable credential handling.
Move roadmap material to historical/planning status only if your documentation policy allows it. Do not describe unimplemented notification adapters as available features.
Tests
Run the full test suite.
If docs include command examples, verify that basic examples correspond to actual CLI behavior.
Completion Criteria
- User-facing docs describe the implemented MVP accurately.
- Roadmap docs no longer masquerade as implemented behavior.
go test ./...passes.
Stage 13: MVP Release Readiness Pass
Goal
Perform a final pre-release quality pass.
Scope
Review:
- package boundaries against
docs/policy/architecture.md; - package layout against
docs/roadmap/packages.md; - implemented contracts against
docs/roadmap/contracts.md; - implemented config behavior against
docs/roadmap/config.md; - docs against
docs/policy/documentation.md; - destructive operation safety;
- logs and errors for unattended operation;
- command UX;
- test coverage for core invariants.
Add any missing small tests or docs discovered during review.
Do not add new product features in this stage.
Completion Criteria
- MVP is ready to deploy against one real producer pipeline.
- A dry-run can be performed safely against a real source and destination.
- Repeated runs are idempotent.
- Destructive replacement cannot occur outside managed destination bundle paths.
- Final docs accurately reflect the application.
Deferred Post-MVP Work
The following items are intentionally outside the MVP unless explicitly pulled into a later roadmap:
- email notifications;
- ntfy/Gotify/Pushover notifications;
- RSS/Atom feed generation;
- static site index pages beyond sidecar HTML output;
- templated HTML themes beyond a minimal deterministic template;
- destination path remapping rules;
- full plugin architecture;
- web UI;
- report editing;
- producer pipeline execution;
- database-backed state;
- complex retry queues;
- concurrent publication workers;
- symlink support;
- warning-only digest mismatch handling;
- force or unmanaged-overwrite destination behavior;
- password-based SSH authentication in YAML.