14 KiB
Distributor Integration Roadmap
This roadmap describes planned work for adding distributor notification
support to weatherreporter. The feature is not implemented yet, so this plan
lives under docs/roadmap/; non-roadmap documentation should not describe
notify.distributor as available until implementation is complete.
Purpose
distributor collects outputs from producer applications and publishes them
through configured downstream pipelines. For weatherreporter, the planned
integration should upload generated Markdown weather reports after successful
report generation, without requiring distributor to scan or understand the
managed workspace/ layout.
Current Repository Facts
weatherreporterdoes not currently implement a notify hook.- Generated Markdown reports are written to the managed report path returned as
ReportResult.ReportPath. - Optional
--outand--out-dircopies are user-requested extra copies and are not canonical integration inputs. - The managed workspace also contains briefing snapshots, metadata, prompt data packages, and preflight artifacts; these should not be treated as a distributor source tree.
- The provided distributor docs recommend
pkg/upload.UploadFilesfor producers that already have generated files, so the initial integration can upload selected report files directly.
Decisions Locked
- Add generic configurable secrets-directory support before the distributor adapter work. This is project infrastructure, not a distributor-only token helper.
- When a secrets directory is configured, each regular file directly under that directory maps filename to environment variable name and file contents to environment variable value.
- Secret-file values overwrite pre-existing environment variables.
- Secrets loading must never log, print, persist, or include secret values in errors, metadata, batch output, examples, or docs.
- Add a narrow
internal/adapters/distributoradapter for the external integration. - Keep
gitea.maximumdirect.net/eric/distributor/pkg/uploadandgitea.maximumdirect.net/eric/distributor/pkg/bundletypes inside that adapter. - Upload one distributor bundle per successfully generated report.
- Include only the generated Markdown report in the initial bundle.
- Use the report definition's batch output filename as the default
bundle-relative Markdown path, such as
daily.md,tomorrow.md,three-day.md,weekend.md, orstorm.md. - Derive the default bundle ID and idempotency key from producer name, location ID, report ID, and RunID.
- Treat distributor upload failure as a report failure when notification is enabled; single-report and batch commands should exit nonzero for enabled upload failures.
- Keep destination routing, Markdown-to-HTML transformation, public URLs, and
nginx directory layout in distributor configuration, not in
weatherreporter.
Planned Configuration
Add future generic secrets configuration:
secrets:
directory: ""
Add future distributor configuration under notify.distributor:
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}"
Planned behavior:
secrets.directorydefaults to an empty string, which disables secrets loading.- When
secrets.directoryis configured, load only regular files directly under that directory. - Each secret filename must be a valid environment variable name matching
[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\nfrom secret file contents for compatibility with common mounted-secret formats. Preserve all other bytes. enableddefaults tofalse.endpointis required only when distributor notification is enabled.token_envis required only when enabled. Raw bearer tokens should not be stored in config files. The token value is read from normal environment state after secrets-directory loading has been applied.timeoutcontrols the upload call timeout and should default to30s.failure_policyshould initially supporterror. Reservewarnas a future option only if it is implemented and tested.- Template variables should be explicit and validated:
location_id,report_id,run_id,artifact_group, andbatch_output_name. idempotency_key_templatemay reference{bundle_id}after the bundle ID is rendered.report_path_templateshould be user-visible from the first implementation because bundle-relative paths can affect downstream distributor routing.- Rendered template values must produce valid distributor bundle paths: relative
slash-separated paths with no empty path, absolute path, backslash,
.,.., empty segment,manifest.json, or.distributor.json.
Recommended Implementation Stages
Stage 1: Secrets Directory Support
Goal: add generic file-backed environment secret loading before distributor notification depends on environment tokens.
Implementation guidance:
- Add a generic
secrets.directoryconfig field underinternal/config. - Default the directory to an empty string so secrets loading is disabled unless explicitly configured.
- Load secrets after config file parsing and before validation that needs environment-backed values.
- Map each regular file directly under the configured directory to an environment variable: filename becomes the variable name and contents become the value.
- Use secret-file values to overwrite pre-existing environment variables.
- Strip one trailing
\nor\r\n; preserve all other bytes. - Reject missing directories, invalid environment-variable filenames, subdirectories, symlinks, unreadable files, and empty filenames.
- Keep secret values out of errors, logs, CLI output, metadata, and examples.
Acceptance criteria:
- Empty
secrets.directoryperforms no environment changes. - Configured secrets directory values are available through normal environment lookup after config loading.
- Secret files overwrite existing environment variables with the same name.
- Invalid directory entries fail with path/name context but without secret values.
- Existing config loading behavior remains unchanged when secrets are disabled.
Suggested tests:
- Config defaults leave secrets loading disabled.
- A configured missing secrets directory fails.
- Regular files set environment variables and overwrite existing values.
- Invalid filenames, subdirectories, symlinks, and unreadable files fail.
- One trailing newline or CRLF is stripped; other content is preserved.
- No secret values appear in returned errors.
Stage 2: Config And Validation
Goal: add disabled-by-default distributor notification configuration.
Implementation guidance:
- Add notify/distributor config structs under
internal/config. - Add defaults for disabled notification, timeout, failure policy, bundle ID template, idempotency key template, and report path template.
- Validate enabled config: endpoint must be an absolute URL,
token_envmust be non-empty, timeout must be positive, failure policy must be supported, and templates must use only known variables. - Keep
token_envas the configured token selector. Do not add raw token config. - Update maintained examples and config documentation only after code support is implemented.
Acceptance criteria:
- Disabled distributor notification requires no endpoint or token env.
- Enabled distributor notification rejects invalid endpoint, missing token env, nonpositive timeout, unsupported failure policy, unknown template variables, and invalid rendered report bundle paths.
- Existing config loading behavior and precedence remain unchanged.
Suggested tests:
- Config defaults include disabled distributor notification.
- Example configs load successfully.
- Enabled config validation covers required fields and invalid template cases.
- A distributor token supplied through
secrets.directoryis visible through the configuredtoken_envname after config loading.
Stage 3: Distributor Adapter
Goal: isolate distributor package usage behind an internal adapter.
Implementation guidance:
- Add
internal/adapters/distributorwith package-owned request and result types. - Read the bearer token from the configured environment variable after secrets-directory loading has populated environment state.
- Use distributor
UploadFileswith one file: the managed Markdown report as the source path and the renderedreport_path_templateas the bundle path. - Return upload result information that app orchestration can record or expose without leaking distributor dependency types.
- Wrap upload errors with endpoint, bundle ID, and report path context while avoiding token exposure.
Acceptance criteria:
- No distributor dependency types leak outside
internal/adapters/distributor. - Missing source report path, missing token, invalid bundle path, and upload failure return actionable errors.
- Idempotency conflict errors are preserved or wrapped clearly enough for troubleshooting.
Suggested tests:
- Adapter request construction uses the configured endpoint, token, bundle ID, idempotency key, source report path, and bundle-relative path.
- Token values do not appear in returned errors.
- Upload conflict and generic upload failure produce useful wrapped errors.
- Tokens supplied through the secrets directory are accepted via
token_env.
Stage 4: App Notify Hook
Goal: upload successful generated reports through the configured notifier.
Implementation guidance:
- Add an app-owned notifier interface so app tests can use fakes.
- Construct distributor notification requests from the successful
ReportResult, resolved report definition, configured location, and notification config. - Invoke notification only after Scriptorium report generation succeeds and final metadata has been saved.
- Do not notify after Weather API, briefing, prompt input, render preflight, or Scriptorium run failure.
- In batch mode, notify each successful report independently. If notification fails and distributor notification is enabled, mark that report failed and make the aggregate batch result nonzero.
Acceptance criteria:
- Disabled notification is a no-op.
- Single-report generation returns an error when enabled upload fails.
- Batch generation continues independent reports, records upload failures per report, and exits nonzero when any enabled notification fails.
- Existing report artifact paths, optional output copies, and Scriptorium behavior remain unchanged.
Suggested tests:
- No notifier call occurs before successful report generation.
- Successful notifier call receives the managed report path, not
--outor--out-dircopy paths. - Enabled notifier failure affects single-report and batch command outcomes.
- Batch still continues later reports after one notification failure.
Stage 5: CLI And Output Behavior
Goal: preserve CLI syntax while surfacing notification outcome where useful.
Implementation guidance:
- Do not add distributor-specific CLI flags in the first implementation; use configuration only.
- Preserve existing command names, flags, help text shape, workspace paths, and generated Markdown outputs.
- Extend batch JSON report items only if the implementation records useful notification status, accepted distributor run ID, or notification error.
- Keep stderr status lines concise and avoid exposing secrets.
Acceptance criteria:
go run ./cmd/weatherreporter --helpremains accurate.- Existing generate and run command syntax remains stable.
- Batch summaries clearly indicate notification-caused report failures if notification status is added.
Suggested tests:
- Existing CLI parser tests continue to pass.
- Batch JSON includes notification fields only when implemented and documented.
- Secret-like token values never appear in CLI output.
Stage 6: Documentation
Goal: document implemented distributor behavior after the feature exists.
Implementation guidance:
- Update
docs/config.mdwithsecrets.directory,notify.distributorfields, and defaults. - Update
docs/operations.mdwith notification timing, failure behavior, and the fact that managed report paths are uploaded. - 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 the adapter contract. - Keep distributor API details in
docs/integrations/distributor/; link there instead of duplicating the full upstream contract.
Acceptance criteria:
- Non-roadmap docs describe only implemented notification behavior.
- Config examples include no raw tokens.
- Secret handling documentation describes mechanisms, not secret values.
- Documentation clearly distinguishes distributor destination routing from weatherreporter upload responsibilities.
Stage 7: Final Validation
Goal: verify the feature without requiring live distributor service access.
Validation commands:
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
Additional validation:
- Run focused config, adapter, app, and CLI tests.
- Confirm maintained example configs load.
- Search for accidental raw-token config examples.
- Confirm
go.modincludes distributor only after implementation requires it. - Confirm no test failures leak secret values in error output.
Open Questions
No questions block this roadmap. The initial implementation should use the locked defaults above and expose configurable templates for bundle identity and bundle-relative report path. Secrets-directory support is generic project infrastructure and should remain useful for future integrations beyond distributor.