The `campaigns/` path segment is intentional. It leaves room for future campaign-level material:
```text
dnd/campaigns/{campaign}/campaign.yml
dnd/campaigns/{campaign}/glossary.yml
dnd/campaigns/{campaign}/characters/
dnd/campaigns/{campaign}/sessions/
```
### 3.2 Session-Level Paths
The session-level root contains the durable, promoted, current view of the session:
```text
audio/
transcripts/
artifacts/
current/
runs/
```
The top-level `audio/` directory is the source of truth for original session audio. Audio is assumed already present in S3 and should not be re-uploaded by `archive` by default.
The top-level `transcripts/` and `artifacts/` directories should contain only configured promoted outputs.
### 3.3 Run-Specific Paths
Each successful run is uploaded under:
```text
runs/{run_id}/
```
This contains the full run record:
- materialized inputs
- intermediate transcripts
- raw WhisperX transcripts
- tool reports
- generated configs
- logs
- generated artifacts
- manifest
Inputs belong under `runs/{run_id}/inputs/`, not at the session root, because inputs are run-specific. A rerun may use different `speakers.yml`, `glossary.yml`, `autocorrect.yml`, `pipeline.resolved.yml`, Scriptorium prompts, trim settings, models, or runtime configuration.
### 3.4 Current Pointer
The effective commit pointer is:
```text
current/run_id.txt
```
This file should be written last during archive.
`current/manifest.json` should also be written during promotion so consumers can inspect the current promoted run without first resolving the run directory.
The archive stage should upload in this order:
1. run record under `runs/{run_id}/`
2. configured promoted outputs under top-level `transcripts/` and `artifacts/`
3.`current/manifest.json`
4.`current/run_id.txt` last
This makes `current/run_id.txt` the closest practical S3 equivalent of an atomic session commit marker.
## 4. Local Filesystem Layout
The production local layout should be:
```text
/var/lib/narratio/
work/
{campaign}/
{session_id}/
{run_id}/
inputs/
audio/
transcripts/
artifacts/
reports/
config/
logs/
manifest.json
.lock
/var/spool/narratio/
{campaign}/
{session_id}/
{run_id}/
audio/
speaker-1.flac
speaker-2.flac
```
For local development, these roots should be configurable. For example:
-`bucket` is required when S3 input/archive is enabled.
-`root_prefix` defaults to `dnd`.
-`region` may be optional depending on SDK behavior.
-`endpoint` is optional for S3-compatible storage.
-`force_path_style` is useful for MinIO/Garage/S3-compatible backends.
- Credentials must not be stored in config. Use standard AWS environment/profile/instance-role mechanisms.
### 6.2 Workspace and Spool Config
Use:
```yaml
workspace:
root: "/var/lib/narratio"
spool:
root: "/var/spool/narratio"
delete_audio_after_archive: false
```
If existing `workspace.root` currently points directly to a work root, the implementation should either preserve the existing semantics or migrate carefully with documentation. The new layout should include campaign/session/run path segments.
### 6.3 Session Config
The session config should include campaign and session ID:
```yaml
session:
id: "2026-04-19"
campaign: "forsaken"
```
If the current config shape uses top-level `session_id`, either migrate to the nested shape with compatibility or maintain the current shape while ensuring both campaign and session ID are available to the path builder.
Existing local audio flow should continue to work unless intentionally changed later.
If both local audio and S3 audio are configured, fail clearly unless a precedence rule is explicitly documented. Prefer requiring exactly one audio input source.
## 11. Archive Stage
The archive stage becomes a real stage.
It should run after `analyze` and before `notify`.
Archive should only upload successful runs.
Since the normal runner is sequential, if any prior stage fails, archive will not run. If the user invokes `run-stage archive` manually, the archive stage should validate prerequisite stages before uploading.
### 11.1 Archive Prerequisites
For v1, require these stages to have succeeded before archive:
```text
prepare
transcribe
merge
polish
normalize
trim
analyze
```
If a stage is optional in a future config, this prerequisite list may become configurable. For now, hardcoded prerequisites are acceptable.
- if `required: true` and the source is missing, archive fails.
- if `required: false` and the source is missing, archive records a skipped promotion.
### 11.4 Commit Pointer
Write these last:
```text
current/manifest.json
current/run_id.txt
```
`current/run_id.txt` should contain exactly the run ID plus a trailing newline.
Writing `current/run_id.txt` last is the effective S3 commit marker.
### 11.5 Failed Runs
Failed runs should not be uploaded to S3.
Failed workdirs should remain local for diagnostics.
The archive stage should never upload a run that does not satisfy its prerequisite success checks.
## 12. Audio Upload Policy
Audio is assumed already present under:
```text
{session_root}/audio/
```
Archive should not re-upload source audio by default.
The archive stage may record audio input provenance in manifest, but should avoid duplicating large FLAC files under `runs/{run_id}/`.
A future option may support uploading local audio into S3, but that is not part of this implementation.
## 13. Promotion Defaults
Default promoted outputs:
```text
transcripts/trimmed.json
artifacts/session_recap.md
```
Always write current pointers:
```text
current/manifest.json
current/run_id.txt
```
Do not promote `session_bounds.json` by default.
Do not promote raw transcripts, logs, tool reports, generated configs, or full intermediate transcript tiers by default. They remain available under `runs/{run_id}/`.
## 14. Testing Strategy
Tests should not require real S3.
Use a fake storage backend for:
- listing audio objects
- downloading objects
- uploading objects
- recording upload order
- simulating missing objects
- simulating upload failures
### 14.1 Config Tests
Test:
- valid S3 storage config
- missing bucket when S3 mode enabled
- default root prefix
- invalid archive promotion rules
- default promotion list
- audio_s3 prefix validation
- local audio config still works
- conflict when both S3 audio and local audio are configured, if that rule is implemented