Files
narratio/docs/roadmap/campaign.md

232 lines
8.0 KiB
Markdown

# Roadmap: Campaign Registry
Status: Implemented
## Problem
Narratio currently treats campaign configuration as one selected
`campaign.yml` file:
- command flags use `--campaign <path>`;
- default discovery searches fixed system file locations;
- `campaign.yml` uses `campaign:` as the identity field.
That model works for a single campaign, but it is awkward for installations
that manage multiple campaigns. Operators need to pass file paths or maintain a
single global campaign config, while the newer session-oriented CLI already
uses concise positional session IDs and remote session lookup.
The campaign selection model should become ID-based and pipeline-owned.
Pipeline config should describe where campaigns live, commands should select a
campaign by ID, and each campaign directory should contain its stable campaign
materials.
## Target Model
`pipeline.yml` owns the campaign registry:
campaigns:
root: /usr/local/share/narratio/campaigns
default_campaign_id: dilfs
Campaign files live at the conventional path:
{campaigns.root}/{campaign_id}/campaign.yml
The first implementation should use only the conventional path. Recursive
discovery of every `campaign.yml` under `campaigns.root` is deferred to a
future stage.
Each campaign file uses `campaign_id` as the canonical identity field:
campaign_id: dilfs
session_template_file: ./session.template.yml
inputs:
speakers_file: ./speakers.yml
autocorrect_file: ./autocorrect.yml
glossary_file: ./glossary.yml
Campaign-relative files continue to resolve relative to the selected
`campaign.yml`, including stable input files and `session_template_file`.
The public CLI changes from path-based campaign selection to ID-based campaign
selection:
- `--campaign <id>` selects a campaign ID.
- `--campaign-file <path>` explicitly loads one campaign file for
development, tests, and unusual local workflows.
- `--campaign` and `--campaign-file` are mutually exclusive.
If neither `--campaign` nor `--campaign-file` is passed, Narratio uses
`pipeline.campaigns.default_campaign_id`. If no campaign can be selected,
commands fail clearly before session loading or stage execution.
Resolved campaign ID remains the campaign segment used for:
- workspace paths;
- spool paths;
- S3 session prefixes;
- remote `session.yml` lookup;
- archive locks and promoted output keys;
- session/campaign mismatch validation;
- status, plan, restore, and helper output.
## Compatibility Policy
This is a breaking public/config contract change.
After the cutover:
- `--campaign` no longer accepts a filesystem path;
- default fixed campaign file discovery is removed;
- `campaign:` is no longer accepted in `campaign.yml`;
- `campaign_id:` is required.
Keep `--campaign-file` as the only explicit file override. Do not retain hidden
aliases for the old `--campaign <path>` behavior.
## Implementation Stages
### Stage 1: Add Campaign Registry Selection
Status: Implemented
Add the registry model and switch command loading to resolve campaigns through
pipeline config.
Implementation requirements:
- Add `pipeline.campaigns.root`.
- Add `pipeline.campaigns.default_campaign_id`.
- Add `campaign_id` to campaign config and make it the canonical identity.
- Resolve pipeline config first, then campaign selection.
- Use this selection order:
1. explicit `--campaign-file <path>`;
2. explicit `--campaign <id>`;
3. `pipeline.campaigns.default_campaign_id`;
4. fail clearly.
- For ID selection, load `{campaigns.root}/{campaign_id}/campaign.yml`.
- Validate that the loaded `campaign_id` matches the selected ID.
- Reject `--campaign` with `--campaign-file`.
- Preserve strict YAML decoding.
- Preserve campaign-relative stable input and session template resolution.
- Keep storage details behind the existing storage adapter and object-store
helper.
- Keep remote session lookup and archive key construction based on the
resolved campaign ID.
Acceptance criteria:
- Commands can run with only a pipeline config and the pipeline default
campaign ID.
- Commands can select another campaign with `--campaign <id>`.
- Commands can load a specific file with `--campaign-file <path>`.
- Existing session loading, remote session fallback, prepare materialization,
restore, archive, locks, clean, analyze, and publish behavior continue to use
the same resolved campaign identity.
- No generic config registry framework is introduced.
### Stage 2: Remove Old Single-File Campaign Behavior
Status: Implemented
Remove the old public campaign file model after registry selection is in
place.
Implementation requirements:
- Remove fixed default campaign config discovery from command loading.
- Remove `DefaultCampaignConfigSearchPaths` and related path-only resolution if
no current tests or helpers still need them.
- Remove support for `campaign:` from `campaign.yml`.
- Update validation errors to refer to `campaign_id`.
- Update examples to use campaign directories and `campaign_id`.
- Update current-behavior docs to document:
- `pipeline.campaigns.root`;
- `pipeline.campaigns.default_campaign_id`;
- `campaign_id`;
- `--campaign <id>`;
- `--campaign-file <path>`.
- Update troubleshooting examples that currently pass `--campaign <path>`.
Acceptance criteria:
- `campaign.yml` files with `campaign:` fail strict decoding.
- `--campaign /path/to/campaign.yml` is treated as a campaign ID and fails
unless that ID exists under `campaigns.root`.
- `--campaign-file /path/to/campaign.yml` is the supported file override.
- User-facing docs no longer describe fixed campaign config discovery.
## Test Guidance
Focused tests:
- `go test ./internal/config -v`
- `go test ./internal/app -v`
- `go test ./internal/stage -run Prepare -v`
Full validation:
- `go test ./...`
Config tests to add or update:
- strict decode accepts `pipeline.campaigns.root`;
- strict decode accepts `pipeline.campaigns.default_campaign_id`;
- strict decode accepts `campaign_id`;
- selected campaign ID mismatch fails;
- missing campaign root fails when ID selection is needed;
- missing default campaign ID fails when no explicit campaign selector is
passed;
- old `campaign:` fails after Stage 2.
App tests to add or update:
- `--campaign <id>` resolves `{campaigns.root}/{id}/campaign.yml`;
- omitted `--campaign` uses `pipeline.campaigns.default_campaign_id`;
- `--campaign-file` loads an explicit campaign file;
- `--campaign` plus `--campaign-file` fails;
- remote session fallback uses the resolved campaign ID;
- `session init`, `run`, `run-stage`, `resume`, `analyze`, `publish`, `clean`,
and `session` subcommands all use the same campaign selection path;
- path-based `--campaign` examples and tests are removed after Stage 2.
## Documentation Guidance
Update current-behavior docs only after implementation lands:
- `docs/config.md`
- `docs/cli.md`
- `docs/operations.md`
- `docs/troubleshooting.md`
- relevant files under `docs/internal/`
- `examples/`
Planned campaign registry behavior belongs only in this roadmap until the code,
tests, examples, and current-behavior docs are updated.
## Architecture Guardrails
- Keep Narratio explicit and stage-driven.
- Do not introduce a generic configuration registry or workflow framework.
- Keep YAML decoding strict.
- Keep defaults centralized and testable.
- Keep campaign-relative path resolution centralized.
- Use centralized S3 and workspace path helpers.
- Keep storage details behind `storage.ObjectStore`.
- Keep secret-backed object-store construction in `internal/app`.
- Preserve manifest-driven resume and restore behavior.
- Do not store raw secrets in campaign configs, manifests, logs, generated
configs, or archive metadata.
## Assumptions
- The canonical pipeline schema is grouped under `campaigns`.
- The canonical campaign identity field is `campaign_id`.
- `--campaign` means campaign ID.
- `--campaign-file` is retained as an explicit override.
- Recursive discovery is planned but not part of the first implementation.
- Existing production configs can be migrated from `campaign:` to
`campaign_id:` and from `--campaign <path>` to `--campaign <id>` or
`--campaign-file <path>`.