337 lines
14 KiB
Markdown
337 lines
14 KiB
Markdown
# 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
|
|
|
|
- `weatherreporter` does not currently implement a notify hook.
|
|
- Generated Markdown reports are written to the managed report path returned as
|
|
`ReportResult.ReportPath`.
|
|
- Optional `--out` and `--out-dir` copies 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.UploadFiles` for
|
|
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/distributor` adapter for the external
|
|
integration.
|
|
- Keep `gitea.maximumdirect.net/eric/distributor/pkg/upload` and
|
|
`gitea.maximumdirect.net/eric/distributor/pkg/bundle` types 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`, or `storm.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:
|
|
|
|
```yaml
|
|
secrets:
|
|
directory: ""
|
|
```
|
|
|
|
Add future distributor configuration under `notify.distributor`:
|
|
|
|
```yaml
|
|
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.directory` defaults to an empty string, which disables secrets
|
|
loading.
|
|
- When `secrets.directory` is 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 `\n` or `\r\n` from secret file contents for compatibility
|
|
with common mounted-secret formats. Preserve all other bytes.
|
|
- `enabled` defaults to `false`.
|
|
- `endpoint` is required only when distributor notification is enabled.
|
|
- `token_env` is 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.
|
|
- `timeout` controls the upload call timeout and should default to `30s`.
|
|
- `failure_policy` should initially support `error`. Reserve `warn` as 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`, and
|
|
`batch_output_name`.
|
|
- `idempotency_key_template` may reference `{bundle_id}` after the bundle ID is
|
|
rendered.
|
|
- `report_path_template` should 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.directory` config field under `internal/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 `\n` or `\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.directory` performs 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_env` must be
|
|
non-empty, timeout must be positive, failure policy must be supported, and
|
|
templates must use only known variables.
|
|
- Keep `token_env` as 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.directory` is visible through
|
|
the configured `token_env` name after config loading.
|
|
|
|
### Stage 3: Distributor Adapter
|
|
|
|
Goal: isolate distributor package usage behind an internal adapter.
|
|
|
|
Implementation guidance:
|
|
|
|
- Add `internal/adapters/distributor` with 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 `UploadFiles` with one file:
|
|
the managed Markdown report as the source path and the rendered
|
|
`report_path_template` as 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 `--out` or
|
|
`--out-dir` copy 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 --help` remains 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.md` with `secrets.directory`,
|
|
`notify.distributor` fields, and defaults.
|
|
- Update `docs/operations.md` with notification timing, failure behavior, and
|
|
the fact that managed report paths are uploaded.
|
|
- Update `docs/troubleshooting.md` with invalid secrets directory, missing
|
|
token, upload conflict, upload rejection, and distributor unavailable cases.
|
|
- Update `docs/internal/app-orchestration.md` to include notify ordering.
|
|
- Add `docs/internal/distributor-adapter.md` for 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:
|
|
|
|
```bash
|
|
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.mod` includes 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.
|