Implemented multiple campaign support via a campaign directory registry with explicit campaign IDs

This commit is contained in:
2026-05-22 23:01:27 -05:00
parent 7657ec3ad6
commit 9c9cb54339
47 changed files with 775 additions and 329 deletions

View File

@@ -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`