Implemented multiple campaign support via a campaign directory registry with explicit campaign IDs
This commit is contained in:
231
docs/roadmap/campaign.md
Normal file
231
docs/roadmap/campaign.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# 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>`.
|
||||
Reference in New Issue
Block a user