Implemented multiple campaign support via a campaign directory registry with explicit campaign IDs
This commit is contained in:
45
docs/cli.md
45
docs/cli.md
@@ -6,9 +6,9 @@
|
||||
narratio run 2026-04-04
|
||||
```
|
||||
|
||||
This command uses default system discovery for `pipeline.yml`, `campaign.yml`, and local `session.yml`. If local session discovery misses and S3 storage is configured, the positional session ID loads remote `session.yml` from the canonical session prefix.
|
||||
This command uses default system discovery for `pipeline.yml`, the pipeline default campaign ID, and local `session.yml`. If local session discovery misses and S3 storage is configured, the positional session ID loads remote `session.yml` from the canonical session prefix.
|
||||
|
||||
Default discovery checks system config locations only. Pass `--config`, `--campaign`, and `--session` to use files from the current working directory.
|
||||
Default pipeline and session discovery checks system config locations only. Pass `--config`, `--campaign-file`, and `--session` to use files from the current working directory. Pass `--campaign <id>` to select a campaign from `pipeline.campaigns.root`.
|
||||
|
||||
Ordinary local and remote `session.yml` files must be concrete YAML. Templates belong to `narratio session init`, which renders a configured campaign template before writing the concrete file.
|
||||
|
||||
@@ -45,7 +45,8 @@ For config semantics, see [docs/config.md](./config.md). For operator lifecycle
|
||||
Most session-aware commands accept:
|
||||
|
||||
- `--config <path>`: optional explicit `pipeline.yml` path.
|
||||
- `--campaign <path>`: optional explicit `campaign.yml` path.
|
||||
- `--campaign <id>`: optional campaign ID selector.
|
||||
- `--campaign-file <path>`: optional explicit `campaign.yml` path.
|
||||
- `--session <path>`: optional explicit concrete `session.yml` path.
|
||||
- `--previous-session-id <value>`: expected previous session identifier.
|
||||
|
||||
@@ -56,7 +57,7 @@ The positional `<session_id>` is required even when `--session` is provided. It
|
||||
### `run`
|
||||
|
||||
```bash
|
||||
narratio run <session_id> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--force] [--artifacts <name[,name...]>]
|
||||
narratio run <session_id> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--force] [--artifacts <name[,name...]>]
|
||||
```
|
||||
|
||||
Purpose:
|
||||
@@ -66,7 +67,8 @@ Success output:
|
||||
- `narratio run: session <session_id>; executed=<n> skipped=<n>; manifest=<path>`
|
||||
|
||||
Common failure cases:
|
||||
- missing system default config/campaign/session paths when flags are omitted.
|
||||
- missing system default config/session paths when flags are omitted.
|
||||
- missing selected campaign under `pipeline.campaigns.root`.
|
||||
- missing local session plus missing/unavailable remote `session.yml`.
|
||||
- templated `session.yml`; run `narratio session init` to generate concrete YAML.
|
||||
- concrete session identity mismatch.
|
||||
@@ -75,7 +77,7 @@ Common failure cases:
|
||||
### `resume`
|
||||
|
||||
```bash
|
||||
narratio resume <session_id> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--force] [--artifacts <name[,name...]>]
|
||||
narratio resume <session_id> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--force] [--artifacts <name[,name...]>]
|
||||
```
|
||||
|
||||
Purpose:
|
||||
@@ -88,7 +90,7 @@ Success output:
|
||||
### `run-stage`
|
||||
|
||||
```bash
|
||||
narratio run-stage <stage> <session_id> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--force] [--artifacts <name[,name...]>]
|
||||
narratio run-stage <stage> <session_id> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--force] [--artifacts <name[,name...]>]
|
||||
```
|
||||
|
||||
Valid stage names:
|
||||
@@ -111,7 +113,7 @@ Success output:
|
||||
### `analyze`
|
||||
|
||||
```bash
|
||||
narratio analyze <session_id> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--artifacts <name[,name...]>]
|
||||
narratio analyze <session_id> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--artifacts <name[,name...]>]
|
||||
```
|
||||
|
||||
Purpose:
|
||||
@@ -123,7 +125,7 @@ Purpose:
|
||||
### `publish`
|
||||
|
||||
```bash
|
||||
narratio publish <session_id> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--artifacts <name[,name...]>]
|
||||
narratio publish <session_id> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--artifacts <name[,name...]>]
|
||||
```
|
||||
|
||||
Purpose:
|
||||
@@ -135,7 +137,7 @@ Purpose:
|
||||
### `clean`
|
||||
|
||||
```bash
|
||||
narratio clean <session_id> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--dry-run] [--clear-cache]
|
||||
narratio clean <session_id> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--dry-run] [--clear-cache]
|
||||
narratio clean --all [--config <pipeline.yml>] [--dry-run] [--clear-cache]
|
||||
```
|
||||
|
||||
@@ -156,7 +158,7 @@ Cache behavior:
|
||||
### `session plan`
|
||||
|
||||
```bash
|
||||
narratio session plan <session_id> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--force]
|
||||
narratio session plan <session_id> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--force]
|
||||
```
|
||||
|
||||
Purpose:
|
||||
@@ -170,7 +172,7 @@ Success output includes:
|
||||
### `session status`
|
||||
|
||||
```bash
|
||||
narratio session status <session_id> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>]
|
||||
narratio session status <session_id> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>]
|
||||
```
|
||||
|
||||
Output includes:
|
||||
@@ -183,7 +185,7 @@ Output includes:
|
||||
### `session validate`
|
||||
|
||||
```bash
|
||||
narratio session validate <session_id> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>]
|
||||
narratio session validate <session_id> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>]
|
||||
```
|
||||
|
||||
Checks include:
|
||||
@@ -200,7 +202,8 @@ Warnings do not fail the command. Any `ERROR` finding exits non-zero.
|
||||
```bash
|
||||
narratio session init <session_id> --output ./session.yml
|
||||
narratio session init <session_id> --remote
|
||||
narratio session init <session_id> --config <pipeline.yml> --campaign <campaign.yml> --remote
|
||||
narratio session init <session_id> --config <pipeline.yml> --campaign icewind --remote
|
||||
narratio session init <session_id> --config <pipeline.yml> --campaign-file ./campaign.yml --remote
|
||||
```
|
||||
|
||||
Additional flags:
|
||||
@@ -214,7 +217,9 @@ Additional flags:
|
||||
|
||||
Behavior:
|
||||
- exactly one of `--output` or `--remote` is required.
|
||||
- `--config` and `--campaign` are optional overrides; omitted values use normal default config discovery.
|
||||
- `--config`, `--campaign`, and `--campaign-file` are optional overrides; omitted campaign selection uses `pipeline.campaigns.default_campaign_id`.
|
||||
- `--campaign <id>` selects a campaign under `pipeline.campaigns.root`.
|
||||
- `--campaign-file <path>` loads an explicit campaign file.
|
||||
- if `campaign.yml` sets `session_template_file`, the template path is resolved relative to `campaign.yml` and rendered from init flags.
|
||||
- if no session template is configured, a minimal concrete session file is generated directly.
|
||||
- template variables must be supplied by matching flags, and supplied template-related flags must be used by the template.
|
||||
@@ -225,7 +230,7 @@ Behavior:
|
||||
### `session restore`
|
||||
|
||||
```bash
|
||||
narratio session restore <session_id> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--dry-run] [--force] [--include-audio]
|
||||
narratio session restore <session_id> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--dry-run] [--force] [--include-audio]
|
||||
```
|
||||
|
||||
Purpose:
|
||||
@@ -241,7 +246,7 @@ When `--include-audio` is set, S3 audio files are restored through the shared au
|
||||
### `session artifacts`
|
||||
|
||||
```bash
|
||||
narratio session artifacts <session_id> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--remote]
|
||||
narratio session artifacts <session_id> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--remote]
|
||||
```
|
||||
|
||||
Purpose:
|
||||
@@ -252,9 +257,9 @@ Purpose:
|
||||
### `session locks`
|
||||
|
||||
```bash
|
||||
narratio session locks <session_id> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>]
|
||||
narratio session locks add <session_id> <source> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--reason <text>] [--force]
|
||||
narratio session locks remove <session_id> <source> [--config <pipeline.yml>] [--campaign <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>]
|
||||
narratio session locks <session_id> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>]
|
||||
narratio session locks add <session_id> <source> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>] [--reason <text>] [--force]
|
||||
narratio session locks remove <session_id> <source> [--config <pipeline.yml>] [--campaign <id>] [--campaign-file <campaign.yml>] [--session <session.yml>] [--previous-session-id <id>]
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
@@ -28,6 +28,7 @@ Behavior:
|
||||
- strict YAML decode is enabled (`KnownFields(true)`): unknown fields fail.
|
||||
- ordinary local and remote `session.yml` files must be concrete YAML; template placeholders are rejected.
|
||||
- defaults are applied for optional pipeline fields.
|
||||
- campaign identity is selected by ID from the pipeline campaign registry unless `--campaign-file` is used.
|
||||
- campaign-level stable input paths fill missing session input paths.
|
||||
- session-level stable input paths override campaign-level input paths.
|
||||
- campaign config may point `session init` to a session template.
|
||||
@@ -60,11 +61,14 @@ Pipeline config lookup:
|
||||
|
||||
Campaign config lookup:
|
||||
|
||||
- if `--campaign <path>` is provided, that path is used.
|
||||
- if omitted, Narratio searches in order:
|
||||
1. `/usr/local/etc/narratio/campaign.yml`
|
||||
2. `/etc/narratio/campaign.yml`
|
||||
- first existing file wins.
|
||||
- pipeline config is loaded first.
|
||||
- if `--campaign-file <path>` is provided, that path is used.
|
||||
- otherwise, if `--campaign <id>` is provided, Narratio loads:
|
||||
- `{pipeline.campaigns.root}/{id}/campaign.yml`
|
||||
- otherwise, Narratio uses `pipeline.campaigns.default_campaign_id` and loads:
|
||||
- `{pipeline.campaigns.root}/{default_campaign_id}/campaign.yml`
|
||||
- `--campaign` and `--campaign-file` are mutually exclusive.
|
||||
- campaign IDs must be single path segments, not paths.
|
||||
|
||||
Session config lookup:
|
||||
|
||||
@@ -76,7 +80,7 @@ Session config lookup:
|
||||
- if no local session file is found, a positional `<session_id>` is present, storage is configured, and campaign identity is resolved, Narratio loads remote `session.yml` from:
|
||||
- `{root_prefix}/campaigns/{campaign}/sessions/{session_id}/session.yml`
|
||||
- local discovery always runs before remote fallback.
|
||||
- local files in the current working directory are used only when passed explicitly, for example `--config ./pipeline.yml --campaign ./campaign.yml --session ./session.yml`.
|
||||
- local files in the current working directory are used only when passed explicitly, for example `--config ./pipeline.yml --campaign-file ./campaign.yml --session ./session.yml`.
|
||||
|
||||
## 3. Session templating
|
||||
|
||||
@@ -109,6 +113,9 @@ Template behavior for `narratio session init`:
|
||||
### `pipeline.yml`
|
||||
|
||||
```yaml
|
||||
campaigns:
|
||||
root: /usr/local/share/narratio/campaigns
|
||||
default_campaign_id: sample-campaign
|
||||
whisperx:
|
||||
transcribe_url: "https://transcription.example.com/transcribe"
|
||||
```
|
||||
@@ -116,13 +123,14 @@ whisperx:
|
||||
Why this is sufficient:
|
||||
|
||||
- `whisperx.transcribe_url` is required.
|
||||
- `campaigns.default_campaign_id` selects the default campaign when `--campaign` is omitted.
|
||||
- `workspace.root` defaults to `/var/lib/narratio`.
|
||||
- optional sections (`seriatim`, `audita`, `archive`, `scriptorium`, `trim`, `normalize`, etc.) receive defaults or stay inactive.
|
||||
|
||||
### `campaign.yml`
|
||||
|
||||
```yaml
|
||||
campaign: sample-campaign
|
||||
campaign_id: sample-campaign
|
||||
session_template_file: ./session.template.yml
|
||||
inputs:
|
||||
speakers_file: ./speakers.yml
|
||||
@@ -132,7 +140,7 @@ inputs:
|
||||
|
||||
Why this is sufficient:
|
||||
|
||||
- `campaign` supplies the stable campaign identity.
|
||||
- `campaign_id` supplies the stable campaign identity.
|
||||
- stable input files are required and resolve relative to `campaign.yml` when copied during `prepare`.
|
||||
|
||||
### `session.yml`
|
||||
@@ -153,7 +161,8 @@ Why this is sufficient:
|
||||
Minimal local-file usage:
|
||||
|
||||
```bash
|
||||
narratio run 2026-05-03 --config /path/to/pipeline.yml --campaign ./campaign.yml --session ./session.yml
|
||||
narratio run 2026-05-03 --config /path/to/pipeline.yml --campaign sample-campaign --session ./session.yml
|
||||
narratio run 2026-05-03 --config /path/to/pipeline.yml --campaign-file ./campaign.yml --session ./session.yml
|
||||
```
|
||||
|
||||
Previous-session-enabled variant:
|
||||
@@ -166,7 +175,7 @@ inputs:
|
||||
```
|
||||
|
||||
```bash
|
||||
narratio run 2026-05-03 --config /path/to/pipeline.yml --campaign ./campaign.yml --session ./session.yml --previous-session-id 2026-04-26
|
||||
narratio run 2026-05-03 --config /path/to/pipeline.yml --campaign sample-campaign --session ./session.yml --previous-session-id 2026-04-26
|
||||
```
|
||||
|
||||
## 5. Production-oriented config set
|
||||
@@ -187,6 +196,10 @@ storage:
|
||||
access_key_id_env: OBJECT_STORAGE_KEY_ID
|
||||
secret_access_key_env: OBJECT_STORAGE_KEY
|
||||
|
||||
campaigns:
|
||||
root: /srv/narratio/campaigns
|
||||
default_campaign_id: forsaken
|
||||
|
||||
spool:
|
||||
root: /var/spool/narratio
|
||||
delete_audio_after_archive: true
|
||||
@@ -230,7 +243,7 @@ scriptorium:
|
||||
### `campaign.yml`
|
||||
|
||||
```yaml
|
||||
campaign: forsaken
|
||||
campaign_id: forsaken
|
||||
inputs:
|
||||
speakers_file: /srv/narratio/campaigns/forsaken/speakers.yml
|
||||
autocorrect_file: /srv/narratio/campaigns/forsaken/autocorrect.yml
|
||||
@@ -260,7 +273,7 @@ For S3-first operation, upload the same `session.yml` content to:
|
||||
Then run with explicit or discovered pipeline/campaign config and no `--session`:
|
||||
|
||||
```bash
|
||||
narratio run 2026-05-03 --config /usr/local/etc/narratio/pipeline.yml --campaign /usr/local/etc/narratio/campaign.yml --previous-session-id 2026-04-26
|
||||
narratio run 2026-05-03 --config /usr/local/etc/narratio/pipeline.yml --campaign forsaken --previous-session-id 2026-04-26
|
||||
```
|
||||
|
||||
Operational notes:
|
||||
@@ -279,6 +292,8 @@ Operational notes:
|
||||
| --- | --- | --- | --- |
|
||||
| `pipeline.workspace.root` | string | No | `/var/lib/narratio` |
|
||||
| `pipeline.workspace.cleanup_after_archive` | bool | No | `false` |
|
||||
| `pipeline.campaigns.root` | string | No | `/usr/local/share/narratio/campaigns` |
|
||||
| `pipeline.campaigns.default_campaign_id` | string | No | empty |
|
||||
| `pipeline.secrets.env_dir` | string | Conditional | none |
|
||||
| `pipeline.storage.backend` | string | No | empty |
|
||||
| `pipeline.storage.s3.bucket` | string | Conditional | empty |
|
||||
@@ -434,7 +449,7 @@ Restore-related implications:
|
||||
|
||||
| Path | Type | Required | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| `campaign.campaign` | string | Yes | none |
|
||||
| `campaign.campaign_id` | string | Yes | none |
|
||||
| `campaign.session_template_file` | string | No | none |
|
||||
| `campaign.inputs.speakers_file` | string | Yes | none |
|
||||
| `campaign.inputs.autocorrect_file` | string | Yes | none |
|
||||
@@ -448,7 +463,7 @@ Campaign input paths and `campaign.session_template_file` may be absolute or rel
|
||||
| --- | --- | --- | --- |
|
||||
| `session.session_id` | string | Yes | none |
|
||||
| `session.previous_session_id` | string | No | empty |
|
||||
| `session.campaign` | string | No | `campaign.campaign` |
|
||||
| `session.campaign` | string | No | `campaign.campaign_id` |
|
||||
| `session.date` | string | No | empty |
|
||||
| `session.title` | string | No | empty |
|
||||
| `session.inputs.audio_dir` | string | Conditional | empty |
|
||||
@@ -499,8 +514,11 @@ Maintained examples:
|
||||
- `examples/pipeline.minimal.yml`
|
||||
- `examples/pipeline.production.yml`
|
||||
- `examples/pipeline.full.annotated.yml`
|
||||
- `examples/campaign.yml`
|
||||
- `examples/session.template.yml`
|
||||
- `examples/campaigns/sample-campaign/campaign.yml`
|
||||
- `examples/campaigns/sample-campaign/speakers.yml`
|
||||
- `examples/campaigns/sample-campaign/autocorrect.yml`
|
||||
- `examples/campaigns/sample-campaign/glossary.yml`
|
||||
- `examples/campaigns/sample-campaign/session.template.yml`
|
||||
- `examples/session.local-audio.yml`
|
||||
- `examples/session.s3-audio.yml`
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Define the implemented `narratio session restore` contract: committed remote-sta
|
||||
## Inputs and outputs
|
||||
Inputs:
|
||||
- CLI syntax: `narratio session restore <session_id>`.
|
||||
- CLI flags: `--config`, `--campaign`, `--session`, `--previous-session-id`, `--dry-run`, `--force`, `--include-audio`.
|
||||
- CLI flags: `--config`, `--campaign`, `--campaign-file`, `--session`, `--previous-session-id`, `--dry-run`, `--force`, `--include-audio`.
|
||||
- Resolved/validated `pipeline.yml` and `session.yml`.
|
||||
- Configured remote object store.
|
||||
- Remote committed current-state markers (`current/run_id.txt`, `current/manifest.json`).
|
||||
|
||||
@@ -62,7 +62,7 @@ Does not own:
|
||||
- `pipeline.scriptorium.artifacts.<name>.enabled`
|
||||
- `pipeline.scriptorium.artifacts.<name>.inputs.<key>.source`
|
||||
- `pipeline.scriptorium.artifacts.<name>.inputs.<key>.required`
|
||||
- `campaign.campaign`
|
||||
- `campaign.campaign_id`
|
||||
- `campaign.inputs.speakers_file`
|
||||
- `campaign.inputs.autocorrect_file`
|
||||
- `campaign.inputs.glossary_file`
|
||||
|
||||
@@ -19,7 +19,7 @@ narratio run 2026-04-04
|
||||
- use `narratio session status <session_id>` for inspection.
|
||||
|
||||
Notes:
|
||||
- default config/campaign/session discovery checks system config locations unless `--config`, `--campaign`, and `--session` are passed.
|
||||
- default pipeline/session discovery checks system config locations; campaign selection uses `pipeline.campaigns.default_campaign_id` unless `--campaign <id>` or `--campaign-file <path>` is passed.
|
||||
- when local `session.yml` discovery misses, positional `<session_id>` loads remote `session.yml` from `{root_prefix}/campaigns/{campaign}/sessions/{session_id}/session.yml`.
|
||||
- S3 audio mode requires `session.inputs.audio_s3.prefix` and valid object-store access.
|
||||
|
||||
@@ -29,7 +29,7 @@ Initialize a remote session skeleton:
|
||||
narratio session init 2026-04-04 --remote
|
||||
```
|
||||
|
||||
Remote init uses normal default config discovery and writes `{root_prefix}/campaigns/{campaign}/sessions/{session_id}/session.yml`. If `campaign.yml` sets `session_template_file`, init renders that template from the supplied flags and writes concrete YAML. Pass `--config` and `--campaign` when testing non-system config files. It fails if the object already exists unless `--force` is passed.
|
||||
Remote init uses normal default config discovery and writes `{root_prefix}/campaigns/{campaign}/sessions/{session_id}/session.yml`. If `campaign.yml` sets `session_template_file`, init renders that template from the supplied flags and writes concrete YAML. Pass `--config`, `--campaign <id>`, or `--campaign-file <path>` when testing non-system config files. It fails if the object already exists unless `--force` is passed.
|
||||
|
||||
Validate before running:
|
||||
|
||||
|
||||
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>`.
|
||||
@@ -9,20 +9,20 @@ Symptom:
|
||||
- `run`, `resume`, `run-stage`, `session plan`, or `session restore` fails with config/session not found.
|
||||
|
||||
Likely Cause:
|
||||
- `pipeline.yml`, `campaign.yml`, or `session.yml` is missing from system discovery paths.
|
||||
- `pipeline.yml` or `session.yml` is missing from system discovery paths.
|
||||
- the selected campaign ID does not exist under `pipeline.campaigns.root`.
|
||||
- a local working-directory config file was not passed explicitly.
|
||||
|
||||
Diagnostics:
|
||||
|
||||
```bash
|
||||
ls -l /usr/local/etc/narratio/pipeline.yml /etc/narratio/pipeline.yml
|
||||
ls -l /usr/local/etc/narratio/campaign.yml /etc/narratio/campaign.yml
|
||||
ls -l /usr/local/etc/narratio/session.yml /etc/narratio/session.yml
|
||||
```
|
||||
|
||||
Safe Fix:
|
||||
- pass explicit `--config`, `--campaign`, and `--session`.
|
||||
- or place files in documented discovery paths.
|
||||
- pass explicit `--config`, `--campaign <id>`, `--campaign-file <path>`, and `--session` as appropriate.
|
||||
- or place files in documented discovery paths and set `pipeline.campaigns.default_campaign_id`.
|
||||
|
||||
Links:
|
||||
- [docs/config.md](./config.md)
|
||||
@@ -40,7 +40,7 @@ Likely Cause:
|
||||
Diagnostics:
|
||||
|
||||
```bash
|
||||
narratio session plan 2026-04-04 --config /path/to/pipeline.yml --campaign /path/to/campaign.yml --session ./session.yml
|
||||
narratio session plan 2026-04-04 --config /path/to/pipeline.yml --campaign-file /path/to/campaign.yml --session ./session.yml
|
||||
```
|
||||
|
||||
Safe Fix:
|
||||
@@ -62,7 +62,7 @@ Likely Cause:
|
||||
Diagnostics:
|
||||
|
||||
```bash
|
||||
narratio session plan 2026-04-04 --config /path/to/pipeline.yml --campaign /path/to/campaign.yml --session /path/to/session.yml
|
||||
narratio session plan 2026-04-04 --config /path/to/pipeline.yml --campaign-file /path/to/campaign.yml --session /path/to/session.yml
|
||||
```
|
||||
|
||||
Safe Fix:
|
||||
@@ -84,7 +84,7 @@ Likely Cause:
|
||||
Diagnostics:
|
||||
|
||||
```bash
|
||||
narratio run 2026-04-04 --config /path/to/pipeline.yml --campaign /path/to/campaign.yml --session /path/to/session.yml --artifacts player_handout
|
||||
narratio run 2026-04-04 --config /path/to/pipeline.yml --campaign-file /path/to/campaign.yml --session /path/to/session.yml --artifacts player_handout
|
||||
```
|
||||
|
||||
Safe Fix:
|
||||
@@ -106,7 +106,7 @@ Likely Cause:
|
||||
Diagnostics:
|
||||
|
||||
```bash
|
||||
narratio run-stage polish 2026-04-04 --config /path/to/pipeline.yml --campaign /path/to/campaign.yml --session /path/to/session.yml --artifacts session_recap
|
||||
narratio run-stage polish 2026-04-04 --config /path/to/pipeline.yml --campaign-file /path/to/campaign.yml --session /path/to/session.yml --artifacts session_recap
|
||||
```
|
||||
|
||||
Safe Fix:
|
||||
@@ -129,7 +129,7 @@ Likely Cause:
|
||||
Diagnostics:
|
||||
|
||||
```bash
|
||||
narratio session plan 2026-04-04 --config /path/to/pipeline.yml --campaign /path/to/campaign.yml --session /path/to/session.yml
|
||||
narratio session plan 2026-04-04 --config /path/to/pipeline.yml --campaign-file /path/to/campaign.yml --session /path/to/session.yml
|
||||
```
|
||||
|
||||
Safe Fix:
|
||||
@@ -154,7 +154,7 @@ Diagnostics:
|
||||
|
||||
```bash
|
||||
narratio session status 2026-04-04
|
||||
narratio run-stage analyze 2026-04-04 --config /path/to/pipeline.yml --campaign /path/to/campaign.yml --session /path/to/session.yml --artifacts player_handout
|
||||
narratio run-stage analyze 2026-04-04 --config /path/to/pipeline.yml --campaign-file /path/to/campaign.yml --session /path/to/session.yml --artifacts player_handout
|
||||
```
|
||||
|
||||
Safe Fix:
|
||||
@@ -226,7 +226,7 @@ Likely Cause:
|
||||
Diagnostics:
|
||||
|
||||
```bash
|
||||
narratio session restore 2026-04-04 --config /path/to/pipeline.yml --campaign /path/to/campaign.yml --session /path/to/session.yml --dry-run
|
||||
narratio session restore 2026-04-04 --config /path/to/pipeline.yml --campaign-file /path/to/campaign.yml --session /path/to/session.yml --dry-run
|
||||
```
|
||||
|
||||
Safe Fix:
|
||||
@@ -249,7 +249,7 @@ Likely Cause:
|
||||
Diagnostics:
|
||||
|
||||
```bash
|
||||
narratio session restore 2026-04-04 --config /path/to/pipeline.yml --campaign /path/to/campaign.yml --session /path/to/session.yml --dry-run
|
||||
narratio session restore 2026-04-04 --config /path/to/pipeline.yml --campaign-file /path/to/campaign.yml --session /path/to/session.yml --dry-run
|
||||
```
|
||||
|
||||
Safe Fix:
|
||||
@@ -271,7 +271,7 @@ Likely Cause:
|
||||
Diagnostics:
|
||||
|
||||
```bash
|
||||
narratio session restore 2026-04-04 --config /path/to/pipeline.yml --campaign /path/to/campaign.yml --session /path/to/session.yml --dry-run
|
||||
narratio session restore 2026-04-04 --config /path/to/pipeline.yml --campaign-file /path/to/campaign.yml --session /path/to/session.yml --dry-run
|
||||
```
|
||||
|
||||
Safe Fix:
|
||||
@@ -341,7 +341,7 @@ Likely Cause:
|
||||
Diagnostics:
|
||||
|
||||
```bash
|
||||
narratio run-stage prepare 2026-04-04 --config /path/to/pipeline.yml --campaign /path/to/campaign.yml --session /path/to/session.yml
|
||||
narratio run-stage prepare 2026-04-04 --config /path/to/pipeline.yml --campaign-file /path/to/campaign.yml --session /path/to/session.yml
|
||||
```
|
||||
|
||||
Safe Fix:
|
||||
@@ -365,7 +365,7 @@ Diagnostics:
|
||||
|
||||
```bash
|
||||
narratio session status 2026-04-04
|
||||
narratio run-stage archive 2026-04-04 --config /path/to/pipeline.yml --campaign /path/to/campaign.yml --session /path/to/session.yml
|
||||
narratio run-stage archive 2026-04-04 --config /path/to/pipeline.yml --campaign-file /path/to/campaign.yml --session /path/to/session.yml
|
||||
```
|
||||
|
||||
Safe Fix:
|
||||
|
||||
Reference in New Issue
Block a user