8.0 KiB
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.ymlusescampaign: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.--campaignand--campaign-fileare 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.ymllookup; - 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:
--campaignno longer accepts a filesystem path;- default fixed campaign file discovery is removed;
campaign:is no longer accepted incampaign.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_idto campaign config and make it the canonical identity. - Resolve pipeline config first, then campaign selection.
- Use this selection order:
- explicit
--campaign-file <path>; - explicit
--campaign <id>; pipeline.campaigns.default_campaign_id;- fail clearly.
- explicit
- For ID selection, load
{campaigns.root}/{campaign_id}/campaign.yml. - Validate that the loaded
campaign_idmatches the selected ID. - Reject
--campaignwith--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
DefaultCampaignConfigSearchPathsand related path-only resolution if no current tests or helpers still need them. - Remove support for
campaign:fromcampaign.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.ymlfiles withcampaign:fail strict decoding.--campaign /path/to/campaign.ymlis treated as a campaign ID and fails unless that ID exists undercampaigns.root.--campaign-file /path/to/campaign.ymlis the supported file override.- User-facing docs no longer describe fixed campaign config discovery.
Test Guidance
Focused tests:
go test ./internal/config -vgo test ./internal/app -vgo 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
--campaignusespipeline.campaigns.default_campaign_id; --campaign-fileloads an explicit campaign file;--campaignplus--campaign-filefails;- remote session fallback uses the resolved campaign ID;
session init,run,run-stage,resume,analyze,publish,clean, andsessionsubcommands all use the same campaign selection path;- path-based
--campaignexamples and tests are removed after Stage 2.
Documentation Guidance
Update current-behavior docs only after implementation lands:
docs/config.mddocs/cli.mddocs/operations.mddocs/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. --campaignmeans campaign ID.--campaign-fileis retained as an explicit override.- Recursive discovery is planned but not part of the first implementation.
- Existing production configs can be migrated from
campaign:tocampaign_id:and from--campaign <path>to--campaign <id>or--campaign-file <path>.