11 KiB
Distributor Feature Implementation Roadmap
This roadmap turns docs/roadmap/distributor.md into staged implementation
work for a future coding agent. It is a planning document only. The feature is
not implemented until the stages below are completed and the implemented docs
are updated.
Purpose
Implement generic file-backed environment secrets and distributor notification
support while preserving current CLI syntax, managed artifact paths, RunIDs,
and report output behavior. Distributor package types must stay behind a narrow
adapter boundary, and distributor should receive explicitly selected generated
Markdown files rather than scanning workspace/.
Locked Decisions
- Implement generic
secrets.directorysupport before distributor notification. - Secret files overwrite pre-existing environment variables.
- Never expose secret values in errors, logs, CLI output, metadata, docs, or examples.
- Add
notify.distributorconfig; do not add raw token config. - Keep public CLI commands and flags unchanged.
- Upload one distributor bundle per successfully generated report.
- Initial distributor bundles include only the managed Markdown report at
ReportResult.ReportPath. - The default bundle-relative Markdown path uses the report definition's
BatchOutputName. - Default bundle ID and idempotency key derive from producer, location ID, report ID, and RunID.
- Enabled distributor upload failure is a report failure and should produce a nonzero command exit.
Stage 1: Secrets Directory Support
Goal: implement generic file-backed environment secret loading in
internal/config.
Implementation guidance:
- Add
secrets.directoryconfig with a built-in default of"", meaning disabled. - Load secrets after config file parsing and CLI overrides, before validation completes.
- Read only regular files directly under the configured directory.
- Map each file basename to an environment variable name and file contents to the value.
- Validate names with
[A-Za-z_][A-Za-z0-9_]*. - Reject missing configured directory, invalid filenames, subdirectories, symlinks, unreadable files, and empty filenames.
- Strip one trailing
\nor\r\n; preserve all other bytes. - Keep error messages actionable but do not include secret values.
Acceptance criteria:
- Empty
secrets.directoryperforms no environment changes. - Valid secret files set environment variables after config loading.
- Secret file values overwrite existing environment variables.
- Invalid entries fail before application workflows run.
- Existing config loading behavior remains unchanged when secrets are disabled.
Tests:
- Defaults leave secrets loading disabled.
- Valid file sets an environment variable.
- File value overwrites an existing environment variable.
- Missing directory, invalid filename, subdirectory, symlink, and unreadable file cases fail.
- Newline and CRLF trimming works exactly once.
- Secret values are absent from error strings.
Stage 2: Notify Configuration And Templates
Goal: add disabled-by-default notify.distributor config and template
validation.
Planned config:
notify:
distributor:
enabled: false
endpoint: https://distributor.example.com
token_env: DISTRIBUTOR_UPLOAD_TOKEN
timeout: 30s
failure_policy: error
bundle_id_template: "weatherreporter.{location_id}.{report_id}.{run_id}"
idempotency_key_template: "{bundle_id}"
report_path_template: "{batch_output_name}"
Implementation guidance:
- Add notify/distributor config structs, defaults, YAML loading, and validation
under
internal/config. - When disabled, omit endpoint and token requirements.
- When enabled, validate absolute endpoint URL, non-empty
token_env, positive timeout,failure_policy=error, known template variables, and valid rendered report bundle paths. - Supported template variables are
location_id,report_id,run_id,artifact_group, andbatch_output_name;idempotency_key_templatemay also reference{bundle_id}. - Do not add distributor-specific CLI flags.
Acceptance criteria:
- Defaults include disabled distributor notification.
- Enabled config rejects invalid endpoint, token env, timeout, failure policy, templates, and rendered bundle paths.
- Rendered bundle paths reject absolute paths, backslashes,
.,.., empty segments,manifest.json, and.distributor.json. - Tokens supplied by
secrets.directoryare visible throughtoken_envafter config loading.
Tests:
- Config defaults and examples load.
- Disabled distributor config accepts omitted distributor fields.
- Enabled validation covers each invalid field.
- Template rendering and validation are covered with table tests.
Stage 3: Distributor Adapter
Goal: isolate distributor upload behavior in internal/adapters/distributor.
Implementation guidance:
- Add weatherreporter-owned request and result types.
- Keep
gitea.maximumdirect.net/eric/distributor/pkg/uploadandgitea.maximumdirect.net/eric/distributor/pkg/bundletypes inside the adapter. - Read the bearer token from
token_envafter config/secrets loading. - Use
UploadFileswith exactly one file: source path is the managed Markdown report path and bundle path is the renderedreport_path_template. - Return the accepted distributor run ID when available.
- Wrap endpoint, bundle ID, idempotency key, source path, and bundle path context into errors without exposing token values.
- Preserve idempotency conflict diagnosis.
Acceptance criteria:
- No distributor dependency types leak outside the adapter.
- Missing token, source path, bundle path, or upload client inputs fail with actionable errors.
- Upload success returns a weatherreporter-owned result.
- Upload failure errors do not include token values.
Tests:
- Fake upload client receives expected endpoint, token, bundle ID, idempotency key, source path, and bundle path.
- Missing token/source path/bundle path errors are actionable.
- Token values are not present in errors.
- Upload conflict and generic failure are wrapped clearly.
Stage 4: App Notification Hook
Goal: add app-level notification orchestration after successful report generation.
Implementation guidance:
- Add an app-owned notifier interface for tests.
- Default to no-op notification when distributor notification is disabled.
- Notify only after successful
scriptorium runand final metadata save. - Use
ReportResult.ReportPathas the distributor source file. - Never use
--outor--out-dircopies as distributor source files. - Do not notify after Weather API, briefing, prompt input, render preflight, or Scriptorium run failure.
- Single-report generation should return an error when enabled notification fails.
- Batch generation should continue independent reports; notification failure marks that report failed and makes the aggregate batch nonzero.
Acceptance criteria:
- Disabled notification leaves current generation behavior unchanged.
- Successful notification is recorded in the app result when useful.
- Enabled notification failure fails a single-report command.
- Batch results distinguish generation failure from notification failure enough for operators to diagnose the failed report.
- Existing artifact paths, output copies, and Scriptorium behavior remain unchanged.
Tests:
- No notifier call occurs on pre-generation failures.
- Notifier receives the managed report path.
- Enabled notifier success and failure are covered.
- Batch continues after one notification failure and reports aggregate failure.
Stage 5: CLI Output And Batch Reporting
Goal: keep public CLI syntax stable while surfacing notification outcome if the app records it.
Implementation guidance:
- Do not add distributor-specific CLI flags.
- Preserve current command names, help shape, generated paths,
--out, and--out-dir. - If batch JSON is extended, use explicit fields such as
notificationStatus,notificationRunId, andnotificationError. - Keep stderr status lines concise and secret-free.
Acceptance criteria:
weatherreporter --helpremains accurate.- Existing CLI parser behavior remains stable.
- Batch JSON includes notification details only when implemented and documented.
- CLI stdout and stderr do not include token values.
Tests:
- Existing CLI parser tests pass, except intentional JSON additions.
- Batch output covers notification success and failure.
- Secret-like token values are absent from CLI output.
Stage 6: Documentation
Goal: update implemented documentation after the feature exists.
Implementation guidance:
- Update
docs/config.mdwithsecrets.directory,notify.distributor, defaults, and secret handling. - Update
docs/operations.mdwith notification timing, failure behavior, and managed report upload source. - Update
docs/troubleshooting.mdwith invalid secrets directory, missing token, upload conflict, upload rejection, and distributor unavailable cases. - Update
docs/internal/app-orchestration.mdto include notify ordering. - Add
docs/internal/distributor-adapter.mdfor adapter inputs, outputs, boundaries, config fields, failure behavior, tests, and invariants. - Keep full upstream distributor contract details in
docs/integrations/distributor/.
Acceptance criteria:
- Non-roadmap docs describe only implemented behavior.
- Config examples load and contain no raw secrets.
- Secret handling docs describe mechanisms, not secret values.
- Docs distinguish weatherreporter upload responsibility from distributor destination routing and Markdown-to-HTML transformation.
Tests and checks:
- Run config example loading tests.
- Search docs and examples for accidental raw tokens.
- Run
git diff --check.
Stage 7: Final Validation
Goal: verify behavior without a live distributor service.
Validation commands:
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
go test ./internal/config ./internal/app ./internal/cli ./internal/adapters/distributor
Manual validation:
- No token values appear in errors, CLI output, docs, examples, metadata, or batch JSON.
go.modincludes distributor only after adapter implementation requires it.- Existing report paths, RunIDs, and
--out/--out-dirbehavior are unchanged. - Distributor upload tests use fakes or local test doubles, not a live service.
Deferred Work
These are out of scope for the initial implementation:
failure_policy: warn.- Uploading metadata, briefing, data package, or preflight artifacts.
- Distributor status polling after
202 Accepted. - Durable upload retry queues.
- Distributor-specific CLI flags.
- Making distributor scan
workspace/. - Destination routing, Markdown-to-HTML transformation, public URLs, or nginx
layout in
weatherreporter.
Global Validation Checklist
- Preserve current public CLI syntax.
- Preserve managed workspace artifact paths.
- Keep distributor dependency types behind
internal/adapters/distributor. - Keep secrets out of user-facing and persisted output.
- Keep examples valid and secret-free.
- Update implemented docs in the same change as implementation.
- Run
go test ./...,go run ./cmd/weatherreporter --help, andgit diff --checkbefore finishing implementation.